Skip to content
Draft
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
1,278 changes: 562 additions & 716 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

12 changes: 5 additions & 7 deletions foundations/core/packages/text-markdown/src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function backticksFor (side: boolean): string {
}

function isPlainURL (link: MarkupMark, parent: MarkupNode, index: number): boolean {
if (link.attrs?.title !== undefined || !/^\w+:/.test(link.attrs?.href)) return false
if (link.attrs?.title != null || !/^\w+:/.test(link.attrs?.href)) return false
const content = parent.content?.[index]
if (content === undefined) {
return false
Expand Down Expand Up @@ -355,13 +355,11 @@ export const storeMarks: Record<string, MarkProcessor> = {
// eslint-disable-next-line
const url = href.replace(/[\(\)"\\<>]/g, '\\$&')
const hasSpaces = url.includes(' ')
const title = (mark.attrs?.title as string) ?? ''

return inAutolink === true
? '>'
: '](' +
(hasSpaces ? `<${url}>` : url) +
(mark.attrs?.title !== undefined ? ` "${(mark.attrs?.title as string).replace(/"/g, '\\"')}"` : '') +
')'
: '](' + (hasSpaces ? `<${url}>` : url) + (title !== '' ? ` "${title.replace(/"/g, '\\"')}"` : '') + ')'
Comment thread
aonnikov marked this conversation as resolved.
Dismissed
}
},
mixable: false,
Expand All @@ -381,13 +379,13 @@ export const storeMarks: Record<string, MarkProcessor> = {
},
textColor: {
open: (state, mark, parent, index) => {
if (mark.attrs?.color === undefined) {
if (mark.attrs?.color == null) {
return ''
}
return `<span style="color: ${mark.attrs.color}" data-color="${mark.attrs.color}">`
},
close: (state, mark, parent, index) => {
if (mark.attrs?.color === undefined) {
if (mark.attrs?.color == null) {
return ''
}
return '</span>'
Expand Down
4 changes: 2 additions & 2 deletions foundations/core/packages/text-ydoc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"@hcengineering/core": "workspace:^0.7.26",
"@hcengineering/text": "workspace:^0.7.19",
"@hcengineering/text-core": "workspace:^0.7.19",
"yjs": "^13.6.27",
"y-protocols": "^1.0.6"
"yjs": "^13.6.30",
"y-protocols": "^1.0.7"
},
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,15 @@ const markups: Array<{ name: string, markup: Markup, skipYdocCompare?: boolean }
markup:
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"hello "},{"type":"text","marks":[{"type":"bold","attrs":{}},{"type":"italic","attrs":{}}],"text":"world"}]}]}'
},
{
name: 'text with link with title',
markup:
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello world","marks":[{"type":"link","attrs":{"href":"http://example.com","target":"_blank","rel":"noopener noreferrer","class":"cursor-pointer","title":"example"}}]}]}]}'
},
{
name: 'text with link and italic marks',
markup:
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello "},{"type":"text","text":"hello world","marks":[{"type":"link","attrs":{"href":"http://example.com","target":"_blank","rel":"noopener noreferrer","class":"cursor-pointer"}},{"type":"italic","attrs":{}}]}]}]}'
'{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello "},{"type":"text","text":"hello world","marks":[{"type":"link","attrs":{"href":"http://example.com","target":"_blank","rel":"noopener noreferrer","class":"cursor-pointer","title":"example"}},{"type":"italic","attrs":{}}]}]}]}'
},
{
name: 'image',
Expand Down
67 changes: 29 additions & 38 deletions foundations/core/packages/text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,46 +43,37 @@
"dependencies": {
"@hcengineering/core": "workspace:^0.7.26",
"@hcengineering/text-core": "workspace:^0.7.19",
"@tiptap/core": "^2.11.7",
"@tiptap/html": "^2.11.7",
"@tiptap/pm": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
"@tiptap/extension-gapcursor": "^2.11.7",
"@tiptap/extension-heading": "^2.11.7",
"@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-history": "^2.11.7",
"@tiptap/extension-link": "^2.11.7",
"@tiptap/extension-mention": "^2.11.7",
"@tiptap/extension-table": "^2.11.7",
"@tiptap/extension-table-cell": "^2.11.7",
"@tiptap/extension-table-header": "^2.11.7",
"@tiptap/extension-table-row": "^2.11.7",
"@tiptap/extension-task-item": "^2.11.7",
"@tiptap/extension-task-list": "^2.11.7",
"@tiptap/extension-bold": "^2.11.7",
"@tiptap/extension-blockquote": "^2.11.7",
"@tiptap/extension-text": "^2.11.7",
"@tiptap/extension-document": "^2.11.7",
"@tiptap/extension-ordered-list": "^2.11.7",
"@tiptap/extension-bullet-list": "^2.11.7",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-dropcursor": "^2.11.7",
"@tiptap/extension-hard-break": "^2.11.7",
"@tiptap/extension-horizontal-rule": "^2.11.7",
"@tiptap/extension-italic": "^2.11.7",
"@tiptap/extension-paragraph": "^2.11.7",
"@tiptap/extension-strike": "^2.11.7",
"@tiptap/extension-typography": "^2.11.7",
"@tiptap/extension-code-block": "^2.11.7",
"@tiptap/extension-code": "^2.11.7",
"@tiptap/extension-underline": "^2.11.7",
"@tiptap/suggestion": "^2.11.7",
"@tiptap/core": "^3.23.5",
"@tiptap/pm": "^3.23.5",
"@tiptap/starter-kit": "^3.23.5",
"@tiptap/extensions": "^3.23.5",
"@tiptap/extension-heading": "^3.23.5",
"@tiptap/extension-highlight": "^3.23.5",
"@tiptap/extension-link": "^3.23.5",
"@tiptap/extension-mention": "^3.23.5",
"@tiptap/extension-table": "^3.23.5",
"@tiptap/extension-bold": "^3.23.5",
"@tiptap/extension-blockquote": "^3.23.5",
"@tiptap/extension-text": "^3.23.5",
"@tiptap/extension-document": "^3.23.5",
"@tiptap/extension-list": "^3.23.5",
"@tiptap/extension-hard-break": "^3.23.5",
"@tiptap/extension-horizontal-rule": "^3.23.5",
"@tiptap/extension-italic": "^3.23.5",
"@tiptap/extension-paragraph": "^3.23.5",
"@tiptap/extension-strike": "^3.23.5",
"@tiptap/extension-typography": "^3.23.5",
"@tiptap/extension-code-block": "^3.23.5",
"@tiptap/extension-code": "^3.23.5",
"@tiptap/extension-underline": "^3.23.5",
"@tiptap/suggestion": "^3.23.5",
"prosemirror-codemark": "^0.4.2",
"fast-equals": "^5.2.2",
"@tiptap/extension-text-align": "~2.11.0",
"@tiptap/extension-text-style": "~2.11.0",
"@tiptap/extension-subscript": "^2.11.7",
"@tiptap/extension-superscript": "^2.11.7"
"zeed-dom": "^0.17.7",
"@tiptap/extension-text-align": "~3.23.5",
"@tiptap/extension-text-style": "~3.23.5",
"@tiptap/extension-subscript": "^3.23.5",
"@tiptap/extension-superscript": "^3.23.5"
},
"repository": {
"type": "git",
Expand Down
6 changes: 3 additions & 3 deletions foundations/core/packages/text/src/kits/common-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
Dropcursor,
Gapcursor,
Heading,
History,
HorizontalRule,
Italic,
Link,
Expand All @@ -37,7 +36,8 @@ import {
TextAlign,
TextStyle,
Typography,
Underline
Underline,
UndoRedo
} from '../tiptapExtensions'

import { ExtensionFactory, extensionKit } from '../kit'
Expand Down Expand Up @@ -81,7 +81,7 @@ export const CommonKitFactory = (e: ExtensionFactory) =>

dropcursor: e(Dropcursor),
gapcursor: e(Gapcursor),
history: e(History)
undoRedo: e(UndoRedo)
}) as const

export const CommonKit = extensionKit('common-kit', CommonKitFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const NAME = 'node-uuid'
*/
export const QMSInlineCommentMark = Mark.create({
name: NAME,
inline: true,

// TODO
// inline: true,

parseHTML () {
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('dsl', () => {
)
)
expect(jsonToHTML(doc)).toEqual(
'<p>Hello, <span data-type="reference" data-id="123" data-objectclass="world" data-label="World" class="antiMention">@World</span></p><p>Check out <a target="_blank" rel="noopener noreferrer" class="cursor-pointer" href="https://example.com"><u>this link</u></a>.</p>'
'<p>Hello, <span data-type="reference" data-id="123" data-objectclass="world" data-label="World" class="antiMention">@World</span></p><p>Check out <a target="_blank" rel="noopener noreferrer" class="cursor-pointer" href="https://example.com" title="this link"><u>this link</u></a>.</p>'
)
})
})
Expand Down
50 changes: 40 additions & 10 deletions foundations/core/packages/text/src/markup/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,26 @@ import { Editor, getSchema } from '@tiptap/core'
import { ServerKit } from '../../kits/server-kit'
import { getMarkup, htmlToJSON, htmlToMarkup, jsonToHTML, jsonToPmNode, jsonToText, pmNodeToJSON } from '../utils'

// mock tiptap functions
jest.mock('@tiptap/html', () => ({
generateHTML: jest.fn(() => '<p>hello</p>'),
generateJSON: jest.fn(() => ({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hello' }] }]
}))
// mock zeed-dom functions
jest.mock('zeed-dom', () => ({
createHTMLDocument: jest.fn(() => ({
createElement: (tag: string) => document.createElement(tag),
createTextNode: (data: string) => document.createTextNode(data),
createDocumentFragment: () => {
const frag = document.createDocumentFragment()
;(frag as any).render = () => {
const div = document.createElement('div')
div.appendChild(frag.cloneNode(true))
return div.innerHTML
}
return frag
}
})),
parseHTML: jest.fn((html: string) => {
const el = document.createElement('div')
el.innerHTML = html
return el
})
}))

const extensions = [ServerKit]
Expand Down Expand Up @@ -332,16 +345,33 @@ describe('jsonToPmNode', () => {
describe('htmlToMarkup', () => {
it('converts HTML to Markup', () => {
const html = '<p>hello</p>'
const expectedMarkup = '{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}'
const expectedMarkup =
'{"type":"doc","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"hello"}]}]}'
expect(htmlToMarkup(html)).toEqual(expectedMarkup)
})
})

describe('htmlToJSON', () => {
it('converts HTML to JSON', () => {
const html = '<p>hello</p>'
const json = nodeDoc(nodeParagraph(nodeText('hello')))
expect(htmlToJSON(html)).toEqual(json)
const node: MarkupNode = {
type: MarkupNodeType.doc,
content: [
{
type: MarkupNodeType.paragraph,
attrs: {
textAlign: null
},
content: [
{
type: MarkupNodeType.text,
text: 'hello'
}
]
}
]
}
expect(htmlToJSON(html)).toEqual(node)
})
})

Expand Down
67 changes: 63 additions & 4 deletions foundations/core/packages/text/src/markup/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
//

import { Markup } from '@hcengineering/core'
import { Editor, Extensions, getSchema } from '@tiptap/core'
import { generateHTML, generateJSON } from '@tiptap/html'
import { Node as ProseMirrorNode, Schema } from '@tiptap/pm/model'

import { MarkupNode, jsonToMarkup } from '@hcengineering/text-core'

import { Editor, Extensions, getSchema, JSONContent } from '@tiptap/core'
import { DOMParser, DOMSerializer, ParseOptions, Node as ProseMirrorNode, Schema } from '@tiptap/pm/model'
import { VHTMLDocument, createHTMLDocument, parseHTML } from 'zeed-dom'

import { defaultExtensions } from '../extensions'

/** @public */
Expand Down Expand Up @@ -78,3 +79,61 @@ export function jsonToHTML (json: MarkupNode, extensions?: Extensions): string {
extensions = extensions ?? defaultExtensions
return generateHTML(json, extensions)
}

// Tiptap 2.x.x utils

export function generateHTML (doc: JSONContent, extensions: Extensions): string {
const schema = getSchema(extensions)
const contentNode = ProseMirrorNode.fromJSON(schema, doc)

return getHTMLFromFragment(contentNode, schema)
}

/**
* Generates a JSON object from the given HTML string and converts it into a Prosemirror node with content.
* @param {string} html - The HTML string to be converted into a Prosemirror node.
* @param {Extensions} extensions - The extensions to be used for generating the schema.
* @param {ParseOptions} options - The options to be supplied to the parser.
* @returns {Record<string, any>} - The generated JSON object.
* @example
* const html = '<p>Hello, world!</p>'
* const extensions = [...]
* const json = generateJSON(html, extensions)
* console.log(json) // { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }] }
*/
export function generateJSON (html: string, extensions: Extensions, options?: ParseOptions): Record<string, any> {
const schema = getSchema(extensions)
const dom = parseHTML(html) as unknown as Node

return DOMParser.fromSchema(schema).parse(dom, options).toJSON()
}

/**
* Returns the HTML string representation of a given document node.
*
* @param doc - The document node to serialize.
* @param schema - The Prosemirror schema to use for serialization.
* @returns The HTML string representation of the document fragment.
*
* @example
* ```typescript
* const html = getHTMLFromFragment(doc, schema)
* ```
*/
export function getHTMLFromFragment (doc: ProseMirrorNode, schema: Schema, options?: { document?: Document }): string {
if (options?.document != null) {
// The caller is relying on their own document implementation. Use this
// instead of the default zeed-dom.
const wrap = options.document.createElement('div')

DOMSerializer.fromSchema(schema).serializeFragment(doc.content, { document: options.document }, wrap)
return wrap.innerHTML
}

// Use zeed-dom for serialization.
const zeedDocument = DOMSerializer.fromSchema(schema).serializeFragment(doc.content, {
document: createHTMLDocument() as unknown as Document
}) as unknown as VHTMLDocument

return zeedDocument.render()
}
4 changes: 3 additions & 1 deletion foundations/core/packages/text/src/nodes/codeblock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import CodeBlock, { CodeBlockOptions } from '@tiptap/extension-code-block'
export const codeBlockOptions: CodeBlockOptions = {
defaultLanguage: 'plaintext',
languageClassPrefix: 'language-',
enableTabIndentation: undefined,
tabSize: undefined,
exitOnArrowDown: true,
exitOnTripleEnter: true,
HTMLAttributes: {
Expand All @@ -44,7 +46,7 @@ export const CodeBlockExtension = CodeBlock.extend({
language: {
default: null,
parseHTML: (element) => {
const { languageClassPrefix } = this.options
const languageClassPrefix = this.options.languageClassPrefix ?? ''
let fchild = element.firstElementChild
if (fchild == null) {
for (const c of element.childNodes) {
Expand Down
3 changes: 1 addition & 2 deletions foundations/core/packages/text/src/nodes/todo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TaskItem } from '@tiptap/extension-task-item'
import { TaskList } from '@tiptap/extension-task-list'
import { TaskItem, TaskList } from '@tiptap/extension-list'

import { getDataAttribute } from './utils'

Expand Down
Loading
Loading