Last active
December 12, 2023 11:40
-
-
Save foxy4096/ee9e7ed0be82814a9d56af52b149a5c8 to your computer and use it in GitHub Desktop.
Snippets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"); | |
.emojione { | |
width: 30px; | |
padding-left: 6px; | |
display: inline-flex; | |
vertical-align: text-top; | |
} | |
.tabbed-set { | |
position: relative; | |
display: flex; | |
flex-wrap: wrap; | |
margin: 1em 0; | |
border-radius: 0.1rem; | |
} | |
.tabbed-set > input { | |
display: none; | |
} | |
.tabbed-set label { | |
width: auto; | |
padding: 0.9375em 1.25em 0.78125em; | |
font-weight: 700; | |
font-size: 0.84em; | |
white-space: nowrap; | |
border-bottom: 0.15rem solid transparent; | |
border-top-left-radius: 0.1rem; | |
border-top-right-radius: 0.1rem; | |
cursor: pointer; | |
transition: background-color 250ms, color 250ms; | |
} | |
.tabbed-set .tabbed-content { | |
width: 100%; | |
display: none; | |
box-shadow: 0 -0.05rem #ddd; | |
} | |
.tabbed-set input { | |
position: absolute; | |
opacity: 0; | |
} | |
.tabbed-set input:checked:nth-child(n + 1) + label { | |
color: rgb(0, 140, 255); | |
border-color: rgb(0, 140, 255); | |
} | |
@media screen { | |
.tabbed-set input:nth-child(n + 1):checked + label + .tabbed-content { | |
order: 99; | |
display: block; | |
} | |
} | |
@media print { | |
.tabbed-content { | |
display: contents; | |
} | |
} | |
#mdout { | |
background: #0f0f0f; | |
border: 1px #363636 solid; | |
border-top: none; | |
padding: 1em; | |
max-height: 200px; /* Set your desired max height */ | |
overflow-x: auto; | |
word-break: break-all; | |
} | |
#mdout:empty { | |
display: none; | |
} | |
.button, | |
input, | |
textarea, | |
select { | |
border-radius: 0% !important; | |
} | |
.modal-background { | |
background-color: rgba(44, 44, 44, 0.493) !important; | |
} | |
.editor-toolbar { | |
display: flex; | |
flex-wrap: wrap; | |
border: 1px #363636 solid; | |
border-bottom: none; | |
justify-content: flex-start; | |
background-color: #0a0a0a; | |
padding: 10px; | |
} | |
#editor { | |
box-sizing: border-box; | |
border-top: none; | |
box-shadow: none; | |
width: 100% !important; | |
resize: both; | |
} | |
.editor-button { | |
padding: 5px 10px; | |
cursor: pointer; | |
margin-right: 10px; | |
background-color: inherit; | |
color: #fff; | |
border: none; | |
border-radius: 5px; | |
margin-bottom: 5px; | |
font-size: large; | |
} | |
.editor-button:hover { | |
background-color: #3b414d; | |
} | |
#editor:hover { | |
border-top: none !important; | |
box-shadow: none !important; | |
} | |
#mdout > ul { | |
margin-left: 5px !important; | |
} | |
#mdout > ul > li { | |
list-style: disc !important; | |
} | |
#mdout, #editor { | |
width: 100%; /* Allow both elements to expand to their container's width */ | |
box-sizing: border-box; /* Include padding and borders in the width calculation */ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.utils.safestring import mark_safe | |
from django.forms.widgets import Textarea | |
from django.template.loader import render_to_string | |
from apps.core.templatetags.convert_markdown import convert_markdown | |
from apps.account.templatetags.user_mention import user_mention | |
class MarkdownWidget(Textarea): | |
""" | |
Custom widget for a textarea with HTMX attributes for live Markdown conversion. | |
Additionally, it wraps the textarea in a <div> container. | |
Args: | |
hx_vars (str, optional): The value of the `hx-vars` attribute for HTMX configuration. | |
*args: Additional positional arguments passed to the parent class constructor. | |
**kwargs: Additional keyword arguments passed to the parent class constructor. | |
Attributes: | |
hx_vars (str): The value of the `hx-vars` attribute for HTMX configuration. | |
Usage: | |
This widget can be used to render a textarea with HTMX attributes for live Markdown conversion. | |
Example: | |
``` | |
bio_widget = MarkdownWidget( | |
hx_vars="name:'bio'" | |
) | |
``` | |
""" | |
def __init__( | |
self, | |
*args, | |
**kwargs, | |
): | |
super().__init__( | |
*args, | |
**kwargs, | |
) | |
self.attrs.update( | |
{ | |
"hx-target": "#mdout", | |
"hx-trigger": "keyup delay:500ms changed, insertMarkdown", | |
"hx-post": "/convert/md/", | |
"style": "font-family: monospace; width: 650px; height: 200px;", | |
} | |
) | |
def render(self, name, value, attrs=None, renderer=None): | |
""" | |
Render the widget. | |
Args: | |
name (str): The name of the HTML input element. | |
value (str): The current value of the input. | |
attrs (dict, optional): Additional HTML attributes for the input element. | |
renderer (str, optional): The renderer for rendering the widget. | |
Returns: | |
str: The HTML rendering of the widget. | |
""" | |
attrs.update({"hx-vars": f"name:'{name}'", "id": f"id_{name}"}) | |
rendered_textarea = super().render(name, value, attrs) | |
toolbar = render_to_string("widgets/markdown_toolbar.html", {"name": name}) | |
initials = user_mention(convert_markdown(value or "")) | |
div_element = f'<div class="content" id="mdout">{initials}</div>' | |
return mark_safe(f"<div>{toolbar}{rendered_textarea} {div_element}</div>") |
How do I configure it in my project?
Firstly add the htmx and main.css in the static folder and then link them in the html.
Then create a file widgets/markdown_toolbar.html
and it should look like this
<div class="editor-toolbar">
<button class="editor-button" type="button" onclick="insertMarkdown('**', '**')"><i
class="bi bi-type-bold"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('_', '_')"><i
class="bi bi-type-italic"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('## ', '')"><i
class="bi bi-type-h2"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('### ', '')"><i
class="bi bi-type-h3"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('1. ', '')"><i
class="bi bi-list-ol"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('- ', '')"><i
class="bi bi-list-ul"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('> ', '')"><i
class="bi bi-quote"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('`', '`')"><i
class="bi bi-code"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('```\n', '\n```')"><i
class="bi bi-file-earmark-code-fill"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('[Link Text](', ')')"><i
class="bi bi-link-45deg"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('![Alt Text](', ')')"><i
class="bi bi-image"></i></button>
<button class="editor-button" type="button" onclick="insertMarkdown('---', '')"><i
class="bi bi-hr"></i></button>
</div>
<script>
function insertMarkdown(startTag, endTag) {
const editor = document.getElementById('id_{{ name }}');
const selectionStart = editor.selectionStart;
const selectionEnd = editor.selectionEnd;
const selectedText = editor.value.substring(selectionStart, selectionEnd);
const replacement = `${startTag}${selectedText}${endTag}`;
editor.value = editor.value.substring(0, selectionStart) + replacement + editor.value.substring(selectionEnd);
// Set the cursor position between the start and end tags
editor.selectionStart = selectionStart + startTag.length;
editor.selectionEnd = selectionStart + startTag.length + selectedText.length;
editor.focus();
}
</script>
Then in the forms.py
you can use the markdown widget
from django import forms
from .models import Post
from apps.core.widgets import MarkdownWidget
class PostCreationForm(forms.ModelForm):
"""
Form for creating a new post.
"""
class Meta:
model = Post
fields = ["body", "status"]
widgets = {
"body": MarkdownWidget(),
}
For using in the admin panel the admin.py
should look like this
class PostAdmin(admin.ModelAdmin):
# --Snippet--
formfield_overrides = {models.TextField: {"widget": MarkdownWidget()}}
class Media:
css = {
"all": ["css/main.css"],
}
js = ["js/htmx.js"]
Even though instead of adding the static files from ModelAdmin
, I manually overided the admin/base_site.html to add the main.css
and htmx.js
.
Okay, thanks
I'll try this out
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How do I configure it in my project?