@manuscripts/body-editor 3.9.11 → 3.9.13

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.
Files changed (105) hide show
  1. package/dist/cjs/commands.js +5 -2
  2. package/dist/cjs/components/keywords/AddKeywordInline.js +12 -1
  3. package/dist/cjs/components/views/FigureDropdown.js +65 -6
  4. package/dist/cjs/configs/ManuscriptsEditor.js +1 -1
  5. package/dist/cjs/index.js +1 -0
  6. package/dist/cjs/keys/misc.js +2 -1
  7. package/dist/cjs/keys/title.js +0 -38
  8. package/dist/cjs/lib/comments.js +1 -0
  9. package/dist/cjs/lib/context-menu.js +74 -8
  10. package/dist/cjs/lib/media.js +27 -3
  11. package/dist/cjs/lib/navigation-utils.js +132 -0
  12. package/dist/cjs/lib/popper.js +25 -1
  13. package/dist/cjs/lib/position-menu.js +3 -0
  14. package/dist/cjs/lib/utils.js +7 -2
  15. package/dist/cjs/plugins/accessibility_element.js +10 -2
  16. package/dist/cjs/plugins/add-subtitle.js +8 -2
  17. package/dist/cjs/plugins/alt-titles.js +6 -1
  18. package/dist/cjs/plugins/comments.js +27 -15
  19. package/dist/cjs/plugins/persistent-cursor.js +4 -6
  20. package/dist/cjs/plugins/section_category.js +42 -9
  21. package/dist/cjs/plugins/translations.js +49 -13
  22. package/dist/cjs/versions.js +1 -1
  23. package/dist/cjs/views/accessibility_element.js +30 -0
  24. package/dist/cjs/views/alt_title.js +29 -0
  25. package/dist/cjs/views/alt_titles_section.js +9 -1
  26. package/dist/cjs/views/attachment.js +1 -1
  27. package/dist/cjs/views/bibliography_element.js +39 -17
  28. package/dist/cjs/views/citation.js +1 -0
  29. package/dist/cjs/views/citation_editable.js +4 -2
  30. package/dist/cjs/views/contributors.js +23 -2
  31. package/dist/cjs/views/cross_reference.js +3 -0
  32. package/dist/cjs/views/editable_block.js +37 -3
  33. package/dist/cjs/views/embed.js +3 -3
  34. package/dist/cjs/views/figure_editable.js +1 -1
  35. package/dist/cjs/views/figure_element.js +3 -0
  36. package/dist/cjs/views/footnote.js +3 -0
  37. package/dist/cjs/views/hero_image.js +4 -1
  38. package/dist/cjs/views/image_element.js +15 -7
  39. package/dist/cjs/views/inline_footnote.js +3 -0
  40. package/dist/cjs/views/keyword.js +15 -0
  41. package/dist/cjs/views/keyword_group.js +38 -0
  42. package/dist/cjs/views/quote_image_editable.js +1 -0
  43. package/dist/cjs/views/supplements.js +4 -1
  44. package/dist/es/commands.js +5 -2
  45. package/dist/es/components/keywords/AddKeywordInline.js +12 -1
  46. package/dist/es/components/views/FigureDropdown.js +66 -7
  47. package/dist/es/configs/ManuscriptsEditor.js +1 -1
  48. package/dist/es/index.js +1 -0
  49. package/dist/es/keys/misc.js +2 -1
  50. package/dist/es/keys/title.js +1 -39
  51. package/dist/es/lib/comments.js +1 -0
  52. package/dist/es/lib/context-menu.js +74 -8
  53. package/dist/es/lib/media.js +27 -3
  54. package/dist/es/lib/navigation-utils.js +122 -0
  55. package/dist/es/lib/popper.js +25 -1
  56. package/dist/es/lib/position-menu.js +3 -0
  57. package/dist/es/lib/utils.js +7 -2
  58. package/dist/es/plugins/accessibility_element.js +10 -2
  59. package/dist/es/plugins/add-subtitle.js +8 -2
  60. package/dist/es/plugins/alt-titles.js +6 -1
  61. package/dist/es/plugins/comments.js +27 -15
  62. package/dist/es/plugins/persistent-cursor.js +4 -6
  63. package/dist/es/plugins/section_category.js +42 -9
  64. package/dist/es/plugins/translations.js +49 -13
  65. package/dist/es/versions.js +1 -1
  66. package/dist/es/views/accessibility_element.js +30 -0
  67. package/dist/es/views/alt_title.js +29 -0
  68. package/dist/es/views/alt_titles_section.js +9 -1
  69. package/dist/es/views/attachment.js +1 -1
  70. package/dist/es/views/bibliography_element.js +39 -17
  71. package/dist/es/views/citation.js +1 -0
  72. package/dist/es/views/citation_editable.js +4 -2
  73. package/dist/es/views/contributors.js +23 -2
  74. package/dist/es/views/cross_reference.js +3 -0
  75. package/dist/es/views/editable_block.js +37 -3
  76. package/dist/es/views/embed.js +3 -3
  77. package/dist/es/views/figure_editable.js +1 -1
  78. package/dist/es/views/figure_element.js +3 -0
  79. package/dist/es/views/footnote.js +3 -0
  80. package/dist/es/views/hero_image.js +4 -1
  81. package/dist/es/views/image_element.js +15 -7
  82. package/dist/es/views/inline_footnote.js +3 -0
  83. package/dist/es/views/keyword.js +15 -0
  84. package/dist/es/views/keyword_group.js +38 -0
  85. package/dist/es/views/quote_image_editable.js +1 -0
  86. package/dist/es/views/supplements.js +4 -1
  87. package/dist/types/configs/ManuscriptsEditor.d.ts +1 -1
  88. package/dist/types/index.d.ts +1 -0
  89. package/dist/types/lib/context-menu.d.ts +1 -0
  90. package/dist/types/lib/media.d.ts +1 -1
  91. package/dist/types/lib/navigation-utils.d.ts +45 -0
  92. package/dist/types/lib/popper.d.ts +3 -0
  93. package/dist/types/lib/utils.d.ts +1 -1
  94. package/dist/types/versions.d.ts +1 -1
  95. package/dist/types/views/accessibility_element.d.ts +2 -0
  96. package/dist/types/views/alt_title.d.ts +2 -0
  97. package/dist/types/views/bibliography_element.d.ts +3 -0
  98. package/dist/types/views/citation_editable.d.ts +1 -1
  99. package/dist/types/views/contributors.d.ts +3 -1
  100. package/dist/types/views/keyword.d.ts +1 -0
  101. package/dist/types/views/keyword_group.d.ts +4 -0
  102. package/package.json +4 -4
  103. package/styles/AdvancedEditor.css +116 -10
  104. package/styles/Editor.css +61 -6
  105. package/styles/popper.css +3 -1
@@ -4,10 +4,18 @@ import { Decoration, DecorationSet } from 'prosemirror-view';
4
4
  import { createToggleButton } from '../lib/utils';
5
5
  export const accessibilityElementKey = new PluginKey('accessibility-element');
6
6
  const nodeTypes = [schema.nodes.alt_text, schema.nodes.long_desc];
7
- const handleExpandButtonClick = (view, node) => {
7
+ const handleExpandButtonClick = (view, node, event) => {
8
8
  const tr = view.state.tr;
9
9
  toggleAccessibilitySection(tr, node);
10
10
  view.dispatch(tr);
11
+ const isKeyboardEvent = event.type === 'keydown';
12
+ if (isKeyboardEvent) {
13
+ const parentElement = view.dom.querySelector(`[id="${node.attrs.id}"]`);
14
+ if (parentElement) {
15
+ const firstInput = parentElement.querySelector('.accessibility_element_input');
16
+ firstInput?.focus();
17
+ }
18
+ }
11
19
  };
12
20
  const isSelectionWithin = (node, pos, selection) => {
13
21
  if (!selection || !nodeTypes.includes(node.type)) {
@@ -24,7 +32,7 @@ const buildExpandButtonDecorations = (doc) => {
24
32
  const container = document.createElement('div');
25
33
  container.className =
26
34
  'accessibility_element_expander_button_container';
27
- container.appendChild(createToggleButton(() => handleExpandButtonClick(view, node), 'additional info'));
35
+ container.appendChild(createToggleButton((e) => handleExpandButtonClick(view, node, e), 'additional info'));
28
36
  return container;
29
37
  }, {
30
38
  key: node.attrs.id,
@@ -18,15 +18,21 @@ import { Plugin, TextSelection } from 'prosemirror-state';
18
18
  import { Decoration, DecorationSet } from 'prosemirror-view';
19
19
  import { v4 as uuidv4 } from 'uuid';
20
20
  import { addAuthorIcon } from '../icons';
21
+ import { handleEnterKey } from '../lib/navigation-utils';
21
22
  import { findInsertionPosition } from '../lib/utils';
22
23
  const createAddSubtitleButton = (handler) => {
23
24
  const button = document.createElement('span');
24
25
  button.className = 'add-subtitle';
25
26
  button.innerHTML = `${addAuthorIcon} <span class="add-subtitle-text">Add subtitle</span>`;
26
- button.addEventListener('mousedown', (e) => {
27
- e.preventDefault();
27
+ button.tabIndex = 0;
28
+ const activate = (event) => {
29
+ event.preventDefault();
28
30
  handler();
31
+ };
32
+ button.addEventListener('mousedown', (e) => {
33
+ activate(e);
29
34
  });
35
+ button.addEventListener('keydown', handleEnterKey(activate));
30
36
  return button;
31
37
  };
32
38
  export default (props) => new Plugin({
@@ -131,11 +131,16 @@ export default () => {
131
131
  else if (pState.title[0].textContent.length) {
132
132
  const titleEnd = pState.title[0].nodeSize + pState.title[1];
133
133
  decorations.push(Decoration.widget(titleEnd - 1, (view) => {
134
- return createToggleButton(() => {
134
+ return createToggleButton((e) => {
135
135
  const tr = view.state.tr.setMeta(altTitlesKey, {
136
136
  collapsed: false,
137
137
  });
138
138
  view.dispatch(skipTracking(tr));
139
+ const isKeyboardEvent = e.type === 'keydown';
140
+ if (isKeyboardEvent) {
141
+ const altTitle = view.dom.querySelector('.alt-title-text');
142
+ altTitle?.focus();
143
+ }
139
144
  }, 'alternative titles');
140
145
  }, {
141
146
  side: -1,
@@ -2,10 +2,28 @@ import { schema, } from '@manuscripts/transform';
2
2
  import { Plugin, PluginKey } from 'prosemirror-state';
3
3
  import { findChildrenByType } from 'prosemirror-utils';
4
4
  import { Decoration, DecorationSet } from 'prosemirror-view';
5
+ import { handleEnterKey } from '../lib/navigation-utils';
5
6
  import { createCommentMarker, getCommentKey, isReply, } from '../lib/comments';
6
7
  export const commentsKey = new PluginKey('comments');
7
8
  const COMMENT_SELECTION = 'comment-selection';
8
9
  const EMPTY_SELECTION = {};
10
+ const handleCommentMarkerInteraction = (view, target) => {
11
+ const state = view.state;
12
+ const com = commentsKey.getState(state);
13
+ const marker = target?.closest('[data-key]');
14
+ if (!marker && !com?.selection) {
15
+ return false;
16
+ }
17
+ const tr = state.tr;
18
+ if (marker) {
19
+ const key = marker.dataset.key;
20
+ setCommentSelection(tr, key, undefined, false);
21
+ }
22
+ else {
23
+ clearCommentSelection(tr);
24
+ }
25
+ view.dispatch(tr);
26
+ };
9
27
  export default () => {
10
28
  return new Plugin({
11
29
  key: commentsKey,
@@ -24,22 +42,16 @@ export default () => {
24
42
  props: {
25
43
  decorations: (state) => commentsKey.getState(state)?.decorations,
26
44
  handleClick: (view, pos, e) => {
27
- const state = view.state;
28
- const com = commentsKey.getState(state);
29
45
  const target = e.target;
30
- const marker = target.closest('[data-key]');
31
- if (!marker && !com?.selection) {
32
- return;
33
- }
34
- const tr = state.tr;
35
- if (marker) {
36
- const key = marker.dataset.key;
37
- setCommentSelection(tr, key, undefined, false);
38
- }
39
- else {
40
- clearCommentSelection(tr);
41
- }
42
- view.dispatch(tr);
46
+ return handleCommentMarkerInteraction(view, target);
47
+ },
48
+ handleDOMEvents: {
49
+ keydown: (view, e) => {
50
+ return handleEnterKey(() => {
51
+ const target = document.activeElement;
52
+ handleCommentMarkerInteraction(view, target);
53
+ })(e);
54
+ },
43
55
  },
44
56
  },
45
57
  });
@@ -46,12 +46,10 @@ export default () => {
46
46
  },
47
47
  handleDOMEvents: {
48
48
  focus(view, event) {
49
- const newTr = view.state.tr.setMeta(persistentCursor, { on: false });
50
- view.dispatch(newTr);
51
- },
52
- blur(view, event) {
53
- const newTr = view.state.tr.setMeta(persistentCursor, { on: true });
54
- view.dispatch(newTr);
49
+ if (this.getState(view.state)?.on) {
50
+ const newTr = view.state.tr.setMeta(persistentCursor, { on: false });
51
+ view.dispatch(newTr);
52
+ }
55
53
  },
56
54
  },
57
55
  },
@@ -3,6 +3,7 @@ import { Plugin, PluginKey } from 'prosemirror-state';
3
3
  import { findChildrenByType, findParentNodeOfTypeClosestToPos, } from 'prosemirror-utils';
4
4
  import { Decoration, DecorationSet } from 'prosemirror-view';
5
5
  import { sectionCategoryIcon } from '../icons';
6
+ import { handleEnterKey, createKeyboardInteraction, } from '../lib/navigation-utils';
6
7
  export const sectionCategoryKey = new PluginKey('section-category');
7
8
  export default (props) => new Plugin({
8
9
  key: sectionCategoryKey,
@@ -29,27 +30,51 @@ const createMenuItem = (props, contents, handler, isDisabled, isSelected) => {
29
30
  item.classList.add('disabled');
30
31
  }
31
32
  item.textContent = contents;
32
- item.addEventListener('mousedown', (event) => {
33
- handler(event);
34
- props.popper.destroy();
35
- });
33
+ item.setAttribute('tabindex', '0');
34
+ item.addEventListener('mousedown', handler);
35
+ item.addEventListener('keydown', handleEnterKey(handler));
36
36
  return item;
37
37
  };
38
38
  const createMenu = (props, currentCategory, categories, usedCategoryIDs, onSelect) => {
39
39
  const menu = document.createElement('div');
40
40
  menu.className = 'section-category menu';
41
+ const menuItems = [];
42
+ const removeKeydownListener = createKeyboardInteraction({
43
+ container: document,
44
+ navigation: {
45
+ getItems: () => menuItems,
46
+ arrowKeys: {
47
+ forward: 'ArrowDown',
48
+ backward: 'ArrowUp',
49
+ },
50
+ getCurrentElement: () => document.activeElement,
51
+ },
52
+ });
53
+ const destroy = () => {
54
+ removeKeydownListener();
55
+ props.popper.destroy();
56
+ };
41
57
  categories.forEach((category) => {
42
- const item = createMenuItem(props, category.titles[0], () => onSelect(category), category.isUnique && usedCategoryIDs.has(category.id), currentCategory === category);
58
+ const item = createMenuItem(props, category.titles[0], () => {
59
+ onSelect(category);
60
+ destroy();
61
+ }, category.isUnique && usedCategoryIDs.has(category.id), currentCategory === category);
62
+ menuItems.push(item);
43
63
  menu.appendChild(item);
44
64
  });
45
- return menu;
65
+ return { menu, destroy };
46
66
  };
47
67
  const createButton = (props, view, pos, currentCategory, categories, usedCategoryIDs, canEdit = true, disabled) => {
68
+ let menuInstance = null;
48
69
  const handleSelect = (category) => {
49
70
  const tr = view.state.tr;
50
71
  tr.setNodeAttribute(pos, 'category', category.id);
51
72
  view.dispatch(tr);
52
73
  };
74
+ const openMenu = () => {
75
+ menuInstance = createMenu(props, currentCategory, categories, usedCategoryIDs, handleSelect);
76
+ props.popper.show(button, menuInstance.menu, 'bottom-end', false);
77
+ };
53
78
  const button = document.createElement('button');
54
79
  button.innerHTML = sectionCategoryIcon;
55
80
  button.classList.add('section-category-button');
@@ -62,9 +87,17 @@ const createButton = (props, view, pos, currentCategory, categories, usedCategor
62
87
  button.classList.add('disabled');
63
88
  }
64
89
  else if (canEdit) {
65
- button.addEventListener('mousedown', () => {
66
- const menu = createMenu(props, currentCategory, categories, usedCategoryIDs, handleSelect);
67
- props.popper.show(button, menu, 'bottom-end', false);
90
+ button.addEventListener('click', openMenu);
91
+ createKeyboardInteraction({
92
+ container: button,
93
+ additionalKeys: {
94
+ Enter: openMenu,
95
+ Escape: (e) => {
96
+ e.preventDefault();
97
+ menuInstance?.destroy();
98
+ menuInstance = null;
99
+ },
100
+ },
68
101
  });
69
102
  }
70
103
  return button;
@@ -20,24 +20,43 @@ import { insertTransAbstract, insertTransGraphicalAbstract } from '../commands';
20
20
  import { addAuthorIcon, translateIcon } from '../icons';
21
21
  import { getLanguage, getLanguageLabel } from '../lib/languages';
22
22
  import { templateAllows } from '../lib/template';
23
- const createMenuItem = (props, contents, handler, isSelected = false) => {
23
+ import { handleEnterKey, createKeyboardInteraction, } from '../lib/navigation-utils';
24
+ const createMenuItem = (props, contents, handler, isSelected = false, tabIndex) => {
24
25
  const item = document.createElement('div');
25
26
  item.className = `menu-item ${isSelected ? 'selected' : ''}`;
26
27
  item.textContent = contents;
27
- item.addEventListener('mousedown', (event) => {
28
- handler(event);
29
- props.popper.destroy();
30
- });
28
+ item.tabIndex = tabIndex;
29
+ item.addEventListener('mousedown', handler);
30
+ item.addEventListener('keydown', handleEnterKey(handler));
31
31
  return item;
32
32
  };
33
33
  const createLanguageMenu = (props, selectedCode, onSelect) => {
34
34
  const menu = document.createElement('div');
35
35
  menu.className = 'language menu';
36
- props.languages.forEach((language) => {
37
- const item = createMenuItem(props, getLanguageLabel(language), () => onSelect(language.code), selectedCode === language.code);
36
+ const menuItems = [];
37
+ const removeKeydownListener = createKeyboardInteraction({
38
+ container: document,
39
+ navigation: {
40
+ getItems: () => menuItems,
41
+ arrowKeys: {
42
+ forward: 'ArrowDown',
43
+ backward: 'ArrowUp',
44
+ },
45
+ },
46
+ });
47
+ const destroy = () => {
48
+ removeKeydownListener();
49
+ props.popper.destroy();
50
+ };
51
+ props.languages.forEach((language, index) => {
52
+ const item = createMenuItem(props, getLanguageLabel(language), () => {
53
+ onSelect(language.code);
54
+ destroy();
55
+ }, selectedCode === language.code, index === 0 ? 0 : -1);
56
+ menuItems.push(item);
38
57
  menu.appendChild(item);
39
58
  });
40
- return menu;
59
+ return { menu, destroy };
41
60
  };
42
61
  export default (props) => new Plugin({
43
62
  props: {
@@ -64,10 +83,11 @@ export default (props) => new Plugin({
64
83
  if (canEdit) {
65
84
  widgets.push(Decoration.widget(pos + 1, (view) => {
66
85
  const $span = document.createElement('span');
86
+ $span.tabIndex = 0;
67
87
  $span.className = 'add-trans-abstract';
68
88
  $span.title = 'Add translation';
69
89
  $span.innerHTML = `${addAuthorIcon} <span class="add-trans-abstract-text">Add translation</span>`;
70
- $span.addEventListener('mousedown', (event) => {
90
+ const handleActivate = (event) => {
71
91
  event.preventDefault();
72
92
  event.stopPropagation();
73
93
  if (isGraphical && category) {
@@ -76,7 +96,9 @@ export default (props) => new Plugin({
76
96
  else {
77
97
  insertTransAbstract(view.state, view.dispatch, node.attrs.category, pos + node.nodeSize);
78
98
  }
79
- });
99
+ };
100
+ $span.addEventListener('mousedown', handleActivate);
101
+ $span.addEventListener('keydown', handleEnterKey(handleActivate));
80
102
  return $span;
81
103
  }));
82
104
  }
@@ -92,12 +114,14 @@ export default (props) => new Plugin({
92
114
  $btn.className = 'language-selector-btn';
93
115
  $btn.setAttribute('data-cy', 'language-selector-btn');
94
116
  $btn.contentEditable = 'false';
117
+ $btn.tabIndex = canEdit ? 0 : -1;
95
118
  const code = node.attrs.lang || 'en';
96
119
  const lang = getLanguage(code, props.languages);
97
120
  const label = getLanguageLabel(lang);
98
121
  $btn.innerHTML = `<span>${label}</span> ${translateIcon}`;
99
122
  if (canEdit) {
100
- $btn.addEventListener('mousedown', (event) => {
123
+ let menuInstance = null;
124
+ const handleOpenMenu = (event) => {
101
125
  event.preventDefault();
102
126
  event.stopPropagation();
103
127
  props.popper.destroy();
@@ -105,9 +129,21 @@ export default (props) => new Plugin({
105
129
  const tr = view.state.tr.setNodeAttribute(pos, 'lang', code);
106
130
  view.dispatch(tr);
107
131
  };
108
- const menu = createLanguageMenu(props, code, handleSelect);
109
- props.popper.show($btn, menu, 'bottom-end', false);
132
+ menuInstance = createLanguageMenu(props, code, handleSelect);
133
+ props.popper.show($btn, menuInstance.menu, 'bottom-end', false);
134
+ };
135
+ createKeyboardInteraction({
136
+ container: $btn,
137
+ additionalKeys: {
138
+ Enter: handleOpenMenu,
139
+ Escape: (e) => {
140
+ e.preventDefault();
141
+ menuInstance?.destroy();
142
+ menuInstance = null;
143
+ },
144
+ },
110
145
  });
146
+ $btn.addEventListener('mousedown', handleOpenMenu);
111
147
  }
112
148
  return $btn;
113
149
  }));
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.9.11';
1
+ export const VERSION = '3.9.13';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -1,4 +1,6 @@
1
1
  import { schema } from '@manuscripts/transform';
2
+ import { TextSelection } from 'prosemirror-state';
3
+ import { createKeyboardInteraction } from '../lib/navigation-utils';
2
4
  import BlockView from './block_view';
3
5
  import { createNodeView } from './creators';
4
6
  export class AccessibilityElementView extends BlockView {
@@ -23,6 +25,34 @@ export class AccessibilityElementView extends BlockView {
23
25
  super.createElement();
24
26
  this.contentDOM.className = 'accessibility_element_input';
25
27
  this.contentDOM.setAttribute('contenteditable', 'true');
28
+ this.contentDOM.tabIndex = this.node.type === schema.nodes.alt_text ? 0 : -1;
29
+ this.removeKeydownListener = createKeyboardInteraction({
30
+ container: this.contentDOM,
31
+ navigation: {
32
+ getItems: () => {
33
+ const parentEl = this.dom.parentElement;
34
+ if (!parentEl) {
35
+ return [];
36
+ }
37
+ return Array.from(parentEl.querySelectorAll('.accessibility_element_input'));
38
+ },
39
+ arrowKeys: { forward: 'ArrowDown', backward: 'ArrowUp' },
40
+ getCurrentElement: () => this.contentDOM,
41
+ },
42
+ additionalKeys: {
43
+ Enter: (e) => {
44
+ e.preventDefault();
45
+ const pos = this.getPos();
46
+ const tr = this.view.state.tr.setSelection(TextSelection.create(this.view.state.doc, pos + 1));
47
+ this.view.dispatch(tr);
48
+ this.view.focus();
49
+ },
50
+ },
51
+ });
52
+ }
53
+ destroy() {
54
+ this.removeKeydownListener?.();
55
+ super.destroy();
26
56
  }
27
57
  }
28
58
  export default createNodeView(AccessibilityElementView);
@@ -13,6 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { TextSelection } from 'prosemirror-state';
17
+ import { createKeyboardInteraction } from '../lib/navigation-utils';
16
18
  import { BaseNodeView } from './base_node_view';
17
19
  import { createNodeView } from './creators';
18
20
  export class AltTitleView extends BaseNodeView {
@@ -31,10 +33,37 @@ export class AltTitleView extends BaseNodeView {
31
33
  this.dom.setAttribute('data-type', this.node.attrs.type);
32
34
  this.contentDOM = document.createElement('div');
33
35
  this.contentDOM.classList.add('alt-title-text');
36
+ this.contentDOM.tabIndex = this.node.attrs.type === 'running' ? 0 : -1;
37
+ this.removeKeydownListener = createKeyboardInteraction({
38
+ container: this.contentDOM,
39
+ navigation: {
40
+ getItems: () => {
41
+ const allAltTitles = Array.from(this.view.dom.querySelectorAll('.alt-title-text'));
42
+ return allAltTitles;
43
+ },
44
+ arrowKeys: { forward: 'ArrowDown', backward: 'ArrowUp' },
45
+ getCurrentElement: () => this.contentDOM,
46
+ },
47
+ additionalKeys: {
48
+ Enter: (e) => {
49
+ e.preventDefault();
50
+ const pos = this.getPos();
51
+ if (typeof pos === 'number') {
52
+ const tr = this.view.state.tr.setSelection(TextSelection.create(this.view.state.doc, pos + 1));
53
+ this.view.dispatch(tr);
54
+ this.view.focus();
55
+ }
56
+ },
57
+ },
58
+ });
34
59
  this.dom.appendChild(label);
35
60
  this.dom.appendChild(this.contentDOM);
36
61
  this.updateContents();
37
62
  };
38
63
  }
64
+ destroy() {
65
+ this.removeKeydownListener?.();
66
+ super.destroy();
67
+ }
39
68
  }
40
69
  export default createNodeView(AltTitleView);
@@ -19,6 +19,7 @@ import { arrowDown } from '../icons';
19
19
  import { altTitlesKey } from '../plugins/alt-titles';
20
20
  import BlockView from './block_view';
21
21
  import { createNodeView } from './creators';
22
+ import { handleEnterKey } from '../lib/navigation-utils';
22
23
  export class AltTitleSectionView extends BlockView {
23
24
  constructor() {
24
25
  super(...arguments);
@@ -41,7 +42,7 @@ export class AltTitleSectionView extends BlockView {
41
42
  button.classList.add('alt-titles-closing-button', 'button-reset');
42
43
  button.setAttribute('aria-label', 'Collapse alternative titles');
43
44
  button.innerHTML = arrowDown;
44
- button.addEventListener('click', () => {
45
+ const handleCollapse = () => {
45
46
  const tr = this.view.state.tr.setMeta(altTitlesKey, {
46
47
  collapsed: true,
47
48
  });
@@ -53,7 +54,14 @@ export class AltTitleSectionView extends BlockView {
53
54
  tr.setSelection(TextSelection.create(tr.doc, titleEndPos));
54
55
  }
55
56
  this.view.dispatch(tr);
57
+ const toggleButton = this.view.dom.querySelector('.toggle-button-open');
58
+ toggleButton?.focus();
59
+ };
60
+ button.addEventListener('click', (e) => {
61
+ e.preventDefault();
62
+ handleCollapse();
56
63
  });
64
+ button.addEventListener('keydown', handleEnterKey(() => handleCollapse()));
57
65
  closingPanel.appendChild(button);
58
66
  return closingPanel;
59
67
  }
@@ -117,7 +117,7 @@ export class AttachmentView extends BlockView {
117
117
  stopPropagation: () => {
118
118
  },
119
119
  };
120
- this.props.onEditorClick(this.getPos(), this.node, this.getPos(), event);
120
+ this.props.onEditorClick(event);
121
121
  }
122
122
  }
123
123
  }
@@ -18,6 +18,7 @@ import { schema, } from '@manuscripts/transform';
18
18
  import { NodeSelection } from 'prosemirror-state';
19
19
  import { ReferencesEditor, } from '../components/references/ReferencesEditor';
20
20
  import { createCommentMarker, handleComment } from '../lib/comments';
21
+ import { handleEnterKey } from '../lib/navigation-utils';
21
22
  import { findNodeByID } from '../lib/doc';
22
23
  import { sanitize } from '../lib/dompurify';
23
24
  import { addTrackChangesAttributes, addTrackChangesClassNames, } from '../lib/track-changes-utils';
@@ -33,31 +34,50 @@ export class BibliographyElementBlockView extends BlockView {
33
34
  super(...arguments);
34
35
  this.stopEvent = () => true;
35
36
  this.ignoreMutation = () => true;
37
+ this.handleCommentMarkerInteraction = (marker) => {
38
+ const key = marker.dataset.key;
39
+ const tr = this.view.state.tr;
40
+ setCommentSelection(tr, key, undefined, false);
41
+ this.view.dispatch(tr);
42
+ };
43
+ this.handleBibItemInteraction = (item) => {
44
+ if (!this.props.getCapabilities().seeReferencesButtons) {
45
+ return;
46
+ }
47
+ this.showContextMenu(item);
48
+ const node = findChildByID(this.view, item.id);
49
+ if (node) {
50
+ const tr = this.view.state.tr;
51
+ tr.setSelection(NodeSelection.create(this.view.state.doc, node.pos));
52
+ this.view.dispatch(tr);
53
+ }
54
+ };
36
55
  this.handleClick = (event) => {
37
56
  const element = event.target;
38
57
  const marker = element.closest('.comment-marker');
39
58
  if (marker) {
40
- const key = marker.dataset.key;
41
- const tr = this.view.state.tr;
42
- setCommentSelection(tr, key, undefined, false);
43
- this.view.dispatch(tr);
59
+ this.handleCommentMarkerInteraction(marker);
44
60
  return;
45
61
  }
46
- if (this.props.getCapabilities().seeReferencesButtons) {
47
- const item = element.closest('.bib-item');
48
- if (item) {
49
- this.showContextMenu(item);
50
- const node = findChildByID(this.view, item.id);
51
- if (!node) {
52
- return;
53
- }
54
- const view = this.view;
55
- const tr = view.state.tr;
56
- tr.setSelection(NodeSelection.create(view.state.doc, node.pos));
57
- view.dispatch(tr);
58
- }
62
+ const item = element.closest('.bib-item');
63
+ if (item) {
64
+ this.handleBibItemInteraction(item);
59
65
  }
60
66
  };
67
+ this.handleKeyDown = (event) => {
68
+ handleEnterKey(() => {
69
+ const target = document.activeElement;
70
+ const marker = target?.closest('.comment-marker');
71
+ if (marker) {
72
+ this.handleCommentMarkerInteraction(marker);
73
+ return;
74
+ }
75
+ const bibItem = target?.closest('.bib-item');
76
+ if (bibItem) {
77
+ this.handleBibItemInteraction(bibItem);
78
+ }
79
+ })(event);
80
+ };
61
81
  this.createElement = () => {
62
82
  this.container = document.createElement('div');
63
83
  this.container.classList.add('block');
@@ -164,6 +184,7 @@ export class BibliographyElementBlockView extends BlockView {
164
184
  const wrapper = document.createElement('div');
165
185
  wrapper.classList.add('contents');
166
186
  wrapper.addEventListener('click', this.handleClick);
187
+ wrapper.addEventListener('keydown', this.handleKeyDown);
167
188
  const [meta, bibliography] = bib.engine.makeBibliography();
168
189
  for (let i = 0; i < bibliography.length; i++) {
169
190
  const id = meta.entry_ids[i][0];
@@ -190,6 +211,7 @@ export class BibliographyElementBlockView extends BlockView {
190
211
  }
191
212
  }
192
213
  const element = sanitize(`<div id="${id}" class="bib-item"><div class="csl-bib-body">${tempDiv.innerHTML}</div></div>`).firstElementChild;
214
+ element.tabIndex = 0;
193
215
  const comment = createCommentMarker('div', id);
194
216
  element.prepend(comment);
195
217
  addTrackChangesAttributes(node.attrs, element);
@@ -29,6 +29,7 @@ export class CitationView extends BaseNodeView {
29
29
  createDOM() {
30
30
  this.dom = document.createElement('span');
31
31
  this.dom.classList.add('citation');
32
+ this.dom.tabIndex = 0;
32
33
  }
33
34
  updateContents() {
34
35
  super.updateContents();
@@ -21,6 +21,7 @@ import { CitationEditor, } from '../components/references/CitationEditor';
21
21
  import { CitationViewer, } from '../components/references/CitationViewer';
22
22
  import { handleComment } from '../lib/comments';
23
23
  import { Crossref } from '../lib/crossref';
24
+ import { handleEnterKey } from '../lib/navigation-utils';
24
25
  import { isDeleted } from '../lib/track-changes-utils';
25
26
  import { deleteNode, findChildByID, updateNodeAttrs } from '../lib/view';
26
27
  import { getBibliographyPluginState } from '../plugins/bibliography';
@@ -41,12 +42,12 @@ export class CitationEditableView extends CitationView {
41
42
  element.classList.contains('comment-icon') ||
42
43
  element.parentElement?.classList.contains('comment-icon'));
43
44
  };
44
- this.handleClick = (event) => {
45
+ this.handleClick = () => {
45
46
  if (!this.can.seeReferencesButtons ||
46
47
  this.dom.classList.contains('inconsistency-highlight')) {
47
48
  this.showPopper();
48
49
  }
49
- else if (!isDeleted(this.node) && event.button === 0) {
50
+ else if (!isDeleted(this.node)) {
50
51
  const attrs = this.node.attrs;
51
52
  if (attrs.rids.length) {
52
53
  this.showContextMenu();
@@ -191,6 +192,7 @@ export class CitationEditableView extends CitationView {
191
192
  createDOM() {
192
193
  super.createDOM();
193
194
  this.dom.addEventListener('mouseup', this.handleClick);
195
+ this.dom.addEventListener('keydown', handleEnterKey(() => this.handleClick()));
194
196
  }
195
197
  insertBibliographyNode(attrs) {
196
198
  const { doc, tr } = this.view.state;