Spaces:
Runtime error
Runtime error
| /// <reference path="../types/typedefs.js" /> | |
| import { app } from '../../scripts/app.js' | |
| import * as shared from './comfy_shared.js' | |
| import { infoLogger, successLogger, errorLogger } from './comfy_shared.js' | |
| import { | |
| DEFAULT_CSS, | |
| DEFAULT_HTML, | |
| DEFAULT_MD, | |
| DEFAULT_MODE, | |
| DEFAULT_THEME, | |
| THEMES, | |
| CSS_RESET, | |
| DEMO_CONTENT, | |
| } from './note_plus.constants.js' | |
| import { LocalStorageManager } from './comfy_shared.js' | |
| const storage = new LocalStorageManager('mtb') | |
| /** | |
| * Uses `@mtb/markdown-parser` (a fork of marked) | |
| * It is statically stored to avoid having | |
| * more than 1 instance ever. | |
| * The size difference between both libraries... | |
| * โญโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฎ | |
| * โ # โ name โ size โ | |
| * โโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโค | |
| * โ 0 โ web-dist/mtb_markdown_plus.mjs โ 1.2 MB โ <- with shiki | |
| * โ 1 โ web-dist/mtb_markdown.mjs โ 44.7 KB โ | |
| * โฐโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโฏ | |
| */ | |
| let useShiki = storage.get('np-use-shiki', false) | |
| const makeResizable = (dialog) => { | |
| dialog.style.resize = 'both' | |
| dialog.style.transformOrigin = 'top left' | |
| dialog.style.overflow = 'auto' | |
| } | |
| const makeDraggable = (dialog, handle) => { | |
| let offsetX = 0 | |
| let offsetY = 0 | |
| let isDragging = false | |
| const onMouseMove = (e) => { | |
| if (isDragging) { | |
| dialog.style.left = `${e.clientX - offsetX}px` | |
| dialog.style.top = `${e.clientY - offsetY}px` | |
| } | |
| } | |
| const onMouseUp = () => { | |
| isDragging = false | |
| document.removeEventListener('mousemove', onMouseMove) | |
| document.removeEventListener('mouseup', onMouseUp) | |
| } | |
| handle.addEventListener('mousedown', (e) => { | |
| isDragging = true | |
| offsetX = e.clientX - dialog.offsetLeft | |
| offsetY = e.clientY - dialog.offsetTop | |
| document.addEventListener('mousemove', onMouseMove) | |
| document.addEventListener('mouseup', onMouseUp) | |
| }) | |
| } | |
| /** @extends {LGraphNode} */ | |
| class NotePlus extends LiteGraph.LGraphNode { | |
| // same values as the comfy note | |
| color = LGraphCanvas.node_colors.yellow.color | |
| bgcolor = LGraphCanvas.node_colors.yellow.bgcolor | |
| groupcolor = LGraphCanvas.node_colors.yellow.groupcolor | |
| /* NOTE: this is not serialized and only there to make multiple | |
| * note+ nodes in the same graph unique. | |
| */ | |
| uuid | |
| /** Stores the dialog observer*/ | |
| resizeObserver | |
| /** Live update the preview*/ | |
| live = true | |
| /** DOM height by adding child size together*/ | |
| calculated_height = 0 | |
| /** ????*/ | |
| _raw_html | |
| /** might not be needed anymore */ | |
| inner | |
| /** the dialog DOM widget*/ | |
| dialog | |
| /** widgets*/ | |
| /** used to store the raw value and display the parsed html at the same time*/ | |
| html_widget | |
| /** hidden widgets for serialization*/ | |
| css_widget | |
| edit_mode_widget | |
| theme_widget | |
| editorsContainer | |
| /** ACE editors instances*/ | |
| html_editor | |
| css_editor | |
| constructor() { | |
| super() | |
| this.uuid = shared.makeUUID() | |
| infoLogger('Constructing Note+ instance') | |
| shared.ensureMarkdownParser((_p) => { | |
| this.updateHTML() | |
| }) | |
| // - litegraph settings | |
| this.collapsable = true | |
| this.isVirtualNode = true | |
| this.shape = LiteGraph.BOX_SHAPE | |
| this.serialize_widgets = true | |
| // - default values, serialization is done through widgets | |
| this._raw_html = DEFAULT_MODE === 'html' ? DEFAULT_HTML : DEFAULT_MD | |
| // - state | |
| this.live = true | |
| this.calculated_height = 0 | |
| // - add widgets | |
| const cinner = document.createElement('div') | |
| this.inner = document.createElement('div') | |
| cinner.append(this.inner) | |
| this.inner.classList.add('note-plus-preview') | |
| cinner.style.margin = '0' | |
| cinner.style.padding = '0' | |
| this.html_widget = this.addDOMWidget('HTML', 'html', cinner, { | |
| setValue: (val) => { | |
| this._raw_html = val | |
| }, | |
| getValue: () => this._raw_html, | |
| getMinHeight: () => this.calculated_height, // (the edit button), | |
| onDraw: () => { | |
| // HACK: dirty hack for now until it's addressed upstream... | |
| this.html_widget.element.style.pointerEvents = 'none' | |
| // NOTE: not sure about this, it avoid the visual "bugs" but scrolling over the wrong area will affect zoom... | |
| // this.html_widget.element.style.overflow = 'scroll' | |
| }, | |
| hideOnZoom: false, | |
| }) | |
| this.setupSerializationWidgets() | |
| this.setupDialog() | |
| this.loadAceEditor() | |
| } | |
| /** | |
| * @param {CanvasRenderingContext2D} ctx canvas context | |
| * @param {any} _graphcanvas | |
| */ | |
| onDrawForeground(ctx, _graphcanvas) { | |
| if (this.flags.collapsed) return | |
| this.drawEditIcon(ctx) | |
| this.drawSideHandle(ctx) | |
| // DEBUG BACKGROUND | |
| // ctx.fillStyle = 'rgba(0, 255, 0, 0.3)' | |
| // const rect = this.rect | |
| // ctx.fillRect(rect.x, rect.y, rect.width, rect.height) | |
| } | |
| drawSideHandle(ctx) { | |
| const handleRect = this.sideHandleRect | |
| const chamfer = 20 | |
| ctx.beginPath() | |
| // top left | |
| ctx.moveTo(handleRect.x, handleRect.y + chamfer) | |
| // top right | |
| ctx.lineTo(handleRect.x + handleRect.width, handleRect.y) | |
| // bottom right | |
| ctx.lineTo( | |
| handleRect.x + handleRect.width, | |
| handleRect.y + handleRect.height, | |
| ) | |
| // bottom left | |
| ctx.lineTo(handleRect.x, handleRect.y + handleRect.height - chamfer) | |
| ctx.closePath() | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.05)' | |
| ctx.fill() | |
| } | |
| drawEditIcon(ctx) { | |
| const rect = this.iconRect | |
| // DEBUG ICON POSITION | |
| // ctx.fillStyle = 'rgba(0, 255, 0, 0.3)' | |
| // ctx.fillRect(rect.x, rect.y, rect.width, rect.height) | |
| const pencilPath = new Path2D( | |
| 'M21.28 6.4l-9.54 9.54c-.95.95-3.77 1.39-4.4.76-.63-.63-.2-3.45.75-4.4l9.55-9.55a2.58 2.58 0 1 1 3.64 3.65z', | |
| ) | |
| const folderPath = new Path2D( | |
| 'M11 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h11c2.21 0 3-1.8 3-4v-5', | |
| ) | |
| ctx.save() | |
| ctx.translate(rect.x, rect.y) | |
| ctx.scale(rect.width / 32, rect.height / 32) | |
| ctx.strokeStyle = 'rgba(255,255,255,0.4)' | |
| ctx.lineCap = 'round' | |
| ctx.lineJoin = 'round' | |
| ctx.lineWidth = 2.4 | |
| ctx.stroke(pencilPath) | |
| ctx.stroke(folderPath) | |
| ctx.restore() | |
| } | |
| /** | |
| * @param {number} x | |
| * @param {number} y | |
| * @param {{x:number,y:number,width:number,height:number}} rect | |
| * @returns {} | |
| */ | |
| inRect(x, y, rect) { | |
| rect = rect || this.iconRect | |
| return ( | |
| x >= rect.x && | |
| x <= rect.x + rect.width && | |
| y >= rect.y && | |
| y <= rect.y + rect.height | |
| ) | |
| } | |
| get rect() { | |
| return { | |
| x: 0, | |
| y: 0, | |
| width: this.size[0], | |
| height: this.size[1], | |
| } | |
| } | |
| get sideHandleRect() { | |
| const w = this.size[0] | |
| const h = this.size[1] | |
| const bw = 32 | |
| const bho = 64 | |
| return { | |
| x: w - bw, | |
| y: bho, | |
| width: bw, | |
| height: h - bho * 1.5, | |
| } | |
| } | |
| get iconRect() { | |
| const iconSize = 32 | |
| const iconMargin = 16 | |
| return { | |
| x: this.size[0] - iconSize - iconMargin, | |
| y: iconMargin * 1.5, | |
| width: iconSize, | |
| height: iconSize, | |
| } | |
| } | |
| onMouseDown(_e, localPos, _graphcanvas) { | |
| if (this.inRect(localPos[0], localPos[1])) { | |
| this.openEditorDialog() | |
| return true | |
| } | |
| return false | |
| } | |
| /* Hidden widgets to store note+ settings in the workflow (stripped in API)*/ | |
| setupSerializationWidgets() { | |
| infoLogger('Setup Serializing widgets') | |
| this.edit_mode_widget = this.addWidget( | |
| 'combo', | |
| 'Mode', | |
| DEFAULT_MODE, | |
| (me) => successLogger('Updating edit_mode', me), | |
| { | |
| values: ['html', 'markdown', 'raw'], | |
| }, | |
| ) | |
| this.css_widget = this.addWidget('text', 'CSS', DEFAULT_CSS, (val) => { | |
| successLogger(`Updating css ${val}`) | |
| }) | |
| this.theme_widget = this.addWidget( | |
| 'text', | |
| 'Theme', | |
| DEFAULT_THEME, | |
| (val) => { | |
| successLogger(`Setting theme ${val}`) | |
| }, | |
| ) | |
| shared.hideWidgetForGood(this, this.edit_mode_widget) | |
| shared.hideWidgetForGood(this, this.css_widget) | |
| shared.hideWidgetForGood(this, this.theme_widget) | |
| } | |
| setupDialog() { | |
| infoLogger('Setup dialog') | |
| this.dialog = new app.ui.dialog.constructor() | |
| this.dialog.element.classList.add('comfy-settings') | |
| Object.assign(this.dialog.element.style, { | |
| position: 'absolute', | |
| boxShadow: 'none', | |
| }) | |
| const subcontainer = this.dialog.textElement.parentElement | |
| if (subcontainer) { | |
| Object.assign(subcontainer.style, { | |
| width: '100%', | |
| }) | |
| } | |
| const closeButton = this.dialog.element.querySelector('button') | |
| closeButton.textContent = 'CANCEL' | |
| closeButton.id = 'cancel-editor-dialog' | |
| closeButton.title = | |
| "Cancel the changes since last opened (doesn't support live mode)" | |
| closeButton.disabled = this.live | |
| closeButton.style.background = this.live | |
| ? 'repeating-linear-gradient(45deg,#606dbc,#606dbc 10px,#465298 10px,#465298 20px)' | |
| : '' | |
| const saveButton = document.createElement('button') | |
| saveButton.textContent = 'SAVE' | |
| saveButton.onclick = () => { | |
| this.closeEditorDialog(true) | |
| } | |
| closeButton.onclick = () => { | |
| this.closeEditorDialog(false) | |
| } | |
| closeButton.before(saveButton) | |
| } | |
| teardownEditors() { | |
| this.css_editor.destroy() | |
| this.css_editor.container.remove() | |
| this.html_editor.destroy() | |
| this.html_editor.container.remove() | |
| } | |
| closeEditorDialog(accept) { | |
| infoLogger('Closing editor dialog', accept) | |
| if (accept && !this.live) { | |
| this.updateHTML(this.html_editor.getValue()) | |
| this.updateCSS(this.css_editor.getValue()) | |
| } | |
| if (this.resizeObserver) { | |
| this.resizeObserver.disconnect() | |
| this.resizeObserver = null | |
| } | |
| this.teardownEditors() | |
| this.dialog.close() | |
| } | |
| /** | |
| * @param {HTMLElement} elem | |
| */ | |
| hookResize(elem) { | |
| if (!this.resizeObserver) { | |
| const observer = () => { | |
| this.html_editor.resize() | |
| this.css_editor.resize() | |
| Object.assign(this.editorsContainer.style, { | |
| minHeight: `${(this.dialog.element.clientHeight / 100) * 50}px`, //'200px', | |
| }) | |
| } | |
| this.resizeObserver = new ResizeObserver(observer).observe(elem) | |
| } | |
| } | |
| openEditorDialog() { | |
| infoLogger(`Current edit mode ${this.edit_mode_widget.value}`) | |
| this.hookResize(this.dialog.element) | |
| const container = document.createElement('div') | |
| Object.assign(container.style, { | |
| display: 'flex', | |
| gap: '10px', | |
| flexDirection: 'column', | |
| }) | |
| this.editorsContainer = document.createElement('div') | |
| Object.assign(this.editorsContainer.style, { | |
| display: 'flex', | |
| gap: '10px', | |
| flexDirection: 'row', | |
| minHeight: this.dialog.element.offsetHeight, //'200px', | |
| width: '100%', | |
| }) | |
| container.append(this.editorsContainer) | |
| this.dialog.show('') | |
| this.dialog.textElement.append(container) | |
| const aceHTML = document.createElement('div') | |
| aceHTML.id = 'noteplus-html-editor' | |
| Object.assign(aceHTML.style, { | |
| width: '100%', | |
| height: '100%', | |
| minWidth: '300px', | |
| minHeight: 'inherit', | |
| }) | |
| this.editorsContainer.append(aceHTML) | |
| const aceCSS = document.createElement('div') | |
| aceCSS.id = 'noteplus-css-editor' | |
| Object.assign(aceCSS.style, { | |
| width: '100%', | |
| height: '100%', | |
| minHeight: 'inherit', | |
| }) | |
| this.editorsContainer.append(aceCSS) | |
| const live_edit = document.createElement('input') | |
| live_edit.type = 'checkbox' | |
| live_edit.checked = this.live | |
| live_edit.onchange = () => { | |
| this.live = live_edit.checked | |
| const cancel_button = this.dialog.element.querySelector( | |
| '#cancel-editor-dialog', | |
| ) | |
| if (cancel_button) { | |
| cancel_button.disabled = this.live | |
| cancel_button.style.background = this.live | |
| ? 'repeating-linear-gradient(45deg,#606dbc,#606dbc 10px,#465298 10px,#465298 20px)' | |
| : '' | |
| } | |
| } | |
| //- "Dynamic" elements | |
| const firstButton = this.dialog.element.querySelector('button') | |
| const syncUI = () => { | |
| let convert_to_html = | |
| this.dialog.element.querySelector('#convert-to-html') | |
| if (this.edit_mode_widget.value === 'markdown') { | |
| if (convert_to_html == null) { | |
| convert_to_html = document.createElement('button') | |
| convert_to_html.textContent = 'Convert to HTML (NO UNDO!)' | |
| convert_to_html.id = 'convert-to-html' | |
| convert_to_html.onclick = () => { | |
| const select_mode = this.dialog.element.querySelector('#edit_mode') | |
| const md = this.html_editor.getValue() | |
| this.edit_mode_widget.value = 'html' | |
| select_mode.value = 'html' | |
| MTB.mdParser.parse(md).then((content) => { | |
| this.html_widget.value = content | |
| this.html_editor.setValue(content) | |
| this.html_editor.session.setMode('ace/mode/html') | |
| this.updateHTML(this.html_widget.value) | |
| convert_to_html.remove() | |
| }) | |
| } | |
| firstButton.before(convert_to_html) | |
| } | |
| } else { | |
| if (convert_to_html != null) { | |
| convert_to_html.remove() | |
| convert_to_html = null | |
| } | |
| } | |
| select_mode.value = this.edit_mode_widget.value | |
| // the header for dragging the dialog | |
| const header = document.createElement('div') | |
| header.style.padding = '8px' | |
| header.style.cursor = 'move' | |
| header.style.backgroundColor = 'rgba(0,0,0,0.5)' | |
| header.style.userSelect = 'none' | |
| header.style.borderBottom = '1px solid #ddd' | |
| header.textContent = 'MTB Note+ Editor' | |
| container.prepend(header) | |
| makeDraggable(this.dialog.element, header) | |
| makeResizable(this.dialog.element) | |
| } | |
| //- combobox | |
| let theme_select = this.dialog.element.querySelector('#theme_select') | |
| if (!theme_select) { | |
| infoLogger('Creating combobox for select') | |
| theme_select = document.createElement('select') | |
| theme_select.name = 'theme' | |
| theme_select.id = 'theme_select' | |
| const addOption = (label) => { | |
| const option = document.createElement('option') | |
| option.value = label | |
| option.textContent = label | |
| theme_select.append(option) | |
| } | |
| for (const t of THEMES) { | |
| addOption(t) | |
| } | |
| theme_select.addEventListener('change', (event) => { | |
| const val = event.target.value | |
| this.setTheme(val) | |
| }) | |
| container.prepend(theme_select) | |
| } | |
| theme_select.value = this.theme_widget.value | |
| let select_mode = this.dialog.element.querySelector('#edit_mode') | |
| if (!select_mode) { | |
| infoLogger('Creating combobox for select') | |
| select_mode = document.createElement('select') | |
| select_mode.name = 'mode' | |
| select_mode.id = 'edit_mode' | |
| const addOption = (label) => { | |
| const option = document.createElement('option') | |
| option.value = label | |
| option.textContent = label | |
| select_mode.append(option) | |
| } | |
| addOption('markdown') | |
| addOption('html') | |
| select_mode.addEventListener('change', (event) => { | |
| const val = event.target.value | |
| this.edit_mode_widget.value = val | |
| if (this.html_editor) { | |
| this.html_editor.session.setMode(`ace/mode/${val}`) | |
| this.updateHTML(this.html_editor.getValue()) | |
| syncUI() | |
| } | |
| }) | |
| container.append(select_mode) | |
| } | |
| select_mode.value = this.edit_mode_widget.value | |
| syncUI() | |
| const live_edit_label = document.createElement('label') | |
| live_edit_label.textContent = 'Live Edit' | |
| // add a tooltip | |
| live_edit_label.title = | |
| 'When this is on, the editor will update the note+ whenever you change the text.' | |
| live_edit_label.append(live_edit) | |
| // select_mode.before(live_edit_label) | |
| container.append(live_edit_label) | |
| this.setupEditors() | |
| } | |
| loadAceEditor() { | |
| shared.loadScript('/mtb_async/ace/ace.js').catch((e) => { | |
| errorLogger(e) | |
| }) | |
| } | |
| onCreate() { | |
| errorLogger('NotePlus onCreate') | |
| } | |
| restoreNodeState(info) { | |
| this.html_widget.element.id = `note-plus-${this.uuid}` | |
| this.setMode(this.edit_mode_widget.value) | |
| this.setTheme(this.theme_widget.value) | |
| this.updateHTML(this.html_widget.value) | |
| this.updateCSS(this.css_widget.value) | |
| if (info?.size) { | |
| this.setSize(info.size) | |
| } | |
| } | |
| configure(info) { | |
| super.configure(info) | |
| infoLogger('Restoring serialized values', info) | |
| this.restoreNodeState(info) | |
| // - update view from serialzed data | |
| } | |
| onNodeCreated() { | |
| infoLogger('Node created', this.uuid) | |
| this.restoreNodeState({}) | |
| // this.html_widget.element.id = `note-plus-${this.uuid}` | |
| // this.setMode(this.edit_mode_widget.value) | |
| // this.setTheme(this.theme_widget.value) | |
| // this.updateHTML(this.html_widget.value) // widget is populated here since we called super | |
| // this.updateCSS(this.css_widget.value) | |
| } | |
| onRemoved() { | |
| infoLogger('Node removed', this.uuid) | |
| } | |
| getExtraMenuOptions() { | |
| const currentMode = this.edit_mode_widget.value | |
| const newMode = currentMode === 'html' ? 'markdown' : 'html' | |
| const debugItems = window.MTB?.DEBUG | |
| ? [ | |
| { | |
| content: 'Replace with demo content (debug)', | |
| callback: () => { | |
| this.html_widget.value = DEMO_CONTENT | |
| }, | |
| }, | |
| ] | |
| : [] | |
| return [ | |
| ...debugItems, | |
| { | |
| content: `Set to ${newMode}`, | |
| callback: () => { | |
| this.edit_mode_widget.value = newMode | |
| this.updateHTML(this.html_widget.value) | |
| }, | |
| }, | |
| ] | |
| } | |
| _setupEditor(editor) { | |
| this.setTheme(this.theme_widget.value) | |
| editor.setShowPrintMargin(false) | |
| editor.session.setUseWrapMode(true) | |
| editor.renderer.setShowGutter(false) | |
| editor.session.setTabSize(4) | |
| editor.session.setUseSoftTabs(true) | |
| editor.setFontSize(14) | |
| editor.setReadOnly(false) | |
| editor.setHighlightActiveLine(false) | |
| editor.setShowFoldWidgets(true) | |
| return editor | |
| } | |
| setTheme(theme) { | |
| this.theme_widget.value = theme | |
| if (this.html_editor) { | |
| this.html_editor.setTheme(`ace/theme/${theme}`) | |
| } | |
| if (this.css_editor) { | |
| this.css_editor.setTheme(`ace/theme/${theme}`) | |
| } | |
| } | |
| setMode(mode) { | |
| this.edit_mode_widget.value = mode | |
| if (this.html_editor) { | |
| this.html_editor.session.setMode(`ace/mode/${mode}`) | |
| } | |
| this.updateHTML(this.html_widget.value) | |
| } | |
| setupEditors() { | |
| infoLogger('NotePlus setupEditor') | |
| this.html_editor = ace.edit('noteplus-html-editor') | |
| this.css_editor = ace.edit('noteplus-css-editor') | |
| this.css_editor.session.setMode('ace/mode/css') | |
| this.setMode(DEFAULT_MODE) | |
| this._setupEditor(this.html_editor) | |
| this._setupEditor(this.css_editor) | |
| this.css_editor.session.on('change', (_delta) => { | |
| // delta.start, delta.end, delta.lines, delta.action | |
| if (this.live) { | |
| this.updateCSS(this.css_editor.getValue()) | |
| } | |
| }) | |
| this.html_editor.session.on('change', (_delta) => { | |
| // delta.start, delta.end, delta.lines, delta.action | |
| if (this.live) { | |
| this.updateHTML(this.html_editor.getValue()) | |
| } | |
| }) | |
| this.html_editor.setValue(this.html_widget.value) | |
| this.css_editor.setValue(this.css_widget.value) | |
| } | |
| scopeCss(css, scopeId) { | |
| return css | |
| .split('}') | |
| .map((rule) => { | |
| if (rule.trim() === '') { | |
| return '' | |
| } | |
| const scopedRule = rule | |
| .split('{') | |
| .map((segment, index) => { | |
| if (index === 0) { | |
| return `#${scopeId} ${segment.trim()}` | |
| } | |
| return `{${segment.trim()}` | |
| }) | |
| .join(' ') | |
| return `${scopedRule}}` | |
| }) | |
| .join('\n') | |
| } | |
| getCssDom() { | |
| const styleTagId = `note-plus-stylesheet-${this.uuid}` | |
| let styleTag = document.head.querySelector(`#${styleTagId}`) | |
| if (!styleTag) { | |
| styleTag = document.createElement('style') | |
| styleTag.type = 'text/css' | |
| styleTag.id = styleTagId | |
| document.head.appendChild(styleTag) | |
| infoLogger(`Creating note-plus-stylesheet-${this.uuid}`, styleTag) | |
| } | |
| return styleTag | |
| } | |
| calculateHeight() { | |
| this.calculated_height = shared.calculateTotalChildrenHeight( | |
| this.html_widget.element, | |
| ) | |
| this.setDirtyCanvas(true, true) | |
| } | |
| updateCSS(css) { | |
| infoLogger('NotePlus updateCSS') | |
| // this.html_widget.element.style = css | |
| const scopedCss = this.scopeCss( | |
| `${CSS_RESET}\n${css}`, | |
| `note-plus-${this.uuid}`, | |
| ) | |
| const cssDom = this.getCssDom() | |
| cssDom.innerHTML = scopedCss | |
| this.css_widget.value = css | |
| this.calculateHeight() | |
| infoLogger('NotePlus updateCSS', this.calculated_height) | |
| // this.setSize(this.computeSize()) | |
| } | |
| parserInitiated() { | |
| if (window.MTB?.mdParser) return true | |
| return false | |
| } | |
| /** to easilty swap purification methods*/ | |
| purify(content) { | |
| return DOMPurify.sanitize(content, { | |
| ADD_TAGS: ['iframe', 'detail', 'summary'], | |
| }) | |
| } | |
| updateHTML(val) { | |
| if (!this.parserInitiated()) { | |
| return | |
| } | |
| val = val || this.html_widget.value | |
| const isHTML = this.edit_mode_widget.value === 'html' | |
| const cleanHTML = this.purify(val) | |
| const value = isHTML | |
| ? cleanHTML | |
| : cleanHTML.replaceAll('>', '>').replaceAll('<', '<') | |
| // .replaceAll('&', '&') | |
| // .replaceAll('"', '"') | |
| // .replaceAll(''', "'") | |
| this.html_widget.value = value | |
| if (isHTML) { | |
| this.inner.innerHTML = value | |
| } else { | |
| MTB.mdParser.parse(value).then((e) => { | |
| this.inner.innerHTML = e | |
| }) | |
| } | |
| // this.html_widget.element.innerHTML = `<div id="note-plus-spacer"></div>${value}` | |
| this.calculateHeight() | |
| // this.setSize(this.computeSize()) | |
| } | |
| } | |
| app.registerExtension({ | |
| name: 'mtb.noteplus', | |
| setup: () => { | |
| app.ui.settings.addSetting({ | |
| id: 'mtb.noteplus.use-shiki', | |
| category: ['mtb', 'Note+', 'use-shiki'], | |
| name: 'Use shiki to highlight code', | |
| tooltip: | |
| 'This will load a larger version of @mtb/markdown-parser that bundles shiki, it supports all shiki transformers (supported langs: html,css,python,markdown)', | |
| type: 'boolean', | |
| defaultValue: false, | |
| attrs: { | |
| style: { | |
| // fontFamily: 'monospace', | |
| }, | |
| }, | |
| async onChange(value) { | |
| storage.set('np-use-shiki', value) | |
| useShiki = value | |
| }, | |
| }) | |
| }, | |
| registerCustomNodes() { | |
| LiteGraph.registerNodeType('Note Plus (mtb)', NotePlus) | |
| NotePlus.category = 'mtb/utils' | |
| NotePlus.title = 'Note+ (mtb)' | |
| NotePlus.title_mode = LiteGraph.NO_TITLE | |
| }, | |
| }) | |