Files
kandev/apps/web/components/task/chat/tiptap-dynamic-placeholder.ts
T
José Almeida 7de788ceed feat: add cross-integration entity references (#1862)
* feat: add cross-integration entity references

* fix(web): anchor entity reference menu to composer

* fix: keep Kandev tasks under @ mentions

* fix: address entity reference review findings

* test(e2e): use option role for prompt menu

* test(e2e): update passthrough mention roles

* fix: preserve queued references and paginate Azure PRs
2026-07-23 09:03:56 +01:00

61 lines
2.1 KiB
TypeScript

import { Extension, isNodeEmpty, type Editor } from "@tiptap/core";
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { Decoration, DecorationSet } from "@tiptap/pm/view";
type DynamicPlaceholderStorage = {
dynamicPlaceholder: { text: string };
};
function placeholderStorage(editor: Editor): DynamicPlaceholderStorage {
return editor.storage as unknown as DynamicPlaceholderStorage;
}
/** Placeholder extension backed by live editor storage, avoiding stale options snapshots. */
export const DynamicPlaceholder = Extension.create({
name: "dynamicPlaceholder",
addStorage() {
return { text: "" };
},
addProseMirrorPlugins() {
const editor = this.editor;
return [
new Plugin({
key: new PluginKey("dynamicPlaceholder"),
props: {
decorations: ({ doc, selection }) => {
if (!editor.isEditable && !editor.isEmpty) return null;
const { anchor } = selection;
const decorations: InstanceType<typeof Decoration>[] = [];
const isEmptyDoc = editor.isEmpty;
doc.descendants((node: ProseMirrorNode, pos: number) => {
const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
const isEmpty = !node.isLeaf && isNodeEmpty(node);
if (hasAnchor && isEmpty) {
const classes = ["is-empty"];
if (isEmptyDoc) classes.push("is-editor-empty");
decorations.push(
Decoration.node(pos, pos + node.nodeSize, {
class: classes.join(" "),
"data-placeholder": placeholderStorage(editor).dynamicPlaceholder.text,
}),
);
}
return false;
});
return DecorationSet.create(doc, decorations);
},
},
}),
];
},
});
export function updateDynamicPlaceholder(editor: Editor | null, text: string) {
if (!editor) return;
placeholderStorage(editor).dynamicPlaceholder.text = text;
editor.view.dispatch(editor.state.tr);
}