Compare commits
5
Commits
v0.3.0
...
fix/note-plus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d576190f07 | ||
|
|
247ad12ec3 | ||
|
|
881573e2f2 | ||
|
|
6f3a5b5c71 | ||
|
|
fa017fbb81 |
+28
-19
@@ -1,22 +1,31 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.6.1/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.6.1/schema.json",
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true
|
"recommended": true,
|
||||||
}
|
"suspicious": {
|
||||||
},
|
"noConsoleLog": "warn"
|
||||||
"formatter": {
|
},
|
||||||
"lineEnding": "lf"
|
"style": {
|
||||||
},
|
"noParameterAssign": "off",
|
||||||
"javascript": {
|
"noShoutyConstants": "warn",
|
||||||
"formatter": {
|
"useNamingConvention": "off"
|
||||||
"quoteStyle": "single",
|
}
|
||||||
"semicolons": "asNeeded",
|
|
||||||
"indentWidth": 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineEnding": "lf"
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"semicolons": "asNeeded"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+156
-87
@@ -11,6 +11,7 @@
|
|||||||
/// <reference path="../types/typedefs.js" />
|
/// <reference path="../types/typedefs.js" />
|
||||||
|
|
||||||
import { app } from '../../scripts/app.js'
|
import { app } from '../../scripts/app.js'
|
||||||
|
import { api } from '../../scripts/api.js'
|
||||||
|
|
||||||
// #region base utils
|
// #region base utils
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ import { app } from '../../scripts/app.js'
|
|||||||
export function makeUUID() {
|
export function makeUUID() {
|
||||||
let dt = new Date().getTime()
|
let dt = new Date().getTime()
|
||||||
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
const r = (dt + Math.random() * 16) % 16 | 0
|
const r = ((dt + Math.random() * 16) % 16) | 0
|
||||||
dt = Math.floor(dt / 16)
|
dt = Math.floor(dt / 16)
|
||||||
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
|
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
|
||||||
})
|
})
|
||||||
@@ -358,15 +359,17 @@ export function getWidgetType(config) {
|
|||||||
|
|
||||||
// #region dynamic connections
|
// #region dynamic connections
|
||||||
/**
|
/**
|
||||||
* @param {NodeType} nodeType
|
* @param {NodeType} nodeType The nodetype to attach the documentation to
|
||||||
* @param {str} prefix
|
* @param {str} prefix A prefix added to each dynamic inputs
|
||||||
* @param {str | [str]} inputType
|
* @param {str | [str]} inputType The datatype(s) of those dynamic inputs
|
||||||
* @param {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} opts
|
* @param {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} opts
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const setupDynamicConnections = (nodeType, prefix, inputType, opts) => {
|
export const setupDynamicConnections = (nodeType, prefix, inputType, opts) => {
|
||||||
infoLogger('Setting up dynamic connections for', nodeType)
|
infoLogger(
|
||||||
|
'Setting up dynamic connections for',
|
||||||
|
Object.getOwnPropertyDescriptors(nodeType).title.value,
|
||||||
|
)
|
||||||
|
|
||||||
/** @type {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}} */
|
/** @type {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}} */
|
||||||
const options = opts || {}
|
const options = opts || {}
|
||||||
@@ -736,10 +739,84 @@ const create_documentation_stylesheet = () => {
|
|||||||
document.head.appendChild(styleTag)
|
document.head.appendChild(styleTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let documentationConverter
|
let parserPromise
|
||||||
|
const callbackQueue = []
|
||||||
|
|
||||||
|
function runQueuedCallbacks() {
|
||||||
|
while (callbackQueue.length) {
|
||||||
|
const cb = callbackQueue.shift()
|
||||||
|
cb(window.MTB.mdParser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadParser(shiki) {
|
||||||
|
if (!parserPromise) {
|
||||||
|
parserPromise = import(
|
||||||
|
shiki
|
||||||
|
? '/mtb_async/mtb_markdown_plus.umd.js'
|
||||||
|
: '/mtb_async/mtb_markdown.umd.js'
|
||||||
|
)
|
||||||
|
.then((_module) =>
|
||||||
|
shiki ? MTBMarkdownPlus.getParser() : MTBMarkdown.getParser(),
|
||||||
|
)
|
||||||
|
.then((instance) => {
|
||||||
|
window.MTB.mdParser = instance
|
||||||
|
runQueuedCallbacks()
|
||||||
|
return instance
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error loading the parser:', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return parserPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ensureMarkdownParser = async (callback) => {
|
||||||
|
infoLogger('Ensuring md parser')
|
||||||
|
let use_shiki = false
|
||||||
|
try {
|
||||||
|
use_shiki = await api.getSetting('mtb.Use Shiki')
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Option not available yet', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.MTB?.mdParser) {
|
||||||
|
infoLogger('Markdown parser found')
|
||||||
|
callback?.(window.MTB.mdParser)
|
||||||
|
return window.MTB.mdParser
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parserPromise) {
|
||||||
|
infoLogger('Running promise to fetch parser')
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadParser(use_shiki) //.then(() => {
|
||||||
|
// callback?.(window.MTB.mdParser)
|
||||||
|
// })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading the parser:', error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
infoLogger('A similar promise is already running, waiting for it to finish')
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callbackQueue.push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
await parserPromise
|
||||||
|
await parserPromise
|
||||||
|
|
||||||
|
return window.MTB.mdParser
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add documentation widget to the selected node
|
* Add documentation widget to the given node.
|
||||||
|
*
|
||||||
|
* This method will add a `docCtrl` property to the node
|
||||||
|
* that contains the AbortController that manages all the events
|
||||||
|
* defined inside it (global and instance ones) without explicit
|
||||||
|
* cleanup method for each.
|
||||||
|
*
|
||||||
* @param {NodeData} nodeData
|
* @param {NodeData} nodeData
|
||||||
* @param {NodeType} nodeType
|
* @param {NodeType} nodeType
|
||||||
* @param {DocumentationOptions} opts
|
* @param {DocumentationOptions} opts
|
||||||
@@ -756,25 +833,10 @@ export const addDocumentation = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!documentationConverter) {
|
|
||||||
infoLogger('Initializing our mardown converter')
|
|
||||||
documentationConverter = new showdown.Converter({
|
|
||||||
tables: true,
|
|
||||||
strikethrough: true,
|
|
||||||
emoji: true,
|
|
||||||
ghCodeBlocks: true,
|
|
||||||
tasklists: true,
|
|
||||||
ghMentions: true,
|
|
||||||
smoothLivePreview: true,
|
|
||||||
simplifiedAutoLink: true,
|
|
||||||
parseImgDimensions: true,
|
|
||||||
openLinksInNewWindow: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = opts || {}
|
const options = opts || {}
|
||||||
const iconSize = options.icon_size || 14
|
const iconSize = options.icon_size || 14
|
||||||
const iconMargin = options.icon_margin || 4
|
const iconMargin = options.icon_margin || 4
|
||||||
|
|
||||||
let docElement = null
|
let docElement = null
|
||||||
let wrapper = null
|
let wrapper = null
|
||||||
|
|
||||||
@@ -820,80 +882,87 @@ export const addDocumentation = (
|
|||||||
|
|
||||||
wrapper = document.createElement('div')
|
wrapper = document.createElement('div')
|
||||||
wrapper.classList.add('documentation-wrapper')
|
wrapper.classList.add('documentation-wrapper')
|
||||||
wrapper.innerHTML = documentationConverter.makeHtml(nodeData.description)
|
|
||||||
docElement.appendChild(wrapper)
|
docElement.appendChild(wrapper)
|
||||||
|
|
||||||
// resize handle
|
// wrapper.innerHTML = documentationConverter.makeHtml(nodeData.description)
|
||||||
resizeHandle = document.createElement('div')
|
|
||||||
resizeHandle.style.width = '0'
|
|
||||||
resizeHandle.style.height = '0'
|
|
||||||
resizeHandle.style.position = 'absolute'
|
|
||||||
resizeHandle.style.bottom = '0'
|
|
||||||
resizeHandle.style.right = '0'
|
|
||||||
|
|
||||||
resizeHandle.style.cursor = 'se-resize'
|
ensureMarkdownParser().then(() => {
|
||||||
resizeHandle.style.userSelect = 'none'
|
MTB.mdParser.parse(nodeData.description).then((e) => {
|
||||||
|
wrapper.innerHTML = e
|
||||||
|
// resize handle
|
||||||
|
resizeHandle = document.createElement('div')
|
||||||
|
resizeHandle.classList.add('doc-resize-handle')
|
||||||
|
resizeHandle.style.width = '0'
|
||||||
|
resizeHandle.style.height = '0'
|
||||||
|
resizeHandle.style.position = 'absolute'
|
||||||
|
resizeHandle.style.bottom = '0'
|
||||||
|
resizeHandle.style.right = '0'
|
||||||
|
|
||||||
resizeHandle.style.borderWidth = '15px'
|
resizeHandle.style.cursor = 'se-resize'
|
||||||
resizeHandle.style.borderStyle = 'solid'
|
resizeHandle.style.userSelect = 'none'
|
||||||
|
|
||||||
resizeHandle.style.borderColor =
|
resizeHandle.style.borderWidth = '15px'
|
||||||
'transparent var(--border-color) var(--border-color) transparent'
|
resizeHandle.style.borderStyle = 'solid'
|
||||||
|
|
||||||
wrapper.appendChild(resizeHandle)
|
resizeHandle.style.borderColor =
|
||||||
let isResizing = false
|
'transparent var(--border-color) var(--border-color) transparent'
|
||||||
|
|
||||||
let startX
|
wrapper.appendChild(resizeHandle)
|
||||||
let startY
|
let isResizing = false
|
||||||
let startWidth
|
|
||||||
let startHeight
|
|
||||||
|
|
||||||
resizeHandle.addEventListener(
|
let startX
|
||||||
'mousedown',
|
let startY
|
||||||
(e) => {
|
let startWidth
|
||||||
e.stopPropagation()
|
let startHeight
|
||||||
isResizing = true
|
|
||||||
startX = e.clientX
|
resizeHandle.addEventListener(
|
||||||
startY = e.clientY
|
'mousedown',
|
||||||
startWidth = Number.parseInt(
|
(e) => {
|
||||||
document.defaultView.getComputedStyle(docElement).width,
|
e.stopPropagation()
|
||||||
10,
|
isResizing = true
|
||||||
|
startX = e.clientX
|
||||||
|
startY = e.clientY
|
||||||
|
startWidth = Number.parseInt(
|
||||||
|
document.defaultView.getComputedStyle(docElement).width,
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
startHeight = Number.parseInt(
|
||||||
|
document.defaultView.getComputedStyle(docElement).height,
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{ signal: this.docCtrl.signal },
|
||||||
)
|
)
|
||||||
startHeight = Number.parseInt(
|
|
||||||
document.defaultView.getComputedStyle(docElement).height,
|
document.addEventListener(
|
||||||
10,
|
'mousemove',
|
||||||
|
(e) => {
|
||||||
|
if (!isResizing) return
|
||||||
|
const scale = app.canvas.ds.scale
|
||||||
|
const newWidth = startWidth + (e.clientX - startX) / scale
|
||||||
|
const newHeight = startHeight + (e.clientY - startY) / scale
|
||||||
|
|
||||||
|
docElement.style.width = `${newWidth}px`
|
||||||
|
docElement.style.height = `${newHeight}px`
|
||||||
|
|
||||||
|
this.docPos = {
|
||||||
|
width: `${newWidth}px`,
|
||||||
|
height: `${newHeight}px`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal: this.docCtrl.signal },
|
||||||
)
|
)
|
||||||
},
|
|
||||||
|
|
||||||
{ signal: this.docCtrl.signal },
|
document.addEventListener(
|
||||||
)
|
'mouseup',
|
||||||
|
() => {
|
||||||
document.addEventListener(
|
isResizing = false
|
||||||
'mousemove',
|
},
|
||||||
(e) => {
|
{ signal: this.docCtrl.signal },
|
||||||
if (!isResizing) return
|
)
|
||||||
const scale = app.canvas.ds.scale
|
})
|
||||||
const newWidth = startWidth + (e.clientX - startX) / scale
|
})
|
||||||
const newHeight = startHeight + (e.clientY - startY) / scale
|
|
||||||
|
|
||||||
docElement.style.width = `${newWidth}px`
|
|
||||||
docElement.style.height = `${newHeight}px`
|
|
||||||
|
|
||||||
this.docPos = {
|
|
||||||
width: `${newWidth}px`,
|
|
||||||
height: `${newHeight}px`,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ signal: this.docCtrl.signal },
|
|
||||||
)
|
|
||||||
|
|
||||||
document.addEventListener(
|
|
||||||
'mouseup',
|
|
||||||
() => {
|
|
||||||
isResizing = false
|
|
||||||
},
|
|
||||||
{ signal: this.docCtrl.signal },
|
|
||||||
)
|
|
||||||
} else if (!this.show_doc && docElement !== null) {
|
} else if (!this.show_doc && docElement !== null) {
|
||||||
docElement.remove()
|
docElement.remove()
|
||||||
docElement = null
|
docElement = null
|
||||||
|
|||||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
-3
File diff suppressed because one or more lines are too long
+1096
-1096
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,246 @@
|
|||||||
|
// web/note_plus.constants.js
|
||||||
|
|
||||||
|
export const DEFAULT_CSS = ''
|
||||||
|
export const DEFAULT_HTML = `<p style='color:red;font-family:monospace'>
|
||||||
|
Note+
|
||||||
|
</p>`
|
||||||
|
export const DEFAULT_MD = '## Note+'
|
||||||
|
export const DEFAULT_MODE = 'markdown'
|
||||||
|
export const DEFAULT_THEME = 'one_dark'
|
||||||
|
|
||||||
|
export const DEMO_CONTENT = `
|
||||||
|
# @mtb/svelte-markdown.
|
||||||
|
## This is a subheader
|
||||||
|
|
||||||
|
[](https://github.com/melMass/comfy_mtb/actions/workflows/test_embedded.yml)
|
||||||
|

|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More details about the inception of the project</summary>
|
||||||
|
|
||||||
|
\`\`\`js
|
||||||
|
class YesMan{
|
||||||
|
constructor(){
|
||||||
|
this.started = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
</details>
|
||||||
|
|
||||||
|
This is a paragraph. If it goes over the maximum width it will not automatically wrap unless it reaches the max-w of \`prose\` check [styles](/styles) for more info.
|
||||||
|
|
||||||
|
This component is useful for building some tools on top. Or even just a static system using svelte at its core. My personal blog is fully powered by **@mtb/svelte-markdown**
|
||||||
|
|
||||||
|
| And this is | A table |
|
||||||
|
|-------------|---------|
|
||||||
|
| With two | columns |
|
||||||
|
|
||||||
|
We also support github callout:
|
||||||
|
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Highlights information that users should take into account, even when skimming.
|
||||||
|
> [!TIP]
|
||||||
|
> Optional information to help a user be more successful.
|
||||||
|
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Crucial information necessary for users to succeed.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Critical content demanding immediate user attention due to potential risks.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Negative potential consequences of an action.
|
||||||
|
`
|
||||||
|
|
||||||
|
export const THEMES = [
|
||||||
|
'ambiance',
|
||||||
|
'chaos',
|
||||||
|
'chrome',
|
||||||
|
'cloud9_day',
|
||||||
|
'cloud9_night',
|
||||||
|
'cloud9_night_low_color',
|
||||||
|
'cloud_editor',
|
||||||
|
'cloud_editor_dark',
|
||||||
|
'clouds',
|
||||||
|
'clouds_midnight',
|
||||||
|
'cobalt',
|
||||||
|
'crimson_editor',
|
||||||
|
'dawn',
|
||||||
|
'dracula',
|
||||||
|
'dreamweaver',
|
||||||
|
'eclipse',
|
||||||
|
'github',
|
||||||
|
'github_dark',
|
||||||
|
'gob',
|
||||||
|
'gruvbox',
|
||||||
|
'gruvbox_dark_hard',
|
||||||
|
'gruvbox_light_hard',
|
||||||
|
'idle_fingers',
|
||||||
|
'iplastic',
|
||||||
|
'katzenmilch',
|
||||||
|
'kr_theme',
|
||||||
|
'kuroir',
|
||||||
|
'merbivore',
|
||||||
|
'merbivore_soft',
|
||||||
|
'mono_industrial',
|
||||||
|
'monokai',
|
||||||
|
'nord_dark',
|
||||||
|
'one_dark',
|
||||||
|
'pastel_on_dark',
|
||||||
|
'solarized_dark',
|
||||||
|
'solarized_light',
|
||||||
|
'sqlserver',
|
||||||
|
'terminal',
|
||||||
|
'textmate',
|
||||||
|
'tomorrow',
|
||||||
|
'tomorrow_night',
|
||||||
|
'tomorrow_night_blue',
|
||||||
|
'tomorrow_night_bright',
|
||||||
|
'tomorrow_night_eighties',
|
||||||
|
'twilight',
|
||||||
|
'vibrant_ink',
|
||||||
|
'vscode',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const CSS_RESET = `
|
||||||
|
* {
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1.25em;
|
||||||
|
}
|
||||||
|
.shiki{
|
||||||
|
padding: 1em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.markdown-callout-title {
|
||||||
|
.octicon{
|
||||||
|
fill:white;
|
||||||
|
}
|
||||||
|
/* background: var(--current-color); */
|
||||||
|
color: var(--current-color);
|
||||||
|
font-weight: bold;
|
||||||
|
/* border-start-end-radius: var(--radius); */
|
||||||
|
/* border-start-start-radius: var(--radius); */
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-inline-start: 1em;
|
||||||
|
}
|
||||||
|
.markdown-callout-content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.markdown-callout {
|
||||||
|
--radius: 8px;
|
||||||
|
--current-color: purple;
|
||||||
|
/* border-start-end-radius: var(--radius); */
|
||||||
|
/* border-start-start-radius: var(--radius); */
|
||||||
|
border-left: 3px solid var(--current-color);
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-callout-tip {
|
||||||
|
--text-color: whitesmoke;
|
||||||
|
--current-color: #50e3c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-callout-note {
|
||||||
|
--text-color: whitesmoke;
|
||||||
|
--current-color: #0070f3;
|
||||||
|
}
|
||||||
|
.markdown-callout-important {
|
||||||
|
--text-color: whitesmoke;
|
||||||
|
--current-color: #7928ca;
|
||||||
|
}
|
||||||
|
.markdown-callout-warning {
|
||||||
|
--current-color: #f5a623;
|
||||||
|
}
|
||||||
|
.markdown-callout-caution {
|
||||||
|
--current-color: #e60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.note-plus-preview {
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width:95%;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top:20px;
|
||||||
|
/*background-color: rgba(255,0,0,0.5)!important;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allowed to be selected*/
|
||||||
|
h1, h2, h3, h4, h5, h6,a, p, ul, ol, dl, blockquote,details,summary {
|
||||||
|
pointer-events:auto;
|
||||||
|
user-select:text;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
display:inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ul, ol, dl, blockquote {
|
||||||
|
margin: 0.3em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
pointer-events: all;
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
padding: 1em 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border:none;
|
||||||
|
pointer-events:all;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #ccc;
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border-left: 1px solid #000;
|
||||||
|
border-right: 1px solid #000;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
border: 1px solid #000;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
+390
-253
@@ -1,155 +1,122 @@
|
|||||||
|
/// <reference path="../types/typedefs.js" />
|
||||||
|
|
||||||
import { app } from '../../scripts/app.js'
|
import { app } from '../../scripts/app.js'
|
||||||
|
|
||||||
import * as shared from './comfy_shared.js'
|
import * as shared from './comfy_shared.js'
|
||||||
import { infoLogger, successLogger, errorLogger } 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 DEFAULT_CSS = ''
|
const storage = new LocalStorageManager('mtb')
|
||||||
const DEFAULT_HTML = `<p style='color:red;font-family:monospace'>
|
|
||||||
Note+
|
|
||||||
</p>`
|
|
||||||
const DEFAULT_MD = '## Note+'
|
|
||||||
const DEFAULT_MODE = 'markdown'
|
|
||||||
const DEFAULT_THEME = 'one_dark'
|
|
||||||
|
|
||||||
const CSS_RESET = `
|
/**
|
||||||
* {
|
* Uses `@mtb/markdown-parser` (a fork of marked)
|
||||||
font-family: monospace;
|
* It is statically stored to avoid having
|
||||||
line-height: 1.25em;
|
* 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'
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
const makeDraggable = (dialog, handle) => {
|
||||||
margin: 0;
|
let offsetX = 0
|
||||||
padding: 0;
|
let offsetY = 0
|
||||||
font-weight: normal;
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
p, ul, ol, dl, blockquote {
|
/** @extends {LGraphNode} */
|
||||||
margin: 0.3em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ul, ol {
|
|
||||||
|
|
||||||
padding-left: 1em;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
pointer-events: all;
|
|
||||||
color: cyan;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
padding: 1em 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border:none;
|
|
||||||
pointer-events:all;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
border-left: 4px solid #ccc;
|
|
||||||
padding-left: 1em;
|
|
||||||
margin-left: 0;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, code {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
border-left: 1px solid #000;
|
|
||||||
border-right: 1px solid #000;
|
|
||||||
padding: 8px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
border: 1px solid #000;
|
|
||||||
|
|
||||||
background-color: rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const themes = [
|
|
||||||
'ambiance',
|
|
||||||
'chaos',
|
|
||||||
'chrome',
|
|
||||||
'cloud9_day',
|
|
||||||
'cloud9_night',
|
|
||||||
'cloud9_night_low_color',
|
|
||||||
'cloud_editor',
|
|
||||||
'cloud_editor_dark',
|
|
||||||
'clouds',
|
|
||||||
'clouds_midnight',
|
|
||||||
'cobalt',
|
|
||||||
'crimson_editor',
|
|
||||||
'dawn',
|
|
||||||
'dracula',
|
|
||||||
'dreamweaver',
|
|
||||||
'eclipse',
|
|
||||||
'github',
|
|
||||||
'github_dark',
|
|
||||||
'gob',
|
|
||||||
'gruvbox',
|
|
||||||
'gruvbox_dark_hard',
|
|
||||||
'gruvbox_light_hard',
|
|
||||||
'idle_fingers',
|
|
||||||
'iplastic',
|
|
||||||
'katzenmilch',
|
|
||||||
'kr_theme',
|
|
||||||
'kuroir',
|
|
||||||
'merbivore',
|
|
||||||
'merbivore_soft',
|
|
||||||
'mono_industrial',
|
|
||||||
'monokai',
|
|
||||||
'nord_dark',
|
|
||||||
'one_dark',
|
|
||||||
'pastel_on_dark',
|
|
||||||
'solarized_dark',
|
|
||||||
'solarized_light',
|
|
||||||
'sqlserver',
|
|
||||||
'terminal',
|
|
||||||
'textmate',
|
|
||||||
'tomorrow',
|
|
||||||
'tomorrow_night',
|
|
||||||
'tomorrow_night_blue',
|
|
||||||
'tomorrow_night_bright',
|
|
||||||
'tomorrow_night_eighties',
|
|
||||||
'twilight',
|
|
||||||
'vibrant_ink',
|
|
||||||
'vscode',
|
|
||||||
]
|
|
||||||
class NotePlus extends LiteGraph.LGraphNode {
|
class NotePlus extends LiteGraph.LGraphNode {
|
||||||
// same values as the comfy note
|
// same values as the comfy note
|
||||||
color = LGraphCanvas.node_colors.yellow.color
|
color = LGraphCanvas.node_colors.yellow.color
|
||||||
bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
|
bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
|
||||||
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor
|
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() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.uuid = shared.makeUUID()
|
this.uuid = shared.makeUUID()
|
||||||
|
|
||||||
infoLogger('Constructing Note+ instance')
|
infoLogger('Constructing Note+ instance')
|
||||||
|
shared.ensureMarkdownParser((_p) => {
|
||||||
|
this.updateHTML()
|
||||||
|
})
|
||||||
// - litegraph settings
|
// - litegraph settings
|
||||||
this.collapsable = true
|
this.collapsable = true
|
||||||
this.isVirtualNode = true
|
this.isVirtualNode = true
|
||||||
@@ -159,35 +126,30 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
// - default values, serialization is done through widgets
|
// - default values, serialization is done through widgets
|
||||||
this._raw_html = DEFAULT_MODE === 'html' ? DEFAULT_HTML : DEFAULT_MD
|
this._raw_html = DEFAULT_MODE === 'html' ? DEFAULT_HTML : DEFAULT_MD
|
||||||
|
|
||||||
// - mardown converter
|
|
||||||
this.markdownConverter = new showdown.Converter({
|
|
||||||
tables: true,
|
|
||||||
strikethrough: true,
|
|
||||||
emoji: true,
|
|
||||||
ghCodeBlocks: true,
|
|
||||||
tasklists: true,
|
|
||||||
ghMentions: true,
|
|
||||||
smoothLivePreview: true,
|
|
||||||
simplifiedAutoLink: true,
|
|
||||||
parseImgDimensions: true,
|
|
||||||
openLinksInNewWindow: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// - state
|
// - state
|
||||||
this.live = true
|
this.live = true
|
||||||
this.calculated_height = 0
|
this.calculated_height = 0
|
||||||
|
|
||||||
// - add widgets
|
// - add widgets
|
||||||
const inner = document.createElement('div')
|
const cinner = document.createElement('div')
|
||||||
inner.style.margin = '0'
|
this.inner = document.createElement('div')
|
||||||
inner.style.padding = '0'
|
|
||||||
inner.style.pointerEvents = 'none'
|
cinner.append(this.inner)
|
||||||
this.html_widget = this.addDOMWidget('HTML', 'html', 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) => {
|
setValue: (val) => {
|
||||||
this._raw_html = val
|
this._raw_html = val
|
||||||
},
|
},
|
||||||
getValue: () => this._raw_html,
|
getValue: () => this._raw_html,
|
||||||
getMinHeight: () => this.calculated_height, // (the edit button),
|
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,
|
hideOnZoom: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -197,22 +159,48 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param {CanvasRenderingContext2D} ctx canvas context
|
||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {any} _graphcanvas
|
||||||
* @param {LGraphCanvas} graphcanvas
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDrawForeground(ctx, _graphcanvas) {
|
onDrawForeground(ctx, _graphcanvas) {
|
||||||
if (this.flags.collapsed) return
|
if (this.flags.collapsed) return
|
||||||
|
this.drawEditIcon(ctx)
|
||||||
|
this.drawSideHandle(ctx)
|
||||||
|
|
||||||
// Define the size and position of the icon
|
// DEBUG BACKGROUND
|
||||||
const iconSize = 14 // Size of the icon
|
// ctx.fillStyle = 'rgba(0, 255, 0, 0.3)'
|
||||||
const iconMargin = 8 // Margin from the edges
|
// const rect = this.rect
|
||||||
const x = this.size[0] - iconSize - iconMargin
|
// ctx.fillRect(rect.x, rect.y, rect.width, rect.height)
|
||||||
const y = iconMargin * 1.5
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
// Create a new Path2D object from SVG path data
|
|
||||||
const pencilPath = new Path2D(
|
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',
|
'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',
|
||||||
)
|
)
|
||||||
@@ -220,41 +208,73 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
'M11 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h11c2.21 0 3-1.8 3-4v-5',
|
'M11 4H6a4 4 0 0 0-4 4v10a4 4 0 0 0 4 4h11c2.21 0 3-1.8 3-4v-5',
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw the paths
|
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.translate(x, y) // Position the icon on the canvas
|
ctx.translate(rect.x, rect.y)
|
||||||
ctx.scale(iconSize / 32, iconSize / 32) // Scale the icon to the desired size
|
ctx.scale(rect.width / 32, rect.height / 32)
|
||||||
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
|
ctx.strokeStyle = 'rgba(255,255,255,0.4)'
|
||||||
|
|
||||||
ctx.lineCap = 'round'
|
ctx.lineCap = 'round'
|
||||||
ctx.lineJoin = 'round'
|
ctx.lineJoin = 'round'
|
||||||
|
|
||||||
ctx.lineWidth = 2.4
|
ctx.lineWidth = 2.4
|
||||||
ctx.stroke(pencilPath)
|
ctx.stroke(pencilPath)
|
||||||
ctx.stroke(folderPath)
|
ctx.stroke(folderPath)
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
onMouseDown(_e, localPos, _graphcanvas) {
|
/**
|
||||||
// Check if the click is within the pencil icon bounds
|
* @param {number} x
|
||||||
const iconSize = 14
|
* @param {number} y
|
||||||
const iconMargin = 8
|
* @param {{x:number,y:number,width:number,height:number}} rect
|
||||||
const iconX = this.size[0] - iconSize - iconMargin
|
* @returns {}
|
||||||
const iconY = iconMargin * 1.5
|
*/
|
||||||
|
inRect(x, y, rect) {
|
||||||
if (
|
rect = rect || this.iconRect
|
||||||
localPos[0] > iconX &&
|
return (
|
||||||
localPos[0] < iconX + iconSize &&
|
x >= rect.x &&
|
||||||
localPos[1] > iconY &&
|
x <= rect.x + rect.width &&
|
||||||
localPos[1] < iconY + iconSize
|
y >= rect.y &&
|
||||||
) {
|
y <= rect.y + rect.height
|
||||||
// Pencil icon was clicked, open the editor
|
)
|
||||||
this.openEditorDialog()
|
}
|
||||||
return true // Return true to indicate the event was handled
|
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]
|
||||||
|
|
||||||
return false // Return false to let the event propagate
|
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() {
|
setupSerializationWidgets() {
|
||||||
infoLogger('Setup Serializing widgets')
|
infoLogger('Setup Serializing widgets')
|
||||||
|
|
||||||
@@ -283,15 +303,36 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
shared.hideWidgetForGood(this, this.css_widget)
|
shared.hideWidgetForGood(this, this.css_widget)
|
||||||
shared.hideWidgetForGood(this, this.theme_widget)
|
shared.hideWidgetForGood(this, this.theme_widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDialog() {
|
setupDialog() {
|
||||||
infoLogger('Setup dialog')
|
infoLogger('Setup dialog')
|
||||||
// this.addWidget('button', 'Edit', 'Edit', this.openEditorDialog.bind(this))
|
|
||||||
|
|
||||||
this.dialog = new app.ui.dialog.constructor()
|
this.dialog = new app.ui.dialog.constructor()
|
||||||
this.dialog.element.classList.add('comfy-settings')
|
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')
|
const closeButton = this.dialog.element.querySelector('button')
|
||||||
closeButton.textContent = 'CANCEL'
|
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')
|
const saveButton = document.createElement('button')
|
||||||
saveButton.textContent = 'SAVE'
|
saveButton.textContent = 'SAVE'
|
||||||
saveButton.onclick = () => {
|
saveButton.onclick = () => {
|
||||||
@@ -313,32 +354,54 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
|
|
||||||
closeEditorDialog(accept) {
|
closeEditorDialog(accept) {
|
||||||
infoLogger('Closing editor dialog', accept)
|
infoLogger('Closing editor dialog', accept)
|
||||||
if (accept) {
|
if (accept && !this.live) {
|
||||||
this.updateHTML(this.html_editor.getValue())
|
this.updateHTML(this.html_editor.getValue())
|
||||||
this.updateCSS(this.css_editor.getValue())
|
this.updateCSS(this.css_editor.getValue())
|
||||||
}
|
}
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect()
|
||||||
|
this.resizeObserver = null
|
||||||
|
}
|
||||||
this.teardownEditors()
|
this.teardownEditors()
|
||||||
this.dialog.close()
|
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() {
|
openEditorDialog() {
|
||||||
infoLogger(`Current edit mode ${this.edit_mode_widget.value}`)
|
infoLogger(`Current edit mode ${this.edit_mode_widget.value}`)
|
||||||
|
this.hookResize(this.dialog.element)
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
|
|
||||||
Object.assign(container.style, {
|
Object.assign(container.style, {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '10px',
|
gap: '10px',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
})
|
})
|
||||||
|
|
||||||
const editorsContainer = document.createElement('div')
|
this.editorsContainer = document.createElement('div')
|
||||||
Object.assign(editorsContainer.style, {
|
|
||||||
|
Object.assign(this.editorsContainer.style, {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '10px',
|
gap: '10px',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
minHeight: this.dialog.element.offsetHeight, //'200px',
|
||||||
|
width: '100%',
|
||||||
})
|
})
|
||||||
|
|
||||||
container.append(editorsContainer)
|
container.append(this.editorsContainer)
|
||||||
|
|
||||||
this.dialog.show('')
|
this.dialog.show('')
|
||||||
this.dialog.textElement.append(container)
|
this.dialog.textElement.append(container)
|
||||||
@@ -346,30 +409,39 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
const aceHTML = document.createElement('div')
|
const aceHTML = document.createElement('div')
|
||||||
aceHTML.id = 'noteplus-html-editor'
|
aceHTML.id = 'noteplus-html-editor'
|
||||||
Object.assign(aceHTML.style, {
|
Object.assign(aceHTML.style, {
|
||||||
width: '300px',
|
width: '100%',
|
||||||
height: '300px',
|
height: '100%',
|
||||||
// backgroundColor: 'rgb(30,30,30)',
|
|
||||||
// color: 'whitesmoke',
|
minWidth: '300px',
|
||||||
|
minHeight: 'inherit',
|
||||||
})
|
})
|
||||||
|
|
||||||
editorsContainer.append(aceHTML)
|
this.editorsContainer.append(aceHTML)
|
||||||
|
|
||||||
const aceCSS = document.createElement('div')
|
const aceCSS = document.createElement('div')
|
||||||
aceCSS.id = 'noteplus-css-editor'
|
aceCSS.id = 'noteplus-css-editor'
|
||||||
Object.assign(aceCSS.style, {
|
Object.assign(aceCSS.style, {
|
||||||
width: '300px',
|
width: '100%',
|
||||||
height: '300px',
|
height: '100%',
|
||||||
// backgroundColor: 'rgb(30,30,30)',
|
minHeight: 'inherit',
|
||||||
// color: 'whitesmoke',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
editorsContainer.append(aceCSS)
|
this.editorsContainer.append(aceCSS)
|
||||||
|
|
||||||
const live_edit = document.createElement('input')
|
const live_edit = document.createElement('input')
|
||||||
live_edit.type = 'checkbox'
|
live_edit.type = 'checkbox'
|
||||||
live_edit.checked = this.live
|
live_edit.checked = this.live
|
||||||
live_edit.onchange = () => {
|
live_edit.onchange = () => {
|
||||||
this.live = live_edit.checked
|
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
|
//- "Dynamic" elements
|
||||||
@@ -388,15 +460,14 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
const md = this.html_editor.getValue()
|
const md = this.html_editor.getValue()
|
||||||
this.edit_mode_widget.value = 'html'
|
this.edit_mode_widget.value = 'html'
|
||||||
select_mode.value = 'html'
|
select_mode.value = 'html'
|
||||||
const html = this.markdownConverter.makeHtml(md)
|
MTB.mdParser.parse(md).then((content) => {
|
||||||
this.html_widget.value = html
|
this.html_widget.value = content
|
||||||
this.html_editor.setValue(html)
|
this.html_editor.setValue(content)
|
||||||
this.html_editor.session.setMode('ace/mode/html')
|
this.html_editor.session.setMode('ace/mode/html')
|
||||||
this.updateHTML(this.html_widget.value)
|
this.updateHTML(this.html_widget.value)
|
||||||
|
convert_to_html.remove()
|
||||||
convert_to_html.remove()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
firstButton.before(convert_to_html)
|
firstButton.before(convert_to_html)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -406,6 +477,19 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
select_mode.value = this.edit_mode_widget.value
|
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
|
//- combobox
|
||||||
let theme_select = this.dialog.element.querySelector('#theme_select')
|
let theme_select = this.dialog.element.querySelector('#theme_select')
|
||||||
@@ -421,7 +505,7 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
option.textContent = label
|
option.textContent = label
|
||||||
theme_select.append(option)
|
theme_select.append(option)
|
||||||
}
|
}
|
||||||
for (const t of themes) {
|
for (const t of THEMES) {
|
||||||
addOption(t)
|
addOption(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,54 +575,59 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
onCreate() {
|
onCreate() {
|
||||||
errorLogger('NotePlus onCreate')
|
errorLogger('NotePlus onCreate')
|
||||||
}
|
}
|
||||||
configure(info) {
|
restoreNodeState(info) {
|
||||||
super.configure(info)
|
|
||||||
infoLogger('Restoring serialized values', info)
|
|
||||||
// - update view from serialzed data
|
|
||||||
this.html_widget.element.id = `note-plus-${this.uuid}`
|
this.html_widget.element.id = `note-plus-${this.uuid}`
|
||||||
this.setMode(this.edit_mode_widget.value)
|
this.setMode(this.edit_mode_widget.value)
|
||||||
this.setTheme(this.theme_widget.value)
|
this.setTheme(this.theme_widget.value)
|
||||||
this.updateHTML(this.html_widget.value)
|
this.updateHTML(this.html_widget.value)
|
||||||
this.updateCSS(this.css_widget.value)
|
this.updateCSS(this.css_widget.value)
|
||||||
this.setSize(info.size)
|
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() {
|
onNodeCreated() {
|
||||||
infoLogger('Node created', this.uuid)
|
infoLogger('Node created', this.uuid)
|
||||||
this.html_widget.element.id = `note-plus-${this.uuid}`
|
this.restoreNodeState({})
|
||||||
this.setMode(this.edit_mode_widget.value)
|
// this.html_widget.element.id = `note-plus-${this.uuid}`
|
||||||
this.setTheme(this.theme_widget.value)
|
// this.setMode(this.edit_mode_widget.value)
|
||||||
this.updateHTML(this.html_widget.value) // widget is populated here since we called super
|
// this.setTheme(this.theme_widget.value)
|
||||||
this.updateCSS(this.css_widget.value)
|
// this.updateHTML(this.html_widget.value) // widget is populated here since we called super
|
||||||
|
// this.updateCSS(this.css_widget.value)
|
||||||
}
|
}
|
||||||
onRemoved() {
|
onRemoved() {
|
||||||
infoLogger('Node removed', this.uuid)
|
infoLogger('Node removed', this.uuid)
|
||||||
}
|
}
|
||||||
getExtraMenuOptions() {
|
getExtraMenuOptions() {
|
||||||
const options = []
|
const currentMode = this.edit_mode_widget.value
|
||||||
// {
|
const newMode = currentMode === 'html' ? 'markdown' : 'html'
|
||||||
// content: string;
|
|
||||||
// callback?: ContextMenuEventListener;
|
|
||||||
// /** Used as innerHTML for extra child element */
|
|
||||||
// title?: string;
|
|
||||||
// disabled?: boolean;
|
|
||||||
// has_submenu?: boolean;
|
|
||||||
// submenu?: {
|
|
||||||
// options: ContextMenuItem[];
|
|
||||||
// } & IContextMenuOptions;
|
|
||||||
// className?: string;
|
|
||||||
// }
|
|
||||||
options.push({
|
|
||||||
content: `Set to ${
|
|
||||||
this.edit_mode_widget.value === 'html' ? 'markdown' : 'html'
|
|
||||||
}`,
|
|
||||||
callback: () => {
|
|
||||||
this.edit_mode_widget.value =
|
|
||||||
this.edit_mode_widget.value === 'html' ? 'markdown' : 'html'
|
|
||||||
this.updateHTML(this.html_widget.value)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return options
|
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) {
|
_setupEditor(editor) {
|
||||||
@@ -663,17 +752,44 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
// this.setSize(this.computeSize())
|
// this.setSize(this.computeSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHTML(val) {
|
parserInitiated() {
|
||||||
const cleanHTML = DOMPurify.sanitize(val, { ADD_TAGS: ['iframe'] })
|
if (window.MTB?.mdParser) return true
|
||||||
this.html_widget.value = cleanHTML
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// update our widget preview
|
/** to easilty swap purification methods*/
|
||||||
if (this.edit_mode_widget.value === 'html') {
|
purify(content) {
|
||||||
this.html_widget.element.innerHTML = cleanHTML
|
return DOMPurify.sanitize(content, {
|
||||||
} else if (this.edit_mode_widget.value === 'markdown') {
|
ADD_TAGS: ['iframe', 'detail', 'summary'],
|
||||||
this.html_widget.element.innerHTML =
|
})
|
||||||
this.markdownConverter.makeHtml(cleanHTML)
|
}
|
||||||
|
|
||||||
|
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.calculateHeight()
|
||||||
// this.setSize(this.computeSize())
|
// this.setSize(this.computeSize())
|
||||||
}
|
}
|
||||||
@@ -681,6 +797,27 @@ class NotePlus extends LiteGraph.LGraphNode {
|
|||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: 'mtb.noteplus',
|
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() {
|
registerCustomNodes() {
|
||||||
LiteGraph.registerNodeType('Note Plus (mtb)', NotePlus)
|
LiteGraph.registerNodeType('Note Plus (mtb)', NotePlus)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user