@fiduswriter/editor 0.1.40 → 0.1.43

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.
@@ -1 +1 @@
1
- {"version":3,"file":"jump_hidden_nodes.d.ts","sourceRoot":"","sources":["../../src/state_plugins/jump_hidden_nodes.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,MAAM,EAA2B,MAAM,mBAAmB,CAAA;AAmBlE,eAAO,MAAM,qBAAqB,GAAI,UAAU;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,gBAwE3D,CAAA"}
1
+ {"version":3,"file":"jump_hidden_nodes.d.ts","sourceRoot":"","sources":["../../src/state_plugins/jump_hidden_nodes.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,MAAM,EAA2B,MAAM,mBAAmB,CAAA;AAoBlE,eAAO,MAAM,qBAAqB,GAAI,UAAU;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,gBA6I3D,CAAA"}
@@ -15,6 +15,61 @@ const posHidden = ($pos) => {
15
15
  const key = new PluginKey("jump-hidden-nodes");
16
16
  export const jumpHiddenNodesPlugin = (_options) => new Plugin({
17
17
  key,
18
+ props: {
19
+ handleKeyDown: (view, event) => {
20
+ if (event.key !== "ArrowLeft") {
21
+ return false;
22
+ }
23
+ const { selection, doc } = view.state;
24
+ if (!selection.empty) {
25
+ return false;
26
+ }
27
+ const $pos = selection.$from;
28
+ if ($pos.depth < 2) {
29
+ return false;
30
+ }
31
+ // Intercept ArrowLeft when at the beginning of a textblock
32
+ // OR inside an empty textblock whose preceding doc-level
33
+ // sibling is hidden. This handles the case where clicking
34
+ // an empty body paragraph and pressing LEFT should land in
35
+ // the abstract (skipping hidden parts like keywords).
36
+ const atStart = $pos.parentOffset === 0;
37
+ const isEmptyTextblock = $pos.parent.type.isTextblock &&
38
+ $pos.parent.content.size === 0;
39
+ if (!atStart && !isEmptyTextblock) {
40
+ return false;
41
+ }
42
+ // Find previous sibling of the parent doc-level node
43
+ const { index } = doc.content.findIndex($pos.pos);
44
+ if (index < 1) {
45
+ return false;
46
+ }
47
+ const prevSibling = doc.child(index - 1);
48
+ if (!prevSibling.attrs.hidden) {
49
+ return false;
50
+ }
51
+ // Walk backward to the previous non-hidden position
52
+ const dir = -1;
53
+ let newPos = selection.from, hidden = true, validTextSelection = false, validGapCursor = false;
54
+ let new$pos = selection.$from;
55
+ while (hidden ||
56
+ (!validGapCursor && !validTextSelection)) {
57
+ newPos += dir;
58
+ if (newPos === 0) {
59
+ return false;
60
+ }
61
+ new$pos = doc.resolve(newPos);
62
+ validTextSelection = new$pos.parent.inlineContent;
63
+ validGapCursor = GapCursor.valid(new$pos);
64
+ hidden = posHidden(new$pos);
65
+ }
66
+ const trSelection = validTextSelection
67
+ ? new TextSelection(new$pos)
68
+ : new GapCursor(new$pos);
69
+ view.dispatch(view.state.tr.setSelection(trSelection));
70
+ return true;
71
+ }
72
+ },
18
73
  appendTransaction: (trs, oldState, state) => {
19
74
  if (!state.selection.empty) {
20
75
  // Only applies to collapsed selection
@@ -41,28 +96,30 @@ export const jumpHiddenNodesPlugin = (_options) => new Plugin({
41
96
  : new GapCursor($pos);
42
97
  return state.tr.setSelection(selection);
43
98
  }
44
- // Also handle the case where the cursor is at the start of a visible
45
- // node whose previous sibling is hidden (e.g. body after abstract
46
- // was unhidden). Move the cursor to the previous non-hidden node.
99
+ // Handle boundary crossing: cursor is at the start of a visible
100
+ // doc-level part whose preceding sibling is hidden. Use
101
+ // findIndex to locate the previous doc child and check if
102
+ // it is hidden.
47
103
  if (selectionSet &&
48
104
  !posHidden(state.selection.$from) &&
49
- state.selection.$from.pos > 0) {
50
- // Check if the position immediately before is in a hidden node
51
- const beforePos = state.selection.$from.pos - 1;
52
- if (beforePos > 0) {
53
- const beforeResolved = state.doc.resolve(beforePos);
54
- if (posHidden(beforeResolved)) {
55
- // Move backward to find a non-hidden position
105
+ state.selection.$from.depth >= 2) {
106
+ const $pos = state.selection.$from;
107
+ const { index } = state.doc.content.findIndex($pos.pos);
108
+ if (index >= 1) {
109
+ const prevSibling = state.doc.child(index - 1);
110
+ if (prevSibling.attrs.hidden) {
56
111
  const dir = -1;
57
112
  let newPos = state.selection.from, hidden = true, validTextSelection = false, validGapCursor = false;
58
113
  let new$pos = state.selection.$from;
59
- while (hidden || (!validGapCursor && !validTextSelection)) {
114
+ while (hidden ||
115
+ (!validGapCursor && !validTextSelection)) {
60
116
  newPos += dir;
61
117
  if (newPos === 0) {
62
118
  return;
63
119
  }
64
120
  new$pos = state.doc.resolve(newPos);
65
- validTextSelection = new$pos.parent.inlineContent;
121
+ validTextSelection =
122
+ new$pos.parent.inlineContent;
66
123
  validGapCursor = GapCursor.valid(new$pos);
67
124
  hidden = posHidden(new$pos);
68
125
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jump_hidden_nodes.js","sourceRoot":"","sources":["../../src/state_plugins/jump_hidden_nodes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jump_hidden_nodes.js","sourceRoot":"","sources":["../../src/state_plugins/jump_hidden_nodes.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from "prosemirror-state";
2
2
  import { DecorationSet } from "prosemirror-view";
3
- export declare const placeholdersPlugin: (_options: {
3
+ export declare const placeholdersPlugin: (options: {
4
4
  editor: unknown;
5
- }) => Plugin<DecorationSet | undefined>;
5
+ }) => Plugin<false | DecorationSet>;
6
6
  //# sourceMappingURL=placeholders.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/state_plugins/placeholders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAY,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAK1D,eAAO,MAAM,kBAAkB,GAAI,UAAU;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,sCAuE7D,CAAA"}
1
+ {"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/state_plugins/placeholders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAY,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAK1D,eAAO,MAAM,kBAAkB,GAAI,SAAS;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,kCAqH5D,CAAA"}
@@ -1,17 +1,17 @@
1
1
  import { Plugin, PluginKey } from "prosemirror-state";
2
2
  import { Decoration, DecorationSet } from "prosemirror-view";
3
3
  const key = new PluginKey("placeholders");
4
- export const placeholdersPlugin = (_options) => {
4
+ export const placeholdersPlugin = (options) => {
5
5
  function calculatePlaceHolderDecorations(state) {
6
6
  const anchor = state.selection.$anchor;
7
7
  const head = state.selection.$head;
8
8
  if (!anchor || !head) {
9
- return;
9
+ return false;
10
10
  }
11
11
  const anchorPart = anchor.node(1);
12
12
  const headPart = head.node(1);
13
13
  if (!anchorPart || !headPart) {
14
- return;
14
+ return false;
15
15
  }
16
16
  const currentPart = anchorPart === headPart ? anchorPart : false;
17
17
  const decorations = [];
@@ -22,20 +22,53 @@ export const placeholdersPlugin = (_options) => {
22
22
  state.schema.nodes["tags_part"],
23
23
  state.schema.nodes["contributors_part"]
24
24
  ].includes(partElement.type) &&
25
- partElement !== currentPart) {
25
+ options.editor.docInfo.access_rights === "write") {
26
+ // We don't need to render placeholders for these kinds
27
+ // of nodes in write mode as their nodeviews will take
28
+ // care of that.
26
29
  return;
27
30
  }
28
- const placeholder = document.createElement("span");
29
- placeholder.classList.add("placeholder");
30
- placeholder.setAttribute("data-placeholder", "true");
31
- const widget = Decoration.widget(offset + 1, () => placeholder, {
32
- side: -1,
33
- placeholder: true
31
+ const text = partElement.type === state.schema.nodes["title"]
32
+ ? `${gettext("Title")}...`
33
+ : `${partElement.attrs.title}...`;
34
+ const placeHolder = document.createElement("span");
35
+ placeHolder.classList.add("placeholder");
36
+ placeHolder.setAttribute("data-placeholder", text);
37
+ if (currentPart === partElement) {
38
+ placeHolder.classList.add("selected");
39
+ }
40
+ let position = 1 + offset;
41
+ // position of decorator: 2 to get inside (doc (1))
42
+ if (!partElement.isTextblock) {
43
+ // In block nodes that are not text blocks (body + abstract)
44
+ // place inside the first child node (a paragraph).
45
+ position += 1;
46
+ }
47
+ decorations.push(Decoration.widget(position, placeHolder, {
48
+ side: 1
49
+ }));
50
+ }
51
+ else if (["richtext_part", "table_part"].includes(partElement.type.name)) {
52
+ partElement.descendants((node, pos) => {
53
+ if (["figure", "table"].includes(node.type.name) &&
54
+ !node.attrs.caption) {
55
+ return false;
56
+ }
57
+ if (["figure_caption", "table_caption"].includes(node.type.name) &&
58
+ node.childCount === 0 &&
59
+ state.selection.$anchor.parent !== node) {
60
+ decorations.push(Decoration.node(1 + offset + pos, 1 + offset + pos + node.nodeSize, {
61
+ class: "empty",
62
+ "data-placeholder": `${gettext("Caption")}...`
63
+ }));
64
+ }
65
+ return false;
34
66
  });
35
- decorations.push(widget);
36
67
  }
37
68
  });
38
- return DecorationSet.create(state.doc, decorations);
69
+ return decorations.length
70
+ ? DecorationSet.create(state.doc, decorations)
71
+ : false;
39
72
  }
40
73
  return new Plugin({
41
74
  key,
@@ -43,18 +76,17 @@ export const placeholdersPlugin = (_options) => {
43
76
  init(_config, state) {
44
77
  return calculatePlaceHolderDecorations(state);
45
78
  },
46
- apply(_tr, value, oldState, newState) {
47
- if (_tr.docChanged ||
48
- oldState.selection.$anchor !== newState.selection.$anchor ||
49
- oldState.selection.$head !== newState.selection.$head) {
50
- return calculatePlaceHolderDecorations(newState);
79
+ apply(tr, _prev, oldState, state) {
80
+ if (tr.docChanged || tr.selectionSet) {
81
+ return calculatePlaceHolderDecorations(state);
51
82
  }
52
- return value;
83
+ return key.getState(oldState);
53
84
  }
54
85
  },
55
86
  props: {
56
87
  decorations(state) {
57
- return key.getState(state);
88
+ const decorationSet = key.getState(state);
89
+ return decorationSet || null;
58
90
  }
59
91
  }
60
92
  });
@@ -1 +1 @@
1
- {"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../../src/state_plugins/placeholders.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../../src/state_plugins/placeholders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAE,SAAS,EAAC,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAC,UAAU,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAG1D,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,cAAc,CAAC,CAAA;AAEzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAA0B,EAAE,EAAE;IAC7D,SAAS,+BAA+B,CAAC,KAAkB;QACvD,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAA;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAA;QAClC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,OAAO,KAAK,CAAA;QAChB,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC7B,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAA;QAChB,CAAC;QACD,MAAM,WAAW,GAAG,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAA;QAEhE,MAAM,WAAW,GAAkF,EAAE,CAAA;QAErG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE;YACtC,IACI,CAAC,WAAW,CAAC,WAAW,IAAI,WAAW,CAAC,UAAU,KAAK,CAAC,CAAC;gBACzD,CAAC,CAAC,WAAW,CAAC,WAAW,IAAI,WAAW,CAAC,QAAQ,KAAK,CAAC,CAAC,EAC1D,CAAC;gBACC,IACI;oBACI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;oBAC/B,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC;iBAC1C,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC;oBAC3B,OAAO,CAAC,MAAc,CAAC,OAAO,CAAC,aAAa,KAAK,OAAO,EAC3D,CAAC;oBACC,uDAAuD;oBACvD,sDAAsD;oBACtD,gBAAgB;oBAChB,OAAM;gBACV,CAAC;gBAED,MAAM,IAAI,GACN,WAAW,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;oBAC5C,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK;oBAC1B,CAAC,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,KAAK,CAAA;gBACzC,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;gBAClD,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;gBACxC,WAAW,CAAC,YAAY,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAA;gBAClD,IAAI,WAAW,KAAK,WAAW,EAAE,CAAC;oBAC9B,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;gBACzC,CAAC;gBACD,IAAI,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAA;gBACzB,mDAAmD;gBACnD,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;oBAC3B,4DAA4D;oBAC5D,mDAAmD;oBACnD,QAAQ,IAAI,CAAC,CAAA;gBACjB,CAAC;gBACD,WAAW,CAAC,IAAI,CACZ,UAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,EAAE;oBACrC,IAAI,EAAE,CAAC;iBACV,CAAC,CACL,CAAA;YACL,CAAC;iBAAM,IACH,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EACjE,CAAC;gBACC,WAAW,CAAC,WAAW,CAAC,CAAC,IAAS,EAAE,GAAW,EAAE,EAAE;oBAC/C,IACI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;wBAC5C,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EACrB,CAAC;wBACC,OAAO,KAAK,CAAA;oBAChB,CAAC;oBACD,IACI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC,QAAQ,CACxC,IAAI,CAAC,IAAI,CAAC,IAAI,CACjB;wBACD,IAAI,CAAC,UAAU,KAAK,CAAC;wBACrB,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,KAAK,IAAI,EACzC,CAAC;wBACC,WAAW,CAAC,IAAI,CACZ,UAAU,CAAC,IAAI,CACX,CAAC,GAAG,MAAM,GAAG,GAAG,EAChB,CAAC,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,EAChC;4BACI,KAAK,EAAE,OAAO;4BACd,kBAAkB,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK;yBACjD,CACG,CACX,CAAA;oBACL,CAAC;oBACD,OAAO,KAAK,CAAA;gBAChB,CAAC,CAAC,CAAA;YACN,CAAC;QACL,CAAC,CAAC,CAAA;QAEF,OAAO,WAAW,CAAC,MAAM;YACrB,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC;YAC9C,CAAC,CAAC,KAAK,CAAA;IACf,CAAC;IAED,OAAO,IAAI,MAAM,CAAC;QACd,GAAG;QACH,KAAK,EAAE;YACH,IAAI,CAAC,OAAO,EAAE,KAAK;gBACf,OAAO,+BAA+B,CAAC,KAAK,CAAC,CAAA;YACjD,CAAC;YACD,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK;gBAC5B,IAAI,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC;oBACnC,OAAO,+BAA+B,CAAC,KAAK,CAAC,CAAA;gBACjD,CAAC;gBACD,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;YACjC,CAAC;SACJ;QACD,KAAK,EAAE;YACH,WAAW,CAAC,KAAK;gBACb,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAGzB,CAAA;gBACf,OAAO,aAAa,IAAI,IAAI,CAAA;YAChC,CAAC;SACJ;KACJ,CAAC,CAAA;AACN,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiduswriter/editor",
3
- "version": "0.1.40",
3
+ "version": "0.1.43",
4
4
  "description": "Fidus Writer browser editor",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,7 @@
1
1
  import {GapCursor} from "prosemirror-gapcursor"
2
2
  import {Plugin, PluginKey, TextSelection} from "prosemirror-state"
3
3
  import type {ResolvedPos} from "prosemirror-model"
4
+ import type {EditorView} from "prosemirror-view"
4
5
 
5
6
  const posHidden = ($pos: ResolvedPos) => {
6
7
  let hidden = false
@@ -21,6 +22,69 @@ const key = new PluginKey("jump-hidden-nodes")
21
22
  export const jumpHiddenNodesPlugin = (_options: {editor: unknown}) =>
22
23
  new Plugin({
23
24
  key,
25
+ props: {
26
+ handleKeyDown: (view: EditorView, event: KeyboardEvent) => {
27
+ if (event.key !== "ArrowLeft") {
28
+ return false
29
+ }
30
+ const {selection, doc} = view.state
31
+ if (!selection.empty) {
32
+ return false
33
+ }
34
+ const $pos = selection.$from
35
+ if ($pos.depth < 2) {
36
+ return false
37
+ }
38
+ // Intercept ArrowLeft when at the beginning of a textblock
39
+ // OR inside an empty textblock whose preceding doc-level
40
+ // sibling is hidden. This handles the case where clicking
41
+ // an empty body paragraph and pressing LEFT should land in
42
+ // the abstract (skipping hidden parts like keywords).
43
+ const atStart = $pos.parentOffset === 0
44
+ const isEmptyTextblock =
45
+ $pos.parent.type.isTextblock &&
46
+ $pos.parent.content.size === 0
47
+ if (!atStart && !isEmptyTextblock) {
48
+ return false
49
+ }
50
+ // Find previous sibling of the parent doc-level node
51
+ const {index} = (doc.content as any).findIndex($pos.pos)
52
+ if (index < 1) {
53
+ return false
54
+ }
55
+ const prevSibling = doc.child(index - 1)
56
+ if (!prevSibling.attrs.hidden) {
57
+ return false
58
+ }
59
+ // Walk backward to the previous non-hidden position
60
+ const dir = -1
61
+ let newPos = selection.from,
62
+ hidden = true,
63
+ validTextSelection = false,
64
+ validGapCursor = false
65
+ let new$pos: ResolvedPos = selection.$from
66
+ while (
67
+ hidden ||
68
+ (!validGapCursor && !validTextSelection)
69
+ ) {
70
+ newPos += dir
71
+ if (newPos === 0) {
72
+ return false
73
+ }
74
+ new$pos = doc.resolve(newPos)
75
+ validTextSelection = new$pos.parent.inlineContent
76
+ validGapCursor = (GapCursor as any).valid(new$pos)
77
+ hidden = posHidden(new$pos)
78
+ }
79
+ const trSelection = validTextSelection
80
+ ? new TextSelection(new$pos)
81
+ : new GapCursor(new$pos)
82
+ view.dispatch(
83
+ view.state.tr.setSelection(trSelection)
84
+ )
85
+ return true
86
+ }
87
+ },
24
88
  appendTransaction: (trs, oldState, state) => {
25
89
  if (!state.selection.empty) {
26
90
  // Only applies to collapsed selection
@@ -51,34 +115,40 @@ export const jumpHiddenNodesPlugin = (_options: {editor: unknown}) =>
51
115
  : new GapCursor($pos)
52
116
  return state.tr.setSelection(selection)
53
117
  }
54
- // Also handle the case where the cursor is at the start of a visible
55
- // node whose previous sibling is hidden (e.g. body after abstract
56
- // was unhidden). Move the cursor to the previous non-hidden node.
118
+ // Handle boundary crossing: cursor is at the start of a visible
119
+ // doc-level part whose preceding sibling is hidden. Use
120
+ // findIndex to locate the previous doc child and check if
121
+ // it is hidden.
57
122
  if (
58
123
  selectionSet &&
59
124
  !posHidden(state.selection.$from) &&
60
- state.selection.$from.pos > 0
125
+ state.selection.$from.depth >= 2
61
126
  ) {
62
- // Check if the position immediately before is in a hidden node
63
- const beforePos = state.selection.$from.pos - 1
64
- if (beforePos > 0) {
65
- const beforeResolved = state.doc.resolve(beforePos)
66
- if (posHidden(beforeResolved)) {
67
- // Move backward to find a non-hidden position
127
+ const $pos = state.selection.$from
128
+ const {index} = (state.doc.content as any).findIndex($pos.pos)
129
+ if (index >= 1) {
130
+ const prevSibling = state.doc.child(index - 1)
131
+ if (prevSibling.attrs.hidden) {
68
132
  const dir = -1
69
133
  let newPos = state.selection.from,
70
134
  hidden = true,
71
135
  validTextSelection = false,
72
136
  validGapCursor = false
73
137
  let new$pos: ResolvedPos = state.selection.$from
74
- while (hidden || (!validGapCursor && !validTextSelection)) {
138
+ while (
139
+ hidden ||
140
+ (!validGapCursor && !validTextSelection)
141
+ ) {
75
142
  newPos += dir
76
143
  if (newPos === 0) {
77
144
  return
78
145
  }
79
146
  new$pos = state.doc.resolve(newPos)
80
- validTextSelection = new$pos.parent.inlineContent
81
- validGapCursor = (GapCursor as any).valid(new$pos)
147
+ validTextSelection =
148
+ new$pos.parent.inlineContent
149
+ validGapCursor = (GapCursor as any).valid(
150
+ new$pos
151
+ )
82
152
  hidden = posHidden(new$pos)
83
153
  }
84
154
  const selection = validTextSelection
@@ -4,21 +4,21 @@ import type {EditorState} from "prosemirror-state"
4
4
 
5
5
  const key = new PluginKey("placeholders")
6
6
 
7
- export const placeholdersPlugin = (_options: {editor: unknown}) => {
8
- function calculatePlaceHolderDecorations(state: EditorState) {
7
+ export const placeholdersPlugin = (options: {editor: unknown}) => {
8
+ function calculatePlaceHolderDecorations(state: EditorState): DecorationSet | false {
9
9
  const anchor = state.selection.$anchor
10
10
  const head = state.selection.$head
11
11
  if (!anchor || !head) {
12
- return
12
+ return false
13
13
  }
14
14
  const anchorPart = anchor.node(1)
15
15
  const headPart = head.node(1)
16
16
  if (!anchorPart || !headPart) {
17
- return
17
+ return false
18
18
  }
19
19
  const currentPart = anchorPart === headPart ? anchorPart : false
20
20
 
21
- const decorations: Array<ReturnType<typeof Decoration.widget>> = []
21
+ const decorations: (ReturnType<typeof Decoration.widget> | ReturnType<typeof Decoration.node>)[] = []
22
22
 
23
23
  state.doc.forEach((partElement, offset) => {
24
24
  if (
@@ -30,26 +30,72 @@ export const placeholdersPlugin = (_options: {editor: unknown}) => {
30
30
  state.schema.nodes["tags_part"],
31
31
  state.schema.nodes["contributors_part"]
32
32
  ].includes(partElement.type) &&
33
- partElement !== currentPart
33
+ (options.editor as any).docInfo.access_rights === "write"
34
34
  ) {
35
+ // We don't need to render placeholders for these kinds
36
+ // of nodes in write mode as their nodeviews will take
37
+ // care of that.
35
38
  return
36
39
  }
37
40
 
38
- const placeholder = document.createElement("span")
39
- placeholder.classList.add("placeholder")
40
- placeholder.setAttribute("data-placeholder", "true")
41
- const widget = Decoration.widget(
42
- offset + 1,
43
- () => placeholder,
44
- {
45
- side: -1,
46
- placeholder: true
47
- }
41
+ const text =
42
+ partElement.type === state.schema.nodes["title"]
43
+ ? `${gettext("Title")}...`
44
+ : `${partElement.attrs.title}...`
45
+ const placeHolder = document.createElement("span")
46
+ placeHolder.classList.add("placeholder")
47
+ placeHolder.setAttribute("data-placeholder", text)
48
+ if (currentPart === partElement) {
49
+ placeHolder.classList.add("selected")
50
+ }
51
+ let position = 1 + offset
52
+ // position of decorator: 2 to get inside (doc (1))
53
+ if (!partElement.isTextblock) {
54
+ // In block nodes that are not text blocks (body + abstract)
55
+ // place inside the first child node (a paragraph).
56
+ position += 1
57
+ }
58
+ decorations.push(
59
+ Decoration.widget(position, placeHolder, {
60
+ side: 1
61
+ })
48
62
  )
49
- decorations.push(widget)
63
+ } else if (
64
+ ["richtext_part", "table_part"].includes(partElement.type.name)
65
+ ) {
66
+ partElement.descendants((node: any, pos: number) => {
67
+ if (
68
+ ["figure", "table"].includes(node.type.name) &&
69
+ !node.attrs.caption
70
+ ) {
71
+ return false
72
+ }
73
+ if (
74
+ ["figure_caption", "table_caption"].includes(
75
+ node.type.name
76
+ ) &&
77
+ node.childCount === 0 &&
78
+ state.selection.$anchor.parent !== node
79
+ ) {
80
+ decorations.push(
81
+ Decoration.node(
82
+ 1 + offset + pos,
83
+ 1 + offset + pos + node.nodeSize,
84
+ {
85
+ class: "empty",
86
+ "data-placeholder": `${gettext("Caption")}...`
87
+ }
88
+ ) as any
89
+ )
90
+ }
91
+ return false
92
+ })
50
93
  }
51
94
  })
52
- return DecorationSet.create(state.doc, decorations)
95
+
96
+ return decorations.length
97
+ ? DecorationSet.create(state.doc, decorations)
98
+ : false
53
99
  }
54
100
 
55
101
  return new Plugin({
@@ -58,20 +104,20 @@ export const placeholdersPlugin = (_options: {editor: unknown}) => {
58
104
  init(_config, state) {
59
105
  return calculatePlaceHolderDecorations(state)
60
106
  },
61
- apply(_tr, value, oldState, newState) {
62
- if (
63
- _tr.docChanged ||
64
- oldState.selection.$anchor !== newState.selection.$anchor ||
65
- oldState.selection.$head !== newState.selection.$head
66
- ) {
67
- return calculatePlaceHolderDecorations(newState)
107
+ apply(tr, _prev, oldState, state) {
108
+ if (tr.docChanged || tr.selectionSet) {
109
+ return calculatePlaceHolderDecorations(state)
68
110
  }
69
- return value
111
+ return key.getState(oldState)
70
112
  }
71
113
  },
72
114
  props: {
73
115
  decorations(state) {
74
- return key.getState(state)
116
+ const decorationSet = key.getState(state) as
117
+ | DecorationSet
118
+ | false
119
+ | undefined
120
+ return decorationSet || null
75
121
  }
76
122
  }
77
123
  })