diff --git a/package-lock.json b/package-lock.json index 85489c970..241e5392f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@codemirror/view": "^6.1.2", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "clipboard": "^2.0.11", "codemirror": "^6.0.1", "dropzone": "^5.9.3", "markdown-it": "^13.0.1", @@ -810,16 +809,6 @@ "node": ">= 8.10.0" } }, - "node_modules/clipboard": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", - "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, "node_modules/cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -912,11 +901,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, "node_modules/dropzone": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", @@ -1218,14 +1202,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", - "dependencies": { - "delegate": "^3.1.2" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2062,11 +2038,6 @@ "node": ">=12.0.0" } }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" - }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -2289,11 +2260,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 9ee57afa5..3579b2d25 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@codemirror/view": "^6.1.2", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "clipboard": "^2.0.11", "codemirror": "^6.0.1", "dropzone": "^5.9.3", "markdown-it": "^13.0.1", diff --git a/readme.md b/readme.md index 28822dd8e..f56b2c2bd 100644 --- a/readme.md +++ b/readme.md @@ -134,7 +134,6 @@ Note: This is not an exhaustive list of all libraries and projects that would be * [Sortable](https://github.com/SortableJS/Sortable) - _[MIT](https://github.com/SortableJS/Sortable/blob/master/LICENSE)_ * [Google Material Icons](https://github.com/google/material-design-icons) - _[Apache-2.0](https://github.com/google/material-design-icons/blob/master/LICENSE)_ * [Dropzone.js](http://www.dropzonejs.com/) - _[MIT](https://github.com/dropzone/dropzone/blob/main/LICENSE)_ -* [clipboard.js](https://clipboardjs.com/) - _[MIT](https://github.com/zenorocha/clipboard.js/blob/master/LICENSE)_ * [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists) - _[MIT](https://github.com/markdown-it/markdown-it/blob/master/LICENSE) and [ISC](https://github.com/revin/markdown-it-task-lists/blob/master/LICENSE)_ * [Dompdf](https://github.com/dompdf/dompdf) - _[LGPL v2.1](https://github.com/dompdf/dompdf/blob/master/LICENSE.LGPL)_ * [BarryVD/Dompdf](https://github.com/barryvdh/laravel-dompdf) - _[MIT](https://github.com/barryvdh/laravel-dompdf/blob/master/LICENSE)_ diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 3fe4a6d86..dbed1c1e6 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,5 +1,5 @@ -import {EditorView, keymap} from "@codemirror/view" -import Clipboard from "clipboard/dist/clipboard.min"; +import {EditorView, keymap} from "@codemirror/view"; +import {copyTextToClipboard} from "../services/clipboard.js" // Modes import {viewer, editor} from "./setups.js"; @@ -57,28 +57,23 @@ function highlightElem(elem) { /** * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. - * @param cmInstance + * @param {EditorView} editorView */ -function addCopyIcon(cmInstance) { - // TODO - // 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); - // }); +function addCopyIcon(editorView) { + const copyIcon = ``; + const copyButton = document.createElement('button'); + copyButton.setAttribute('type', 'button') + copyButton.classList.add('cm-copy-button'); + copyButton.innerHTML = copyIcon; + editorView.dom.appendChild(copyButton); + + copyButton.addEventListener('click', event => { + copyTextToClipboard(editorView.state.doc.toString()); + copyButton.classList.add('success'); + setTimeout(() => { + copyButton.classList.remove('success'); + }, 240); + }); } /** @@ -187,11 +182,7 @@ export function updateLayout(cmInstance) { */ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { const content = elem.textContent; - - // TODO - Change to pass something else that's useful, probably extension array? - // window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config}); - - const ev = createView({ + const config = { parent: elem.parentNode, doc: content, extensions: [ @@ -202,8 +193,13 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { EditorView.domEventHandlers(domEventHandlers), keymap.of(keyBindings), ], - }); + }; + // Emit a pre-event public event to allow tweaking of the configure before view creation. + window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {cmEditorViewConfig: config}); + + // Create editor view, hide original input + const ev = createView(config); elem.style.display = 'none'; return ev; diff --git a/resources/js/code/setups.js b/resources/js/code/setups.js index 00366ee5e..b061bb3fe 100644 --- a/resources/js/code/setups.js +++ b/resources/js/code/setups.js @@ -45,5 +45,6 @@ export function editor(language) { ...historyKeymap, ]), getLanguageExtension(language, ''), + EditorView.lineWrapping, ]; } \ No newline at end of file diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index 922916701..9d687c83c 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -1,4 +1,3 @@ -import {debounce} from "../services/util"; import {Component} from "./component"; import {init as initEditor} from "../markdown/editor"; @@ -45,8 +44,7 @@ export class MarkdownEditor extends Component { window.$events.emitPublic(this.elem, 'editor-markdown::setup', { markdownIt: this.editor.markdown.getRenderer(), displayEl: this.display, - // TODO - change to codeMirrorView? - // codeMirrorInstance: this.editor.cm, + cmEditorView: this.editor.cm, }); } @@ -81,12 +79,6 @@ export class MarkdownEditor extends Component { toolbarLabel.closest('.markdown-editor-wrap').classList.add('active'); }); - // Refresh CodeMirror on container resize - // TODO - // const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false); - // const observer = new ResizeObserver(resizeDebounced); - // observer.observe(this.elem); - this.handleDividerDrag(); } @@ -104,8 +96,6 @@ export class MarkdownEditor extends Component { window.removeEventListener('pointerup', upListener); this.display.style.pointerEvents = null; document.body.style.userSelect = null; - // TODO - // this.editor.cm.refresh(); }; this.display.style.pointerEvents = 'none'; diff --git a/resources/js/components/pointer.js b/resources/js/components/pointer.js index d884dc721..a60525cb4 100644 --- a/resources/js/components/pointer.js +++ b/resources/js/components/pointer.js @@ -1,12 +1,14 @@ import * as DOM from "../services/dom"; -import Clipboard from "clipboard/dist/clipboard.min"; import {Component} from "./component"; +import {copyTextToClipboard} from "../services/clipboard"; export class Pointer extends Component { setup() { this.container = this.$el; + this.input = this.$refs.input; + this.button = this.$refs.button; this.pageId = this.$opts.pageId; // Instance variables @@ -16,15 +18,17 @@ export class Pointer extends Component { this.pointerSectionId = ''; this.setupListeners(); - - // Set up clipboard - new Clipboard(this.container.querySelector('button')); } setupListeners() { + // Copy on copy button click + this.button.addEventListener('click', event => { + copyTextToClipboard(this.input.value); + }); + // Select all contents on input click - DOM.onChildEvent(this.container, 'input', 'click', (event, input) => { - input.select(); + this.input.addEventListener('click', event => { + this.input.select(); event.stopPropagation(); }); @@ -112,7 +116,7 @@ export class Pointer extends Component { inputText = window.location.protocol + "//" + window.location.host + inputText; } - this.container.querySelector('input').value = inputText; + this.input.value = inputText; // Update anchor if present const editAnchor = this.container.querySelector('#pointer-edit'); diff --git a/resources/js/markdown/codemirror.js b/resources/js/markdown/codemirror.js index dbf1925c0..55ea485e3 100644 --- a/resources/js/markdown/codemirror.js +++ b/resources/js/markdown/codemirror.js @@ -64,11 +64,10 @@ export async function init(editor) { domEventHandlers, provideKeyBindings(editor), ); - window.cm = cm; - // Will force to remain as ltr for now due to issues when HTML is in editor. - // TODO - // cm.setOption('direction', 'ltr'); + // Add editor view to window for easy access/debugging. + // Not part of official API/Docs + window.mdEditorView = cm; return cm; } \ No newline at end of file diff --git a/resources/js/services/clipboard.js b/resources/js/services/clipboard.js index da921e515..6e59270a9 100644 --- a/resources/js/services/clipboard.js +++ b/resources/js/services/clipboard.js @@ -1,5 +1,5 @@ -class Clipboard { +export class Clipboard { /** * Constructor @@ -51,4 +51,8 @@ class Clipboard { } } +export function copyTextToClipboard(text) { + return navigator.clipboard.writeText(text); +} + export default Clipboard; \ No newline at end of file diff --git a/resources/sass/_codemirror.scss b/resources/sass/_codemirror.scss index 1dee39cd2..aa3729606 100644 --- a/resources/sass/_codemirror.scss +++ b/resources/sass/_codemirror.scss @@ -450,7 +450,7 @@ html.dark-mode .CodeMirror pre { /** * Custom Copy Button */ -.CodeMirror-copy { +.cm-copy-button { position: absolute; top: -1px; right: -1px; @@ -478,7 +478,7 @@ html.dark-mode .CodeMirror pre { } } } -.CodeMirror:hover .CodeMirror-copy { +.cm-editor:hover .cm-copy-button { user-select: all; opacity: 1; pointer-events: all; diff --git a/resources/views/pages/parts/markdown-editor.blade.php b/resources/views/pages/parts/markdown-editor.blade.php index fd8a20a04..c488f0e11 100644 --- a/resources/views/pages/parts/markdown-editor.blade.php +++ b/resources/views/pages/parts/markdown-editor.blade.php @@ -30,7 +30,7 @@ -
+