You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
easy-markdown-editor/src/ts/newmde.ts

517 lines
18 KiB
TypeScript

import * as cm from 'codemirror';
import 'codemirror/addon/display/fullscreen';
import * as hljs from 'highlight.js';
import * as marked from 'marked';
interface IRenderingConfig {
markedOptions?: any;
singleLineBreaks?: any;
codeSyntaxHighlighting?: any;
hljs?: hljs.HLJSStatic;
}
interface IOptions {
autoDownloadFontAwesome?: boolean | undefined;
renderingConfig?: IRenderingConfig | undefined;
}
interface IToolBarButtonOptions {
action?: any;
icon: string;
name: string;
title: string;
}
module.exports = class NewMDE {
private static verifyAndReturnElement(element?: HTMLElement): HTMLTextAreaElement {
if (!element) {
throw new Error('SimpleMDE: Parameter "element" is null.');
}
if (!(element instanceof HTMLTextAreaElement)) {
throw new Error('SimpleMDE: Parameter "element" must be a TextArea.');
}
return element;
}
private element: HTMLTextAreaElement;
private options: IOptions;
private codemirror: cm.EditorFromTextArea;
private toolBar: HTMLDivElement;
private rendered: boolean;
private fullScreenActive = false;
private sideBySideActive = false;
constructor(element?: HTMLElement, options: IOptions = {}) {
this.element = NewMDE.verifyAndReturnElement(element);
this.rendered = !!this.element.getAttribute('rendered');
this.options = options;
this.codemirror = this.render();
this.toolBar = this.createToolBar();
}
private markdown(text: string): string {
console.log('Markdown!');
// Initialize with default options
let markedOptions: marked.MarkedOptions = {
breaks: true,
};
if (this.options.renderingConfig) {
if (this.options.renderingConfig.markedOptions !== undefined) {
markedOptions = this.options.renderingConfig.markedOptions;
}
if (this.options.renderingConfig.singleLineBreaks !== undefined) {
markedOptions.breaks = this.options.renderingConfig.singleLineBreaks;
}
if (this.options.renderingConfig.codeSyntaxHighlighting !== undefined) {
markedOptions.highlight = ((code: string) => hljs.highlightAuto(code).value);
}
}
marked.setOptions(markedOptions);
console.log(marked(text));
return marked(text);
}
private render(): cm.EditorFromTextArea {
if (this.rendered) {
throw new Error(`SimpleMDE: Element with ID "${this.element.id}" is already a SimpleMDE instance.`);
// Already rendered.
// return;
}
// var keyMaps = {};
//
// for (var key in options.shortcuts) {
// // null stands for "do not bind this command"
// if (options.shortcuts[key] !== null && bindings[key] !== null) {
// (function (key) {
// keyMaps[fixShortcut(options.shortcuts[key])] = function () {
// bindings[key](self);
// };
// })(key);
// }
// }
//
// keyMaps['Enter'] = 'newlineAndIndentContinueMarkdownList';
// keyMaps['Tab'] = 'tabAndIndentMarkdownList';
// keyMaps['Shift-Tab'] = 'shiftTabAndUnindentMarkdownList';
// keyMaps['Esc'] = function (cm) {
// if (cm.getOption('fullScreen')) toggleFullScreen(self);
// };
const codemirror = cm.fromTextArea(this.element, {
// lineWrapping: (options.lineWrapping === false) ? false : true,
// allowDropFileTypes: ['text/plain'],
// indentWithTabs: (options.indentWithTabs === false) ? false : true,
lineNumbers: false,
// tabSize: (options.tabSize != undefined) ? options.tabSize : 2,
// indentUnit: (options.tabSize != undefined) ? options.tabSize : 2,
mode: {
gitHubSpice: false,
name: 'gfm',
},
// autofocus: (options.autofocus === true) ? true : false,
// extraKeys: keyMaps,
placeholder: 'This is a thing!',
// backdrop: null,
theme: 'paper',
// styleSelectedText: (options.styleSelectedText != undefined) ? options.styleSelectedText : !isMobile(),
});
codemirror.getScrollerElement().style.minHeight = '300px';
this.createSideBySide(codemirror);
this.element.setAttribute('rendered', 'true');
return codemirror;
}
private createSideBySide(codemirror: cm.EditorFromTextArea) {
const wrapper = codemirror.getWrapperElement();
let preview = wrapper.nextElementSibling;
if (!preview || !preview.classList.contains('editor-preview-side')) {
preview = document.createElement('div');
preview.classList.add('editor-preview-side');
if (wrapper.parentNode) {
wrapper.parentNode.insertBefore(preview, wrapper.nextSibling);
} else {
throw new Error('Wrapper has no parent node!');
}
}
if (!preview) {
throw new Error('Cannot find preview element!');
}
let cScroll = false;
let pScroll = false;
// Syncs scroll editor -> preview
cm.on(codemirror, 'scroll', (v: cm.EditorFromTextArea) => {
preview = preview as HTMLDivElement;
if (cScroll) {
cScroll = false;
return;
}
pScroll = true;
const height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
const ratio = parseFloat(v.getScrollInfo().top) / height;
const move = (preview.scrollHeight - preview.clientHeight) * ratio;
preview.scrollTop = move;
});
(preview as HTMLDivElement).onscroll = () => {
preview = preview as HTMLDivElement;
if (pScroll) {
pScroll = false;
return;
}
cScroll = true;
const height = preview.scrollHeight - preview.clientHeight;
const ratio = preview.scrollTop / height;
const move = (this.codemirror.getScrollInfo().height - this.codemirror.getScrollInfo().clientHeight) * ratio;
this.codemirror.scrollTo(0, move);
};
return preview;
}
// SimpleMDE.prototype.createSideBySide = function () {
// var cm = this.codemirror;
// var wrapper = cm.getWrapperElement();
// var preview = wrapper.nextSibling;
//
// if (!preview || !/editor-preview-side/.test(preview.className)) {
// preview = document.createElement('div');
// preview.className = 'editor-preview-side';
// wrapper.parentNode.insertBefore(preview, wrapper.nextSibling);
// }
//
// if (this.options.syncSideBySidePreviewScroll === false) return preview;
// // Syncs scroll editor -> preview
// var cScroll = false;
// var pScroll = false;
// cm.on('scroll', function (v) {
// if (cScroll) {
// cScroll = false;
// return;
// }
// pScroll = true;
// var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
// var ratio = parseFloat(v.getScrollInfo().top) / height;
// var move = (preview.scrollHeight - preview.clientHeight) * ratio;
// preview.scrollTop = move;
// });
//
// // Syncs scroll preview -> editor
// preview.onscroll = function () {
// if (pScroll) {
// pScroll = false;
// return;
// }
// cScroll = true;
// var height = preview.scrollHeight - preview.clientHeight;
// var ratio = parseFloat(preview.scrollTop) / height;
// var move = (cm.getScrollInfo().height - cm.getScrollInfo().clientHeight) * ratio;
// cm.scrollTo(0, move);
// };
// return preview;
// };
private static toggleSideBySide(editor: NewMDE) {
console.log('s-by-s');
const codeMirrorInstance = editor.codemirror;
const wrapper = codeMirrorInstance.getWrapperElement();
const preview = wrapper.nextElementSibling;
if (!preview) {
throw new Error('Could not find element to render preview to!');
}
preview.classList.toggle('editor-preview-active-side');
editor.toggleToolBarButtonActive(editor.toolBar.getElementsByClassName('side-by-side')[0]);
wrapper.classList.toggle('CodeMirror-sided');
const func = () => preview.innerHTML = editor.markdown(codeMirrorInstance.getValue());
cm.on(codeMirrorInstance, 'update', func);
// preview.innerHTML = editor.
if (!editor.fullScreenActive) {
// Side-by-side can only be shown in fullscreen.
NewMDE.toggleFullScreen(editor);
}
editor.sideBySideActive = !editor.sideBySideActive;
}
// function toggleSideBySide(editor) {
// var cm = editor.codemirror;
// var wrapper = cm.getWrapperElement();
// var preview = wrapper.nextSibling;
// var toolbarButton = editor.toolbarElements['side-by-side'];
// var useSideBySideListener = false;
// if (/editor-preview-active-side/.test(preview.className)) {
// preview.className = preview.className.replace(
// /\s*editor-preview-active-side\s*/g, ''
// );
// toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, '');
// wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, ' ');
// } else {
// // When the preview button is clicked for the first time,
// // give some time for the transition from editor.css to fire and the view to slide from right to left,
// // instead of just appearing.
// setTimeout(function () {
// if (!cm.getOption('fullScreen'))
// toggleFullScreen(editor);
// preview.className += ' editor-preview-active-side';
// }, 1);
// toolbarButton.className += ' active';
// wrapper.className += ' CodeMirror-sided';
// useSideBySideListener = true;
// }
//
// // Hide normal preview if active
// var previewNormal = wrapper.lastChild;
// if (/editor-preview-active/.test(previewNormal.className)) {
// previewNormal.className = previewNormal.className.replace(
// /\s*editor-preview-active\s*/g, ''
// );
// var toolbar = editor.toolbarElements.preview;
// var toolbar_div = wrapper.previousSibling;
// toolbar.className = toolbar.className.replace(/\s*active\s*/g, '');
// toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, '');
// }
//
// var sideBySideRenderingFunction = function () {
// preview.innerHTML = editor.options.previewRender(editor.value(), preview);
// };
//
// if (!cm.sideBySideRenderingFunction) {
// cm.sideBySideRenderingFunction = sideBySideRenderingFunction;
// }
//
// if (useSideBySideListener) {
// preview.innerHTML = editor.options.previewRender(editor.value(), preview);
// cm.on('update', cm.sideBySideRenderingFunction);
// } else {
// cm.off('update', cm.sideBySideRenderingFunction);
// }
//
// // Refresh to fix selection being off (#309)
// cm.refresh();
// }
public static toggleBold(_editor: NewMDE) {
console.log('Bold!');
// _toggleBlock(editor, 'bold', editor.options.blockStyles.bold);
}
private static toggleFullScreen(editor: NewMDE) {
editor.toolBar.classList.toggle('fullscreen');
editor.codemirror.getWrapperElement().classList.toggle('CodeMirror-fullscreen');
editor.toggleToolBarButtonActive(editor.toolBar.getElementsByClassName('fullscreen')[0]);
if (editor.fullScreenActive && editor.sideBySideActive) {
NewMDE.toggleSideBySide(editor);
}
editor.fullScreenActive = !editor.fullScreenActive;
}
private toggleToolBarButtonActive(buttonElement: Element | undefined) {
if (buttonElement) {
buttonElement.classList.toggle('active');
}
}
private togglePreview(_editor: NewMDE) {
console.log('Preview!');
console.log(this);
}
private undo(editor: NewMDE) {
editor.codemirror.execCommand('undo');
}
private redo(editor: NewMDE) {
editor.codemirror.execCommand('redo');
}
// IconsSet ?
private createToolBar(): HTMLDivElement {
const defaultToolBarLayout: IToolBarButtonOptions[][] = [
[{
action: NewMDE.toggleBold,
icon: 'fa fa-bold',
name: 'bold',
title: 'Bold',
}, {
// action: toggleItalic,
icon: 'fa fa-italic',
name: 'italic',
title: 'Italic',
}, {
// action: toggleStrikethrough,
icon: 'fa fa-strikethrough',
name: 'strikethrough',
title: 'Strikethrough',
}, {
// action: toggleHeadingSmaller,
icon: 'fa fa-header fa-heading',
name: 'heading',
title: 'Heading',
}], [{
// action: toggleCodeBlock,
icon: 'fa fa-code',
name: 'code',
title: 'Code',
}, {
// action: toggleBlockquote,
icon: 'fa fa-quote-left',
name: 'quote',
title: 'Quote',
}, {
// action: toggleUnorderedList,
icon: 'fa fa-list-ul',
name: 'unordered-list',
title: 'Generic List',
}, {
// action: toggleOrderedList,
icon: 'fa fa-list-ol',
name: 'ordered-list',
title: 'Numbered List',
}, {
// action: cleanBlock,
icon: 'fa fa-eraser fa-clean-block',
name: 'clean-block',
title: 'Clean block',
}], [{
// action: drawLink,
icon: 'fa fa-link',
name: 'link',
title: 'Create Link',
}, {
// action: drawImage,
icon: 'fa fa-image',
name: 'image',
title: 'Insert Image',
}, {
// action: drawHorizontalRule,
icon: 'fa fa-minus',
name: 'horizontal-rule',
title: 'Insert Horizontal Line',
}], [{
action: this.togglePreview,
icon: 'fa fa-eye',
name: 'preview',
// noDisable: true,
// noMobile: true,
title: 'Toggle Preview',
}, {
action: NewMDE.toggleSideBySide,
icon: 'fa fa-columns',
name: 'side-by-side',
// noDisable: true,
// noMobile: true,
title: 'Toggle Side by Side',
}, {
action: NewMDE.toggleFullScreen,
icon: 'fa fa-arrows-alt',
name: 'fullscreen',
// noDisable: true,
// noMobile: true,
title: 'Toggle Fullscreen',
}], [{
action: 'https://simplemde.com/markdown-guide',
icon: 'fa fa-question-circle',
name: 'guide',
// noDisable: true,
title: 'Markdown Guide',
}], [{
action: this.undo,
icon: 'fa fa-undo',
name: 'undo',
// noDisable: true,
title: 'Undo',
}, {
action: this.redo,
icon: 'fa fa-repeat',
name: 'redo',
// noDisable: true,
title: 'Redo',
}],
];
const toolBar = document.createElement('div');
toolBar.className = 'editor-toolbar';
for (const toolBarButtonSection of defaultToolBarLayout) {
const toolBarSection: HTMLElement[] = [];
for (const toolBarButtonOptions of toolBarButtonSection) {
toolBarSection.push(this.createToolBarButton(toolBarButtonOptions));
}
// Create a separator if this is not the last toolbar section.
if (defaultToolBarLayout.indexOf(toolBarButtonSection) !== (defaultToolBarLayout.length - 1)) {
toolBarSection.push(this.createToolBarSeparator());
}
for (const toolBarEntry of toolBarSection) {
toolBar.appendChild(toolBarEntry);
}
}
// Add the toolbar to the editor.
const cmWrapper = this.codemirror.getWrapperElement();
if (cmWrapper.parentNode) {
cmWrapper.parentNode.insertBefore(toolBar, cmWrapper);
}
return toolBar;
}
private createToolBarButton(toolBarButtonOptions: IToolBarButtonOptions): HTMLButtonElement {
const buttonElement: HTMLButtonElement = document.createElement('button');
buttonElement.tabIndex = -1;
buttonElement.classList.add(toolBarButtonOptions.name);
// Set the button tooltip.
buttonElement.title = toolBarButtonOptions.title;
// Set the button onclick action.
if (typeof toolBarButtonOptions.action === 'function') {
buttonElement.onclick = () => toolBarButtonOptions.action(this);
// buttonElement.addEventListener()
} else if (typeof toolBarButtonOptions.action === 'string') {
buttonElement.onclick = () => window.open(toolBarButtonOptions.action);
}
// Set the button icon.
const buttonIcon = document.createElement('i');
buttonIcon.className = toolBarButtonOptions.icon;
buttonElement.appendChild(buttonIcon);
return buttonElement;
}
private createToolBarSeparator() {
const separatorElement = document.createElement('span');
separatorElement.className = 'separator';
separatorElement.innerHTML = '|';
return separatorElement;
}
};