import $ from 'jquery'; import { config, translations } from 'grav-config'; import request from '../../../utils/request'; let replacer = ({ name, replace, codemirror, button, mode = 'replaceSelections', runner }) => { button.on(`click.editor.${name}`, () => { strategies[mode]({ token: '$1', template: replace, codemirror, runner }); }); }; export let strategies = { replaceSelections({ template, token, codemirror, runner }) { let replacements = []; let ranges = []; let selections = codemirror.getSelections(); let list = codemirror.listSelections(); let accumulator = {}; selections.forEach((selection, index) => { let markup = template.replace(token, selection); let cursor = markup.indexOf('$cur'); let { line, ch } = list[index].anchor; markup = markup.replace('$cur', ''); markup = runner ? runner(selection, markup, list) : markup; replacements.push(markup); if (!accumulator[line]) { accumulator[line] = 0; } ch += accumulator[line] + (cursor === -1 ? markup.length : cursor); let range = { ch, line }; ranges.push({ anchor: range, head: range }); accumulator[line] += markup.length - selection.length; }); codemirror.replaceSelections(replacements); codemirror.setSelections(ranges); codemirror.focus(); }, replaceLine({ template, token, codemirror, runner }) { let list = codemirror.listSelections(); let range; list.forEach((selection) => { let lines = { min: Math.min(selection.anchor.line, selection.head.line), max: Math.max(selection.anchor.line, selection.head.line) }; codemirror.eachLine(lines.min, lines.max + 1, (handler) => { let markup = template.replace(token, handler.text); let line = codemirror.getLineNumber(handler); markup = runner ? runner(handler, markup) : markup; codemirror.replaceRange(markup, { line, ch: 0 }, { line, ch: markup.length }); range = { line, ch: markup.length }; }); }); codemirror.setSelection(range, range, 'end'); codemirror.focus(); }, replaceRange() {} }; const flipDisabled = (codemirror, button, type) => { let hasHistory = codemirror.historySize()[type]; let element = button.find('a'); button[hasHistory ? 'removeClass' : 'addClass']('button-disabled'); if (!hasHistory) { element.attr('title-disabled', element.attr('title')); element.attr('data-hint-disabled', element.attr('data-hint')); element.removeAttr('title').removeAttr('data-hint'); } else { element.attr('title', element.attr('title-disabled')); element.attr('data-hint', element.attr('data-hint-disabled')); element.removeAttr('title-disabled').removeAttr('data-hint-disabled'); } }; export default { navigation: [ { undo: { identifier: 'undo', title: translations.PLUGIN_ADMIN.UNDO, label: '', modes: [], action({ codemirror, button, textarea}) { button.addClass('button-disabled'); codemirror.on('change', () => flipDisabled(codemirror, button, 'undo')); button.on('click.editor.undo', () => { codemirror.undo(); }); } } }, { redo: { identifier: 'redo', title: translations.PLUGIN_ADMIN.REDO, label: '', modes: [], action({ codemirror, button, textarea}) { button.addClass('button-disabled'); codemirror.on('change', () => flipDisabled(codemirror, button, 'redo')); button.on('click.editor.redo', () => { codemirror.redo(); }); } } }, { headers: { identifier: 'headers', title: translations.PLUGIN_ADMIN.HEADERS, label: '', modes: ['gfm', 'markdown'], children: [ { h1: { identifier: 'h1', label: '1', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h1', replace: '# $1', codemirror, button, mode: 'replaceLine' }); } } }, { h2: { identifier: 'h2', label: '2', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h2', replace: '## $1', codemirror, button, mode: 'replaceLine' }); } } }, { h3: { identifier: 'h3', label: '3', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h3', replace: '### $1', codemirror, button, mode: 'replaceLine' }); } } }, { h4: { identifier: 'h4', label: '4', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h4', replace: '#### $1', codemirror, button, mode: 'replaceLine' }); } } }, { h5: { identifier: 'h5', label: '5', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h5', replace: '##### $1', codemirror, button, mode: 'replaceLine' }); } } }, { h6: { identifier: 'h6', label: '6', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'h6', replace: '###### $1', codemirror, button, mode: 'replaceLine' }); } } } ] } }, { bold: { identifier: 'bold', title: translations.PLUGIN_ADMIN.BOLD, label: '', modes: ['gfm', 'markdown'], shortcut: ['Ctrl-B', 'Cmd-B'], action({ codemirror, button, textarea }) { replacer({ name: 'bold', replace: '**$1$cur**', codemirror, button }); } } }, { italic: { identifier: 'italic', title: translations.PLUGIN_ADMIN.ITALIC, label: '', modes: ['gfm', 'markdown'], shortcut: ['Ctrl-I', 'Cmd-I'], action({ codemirror, button, textarea }) { replacer({ name: 'italic', replace: '_$1$cur_', codemirror, button }); } } }, { strike: { identifier: 'strike', title: translations.PLUGIN_ADMIN.STRIKETHROUGH, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'strike', replace: '~~$1$cur~~', codemirror, button }); } } }, { delimiter: { identifier: 'delimiter', title: translations.PLUGIN_ADMIN.SUMMARY_DELIMITER, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'delimiter', replace: `${config.site.delimiter}$1`, codemirror, button, mode: 'replaceLine' }); } } }, { link: { identifier: 'link', title: translations.PLUGIN_ADMIN.LINK, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'link', replace: '[$1](http://$cur)', codemirror, button }); } } }, { image: { identifier: 'image', title: translations.PLUGIN_ADMIN.IMAGE, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'image', replace: '', codemirror, button }); } } }, { blockquote: { identifier: 'blockquote', title: translations.PLUGIN_ADMIN.BLOCKQUOTE, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'blockquote', replace: '> $1', codemirror, button, mode: 'replaceLine' }); } } }, { listUl: { identifier: 'listUl', title: translations.PLUGIN_ADMIN.UNORDERED_LIST, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'listUl', replace: '* $1', codemirror, button, mode: 'replaceLine' }); } } }, { listOl: { identifier: 'listOl', title: translations.PLUGIN_ADMIN.ORDERED_LIST, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea }) { replacer({ name: 'listOl', replace: '. $1', codemirror, button, mode: 'replaceLine', runner: function(line, markup) { let lineNo = codemirror.getLineNumber(line); let previousLine = codemirror.getLine(lineNo - 1) || ''; let match = previousLine.match(/^(\d+)\./); let prefix = 1 + (match ? Number(match[1]) : 0); return `${prefix}${markup}`; } }); } } } ], states: [{ code: { identifier: 'editor', title: translations.PLUGIN_ADMIN.EDITOR, label: '', action({ codemirror, button, textarea, ui }) { if (textarea.data('grav-editor-mode') === 'editor') { button.addClass('editor-active'); } button.on('click.states.editor', () => { button.siblings().removeClass('editor-active'); button.addClass('editor-active'); textarea.data('grav-editor-mode', 'editor'); let previewContainer = textarea.data('grav-editor-preview-container'); let content = textarea.parent('.grav-editor-content'); content.css('display', 'block'); ui.navigation.find('.grav-editor-actions').css('visibility', 'visible'); if (previewContainer) { previewContainer.css('display', 'none'); } }); } } }, { preview: { identifier: 'preview', title: translations.PLUGIN_ADMIN.PREVIEW, label: '', modes: ['gfm', 'markdown'], action({ codemirror, button, textarea, ui }) { if (textarea.data('grav-editor-mode') === 'preview') { button.addClass('editor-active'); } button.on('click.states.preview', () => { let previewContainer = textarea.data('grav-editor-preview-container'); let content = textarea.parent('.grav-editor-content'); button.siblings().removeClass('editor-active'); button.addClass('editor-active'); textarea.data('grav-editor-mode', 'preview'); if (!previewContainer) { previewContainer = $('
'); content.after(previewContainer); textarea.data('grav-editor-preview-container', previewContainer); } previewContainer.css({ height: content.height(), display: 'block' }); content.css('display', 'none'); ui.navigation.find('.grav-editor-actions').css('visibility', 'hidden'); let url = `${textarea.data('grav-urlpreview')}/task${config.param_sep}processmarkdown`; let params = textarea.closest('form').serializeArray(); let body = {}; params.map((obj) => { body[obj.name] = obj.value; }); request(url, { method: 'post', body }, (response) => previewContainer.html(response.preview)); }); } } }, { fullscreen: { identifier: 'fullscreen', title: translations.PLUGIN_ADMIN.FULLSCREEN, label: '', action({ codemirror, button, textarea }) { button.on('click.editor.fullscreen', () => { let container = textarea.closest('.grav-editor'); let wrapper = codemirror.getWrapperElement(); let contentWrapper = $('.content-wrapper'); if (!container.hasClass('grav-editor-fullscreen')) { textarea.data('fullScreenRestore', { scrollTop: global.pageYOffset, scrollLeft: global.pageXOffset, width: wrapper.style.width, height: wrapper.style.height }); wrapper.style.width = ''; wrapper.style.height = textarea.parent('.grav-editor-content').height() + 'px'; global.document.documentElement.style.overflow = 'hidden'; let hints = container.find('.grav-editor-toolbar .hint--top'); if (hints) { hints.removeClass('hint--top').addClass('hint--bottom'); $(hints[hints.length - 1]).addClass('hint--bottom-left'); } if (contentWrapper) { contentWrapper.css('overflow', 'visible'); } } else { global.document.documentElement.style.overflow = ''; let state = textarea.data('fullScreenRestore'); wrapper.style.width = state.width; wrapper.style.height = state.height; global.scrollTo(state.scrollLeft, state.scrollTop); let hints = container.find('.grav-editor-toolbar .hint--bottom'); if (hints) { hints.removeClass('hint--bottom').addClass('hint--top'); $(hints[hints.length - 1]).removeClass('hint--bottom-left'); } if (contentWrapper) { contentWrapper.css('overflow', 'auto'); } } container.toggleClass('grav-editor-fullscreen'); setTimeout(() => { codemirror.refresh(); // this.preview.parent().css('height', this.code.height()); $(global).trigger('resize'); }, 5); }); } } }] };