mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 19:58:09 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
bfba2a6cf8
File diff suppressed because it is too large
Load Diff
@ -1213,11 +1213,12 @@ export function devTool (
|
|||||||
throw new Error('Datalake storage config is required')
|
throw new Error('Datalake storage config is required')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolCtx.info('using datalake', { datalake: datalakeConfig })
|
||||||
const datalake = createDatalakeClient(datalakeConfig as DatalakeConfig)
|
const datalake = createDatalakeClient(datalakeConfig as DatalakeConfig)
|
||||||
|
|
||||||
let workspaces: Workspace[] = []
|
let workspaces: Workspace[] = []
|
||||||
const { dbUrl } = prepareTools()
|
const accountUrl = getAccountDBUrl()
|
||||||
await withDatabase(dbUrl, async (db) => {
|
await withDatabase(accountUrl, async (db) => {
|
||||||
workspaces = await listWorkspacesPure(db)
|
workspaces = await listWorkspacesPure(db)
|
||||||
workspaces = workspaces
|
workspaces = workspaces
|
||||||
.filter((p) => p.mode !== 'archived')
|
.filter((p) => p.mode !== 'archived')
|
||||||
|
@ -282,10 +282,19 @@ export async function copyToDatalake (
|
|||||||
let time = Date.now()
|
let time = Date.now()
|
||||||
let processedCnt = 0
|
let processedCnt = 0
|
||||||
let skippedCnt = 0
|
let skippedCnt = 0
|
||||||
|
let failedCnt = 0
|
||||||
|
|
||||||
function printStats (): void {
|
function printStats (): void {
|
||||||
const duration = Date.now() - time
|
const duration = Date.now() - time
|
||||||
console.log('...processed', processedCnt, 'skipped', skippedCnt, Math.round(duration / 1000) + 's')
|
console.log(
|
||||||
|
'...processed',
|
||||||
|
processedCnt,
|
||||||
|
'skipped',
|
||||||
|
skippedCnt,
|
||||||
|
'failed',
|
||||||
|
failedCnt,
|
||||||
|
Math.round(duration / 1000) + 's'
|
||||||
|
)
|
||||||
|
|
||||||
time = Date.now()
|
time = Date.now()
|
||||||
}
|
}
|
||||||
@ -319,6 +328,7 @@ export async function copyToDatalake (
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('failed to process blob', objectName, err)
|
console.error('failed to process blob', objectName, err)
|
||||||
|
failedCnt++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ table.proseTable {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
> * {
|
>* {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,15 +72,19 @@ table.proseTable {
|
|||||||
left: var(--table-selection-border-indent);
|
left: var(--table-selection-border-indent);
|
||||||
right: var(--table-selection-border-indent);
|
right: var(--table-selection-border-indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__border-top::before {
|
&__border-top::before {
|
||||||
border-top-width: var(--table-selection-border-width);
|
border-top-width: var(--table-selection-border-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__border-bottom::before {
|
&__border-bottom::before {
|
||||||
border-bottom-width: var(--table-selection-border-width);
|
border-bottom-width: var(--table-selection-border-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__border-left::before {
|
&__border-left::before {
|
||||||
border-left-width: var(--table-selection-border-width);
|
border-left-width: var(--table-selection-border-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__border-right::before {
|
&__border-right::before {
|
||||||
border-right-width: var(--table-selection-border-width);
|
border-right-width: var(--table-selection-border-width);
|
||||||
}
|
}
|
||||||
@ -276,7 +280,9 @@ table.proseTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover + .table-insert-marker { opacity: 1; }
|
&:hover+.table-insert-marker {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-insert-marker {
|
.table-insert-marker {
|
||||||
@ -381,19 +387,94 @@ pre.proseCodeBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fixes for MessageViewer
|
// Fixes for MessageViewer
|
||||||
pre.proseCodeBlock > pre.proseCode {
|
pre.proseCodeBlock>pre.proseCode {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proseHeading { line-height: 110%; }
|
.proseHeading {
|
||||||
|
line-height: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
// Fixes for cursors
|
// Fixes for cursors
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
h1, h2, h3, p, pre, code { cursor: text; }
|
|
||||||
p div { cursor: auto; }
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
p,
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
p div {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.proseMermaidDiagram {
|
||||||
|
--border-color: transparent;
|
||||||
|
|
||||||
|
cursor: auto;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: .25rem;
|
||||||
|
|
||||||
|
|
||||||
|
&:not(.selected) header {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.folded) {
|
||||||
|
--border-color: var(--theme-button-border);
|
||||||
|
|
||||||
|
header {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
--border-color: var(--theme-editbox-focus-border)
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
user-select: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
top: 0.5rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
>code {
|
||||||
|
display: block;
|
||||||
|
padding: 0.75rem;
|
||||||
|
user-select: text;
|
||||||
|
font-family: var(--mono-font);
|
||||||
|
color: var(--theme-content-color);
|
||||||
|
|
||||||
|
min-height: 3rem;
|
||||||
|
|
||||||
|
background: var(--theme-button-default);
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaidPreviewContainer {
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.folded) .mermaidPreviewContainer {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
min-height: 6rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
|
@ -39,7 +39,6 @@
|
|||||||
"FullDescription": "Full description",
|
"FullDescription": "Full description",
|
||||||
"NoFullDescription": "There are no detailed description",
|
"NoFullDescription": "There are no detailed description",
|
||||||
"EnableDiffMode": "Diff mode",
|
"EnableDiffMode": "Diff mode",
|
||||||
|
|
||||||
"AddColumnBefore": "Add before",
|
"AddColumnBefore": "Add before",
|
||||||
"AddColumnAfter": "Add after",
|
"AddColumnAfter": "Add after",
|
||||||
"DeleteColumn": "Delete",
|
"DeleteColumn": "Delete",
|
||||||
@ -48,7 +47,6 @@
|
|||||||
"DeleteRow": "Delete",
|
"DeleteRow": "Delete",
|
||||||
"DeleteTable": "Delete",
|
"DeleteTable": "Delete",
|
||||||
"Duplicate": "Duplicate",
|
"Duplicate": "Duplicate",
|
||||||
|
|
||||||
"CategoryRow": "Rows",
|
"CategoryRow": "Rows",
|
||||||
"CategoryColumn": "Columns",
|
"CategoryColumn": "Columns",
|
||||||
"Table": "Table",
|
"Table": "Table",
|
||||||
@ -60,6 +58,7 @@
|
|||||||
"Image": "Image",
|
"Image": "Image",
|
||||||
"SeparatorLine": "Separator line",
|
"SeparatorLine": "Separator line",
|
||||||
"TodoList": "Action item",
|
"TodoList": "Action item",
|
||||||
"DrawingBoard": "Drawing board"
|
"DrawingBoard": "Drawing board",
|
||||||
|
"MermaidDiargram": "Diagram"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,7 +39,6 @@
|
|||||||
"FullDescription": "Детальное описание",
|
"FullDescription": "Детальное описание",
|
||||||
"NoFullDescription": "Нет детального описания",
|
"NoFullDescription": "Нет детального описания",
|
||||||
"EnableDiffMode": "Режим сравнения",
|
"EnableDiffMode": "Режим сравнения",
|
||||||
|
|
||||||
"AddColumnBefore": "Добавить до",
|
"AddColumnBefore": "Добавить до",
|
||||||
"AddColumnAfter": "Добавить после",
|
"AddColumnAfter": "Добавить после",
|
||||||
"DeleteColumn": "Удалить",
|
"DeleteColumn": "Удалить",
|
||||||
@ -48,7 +47,6 @@
|
|||||||
"DeleteRow": "Удалить",
|
"DeleteRow": "Удалить",
|
||||||
"DeleteTable": "Удалить",
|
"DeleteTable": "Удалить",
|
||||||
"Duplicate": "Дублировать",
|
"Duplicate": "Дублировать",
|
||||||
|
|
||||||
"CategoryRow": "Строки",
|
"CategoryRow": "Строки",
|
||||||
"CategoryColumn": "Колонки",
|
"CategoryColumn": "Колонки",
|
||||||
"Table": "Таблица",
|
"Table": "Таблица",
|
||||||
@ -60,6 +58,7 @@
|
|||||||
"Image": "Изображение",
|
"Image": "Изображение",
|
||||||
"SeparatorLine": "Разделительная линия",
|
"SeparatorLine": "Разделительная линия",
|
||||||
"TodoList": "Действие",
|
"TodoList": "Действие",
|
||||||
"DrawingBoard": "Доска"
|
"DrawingBoard": "Доска",
|
||||||
|
"MermaidDiargram": "Диаграмма"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -83,6 +83,8 @@
|
|||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"lib0": "^0.2.88",
|
"lib0": "^0.2.88",
|
||||||
"y-indexeddb": "^9.0.12",
|
"y-indexeddb": "^9.0.12",
|
||||||
"lowlight": "^3.1.0"
|
"lowlight": "^3.1.0",
|
||||||
|
"mermaid": "~11.4.1",
|
||||||
|
"@hcengineering/theme": "^0.6.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
import { LeftMenuExtension } from './extension/leftMenu'
|
import { LeftMenuExtension } from './extension/leftMenu'
|
||||||
import { type FileAttachFunction } from './extension/types'
|
import { type FileAttachFunction } from './extension/types'
|
||||||
import { completionConfig, inlineCommandsConfig } from './extensions'
|
import { completionConfig, inlineCommandsConfig } from './extensions'
|
||||||
|
import { MermaidExtension, mermaidOptions } from './extension/mermaid'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
export let attribute: KeyedAttribute
|
export let attribute: KeyedAttribute
|
||||||
@ -279,7 +280,8 @@
|
|||||||
{ id: 'code-block', label: textEditor.string.CodeBlock, icon: view.icon.CodeBlock },
|
{ id: 'code-block', label: textEditor.string.CodeBlock, icon: view.icon.CodeBlock },
|
||||||
{ id: 'separator-line', label: textEditor.string.SeparatorLine, icon: view.icon.SeparatorLine },
|
{ id: 'separator-line', label: textEditor.string.SeparatorLine, icon: view.icon.SeparatorLine },
|
||||||
{ id: 'todo-list', label: textEditor.string.TodoList, icon: view.icon.TodoList },
|
{ id: 'todo-list', label: textEditor.string.TodoList, icon: view.icon.TodoList },
|
||||||
{ id: 'drawing-board', label: textEditor.string.DrawingBoard, icon: IconScribble as any }
|
{ id: 'drawing-board', label: textEditor.string.DrawingBoard, icon: IconScribble as any },
|
||||||
|
{ id: 'mermaid', label: textEditor.string.MermaidDiargram, icon: view.icon.Model }
|
||||||
],
|
],
|
||||||
handleSelect: handleLeftMenuClick
|
handleSelect: handleLeftMenuClick
|
||||||
})
|
})
|
||||||
@ -375,6 +377,9 @@
|
|||||||
case 'drawing-board':
|
case 'drawing-board':
|
||||||
editor.commands.insertContentAt(pos, { type: 'drawingBoard', attrs: { id: generateId() } })
|
editor.commands.insertContentAt(pos, { type: 'drawingBoard', attrs: { id: generateId() } })
|
||||||
break
|
break
|
||||||
|
case 'mermaid':
|
||||||
|
editor.commands.insertContentAt(pos, { type: 'mermaid' })
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,6 +471,7 @@
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
EmojiExtension,
|
EmojiExtension,
|
||||||
|
MermaidExtension.configure({ ...mermaidOptions, ydoc, ydocContentField: field }),
|
||||||
DrawingBoardExtension.configure({ getSavedBoard }),
|
DrawingBoardExtension.configure({ getSavedBoard }),
|
||||||
...extensions
|
...extensions
|
||||||
],
|
],
|
||||||
|
@ -15,12 +15,23 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SearchResultDoc } from '@hcengineering/core'
|
import { SearchResultDoc } from '@hcengineering/core'
|
||||||
import presentation, { SearchResult, reduceCalls, searchFor, type SearchItem } from '@hcengineering/presentation'
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
import presentation, {
|
||||||
|
SearchResult,
|
||||||
|
getClient,
|
||||||
|
reduceCalls,
|
||||||
|
searchFor,
|
||||||
|
type SearchItem
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
|
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
export let query: string = ''
|
export let query: string = ''
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
let items: SearchItem[] = []
|
let items: SearchItem[] = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -29,10 +40,21 @@
|
|||||||
let scrollContainer: HTMLElement
|
let scrollContainer: HTMLElement
|
||||||
let selection = 0
|
let selection = 0
|
||||||
|
|
||||||
function dispatchItem (item: SearchResultDoc): void {
|
async function getIdentifier (item: SearchResultDoc): Promise<string | undefined> {
|
||||||
|
const identifierProvider = hierarchy.classHierarchyMixin(item.doc._class, view.mixin.ObjectIdentifier)
|
||||||
|
if (identifierProvider === undefined) {
|
||||||
|
return item.shortTitle ?? item.title
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = await getResource(identifierProvider.provider)
|
||||||
|
return await resource(client, item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSelectItem (item: SearchResultDoc): Promise<void> {
|
||||||
|
const identifier = await getIdentifier(item)
|
||||||
dispatch('close', {
|
dispatch('close', {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.shortTitle ?? item.title,
|
label: identifier,
|
||||||
objectclass: item.doc._class
|
objectclass: item.doc._class
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -58,7 +80,7 @@
|
|||||||
key.stopPropagation()
|
key.stopPropagation()
|
||||||
if (selection < items.length) {
|
if (selection < items.length) {
|
||||||
const searchItem = items[selection]
|
const searchItem = items[selection]
|
||||||
dispatchItem(searchItem.item)
|
void handleSelectItem(searchItem.item)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -102,7 +124,7 @@
|
|||||||
<div
|
<div
|
||||||
class="ap-menuItem withComp h-8"
|
class="ap-menuItem withComp h-8"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
dispatchItem(doc)
|
void handleSelectItem(doc)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SearchResult value={doc} />
|
<SearchResult value={doc} />
|
||||||
|
@ -0,0 +1,711 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { codeBlockOptions } from '@hcengineering/text'
|
||||||
|
import { type CodeBlockLowlightOptions, CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
|
||||||
|
import { type Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||||
|
import { NodeSelection, Plugin, PluginKey, TextSelection, type Transaction } from '@tiptap/pm/state'
|
||||||
|
import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'
|
||||||
|
import { createLowlight } from 'lowlight'
|
||||||
|
import { isChangeEditable } from './editable'
|
||||||
|
import type { MermaidConfig } from 'mermaid'
|
||||||
|
import { getCurrentTheme, isThemeDark, themeStore } from '@hcengineering/theme'
|
||||||
|
|
||||||
|
import { createRelativePositionFromTypeIndex, type RelativePosition, type Doc as YDoc } from 'yjs'
|
||||||
|
import { mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
|
export interface MermaidOptions extends CodeBlockLowlightOptions {
|
||||||
|
ydoc?: YDoc
|
||||||
|
ydocContentField?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mermaidOptions: CodeBlockLowlightOptions = {
|
||||||
|
...codeBlockOptions,
|
||||||
|
lowlight: createLowlight({ mermaid: mermaidHLJS }),
|
||||||
|
defaultLanguage: 'mermaid'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mermaidMetaTxField = 'mermaid-meta-tx'
|
||||||
|
|
||||||
|
interface TxMetaContainer {
|
||||||
|
nodePatch?: NodePatchSpec
|
||||||
|
renderResult?: MermaidRenderResult
|
||||||
|
updateDecorations?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NodeDecorationState {
|
||||||
|
type: 'mermaid'
|
||||||
|
|
||||||
|
diagramBuilder: (view: EditorView) => MermaidRenderResult | null
|
||||||
|
|
||||||
|
folded: boolean
|
||||||
|
textContent: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTxMeta (tx?: Transaction): TxMetaContainer | undefined {
|
||||||
|
return tx?.getMeta('mermaid-meta-tx')
|
||||||
|
}
|
||||||
|
function setTxMeta (tx: Transaction, meta: TxMetaContainer): Transaction {
|
||||||
|
return tx.setMeta(mermaidMetaTxField, meta).setMeta('addToHistory', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NodePatchSpec {
|
||||||
|
pos: number
|
||||||
|
folded: boolean
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MermaidExtension = CodeBlockLowlight.extend<MermaidOptions>({
|
||||||
|
name: 'mermaid',
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
draggable: true,
|
||||||
|
selectable: true,
|
||||||
|
|
||||||
|
parseHTML () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: 'div.mermaid-diagram',
|
||||||
|
preserveWhitespace: 'full'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes () {
|
||||||
|
return {
|
||||||
|
language: {
|
||||||
|
default: 'mermaid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML ({ node, HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
'div',
|
||||||
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||||
|
class: 'mermaid-diagram'
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
'code',
|
||||||
|
{
|
||||||
|
class: node.attrs.language !== undefined ? this.options.languageClassPrefix + node.attrs.language : null
|
||||||
|
},
|
||||||
|
0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands () {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
|
||||||
|
addInputRules () {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins () {
|
||||||
|
return [...(this.parent?.() ?? []), MermaidDecorator(this.options)]
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView () {
|
||||||
|
return ({ getPos, editor, node, decorations }) => {
|
||||||
|
const containerNode = document.createElement('pre')
|
||||||
|
containerNode.className = 'proseMermaidDiagram'
|
||||||
|
|
||||||
|
const headerNode = containerNode.appendChild(document.createElement('header'))
|
||||||
|
headerNode.contentEditable = 'false'
|
||||||
|
|
||||||
|
const contentNode = containerNode.appendChild(document.createElement('code'))
|
||||||
|
contentNode.translate = false
|
||||||
|
contentNode.spellcheck = false
|
||||||
|
|
||||||
|
const previewNode = containerNode.appendChild(document.createElement('div'))
|
||||||
|
previewNode.className = 'mermaidPreviewContainer'
|
||||||
|
previewNode.contentEditable = 'false'
|
||||||
|
|
||||||
|
const languageLabelNode = headerNode.appendChild(document.createElement('button'))
|
||||||
|
|
||||||
|
const languageLabelSpanNode = languageLabelNode.appendChild(document.createElement('span'))
|
||||||
|
languageLabelSpanNode.className = 'overflow-label label disabled mr-2'
|
||||||
|
languageLabelSpanNode.textContent = 'mermaid'
|
||||||
|
|
||||||
|
const toggleButtonNode = headerNode.appendChild(document.createElement('button'))
|
||||||
|
|
||||||
|
const toggleButtonIconNode = toggleButtonNode.appendChild(document.createElement('div'))
|
||||||
|
toggleButtonIconNode.className = 'btn-icon'
|
||||||
|
|
||||||
|
let nodeState: NodeDecorationState = {
|
||||||
|
type: 'mermaid',
|
||||||
|
folded: false,
|
||||||
|
selected: false,
|
||||||
|
diagramBuilder: () => null,
|
||||||
|
textContent: node.textContent
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFoldState = (newState: boolean, event?: MouseEvent): void => {
|
||||||
|
event?.preventDefault()
|
||||||
|
if (typeof getPos !== 'function') return
|
||||||
|
|
||||||
|
const pos = getPos()
|
||||||
|
const node = editor.view.state.doc.nodeAt(pos)
|
||||||
|
|
||||||
|
if (node?.type.name !== MermaidExtension.name) return
|
||||||
|
|
||||||
|
const nodePatch: NodePatchSpec = {
|
||||||
|
pos,
|
||||||
|
folded: !nodeState.folded,
|
||||||
|
selected: nodeState.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
const tr = setTxMeta(editor.view.state.tr, { nodePatch })
|
||||||
|
|
||||||
|
if (nodePatch.folded) {
|
||||||
|
tr.setSelection(NodeSelection.create(editor.view.state.doc, pos))
|
||||||
|
} else {
|
||||||
|
const selection = TextSelection.findFrom(editor.view.state.doc.resolve(pos + node.nodeSize), -1)
|
||||||
|
if (selection !== null) tr.setSelection(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.view.dispatch(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleButtonNode.onmousedown = (e) => {
|
||||||
|
toggleFoldState(!nodeState.folded, e)
|
||||||
|
}
|
||||||
|
previewNode.ondblclick = (e) => {
|
||||||
|
toggleFoldState(!nodeState.folded, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
previewNode.onclick = (e) => {
|
||||||
|
if (typeof getPos !== 'function') return
|
||||||
|
const pos = getPos()
|
||||||
|
const selection = NodeSelection.create(editor.view.state.doc, pos)
|
||||||
|
editor.view.dispatch(editor.view.state.tr.setSelection(selection))
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncState = (decorations: readonly Decoration[]): void => {
|
||||||
|
nodeState = decorations.find((d) => d.spec.type === MermaidExtension.name)?.spec ?? nodeState
|
||||||
|
|
||||||
|
const isEmpty = nodeState.textContent.trim().length === 0
|
||||||
|
const diagram = nodeState.diagramBuilder?.(editor.view) ?? null
|
||||||
|
const error = diagram?.error ?? null
|
||||||
|
const diagramNode = error === null ? diagram?.domFragments[0] ?? null : null
|
||||||
|
|
||||||
|
const allowFold = !isEmpty && error === null
|
||||||
|
|
||||||
|
if (nodeState.folded) {
|
||||||
|
containerNode.classList.add('folded')
|
||||||
|
contentNode.style.display = 'none'
|
||||||
|
languageLabelNode.style.display = 'none'
|
||||||
|
toggleButtonIconNode.innerHTML = editIconSvg
|
||||||
|
toggleButtonNode.className = 'antiButton primary medium sh-no-shape bs-none only-icon'
|
||||||
|
} else {
|
||||||
|
containerNode.classList.remove('folded')
|
||||||
|
contentNode.style.display = ''
|
||||||
|
languageLabelNode.style.display = ''
|
||||||
|
toggleButtonIconNode.innerHTML = foldIconSvg
|
||||||
|
toggleButtonNode.className = 'antiButton link-bordered medium sh-no-shape bs-none only-icon'
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleButtonNode.style.display = allowFold ? '' : 'none'
|
||||||
|
previewNode.style.display = diagramNode !== null ? '' : 'none'
|
||||||
|
|
||||||
|
if (nodeState.selected) {
|
||||||
|
containerNode.classList.add('selected')
|
||||||
|
} else {
|
||||||
|
containerNode.classList.remove('selected')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmpty && error !== null) {
|
||||||
|
languageLabelNode.className =
|
||||||
|
'antiButton negative medium sh-no-shape bs-none gap-medium iconR pointer-events-none'
|
||||||
|
} else {
|
||||||
|
languageLabelNode.className =
|
||||||
|
'antiButton link-bordered medium sh-no-shape bs-none gap-medium iconR pointer-events-none'
|
||||||
|
}
|
||||||
|
|
||||||
|
while (previewNode.firstChild !== null && previewNode.firstChild !== diagramNode) {
|
||||||
|
previewNode.removeChild(previewNode.firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagramNode?.parentElement !== previewNode) {
|
||||||
|
diagramNode?.parentElement?.removeChild(diagramNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagramNode !== null && previewNode.firstChild !== diagramNode) {
|
||||||
|
previewNode.appendChild(diagramNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowFold && nodeState.folded) {
|
||||||
|
toggleFoldState(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSelection = (newState: boolean): void => {
|
||||||
|
if (nodeState.selected === newState) return
|
||||||
|
if (typeof getPos !== 'function') return
|
||||||
|
const pos = getPos()
|
||||||
|
const tr = setTxMeta(editor.view.state.tr, {
|
||||||
|
nodePatch: {
|
||||||
|
pos,
|
||||||
|
folded: nodeState.folded,
|
||||||
|
selected: newState
|
||||||
|
}
|
||||||
|
})
|
||||||
|
editor.view.dispatch(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
syncState(decorations)
|
||||||
|
|
||||||
|
return {
|
||||||
|
dom: containerNode,
|
||||||
|
contentDOM: contentNode,
|
||||||
|
|
||||||
|
selectNode: () => {
|
||||||
|
toggleSelection(true)
|
||||||
|
},
|
||||||
|
deselectNode: () => {
|
||||||
|
toggleSelection(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
stopEvent: (event) => {
|
||||||
|
if (event instanceof DragEvent && !nodeState.folded) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: (node, decorations) => {
|
||||||
|
if (node.type.name !== MermaidExtension.name) return false
|
||||||
|
syncState(decorations)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
interface MermaidPluginState {
|
||||||
|
decorationSet: DecorationSet
|
||||||
|
decorationCache: Map<number | string, NodeDecorationState>
|
||||||
|
|
||||||
|
nodeidCache: Map<number | string, MermaidRenderResult>
|
||||||
|
renderCache: Map<string, MermaidRenderResult>
|
||||||
|
pendingFragments: Set<string>
|
||||||
|
|
||||||
|
throttle: ThrottledCaller<string | number>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MermaidRenderResult {
|
||||||
|
svg?: string
|
||||||
|
error?: {
|
||||||
|
name?: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
input: string
|
||||||
|
domFragments: HTMLElement[]
|
||||||
|
theme: MermaidConfig['theme']
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderMermaidDiagram (code: string, theme: MermaidConfig['theme']): Promise<MermaidRenderResult> {
|
||||||
|
// Has no practical effect now, but in case the Webpack
|
||||||
|
// configuration gets changed to split the vendor bundle, it might come in handy
|
||||||
|
const mermaid = (await import(/* webpackMode: "lazy-once" */ 'mermaid')).default
|
||||||
|
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
fontFamily: 'var(--font-family)',
|
||||||
|
logLevel: 5,
|
||||||
|
theme
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = `mermaid-diagram-${Math.random().toString(36).substring(2, 9)}`
|
||||||
|
|
||||||
|
const result: MermaidRenderResult = {
|
||||||
|
input: code,
|
||||||
|
domFragments: [],
|
||||||
|
theme
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const render = await mermaid.render(id, code)
|
||||||
|
result.svg = render.svg
|
||||||
|
} catch (e: any) {
|
||||||
|
if (typeof e?.message === 'string') {
|
||||||
|
result.error = {
|
||||||
|
name: e.name,
|
||||||
|
message: e.message
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.error = {
|
||||||
|
name: 'unknown',
|
||||||
|
message: 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildState (
|
||||||
|
options: MermaidOptions,
|
||||||
|
prev: MermaidPluginState,
|
||||||
|
doc: ProseMirrorNode,
|
||||||
|
tr?: Transaction
|
||||||
|
): MermaidPluginState {
|
||||||
|
const renderCache = new Map(prev.renderCache)
|
||||||
|
const nodeidCache = new Map<string | number, MermaidRenderResult>()
|
||||||
|
const decorationCache = new Map<string | number, NodeDecorationState>()
|
||||||
|
const pendingFragments = new Set(prev.pendingFragments)
|
||||||
|
const theme: MermaidConfig['theme'] = isThemeDark(getCurrentTheme()) ? 'dark' : 'default'
|
||||||
|
|
||||||
|
const unusedRenderCache = new Set(renderCache.keys())
|
||||||
|
const usedFragments = new Set<HTMLElement>()
|
||||||
|
|
||||||
|
const newRenderCacheEntry = getTxMeta(tr)?.renderResult
|
||||||
|
if (newRenderCacheEntry !== undefined) {
|
||||||
|
renderCache.set(newRenderCacheEntry.input, newRenderCacheEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildOrReuseFragment = (renderState: MermaidRenderResult | null): HTMLElement | null => {
|
||||||
|
if (renderState?.svg === undefined) return null
|
||||||
|
|
||||||
|
const reuse = renderState.domFragments.find((f) => !usedFragments.has(f))
|
||||||
|
if (reuse !== undefined) {
|
||||||
|
usedFragments.add(reuse)
|
||||||
|
return reuse
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.className = 'mermaidPreview'
|
||||||
|
container.innerHTML = renderState.svg
|
||||||
|
|
||||||
|
renderState.domFragments.push(container)
|
||||||
|
usedFragments.add(container)
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildDiagram = (
|
||||||
|
node: ProseMirrorNode,
|
||||||
|
nodeid: string | number,
|
||||||
|
view: EditorView,
|
||||||
|
theme: MermaidConfig['theme']
|
||||||
|
): MermaidRenderResult => {
|
||||||
|
const textContent = node.textContent
|
||||||
|
const nodeRenderState = renderCache.get(textContent)
|
||||||
|
const nodeTargetRenderState = nodeRenderState ??
|
||||||
|
prev.nodeidCache.get(nodeid) ?? { input: textContent, theme, domFragments: [] }
|
||||||
|
|
||||||
|
unusedRenderCache.delete(textContent)
|
||||||
|
|
||||||
|
nodeidCache.set(nodeid, nodeTargetRenderState)
|
||||||
|
const element = buildOrReuseFragment(nodeTargetRenderState)
|
||||||
|
|
||||||
|
if ((nodeRenderState === undefined && !pendingFragments.has(textContent)) || nodeRenderState?.theme !== theme) {
|
||||||
|
pendingFragments.add(textContent)
|
||||||
|
|
||||||
|
prev.throttle.call(nodeid, () => {
|
||||||
|
renderMermaidDiagram(textContent, theme).then(
|
||||||
|
(renderResult) => {
|
||||||
|
view.dispatch(setTxMeta(view.state.tr, { renderResult }))
|
||||||
|
pendingFragments.delete(textContent)
|
||||||
|
},
|
||||||
|
(reject) => {
|
||||||
|
console.log(reject)
|
||||||
|
pendingFragments.delete(textContent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...nodeTargetRenderState, domFragments: element !== null ? [element] : [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const decorations: Decoration[] = []
|
||||||
|
const lastDecorationSet = tr !== undefined ? prev.decorationSet.map(tr.mapping, tr.doc) : prev.decorationSet
|
||||||
|
|
||||||
|
const nodeStatePatch = getTxMeta(tr)?.nodePatch
|
||||||
|
|
||||||
|
let mIndex = 0
|
||||||
|
doc.descendants((node, pos, parent, index) => {
|
||||||
|
if (node.type.name !== MermaidExtension.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/yjs/y-prosemirror/issues/49
|
||||||
|
// Ideally, one would hope that decorations would be recovered through the use of
|
||||||
|
// transaction mapping, but with Yjs this is not possible in many cases.
|
||||||
|
// So there's a need for dirty hacks to keep the state of the node intact. (See below).
|
||||||
|
const yjsdoc = options.ydoc?.getXmlFragment(options.ydocContentField ?? 'content')
|
||||||
|
const yid =
|
||||||
|
yjsdoc !== undefined ? yRelativePositionToString(createRelativePositionFromTypeIndex(yjsdoc, index)) : undefined
|
||||||
|
const nodeid = yid ?? index
|
||||||
|
const oldCache = prev.decorationCache
|
||||||
|
const oldState =
|
||||||
|
(lastDecorationSet.find(pos, pos + node.nodeSize).find((d) => d.spec.type === MermaidExtension.name)
|
||||||
|
?.spec as NodeDecorationState) ??
|
||||||
|
(yid !== undefined ? oldCache.get(nodeid) : undefined) ??
|
||||||
|
(oldCache.get(mIndex)?.textContent === node.textContent ? oldCache.get(mIndex) : undefined)
|
||||||
|
|
||||||
|
const newState: NodeDecorationState = {
|
||||||
|
type: 'mermaid',
|
||||||
|
diagramBuilder: (view: EditorView) => buildDiagram(node, nodeid, view, theme),
|
||||||
|
folded: oldState?.folded ?? node.textContent.trim().length > 0,
|
||||||
|
selected: oldState?.selected ?? false,
|
||||||
|
textContent: node.textContent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeStatePatch !== undefined && pos === nodeStatePatch.pos) {
|
||||||
|
newState.folded = nodeStatePatch.folded
|
||||||
|
newState.selected = nodeStatePatch.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yid !== undefined) decorationCache.set(yid, newState)
|
||||||
|
decorationCache.set(mIndex, newState)
|
||||||
|
mIndex++
|
||||||
|
|
||||||
|
decorations.push(Decoration.node(pos, pos + node.nodeSize, {}, newState))
|
||||||
|
})
|
||||||
|
|
||||||
|
const decorationSet = DecorationSet.create(doc, decorations)
|
||||||
|
|
||||||
|
if (unusedRenderCache.size >= 16) {
|
||||||
|
for (const key of unusedRenderCache) {
|
||||||
|
renderCache.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
decorationSet,
|
||||||
|
decorationCache,
|
||||||
|
renderCache,
|
||||||
|
nodeidCache,
|
||||||
|
throttle: prev.throttle,
|
||||||
|
pendingFragments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MermaidDecorator (options: MermaidOptions): Plugin {
|
||||||
|
return new Plugin<MermaidPluginState>({
|
||||||
|
key: new PluginKey('mermaid-decorator'),
|
||||||
|
props: {
|
||||||
|
decorations (state) {
|
||||||
|
return this.getState(state)?.decorationSet
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
init (config, state) {
|
||||||
|
return buildState(
|
||||||
|
options,
|
||||||
|
{
|
||||||
|
decorationSet: DecorationSet.create(state.doc, []),
|
||||||
|
decorationCache: new Map(),
|
||||||
|
nodeidCache: new Map(),
|
||||||
|
renderCache: new Map(),
|
||||||
|
throttle: new ThrottledCaller(150),
|
||||||
|
pendingFragments: new Set()
|
||||||
|
},
|
||||||
|
state.doc
|
||||||
|
)
|
||||||
|
},
|
||||||
|
apply (tr, prev, oldState, newState) {
|
||||||
|
if (tr.docChanged || isChangeEditable(tr) || getTxMeta(tr) !== undefined) {
|
||||||
|
return buildState(options, prev, newState.doc, tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
},
|
||||||
|
view (editorView) {
|
||||||
|
const unsubscribe = themeStore.subscribe(() => {
|
||||||
|
editorView.dispatch(setTxMeta(editorView.state.tr, { updateDecorations: true }))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy () {
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThrottledCaller<T> {
|
||||||
|
timers = new Map<T, number>()
|
||||||
|
delay: number
|
||||||
|
|
||||||
|
lookup (key: T): number {
|
||||||
|
return this.timers.get(key) ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
update (key: T, increment: number = 0): number {
|
||||||
|
const ticks = this.lookup(key) + increment
|
||||||
|
this.timers.set(key, ticks)
|
||||||
|
if (ticks === 0) this.timers.delete(key)
|
||||||
|
return ticks
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (delay: number = 150) {
|
||||||
|
this.delay = delay
|
||||||
|
}
|
||||||
|
|
||||||
|
call (key: T, callback: () => void): void {
|
||||||
|
const instant = this.lookup(key) === 0
|
||||||
|
if (instant) callback()
|
||||||
|
|
||||||
|
this.update(key, 1)
|
||||||
|
setTimeout(() => {
|
||||||
|
const ticks = this.update(key, -1)
|
||||||
|
if (ticks === 0 && !instant) callback()
|
||||||
|
}, this.delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function yRelativePositionToString (id: RelativePosition): string | undefined {
|
||||||
|
if (id.item?.client === undefined) return
|
||||||
|
return `${id.item.client}_${id.item.clock}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no public implementation of memraid syntax for higlightjs, while implementations for
|
||||||
|
// other highlighting tools are either incomplete or outdated (even the one used in the official live editor).
|
||||||
|
// This is a crude (broken) and incomplete port of the grammar.
|
||||||
|
function mermaidHLJS (hljs: any): any {
|
||||||
|
return {
|
||||||
|
case_insensitive: false,
|
||||||
|
contains: [
|
||||||
|
// Comments
|
||||||
|
hljs.COMMENT('%%', '%%', {}),
|
||||||
|
|
||||||
|
// Style definitions
|
||||||
|
{
|
||||||
|
className: 'style',
|
||||||
|
begin: /^([ \t]*(?:classDef|linkStyle|style)[ \t]+[\w$-]+[ \t]+)\w.*[^\s;]/m,
|
||||||
|
keywords: {
|
||||||
|
name: 'classDef linkStyle style'
|
||||||
|
},
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'property',
|
||||||
|
begin: /\b\w[\w-]*(?=[ \t]*:)/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'operator',
|
||||||
|
begin: /:/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'punctuation',
|
||||||
|
begin: /,/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Inter-arrow labels
|
||||||
|
{
|
||||||
|
className: 'operator',
|
||||||
|
begin:
|
||||||
|
/([^<>ox.=-])(?:-[-.]|==)(?![<>ox.=-])[ \t]*(?:"[^"\r\n]*"|[^\s".=-](?:[^\r\n.=-]*[^\s.=-])?)[ \t]*(?:\.+->?|--+[->]|==+[=>])(?![<>ox.=-])/,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'operator',
|
||||||
|
begin: /(?:\.+->?|--+[->]|==+[=>])$/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'string',
|
||||||
|
begin: /^([\s\S]{2}[ \t]*)\S(?:[\s\S]*\S)?/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'arrow-head',
|
||||||
|
begin: /^\S+/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Arrow types
|
||||||
|
{
|
||||||
|
className: 'operator',
|
||||||
|
variants: [
|
||||||
|
{ begin: /(?<=^|[^{}|o.-])[|}][|o](?:--|\.\.)[|o][|{](?![{}|o.-])/ },
|
||||||
|
{
|
||||||
|
begin:
|
||||||
|
/(?<=^|[^<>ox.=-])(?:[<ox](?:==+|--+|-\.*-)[>ox]?|(?:==+|--+|-\.*-)[>ox]|===+|---+|-\.+-)(?![<>ox.=-])/
|
||||||
|
},
|
||||||
|
{ begin: /(?<=^|[^<>()x-])(?:--?(?:>>|[x>)])(?![<>()x])|(?:<<|[x<(])--?(?!-))/ },
|
||||||
|
{ begin: /(?<=^|[^<>|*o.-])(?:[*o]--|--[*o]|<\|?(?:--|\.\.)|(?:--|\.\.)\|?>|--|\.\.)(?![<>|*o.-])/ }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
{
|
||||||
|
className: 'string',
|
||||||
|
begin: /(^|[^|<])\|(?:[^\r\n"|]|"[^"\r\n]*")+\|/
|
||||||
|
},
|
||||||
|
|
||||||
|
// Text elements
|
||||||
|
{
|
||||||
|
className: 'string',
|
||||||
|
begin: /(?:[([{]+|\b>)(?:[^\r\n"()[\]{}]|"[^"\r\n]*")+(?:[)\]}]+|>)/
|
||||||
|
},
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
{
|
||||||
|
className: 'string',
|
||||||
|
begin: /"[^"\r\n]*"/
|
||||||
|
},
|
||||||
|
|
||||||
|
// Annotations
|
||||||
|
{
|
||||||
|
className: 'keyword',
|
||||||
|
begin: /<<(?:abstract|choice|enumeration|fork|interface|join|service)>>|\[\[(?:choice|fork|join)\]\]/i
|
||||||
|
},
|
||||||
|
|
||||||
|
// Keywords
|
||||||
|
{
|
||||||
|
className: 'keyword',
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
begin:
|
||||||
|
/(^[ \t]*)(?:action|callback|class|classDef|classDiagram|click|direction|erDiagram|flowchart|gantt|gitGraph|graph|journey|link|linkStyle|pie|requirementDiagram|sequenceDiagram|stateDiagram|stateDiagram-v2|style|subgraph)(?![\w$-])/m
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin:
|
||||||
|
/(^[ \t]*)(?:activate|alt|and|as|autonumber|deactivate|else|end(?:[ \t]+note)?|loop|opt|par|participant|rect|state|note[ \t]+(?:over|(?:left|right)[ \t]+of))(?![\w$-])/im
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Entities
|
||||||
|
{
|
||||||
|
className: 'variable',
|
||||||
|
begin: /#[a-z0-9]+;/
|
||||||
|
},
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
{
|
||||||
|
className: 'operator',
|
||||||
|
begin: /(\w[ \t]*)&(?=[ \t]*\w)|:::|:/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editIconSvg = `<svg class="svg-medium" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path d="M14,13.1H9.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5H14c0.3,0,0.5-0.2,0.5-0.5S14.3,13.1,14,13.1z"></path><path d="M11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1c1.2-1.6,1.3-1.6,1.3-1.6C12.9,5,13,4.5,12.9,4c-0.1-0.5-0.4-0.9-0.8-1.2 c0,0-1.1-0.9-1.1-0.9c-0.8-0.7-2.1-0.6-2.8,0.3c0,0,0,0,0,0l-6.3,7.9c-0.3,0.4-0.4,0.9-0.3,1.4l0.5,2.3c0.1,0.2,0.3,0.4,0.5,0.4 c0,0,0,0,0,0l2.4,0c0.5,0,1-0.2,1.3-0.6C8.9,10.2,10.5,8.2,11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1z M8.9,2.8c0.3-0.4,1-0.5,1.4-0.1 c0,0,1.2,0.9,1.2,0.9c0.2,0.1,0.4,0.3,0.4,0.6c0.1,0.2,0,0.5-0.1,0.7c0,0-0.4,0.5-0.9,1.2L8.1,3.9L8.9,2.8z M5.5,12.9 C5.4,13,5.2,13.1,5,13.1l-2,0l-0.5-1.9c0-0.2,0-0.4,0.1-0.5l4.8-6l2.8,2.2C8.9,8.6,6.8,11.2,5.5,12.9z"></path>
|
||||||
|
</svg>`
|
||||||
|
|
||||||
|
const foldIconSvg = `<svg class="svg-medium" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M14.1464 1.14645C14.3417 0.951184 14.6583 0.951184 14.8535 1.14645C15.0477 1.34063 15.0488 1.6548 14.8567 1.85031L10.707 5.99999H14V6.99999H8.99996V1.99999H9.99996V5.29299L14.1464 1.14645Z"></path>
|
||||||
|
<path d="M1.99996 8.99999V9.99999H5.29296L1.14645 14.1464C0.951184 14.3417 0.951184 14.6583 1.14645 14.8535C1.34171 15.0488 1.65829 15.0488 1.85355 14.8535L5.99996 10.707V14H6.99996V8.99999H1.99996Z"></path>
|
||||||
|
</svg>`
|
@ -149,7 +149,8 @@ export function inlineCommandsConfig (
|
|||||||
{ id: 'code-block', label: textEditor.string.CodeBlock, icon: view.icon.CodeBlock },
|
{ id: 'code-block', label: textEditor.string.CodeBlock, icon: view.icon.CodeBlock },
|
||||||
{ id: 'separator-line', label: textEditor.string.SeparatorLine, icon: view.icon.SeparatorLine },
|
{ id: 'separator-line', label: textEditor.string.SeparatorLine, icon: view.icon.SeparatorLine },
|
||||||
{ id: 'todo-list', label: textEditor.string.TodoList, icon: view.icon.TodoList },
|
{ id: 'todo-list', label: textEditor.string.TodoList, icon: view.icon.TodoList },
|
||||||
{ id: 'drawing-board', label: textEditor.string.DrawingBoard, icon: IconScribble as any }
|
{ id: 'drawing-board', label: textEditor.string.DrawingBoard, icon: IconScribble as any },
|
||||||
|
{ id: 'mermaid', label: textEditor.string.MermaidDiargram, icon: view.icon.Model }
|
||||||
].filter(({ id }) => !excludedCommands.includes(id as InlineCommandId))
|
].filter(({ id }) => !excludedCommands.includes(id as InlineCommandId))
|
||||||
},
|
},
|
||||||
command: ({ editor, range, props }: { editor: Editor, range: Range, props: any }) => {
|
command: ({ editor, range, props }: { editor: Editor, range: Range, props: any }) => {
|
||||||
|
@ -94,7 +94,8 @@ export default plugin(textEditorId, {
|
|||||||
Image: '' as IntlString,
|
Image: '' as IntlString,
|
||||||
SeparatorLine: '' as IntlString,
|
SeparatorLine: '' as IntlString,
|
||||||
TodoList: '' as IntlString,
|
TodoList: '' as IntlString,
|
||||||
DrawingBoard: '' as IntlString
|
DrawingBoard: '' as IntlString,
|
||||||
|
MermaidDiargram: '' as IntlString
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
Header1: '' as Asset,
|
Header1: '' as Asset,
|
||||||
|
Loading…
Reference in New Issue
Block a user