diff --git a/README.md b/README.md index 1ef32e74..2ce20346 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ 📖 **Documentation:** https://django-markdown-editor.readthedocs.io -**Martor** is a Markdown Editor plugin for Django, supported for _Bootstrap_ & _Semantic-UI_. +**Martor** is a Markdown Editor plugin for Django, supported for _Bootstrap_, _Semantic-UI_ & _Tailwind CSS_. ### Features * Live Preview * Integrated with [_Ace Editor_](https://ace.c9.io) -* Supported with [_Bootstrap_](https://getbootstrap.com) and [_Semantic-UI_](https://semantic-ui.com) +* Supported with [_Bootstrap_](https://getbootstrap.com), [_Semantic-UI_](https://semantic-ui.com) and [_Tailwind CSS_](https://tailwindcss.com) * Supported Multiple Fields [_fixed this issue_](https://github.com/agusmakmun/django-markdown-editor/issues/3) * Upload Images to imgur.com _(via API)_ and [custom uploader][13] * Direct Mention users `@[username]` - _(requires user to logged in)_. @@ -86,7 +86,7 @@ Please register your application at https://api.imgur.com/oauth2/addclient to get `IMGUR_CLIENT_ID` and `IMGUR_API_KEY`. ```python -# Choices are: "semantic", "bootstrap" +# Choices are: "semantic", "bootstrap", "tailwind" MARTOR_THEME = 'bootstrap' # Global martor settings @@ -335,6 +335,90 @@ Different with *Template Renderer*, the *Template Editor Form* have more css & j ``` +#### Tailwind CSS Theme + +When using `MARTOR_THEME = 'tailwind'`, include the required CSS and JavaScript files: + +**Template Renderer (Tailwind):** + +```html +{% extends "base.html" %} +{% load static %} +{% load martortags %} + +{% block css %} + + + +{% endblock %} + +{% block content %} +
Nothing to preview
'); + } + }, + error: function (response) { + console.log("error", response); + } + }); + }; + + let timeoutID; + + var refreshPreviewTimeout = function () { + if (timeoutID) { + clearTimeout(timeoutID); + } + timeoutID = setTimeout(refreshPreview, textareaId.data("save-timeout")); + } + + // Refresh the preview unconditionally on first load. + window.onload = function () { + refreshPreview(); + }; + + if (editorConfig.living !== 'true') { + previewTabButton.click(function () { + // hide the `.martor-toolbar` for this current editor if under preview. + toolbarButtons.hide(); + refreshPreview(); + }); + } else { + editor.on('change', refreshPreviewTimeout); + } + + // spellcheck + if (editorConfig.spellcheck == 'true') { + try { + enable_spellcheck(editorId); + } catch (e) { + console.log("Spellcheck lib doesn't installed."); + } + } + + if (editorConfig.emoji == 'true') { + langTools = ace.require("ace/ext/language_tools"); + langTools.addCompleter(emojiWordCompleter); + } + + if (editorConfig.mention == 'true') { + langTools = ace.require("ace/ext/language_tools"); + langTools.addCompleter(mentionWordCompleter); + } + + // Handle toolbar dropdowns for Tailwind + $('.martor-toolbar .dropdown').each(function() { + var dropdown = $(this); + var toggle = dropdown.find('.dropdown-toggle'); + var menu = dropdown.find('.dropdown-menu'); + + toggle.click(function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Close other dropdowns + $('.martor-toolbar .dropdown-menu').not(menu).addClass('hidden'); + + // Toggle current dropdown + menu.toggleClass('hidden'); + }); + + // Close dropdown when clicking outside + $(document).click(function(e) { + if (!dropdown.is(e.target) && dropdown.has(e.target).length === 0) { + menu.addClass('hidden'); + } + }); + }); + + // Modal Popup for Help Guide & Emoji Cheat Sheet + $('.markdown-help[data-field-name=' + field_name + ']').click(function (e) { + e.preventDefault(); + console.log('Help button clicked for field:', field_name); + var modalSelector = '.modal-help-guide[data-field-name=' + field_name + ']'; + console.log('Modal selector:', modalSelector); + var modal = $(modalSelector); + console.log('Modal found:', modal.length); + TailwindUtils.showModal(modalSelector); + }); + + // Close modal functionality + $('.modal-help-guide[data-field-name=' + field_name + '] .modal-close').click(function (e) { + e.preventDefault(); + TailwindUtils.hideModal('.modal-help-guide[data-field-name=' + field_name + ']'); + }); + + // Also handle click on modal backdrop to close + $('.modal-help-guide[data-field-name=' + field_name + ']').click(function (e) { + if (e.target === this || $(e.target).hasClass('bg-gray-500')) { + TailwindUtils.hideModal('.modal-help-guide[data-field-name=' + field_name + ']'); + } + }); + + // Toggle editor, preview, maximize + var martorField = $('.martor-field-' + field_name); + var btnToggleMaximize = $('.markdown-toggle-maximize[data-field-name=' + field_name + ']'); + + // Toggle maximize and minimize + var handleToggleMinimize = function () { + $(document.body).removeClass('overflow-hidden'); + $(this).attr({ 'title': 'Full Screen' }); + $(this).find('svg.bi-arrows-angle-expand').removeClass('hidden'); + $(this).find('svg.bi-arrows-angle-contract').addClass('hidden'); + $('.main-martor-fullscreen').find('.martor-preview').removeAttr('style'); + mainMartor.removeClass('main-martor-fullscreen'); + martorField.removeAttr('style'); + editor.resize(); + } + var handleToggleMaximize = function (selector) { + selector.attr({ 'title': 'Minimize' }); + selector.find('svg.bi-arrows-angle-expand').addClass('hidden'); + selector.find('svg.bi-arrows-angle-contract').removeClass('hidden'); + mainMartor.addClass('main-martor-fullscreen'); + + var clientHeight = document.body.clientHeight - 90; + martorField.attr({ 'style': 'height:' + clientHeight + 'px' }); + + var preview = $('.main-martor-fullscreen').find('.martor-preview'); + preview.attr({ 'style': 'overflow-y: auto;height:' + clientHeight + 'px' }); + + editor.resize(); + selector.one('click', handleToggleMinimize); + $(document.body).addClass('overflow-hidden'); + } + btnToggleMaximize.on('click', function () { + handleToggleMaximize($(this)); + }); + + // Exit full screen when `ESC` is pressed. + $(document).keyup(function (e) { + if (e.keyCode == 27 && mainMartor.hasClass('main-martor-fullscreen')) { + btnToggleMaximize.trigger('click'); + } + }); + + // markdown insert emoji from the modal + $('.markdown-emoji[data-field-name=' + field_name + ']').click(function () { + var modalEmoji = $('.modal-emoji[data-field-name=' + field_name + ']'); + TailwindUtils.showModal(modalEmoji); + var emojiList = typeof (emojis) != "undefined" ? emojis : []; // from `plugins/js/emojis.min.js` + var emojiGrid = modalEmoji.find('#emoji-grid-' + field_name); + var loaderInit = modalEmoji.find('.emoji-loader-init'); + + // Only load emojis if grid is empty + if (emojiGrid.children().length === 0) { + // Show loader + TailwindUtils.showElement(loaderInit); + + // Load emojis with timeout to show loading state + setTimeout(function() { + try { + console.log('Loading emojis, count:', emojiList.length); + + for (var i = 0; i < Math.min(emojiList.length, 300); i++) { // Increased to 300 for more emojis + var emoji = emojiList[i]; + var linkEmoji = textareaId.data('base-emoji-url') + emoji.replace(/:/g, '') + '.png'; + + var emojiButton = $(''); + + emojiGrid.append(emojiButton); + } + + // Attach click handlers to dynamically created buttons + modalEmoji.find('.insert-emoji').off('click').on('click', function() { + var emojiTarget = $(this).data('emoji-target'); + console.log('Emoji selected:', emojiTarget); + markdownToEmoji(editor, emojiTarget); + TailwindUtils.hideModal(modalEmoji); + }); + + TailwindUtils.hideElement(loaderInit); + console.log('Emojis loaded successfully'); + } catch (error) { + console.error('Error loading emojis:', error); + TailwindUtils.hideElement(loaderInit); + emojiGrid.html('+ A powerful Django Markdown Editor with Tailwind CSS styling +
+Real-time markdown preview
+Drag & drop image support
+Built-in emoji picker
+Code block highlighting
+@username support
+Tables, lists, links & more
++ See how Martor renders Markdown content with Tailwind CSS styling +
+{{ content }}
+
+# Heading 1
+## Heading 2
+### Heading 3
+
+
+**Bold text**
+*Italic text*
+~~Strikethrough~~
+++Underline++
+`inline code`
+
+ Bold text
+Italic text
+Strikethrough
Underline
+inline code
+* Unordered item 1
+* Unordered item 2
+
+1. Ordered item 1
+2. Ordered item 2
+
+
+[Link text](https://example.com)
+
+
+
+```python
+def hello_world():
+ print("Hello, World!")
+```
+
+ def hello_world():
+ print("Hello, World!")
+
+| Name | Age | City |
+|------|-----|------|
+| John | 30 | NYC |
+| Jane | 25 | LA |
+
+ | Name | +Age | +City | +
|---|---|---|
| John | +30 | +NYC | +
| Jane | +25 | +LA | +
+> This is a blockquote
+> with multiple lines
+>
+> > Nested quote
+
+ + This is a blockquote+
+ with multiple lines ++ Nested quote ++