diff --git a/src/components/TheEditor.vue b/src/components/TheEditor.vue index 75420cad1..1d98c9859 100644 --- a/src/components/TheEditor.vue +++ b/src/components/TheEditor.vue @@ -52,7 +52,8 @@ ref="editor" v-model="sourcecode" :name="filename" - :file-extension="fileExtension"> + :file-extension="fileExtension"> + diff --git a/src/components/inputs/Codemirror.vue b/src/components/inputs/Codemirror.vue index 242072988..73d75f14c 100644 --- a/src/components/inputs/Codemirror.vue +++ b/src/components/inputs/Codemirror.vue @@ -19,6 +19,7 @@ import { gcode } from '@/plugins/StreamParserGcode' import { indentWithTab } from '@codemirror/commands' import { json } from '@codemirror/lang-json' import { css } from '@codemirror/lang-css' +import { linkWidgets } from './CodemirrorLinkWidgets' @Component export default class Codemirror extends Mixins(BaseMixin) { @@ -83,6 +84,7 @@ export default class Codemirror extends Mixins(BaseMixin) { const extensions = [ basicSetup, mainsailTheme, + linkWidgets(), keymap.of([indentWithTab]), EditorView.updateListener.of((update) => { this.content = update.state?.doc.toString() diff --git a/src/components/inputs/CodemirrorLinkWidgets.ts b/src/components/inputs/CodemirrorLinkWidgets.ts new file mode 100644 index 000000000..0e7418ddb --- /dev/null +++ b/src/components/inputs/CodemirrorLinkWidgets.ts @@ -0,0 +1,70 @@ +import { syntaxTree } from '@codemirror/language' +import type { EditorState, Extension, Range } from '@codemirror/state' +import { RangeSet, StateField } from '@codemirror/state' +import type { DecorationSet } from '@codemirror/view' +import { Decoration, EditorView, WidgetType } from '@codemirror/view' + +class LinkWidget extends WidgetType { + readonly linkName + readonly url + + constructor(linkName: string, url: string) { + super() + this.linkName = linkName + this.url = url + } + + eq(LinkWidget: LinkWidget) { + return LinkWidget.url === this.url + } + toDOM() { + let container = document.createElement('a') + container.innerHTML = this.linkName + container.setAttribute('href', this.url) + container.setAttribute('target', '_blank') + container.setAttribute('rel', 'noopener noreferrer') + container.setAttribute('style', 'color:Tomato;text-decoration: none;') + return container + } +} + +export const linkWidgets = (): Extension => { + const linkWidgetDecoration = (linkName: string, url: string) => + Decoration.widget({ + widget: new LinkWidget(linkName, url), + side: -1, + block: true, + }) + + const decorate = (state: EditorState) => { + const widgets: Range[] = [] + widgets.push( + linkWidgetDecoration( + "View 'mcu' documentation", + 'https://www.klipper3d.org/Config_Reference.html#mcu' + ).range(state.doc.lineAt(245).from) + ) + widgets.push( + linkWidgetDecoration( + "View 'printer' documentation", + 'https://www.klipper3d.org/Config_Reference.html#printer' + ).range(state.doc.lineAt(300).from) + ) + return widgets.length > 0 ? RangeSet.of(widgets) : Decoration.none + } + + const linkWidgetsField = StateField.define({ + create(state) { + return decorate(state) + }, + update(images, transaction) { + if (transaction.docChanged) return decorate(transaction.state) + return images.map(transaction.changes) + }, + provide(field) { + return EditorView.decorations.from(field) + }, + }) + + return [linkWidgetsField] +} \ No newline at end of file