]> BookStack Code Mirror - bookstack/commitdiff
Addressed existing cm6 todos
authorDan Brown <redacted>
Fri, 14 Apr 2023 13:08:40 +0000 (14:08 +0100)
committerDan Brown <redacted>
Fri, 14 Apr 2023 13:08:40 +0000 (14:08 +0100)
- Updated clipboard handling
  - Removed old clipboard package for browser-native API.
- Updated codemirror editor events to use new props for new data types.

12 files changed:
package-lock.json
package.json
readme.md
resources/js/code/index.mjs
resources/js/code/setups.js
resources/js/components/markdown-editor.js
resources/js/components/pointer.js
resources/js/markdown/codemirror.js
resources/js/services/clipboard.js
resources/sass/_codemirror.scss
resources/views/pages/parts/markdown-editor.blade.php
resources/views/pages/parts/pointer.blade.php

index 85489c970cf34e59b0cd4c54107d7e9e823d3bc7..241e5392f32efc646b5ac17c04ba30205f68e058 100644 (file)
@@ -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",
         "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",
         "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",
         "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",
         "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",
         "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",
index 9ee57afa5fa5ea9f421bf5d62a0db79271f4377a..3579b2d25eb0a4a809d58fe9537c37527f24251e 100644 (file)
@@ -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",
index 28822dd8ec07abbf8af8513b8c0cdbce925d28a1..f56b2c2bd97853a763cbf85c4437e837f4b6e3f9 100644 (file)
--- 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)_
index 3fe4a6d86bd5107a30ee44388607243a5c9e738b..dbed1c1e6dfd0da1df1c75823a543b8c9d72a8de 100644 (file)
@@ -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 = `<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
-    // 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 = `<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
+    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;
index 00366ee5eaaf599b648fbc0b5ba73ec511f6dfb0..b061bb3fe9fa93e545f72578dfa33fc04ba6413f 100644 (file)
@@ -45,5 +45,6 @@ export function editor(language) {
             ...historyKeymap,
         ]),
         getLanguageExtension(language, ''),
+        EditorView.lineWrapping,
     ];
 }
\ No newline at end of file
index 922916701085851e7a90989cfd3676b45a93b599..9d687c83c1cf4d4c98297cf0f98e49d81b337203 100644 (file)
@@ -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';
index d884dc7214ce3ac71673ac0bddf71f39c2755fee..a60525cb401bb554f0e98bd8a265038449116888 100644 (file)
@@ -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');
index dbf1925c089e18c4979c63082027165e20e02c4e..55ea485e3687e450f99ab26848847689ab461ae9 100644 (file)
@@ -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
index da921e51590d0c67e63b994e4cf1d4a25ae5e772..6e59270a9b2cfea74e9102b8e102bc7ef0025660 100644 (file)
@@ -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
index 1dee39cd2c6bbf047066da1d67346949e62f9123..aa3729606073967d27f93ed74f7e279c7cbece3e 100644 (file)
@@ -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;
index fd8a20a0484f68722ad026f6dcf1245187b5fdb4..c488f0e115c8b9eb49120735fd898583440f1866 100644 (file)
@@ -30,7 +30,7 @@
             </div>
         </div>
 
-        <div markdown-input class="flex flex-fill">
+        <div class="flex flex-fill" dir="ltr">
             <textarea id="markdown-editor-input"
                       refs="markdown-editor@input"
                       @if($errors->has('markdown')) class="text-neg" @endif
index b8fe62fa45cee2317d9d759b89e693d8e41aeda7..5bafa6e153fffea70da598c60e53b212e382c064 100644 (file)
@@ -5,8 +5,8 @@
     <div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
         <span class="icon mr-xxs">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
         <div class="input-group inline block">
-            <input readonly="readonly" type="text" id="pointer-url" placeholder="url">
-            <button class="button outline icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
+            <input refs="pointer@input" readonly="readonly" type="text" id="pointer-url" placeholder="url">
+            <button refs="pointer@button" class="button outline icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
         </div>
         @if(userCan('page-update', $page))
             <a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
Morty Proxy This is a proxified and sanitized view of the page, visit original site.