@fluidframework/react 2.90.0-378676 → 2.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/api-report/react.alpha.api.md +8 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/propNode.js.map +1 -1
- package/lib/reactSharedTreeView.d.ts +6 -6
- package/lib/reactSharedTreeView.d.ts.map +1 -1
- package/lib/reactSharedTreeView.js +16 -18
- package/lib/reactSharedTreeView.js.map +1 -1
- package/lib/test/mochaHooks.js +13 -0
- package/lib/test/mochaHooks.js.map +1 -0
- package/lib/test/reactSharedTreeView.spec.js +3 -3
- package/lib/test/reactSharedTreeView.spec.js.map +1 -1
- package/lib/test/text/plainUtils.test.js +75 -0
- package/lib/test/text/plainUtils.test.js.map +1 -0
- package/lib/test/text/textEditor.test.js +760 -0
- package/lib/test/text/textEditor.test.js.map +1 -0
- package/lib/test/undoRedo.test.js +62 -0
- package/lib/test/undoRedo.test.js.map +1 -0
- package/lib/test/useObservation.spec.js +8 -9
- package/lib/test/useObservation.spec.js.map +1 -1
- package/lib/test/useTree.spec.js +15 -16
- package/lib/test/useTree.spec.js.map +1 -1
- package/lib/text/formatted/index.d.ts +6 -0
- package/lib/text/formatted/index.d.ts.map +1 -0
- package/lib/text/formatted/index.js +6 -0
- package/lib/text/formatted/index.js.map +1 -0
- package/lib/text/formatted/quillFormattedView.d.ts +66 -0
- package/lib/text/formatted/quillFormattedView.d.ts.map +1 -0
- package/lib/text/formatted/quillFormattedView.js +520 -0
- package/lib/text/formatted/quillFormattedView.js.map +1 -0
- package/lib/text/index.d.ts +7 -0
- package/lib/text/index.d.ts.map +1 -0
- package/lib/text/index.js +7 -0
- package/lib/text/index.js.map +1 -0
- package/lib/text/plain/index.d.ts +7 -0
- package/lib/text/plain/index.d.ts.map +1 -0
- package/lib/text/plain/index.js +7 -0
- package/lib/text/plain/index.js.map +1 -0
- package/lib/text/plain/plainTextView.d.ts +14 -0
- package/lib/text/plain/plainTextView.d.ts.map +1 -0
- package/lib/text/plain/plainTextView.js +70 -0
- package/lib/text/plain/plainTextView.js.map +1 -0
- package/lib/text/plain/plainUtils.d.ts +23 -0
- package/lib/text/plain/plainUtils.d.ts.map +1 -0
- package/lib/text/plain/plainUtils.js +51 -0
- package/lib/text/plain/plainUtils.js.map +1 -0
- package/lib/text/plain/quillView.d.ts +22 -0
- package/lib/text/plain/quillView.d.ts.map +1 -0
- package/lib/text/plain/quillView.js +106 -0
- package/lib/text/plain/quillView.js.map +1 -0
- package/lib/undoRedo.d.ts +51 -0
- package/lib/undoRedo.d.ts.map +1 -0
- package/lib/undoRedo.js +76 -0
- package/lib/undoRedo.js.map +1 -0
- package/lib/useObservation.js +6 -6
- package/lib/useObservation.js.map +1 -1
- package/lib/useTree.d.ts +7 -7
- package/lib/useTree.d.ts.map +1 -1
- package/lib/useTree.js +6 -6
- package/lib/useTree.js.map +1 -1
- package/package.json +28 -46
- package/react.test-files.tar +0 -0
- package/src/index.ts +10 -0
- package/src/propNode.ts +1 -1
- package/src/reactSharedTreeView.tsx +11 -13
- package/src/text/formatted/index.ts +11 -0
- package/src/text/formatted/quillFormattedView.tsx +627 -0
- package/src/text/index.ts +15 -0
- package/src/text/plain/index.ts +7 -0
- package/src/text/plain/plainTextView.tsx +110 -0
- package/src/text/plain/plainUtils.ts +68 -0
- package/src/text/plain/quillView.tsx +149 -0
- package/src/undoRedo.ts +117 -0
- package/src/useObservation.ts +6 -6
- package/src/useTree.ts +19 -12
- package/tsconfig.json +6 -0
- package/api-extractor/api-extractor-lint-alpha.cjs.json +0 -5
- package/api-extractor/api-extractor-lint-beta.cjs.json +0 -5
- package/api-extractor/api-extractor-lint-public.cjs.json +0 -5
- package/dist/alpha.d.ts +0 -45
- package/dist/beta.d.ts +0 -15
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/package.json +0 -4
- package/dist/propNode.d.ts +0 -114
- package/dist/propNode.d.ts.map +0 -1
- package/dist/propNode.js +0 -43
- package/dist/propNode.js.map +0 -1
- package/dist/public.d.ts +0 -15
- package/dist/reactSharedTreeView.d.ts +0 -119
- package/dist/reactSharedTreeView.d.ts.map +0 -1
- package/dist/reactSharedTreeView.js +0 -206
- package/dist/reactSharedTreeView.js.map +0 -1
- package/dist/simpleIdentifier.d.ts +0 -19
- package/dist/simpleIdentifier.d.ts.map +0 -1
- package/dist/simpleIdentifier.js +0 -33
- package/dist/simpleIdentifier.js.map +0 -1
- package/dist/useObservation.d.ts +0 -83
- package/dist/useObservation.d.ts.map +0 -1
- package/dist/useObservation.js +0 -295
- package/dist/useObservation.js.map +0 -1
- package/dist/useTree.d.ts +0 -80
- package/dist/useTree.d.ts.map +0 -1
- package/dist/useTree.js +0 -137
- package/dist/useTree.js.map +0 -1
- package/tsconfig.cjs.json +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quillFormattedView.js","sourceRoot":"","sources":["../../../src/text/formatted/quillFormattedView.tsx"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport { Tree, TreeAlpha, FormattedTextAsTree } from \"@fluidframework/tree/internal\";\nexport { FormattedTextAsTree } from \"@fluidframework/tree/internal\";\nimport Quill, { type EmitterSource } from \"quill\";\nimport DeltaPackage from \"quill-delta\";\nimport {\n\tforwardRef,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseReducer,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\nimport { type PropTreeNode, unwrapPropTreeNode } from \"../../propNode.js\";\nimport type { UndoRedo } from \"../../undoRedo.js\";\n\n// Workaround for quill-delta's export style not working well with node16 module resolution.\ntype Delta = DeltaPackage.default;\ntype QuillDeltaOp = DeltaPackage.Op;\nconst Delta = DeltaPackage.default;\n\n/**\n * Props for the FormattedMainView component.\n * @input @internal\n */\nexport interface FormattedMainViewProps {\n\treadonly root: PropTreeNode<FormattedTextAsTree.Tree>;\n\t/** Optional undo/redo stack for the editor. */\n\treadonly undoRedo?: UndoRedo;\n}\n\n/**\n * Ref handle exposing undo/redo methods for the formatted editor.\n * @input @internal\n */\nexport type FormattedEditorHandle = Pick<UndoRedo, \"undo\" | \"redo\">;\n\n/**\n * A React component for formatted text editing.\n * @remarks\n * Uses {@link @fluidframework/tree#FormattedTextAsTree.Tree} for the data-model and Quill for the rich text editor UI.\n * @internal\n */\nexport const FormattedMainView = forwardRef<FormattedEditorHandle, FormattedMainViewProps>(\n\t({ root, undoRedo }, ref) => {\n\t\treturn <FormattedTextEditorView root={root} undoRedo={undoRedo} ref={ref} />;\n\t},\n);\nFormattedMainView.displayName = \"FormattedMainView\";\n\n/** Quill size names mapped to pixel values for tree storage. */\nconst sizeMap = { small: 10, large: 18, huge: 24 } as const;\n/** Reverse mapping: pixel values back to Quill size names for display. */\nconst sizeReverse = { 10: \"small\", 18: \"large\", 24: \"huge\" } as const;\n/** Set of recognized font families for Quill. */\nconst fontSet = new Set<string>([\"monospace\", \"serif\", \"sans-serif\", \"Arial\"]);\n/** Default formatting values when no explicit format is specified. */\nconst defaultSize = 12;\n/** Default font when no explicit font is specified. */\nconst defaultFont = \"Arial\";\n/** default heading for when an unsupported header is supplied */\nconst defaultHeading = \"h5\";\n/** The string literal values accepted by LineTag. */\ntype LineTagValue = Parameters<typeof FormattedTextAsTree.LineTag>[0];\n/** Quill header numbers → LineTag values. */\nconst headerToLineTag = {\n\t1: \"h1\",\n\t2: \"h2\",\n\t3: \"h3\",\n\t4: \"h4\",\n\t5: \"h5\",\n} as const satisfies Readonly<Record<number, LineTagValue>>;\n/** LineTag values → Quill attributes. Used by buildDeltaFromTree (tree → Quill). */\nconst lineTagToQuillAttributes = {\n\th1: { header: 1 },\n\th2: { header: 2 },\n\th3: { header: 3 },\n\th4: { header: 4 },\n\th5: { header: 5 },\n\tli: { list: \"bullet\" },\n} as const satisfies Readonly<Record<LineTagValue, Record<string, unknown>>>;\n/**\n * Parse CSS font-size from a pasted HTML element's inline style.\n * Returns a Quill size name if the pixel value matches a supported size, undefined otherwise.\n * 12px is the default size and returns undefined (no Quill attribute needed).\n */\nexport function parseCssFontSize(node: HTMLElement): string | undefined {\n\tconst style = node.style.fontSize;\n\tif (!style) return undefined;\n\n\t// check if pixel value is in <size>px format\n\tif (style.endsWith(\"px\")) {\n\t\t// Parse pixel value (e.g., \"18px\" -> 18)\n\t\tconst parsed = Number.parseFloat(style);\n\t\tif (Number.isNaN(parsed)) return undefined;\n\n\t\t// Round to nearest integer and look up Quill size name\n\t\tconst rounded = Math.round(parsed);\n\t\tif (rounded in sizeReverse) {\n\t\t\treturn sizeReverse[rounded as keyof typeof sizeReverse];\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Parse CSS font-family from a pasted HTML element's inline style.\n * Tries fonts in priority order (first to last per CSS spec) and returns\n * the first recognized Quill font value.\n */\nexport function parseCssFontFamily(node: HTMLElement): string | undefined {\n\tconst style = node.style.fontFamily;\n\tif (style === \"\") return undefined;\n\n\t// Splitting on \",\" does not handle commas inside quoted font names, and escape\n\t// sequences within font names are not supported. This is fine since none of the\n\t// font names we match against contain commas or escapes.\n\tconst fonts = style.split(\",\");\n\tfor (const raw of fonts) {\n\t\t// Trim whitespace and leading and trailing quotes\n\t\tconst font = raw.trim().replace(/^[\"']/, \"\").replace(/[\"']$/, \"\");\n\t\t// check if font is in our supported font set\n\t\tif (fontSet.has(font)) {\n\t\t\treturn font;\n\t\t}\n\t}\n\t// No recognized font family found; fall back to default (Arial)\n\treturn undefined;\n}\n\n/**\n * Clipboard matcher that preserves recognized font-size and font-family\n * from pasted HTML elements. Applies each format independently via\n * compose/retain so new attributes can be added without risk of an\n * early return skipping them.\n * @see https://quilljs.com/docs/modules/clipboard#addmatcher\n */\nexport function clipboardFormatMatcher(node: Node, delta: Delta): Delta {\n\tif (!(node instanceof HTMLElement)) return delta;\n\n\tconst size = parseCssFontSize(node);\n\tconst font = parseCssFontFamily(node);\n\n\tlet result = delta;\n\tif (size !== undefined) {\n\t\tresult = result.compose(new Delta().retain(result.length(), { size }));\n\t}\n\tif (font !== undefined) {\n\t\tresult = result.compose(new Delta().retain(result.length(), { font }));\n\t}\n\treturn result;\n}\n\n/**\n * Parse a size value from Quill into a numeric pixel value.\n * Handles Quill's named sizes (small, large, huge), numeric values, and pixel strings.\n */\nfunction parseSize(size: unknown): number {\n\tif (typeof size === \"number\") return size;\n\tif (size === \"small\" || size === \"large\" || size === \"huge\") {\n\t\treturn sizeMap[size];\n\t}\n\tif (typeof size === \"string\") {\n\t\tconst parsed = Number.parseInt(size, 10);\n\t\tif (!Number.isNaN(parsed)) {\n\t\t\treturn parsed;\n\t\t}\n\t}\n\treturn defaultSize;\n}\n\n/** Extract a LineTag from Quill attributes, or undefined if none present. Quill only supports one LineTag at a time. */\nexport function parseLineTag(\n\tattributes?: Record<string, unknown>,\n): FormattedTextAsTree.LineTag | undefined {\n\tif (!attributes) return undefined;\n\t// Quill should never send both header and list attributes simultaneously.\n\tassert(\n\t\t!(typeof attributes.header === \"number\" && typeof attributes.list === \"string\"),\n\t\t0xce2 /* expected at most one line tag (header or list), but received both */,\n\t);\n\tif (typeof attributes.header === \"number\") {\n\t\tconst tag: LineTagValue =\n\t\t\theaderToLineTag[attributes.header as keyof typeof headerToLineTag] ?? defaultHeading;\n\t\treturn FormattedTextAsTree.LineTag(tag);\n\t}\n\tif (attributes.list === \"bullet\") {\n\t\treturn FormattedTextAsTree.LineTag(\"li\");\n\t}\n\treturn undefined;\n}\n\n/** Create a StringAtom containing a StringLineAtom with the given line tag. */\nfunction createLineAtom(lineTag: FormattedTextAsTree.LineTag): FormattedTextAsTree.StringAtom {\n\treturn new FormattedTextAsTree.StringAtom({\n\t\tcontent: new FormattedTextAsTree.StringLineAtom({\n\t\t\ttag: lineTag,\n\t\t}),\n\t\tformat: new FormattedTextAsTree.CharacterFormat(quillAttributesToFormat()),\n\t});\n}\n\n/**\n * Convert Quill attributes to a complete CharacterFormat object.\n * Used when inserting new characters - all format properties must have values.\n * Missing attributes default to false/default values.\n */\nfunction quillAttributesToFormat(attributes?: Record<string, unknown>): {\n\tbold: boolean;\n\titalic: boolean;\n\tunderline: boolean;\n\tsize: number;\n\tfont: string;\n} {\n\treturn {\n\t\tbold: attributes?.bold === true,\n\t\titalic: attributes?.italic === true,\n\t\tunderline: attributes?.underline === true,\n\t\tsize: parseSize(attributes?.size),\n\t\tfont: typeof attributes?.font === \"string\" ? attributes.font : defaultFont,\n\t};\n}\n\n/**\n * Convert Quill attributes to a partial CharacterFormat object.\n * Used when applying formatting to existing text via retain operations.\n * Only includes properties that were explicitly set in the Quill attributes,\n * allowing selective format updates without overwriting unrelated properties.\n */\nfunction quillAttributesToPartial(\n\tattributes?: Record<string, unknown>,\n): Partial<FormattedTextAsTree.CharacterFormat> {\n\tif (!attributes) return {};\n\tconst format: Partial<FormattedTextAsTree.CharacterFormat> = {};\n\t// Only include attributes that are explicitly present in the Quill delta\n\tif (\"bold\" in attributes) format.bold = attributes.bold === true;\n\tif (\"italic\" in attributes) format.italic = attributes.italic === true;\n\tif (\"underline\" in attributes) format.underline = attributes.underline === true;\n\tif (\"size\" in attributes) format.size = parseSize(attributes.size);\n\tif (\"font\" in attributes)\n\t\tformat.font = typeof attributes.font === \"string\" ? attributes.font : defaultFont;\n\treturn format;\n}\n\n/**\n * Convert a CharacterFormat from the tree to Quill attributes.\n * Used when building Quill deltas from tree content to sync external changes.\n * Only includes non-default values to keep deltas minimal.\n */\nfunction formatToQuillAttributes(\n\tformat: FormattedTextAsTree.CharacterFormat,\n): Record<string, unknown> {\n\tconst attributes: Record<string, unknown> = {};\n\t// Only include non-default formatting to keep Quill deltas minimal\n\tif (format.bold) attributes.bold = true;\n\tif (format.italic) attributes.italic = true;\n\tif (format.underline) attributes.underline = true;\n\tif (format.size !== defaultSize) {\n\t\t// Convert pixel value back to Quill size name if possible\n\t\tattributes.size =\n\t\t\tformat.size in sizeReverse\n\t\t\t\t? sizeReverse[format.size as keyof typeof sizeReverse]\n\t\t\t\t: `${format.size}px`;\n\t}\n\tif (format.font !== defaultFont) attributes.font = format.font;\n\treturn attributes;\n}\n\n/**\n * Build a Quill Delta representing the full tree content.\n * Iterates through formatted characters and groups consecutive characters\n * with identical formatting into single insert operations for efficiency.\n *\n * @remarks\n * This is used to sync Quill's display when the tree changes externally\n * (e.g., from a remote collaborator's edit).\n */\nexport function buildDeltaFromTree(root: FormattedTextAsTree.Tree): QuillDeltaOp[] {\n\tconst ops: QuillDeltaOp[] = [];\n\t// Accumulator for current run of identically-formatted text\n\tlet text = \"\";\n\tlet previousAttributes: Record<string, unknown> = {};\n\t// JSON key for current attributes, used for equality comparison\n\t// TODO:Performance: implement faster equality check.\n\tlet key = \"\";\n\n\t// Helper to push accumulated text as an insert operation\n\tconst pushRun = (): void => {\n\t\tif (!text) return;\n\t\tconst op: QuillDeltaOp = { insert: text };\n\t\tif (Object.keys(previousAttributes).length > 0) op.attributes = previousAttributes;\n\t\tops.push(op);\n\t};\n\n\t// Iterate through each formatted character in the tree\n\t// TODO:Performance: Optimize this loop by adding an API to get runs to FormattedTextAsTree.Tree, and implementing that using cursors.\n\t// Something like `getUniformRun(startIndex, maxLength): number` and `substring(startIndex, length): string`.\n\tfor (const atom of root.charactersWithFormatting()) {\n\t\tconst currentAttributes = formatToQuillAttributes(atom.format);\n\n\t\tif (atom.content instanceof FormattedTextAsTree.StringLineAtom) {\n\t\t\t// Merge line-specific attributes (header/list) into the format\n\t\t\tconst lineTag = atom.content.tag.value;\n\t\t\tObject.assign(currentAttributes, lineTagToQuillAttributes[lineTag]);\n\n\t\t\t// Line atoms always break the current run and emit a newline\n\t\t\tpushRun();\n\t\t\ttext = \"\";\n\t\t\tkey = \"\";\n\t\t\tconst op: QuillDeltaOp = { insert: \"\\n\" };\n\t\t\tif (Object.keys(currentAttributes).length > 0) op.attributes = currentAttributes;\n\t\t\tops.push(op);\n\t\t} else {\n\t\t\tconst stringifiedAttributes = JSON.stringify(currentAttributes);\n\t\t\tif (stringifiedAttributes === key) {\n\t\t\t\t// Same formatting as previous character - extend run\n\t\t\t\ttext += atom.content.content;\n\t\t\t} else {\n\t\t\t\t// Different formatting - push previous run and start a new one\n\t\t\t\tpushRun();\n\t\t\t\ttext = atom.content.content;\n\t\t\t\tpreviousAttributes = currentAttributes;\n\t\t\t\tkey = stringifiedAttributes;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Push any remaining accumulated text\n\tpushRun();\n\n\t// Quill expects documents to end with a newline\n\t// eslint-disable-next-line unicorn/prefer-at -- .at() not available in target\n\tconst last = ops[ops.length - 1];\n\tif (typeof last?.insert !== \"string\" || !last.insert.endsWith(\"\\n\")) {\n\t\tops.push({ insert: \"\\n\" });\n\t}\n\treturn ops;\n}\n\n/**\n * The formatted text editor view component with Quill integration.\n * Uses FormattedTextAsTree for collaborative rich text storage with formatting.\n *\n * @remarks\n * This component uses event-based synchronization via Tree.on(\"treeChanged\")\n * to efficiently handle external changes without expensive render-time operations.\n * Unlike the plain text version, this component uses Quill's delta operations\n * to make targeted edits (insert at index, delete range, format range) rather\n * than replacing all content on each change.\n */\nconst FormattedTextEditorView = forwardRef<\n\tFormattedEditorHandle,\n\t{\n\t\troot: PropTreeNode<FormattedTextAsTree.Tree>;\n\t\tundoRedo?: UndoRedo;\n\t}\n>(({ root: propRoot, undoRedo }, ref) => {\n\t// Unwrap the PropTreeNode to get the actual tree node\n\tconst root = unwrapPropTreeNode(propRoot);\n\t// DOM element where Quill will mount its editor\n\tconst editorRef = useRef<HTMLDivElement>(null);\n\t// Quill instance, persisted across renders to avoid re-initialization\n\tconst quillRef = useRef<Quill | null>(null);\n\t// Guards against update loops between Quill and the tree\n\tconst isUpdating = useRef(false);\n\t// Container element for undo/redo button portal\n\tconst [undoRedoContainer, setUndoRedoContainer] = useState<HTMLElement | undefined>(\n\t\tundefined,\n\t);\n\t// Force re-render when undo/redo state changes\n\tconst [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n\t// Expose undo/redo methods via ref\n\tuseImperativeHandle(ref, () => ({\n\t\tundo: () => undoRedo?.undo(),\n\t\tredo: () => undoRedo?.redo(),\n\t}));\n\n\t// Initialize Quill editor with formatting toolbar using Quill provided CSS\n\tuseEffect(() => {\n\t\tif (!editorRef.current || quillRef.current) return;\n\t\tconst quill = new Quill(editorRef.current, {\n\t\t\ttheme: \"snow\",\n\t\t\tplaceholder: \"Start typing with formatting...\",\n\t\t\tmodules: {\n\t\t\t\thistory: false, // Disable Quill's built-in undo/redo\n\t\t\t\ttoolbar: [\n\t\t\t\t\t[\"bold\", \"italic\", \"underline\"],\n\t\t\t\t\t[{ size: [\"small\", false, \"large\", \"huge\"] }],\n\t\t\t\t\t[{ font: [] }],\n\t\t\t\t\t[{ header: [1, 2, 3, 4, 5, false] }],\n\t\t\t\t\t[{ list: \"bullet\" }],\n\t\t\t\t\t[\"clean\"],\n\t\t\t\t],\n\t\t\t\tclipboard: [Node.ELEMENT_NODE, clipboardFormatMatcher],\n\t\t\t},\n\t\t});\n\n\t\t// Set initial content from tree\n\t\tquill.setContents(buildDeltaFromTree(root));\n\n\t\t// Listen to local Quill changes and apply them to the tree.\n\t\t// We process delta operations to make targeted edits, preserving collaboration integrity.\n\t\t// Note: Quill uses UTF-16 code units for positions, but the tree uses Unicode codepoints.\n\t\t// We must convert between them to handle emoji and other non-BMP characters correctly.\n\t\t//\n\t\t// The typing here is very fragile: if no parameter types are given,\n\t\t// the inference for this event is strongly typed, but the types are wrong (The wrong \"Delta\" type is provided).\n\t\t// This is likely related to the node16 module resolution issues with quill-delta.\n\t\t// If we break that inference by adding types, `any` is inferred for all of them, so incorrect types here would still compile.\n\t\tquill.on(\"text-change\", (delta: Delta, _oldDelta: Delta, source: EmitterSource) => {\n\t\t\tif (source !== \"user\" || isUpdating.current) return;\n\t\t\tisUpdating.current = true;\n\n\t\t\t// Wrap all tree mutations in a transaction so they undo/redo as one atomic unit.\n\t\t\t// If the node is not part of a branch (e.g. unhydrated), apply edits directly.\n\t\t\tconst branch = TreeAlpha.branch(root);\n\t\t\tconst applyDelta = (): void => {\n\t\t\t\t// Helper to count Unicode codepoints in a string\n\t\t\t\tconst codepointCount = (s: string): number => [...s].length;\n\n\t\t\t\t// Get current content for UTF-16 to codepoint position mapping\n\t\t\t\t// We update this as we process operations to keep positions accurate\n\t\t\t\tlet content = root.fullString();\n\t\t\t\tlet utf16Pos = 0; // Position in UTF-16 code units (Quill's view)\n\t\t\t\tlet cpPos = 0; // Position in codepoints (tree's view)\n\n\t\t\t\tfor (const op of delta.ops) {\n\t\t\t\t\tif (op.retain !== undefined) {\n\t\t\t\t\t\t// The docs for retain imply this is always a number, but the type definitions allow a record here.\n\t\t\t\t\t\t// It is unknown why the type definitions allow a record as they have no doc comments.\n\t\t\t\t\t\t// For now this assert seems to be passing, so we just assume its always a number.\n\t\t\t\t\t\tassert(\n\t\t\t\t\t\t\ttypeof op.retain === \"number\",\n\t\t\t\t\t\t\t0xcdf /* Expected retain count to be a number */,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// Convert UTF-16 retain count to codepoint count\n\t\t\t\t\t\tconst retainedStr = content.slice(utf16Pos, utf16Pos + op.retain);\n\t\t\t\t\t\tconst cpCount = codepointCount(retainedStr);\n\n\t\t\t\t\t\tif (op.attributes) {\n\t\t\t\t\t\t\tconst lineTag = parseLineTag(op.attributes);\n\t\t\t\t\t\t\t// Case 1: Applying line formatting (header/list) to an existing newline in the document.\n\t\t\t\t\t\t\tif (lineTag !== undefined && content[utf16Pos] === \"\\n\") {\n\t\t\t\t\t\t\t\t// Swap existing newline atom to StringLineAtom\n\t\t\t\t\t\t\t\troot.removeRange(cpPos, cpPos + 1);\n\t\t\t\t\t\t\t\troot.insertWithFormattingAt(cpPos, [createLineAtom(lineTag)]);\n\t\t\t\t\t\t\t\t// Case 2: Applying line formatting past the end of content. Quill's implicit trailing newline.\n\t\t\t\t\t\t\t} else if (lineTag !== undefined && utf16Pos >= content.length) {\n\t\t\t\t\t\t\t\t// Quill's implicit trailing newline — insert a new line atom\n\t\t\t\t\t\t\t\troot.insertWithFormattingAt(cpPos, [createLineAtom(lineTag)]);\n\t\t\t\t\t\t\t\tcontent += \"\\n\";\n\t\t\t\t\t\t\t\t// Case 3: clearing line formatting. Deletes StringLineAtom and inserts a plain\n\t\t\t\t\t\t\t\t// StringTextAtom(\"\\n\") in its place.\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\tlineTag === undefined &&\n\t\t\t\t\t\t\t\tcontent[utf16Pos] === \"\\n\" &&\n\t\t\t\t\t\t\t\troot.charactersWithFormatting()[cpPos]?.content instanceof\n\t\t\t\t\t\t\t\t\tFormattedTextAsTree.StringLineAtom\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// Quill is clearing line formatting (e.g. { retain: 1, attributes: { header: null } }).\n\t\t\t\t\t\t\t\t// StringLineAtom and StringTextAtom are distinct schema types in the tree,\n\t\t\t\t\t\t\t\t// so we can't convert between them via formatRange — we must delete the\n\t\t\t\t\t\t\t\t// StringLineAtom and insert a plain StringTextAtom(\"\\n\") in its place.\n\t\t\t\t\t\t\t\troot.removeRange(cpPos, cpPos + 1);\n\t\t\t\t\t\t\t\troot.insertAt(cpPos, \"\\n\");\n\t\t\t\t\t\t\t\t// Case 4: Normal character formatting (bold, italic, size, etc...)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\troot.formatRange(\n\t\t\t\t\t\t\t\t\tcpPos,\n\t\t\t\t\t\t\t\t\tcpPos + cpCount,\n\t\t\t\t\t\t\t\t\tquillAttributesToPartial(op.attributes),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tutf16Pos += op.retain;\n\t\t\t\t\t\tcpPos += cpCount;\n\t\t\t\t\t} else if (op.delete !== undefined) {\n\t\t\t\t\t\t// Convert UTF-16 delete count to codepoint count\n\t\t\t\t\t\tconst deletedStr = content.slice(utf16Pos, utf16Pos + op.delete);\n\t\t\t\t\t\tconst cpCount = codepointCount(deletedStr);\n\n\t\t\t\t\t\troot.removeRange(cpPos, cpPos + cpCount);\n\t\t\t\t\t\t// Update content to reflect deletion for future position calculations\n\t\t\t\t\t\tcontent = content.slice(0, utf16Pos) + content.slice(utf16Pos + op.delete);\n\t\t\t\t\t\t// Don't advance positions - next op starts at same position\n\t\t\t\t\t} else if (typeof op.insert === \"string\") {\n\t\t\t\t\t\tconst lineTag = parseLineTag(op.attributes);\n\t\t\t\t\t\tif (lineTag !== undefined && op.insert === \"\\n\") {\n\t\t\t\t\t\t\troot.insertWithFormattingAt(cpPos, [createLineAtom(lineTag)]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Insert: add new text with formatting at current position\n\t\t\t\t\t\t\troot.defaultFormat = new FormattedTextAsTree.CharacterFormat(\n\t\t\t\t\t\t\t\tquillAttributesToFormat(op.attributes),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\troot.insertAt(cpPos, op.insert);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Update content to reflect insertion\n\t\t\t\t\t\tcontent = content.slice(0, utf16Pos) + op.insert + content.slice(utf16Pos);\n\t\t\t\t\t\t// Advance by inserted content length\n\t\t\t\t\t\tutf16Pos += op.insert.length;\n\t\t\t\t\t\tcpPos += codepointCount(op.insert);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tif (branch === undefined) {\n\t\t\t\tapplyDelta();\n\t\t\t} else {\n\t\t\t\tbranch.runTransaction(applyDelta);\n\t\t\t}\n\n\t\t\tisUpdating.current = false;\n\t\t});\n\n\t\tquillRef.current = quill;\n\n\t\t// Create container for React-controlled undo/redo buttons and prepend to toolbar\n\t\tconst toolbar = editorRef.current.previousElementSibling as HTMLElement;\n\t\tconst container = document.createElement(\"span\");\n\t\tcontainer.className = \"ql-formats\";\n\t\ttoolbar.prepend(container);\n\t\tsetUndoRedoContainer(container);\n\t\t// In React strict mode, effects run twice. The `!quillRef.current` check above\n\t\t// makes the second call a no-op, preventing double-initialization of Quill.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, []);\n\n\t// Sync Quill when tree changes externally (e.g., from remote collaborators).\n\t// Uses event subscription instead of render-time observation for efficiency.\n\tuseEffect(() => {\n\t\treturn Tree.on(root, \"treeChanged\", () => {\n\t\t\t// Skip if we caused the tree change ourselves via the text-change handler\n\t\t\tif (!quillRef.current || isUpdating.current) return;\n\n\t\t\t// TODO:Performance: Once SharedTree has better ArrayNode change events,\n\t\t\t// use those events to construct a delta, instead of rebuilding a new delta then diffing every edit.\n\t\t\t// After doing the optimization, keep this diffing logic as a way to test for de-sync between the tree and Quill:\n\t\t\t// Use it in tests and possibly occasionally in debug builds.\n\t\t\tconst treeDelta = buildDeltaFromTree(root);\n\n\t\t\t// eslint doesn't seem to be resolving the types correctly here.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\tconst quillDelta: Delta = quillRef.current.getContents();\n\n\t\t\t// Compute diff between current Quill state and tree state\n\t\t\tconst diff = new Delta(quillDelta).diff(new Delta(treeDelta));\n\n\t\t\t// Only update if there are actual differences\n\t\t\tif (diff.ops.length > 0) {\n\t\t\t\tisUpdating.current = true;\n\n\t\t\t\t// Apply only the diff for surgical updates (better cursor preservation)\n\t\t\t\tquillRef.current.updateContents(diff.ops);\n\n\t\t\t\tisUpdating.current = false;\n\t\t\t}\n\t\t});\n\t}, [root]);\n\n\t// Subscribe to undo/redo state changes to update button disabled state\n\tuseEffect(() => {\n\t\tif (!undoRedo) return;\n\t\treturn undoRedo.onStateChange(() => {\n\t\t\tforceUpdate();\n\t\t});\n\t}, [undoRedo]);\n\n\t// Render undo/redo buttons via portal into Quill toolbar\n\tconst undoRedoButtons = undoRedoContainer\n\t\t? ReactDOM.createPortal(\n\t\t\t\t<>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ql-undo\"\n\t\t\t\t\t\tdisabled={undoRedo?.canUndo() !== true}\n\t\t\t\t\t\tonClick={() => undoRedo?.undo()}\n\t\t\t\t\t/>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ql-redo\"\n\t\t\t\t\t\tdisabled={undoRedo?.canRedo() !== true}\n\t\t\t\t\t\tonClick={() => undoRedo?.redo()}\n\t\t\t\t\t/>\n\t\t\t\t</>,\n\t\t\t\tundoRedoContainer,\n\t\t\t)\n\t\t: undefined;\n\n\treturn (\n\t\t<div style={{ height: \"100%\", display: \"flex\", flexDirection: \"column\" }}>\n\t\t\t<style>{`\n\t\t\t\t.ql-container { height: 100%; font-size: 14px; }\n\t\t\t\t.ql-editor { height: 100%; outline: none; }\n\t\t\t\t.ql-editor.ql-blank::before { color: #999; font-style: italic; }\n\t\t\t\t.ql-toolbar { border-radius: 4px 4px 0 0; background: #f8f9fa; }\n\t\t\t\t.ql-container.ql-snow { border-radius: 0 0 4px 4px; }\n\t\t\t\t.ql-undo, .ql-redo { width: 28px !important; }\n\t\t\t\t.ql-undo::after { content: \"↶\"; font-size: 18px; }\n\t\t\t\t.ql-redo::after { content: \"↷\"; font-size: 18px; }\n\t\t\t\t.ql-undo:disabled, .ql-redo:disabled { opacity: 0.3; cursor: not-allowed; }\n\t\t\t\t/* custom css altering Quill's default bullet point alignment */\n\t\t\t\t/* vertically center bullets in list items, since Quill's bullet has no inherent height */\n\t\t\t\tli[data-list=\"bullet\"] { display: flex; align-items: center; }\n\t\t\t\tli[data-list=\"bullet\"] .ql-ui { align-self: center; }\n\t\t\t`}</style>\n\t\t\t<h2 style={{ margin: \"10px 0\" }}>Collaborative Formatted Text Editor</h2>\n\t\t\t<div\n\t\t\t\tref={editorRef}\n\t\t\t\tstyle={{\n\t\t\t\t\tflex: 1,\n\t\t\t\t\tminHeight: \"300px\",\n\t\t\t\t\tborder: \"1px solid #ccc\",\n\t\t\t\t\tborderRadius: \"4px\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t{undoRedoButtons}\n\t\t</div>\n\t);\n});\nFormattedTextEditorView.displayName = \"FormattedTextEditorView\";\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
export { FormattedMainView, type FormattedMainViewProps, type FormattedEditorHandle, } from "./formatted/index.js";
|
|
6
|
+
export { PlainTextMainView, QuillMainView as PlainQuillView, type MainViewProps as PlainMainViewProps, } from "./plain/index.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,iBAAiB,EACjB,aAAa,IAAI,cAAc,EAC/B,KAAK,aAAa,IAAI,kBAAkB,GACxC,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
export { FormattedMainView, } from "./formatted/index.js";
|
|
6
|
+
export { PlainTextMainView, QuillMainView as PlainQuillView, } from "./plain/index.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,iBAAiB,GAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,iBAAiB,EACjB,aAAa,IAAI,cAAc,GAE/B,MAAM,kBAAkB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport {\n\tFormattedMainView,\n\ttype FormattedMainViewProps,\n\ttype FormattedEditorHandle,\n} from \"./formatted/index.js\";\nexport {\n\tPlainTextMainView,\n\tQuillMainView as PlainQuillView,\n\ttype MainViewProps as PlainMainViewProps,\n} from \"./plain/index.js\";\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
export { MainView as PlainTextMainView } from "./plainTextView.js";
|
|
6
|
+
export { MainView as QuillMainView, type MainViewProps } from "./quillView.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/text/plain/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
export { MainView as PlainTextMainView } from "./plainTextView.js";
|
|
6
|
+
export { MainView as QuillMainView } from "./quillView.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/text/plain/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAsB,MAAM,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { MainView as PlainTextMainView } from \"./plainTextView.js\";\nexport { MainView as QuillMainView, type MainViewProps } from \"./quillView.js\";\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { type FC } from "react";
|
|
6
|
+
import type { MainViewProps } from "./quillView.js";
|
|
7
|
+
/**
|
|
8
|
+
* A React component for plain text editing.
|
|
9
|
+
* @remarks
|
|
10
|
+
* Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and an HTML textarea for the UI.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare const MainView: FC<MainViewProps>;
|
|
14
|
+
//# sourceMappingURL=plainTextView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plainTextView.d.ts","sourceRoot":"","sources":["../../../src/text/plain/plainTextView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAoB,KAAK,EAAE,EAAuB,MAAM,OAAO,CAAC;AAKvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CAEtC,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
|
+
import { withMemoizedTreeObservations } from "../../useTree.js";
|
|
4
|
+
import { syncTextToTree } from "./plainUtils.js";
|
|
5
|
+
/**
|
|
6
|
+
* A React component for plain text editing.
|
|
7
|
+
* @remarks
|
|
8
|
+
* Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and an HTML textarea for the UI.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export const MainView = ({ root }) => {
|
|
12
|
+
return _jsx(PlainTextEditorView, { root: root });
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A plain text editor view component using a native HTML textarea.
|
|
16
|
+
* Uses TextAsTree for collaborative plain text storage.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* This uses withMemoizedTreeObservations to automatically re-render
|
|
20
|
+
* when the tree changes.
|
|
21
|
+
*/
|
|
22
|
+
const PlainTextEditorView = withMemoizedTreeObservations(({ root }) => {
|
|
23
|
+
// Reference to the textarea element
|
|
24
|
+
const textareaRef = useRef(null);
|
|
25
|
+
// Guards against update loops between textarea and the tree
|
|
26
|
+
const isUpdatingRef = useRef(false);
|
|
27
|
+
// Access tree content during render to establish observation.
|
|
28
|
+
// The HOC will automatically re-render when this content changes.
|
|
29
|
+
const currentText = root.fullString();
|
|
30
|
+
// Handle textarea changes - sync textarea → tree
|
|
31
|
+
const handleChange = useCallback((event) => {
|
|
32
|
+
if (isUpdatingRef.current) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
isUpdatingRef.current = true;
|
|
36
|
+
const newText = event.target.value;
|
|
37
|
+
syncTextToTree(root, newText);
|
|
38
|
+
isUpdatingRef.current = false;
|
|
39
|
+
}, [root]);
|
|
40
|
+
// Sync textarea when tree changes externally.
|
|
41
|
+
// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves
|
|
42
|
+
// via the handleChange above - in that case textarea already has the correct content.
|
|
43
|
+
if (textareaRef.current && !isUpdatingRef.current) {
|
|
44
|
+
const textareaValue = textareaRef.current.value;
|
|
45
|
+
// Only update if content actually differs (avoids cursor jump on local edits)
|
|
46
|
+
if (textareaValue !== currentText) {
|
|
47
|
+
isUpdatingRef.current = true;
|
|
48
|
+
// Preserve cursor position
|
|
49
|
+
const selectionStart = textareaRef.current.selectionStart;
|
|
50
|
+
const selectionEnd = textareaRef.current.selectionEnd;
|
|
51
|
+
textareaRef.current.value = currentText;
|
|
52
|
+
// Restore cursor position, clamped to new text length
|
|
53
|
+
const newPosition = Math.min(selectionStart, currentText.length);
|
|
54
|
+
const newEnd = Math.min(selectionEnd, currentText.length);
|
|
55
|
+
textareaRef.current.setSelectionRange(newPosition, newEnd);
|
|
56
|
+
isUpdatingRef.current = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return (_jsxs("div", { className: "text-editor-container", style: { height: "100%", display: "flex", flexDirection: "column" }, children: [_jsx("h2", { style: { margin: "10px 0" }, children: "Collaborative Text Editor" }), _jsx("textarea", { ref: textareaRef, defaultValue: currentText, onChange: handleChange, placeholder: "Start typing...", style: {
|
|
60
|
+
flex: 1,
|
|
61
|
+
minHeight: "300px",
|
|
62
|
+
border: "1px solid #ccc",
|
|
63
|
+
borderRadius: "4px",
|
|
64
|
+
padding: "8px",
|
|
65
|
+
fontSize: "14px",
|
|
66
|
+
fontFamily: "inherit",
|
|
67
|
+
resize: "vertical",
|
|
68
|
+
} })] }));
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=plainTextView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plainTextView.js","sourceRoot":"","sources":["../../../src/text/plain/plainTextView.tsx"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { TextAsTree } from \"@fluidframework/tree/internal\";\nimport { type ChangeEvent, type FC, useCallback, useRef } from \"react\";\n\nimport { withMemoizedTreeObservations } from \"../../useTree.js\";\n\nimport { syncTextToTree } from \"./plainUtils.js\";\nimport type { MainViewProps } from \"./quillView.js\";\n\n/**\n * A React component for plain text editing.\n * @remarks\n * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and an HTML textarea for the UI.\n * @internal\n */\nexport const MainView: FC<MainViewProps> = ({ root }) => {\n\treturn <PlainTextEditorView root={root} />;\n};\n\n/**\n * A plain text editor view component using a native HTML textarea.\n * Uses TextAsTree for collaborative plain text storage.\n *\n * @remarks\n * This uses withMemoizedTreeObservations to automatically re-render\n * when the tree changes.\n */\nconst PlainTextEditorView = withMemoizedTreeObservations(\n\t({ root }: { root: TextAsTree.Tree }) => {\n\t\t// Reference to the textarea element\n\t\tconst textareaRef = useRef<HTMLTextAreaElement>(null);\n\t\t// Guards against update loops between textarea and the tree\n\t\tconst isUpdatingRef = useRef<boolean>(false);\n\n\t\t// Access tree content during render to establish observation.\n\t\t// The HOC will automatically re-render when this content changes.\n\t\tconst currentText = root.fullString();\n\n\t\t// Handle textarea changes - sync textarea → tree\n\t\tconst handleChange = useCallback(\n\t\t\t(event: ChangeEvent<HTMLTextAreaElement>) => {\n\t\t\t\tif (isUpdatingRef.current) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\tconst newText = event.target.value;\n\t\t\t\tsyncTextToTree(root, newText);\n\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t},\n\t\t\t[root],\n\t\t);\n\n\t\t// Sync textarea when tree changes externally.\n\t\t// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves\n\t\t// via the handleChange above - in that case textarea already has the correct content.\n\t\tif (textareaRef.current && !isUpdatingRef.current) {\n\t\t\tconst textareaValue = textareaRef.current.value;\n\n\t\t\t// Only update if content actually differs (avoids cursor jump on local edits)\n\t\t\tif (textareaValue !== currentText) {\n\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\t// Preserve cursor position\n\t\t\t\tconst selectionStart = textareaRef.current.selectionStart;\n\t\t\t\tconst selectionEnd = textareaRef.current.selectionEnd;\n\n\t\t\t\ttextareaRef.current.value = currentText;\n\n\t\t\t\t// Restore cursor position, clamped to new text length\n\t\t\t\tconst newPosition = Math.min(selectionStart, currentText.length);\n\t\t\t\tconst newEnd = Math.min(selectionEnd, currentText.length);\n\t\t\t\ttextareaRef.current.setSelectionRange(newPosition, newEnd);\n\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t}\n\t\t}\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName=\"text-editor-container\"\n\t\t\t\tstyle={{ height: \"100%\", display: \"flex\", flexDirection: \"column\" }}\n\t\t\t>\n\t\t\t\t<h2 style={{ margin: \"10px 0\" }}>Collaborative Text Editor</h2>\n\t\t\t\t<textarea\n\t\t\t\t\tref={textareaRef}\n\t\t\t\t\tdefaultValue={currentText}\n\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\tplaceholder=\"Start typing...\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\tminHeight: \"300px\",\n\t\t\t\t\t\tborder: \"1px solid #ccc\",\n\t\t\t\t\t\tborderRadius: \"4px\",\n\t\t\t\t\t\tpadding: \"8px\",\n\t\t\t\t\t\tfontSize: \"14px\",\n\t\t\t\t\t\tfontFamily: \"inherit\",\n\t\t\t\t\t\tresize: \"vertical\",\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t);\n\t},\n);\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { TextAsTree } from "@fluidframework/tree/internal";
|
|
6
|
+
/**
|
|
7
|
+
* Sync `newText` into the provided `root` tree.
|
|
8
|
+
*/
|
|
9
|
+
export declare function syncTextToTree(root: TextAsTree.Tree, newText: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Sync `newText` into the provided `root` tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeSync<T>(existing: readonly T[], final: readonly T[]): {
|
|
14
|
+
remove?: {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
};
|
|
18
|
+
insert?: {
|
|
19
|
+
location: number;
|
|
20
|
+
slice: T[];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=plainUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plainUtils.d.ts","sourceRoot":"","sources":["../../../src/text/plain/plainUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC5B,QAAQ,EAAE,SAAS,CAAC,EAAE,EACtB,KAAK,EAAE,SAAS,CAAC,EAAE,GACjB;IAAE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,EAAE,CAAA;KAAE,CAAA;CAAE,CAwCxF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Sync `newText` into the provided `root` tree.
|
|
7
|
+
*/
|
|
8
|
+
export function syncTextToTree(root, newText) {
|
|
9
|
+
const sync = computeSync(root.charactersCopy(), [...newText]);
|
|
10
|
+
if (sync.remove) {
|
|
11
|
+
root.removeRange(sync.remove.start, sync.remove.end);
|
|
12
|
+
}
|
|
13
|
+
if (sync.insert) {
|
|
14
|
+
root.insertAt(sync.insert.location, sync.insert.slice.join(""));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sync `newText` into the provided `root` tree.
|
|
19
|
+
*/
|
|
20
|
+
export function computeSync(existing, final) {
|
|
21
|
+
// Find common prefix and suffix to minimize changes
|
|
22
|
+
let prefixLength = 0;
|
|
23
|
+
while (prefixLength < existing.length &&
|
|
24
|
+
prefixLength < final.length &&
|
|
25
|
+
existing[prefixLength] === final[prefixLength]) {
|
|
26
|
+
prefixLength++;
|
|
27
|
+
}
|
|
28
|
+
let suffixLength = 0;
|
|
29
|
+
while (suffixLength + prefixLength < existing.length &&
|
|
30
|
+
suffixLength + prefixLength < final.length &&
|
|
31
|
+
existing[existing.length - 1 - suffixLength] === final[final.length - 1 - suffixLength]) {
|
|
32
|
+
suffixLength++;
|
|
33
|
+
}
|
|
34
|
+
// Locate middle replaced range in existing and final
|
|
35
|
+
const existingMiddleStart = prefixLength;
|
|
36
|
+
const existingMiddleEnd = existing.length - suffixLength;
|
|
37
|
+
const newMiddleStart = prefixLength;
|
|
38
|
+
const newMiddleEnd = final.length - suffixLength;
|
|
39
|
+
return {
|
|
40
|
+
remove: existingMiddleStart < existingMiddleEnd
|
|
41
|
+
? { start: existingMiddleStart, end: existingMiddleEnd }
|
|
42
|
+
: undefined,
|
|
43
|
+
insert: newMiddleStart < newMiddleEnd
|
|
44
|
+
? {
|
|
45
|
+
location: existingMiddleStart,
|
|
46
|
+
slice: final.slice(newMiddleStart, newMiddleEnd),
|
|
47
|
+
}
|
|
48
|
+
: undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=plainUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plainUtils.js","sourceRoot":"","sources":["../../../src/text/plain/plainUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAqB,EAAE,OAAe;IACpE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAE9D,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAC1B,QAAsB,EACtB,KAAmB;IAEnB,oDAAoD;IAEpD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,OACC,YAAY,GAAG,QAAQ,CAAC,MAAM;QAC9B,YAAY,GAAG,KAAK,CAAC,MAAM;QAC3B,QAAQ,CAAC,YAAY,CAAC,KAAK,KAAK,CAAC,YAAY,CAAC,EAC7C,CAAC;QACF,YAAY,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,OACC,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC,MAAM;QAC7C,YAAY,GAAG,YAAY,GAAG,KAAK,CAAC,MAAM;QAC1C,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,YAAY,CAAC,EACtF,CAAC;QACF,YAAY,EAAE,CAAC;IAChB,CAAC;IAED,qDAAqD;IACrD,MAAM,mBAAmB,GAAG,YAAY,CAAC;IACzC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,GAAG,YAAY,CAAC;IACzD,MAAM,cAAc,GAAG,YAAY,CAAC;IACpC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;IAEjD,OAAO;QACN,MAAM,EACL,mBAAmB,GAAG,iBAAiB;YACtC,CAAC,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,iBAAiB,EAAE;YACxD,CAAC,CAAC,SAAS;QACb,MAAM,EACL,cAAc,GAAG,YAAY;YAC5B,CAAC,CAAC;gBACA,QAAQ,EAAE,mBAAmB;gBAC7B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,YAAY,CAAC;aAChD;YACF,CAAC,CAAC,SAAS;KACb,CAAC;AACH,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { TextAsTree } from \"@fluidframework/tree/internal\";\n\n/**\n * Sync `newText` into the provided `root` tree.\n */\nexport function syncTextToTree(root: TextAsTree.Tree, newText: string): void {\n\tconst sync = computeSync(root.charactersCopy(), [...newText]);\n\n\tif (sync.remove) {\n\t\troot.removeRange(sync.remove.start, sync.remove.end);\n\t}\n\tif (sync.insert) {\n\t\troot.insertAt(sync.insert.location, sync.insert.slice.join(\"\"));\n\t}\n}\n\n/**\n * Sync `newText` into the provided `root` tree.\n */\nexport function computeSync<T>(\n\texisting: readonly T[],\n\tfinal: readonly T[],\n): { remove?: { start: number; end: number }; insert?: { location: number; slice: T[] } } {\n\t// Find common prefix and suffix to minimize changes\n\n\tlet prefixLength = 0;\n\twhile (\n\t\tprefixLength < existing.length &&\n\t\tprefixLength < final.length &&\n\t\texisting[prefixLength] === final[prefixLength]\n\t) {\n\t\tprefixLength++;\n\t}\n\n\tlet suffixLength = 0;\n\twhile (\n\t\tsuffixLength + prefixLength < existing.length &&\n\t\tsuffixLength + prefixLength < final.length &&\n\t\texisting[existing.length - 1 - suffixLength] === final[final.length - 1 - suffixLength]\n\t) {\n\t\tsuffixLength++;\n\t}\n\n\t// Locate middle replaced range in existing and final\n\tconst existingMiddleStart = prefixLength;\n\tconst existingMiddleEnd = existing.length - suffixLength;\n\tconst newMiddleStart = prefixLength;\n\tconst newMiddleEnd = final.length - suffixLength;\n\n\treturn {\n\t\tremove:\n\t\t\texistingMiddleStart < existingMiddleEnd\n\t\t\t\t? { start: existingMiddleStart, end: existingMiddleEnd }\n\t\t\t\t: undefined,\n\t\tinsert:\n\t\t\tnewMiddleStart < newMiddleEnd\n\t\t\t\t? {\n\t\t\t\t\t\tlocation: existingMiddleStart,\n\t\t\t\t\t\tslice: final.slice(newMiddleStart, newMiddleEnd),\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { TextAsTree } from "@fluidframework/tree/internal";
|
|
6
|
+
import { type FC } from "react";
|
|
7
|
+
import type { PropTreeNode } from "../../propNode.js";
|
|
8
|
+
/**
|
|
9
|
+
* Props for the MainView component.
|
|
10
|
+
* @input @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface MainViewProps {
|
|
13
|
+
root: PropTreeNode<TextAsTree.Tree>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A React component for plain text editing.
|
|
17
|
+
* @remarks
|
|
18
|
+
* Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare const MainView: FC<MainViewProps>;
|
|
22
|
+
//# sourceMappingURL=quillView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quillView.d.ts","sourceRoot":"","sources":["../../../src/text/plain/quillView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,KAAK,EAAE,EAAqB,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKtD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;CACpC;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CAEtC,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Quill from "quill";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { withMemoizedTreeObservations } from "../../useTree.js";
|
|
5
|
+
import { syncTextToTree } from "./plainUtils.js";
|
|
6
|
+
/**
|
|
7
|
+
* A React component for plain text editing.
|
|
8
|
+
* @remarks
|
|
9
|
+
* Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const MainView = ({ root }) => {
|
|
13
|
+
return _jsx(TextEditorView, { root: root });
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The text editor view component with Quill integration.
|
|
17
|
+
* Uses TextAsTree for collaborative plain text storage.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* This uses withMemoizedTreeObservations to automatically re-render
|
|
21
|
+
* when the tree changes.
|
|
22
|
+
*/
|
|
23
|
+
const TextEditorView = withMemoizedTreeObservations(({ root }) => {
|
|
24
|
+
// DOM element where Quill will mount its editor
|
|
25
|
+
const editorRef = useRef(null);
|
|
26
|
+
// Quill instance, persisted across renders to avoid re-initialization
|
|
27
|
+
const quillRef = useRef(null);
|
|
28
|
+
// Guards against update loops between Quill and the tree
|
|
29
|
+
const isUpdatingRef = useRef(false);
|
|
30
|
+
// Access tree content during render to establish observation.
|
|
31
|
+
// The HOC will automatically re-render when this content changes.
|
|
32
|
+
const currentText = root.fullString();
|
|
33
|
+
// Initialize Quill editor
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (editorRef.current && !quillRef.current) {
|
|
36
|
+
const quill = new Quill(editorRef.current, {
|
|
37
|
+
placeholder: "Start typing...",
|
|
38
|
+
});
|
|
39
|
+
// Set initial content from tree (add trailing newline to match Quill's convention)
|
|
40
|
+
const initialText = root.fullString();
|
|
41
|
+
if (initialText.length > 0) {
|
|
42
|
+
const textWithNewline = initialText.endsWith("\n") ? initialText : `${initialText}\n`;
|
|
43
|
+
quill.setText(textWithNewline);
|
|
44
|
+
}
|
|
45
|
+
// Listen to local Quill changes
|
|
46
|
+
quill.on("text-change", (_delta, _oldDelta, source) => {
|
|
47
|
+
if (source === "user" && !isUpdatingRef.current) {
|
|
48
|
+
isUpdatingRef.current = true;
|
|
49
|
+
// Get plain text from Quill and preserve trailing newline
|
|
50
|
+
const newText = quill.getText();
|
|
51
|
+
// TODO: Consider using delta from Quill to compute a more minimal update,
|
|
52
|
+
// and maybe add a debugAssert that the delta actually gets the strings synchronized.
|
|
53
|
+
syncTextToTree(root, newText);
|
|
54
|
+
isUpdatingRef.current = false;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
quillRef.current = quill;
|
|
58
|
+
}
|
|
59
|
+
// In React strict mode, effects run twice. The `!quillRef.current` check above
|
|
60
|
+
// makes the second call a no-op, preventing double-initialization of Quill.
|
|
61
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
+
}, []);
|
|
63
|
+
// Sync Quill when tree changes externally.
|
|
64
|
+
// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves
|
|
65
|
+
// via the text-change handler above - in that case Quill already has the correct content.
|
|
66
|
+
// No update is lost because isUpdatingRef is only true synchronously during our own
|
|
67
|
+
// handler execution, so Quill already reflects the change.
|
|
68
|
+
if (quillRef.current && !isUpdatingRef.current) {
|
|
69
|
+
const quillText = quillRef.current.getText();
|
|
70
|
+
// Normalize tree text to match Quill's trailing newline convention
|
|
71
|
+
const treeTextWithNewline = currentText.endsWith("\n") ? currentText : `${currentText}\n`;
|
|
72
|
+
// Only update if content actually differs (avoids cursor jump on local edits)
|
|
73
|
+
if (quillText !== treeTextWithNewline) {
|
|
74
|
+
isUpdatingRef.current = true;
|
|
75
|
+
const selection = quillRef.current.getSelection();
|
|
76
|
+
quillRef.current.setText(treeTextWithNewline);
|
|
77
|
+
if (selection) {
|
|
78
|
+
const length = quillRef.current.getLength();
|
|
79
|
+
const newPosition = Math.min(selection.index, length - 1);
|
|
80
|
+
quillRef.current.setSelection(newPosition, 0);
|
|
81
|
+
}
|
|
82
|
+
isUpdatingRef.current = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return (_jsxs("div", { className: "text-editor-container", style: { height: "100%", display: "flex", flexDirection: "column" }, children: [_jsx("style", { children: `
|
|
86
|
+
.ql-container {
|
|
87
|
+
height: 100%;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
}
|
|
90
|
+
.ql-editor {
|
|
91
|
+
height: 100%;
|
|
92
|
+
outline: none;
|
|
93
|
+
}
|
|
94
|
+
.ql-editor.ql-blank::before {
|
|
95
|
+
color: #999;
|
|
96
|
+
font-style: italic;
|
|
97
|
+
}
|
|
98
|
+
` }), _jsx("h2", { style: { margin: "10px 0" }, children: "Collaborative Text Editor" }), _jsx("div", { ref: editorRef, style: {
|
|
99
|
+
flex: 1,
|
|
100
|
+
minHeight: "300px",
|
|
101
|
+
border: "1px solid #ccc",
|
|
102
|
+
borderRadius: "4px",
|
|
103
|
+
padding: "8px",
|
|
104
|
+
} })] }));
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=quillView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quillView.js","sourceRoot":"","sources":["../../../src/text/plain/quillView.tsx"],"names":[],"mappings":";AAMA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAW,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAUjD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAsB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;IACvD,OAAO,KAAC,cAAc,IAAC,IAAI,EAAE,IAAI,GAAI,CAAC;AACvC,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,4BAA4B,CAAC,CAAC,EAAE,IAAI,EAA6B,EAAE,EAAE;IAC3F,gDAAgD;IAChD,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC/C,sEAAsE;IACtE,MAAM,QAAQ,GAAG,MAAM,CAAe,IAAI,CAAC,CAAC;IAC5C,yDAAyD;IACzD,MAAM,aAAa,GAAG,MAAM,CAAU,KAAK,CAAC,CAAC;IAE7C,8DAA8D;IAC9D,kEAAkE;IAClE,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IAEtC,0BAA0B;IAC1B,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,SAAS,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE;gBAC1C,WAAW,EAAE,iBAAiB;aAC9B,CAAC,CAAC;YAEH,mFAAmF;YACnF,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,eAAe,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,CAAC;gBACtF,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;YAChC,CAAC;YAED,gCAAgC;YAChC,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE;gBACrD,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;oBACjD,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;oBAE7B,0DAA0D;oBAC1D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;oBAChC,0EAA0E;oBAC1E,qFAAqF;oBACrF,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBAE9B,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;gBAC/B,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAC;QAC1B,CAAC;QACD,+EAA+E;QAC/E,4EAA4E;QAC5E,uDAAuD;IACxD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,2CAA2C;IAC3C,qFAAqF;IACrF,0FAA0F;IAC1F,oFAAoF;IACpF,2DAA2D;IAC3D,IAAI,QAAQ,CAAC,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7C,mEAAmE;QACnE,MAAM,mBAAmB,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,CAAC;QAE1F,8EAA8E;QAC9E,IAAI,SAAS,KAAK,mBAAmB,EAAE,CAAC;YACvC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAE7B,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAClD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;YAC9C,IAAI,SAAS,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC1D,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;YAC/C,CAAC;YAED,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,OAAO,CACN,eACC,SAAS,EAAC,uBAAuB,EACjC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,aAEnE,0BACE;;;;;;;;;;;;;KAaA,GACM,EACR,aAAI,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,0CAAgC,EAC/D,cACC,GAAG,EAAE,SAAS,EACd,KAAK,EAAE;oBACN,IAAI,EAAE,CAAC;oBACP,SAAS,EAAE,OAAO;oBAClB,MAAM,EAAE,gBAAgB;oBACxB,YAAY,EAAE,KAAK;oBACnB,OAAO,EAAE,KAAK;iBACd,GACA,IACG,CACN,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { TextAsTree } from \"@fluidframework/tree/internal\";\nimport Quill from \"quill\";\nimport { type FC, useEffect, useRef } from \"react\";\n\nimport type { PropTreeNode } from \"../../propNode.js\";\nimport { withMemoizedTreeObservations } from \"../../useTree.js\";\n\nimport { syncTextToTree } from \"./plainUtils.js\";\n\n/**\n * Props for the MainView component.\n * @input @internal\n */\nexport interface MainViewProps {\n\troot: PropTreeNode<TextAsTree.Tree>;\n}\n\n/**\n * A React component for plain text editing.\n * @remarks\n * Uses {@link @fluidframework/tree#TextAsTree.Tree} for the data-model and Quill for the UI.\n * @internal\n */\nexport const MainView: FC<MainViewProps> = ({ root }) => {\n\treturn <TextEditorView root={root} />;\n};\n\n/**\n * The text editor view component with Quill integration.\n * Uses TextAsTree for collaborative plain text storage.\n *\n * @remarks\n * This uses withMemoizedTreeObservations to automatically re-render\n * when the tree changes.\n */\nconst TextEditorView = withMemoizedTreeObservations(({ root }: { root: TextAsTree.Tree }) => {\n\t// DOM element where Quill will mount its editor\n\tconst editorRef = useRef<HTMLDivElement>(null);\n\t// Quill instance, persisted across renders to avoid re-initialization\n\tconst quillRef = useRef<Quill | null>(null);\n\t// Guards against update loops between Quill and the tree\n\tconst isUpdatingRef = useRef<boolean>(false);\n\n\t// Access tree content during render to establish observation.\n\t// The HOC will automatically re-render when this content changes.\n\tconst currentText = root.fullString();\n\n\t// Initialize Quill editor\n\tuseEffect(() => {\n\t\tif (editorRef.current && !quillRef.current) {\n\t\t\tconst quill = new Quill(editorRef.current, {\n\t\t\t\tplaceholder: \"Start typing...\",\n\t\t\t});\n\n\t\t\t// Set initial content from tree (add trailing newline to match Quill's convention)\n\t\t\tconst initialText = root.fullString();\n\t\t\tif (initialText.length > 0) {\n\t\t\t\tconst textWithNewline = initialText.endsWith(\"\\n\") ? initialText : `${initialText}\\n`;\n\t\t\t\tquill.setText(textWithNewline);\n\t\t\t}\n\n\t\t\t// Listen to local Quill changes\n\t\t\tquill.on(\"text-change\", (_delta, _oldDelta, source) => {\n\t\t\t\tif (source === \"user\" && !isUpdatingRef.current) {\n\t\t\t\t\tisUpdatingRef.current = true;\n\n\t\t\t\t\t// Get plain text from Quill and preserve trailing newline\n\t\t\t\t\tconst newText = quill.getText();\n\t\t\t\t\t// TODO: Consider using delta from Quill to compute a more minimal update,\n\t\t\t\t\t// and maybe add a debugAssert that the delta actually gets the strings synchronized.\n\t\t\t\t\tsyncTextToTree(root, newText);\n\n\t\t\t\t\tisUpdatingRef.current = false;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tquillRef.current = quill;\n\t\t}\n\t\t// In React strict mode, effects run twice. The `!quillRef.current` check above\n\t\t// makes the second call a no-op, preventing double-initialization of Quill.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, []);\n\n\t// Sync Quill when tree changes externally.\n\t// We skip this if isUpdatingRef is true, meaning we caused the tree change ourselves\n\t// via the text-change handler above - in that case Quill already has the correct content.\n\t// No update is lost because isUpdatingRef is only true synchronously during our own\n\t// handler execution, so Quill already reflects the change.\n\tif (quillRef.current && !isUpdatingRef.current) {\n\t\tconst quillText = quillRef.current.getText();\n\t\t// Normalize tree text to match Quill's trailing newline convention\n\t\tconst treeTextWithNewline = currentText.endsWith(\"\\n\") ? currentText : `${currentText}\\n`;\n\n\t\t// Only update if content actually differs (avoids cursor jump on local edits)\n\t\tif (quillText !== treeTextWithNewline) {\n\t\t\tisUpdatingRef.current = true;\n\n\t\t\tconst selection = quillRef.current.getSelection();\n\t\t\tquillRef.current.setText(treeTextWithNewline);\n\t\t\tif (selection) {\n\t\t\t\tconst length = quillRef.current.getLength();\n\t\t\t\tconst newPosition = Math.min(selection.index, length - 1);\n\t\t\t\tquillRef.current.setSelection(newPosition, 0);\n\t\t\t}\n\n\t\t\tisUpdatingRef.current = false;\n\t\t}\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"text-editor-container\"\n\t\t\tstyle={{ height: \"100%\", display: \"flex\", flexDirection: \"column\" }}\n\t\t>\n\t\t\t<style>\n\t\t\t\t{`\n\t\t\t\t\t.ql-container {\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t}\n\t\t\t\t\t.ql-editor {\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\toutline: none;\n\t\t\t\t\t}\n\t\t\t\t\t.ql-editor.ql-blank::before {\n\t\t\t\t\t\tcolor: #999;\n\t\t\t\t\t\tfont-style: italic;\n\t\t\t\t\t}\n\t\t\t\t`}\n\t\t\t</style>\n\t\t\t<h2 style={{ margin: \"10px 0\" }}>Collaborative Text Editor</h2>\n\t\t\t<div\n\t\t\t\tref={editorRef}\n\t\t\t\tstyle={{\n\t\t\t\t\tflex: 1,\n\t\t\t\t\tminHeight: \"300px\",\n\t\t\t\t\tborder: \"1px solid #ccc\",\n\t\t\t\t\tborderRadius: \"4px\",\n\t\t\t\t\tpadding: \"8px\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t);\n});\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { Listenable } from "@fluidframework/core-interfaces";
|
|
6
|
+
import { type TreeViewEvents } from "@fluidframework/tree";
|
|
7
|
+
/**
|
|
8
|
+
* Interface for undo/redo stack operations.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export interface UndoRedo {
|
|
12
|
+
/**
|
|
13
|
+
* Reverts the most recent change. Only valid to call when {@link UndoRedo.canUndo} returns true.
|
|
14
|
+
* @throws Error if there is nothing to undo.
|
|
15
|
+
*/
|
|
16
|
+
undo(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Reapplies the most recently undone change. Only valid to call when {@link UndoRedo.canRedo} returns true.
|
|
19
|
+
* @throws Error if there is nothing to redo.
|
|
20
|
+
*/
|
|
21
|
+
redo(): void;
|
|
22
|
+
dispose(): void;
|
|
23
|
+
canUndo(): boolean;
|
|
24
|
+
canRedo(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Subscribe to state changes (when canUndo/canRedo may have changed).
|
|
27
|
+
* @param callback - Called when the undo/redo stack state changes
|
|
28
|
+
* @returns Unsubscribe function
|
|
29
|
+
*/
|
|
30
|
+
onStateChange(callback: () => void): () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Manages undo and redo stacks for a TreeView.
|
|
34
|
+
* Listens to commitApplied events and manages Revertible objects.
|
|
35
|
+
* @sealed @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare class UndoRedoStacks implements UndoRedo {
|
|
38
|
+
private readonly undoStack;
|
|
39
|
+
private readonly redoStack;
|
|
40
|
+
private readonly listeners;
|
|
41
|
+
private readonly unsubscribe;
|
|
42
|
+
constructor(events: Listenable<TreeViewEvents>);
|
|
43
|
+
undo(): void;
|
|
44
|
+
redo(): void;
|
|
45
|
+
dispose(): void;
|
|
46
|
+
canUndo(): boolean;
|
|
47
|
+
canRedo(): boolean;
|
|
48
|
+
onStateChange(callback: () => void): () => void;
|
|
49
|
+
private notifyListeners;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=undoRedo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undoRedo.d.ts","sourceRoot":"","sources":["../src/undoRedo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAE9B;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACxB;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAC;IACb;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAChB,OAAO,IAAI,OAAO,CAAC;IACnB,OAAO,IAAI,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;gBAEtB,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC;IAoB9C,IAAI,IAAI,IAAI;IASZ,IAAI,IAAI,IAAI;IASZ,OAAO,IAAI,IAAI;IASf,OAAO,IAAI,OAAO;IAIlB,OAAO,IAAI,OAAO;IAIlB,aAAa,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKtD,OAAO,CAAC,eAAe;CAKvB"}
|