import CodeMirror from "codemirror"; import Clipboard from "clipboard/dist/clipboard.min"; // Modes import 'codemirror/mode/css/css'; import 'codemirror/mode/clike/clike'; import 'codemirror/mode/diff/diff'; import 'codemirror/mode/fortran/fortran'; import 'codemirror/mode/go/go'; import 'codemirror/mode/haskell/haskell'; import 'codemirror/mode/htmlmixed/htmlmixed'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/julia/julia'; import 'codemirror/mode/lua/lua'; import 'codemirror/mode/markdown/markdown'; import 'codemirror/mode/mllike/mllike'; import 'codemirror/mode/nginx/nginx'; import 'codemirror/mode/perl/perl'; import 'codemirror/mode/pascal/pascal'; import 'codemirror/mode/php/php'; import 'codemirror/mode/powershell/powershell'; import 'codemirror/mode/properties/properties'; import 'codemirror/mode/python/python'; import 'codemirror/mode/ruby/ruby'; import 'codemirror/mode/rust/rust'; import 'codemirror/mode/shell/shell'; import 'codemirror/mode/sql/sql'; import 'codemirror/mode/toml/toml'; import 'codemirror/mode/vb/vb'; import 'codemirror/mode/vbscript/vbscript'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/yaml/yaml'; // Addons import 'codemirror/addon/scroll/scrollpastend'; // Mapping of possible languages or formats from user input to their codemirror modes. // Value can be a mode string or a function that will receive the code content & return the mode string. // The function option is used in the event the exact mode could be dynamic depending on the code. const modeMap = { css: 'css', c: 'text/x-csrc', java: 'text/x-java', scala: 'text/x-scala', kotlin: 'text/x-kotlin', 'c++': 'text/x-c++src', 'c#': 'text/x-csharp', csharp: 'text/x-csharp', diff: 'diff', for: 'fortran', fortran: 'fortran', go: 'go', haskell: 'haskell', hs: 'haskell', html: 'htmlmixed', ini: 'properties', javascript: 'javascript', json: {name: 'javascript', json: true}, js: 'javascript', jl: 'julia', julia: 'julia', lua: 'lua', md: 'markdown', mdown: 'markdown', markdown: 'markdown', ml: 'mllike', nginx: 'nginx', perl: 'perl', pl: 'perl', powershell: 'powershell', properties: 'properties', ocaml: 'mllike', pascal: 'text/x-pascal', pas: 'text/x-pascal', php: (content) => { return content.includes('/gi ,'\n'); const content = elem.textContent.trimEnd(); let mode = ''; if (innerCodeElem !== null) { const langName = innerCodeElem.className.replace('language-', ''); mode = getMode(langName, content); } const cm = CodeMirror(function(elt) { elem.parentNode.replaceChild(elt, elem); }, { value: content, mode: mode, lineNumbers: true, lineWrapping: false, theme: getTheme(), readOnly: true }); addCopyIcon(cm); } /** * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. * @param cmInstance */ function addCopyIcon(cmInstance) { const copyIcon = ``; const copyButton = document.createElement('div'); copyButton.classList.add('CodeMirror-copy'); copyButton.innerHTML = copyIcon; cmInstance.display.wrapper.appendChild(copyButton); const clipboard = new Clipboard(copyButton, { text: function(trigger) { return cmInstance.getValue() } }); clipboard.on('success', event => { copyButton.classList.add('success'); setTimeout(() => { copyButton.classList.remove('success'); }, 240); }); } /** * Search for a codemirror code based off a user suggestion * @param {String} suggestion * @param {String} content * @returns {string} */ function getMode(suggestion, content) { suggestion = suggestion.trim().replace(/^\./g, '').toLowerCase(); const modeMapType = typeof modeMap[suggestion]; if (modeMapType === 'undefined') { return ''; } if (modeMapType === 'function') { return modeMap[suggestion](content); } return modeMap[suggestion]; } /** * Ge the theme to use for CodeMirror instances. * @returns {*|string} */ function getTheme() { const darkMode = document.documentElement.classList.contains('dark-mode'); return window.codeTheme || (darkMode ? 'darcula' : 'default'); } /** * Create a CodeMirror instance for showing inside the WYSIWYG editor. * Manages a textarea element to hold code content. * @param {HTMLElement} elem * @returns {{wrap: Element, editor: *}} */ export function wysiwygView(elem) { const doc = elem.ownerDocument; const codeElem = elem.querySelector('code'); let lang = getLanguageFromCssClasses(elem.className || ''); if (!lang && codeElem) { lang = getLanguageFromCssClasses(codeElem.className || ''); } elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); const content = elem.textContent; const newWrap = doc.createElement('div'); const newTextArea = doc.createElement('textarea'); newWrap.className = 'CodeMirrorContainer'; newWrap.setAttribute('data-lang', lang); newWrap.setAttribute('dir', 'ltr'); newTextArea.style.display = 'none'; elem.parentNode.replaceChild(newWrap, elem); newWrap.appendChild(newTextArea); newWrap.contentEditable = 'false'; newTextArea.textContent = content; let cm = CodeMirror(function(elt) { newWrap.appendChild(elt); }, { value: content, mode: getMode(lang, content), lineNumbers: true, lineWrapping: false, theme: getTheme(), readOnly: true }); return {wrap: newWrap, editor: cm}; } /** * Get the code language from the given css classes. * @param {String} classes * @return {String} */ function getLanguageFromCssClasses(classes) { const langClasses = classes.split(' ').filter(cssClass => cssClass.startsWith('language-')); return (langClasses[0] || '').replace('language-', ''); } /** * Create a CodeMirror instance to show in the WYSIWYG pop-up editor * @param {HTMLElement} elem * @param {String} modeSuggestion * @returns {*} */ export function popupEditor(elem, modeSuggestion) { const content = elem.textContent; return CodeMirror(function(elt) { elem.parentNode.insertBefore(elt, elem); elem.style.display = 'none'; }, { value: content, mode: getMode(modeSuggestion, content), lineNumbers: true, lineWrapping: false, theme: getTheme() }); } /** * Set the mode of a codemirror instance. * @param cmInstance * @param modeSuggestion */ export function setMode(cmInstance, modeSuggestion, content) { cmInstance.setOption('mode', getMode(modeSuggestion, content)); } /** * Set the content of a cm instance. * @param cmInstance * @param codeContent */ export function setContent(cmInstance, codeContent) { cmInstance.setValue(codeContent); setTimeout(() => { updateLayout(cmInstance); }, 10); } /** * Update the layout (codemirror refresh) of a cm instance. * @param cmInstance */ export function updateLayout(cmInstance) { cmInstance.refresh(); } /** * Get a CodeMirror instance to use for the markdown editor. * @param {HTMLElement} elem * @returns {*} */ export function markdownEditor(elem) { const content = elem.textContent; const config = { value: content, mode: "markdown", lineNumbers: true, lineWrapping: true, theme: getTheme(), scrollPastEnd: true, }; window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config}); return CodeMirror(function (elt) { elem.parentNode.insertBefore(elt, elem); elem.style.display = 'none'; }, config); } /** * Get the 'meta' key dependent on the user's system. * @returns {string} */ export function getMetaKey() { let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; return mac ? "Cmd" : "Ctrl"; }