Last active
May 29, 2026 03:59
-
-
Save roylez/ff55a382c21f403bcb1a001640e7c19f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| // ==UserScript== | |
| // @name Salesforce Markdown Converter | |
| // @namespace https://gist.github.com/roylez/ff55a382c21f403bcb1a001640e7c19f | |
| // @version 1.7.0 | |
| // @description Convert Markdown to HTML in Salesforce Rich Text fields | |
| // @author roylez | |
| // @match https://*.salesforce.com/* | |
| // @match https://*.force.com/* | |
| // @grant none | |
| // @require https://cdn.jsdelivr.net/npm/marked@12.0.0/marked.min.js | |
| // @updateURL https://gist.github.com/roylez/ff55a382c21f403bcb1a001640e7c19f/raw/salesforce-md-converter.user.js | |
| // @downloadURL https://gist.github.com/roylez/ff55a382c21f403bcb1a001640e7c19f/raw/salesforce-md-converter.user.js | |
| // @homepageURL https://gist.github.com/roylez/ff55a382c21f403bcb1a001640e7c19f | |
| // @supportURL https://gist.github.com/roylez/ff55a382c21f403bcb1a001640e7c19f | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const BUTTON_ID = 'sf-md-convert-btn'; | |
| const CLEAR_BTN_ID = 'sf-md-clear-btn'; | |
| const VISIBILITY_BTN_ID = 'sf-md-visibility-btn'; | |
| const DATA_ATTR = 'mdConverterAdded'; | |
| let isConverting = false; | |
| function createStyles() { | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| /* Markdown Converter Buttons */ | |
| .${BUTTON_ID}, .${CLEAR_BTN_ID}, .${VISIBILITY_BTN_ID} { | |
| height: 32px; | |
| padding: 0 12px; | |
| font-size: 12px; | |
| font-weight: 400; | |
| border-radius: 16px; | |
| border: 1px solid transparent; | |
| cursor: pointer; | |
| white-space: nowrap; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 6px; | |
| transition: all 0.15s ease; | |
| line-height: 1; | |
| vertical-align: middle; | |
| margin-left: 4px; | |
| } | |
| .${BUTTON_ID} svg, .${CLEAR_BTN_ID} svg, .${VISIBILITY_BTN_ID} svg { | |
| width: 14px; | |
| height: 14px; | |
| flex-shrink: 0; | |
| } | |
| .${BUTTON_ID} span, .${CLEAR_BTN_ID} span, .${VISIBILITY_BTN_ID} span { | |
| font-size: 12px; | |
| font-weight: 600; | |
| } | |
| .${BUTTON_ID} { | |
| background: #0070d2; | |
| color: white; | |
| border-color: #0070d2; | |
| } | |
| .${BUTTON_ID}:hover { | |
| background: #005fb2; | |
| border-color: #005fb2; | |
| } | |
| .${CLEAR_BTN_ID} { | |
| background: #ea001e; | |
| color: white; | |
| border-color: #ea001e; | |
| margin-left: auto; | |
| } | |
| .${CLEAR_BTN_ID}:hover { | |
| background: #c40019; | |
| border-color: #c40019; | |
| } | |
| .${VISIBILITY_BTN_ID} { | |
| background: #32A951; | |
| color: white; | |
| border-color: #32A951; | |
| } | |
| .${VISIBILITY_BTN_ID}:hover { | |
| background: #2B8E44; | |
| border-color: #2B8E44; | |
| } | |
| /* Better paragraph spacing - more compact, higher specificity */ | |
| .slds-rich-text-editor__textarea .ql-editor p, | |
| .ql-editor p { | |
| margin-top: 0.3em !important; | |
| margin-bottom: 0.3em !important; | |
| line-height: 1.4 !important; | |
| } | |
| /* Reduce spacing after headings */ | |
| .slds-rich-text-editor__textarea .ql-editor h1, | |
| .slds-rich-text-editor__textarea .ql-editor h2, | |
| .slds-rich-text-editor__textarea .ql-editor h3, | |
| .slds-rich-text-editor__textarea .ql-editor h4, | |
| .slds-rich-text-editor__textarea .ql-editor h5, | |
| .slds-rich-text-editor__textarea .ql-editor h6, | |
| .ql-editor h1, .ql-editor h2, .ql-editor h3, .ql-editor h4, .ql-editor h5, .ql-editor h6 { | |
| margin-top: 0.5em !important; | |
| margin-bottom: 0.3em !important; | |
| line-height: 1.3 !important; | |
| } | |
| /* Compact lists */ | |
| .slds-rich-text-editor__textarea .ql-editor ul, | |
| .slds-rich-text-editor__textarea .ql-editor ol, | |
| .ql-editor ul, .ql-editor ol { | |
| margin-top: 0.3em !important; | |
| margin-bottom: 0.3em !important; | |
| padding-left: 1.5em !important; | |
| } | |
| .slds-rich-text-editor__textarea .ql-editor li, | |
| .ql-editor li { | |
| margin-top: 0.1em !important; | |
| margin-bottom: 0.1em !important; | |
| } | |
| /* Code blocks - add breathing room but not too much */ | |
| .slds-rich-text-editor__textarea .ql-editor pre, | |
| .ql-editor pre { | |
| margin-top: 0.5em !important; | |
| margin-bottom: 0.5em !important; | |
| } | |
| /* Page Header Improvements */ | |
| .slds-page-header_record-home { | |
| padding: 1rem 1.5rem !important; | |
| background: linear-gradient(to bottom, #f8f9fa, #ffffff) !important; | |
| border-bottom: 1px solid #e5e5e5 !important; | |
| } | |
| .slds-page-header h1 { | |
| font-size: 1.5rem !important; | |
| font-weight: 700 !important; | |
| line-height: 1.4 !important; | |
| color: #181818 !important; | |
| flex: 1 !important; | |
| min-width: 0 !important; | |
| } | |
| .entityNameTitle { | |
| font-size: 0.75rem !important; | |
| color: #706e6b !important; | |
| text-transform: uppercase !important; | |
| letter-spacing: 0.5px !important; | |
| margin-bottom: 0.25rem !important; | |
| } | |
| .slds-page-header__title { | |
| font-size: 1.125rem !important; | |
| line-height: 1.3 !important; | |
| max-width: none !important; | |
| } | |
| .clip-text-slds-plus { | |
| display: -webkit-box !important; | |
| -webkit-line-clamp: 2 !important; | |
| -webkit-box-orient: vertical !important; | |
| overflow: hidden !important; | |
| } | |
| /* Compact Action Buttons - Only in page header, not toolbar */ | |
| .slds-page-header .forceActionsContainer .slds-button, | |
| .slds-page-header .actionsContainer .slds-button { | |
| padding: 0 0.5rem !important; | |
| font-size: 0.75rem !important; | |
| height: 1.75rem !important; | |
| min-width: unset !important; | |
| white-space: nowrap !important; | |
| } | |
| .slds-page-header .forceActionsContainer .slds-button_neutral, | |
| .slds-page-header .actionsContainer .slds-button_neutral { | |
| background: #f3f3f3 !important; | |
| border-color: #d8d8d8 !important; | |
| } | |
| .slds-page-header .forceActionsContainer .slds-button_neutral:hover, | |
| .slds-page-header .actionsContainer .slds-button_neutral:hover { | |
| background: #e8e8e8 !important; | |
| } | |
| /* Hide icons to save space - only in page header */ | |
| .slds-page-header .forceActionsContainer .slds-button .slds-button__icon, | |
| .slds-page-header .actionsContainer .slds-button .slds-button__icon { | |
| display: none !important; | |
| } | |
| .forceActionsContainer .slds-button_icon-border-filled, | |
| .actionsContainer .slds-button_icon-border-filled { | |
| padding: 0.5rem !important; | |
| width: 2rem !important; | |
| height: 2rem !important; | |
| } | |
| /* Hide Follow button */ | |
| [data-target-selection-name="sfdc:StandardButton.Case.Follow"], | |
| [data-target-selection-name="sfdc:StandardButton.*.Follow"] { | |
| display: none !important; | |
| } | |
| /* Hide Sharing Hierarchy dropdown */ | |
| [data-target-reveals="sfdc:StandardButton.Case.RecordShareHierarchy"], | |
| .slds-dropdown-trigger.slds-button_last { | |
| display: none !important; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| function getEditorContent(editor) { | |
| if (editor.value !== undefined) { | |
| return editor.value; | |
| } | |
| return editor.innerText || editor.textContent || ''; | |
| } | |
| function setEditorContent(editor, content) { | |
| if (editor.value !== undefined) { | |
| editor.value = content; | |
| return; | |
| } | |
| const container = editor.closest('.ql-container'); | |
| if (container) { | |
| const quill = container.__quill; | |
| if (quill) { | |
| // Clear first, then paste to replace content | |
| quill.setText(''); | |
| quill.clipboard.dangerouslyPasteHTML(0, content, 'api'); | |
| return; | |
| } | |
| } | |
| editor.innerHTML = content; | |
| } | |
| function convertMarkdown(editor) { | |
| if (isConverting) return; | |
| const markdown = getEditorContent(editor).trim(); | |
| if (!markdown) return; | |
| isConverting = true; | |
| try { | |
| const html = marked.parse(markdown); | |
| // Remove <p> tags from within <li> - Quill doesn't like block elements in lists | |
| let cleanHtml = html.replace(/<li>\s*<p>/gi, '<li>').replace(/<\/p>\s*<\/li>/gi, '</li>'); | |
| // Preserve blank lines - insert <br> between paragraphs with whitespace (blank lines) | |
| cleanHtml = cleanHtml.replace(/<\/p>\s*<p>/gi, '</p><br><p>'); | |
| function formatCodeBlock(lang, code) { | |
| // marked adds \n\n after lines, but intentional blank lines become 3+ newlines | |
| // Use temp marker to preserve intentional blank lines | |
| let normalizedCode = code.replace(/\n\n\n+/g, '\u0000\u0000'); // 3+ newlines -> temp marker | |
| normalizedCode = normalizedCode.replace(/\n\n/g, '\n'); // 2 newlines -> 1 (remove artificial) | |
| normalizedCode = normalizedCode.replace(/\u0000\u0000/g, '\n\n'); // restore blank lines | |
| // Remove leading/trailing blank lines | |
| const trimmedCode = normalizedCode.replace(/^\n+/, '').replace(/\n+$/, ''); | |
| const lines = trimmedCode.split('\n'); | |
| // Build HTML - ALL lines get spans, blank lines included | |
| const linesHtml = lines.map((line, lineIndex) => { | |
| if (line === '') { | |
| // Use <br> for blank lines - no invisible characters | |
| return `<br class="L${lineIndex}"/>`; | |
| } | |
| return `<span class="L${lineIndex}"><span class="pln">${escapeHtml(line)}</span></span>`; | |
| }).join(''); | |
| // data-code - use \n as separator (blank lines are empty strings in array) | |
| const codeForData = lines.join('\n'); | |
| const langAttr = lang ? `class="language-${lang}"` : ''; | |
| return `<pre class="ql-codesnippet quill_widget_wrapper quill_widget_block hasEditEventListener" contenteditable="false" tabindex="-1" data-code="${escapeHtml(codeForData)}"><pre spellcheck="false" data-widget="codeSnippet" class="quill_widget_element"><code ${langAttr}><span class="linenums">${linesHtml}</span></code></pre></pre>`; | |
| } | |
| function formatInlineCode(code) { | |
| // Salesforce inline code styling | |
| return `<code style="background-color: #f6f6f6; padding: 2px 6px; border-radius: 3px; font-family: Consolas, Monaco, monospace; font-size: 0.9em; color: #d73a49; white-space: nowrap;">${escapeHtml(code)}</code>`; | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // Handle code blocks - convert to Salesforce-specific format | |
| cleanHtml = cleanHtml.replace(/<pre><code class="language-([^"]*)">([\s\S]*?)<\/code><\/pre>/gi, (match, lang, code) => formatCodeBlock(lang, code)); | |
| cleanHtml = cleanHtml.replace(/<pre><code>([\s\S]*?)<\/code><\/pre>/gi, (match, code) => formatCodeBlock('', code)); | |
| setEditorContent(editor, cleanHtml); | |
| } finally { | |
| setTimeout(() => { isConverting = false; }, 100); | |
| } | |
| } | |
| function clearEditor(editor) { | |
| // For Quill editors, properly reset the internal state | |
| const container = editor.closest('.ql-container'); | |
| if (container) { | |
| const quill = container.__quill; | |
| if (quill) { | |
| try { | |
| quill.setText('', 'user'); | |
| return; | |
| } catch { | |
| // Fall through | |
| } | |
| } | |
| } | |
| setEditorContent(editor, ''); | |
| } | |
| function createClearButton(editor) { | |
| const btn = document.createElement('button'); | |
| btn.className = CLEAR_BTN_ID; | |
| btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg> <span>Clear</span>`; | |
| btn.type = 'button'; | |
| btn._editor = editor; | |
| btn.setAttribute('aria-label', 'Clear editor content'); | |
| btn.title = 'Clear'; | |
| return btn; | |
| } | |
| function createVisibilityButton(editor) { | |
| const btn = document.createElement('button'); | |
| btn.className = VISIBILITY_BTN_ID; | |
| btn.id = VISIBILITY_BTN_ID + '-' + Date.now(); // Unique ID for finding | |
| btn.type = 'button'; | |
| btn._editor = editor; | |
| btn.setAttribute('aria-label', 'Toggle visibility'); | |
| updateVisibilityButtonText(btn); | |
| return btn; | |
| } | |
| function updateVisibilityButtonText(btn) { | |
| const trigger = document.querySelector('.cuf-visibilityMenu'); | |
| if (!trigger) return; | |
| const currentText = trigger.textContent.trim(); | |
| const isPrivate = currentText.includes('Only') || currentText.includes('Private'); | |
| const visibilityText = isPrivate ? 'Private' : 'Public'; | |
| // Better icons using lock/unlock metaphor | |
| const lockIcon = isPrivate | |
| ? `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>` | |
| : `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/><path d="M10 16V4"/><path d="M20 16V4"/><path d="M15 8l-5 5-5-5"/></svg>`; | |
| btn.innerHTML = `${lockIcon} <span>${visibilityText}</span>`; | |
| btn.title = `Toggle to ${isPrivate ? 'Public' : 'Private'}`; | |
| // Update button color based on state | |
| if (isPrivate) { | |
| btn.style.background = '#008080'; // Cyan | |
| btn.style.borderColor = '#008080'; | |
| btn.style.color = ''; // Default white text | |
| } else { | |
| btn.style.background = '#FF9800'; // Warning color (orange) | |
| btn.style.borderColor = '#FF9800'; | |
| btn.style.color = '#181818'; // Dark text for orange background | |
| } | |
| } | |
| function createConvertButton(editor) { | |
| const btn = document.createElement('button'); | |
| btn.className = BUTTON_ID; | |
| btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> <span>Convert</span>`; | |
| btn.type = 'button'; | |
| btn._editor = editor; | |
| btn.setAttribute('aria-label', 'Convert Markdown to HTML'); | |
| btn.title = 'Convert MD → HTML'; | |
| return btn; | |
| } | |
| function handleButtonClick(e) { | |
| const button = e.target.closest(`.${BUTTON_ID}, .${CLEAR_BTN_ID}, .${VISIBILITY_BTN_ID}`); | |
| if (!button) return; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| if (button.classList.contains(BUTTON_ID)) { | |
| const editor = button._editor; | |
| if (editor) convertMarkdown(editor); | |
| } else if (button.classList.contains(CLEAR_BTN_ID)) { | |
| const editor = button._editor; | |
| if (editor) clearEditor(editor); | |
| } else if (button.classList.contains(VISIBILITY_BTN_ID)) { | |
| toggleVisibility(); | |
| } | |
| } | |
| function updateAllVisibilityButtons() { | |
| const buttons = document.querySelectorAll(`.${VISIBILITY_BTN_ID}`); | |
| buttons.forEach(btn => updateVisibilityButtonText(btn)); | |
| } | |
| function toggleVisibility() { | |
| // Try to find the visibility dropdown and trigger click | |
| const trigger = document.querySelector('.cuf-visibilityMenu'); | |
| if (!trigger) return; | |
| const currentText = trigger.textContent.trim(); | |
| const isPrivate = currentText.includes('Only') || currentText.includes('Private'); | |
| // Get the popup trigger wrapper | |
| const triggerWrapper = trigger.closest('.uiPopupTrigger'); | |
| if (!triggerWrapper) return; | |
| // Click to open menu | |
| trigger.click(); | |
| setTimeout(() => { | |
| // Find the popup target that appears | |
| const dropdowns = document.querySelectorAll('.uiPopupTarget'); | |
| for (const dropdown of dropdowns) { | |
| const html = dropdown.innerHTML; | |
| // Skip the global actions menu | |
| if (html.includes('Global Actions')) continue; | |
| // This should be the visibility dropdown | |
| const listItems = dropdown.querySelectorAll('ul > li > a, ul > li > span'); | |
| for (const item of listItems) { | |
| const itemText = item.textContent.trim(); | |
| if (!itemText || itemText === currentText || itemText.length < 2) continue; | |
| if (isPrivate && (itemText.includes('All') || itemText.includes('People'))) { | |
| item.click(); | |
| setTimeout(() => updateAllVisibilityButtons(), 300); | |
| return; | |
| } | |
| if (!isPrivate && (itemText.includes('Only') || itemText.includes('Private'))) { | |
| item.click(); | |
| setTimeout(() => updateAllVisibilityButtons(), 300); | |
| return; | |
| } | |
| } | |
| } | |
| }, 500); | |
| } | |
| function addButtonToEditor(editor) { | |
| // Re-check: if marked but buttons are gone (SPA recycled DOM), reset | |
| if (editor.dataset[DATA_ATTR]) { | |
| const toolbar = editor.closest('.slds-rich-text-editor, .input-section, .publisherInputContainer') | |
| ?.querySelector('.slds-rich-text-editor__toolbar, [role="toolbar"]'); | |
| if (toolbar && !toolbar.querySelector(`.${BUTTON_ID}`)) { | |
| delete editor.dataset[DATA_ATTR]; | |
| } else { | |
| return; | |
| } | |
| } | |
| // Find the toolbar first (most reliable) | |
| const toolbar = editor.closest('.slds-rich-text-editor, .input-section, .publisherInputContainer') | |
| ?.querySelector('.slds-rich-text-editor__toolbar, [role="toolbar"]'); | |
| if (toolbar) { | |
| toolbar.appendChild(createConvertButton(editor)); | |
| toolbar.appendChild(createVisibilityButton(editor)); | |
| toolbar.appendChild(createClearButton(editor)); | |
| editor.dataset[DATA_ATTR] = 'true'; | |
| // Update button text after a delay when page is fully loaded | |
| setTimeout(() => updateAllVisibilityButtons(), 500); | |
| return; | |
| } | |
| // Fallback: find any container | |
| const container = editor.closest('.slds-rich-text-editor__textarea, .ql-container, [class*="editor"]'); | |
| if (container) { | |
| container.appendChild(createConvertButton(editor)); | |
| container.appendChild(createVisibilityButton(editor)); | |
| container.appendChild(createClearButton(editor)); | |
| } | |
| editor.dataset[DATA_ATTR] = 'true'; | |
| } | |
| function isActualEditor(node) { | |
| if (node.classList.contains('ql-clipboard') || | |
| node.classList.contains('ql-tooltip') || | |
| node.getAttribute('aria-hidden') === 'true') { | |
| return false; | |
| } | |
| return true; | |
| } | |
| function processAddedNode(node) { | |
| if (node.nodeType !== Node.ELEMENT_NODE) return; | |
| if (node.tagName === 'TEXTAREA') { | |
| addButtonToEditor(node); | |
| } | |
| if (node.contentEditable === 'true' || node.getAttribute('contenteditable') === 'true') { | |
| if (isActualEditor(node)) { | |
| addButtonToEditor(node); | |
| } | |
| return; | |
| } | |
| node.querySelectorAll('textarea').forEach(addButtonToEditor); | |
| node.querySelectorAll('[contenteditable="true"]').forEach(el => { | |
| if (isActualEditor(el)) { | |
| addButtonToEditor(el); | |
| } | |
| }); | |
| } | |
| function observeEditors() { | |
| const observer = new MutationObserver((mutations) => { | |
| for (const mutation of mutations) { | |
| if (mutation.type === 'childList') { | |
| for (const node of mutation.addedNodes) { | |
| processAddedNode(node); | |
| } | |
| } else if (mutation.type === 'attributes') { | |
| const node = mutation.target; | |
| if (node.nodeType === Node.ELEMENT_NODE && | |
| node.getAttribute('contenteditable') === 'true' && | |
| isActualEditor(node)) { | |
| addButtonToEditor(node); | |
| } | |
| } | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| attributeFilter: ['contenteditable'] | |
| }); | |
| scanEditors(); | |
| } | |
| function renameActionButtons() { | |
| // Removed - not working reliably | |
| } | |
| function isCasePage() { | |
| return /\/lightning\/r\/Case\//.test(location.pathname); | |
| } | |
| function scanEditors() { | |
| document.querySelectorAll('textarea').forEach(addButtonToEditor); | |
| document.querySelectorAll('[contenteditable="true"]').forEach(el => { | |
| if (isActualEditor(el)) { | |
| addButtonToEditor(el); | |
| } | |
| }); | |
| } | |
| function observeNavigation() { | |
| let lastUrl = location.href; | |
| function onNavChange() { | |
| if (location.href !== lastUrl) { | |
| lastUrl = location.href; | |
| if (isCasePage()) { | |
| setTimeout(scanEditors, 500); | |
| setTimeout(scanEditors, 1500); | |
| setTimeout(scanEditors, 3000); | |
| } | |
| } | |
| } | |
| // Intercept pushState/replaceState (used by most SPAs) | |
| const origPushState = history.pushState; | |
| const origReplaceState = history.replaceState; | |
| history.pushState = function() { | |
| origPushState.apply(this, arguments); | |
| onNavChange(); | |
| }; | |
| history.replaceState = function() { | |
| origReplaceState.apply(this, arguments); | |
| onNavChange(); | |
| }; | |
| // Also listen for popstate (back/forward) | |
| window.addEventListener('popstate', onNavChange); | |
| // Fallback polling for any edge cases | |
| setInterval(onNavChange, 2000); | |
| } | |
| function init() { | |
| createStyles(); | |
| document.addEventListener('click', handleButtonClick, true); | |
| document.addEventListener('mousedown', handleButtonClick, true); | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| observeEditors(); | |
| observeNavigation(); | |
| }); | |
| } else { | |
| observeEditors(); | |
| observeNavigation(); | |
| } | |
| } | |
| init(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment