From 24bd0e9f59fe1de162d2d92ad1be1870044803e1 Mon Sep 17 00:00:00 2001
From: Alexander Onnikov
Date: Fri, 13 Sep 2024 21:48:00 +0700
Subject: [PATCH] fix: codeblock various fixes (#6550)
---
common/config/rush/pnpm-lock.yaml | 26 +++++++++++++--
packages/presentation/package.json | 1 +
.../components/markup/CodeBlockNode.svelte | 32 +++++++++++++++++++
.../src/components/markup/NodeContent.svelte | 3 +-
packages/theme/styles/_colors.scss | 1 -
packages/theme/styles/prose.scss | 1 -
packages/ui/package.json | 2 ++
packages/ui/src/components/Html.svelte | 23 +++++++++++++
packages/ui/src/index.ts | 1 +
.../src/components/Highlight.svelte | 26 +++++++++++++++
.../src/highlight/highlight.ts | 12 +++----
plugins/diffview-resources/src/index.ts | 4 ++-
plugins/diffview/src/index.ts | 3 +-
.../src/components/extension/codeblock.ts | 19 +++++++----
.../src/kits/default-kit.ts | 10 ++----
.../src/kits/editor-kit.ts | 5 +--
16 files changed, 141 insertions(+), 28 deletions(-)
create mode 100644 packages/presentation/src/components/markup/CodeBlockNode.svelte
create mode 100644 packages/ui/src/components/Html.svelte
create mode 100644 plugins/diffview-resources/src/components/Highlight.svelte
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 36367f9c66..006a5fa12e 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -1223,6 +1223,9 @@ dependencies:
'@types/domhandler':
specifier: ^2.4.5
version: 2.4.5
+ '@types/dompurify':
+ specifier: ^3.0.5
+ version: 3.0.5
'@types/email-addresses':
specifier: ^3.0.0
version: 3.0.0
@@ -1451,6 +1454,9 @@ dependencies:
domhandler:
specifier: ^5.0.3
version: 5.0.3
+ dompurify:
+ specifier: ^3.1.6
+ version: 3.1.6
domutils:
specifier: ^3.1.0
version: 3.1.0
@@ -8954,6 +8960,12 @@ packages:
resolution: {integrity: sha512-lANhC2grmFG1gBac/8sDAKdIXx+TzAdkJIAjEOSMA+qW3297ybACEbacJnG15aNYfrzDO6fdcoouokqAKsy6aQ==}
dev: false
+ /@types/dompurify@3.0.5:
+ resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
+ dependencies:
+ '@types/trusted-types': 2.0.7
+ dev: false
+
/@types/domutils@1.7.8:
resolution: {integrity: sha512-iZGboDV79ibrO3D625p9yD+VgmMDnyJocdIRJvu9Xz66R8SHfOY/XNgdjY5SFoFiLgILceVfSLt7IUhlk1Vhhg==}
dependencies:
@@ -9534,6 +9546,10 @@ packages:
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
dev: false
+ /@types/trusted-types@2.0.7:
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+ dev: false
+
/@types/unist@2.0.10:
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
dev: false
@@ -12958,6 +12974,10 @@ packages:
domelementtype: 2.3.0
dev: false
+ /dompurify@3.1.6:
+ resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==}
+ dev: false
+
/domutils@1.5.1:
resolution: {integrity: sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==}
dependencies:
@@ -30472,7 +30492,7 @@ packages:
dev: false
file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
- resolution: {integrity: sha512-r+NP0EMgEeKbfaa4v8P1Iho0cfYqe9PhOBfV6SPd/9xnNPt42nK9Gu4r5so1LTolhEUzbFiKh7zSX1ADL5e/3g==, tarball: file:projects/presentation.tgz}
+ resolution: {integrity: sha512-ryBht4b1zE/Ik6KZqDL/joAzt3968bkRbGZOt3x+pE929i7yCtHmlMC7W65Nlr1eglhC2JTSy2NiKTNv9yjcuw==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz
name: '@rush-temp/presentation'
version: 0.0.0
@@ -35107,17 +35127,19 @@ packages:
dev: false
file:projects/ui.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
- resolution: {integrity: sha512-WtSFJW84fNe+3lwzv2a8CRmyYIsY8B6HHJwg3YKLd7jWHF4T8hYIf892hAEv7kvh/vrZ7elq8E8b1znmCNd7Sw==, tarball: file:projects/ui.tgz}
+ resolution: {integrity: sha512-umESBjjPj7ES3uF9YcS31H5dwqZtMATByltYeDc+XG+7ovD1SOM11UAjBpHCqj026RvvqcSjE8lAQP1zRXxCoA==, tarball: file:projects/ui.tgz}
id: file:projects/ui.tgz
name: '@rush-temp/ui'
version: 0.0.0
dependencies:
+ '@types/dompurify': 3.0.5
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
autolinker: 4.0.0
date-fns: 2.30.0
date-fns-tz: 2.0.0(date-fns@2.30.0)
+ dompurify: 3.1.6
emoji-regex: 10.3.0
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
diff --git a/packages/presentation/package.json b/packages/presentation/package.json
index 0609bcfbaa..4bdad6e660 100644
--- a/packages/presentation/package.json
+++ b/packages/presentation/package.json
@@ -48,6 +48,7 @@
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/text": "^0.6.5",
+ "@hcengineering/diffview": "^0.6.0",
"@hcengineering/uploader": "^0.6.0",
"svelte": "^4.2.12",
"@hcengineering/client": "^0.6.18",
diff --git a/packages/presentation/src/components/markup/CodeBlockNode.svelte b/packages/presentation/src/components/markup/CodeBlockNode.svelte
new file mode 100644
index 0000000000..870f78fea3
--- /dev/null
+++ b/packages/presentation/src/components/markup/CodeBlockNode.svelte
@@ -0,0 +1,32 @@
+
+
+
+{#if node}
+
+{/if}
diff --git a/packages/presentation/src/components/markup/NodeContent.svelte b/packages/presentation/src/components/markup/NodeContent.svelte
index 2986e53582..db31fd0f8d 100644
--- a/packages/presentation/src/components/markup/NodeContent.svelte
+++ b/packages/presentation/src/components/markup/NodeContent.svelte
@@ -17,6 +17,7 @@
import { AttrValue, MarkupNode, MarkupNodeType } from '@hcengineering/text'
import MarkupNodes from './Nodes.svelte'
+ import CodeBlockNode from './CodeBlockNode.svelte'
import ObjectNode from './ObjectNode.svelte'
export let node: MarkupNode
@@ -71,7 +72,7 @@
{:else if node.type === MarkupNodeType.code_block}
-
+
{:else if node.type === MarkupNodeType.image}
{@const src = toString(attrs.src)}
{@const alt = toString(attrs.alt)}
diff --git a/packages/theme/styles/_colors.scss b/packages/theme/styles/_colors.scss
index 02ba32a4dd..a78a677c6b 100644
--- a/packages/theme/styles/_colors.scss
+++ b/packages/theme/styles/_colors.scss
@@ -77,7 +77,6 @@
--text-editor-highlighted-node-delete-background-color: #F6DCDA;
--text-editor-highlighted-node-delete-font-color: #54201C;
- --text-editor-inline-code-color: #B02B46;
--text-editor-table-marker-color: #bebebf;
--theme-clockface-sec-arrow: conic-gradient(at 50% -10px, rgba(255, 0, 0, 0), rgba(255, 0, 0, 0) 49%, #F47758 50%, rgba(255, 0, 0, 0) 51%, rgba(255, 0, 0, 0) 100%);
diff --git a/packages/theme/styles/prose.scss b/packages/theme/styles/prose.scss
index a75196081a..cc905be1b4 100644
--- a/packages/theme/styles/prose.scss
+++ b/packages/theme/styles/prose.scss
@@ -345,7 +345,6 @@ table.proseTable {
margin: 0 1px;
padding: 0 .25rem;
font-family: var(--mono-font);
- color: var(--text-editor-inline-code-color);
background-color: var(--theme-button-default);
border: 1px solid var(--theme-button-border);
border-radius: .25rem;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 3c868d4bcf..cecc1cad46 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -33,6 +33,7 @@
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"@types/jest": "^29.5.5",
+ "@types/dompurify": "^3.0.5",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"svelte-eslint-parser": "^0.33.1"
@@ -47,6 +48,7 @@
"emoji-regex": "^10.1.0",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
+ "dompurify": "^3.1.6",
"@hcengineering/analytics": "^0.6.0"
},
"repository": "https://github.com/hcenginneing/anticrm",
diff --git a/packages/ui/src/components/Html.svelte b/packages/ui/src/components/Html.svelte
new file mode 100644
index 0000000000..679ac0e1a1
--- /dev/null
+++ b/packages/ui/src/components/Html.svelte
@@ -0,0 +1,23 @@
+
+
+
+{@html sanitized}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 848610f51c..2a14bf1aea 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -96,6 +96,7 @@ export { default as DatePresenter } from './components/calendar/DatePresenter.sv
export { default as DueDatePresenter } from './components/calendar/DueDatePresenter.svelte'
export { default as DateTimePresenter } from './components/calendar/DateTimePresenter.svelte'
export { default as TimeInputBox } from './components/calendar/TimeInputBox.svelte'
+export { default as Html } from './components/Html.svelte'
export { default as StylishEdit } from './components/StylishEdit.svelte'
export { default as Grid } from './components/Grid.svelte'
export { default as Row } from './components/Row.svelte'
diff --git a/plugins/diffview-resources/src/components/Highlight.svelte b/plugins/diffview-resources/src/components/Highlight.svelte
new file mode 100644
index 0000000000..cb731cabcf
--- /dev/null
+++ b/plugins/diffview-resources/src/components/Highlight.svelte
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/plugins/diffview-resources/src/highlight/highlight.ts b/plugins/diffview-resources/src/highlight/highlight.ts
index 96c37bca6c..71109341fd 100644
--- a/plugins/diffview-resources/src/highlight/highlight.ts
+++ b/plugins/diffview-resources/src/highlight/highlight.ts
@@ -19,18 +19,18 @@ import { hljsDefineSvelte } from './languages/svelte-hljs'
hljs.registerLanguage('svelte', hljsDefineSvelte)
export interface HighlightOptions {
- language: string
+ language: string | undefined
}
export function highlightText (text: string, options: HighlightOptions): string {
// We should always use highlighter because it sanitizes the input
// We have to always use highlighter to ensure that the input is sanitized
- const validLanguage = options.language !== '' && hljs.getLanguage(options.language) !== undefined
- const language = validLanguage ? options.language : 'text'
+ const { language } = options
+ const validLanguage = language !== undefined && hljs.getLanguage(language) !== undefined
- const { value: highlighted } = hljs.highlight(text, { language })
- const normalized = normalizeHighlightTags(highlighted)
- return normalized
+ const { value: highlighted } = validLanguage ? hljs.highlight(text, { language }) : hljs.highlightAuto(text)
+
+ return normalizeHighlightTags(highlighted)
}
export function highlightLines (lines: string[], options: HighlightOptions): string[] {
diff --git a/plugins/diffview-resources/src/index.ts b/plugins/diffview-resources/src/index.ts
index 4d1c1636e6..0133d541a9 100644
--- a/plugins/diffview-resources/src/index.ts
+++ b/plugins/diffview-resources/src/index.ts
@@ -15,10 +15,12 @@
import { type Resources } from '@hcengineering/platform'
import DiffView from './components/DiffView.svelte'
+import Highlight from './components/Highlight.svelte'
import InlineDiffView from './components/InlineDiffView.svelte'
export default async (): Promise => ({
component: {
DiffView,
- InlineDiffView
+ InlineDiffView,
+ Highlight
}
})
diff --git a/plugins/diffview/src/index.ts b/plugins/diffview/src/index.ts
index f7be7e7c07..7d73b026f0 100644
--- a/plugins/diffview/src/index.ts
+++ b/plugins/diffview/src/index.ts
@@ -48,7 +48,8 @@ export interface DiffFileId {
export default plugin(diffviewId, {
component: {
DiffView: '' as AnyComponent,
- InlineDiffView: '' as AnyComponent
+ InlineDiffView: '' as AnyComponent,
+ Highlight: '' as AnyComponent
},
string: {
ViewMode: '' as IntlString,
diff --git a/plugins/text-editor-resources/src/components/extension/codeblock.ts b/plugins/text-editor-resources/src/components/extension/codeblock.ts
index 1b82a0bf9f..95375706c5 100644
--- a/plugins/text-editor-resources/src/components/extension/codeblock.ts
+++ b/plugins/text-editor-resources/src/components/extension/codeblock.ts
@@ -13,12 +13,13 @@
// limitations under the License.
//
+import { codeBlockOptions } from '@hcengineering/text'
import { DropdownLabelsPopup, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { type CodeBlockLowlightOptions, CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'
-import { type createLowlight } from 'lowlight'
+import { common, createLowlight } from 'lowlight'
type Lowlight = ReturnType
@@ -26,14 +27,19 @@ const chevronSvg = `
`
-export const CodeBlockExtension = CodeBlockLowlight.extend({
+export const codeBlockHighlightOptions: CodeBlockLowlightOptions = {
+ ...codeBlockOptions,
+ lowlight: createLowlight(common)
+}
+
+export const CodeBlockHighlighExtension = CodeBlockLowlight.extend({
addProseMirrorPlugins () {
return [...(this.parent?.() ?? []), LanguageSelector(this.options)]
}
})
export function LanguageSelector (options: CodeBlockLowlightOptions): Plugin {
- return new Plugin({
+ return new Plugin({
key: new PluginKey('codeblock-language-selector'),
props: {
decorations (state) {
@@ -41,13 +47,14 @@ export function LanguageSelector (options: CodeBlockLowlightOptions): Plugin {
}
},
state: {
- init () {
- return DecorationSet.empty
+ init (config, state) {
+ return createDecorations(state.doc, options)
},
apply (tr, prev) {
if (tr.docChanged) {
return createDecorations(tr.doc, options)
}
+
return prev
}
}
@@ -84,7 +91,7 @@ function createDecorations (doc: ProseMirrorNode, options: CodeBlockLowlightOpti
function createLangButton (language: string | null): HTMLButtonElement {
const button = document.createElement('button')
- button.className = 'antiButton ghost small sh-no-shape bs-none gap-medium iconR'
+ button.className = 'antiButton link-bordered small sh-no-shape bs-none gap-medium iconR'
button.style.position = 'absolute'
button.style.top = '0.375rem'
button.style.right = '0.375rem'
diff --git a/plugins/text-editor-resources/src/kits/default-kit.ts b/plugins/text-editor-resources/src/kits/default-kit.ts
index 8158107a84..facd5681dc 100644
--- a/plugins/text-editor-resources/src/kits/default-kit.ts
+++ b/plugins/text-editor-resources/src/kits/default-kit.ts
@@ -13,7 +13,7 @@
// limitations under the License.
//
-import { codeBlockOptions, codeOptions } from '@hcengineering/text'
+import { codeOptions } from '@hcengineering/text'
import { showPopup } from '@hcengineering/ui'
import { type Editor, Extension } from '@tiptap/core'
import type { CodeOptions } from '@tiptap/extension-code'
@@ -25,10 +25,9 @@ import Link from '@tiptap/extension-link'
import Typography from '@tiptap/extension-typography'
import Underline from '@tiptap/extension-underline'
import StarterKit from '@tiptap/starter-kit'
-import { common, createLowlight } from 'lowlight'
import LinkPopup from '../components/LinkPopup.svelte'
-import { CodeBlockExtension } from '../components/extension/codeblock'
+import { CodeBlockHighlighExtension, codeBlockHighlightOptions } from '../components/extension/codeblock'
export interface DefaultKitOptions {
codeBlock?: Partial | false
@@ -66,10 +65,7 @@ export const DefaultKit = Extension.create({
openOnClick: true,
HTMLAttributes: { class: 'cursor-pointer', rel: 'noopener noreferrer', target: '_blank' }
}),
- CodeBlockExtension.configure({
- ...codeBlockOptions,
- lowlight: createLowlight(common)
- })
+ CodeBlockHighlighExtension.configure(codeBlockHighlightOptions)
]
}
})
diff --git a/plugins/text-editor-resources/src/kits/editor-kit.ts b/plugins/text-editor-resources/src/kits/editor-kit.ts
index 39546867f9..a852f7edde 100644
--- a/plugins/text-editor-resources/src/kits/editor-kit.ts
+++ b/plugins/text-editor-resources/src/kits/editor-kit.ts
@@ -15,7 +15,7 @@
import { type Class, type Doc, type Ref, type Space } from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { getBlobRef, getClient } from '@hcengineering/presentation'
-import { CodeBlockExtension, codeBlockOptions, CodeExtension, codeOptions } from '@hcengineering/text'
+import { CodeExtension, codeOptions } from '@hcengineering/text'
import textEditor, { type ActionContext, type ExtensionCreator, type TextEditorMode } from '@hcengineering/text-editor'
import { type AnyExtension, type Editor, Extension } from '@tiptap/core'
import { type Level } from '@tiptap/extension-heading'
@@ -23,6 +23,7 @@ import ListKeymap from '@tiptap/extension-list-keymap'
import TableHeader from '@tiptap/extension-table-header'
import 'prosemirror-codemark/dist/codemark.css'
+import { CodeBlockHighlighExtension, codeBlockHighlightOptions } from '../components/extension/codeblock'
import { NoteExtension, type NoteOptions } from '../components/extension/note'
import { FileExtension, type FileOptions } from '../components/extension/fileExt'
import { HardBreakExtension } from '../components/extension/hardBreak'
@@ -171,7 +172,7 @@ async function buildEditorKit (): Promise> {
}
})
],
- [200, CodeBlockExtension.configure(codeBlockOptions)],
+ [200, CodeBlockHighlighExtension.configure(codeBlockHighlightOptions)],
[210, CodeExtension.configure(codeOptions)],
[220, HardBreakExtension.configure({ shortcuts: mode })]
]