mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 04:08:19 +00:00
UBER-1117 Add ToDo to wiki documents (#3948)
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
parent
c9aceeaa8f
commit
1db7ec4d79
@ -82,8 +82,8 @@
|
|||||||
"@tiptap/extension-code": "^2.1.12",
|
"@tiptap/extension-code": "^2.1.12",
|
||||||
"@tiptap/extension-bubble-menu": "^2.1.12",
|
"@tiptap/extension-bubble-menu": "^2.1.12",
|
||||||
"@tiptap/extension-underline": "^2.1.12",
|
"@tiptap/extension-underline": "^2.1.12",
|
||||||
"@hocuspocus/provider": "^2.5.0",
|
|
||||||
"@tiptap/extension-list-keymap": "^2.1.12",
|
"@tiptap/extension-list-keymap": "^2.1.12",
|
||||||
|
"@hocuspocus/provider": "^2.5.0",
|
||||||
"slugify": "^1.6.6"
|
"slugify": "^1.6.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,9 +97,9 @@ export const Completion = Node.create<CompletionOptions>({
|
|||||||
|
|
||||||
addAttributes () {
|
addAttributes () {
|
||||||
return {
|
return {
|
||||||
id: getDataAttribute('id', null),
|
id: getDataAttribute('id'),
|
||||||
label: getDataAttribute('label', null),
|
label: getDataAttribute('label'),
|
||||||
objectclass: getDataAttribute('objectclass', null)
|
objectclass: getDataAttribute('objectclass')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
35
packages/text-editor/src/components/extension/todo.ts
Normal file
35
packages/text-editor/src/components/extension/todo.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { TaskItem } from '@tiptap/extension-task-item'
|
||||||
|
import { TaskList } from '@tiptap/extension-task-list'
|
||||||
|
|
||||||
|
import { getDataAttribute } from '../../utils'
|
||||||
|
|
||||||
|
export const TodoItemExtension = TaskItem.extend({
|
||||||
|
name: 'todoItem',
|
||||||
|
|
||||||
|
addOptions () {
|
||||||
|
return {
|
||||||
|
nested: false,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
taskListTypeName: 'todoList'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes () {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
todoid: getDataAttribute('todoid', { default: null, keepOnSplit: false }),
|
||||||
|
userid: getDataAttribute('userid', { default: null, keepOnSplit: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TodoListExtension = TaskList.extend({
|
||||||
|
name: 'todoList',
|
||||||
|
|
||||||
|
addOptions () {
|
||||||
|
return {
|
||||||
|
itemTypeName: 'todoItem',
|
||||||
|
HTMLAttributes: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -19,8 +19,8 @@ import Link from '@tiptap/extension-link'
|
|||||||
import Typography from '@tiptap/extension-typography'
|
import Typography from '@tiptap/extension-typography'
|
||||||
import { CompletionOptions } from '../Completion'
|
import { CompletionOptions } from '../Completion'
|
||||||
import MentionList from './MentionList.svelte'
|
import MentionList from './MentionList.svelte'
|
||||||
import { SvelteRenderer } from './SvelteRenderer'
|
|
||||||
import { NodeUuidExtension } from './extension/nodeUuid'
|
import { NodeUuidExtension } from './extension/nodeUuid'
|
||||||
|
import { SvelteRenderer } from './node-view'
|
||||||
|
|
||||||
export const tableExtensions = [
|
export const tableExtensions = [
|
||||||
Table.configure({
|
Table.configure({
|
||||||
@ -164,9 +164,12 @@ export const completionConfig: Partial<CompletionOptions> = {
|
|||||||
return {
|
return {
|
||||||
onStart: (props: any) => {
|
onStart: (props: any) => {
|
||||||
component = new SvelteRenderer(MentionList, {
|
component = new SvelteRenderer(MentionList, {
|
||||||
...props,
|
element: document.body,
|
||||||
close: () => {
|
props: {
|
||||||
component.destroy()
|
...props,
|
||||||
|
close: () => {
|
||||||
|
component.destroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
//
|
||||||
|
// Copyright © 2023 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.
|
||||||
|
//
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { getNodeViewContext } from './context'
|
||||||
|
|
||||||
|
export let as = 'div'
|
||||||
|
|
||||||
|
const { onContentElement } = getNodeViewContext()
|
||||||
|
|
||||||
|
let element: HTMLElement
|
||||||
|
$: if (element) {
|
||||||
|
element.style.whiteSpace = 'pre-wrap'
|
||||||
|
onContentElement(element)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={as} bind:this={element} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
//
|
||||||
|
// Copyright © 2023 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.
|
||||||
|
//
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount, tick } from 'svelte'
|
||||||
|
import { getNodeViewContext } from './context'
|
||||||
|
|
||||||
|
export let as = 'div'
|
||||||
|
|
||||||
|
const { onDragStart } = getNodeViewContext()
|
||||||
|
|
||||||
|
let element: HTMLElement
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await tick()
|
||||||
|
element.style.whiteSpace = 'normal'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={as}
|
||||||
|
bind:this={element}
|
||||||
|
data-node-view-wrapper=""
|
||||||
|
on:dragstart={onDragStart}
|
||||||
|
role="none"
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
33
packages/text-editor/src/components/node-view/context.ts
Normal file
33
packages/text-editor/src/components/node-view/context.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2023 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 { getContext } from 'svelte'
|
||||||
|
|
||||||
|
const key = 'tiptap-node-view-context'
|
||||||
|
|
||||||
|
export interface TiptapNodeViewContext {
|
||||||
|
onDragStart: (event: DragEvent) => void
|
||||||
|
onContentElement: (element: HTMLElement) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNodeViewContext (): TiptapNodeViewContext {
|
||||||
|
return getContext(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNodeViewContext (value: TiptapNodeViewContext): Map<any, any> {
|
||||||
|
const context = new Map()
|
||||||
|
context.set(key, value)
|
||||||
|
return context
|
||||||
|
}
|
24
packages/text-editor/src/components/node-view/index.ts
Normal file
24
packages/text-editor/src/components/node-view/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2023 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
export { default as NodeViewContent } from './NodeViewContent.svelte'
|
||||||
|
export { default as NodeViewWrapper } from './NodeViewWrapper.svelte'
|
||||||
|
export {
|
||||||
|
default as SvelteNodeViewRenderer,
|
||||||
|
SvelteNodeViewComponent,
|
||||||
|
SvelteNodeViewProps as NodeViewProps,
|
||||||
|
SvelteNodeViewRendererOptions
|
||||||
|
} from './svelte-node-view-renderer'
|
||||||
|
export { SvelteRenderer, SvelteRendererComponent, SvelteRendererOptions } from './svelte-renderer'
|
@ -0,0 +1,155 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2023 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 {
|
||||||
|
DecorationWithType,
|
||||||
|
Editor,
|
||||||
|
NodeView,
|
||||||
|
NodeViewProps,
|
||||||
|
NodeViewRenderer,
|
||||||
|
NodeViewRendererOptions
|
||||||
|
} from '@tiptap/core'
|
||||||
|
import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||||
|
import type { ComponentType, SvelteComponent } from 'svelte'
|
||||||
|
|
||||||
|
import { createNodeViewContext } from './context'
|
||||||
|
import { SvelteRenderer } from './svelte-renderer'
|
||||||
|
|
||||||
|
export interface SvelteNodeViewRendererOptions extends NodeViewRendererOptions {
|
||||||
|
update?: (node: ProseMirrorNode, decorations: DecorationWithType[]) => boolean
|
||||||
|
contentAs?: string
|
||||||
|
contentDOMElementAs?: string
|
||||||
|
componentProps?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SvelteNodeViewProps = NodeViewProps & {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SvelteNodeViewComponent = typeof SvelteComponent | ComponentType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Svelte NodeView renderer, inspired by React and Vue implementation by Tiptap
|
||||||
|
* https://tiptap.dev/guide/node-views/react/
|
||||||
|
*/
|
||||||
|
class SvelteNodeView extends NodeView<SvelteNodeViewComponent, Editor, SvelteNodeViewRendererOptions> {
|
||||||
|
renderer!: SvelteRenderer
|
||||||
|
|
||||||
|
contentDOMElement!: HTMLElement | null
|
||||||
|
|
||||||
|
override mount (): void {
|
||||||
|
const props: SvelteNodeViewProps = {
|
||||||
|
editor: this.editor,
|
||||||
|
node: this.node,
|
||||||
|
decorations: this.decorations,
|
||||||
|
selected: false,
|
||||||
|
extension: this.extension,
|
||||||
|
getPos: () => this.getPos(),
|
||||||
|
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
||||||
|
deleteNode: () => this.deleteNode(),
|
||||||
|
...(this.options.componentProps ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.node.isLeaf) {
|
||||||
|
this.contentDOMElement = null
|
||||||
|
} else if (this.options.contentDOMElementAs !== undefined) {
|
||||||
|
this.contentDOMElement = document.createElement(this.options.contentDOMElementAs)
|
||||||
|
} else {
|
||||||
|
this.contentDOMElement = document.createElement(this.node.isInline ? 'span' : 'div')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.contentDOMElement !== null) {
|
||||||
|
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
||||||
|
// With this fix it seems to work fine
|
||||||
|
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
||||||
|
this.contentDOMElement.style.whiteSpace = 'inherit'
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentAs = this.options.contentAs ?? (this.node.isInline ? 'span' : 'div')
|
||||||
|
|
||||||
|
const target = document.createElement(contentAs)
|
||||||
|
target.classList.add(`node-${this.node.type.name}`)
|
||||||
|
|
||||||
|
const context = createNodeViewContext({
|
||||||
|
onDragStart: this.onDragStart.bind(this),
|
||||||
|
onContentElement: (element) => {
|
||||||
|
if (this.contentDOMElement !== null && !element.contains(this.contentDOMElement)) {
|
||||||
|
element.appendChild(this.contentDOMElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.renderer = new SvelteRenderer(this.component, { element: target, props, context })
|
||||||
|
}
|
||||||
|
|
||||||
|
override get dom (): HTMLElement {
|
||||||
|
if (this.renderer.element.firstElementChild?.hasAttribute('data-node-view-wrapper') === false) {
|
||||||
|
throw Error('Please use the NodeViewWrapper component for your node view.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderer.element
|
||||||
|
}
|
||||||
|
|
||||||
|
override get contentDOM (): HTMLElement | null {
|
||||||
|
if (this.node.isLeaf) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.contentDOMElement
|
||||||
|
}
|
||||||
|
|
||||||
|
update (node: ProseMirrorNode, decorations: DecorationWithType[]): boolean {
|
||||||
|
if (typeof this.options.update === 'function') {
|
||||||
|
return this.options.update(node, decorations)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type !== this.node.type) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node === this.node && this.decorations === decorations) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.node = node
|
||||||
|
this.decorations = decorations
|
||||||
|
|
||||||
|
this.renderer.updateProps({ node, decorations })
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNode (): void {
|
||||||
|
this.renderer.updateProps({ selected: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectNode (): void {
|
||||||
|
this.renderer.updateProps({ selected: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
this.renderer.destroy()
|
||||||
|
this.contentDOMElement = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SvelteNodeViewRenderer = (
|
||||||
|
component: SvelteNodeViewComponent,
|
||||||
|
options: Partial<SvelteNodeViewRendererOptions>
|
||||||
|
): NodeViewRenderer => {
|
||||||
|
return (props) => new SvelteNodeView(component, props, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SvelteNodeViewRenderer
|
@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
// Copyright © 2021 Hardcore Engineering Inc.
|
// Copyright © 2021, 2023 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
@ -14,14 +14,27 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { ComponentType, SvelteComponent } from 'svelte'
|
import type { ComponentType, SvelteComponent } from 'svelte'
|
||||||
|
|
||||||
|
export type SvelteRendererComponent = typeof SvelteComponent | ComponentType
|
||||||
|
|
||||||
|
export interface SvelteRendererOptions {
|
||||||
|
element: HTMLElement
|
||||||
|
props?: any
|
||||||
|
context?: any
|
||||||
|
}
|
||||||
|
|
||||||
export class SvelteRenderer {
|
export class SvelteRenderer {
|
||||||
private readonly component: SvelteComponent
|
private readonly component: SvelteComponent
|
||||||
|
element: HTMLElement
|
||||||
|
|
||||||
constructor (comp: typeof SvelteComponent | ComponentType, props: any) {
|
constructor (component: SvelteRendererComponent, { element, props, context }: SvelteRendererOptions) {
|
||||||
const options = { target: document.body, props }
|
this.element = element
|
||||||
this.component = new (comp as any)(options)
|
this.element.classList.add('svelte-renderer')
|
||||||
|
|
||||||
|
const options = { target: element, props, context }
|
||||||
|
const Component = component
|
||||||
|
this.component = new Component(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProps (props: Record<string, any>): void {
|
updateProps (props: Record<string, any>): void {
|
@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
// Copyright © 2021 Hardcore Engineering Inc.
|
// Copyright © 2021, 2023 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
@ -30,6 +30,7 @@ export { default as TextEditor } from './components/TextEditor.svelte'
|
|||||||
export { default as TextEditorStyleToolbar } from './components/TextEditorStyleToolbar.svelte'
|
export { default as TextEditorStyleToolbar } from './components/TextEditorStyleToolbar.svelte'
|
||||||
export { default as AttachIcon } from './components/icons/Attach.svelte'
|
export { default as AttachIcon } from './components/icons/Attach.svelte'
|
||||||
export { default as TableOfContents } from './components/toc/TableOfContents.svelte'
|
export { default as TableOfContents } from './components/toc/TableOfContents.svelte'
|
||||||
|
export * from './components/node-view'
|
||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
@ -58,5 +59,6 @@ export {
|
|||||||
type InlineStyleToolbarStorage
|
type InlineStyleToolbarStorage
|
||||||
} from './components/extension/inlineStyleToolbar'
|
} from './components/extension/inlineStyleToolbar'
|
||||||
export { ImageExtension, type ImageOptions } from './components/extension/imageExt'
|
export { ImageExtension, type ImageOptions } from './components/extension/imageExt'
|
||||||
|
export { TodoItemExtension, TodoListExtension } from './components/extension/todo'
|
||||||
|
|
||||||
export { textEditorId }
|
export { textEditorId }
|
||||||
|
@ -78,11 +78,14 @@ export function copyDocumentContent (
|
|||||||
provider.copyContent(documentId, snapshotId)
|
provider.copyContent(documentId, snapshotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataAttribute (name: string, def?: unknown | null): Partial<Attribute> {
|
export function getDataAttribute (
|
||||||
|
name: string,
|
||||||
|
options?: Omit<Attribute, 'parseHTML' | 'renderHTML'>
|
||||||
|
): Partial<Attribute> {
|
||||||
const dataName = `data-${name}`
|
const dataName = `data-${name}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
default: def,
|
default: null,
|
||||||
parseHTML: (element) => element.getAttribute(dataName),
|
parseHTML: (element) => element.getAttribute(dataName),
|
||||||
renderHTML: (attributes) => {
|
renderHTML: (attributes) => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -93,6 +96,7 @@ export function getDataAttribute (name: string, def?: unknown | null): Partial<A
|
|||||||
return {
|
return {
|
||||||
[dataName]: attributes[name]
|
[dataName]: attributes[name]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
...(options !== undefined ? options : {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import TaskItem from '@tiptap/extension-task-item'
|
|||||||
import TaskList from '@tiptap/extension-task-list'
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
import Typography from '@tiptap/extension-typography'
|
import Typography from '@tiptap/extension-typography'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import { ImageNode, ReferenceNode } from './nodes'
|
import { ImageNode, ReferenceNode, TodoItemNode, TodoListNode } from './nodes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -97,4 +97,10 @@ export const defaultExtensions: AnyExtension[] = [
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const serverExtensions: AnyExtension[] = [...defaultExtensions, ImageNode, ReferenceNode]
|
export const serverExtensions: AnyExtension[] = [
|
||||||
|
...defaultExtensions,
|
||||||
|
ImageNode,
|
||||||
|
ReferenceNode,
|
||||||
|
TodoItemNode,
|
||||||
|
TodoListNode
|
||||||
|
]
|
||||||
|
@ -15,4 +15,5 @@
|
|||||||
|
|
||||||
export * from './image'
|
export * from './image'
|
||||||
export * from './reference'
|
export * from './reference'
|
||||||
|
export * from './todo'
|
||||||
export { getDataAttribute } from './utils'
|
export { getDataAttribute } from './utils'
|
||||||
|
35
packages/text/src/nodes/todo.ts
Normal file
35
packages/text/src/nodes/todo.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { TaskItem } from '@tiptap/extension-task-item'
|
||||||
|
import { TaskList } from '@tiptap/extension-task-list'
|
||||||
|
|
||||||
|
import { getDataAttribute } from './utils'
|
||||||
|
|
||||||
|
export const TodoItemNode = TaskItem.extend({
|
||||||
|
name: 'todoItem',
|
||||||
|
|
||||||
|
addOptions () {
|
||||||
|
return {
|
||||||
|
nested: false,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
taskListTypeName: 'todoList'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes () {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
todoid: getDataAttribute('todoid', { default: null, keepOnSplit: false }),
|
||||||
|
userid: getDataAttribute('userid', { default: null, keepOnSplit: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TodoListNode = TaskList.extend({
|
||||||
|
name: 'todoList',
|
||||||
|
|
||||||
|
addOptions () {
|
||||||
|
return {
|
||||||
|
itemTypeName: 'todoItem',
|
||||||
|
HTMLAttributes: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -18,11 +18,14 @@ import { Attribute } from '@tiptap/core'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getDataAttribute (name: string, def?: unknown | null): Partial<Attribute> {
|
export function getDataAttribute (
|
||||||
|
name: string,
|
||||||
|
options?: Omit<Attribute, 'parseHTML' | 'renderHTML'>
|
||||||
|
): Partial<Attribute> {
|
||||||
const dataName = `data-${name}`
|
const dataName = `data-${name}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
default: def,
|
default: null,
|
||||||
parseHTML: (element) => element.getAttribute(dataName),
|
parseHTML: (element) => element.getAttribute(dataName),
|
||||||
renderHTML: (attributes) => {
|
renderHTML: (attributes) => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -33,6 +36,7 @@ export function getDataAttribute (name: string, def?: unknown | null): Partial<A
|
|||||||
return {
|
return {
|
||||||
[dataName]: attributes[name]
|
[dataName]: attributes[name]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
...(options !== undefined ? options : {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,17 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul[data-type="todoList"] {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ol ol { list-style: lower-alpha; }
|
ol ol { list-style: lower-alpha; }
|
||||||
ol ol ol { list-style: lower-roman; }
|
ol ol ol { list-style: lower-roman; }
|
||||||
ol ol ol ol { list-style: decimal; }
|
ol ol ol ol { list-style: decimal; }
|
||||||
|
Loading…
Reference in New Issue
Block a user