diff --git a/packages/server/postgres/migrations/2025-01-07T19:41:35.458Z_reflectionsToTipTap.ts b/packages/server/postgres/migrations/2025-01-07T19:41:35.458Z_reflectionsToTipTap.ts new file mode 100644 index 00000000000..edc0720eeba --- /dev/null +++ b/packages/server/postgres/migrations/2025-01-07T19:41:35.458Z_reflectionsToTipTap.ts @@ -0,0 +1,148 @@ +import {generateText, mergeAttributes, type JSONContent} from '@tiptap/core' +import BaseLink from '@tiptap/extension-link' +import Mention from '@tiptap/extension-mention' +import {generateJSON} from '@tiptap/html' +import StarterKit from '@tiptap/starter-kit' +import {convertFromRaw, RawDraftContentState} from 'draft-js' +import {Options, stateToHTML} from 'draft-js-export-html' +import type {Kysely} from 'kysely' + +const serverTipTapExtensions = [ + StarterKit, + Mention.configure({ + renderText({node}) { + return node.attrs.label + }, + renderHTML({options, node}) { + return ['span', options.HTMLAttributes, `${node.attrs.label ?? node.attrs.id}`] + } + }), + BaseLink.extend({ + parseHTML() { + return [ + { + tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])' + } + ] + }, + + renderHTML({HTMLAttributes}) { + return [ + 'a', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + class: 'link' + }), + 0 + ] + } + }) +] + +const getNameFromEntity = (content: RawDraftContentState, userId: string) => { + const {blocks, entityMap} = content + const entityKey = Number( + Object.keys(entityMap).find((key) => entityMap[key]!.data?.userId === userId) + ) + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]! + const {entityRanges, text} = block + const entityRange = entityRanges.find((range) => range.key === entityKey) + if (!entityRange) continue + const {length, offset} = entityRange + return text.slice(offset, offset + length) + } + console.log('found unknown for', userId, JSON.stringify(content)) + return 'Unknown User' +} + +const getTrimmedContent = (content: JSONContent) => { + const contentStr = JSON.stringify(content) + if (contentStr.length <= 2000) return contentStr + const plaintextContent = generateText(content, serverTipTapExtensions) + const plainJSONContent = generateJSON(`
${plaintextContent}
`, serverTipTapExtensions) + return JSON.stringify(plainJSONContent) +} + +export const convertKnownDraftToTipTap = (content: RawDraftContentState) => { + const contentState = convertFromRaw(content) + const options: Options = { + entityStyleFn: (entity) => { + const entityType = entity.getType().toLowerCase() + const data = entity.getData() + if (entityType === 'tag') { + return { + element: 'span', + attributes: { + 'data-id': data.value, + 'data-type': 'taskTag' + } + } + } + if (entityType === 'mention') { + const label = getNameFromEntity(content, data.userId) + return { + element: 'span', + attributes: { + 'data-id': data.userId.toWellFormed(), + 'data-label': label.toWellFormed(), + 'data-type': 'mention' + } + } + } + return + } + } + const html = stateToHTML(contentState, options) + const json = generateJSON(html, serverTipTapExtensions) + return json +} + +export async function up(db: Kysely