Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 45 additions & 4 deletions src/lib/litegraph/src/ContextMenu.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
import DOMPurify from 'dompurify'

Check warning on line 1 in src/lib/litegraph/src/ContextMenu.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Using exported name 'DOMPurify' as identifier for default export

Check warning on line 1 in src/lib/litegraph/src/ContextMenu.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Using exported name 'DOMPurify' as identifier for default export

import type {
ContextMenuDivElement,
IContextMenuOptions,
IContextMenuValue
} from './interfaces'
import { LiteGraph } from './litegraph'

const ALLOWED_TAGS = ['span', 'b', 'i', 'em', 'strong']
const ALLOWED_STYLE_PROPS = new Set([
'display',
'color',
'background-color',
'padding-left',
'border-left'
])

DOMPurify.addHook('uponSanitizeAttribute', (_node, data) => {
if (data.attrName === 'style') {
const sanitizedStyle = data.attrValue
.split(';')
.map((s) => s.trim())
.filter((s) => {
const colonIdx = s.indexOf(':')
if (colonIdx === -1) return false
const prop = s.slice(0, colonIdx).trim().toLowerCase()
return ALLOWED_STYLE_PROPS.has(prop)
})
.join('; ')
data.attrValue = sanitizedStyle
}
})

function sanitizeMenuHTML(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS,
ALLOWED_ATTR: ['style']
})
}

// TODO: Replace this pattern with something more modern.
export interface ContextMenu<TValue = unknown> {
constructor: new (
Expand Down Expand Up @@ -123,7 +157,7 @@
if (options.title) {
const element = document.createElement('div')
element.className = 'litemenu-title'
element.innerHTML = options.title
element.textContent = options.title
root.append(element)
}

Expand Down Expand Up @@ -218,11 +252,18 @@
if (value === null) {
element.classList.add('separator')
} else {
const innerHtml = name === null ? '' : String(name)
const label = name === null ? '' : String(name)
if (typeof value === 'string') {
element.innerHTML = innerHtml
element.textContent = label
} else {
element.innerHTML = value?.title ?? innerHtml
// Use innerHTML for content that contains HTML tags, textContent otherwise
const hasHtmlContent =
value?.content !== undefined && /<[a-z][\s\S]*>/i.test(value.content)
if (hasHtmlContent) {
element.innerHTML = sanitizeMenuHTML(value.content!)
} else {
element.textContent = value?.title ?? label
}

if (value.disabled) {
disabled = true
Expand Down