@gravity-ui/markdown-editor 15.34.4 → 15.34.5

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.
@@ -7,7 +7,6 @@ const prosemirror_utils_1 = require("prosemirror-utils");
7
7
  const prosemirror_view_1 = require("prosemirror-view");
8
8
  const classname_1 = require("../../../classname.js");
9
9
  const lodash_1 = require("../../../lodash.js");
10
- const nodes_1 = require("../../../utils/nodes.js");
11
10
  const placeholder_1 = require("../../../utils/placeholder.js");
12
11
  const selection_1 = require("../../../utils/selection.js");
13
12
  require("./index.css");
@@ -40,10 +39,32 @@ const createPlaceholder = (node, parent, focus) => {
40
39
  };
41
40
  exports.createPlaceholder = createPlaceholder;
42
41
  const placeholderNeeded = (node) => {
43
- const childrenWithPlaceholderVisible = (0, prosemirror_utils_1.findChildren)(node, (n) => Boolean(n.type.spec.placeholder?.alwaysVisible));
44
- return ((0, nodes_1.isNodeEmpty)(node) &&
45
- // If there are child nodes with constant placeholder - give them the priority
46
- !childrenWithPlaceholderVisible.length);
42
+ // Combine all checks in a single descendants pass
43
+ let hasAlwaysVisiblePlaceholder = false;
44
+ let hasNonEmptyContent = false;
45
+ node.descendants((n) => {
46
+ // Check for alwaysVisible placeholder
47
+ if (n.type.spec.placeholder?.alwaysVisible) {
48
+ hasAlwaysVisiblePlaceholder = true;
49
+ return false; // Stop traversal early
50
+ }
51
+ // Check if node has non-empty content (same logic as isNodeEmpty)
52
+ if (n.isAtom) {
53
+ hasNonEmptyContent = true;
54
+ return false; // Stop traversal early
55
+ }
56
+ if (n.isText && n.textContent) {
57
+ hasNonEmptyContent = true;
58
+ return false; // Stop traversal early
59
+ }
60
+ // Non-empty paragraph or other block with content
61
+ if (n.content.size > 0 && n.type.name !== 'paragraph') {
62
+ hasNonEmptyContent = true;
63
+ return false; // Stop traversal early
64
+ }
65
+ return true;
66
+ });
67
+ return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;
47
68
  };
48
69
  const addDecoration = (widgetsMap, node, pos, parent, cursorPos, globalState) => {
49
70
  const placeholderSpec = node.type.spec.placeholder;
@@ -92,19 +113,19 @@ const Placeholder = (builder, opts) => {
92
113
  }), builder.Priority.VeryHigh);
93
114
  };
94
115
  exports.Placeholder = Placeholder;
95
- function getPlaceholderWidgetSpecs(state) {
116
+ function getPlaceholderWidgetSpecs(state, pluginKeys) {
96
117
  const globalState = { hasFocus: false };
97
118
  const widgetsMap = {};
98
119
  const { selection } = state;
99
120
  const cursorPos = (0, selection_1.isTextSelection)(selection) ? selection.$cursor?.pos : null;
100
- getPlaceholderPluginKeys(state.schema).forEach((f) => {
121
+ pluginKeys.forEach((f) => {
101
122
  // We use find because it can be used to iterate over the DecorationSet.
102
123
  f.getState(state)?.find(undefined, undefined, (spec) => {
103
124
  widgetsMap[spec.pos] = f;
104
125
  return false;
105
126
  });
106
127
  });
107
- // Fraw placeholder for all nodes where placeholder is alwaysVisible
128
+ // Draw placeholder for all nodes where placeholder is alwaysVisible
108
129
  const decorate = (node, pos, parent) => {
109
130
  const placeholderSpec = node.type.spec.placeholder;
110
131
  if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {
@@ -129,23 +150,36 @@ function getPlaceholderWidgetSpecs(state) {
129
150
  return { widgetSpecs, hasFocus: globalState.hasFocus };
130
151
  }
131
152
  function initState(state) {
132
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state);
153
+ const pluginKeys = getPlaceholderPluginKeys(state.schema);
154
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state, pluginKeys);
133
155
  const decorationSet = prosemirror_view_1.DecorationSet.create(state.doc, widgetSpecs.map((widget) => prosemirror_view_1.Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
134
- return { decorationSet, hasFocus };
156
+ return { decorationSet, hasFocus, pluginKeys };
135
157
  }
136
- function applyState(tr, oldPluginState, _oldState, newState) {
137
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState);
158
+ function applyState(tr, oldPluginState, oldState, newState) {
159
+ // Early return if document hasn't changed and selection hasn't changed
160
+ // This avoids unnecessary recalculation of decorations
161
+ if (!tr.docChanged && !tr.selectionSet) {
162
+ return oldPluginState;
163
+ }
164
+ // Reuse cached plugin keys if schema hasn't changed
165
+ const pluginKeys = oldState.schema === newState.schema
166
+ ? oldPluginState.pluginKeys
167
+ : getPlaceholderPluginKeys(newState.schema);
168
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState, pluginKeys);
138
169
  const { decorationSet } = oldPluginState;
139
170
  const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);
140
171
  // Find all decorations that are present in old and new set
141
- const decorationsThatDidNotChange = widgetSpecs.reduce((a, { pos, spec }) => {
172
+ const unchangedPositions = new Set();
173
+ const decorationsThatDidNotChange = [];
174
+ widgetSpecs.forEach(({ pos, spec }) => {
142
175
  const deco = oldMappedSet.find(pos, pos);
143
- if (deco.length && (0, lodash_1.isEqual)(deco[0].spec, spec))
144
- a.push(...deco);
145
- return a;
146
- }, []);
147
- // Those are decorations that are presenr only in new set
148
- const newAddedDecorations = widgetSpecs.filter(({ pos }) => !decorationsThatDidNotChange.map(({ from }) => from).includes(pos));
176
+ if (deco.length && (0, lodash_1.isEqual)(deco[0].spec, spec)) {
177
+ unchangedPositions.add(pos);
178
+ decorationsThatDidNotChange.push(...deco);
179
+ }
180
+ });
181
+ // Those are decorations that are present only in new set
182
+ const newAddedDecorations = widgetSpecs.filter(({ pos }) => !unchangedPositions.has(pos));
149
183
  // That is a set with decorations that are present in old set and absent in new set
150
184
  const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);
151
185
  let newSet = oldMappedSet;
@@ -155,6 +189,6 @@ function applyState(tr, oldPluginState, _oldState, newState) {
155
189
  // Add new decorations
156
190
  if (newAddedDecorations.length)
157
191
  newSet = newSet.add(tr.doc, newAddedDecorations.map((widget) => prosemirror_view_1.Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
158
- return { decorationSet: newSet, hasFocus };
192
+ return { decorationSet: newSet, hasFocus, pluginKeys };
159
193
  }
160
194
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findChildren, findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from '../../../classname';\nimport type {ExtensionAuto} from '../../../core';\nimport {isEqual} from '../../../lodash';\nimport {isNodeEmpty} from '../../../utils/nodes';\nimport {type PlaceholderOptions, getPlaceholderContent} from '../../../utils/placeholder';\nimport {isTextSelection} from '../../../utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n const childrenWithPlaceholderVisible = findChildren(node, (n: Node) =>\n Boolean(n.type.spec.placeholder?.alwaysVisible),\n );\n\n return (\n isNodeEmpty(node) &&\n // If there are child nodes with constant placeholder - give them the priority\n !childrenWithPlaceholderVisible.length\n );\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {decorationSet: DecorationSet; hasFocus: boolean};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n getPlaceholderPluginKeys(state.schema).forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Fraw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n _oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const decorationsThatDidNotChange = widgetSpecs.reduce((a: Decoration[], {pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) a.push(...deco);\n return a;\n }, []);\n\n // Those are decorations that are presenr only in new set\n const newAddedDecorations = widgetSpecs.filter(\n ({pos}) => !decorationsThatDidNotChange.map(({from}) => from).includes(pos),\n );\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from 'src/classname';\nimport type {ExtensionAuto} from 'src/core';\nimport {isEqual} from 'src/lodash';\nimport {type PlaceholderOptions, getPlaceholderContent} from 'src/utils/placeholder';\nimport {isTextSelection} from 'src/utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n // Combine all checks in a single descendants pass\n let hasAlwaysVisiblePlaceholder = false;\n let hasNonEmptyContent = false;\n\n node.descendants((n: Node) => {\n // Check for alwaysVisible placeholder\n if (n.type.spec.placeholder?.alwaysVisible) {\n hasAlwaysVisiblePlaceholder = true;\n return false; // Stop traversal early\n }\n\n // Check if node has non-empty content (same logic as isNodeEmpty)\n if (n.isAtom) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n if (n.isText && n.textContent) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n // Non-empty paragraph or other block with content\n if (n.content.size > 0 && n.type.name !== 'paragraph') {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n return true;\n });\n\n return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {\n decorationSet: DecorationSet;\n hasFocus: boolean;\n pluginKeys: PluginKey<DecorationSet>[];\n};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState, pluginKeys: PluginKey<DecorationSet>[]) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n pluginKeys.forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Draw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const pluginKeys = getPlaceholderPluginKeys(state.schema);\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state, pluginKeys);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus, pluginKeys};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n // Early return if document hasn't changed and selection hasn't changed\n // This avoids unnecessary recalculation of decorations\n if (!tr.docChanged && !tr.selectionSet) {\n return oldPluginState;\n }\n\n // Reuse cached plugin keys if schema hasn't changed\n const pluginKeys =\n oldState.schema === newState.schema\n ? oldPluginState.pluginKeys\n : getPlaceholderPluginKeys(newState.schema);\n\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState, pluginKeys);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const unchangedPositions = new Set<number>();\n const decorationsThatDidNotChange: Decoration[] = [];\n\n widgetSpecs.forEach(({pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) {\n unchangedPositions.add(pos);\n decorationsThatDidNotChange.push(...deco);\n }\n });\n\n // Those are decorations that are present only in new set\n const newAddedDecorations = widgetSpecs.filter(({pos}) => !unchangedPositions.has(pos));\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus, pluginKeys};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  /** During build process, the current version will be injected here */
5
- exports.VERSION = typeof '15.34.4' !== 'undefined' ? '15.34.4' : 'unknown';
5
+ exports.VERSION = typeof '15.34.5' !== 'undefined' ? '15.34.5' : 'unknown';
6
6
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"../../src","sources":["version.ts"],"names":[],"mappings":";;;AAAA,sEAAsE;AACzD,QAAA,OAAO,GAAG,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC","sourcesContent":["/** During build process, the current version will be injected here */\nexport const VERSION = typeof '15.34.4' !== 'undefined' ? '15.34.4' : 'unknown';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"../../src","sources":["version.ts"],"names":[],"mappings":";;;AAAA,sEAAsE;AACzD,QAAA,OAAO,GAAG,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC","sourcesContent":["/** During build process, the current version will be injected here */\nexport const VERSION = typeof '15.34.5' !== 'undefined' ? '15.34.5' : 'unknown';\n"]}
@@ -1,10 +1,9 @@
1
1
  import { Plugin, PluginKey } from 'prosemirror-state';
2
2
  // @ts-ignore // TODO: fix cjs build
3
- import { findChildren, findParentNodeClosestToPos } from 'prosemirror-utils';
3
+ import { findParentNodeClosestToPos } from 'prosemirror-utils';
4
4
  import { Decoration, DecorationSet } from 'prosemirror-view';
5
5
  import { cn } from "../../../classname.js";
6
6
  import { isEqual } from "../../../lodash.js";
7
- import { isNodeEmpty } from "../../../utils/nodes.js";
8
7
  import { getPlaceholderContent } from "../../../utils/placeholder.js";
9
8
  import { isTextSelection } from "../../../utils/selection.js";
10
9
  import "./index.css";
@@ -36,10 +35,32 @@ export const createPlaceholder = (node, parent, focus) => {
36
35
  return placeholder;
37
36
  };
38
37
  const placeholderNeeded = (node) => {
39
- const childrenWithPlaceholderVisible = findChildren(node, (n) => Boolean(n.type.spec.placeholder?.alwaysVisible));
40
- return (isNodeEmpty(node) &&
41
- // If there are child nodes with constant placeholder - give them the priority
42
- !childrenWithPlaceholderVisible.length);
38
+ // Combine all checks in a single descendants pass
39
+ let hasAlwaysVisiblePlaceholder = false;
40
+ let hasNonEmptyContent = false;
41
+ node.descendants((n) => {
42
+ // Check for alwaysVisible placeholder
43
+ if (n.type.spec.placeholder?.alwaysVisible) {
44
+ hasAlwaysVisiblePlaceholder = true;
45
+ return false; // Stop traversal early
46
+ }
47
+ // Check if node has non-empty content (same logic as isNodeEmpty)
48
+ if (n.isAtom) {
49
+ hasNonEmptyContent = true;
50
+ return false; // Stop traversal early
51
+ }
52
+ if (n.isText && n.textContent) {
53
+ hasNonEmptyContent = true;
54
+ return false; // Stop traversal early
55
+ }
56
+ // Non-empty paragraph or other block with content
57
+ if (n.content.size > 0 && n.type.name !== 'paragraph') {
58
+ hasNonEmptyContent = true;
59
+ return false; // Stop traversal early
60
+ }
61
+ return true;
62
+ });
63
+ return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;
43
64
  };
44
65
  const addDecoration = (widgetsMap, node, pos, parent, cursorPos, globalState) => {
45
66
  const placeholderSpec = node.type.spec.placeholder;
@@ -87,19 +108,19 @@ export const Placeholder = (builder, opts) => {
87
108
  },
88
109
  }), builder.Priority.VeryHigh);
89
110
  };
90
- function getPlaceholderWidgetSpecs(state) {
111
+ function getPlaceholderWidgetSpecs(state, pluginKeys) {
91
112
  const globalState = { hasFocus: false };
92
113
  const widgetsMap = {};
93
114
  const { selection } = state;
94
115
  const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;
95
- getPlaceholderPluginKeys(state.schema).forEach((f) => {
116
+ pluginKeys.forEach((f) => {
96
117
  // We use find because it can be used to iterate over the DecorationSet.
97
118
  f.getState(state)?.find(undefined, undefined, (spec) => {
98
119
  widgetsMap[spec.pos] = f;
99
120
  return false;
100
121
  });
101
122
  });
102
- // Fraw placeholder for all nodes where placeholder is alwaysVisible
123
+ // Draw placeholder for all nodes where placeholder is alwaysVisible
103
124
  const decorate = (node, pos, parent) => {
104
125
  const placeholderSpec = node.type.spec.placeholder;
105
126
  if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {
@@ -124,23 +145,36 @@ function getPlaceholderWidgetSpecs(state) {
124
145
  return { widgetSpecs, hasFocus: globalState.hasFocus };
125
146
  }
126
147
  function initState(state) {
127
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state);
148
+ const pluginKeys = getPlaceholderPluginKeys(state.schema);
149
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state, pluginKeys);
128
150
  const decorationSet = DecorationSet.create(state.doc, widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
129
- return { decorationSet, hasFocus };
151
+ return { decorationSet, hasFocus, pluginKeys };
130
152
  }
131
- function applyState(tr, oldPluginState, _oldState, newState) {
132
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState);
153
+ function applyState(tr, oldPluginState, oldState, newState) {
154
+ // Early return if document hasn't changed and selection hasn't changed
155
+ // This avoids unnecessary recalculation of decorations
156
+ if (!tr.docChanged && !tr.selectionSet) {
157
+ return oldPluginState;
158
+ }
159
+ // Reuse cached plugin keys if schema hasn't changed
160
+ const pluginKeys = oldState.schema === newState.schema
161
+ ? oldPluginState.pluginKeys
162
+ : getPlaceholderPluginKeys(newState.schema);
163
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState, pluginKeys);
133
164
  const { decorationSet } = oldPluginState;
134
165
  const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);
135
166
  // Find all decorations that are present in old and new set
136
- const decorationsThatDidNotChange = widgetSpecs.reduce((a, { pos, spec }) => {
167
+ const unchangedPositions = new Set();
168
+ const decorationsThatDidNotChange = [];
169
+ widgetSpecs.forEach(({ pos, spec }) => {
137
170
  const deco = oldMappedSet.find(pos, pos);
138
- if (deco.length && isEqual(deco[0].spec, spec))
139
- a.push(...deco);
140
- return a;
141
- }, []);
142
- // Those are decorations that are presenr only in new set
143
- const newAddedDecorations = widgetSpecs.filter(({ pos }) => !decorationsThatDidNotChange.map(({ from }) => from).includes(pos));
171
+ if (deco.length && isEqual(deco[0].spec, spec)) {
172
+ unchangedPositions.add(pos);
173
+ decorationsThatDidNotChange.push(...deco);
174
+ }
175
+ });
176
+ // Those are decorations that are present only in new set
177
+ const newAddedDecorations = widgetSpecs.filter(({ pos }) => !unchangedPositions.has(pos));
144
178
  // That is a set with decorations that are present in old set and absent in new set
145
179
  const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);
146
180
  let newSet = oldMappedSet;
@@ -150,6 +184,6 @@ function applyState(tr, oldPluginState, _oldState, newState) {
150
184
  // Add new decorations
151
185
  if (newAddedDecorations.length)
152
186
  newSet = newSet.add(tr.doc, newAddedDecorations.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
153
- return { decorationSet: newSet, hasFocus };
187
+ return { decorationSet: newSet, hasFocus, pluginKeys };
154
188
  }
155
189
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findChildren, findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from '../../../classname';\nimport type {ExtensionAuto} from '../../../core';\nimport {isEqual} from '../../../lodash';\nimport {isNodeEmpty} from '../../../utils/nodes';\nimport {type PlaceholderOptions, getPlaceholderContent} from '../../../utils/placeholder';\nimport {isTextSelection} from '../../../utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n const childrenWithPlaceholderVisible = findChildren(node, (n: Node) =>\n Boolean(n.type.spec.placeholder?.alwaysVisible),\n );\n\n return (\n isNodeEmpty(node) &&\n // If there are child nodes with constant placeholder - give them the priority\n !childrenWithPlaceholderVisible.length\n );\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {decorationSet: DecorationSet; hasFocus: boolean};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n getPlaceholderPluginKeys(state.schema).forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Fraw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n _oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const decorationsThatDidNotChange = widgetSpecs.reduce((a: Decoration[], {pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) a.push(...deco);\n return a;\n }, []);\n\n // Those are decorations that are presenr only in new set\n const newAddedDecorations = widgetSpecs.filter(\n ({pos}) => !decorationsThatDidNotChange.map(({from}) => from).includes(pos),\n );\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from 'src/classname';\nimport type {ExtensionAuto} from 'src/core';\nimport {isEqual} from 'src/lodash';\nimport {type PlaceholderOptions, getPlaceholderContent} from 'src/utils/placeholder';\nimport {isTextSelection} from 'src/utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n // Combine all checks in a single descendants pass\n let hasAlwaysVisiblePlaceholder = false;\n let hasNonEmptyContent = false;\n\n node.descendants((n: Node) => {\n // Check for alwaysVisible placeholder\n if (n.type.spec.placeholder?.alwaysVisible) {\n hasAlwaysVisiblePlaceholder = true;\n return false; // Stop traversal early\n }\n\n // Check if node has non-empty content (same logic as isNodeEmpty)\n if (n.isAtom) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n if (n.isText && n.textContent) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n // Non-empty paragraph or other block with content\n if (n.content.size > 0 && n.type.name !== 'paragraph') {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n return true;\n });\n\n return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {\n decorationSet: DecorationSet;\n hasFocus: boolean;\n pluginKeys: PluginKey<DecorationSet>[];\n};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState, pluginKeys: PluginKey<DecorationSet>[]) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n pluginKeys.forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Draw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const pluginKeys = getPlaceholderPluginKeys(state.schema);\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state, pluginKeys);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus, pluginKeys};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n // Early return if document hasn't changed and selection hasn't changed\n // This avoids unnecessary recalculation of decorations\n if (!tr.docChanged && !tr.selectionSet) {\n return oldPluginState;\n }\n\n // Reuse cached plugin keys if schema hasn't changed\n const pluginKeys =\n oldState.schema === newState.schema\n ? oldPluginState.pluginKeys\n : getPlaceholderPluginKeys(newState.schema);\n\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState, pluginKeys);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const unchangedPositions = new Set<number>();\n const decorationsThatDidNotChange: Decoration[] = [];\n\n widgetSpecs.forEach(({pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) {\n unchangedPositions.add(pos);\n decorationsThatDidNotChange.push(...deco);\n }\n });\n\n // Those are decorations that are present only in new set\n const newAddedDecorations = widgetSpecs.filter(({pos}) => !unchangedPositions.has(pos));\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus, pluginKeys};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  /** During build process, the current version will be injected here */
2
- export const VERSION = typeof '15.34.4' !== 'undefined' ? '15.34.4' : 'unknown';
2
+ export const VERSION = typeof '15.34.5' !== 'undefined' ? '15.34.5' : 'unknown';
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"../../src","sources":["version.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC","sourcesContent":["/** During build process, the current version will be injected here */\nexport const VERSION = typeof '15.34.4' !== 'undefined' ? '15.34.4' : 'unknown';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"../../src","sources":["version.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC","sourcesContent":["/** During build process, the current version will be injected here */\nexport const VERSION = typeof '15.34.5' !== 'undefined' ? '15.34.5' : 'unknown';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/markdown-editor",
3
- "version": "15.34.4",
3
+ "version": "15.34.5",
4
4
  "description": "Markdown wysiwyg and markup editor",
5
5
  "license": "MIT",
6
6
  "repository": {