@sciflow/editor-start 0.0.1-beta2 → 0.0.3

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 (44) hide show
  1. package/README.md +49 -2
  2. package/THIRD_PARTY_LICENSES +9 -0
  3. package/bin/extract-translations.mjs +54 -0
  4. package/dist/bundle/sciflow-editor.css +1 -1
  5. package/dist/bundle/sciflow-editor.js +9821 -16643
  6. package/dist/index.d.ts +7 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +6 -3
  9. package/dist/lib/citation-source-adapter.d.ts +1 -1
  10. package/dist/lib/citation-source-adapter.d.ts.map +1 -1
  11. package/dist/lib/citation-source-adapter.js +1 -1
  12. package/dist/lib/drag-preview.d.ts.map +1 -1
  13. package/dist/lib/drag-preview.js +9 -2
  14. package/dist/lib/editor-element.d.ts +14 -3
  15. package/dist/lib/editor-element.d.ts.map +1 -1
  16. package/dist/lib/editor-element.js +25 -1
  17. package/dist/lib/format-bar.d.ts +19 -2
  18. package/dist/lib/format-bar.d.ts.map +1 -1
  19. package/dist/lib/format-bar.js +303 -59
  20. package/dist/lib/ghost-cursor.d.ts +10 -0
  21. package/dist/lib/ghost-cursor.d.ts.map +1 -0
  22. package/dist/lib/ghost-cursor.js +59 -0
  23. package/dist/lib/icons-generated.d.ts +14 -0
  24. package/dist/lib/icons-generated.d.ts.map +1 -0
  25. package/dist/lib/icons-generated.js +60 -0
  26. package/dist/lib/outline.d.ts +8 -5
  27. package/dist/lib/outline.d.ts.map +1 -1
  28. package/dist/lib/outline.js +80 -35
  29. package/dist/lib/reference-list.d.ts +10 -2
  30. package/dist/lib/reference-list.d.ts.map +1 -1
  31. package/dist/lib/reference-list.js +45 -9
  32. package/dist/lib/selection-editor.d.ts +16 -0
  33. package/dist/lib/selection-editor.d.ts.map +1 -1
  34. package/dist/lib/selection-editor.js +291 -148
  35. package/dist/lib/selection-ui-renderer.d.ts +1 -0
  36. package/dist/lib/selection-ui-renderer.d.ts.map +1 -1
  37. package/dist/lib/selection-ui-renderer.js +3 -3
  38. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  39. package/package.json +24 -17
  40. package/dist/demo/demo.js +0 -56
  41. package/dist/demo/index.html +0 -169
  42. package/dist/lib/citation-drop.d.ts +0 -25
  43. package/dist/lib/citation-drop.d.ts.map +0 -1
  44. package/dist/lib/citation-drop.js +0 -53
@@ -16,7 +16,8 @@ import { __decorate } from "tslib";
16
16
  import { css, html, LitElement, unsafeCSS } from 'lit';
17
17
  import { customElement, property, state } from 'lit/decorators.js';
18
18
  import { ref } from 'lit/directives/ref.js';
19
- import { SourceField } from '@sciflow/editor-core';
19
+ import { SourceField, t, LOCALE_CHANGE_EVENT } from '@sciflow/editor-core';
20
+ import { getMarkRange } from '@sciflow/editor-core/commands';
20
21
  import { renderTexToSvg } from '@sciflow/editor-core/features/math';
21
22
  import { Fragment as PMFragment } from 'prosemirror-model';
22
23
  import { EditorState, NodeSelection } from 'prosemirror-state';
@@ -54,9 +55,12 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
54
55
  this.citationDraft = null;
55
56
  /** Draft values for cross-reference sidebar (display text + target href). */
56
57
  this.crossReferenceDraft = null;
58
+ /** Draft values for hyperlink (anchor mark) sidebar. */
59
+ this.hyperlinkDraft = null;
57
60
  this.draftAttrs = null;
58
61
  this.draftDirty = false;
59
62
  this.pinSelection = false;
63
+ this.showAllAttrs = false;
60
64
  this.resolvedEditor = null;
61
65
  this.themeStyleElements = [];
62
66
  this.citationSourceContainer = null;
@@ -72,6 +76,8 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
72
76
  static { this.styles = css `${unsafeCSS(selectionEditorCss)}`; }
73
77
  connectedCallback() {
74
78
  super.connectedCallback();
79
+ this.localeListener = () => this.requestUpdate();
80
+ document.addEventListener(LOCALE_CHANGE_EVENT, this.localeListener);
75
81
  this.themeUnsub = subscribeToSciFlowTheme((cssTexts) => {
76
82
  this.themeStyleElements = applyThemeStylesToRoot(this.renderRoot, this.themeStyleElements, cssTexts);
77
83
  });
@@ -85,6 +91,10 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
85
91
  clearTimeout(this.selectionDebounce);
86
92
  this.selectionDebounce = null;
87
93
  }
94
+ if (this.localeListener) {
95
+ document.removeEventListener(LOCALE_CHANGE_EVENT, this.localeListener);
96
+ this.localeListener = undefined;
97
+ }
88
98
  this.themeUnsub?.();
89
99
  this.themeUnsub = undefined;
90
100
  }
@@ -94,6 +104,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
94
104
  this.resolveEditorReference();
95
105
  }
96
106
  if (changedProperties.has('elementInfo')) {
107
+ this.showAllAttrs = false;
97
108
  if (this.elementInfo?.type === 'math') {
98
109
  const attrs = this.elementInfo.attrs;
99
110
  this.mathDraft = {
@@ -134,6 +145,23 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
134
145
  this.mathDraft = null;
135
146
  this.mathPreviewResult = null;
136
147
  this.citationDraft = null;
148
+ this.hyperlinkDraft = null;
149
+ this.resetDraftAttrs();
150
+ this.destroyCitationContentView();
151
+ if (this.mathPreviewDebounce) {
152
+ clearTimeout(this.mathPreviewDebounce);
153
+ this.mathPreviewDebounce = null;
154
+ }
155
+ }
156
+ else if (this.elementInfo?.type === 'anchor') {
157
+ const attrs = this.elementInfo.attrs;
158
+ this.hyperlinkDraft = {
159
+ href: typeof attrs?.href === 'string' ? attrs.href : '',
160
+ };
161
+ this.mathDraft = null;
162
+ this.mathPreviewResult = null;
163
+ this.citationDraft = null;
164
+ this.crossReferenceDraft = null;
137
165
  this.resetDraftAttrs();
138
166
  this.destroyCitationContentView();
139
167
  if (this.mathPreviewDebounce) {
@@ -146,6 +174,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
146
174
  this.mathPreviewResult = null;
147
175
  this.citationDraft = null;
148
176
  this.crossReferenceDraft = null;
177
+ this.hyperlinkDraft = null;
149
178
  this.resetDraftAttrs();
150
179
  this.destroyCitationContentView();
151
180
  if (this.mathPreviewDebounce) {
@@ -168,7 +197,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
168
197
  this.citationDraft = { ...this.citationDraft, sourceEncoded: encoded };
169
198
  }
170
199
  },
171
- getReferences: () => refs.map((r) => ({ id: r.id, rawCitation: r.rawCitation })),
200
+ getReferences: () => refs.map((r) => ({ id: r.id, rawReference: r.rawReference })),
172
201
  };
173
202
  if (adapter.update) {
174
203
  adapter.update(this.citationSourceContainer, items, context);
@@ -181,7 +210,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
181
210
  return html `
182
211
  <div class="selection-editor-container" @keydown=${this.handleGenericKeydown}>
183
212
  <p class="selection-editor-placeholder">
184
- No element selected. Click or select an element in the editor.
213
+ ${t('selectionEditor.noElement')}
185
214
  </p>
186
215
  </div>
187
216
  `;
@@ -195,38 +224,29 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
195
224
  if (this.elementInfo.type === 'link' && this.crossReferenceDraft) {
196
225
  return this.renderCrossReferenceSidebar();
197
226
  }
227
+ if (this.elementInfo.type === 'anchor' && this.hyperlinkDraft) {
228
+ return this.renderHyperlinkSidebar();
229
+ }
198
230
  if (this.elementInfo.type === 'figure') {
199
231
  return this.renderFigureSidebar();
200
232
  }
201
233
  const uiSchema = this.resolvedEditor?.getSelectionUiSchema?.();
202
234
  const nodeFragment = uiSchema?.properties?.nodes?.[this.elementInfo.type];
203
- const useSchemaDriven = nodeFragment &&
204
- (this.elementInfo.type === 'heading' ||
205
- this.elementInfo.type === 'figure');
235
+ const useSchemaDriven = !!nodeFragment;
206
236
  if (useSchemaDriven && nodeFragment) {
207
237
  const attrs = this.getEffectiveAttrs();
208
238
  return html `
209
239
  <div class="selection-editor-container">
210
240
  ${this.renderSelectionControls()}
211
- <div class="selection-editor-field">
212
- <span class="selection-editor-label">Type:</span>
213
- <code class="selection-editor-value">${this.elementInfo.type}</code>
214
- </div>
215
- ${this.elementInfo.id
216
- ? html `
217
- <div class="selection-editor-field">
218
- <span class="selection-editor-label">ID:</span>
219
- <code class="selection-editor-value">${this.elementInfo.id}</code>
220
- </div>
221
- `
222
- : ''}
241
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
223
242
  ${renderNodeFromUiSchema(nodeFragment, {
224
243
  attrs,
225
244
  onAttrChange: (attr, value) => this.setDraftAttr(attr, value),
245
+ showHidden: this.showAllAttrs,
226
246
  getOptionsSource: (id) => {
227
247
  if (id === 'references') {
228
248
  const refs = this.resolvedEditor?.references ?? [];
229
- return refs.map((r) => ({ id: r.id, label: r.rawCitation ?? r.id }));
249
+ return refs.map((r) => ({ id: r.id, label: r.rawReference ?? r.id }));
230
250
  }
231
251
  if (id === 'outlineTargets') {
232
252
  return this.getOutlineTargetOptions();
@@ -235,88 +255,60 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
235
255
  },
236
256
  })}
237
257
  ${this.renderDraftActions()}
258
+ <button
259
+ class="selection-editor-expand-btn"
260
+ @click=${() => { this.showAllAttrs = !this.showAllAttrs; }}
261
+ >${this.showAllAttrs ? t('selectionEditor.collapseAttributes') : t('selectionEditor.expandAttributes')}</button>
238
262
  </div>
239
263
  `;
240
264
  }
241
265
  return html `
242
266
  <div class="selection-editor-container" @keydown=${this.handleGenericKeydown}>
243
267
  ${this.renderSelectionControls()}
244
- <!-- Type display -->
245
- <div class="selection-editor-field">
246
- <span class="selection-editor-label">Type:</span>
247
- <code class="selection-editor-value">${this.elementInfo.type}</code>
248
- </div>
249
268
 
250
- <!-- ID display (read-only) -->
251
- ${this.elementInfo.id
252
- ? html `
253
- <div class="selection-editor-field">
254
- <span class="selection-editor-label">ID:</span>
255
- <code class="selection-editor-value">${this.elementInfo.id}</code>
256
- </div>
257
- `
258
- : ''}
269
+ <!-- ELEMENT section -->
270
+ <div class="selection-editor-section">
271
+ <span class="selection-editor-section-label">${t('selectionEditor.sectionElement')}</span>
272
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
273
+ </div>
259
274
 
260
- <!-- Attributes -->
275
+ <!-- FORMATTING / Attributes -->
261
276
  ${this.elementInfo.attrs && Object.keys(this.elementInfo.attrs).length > 0
262
277
  ? (() => {
263
278
  const attrs = this.getEffectiveAttrs();
264
279
  const filtered = Object.entries(attrs).filter(([key]) => key !== 'id' &&
265
280
  key !== 'data' &&
266
281
  !(this.elementInfo.type === 'citation' && key === 'source'));
267
- if (this.elementInfo.type === 'heading' && filtered.length > 0) {
268
- const levelEntry = filtered.find(([k]) => k === 'level');
269
- const rest = filtered.filter(([k]) => k !== 'level');
270
- return html `
271
- ${levelEntry
272
- ? html `
273
- <div class="selection-editor-attrs">
274
- ${this.renderAttributeField(levelEntry[0], levelEntry[1])}
275
- </div>
276
- `
277
- : ''}
278
- ${rest.length > 0
279
- ? html `
280
- <details class="selection-editor-details">
281
- <summary class="selection-editor-details-summary">More attributes</summary>
282
- <div class="selection-editor-details-body">
283
- ${rest.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))}
284
- </div>
285
- </details>
286
- `
287
- : ''}
288
- `;
289
- }
290
- if (this.elementInfo.type === 'paragraph' && filtered.length > 0) {
291
- const alignmentEntry = filtered.find(([k]) => k === 'text-align');
292
- const rest = filtered.filter(([k]) => k !== 'text-align');
293
- return html `
294
- ${alignmentEntry
295
- ? html `
296
- <div class="selection-editor-attrs">
297
- ${this.renderAttributeField(alignmentEntry[0], alignmentEntry[1])}
298
- </div>
299
- `
300
- : ''}
301
- ${rest.length > 0
302
- ? html `
303
- <details class="selection-editor-details">
304
- <summary class="selection-editor-details-summary">More attributes</summary>
305
- <div class="selection-editor-details-body">
306
- ${rest.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))}
307
- </div>
308
- </details>
309
- `
310
- : ''}
311
- `;
312
- }
282
+ // Split into primary (formatting) attributes and the rest
283
+ const primaryKeys = ['text-align', 'level', 'type'];
284
+ const primary = filtered.filter(([k]) => primaryKeys.includes(k));
285
+ const rest = filtered.filter(([k]) => !primaryKeys.includes(k));
313
286
  return html `
314
- <div class="selection-editor-field">
315
- <span class="selection-editor-label">Attributes:</span>
316
- <div class="selection-editor-attrs">
317
- ${filtered.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))}
318
- </div>
319
- </div>
287
+ ${primary.length > 0
288
+ ? html `
289
+ <div class="selection-editor-section">
290
+ <span class="selection-editor-section-label">${t('selectionEditor.sectionFormatting')}</span>
291
+ <div class="selection-editor-attrs">
292
+ ${primary.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))}
293
+ </div>
294
+ </div>
295
+ `
296
+ : ''}
297
+ ${rest.length > 0 || primary.length === 0
298
+ ? html `
299
+ <details class="selection-editor-details">
300
+ <summary class="selection-editor-details-summary">
301
+ <span class="selection-editor-section-label">${t('selectionEditor.sectionAttributes')}</span>
302
+ <svg class="selection-editor-details-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>
303
+ </summary>
304
+ <div class="selection-editor-details-body">
305
+ ${rest.length > 0
306
+ ? rest.map(([attrName, attrValue]) => this.renderAttributeField(attrName, attrValue))
307
+ : html `<span class="selection-editor-placeholder">${t('selectionEditor.noElement')}</span>`}
308
+ </div>
309
+ </details>
310
+ `
311
+ : ''}
320
312
  `;
321
313
  })()
322
314
  : ''}
@@ -457,8 +449,40 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
457
449
  };
458
450
  return;
459
451
  }
452
+ // Check for anchor mark (hyperlink) at cursor or in selection
453
+ const anchorMarkType = view.state.schema.marks['anchor'];
454
+ if (anchorMarkType) {
455
+ const { from, to, empty } = sel;
456
+ let anchorMark = null;
457
+ if (empty) {
458
+ const marks = view.state.storedMarks ?? sel.$from.marks();
459
+ anchorMark = marks.find((m) => m.type === anchorMarkType) ?? null;
460
+ if (!anchorMark) {
461
+ // Also check the character before the cursor
462
+ const marksBefore = sel.$from.marks();
463
+ anchorMark = marksBefore.find((m) => m.type === anchorMarkType) ?? null;
464
+ }
465
+ }
466
+ else {
467
+ view.state.doc.nodesBetween(from, to, (node) => {
468
+ if (!anchorMark && node.isText) {
469
+ anchorMark = node.marks.find((m) => m.type === anchorMarkType) ?? null;
470
+ }
471
+ return !anchorMark;
472
+ });
473
+ }
474
+ if (anchorMark) {
475
+ this.elementInfo = {
476
+ type: 'anchor',
477
+ id: null,
478
+ attrs: { ...anchorMark.attrs },
479
+ position: null,
480
+ };
481
+ return;
482
+ }
483
+ }
460
484
  let elementInfo = null;
461
- let nodePosition = null;
485
+ let nodePosition;
462
486
  const anchor = selection?.anchor ?? 0;
463
487
  const $pos = view.state.doc.resolve(anchor);
464
488
  const citationType = view.state.schema.nodes.citation;
@@ -640,7 +664,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
640
664
  if (!id)
641
665
  continue;
642
666
  const text = f.text?.trim() || id;
643
- options.push({ id: `#${id}`, label: `Figure: ${text}` });
667
+ options.push({ id: `#${id}`, label: `${t('outline.figure')}: ${text}` });
644
668
  }
645
669
  this.outlineTargetOptionsCacheDoc = pmDoc;
646
670
  this.outlineTargetOptionsCache = options;
@@ -695,9 +719,23 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
695
719
  /**
696
720
  * Render a single attribute field, decoding known encoded values for readability.
697
721
  */
722
+ getNodeTypeLabel(type) {
723
+ const known = {
724
+ figure: () => t('selectionEditor.figure'),
725
+ math: () => t('selectionEditor.math'),
726
+ citation: () => t('selectionEditor.citation'),
727
+ anchor: () => t('selectionEditor.hyperlink'),
728
+ link: () => t('selectionEditor.crossReference'),
729
+ heading: () => t('selectionEditor.heading'),
730
+ paragraph: () => t('selectionEditor.paragraph'),
731
+ };
732
+ const label = known[type]?.() ?? type.replace(/_/g, ' ');
733
+ return label.replace(/^\w/, (m) => m.toUpperCase());
734
+ }
698
735
  renderAttributeField(attrName, attrValue) {
699
736
  const decodedValue = this.decodeSpecialAttribute(attrName, attrValue);
700
737
  const renderedValue = decodedValue === null || decodedValue === undefined ? '' : String(decodedValue);
738
+ const friendlyLabel = this.getAttributeLabel(attrName);
701
739
  if (attrName === 'level' && this.elementInfo?.type === 'heading') {
702
740
  const level = Math.min(6, Math.max(1, parseInt(renderedValue, 10) || 1));
703
741
  return html `
@@ -706,7 +744,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
706
744
  class="selection-editor-attr-label"
707
745
  for="selection-editor-attr-${attrName}"
708
746
  >
709
- ${attrName}:
747
+ ${friendlyLabel}
710
748
  </label>
711
749
  <select
712
750
  id="selection-editor-attr-${attrName}"
@@ -725,19 +763,27 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
725
763
  class="selection-editor-attr-label"
726
764
  for="selection-editor-attr-${attrName}"
727
765
  >
728
- ${attrName}:
766
+ ${friendlyLabel}
729
767
  </label>
730
768
  <input
731
769
  type="text"
732
770
  id="selection-editor-attr-${attrName}"
733
771
  class="selection-editor-input"
734
772
  .value=${renderedValue}
735
- placeholder="No value"
773
+ placeholder=${t('selectionEditor.defaultValue')}
736
774
  @change=${(e) => this.handleDraftAttrChange(e, attrName)}
737
775
  />
738
776
  </div>
739
777
  `;
740
778
  }
779
+ /**
780
+ * Convert a raw attribute name like 'text-align' to a human-readable label.
781
+ */
782
+ getAttributeLabel(attrName) {
783
+ return attrName
784
+ .replace(/[-_]/g, ' ')
785
+ .replace(/^\w/, (m) => m.toUpperCase());
786
+ }
741
787
  /**
742
788
  * Decode known encoded attributes for more readable display/editing.
743
789
  */
@@ -783,7 +829,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
783
829
  this.citationDraft = { ...this.citationDraft, sourceEncoded: encoded };
784
830
  }
785
831
  },
786
- getReferences: () => refs.map((r) => ({ id: r.id, rawCitation: r.rawCitation })),
832
+ getReferences: () => refs.map((r) => ({ id: r.id, rawReference: r.rawReference })),
787
833
  };
788
834
  adapter.render(container, items.length > 0 ? items : [{ id: '' }], context);
789
835
  }
@@ -830,28 +876,17 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
830
876
  const attrs = this.getEffectiveAttrs();
831
877
  const alt = typeof attrs.alt === 'string' ? attrs.alt : '';
832
878
  return html `
833
- <div class="selection-editor-container selection-editor-figure" role="form" aria-label="Edit figure" @keydown=${this.handleGenericKeydown}>
879
+ <div class="selection-editor-container selection-editor-figure" role="form" aria-label=${t('selectionEditor.editFigure')} @keydown=${this.handleGenericKeydown}>
834
880
  ${this.renderSelectionControls()}
881
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
835
882
  <div class="selection-editor-field">
836
- <span class="selection-editor-label">Type:</span>
837
- <code class="selection-editor-value">figure</code>
838
- </div>
839
- ${this.elementInfo?.id
840
- ? html `
841
- <div class="selection-editor-field">
842
- <span class="selection-editor-label">ID:</span>
843
- <code class="selection-editor-value">${this.elementInfo.id}</code>
844
- </div>
845
- `
846
- : ''}
847
- <div class="selection-editor-field">
848
- <label class="selection-editor-label" for="figure-alt">Alt text</label>
883
+ <label class="selection-editor-label" for="figure-alt">${t('selectionEditor.altText')}</label>
849
884
  <input
850
885
  type="text"
851
886
  id="figure-alt"
852
887
  class="selection-editor-input"
853
888
  .value=${alt}
854
- placeholder="Describe the image for accessibility"
889
+ placeholder=${t('selectionEditor.altTextPlaceholder')}
855
890
  @change=${(e) => this.handleDraftAttrChange(e, 'alt')}
856
891
  />
857
892
  </div>
@@ -870,21 +905,18 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
870
905
  <div
871
906
  class="selection-editor-container selection-editor-math"
872
907
  role="form"
873
- aria-label="Edit equation"
908
+ aria-label=${t('selectionEditor.editEquation')}
874
909
  @keydown=${this.handleMathKeydown}
875
910
  >
876
911
  ${this.renderSelectionControls()}
912
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
877
913
  <div class="selection-editor-field">
878
- <span class="selection-editor-label">Type:</span>
879
- <code class="selection-editor-value">math</code>
880
- </div>
881
- <div class="selection-editor-field">
882
- <label class="selection-editor-label" for="math-tex">TeX</label>
914
+ <label class="selection-editor-label" for="math-tex">${t('selectionEditor.tex')}</label>
883
915
  <textarea
884
916
  id="math-tex"
885
917
  class="selection-editor-textarea"
886
918
  .value=${d.tex}
887
- placeholder="e.g. x^2 + y^2 = z^2"
919
+ placeholder=${t('selectionEditor.texPlaceholder')}
888
920
  rows="4"
889
921
  aria-describedby=${previewErr ? 'math-preview-error' : previewWarnings.length ? 'math-preview-warnings' : undefined}
890
922
  @input=${(e) => {
@@ -895,7 +927,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
895
927
  ></textarea>
896
928
  </div>
897
929
  <div class="selection-editor-field">
898
- <label class="selection-editor-label" for="math-style">Style</label>
930
+ <label class="selection-editor-label" for="math-style">${t('selectionEditor.style')}</label>
899
931
  <select
900
932
  id="math-style"
901
933
  class="selection-editor-input"
@@ -905,25 +937,25 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
905
937
  this.scheduleMathPreview();
906
938
  }}
907
939
  >
908
- <option value="inline">Inline</option>
909
- <option value="display">Display</option>
940
+ <option value="inline">${t('selectionEditor.inline')}</option>
941
+ <option value="display">${t('selectionEditor.display')}</option>
910
942
  </select>
911
943
  </div>
912
944
  <div class="selection-editor-field">
913
- <label class="selection-editor-label" for="math-label">Label (optional)</label>
945
+ <label class="selection-editor-label" for="math-label">${t('selectionEditor.labelOptional')}</label>
914
946
  <input
915
947
  id="math-label"
916
948
  type="text"
917
949
  class="selection-editor-input"
918
950
  .value=${d.label}
919
- placeholder="eq:mylabel"
951
+ placeholder=${t('selectionEditor.labelPlaceholder')}
920
952
  @input=${(e) => {
921
953
  this.mathDraft = { ...this.mathDraft, label: e.target.value };
922
954
  }}
923
955
  />
924
956
  </div>
925
957
  <div class="selection-editor-field">
926
- <span class="selection-editor-label">Preview</span>
958
+ <span class="selection-editor-label">${t('selectionEditor.preview')}</span>
927
959
  <div
928
960
  class="selection-editor-math-preview"
929
961
  ${ref((el) => {
@@ -933,7 +965,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
933
965
  }
934
966
  })}
935
967
  role="img"
936
- aria-label="Equation preview"
968
+ aria-label=${t('selectionEditor.equationPreview')}
937
969
  ></div>
938
970
  ${previewErr
939
971
  ? html `<p id="math-preview-error" class="selection-editor-math-error" role="alert">${previewErr}</p>`
@@ -952,9 +984,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
952
984
  class="selection-editor-button selection-editor-button--primary"
953
985
  @click=${this.applyMathDraft}
954
986
  >
955
- Apply
987
+ ${t('selectionEditor.apply')}
956
988
  </button>
957
- <span class="selection-editor-math-hint">Ctrl+Enter to apply, Escape to cancel</span>
989
+ <span class="selection-editor-math-hint">${t('selectionEditor.mathHint')}</span>
958
990
  </div>
959
991
  <p class="selection-editor-math-docs">
960
992
  <a
@@ -962,7 +994,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
962
994
  target="_blank"
963
995
  rel="noopener noreferrer"
964
996
  class="selection-editor-math-docs-link"
965
- >MathJax 4 docs</a>
997
+ >${t('selectionEditor.mathDocsLink')}</a>
966
998
  </p>
967
999
  </div>
968
1000
  `;
@@ -1056,21 +1088,18 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1056
1088
  <div
1057
1089
  class="selection-editor-container selection-editor-citation"
1058
1090
  role="form"
1059
- aria-label="Edit citation"
1091
+ aria-label=${t('selectionEditor.editCitation')}
1060
1092
  @keydown=${this.handleCitationKeydown}
1061
1093
  >
1062
1094
  ${this.renderSelectionControls()}
1095
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
1063
1096
  <div class="selection-editor-field">
1064
- <span class="selection-editor-label">Type:</span>
1065
- <code class="selection-editor-value">citation</code>
1066
- </div>
1067
- <div class="selection-editor-field">
1068
- <label class="selection-editor-label" for="citation-content">Raw citation text</label>
1097
+ <label class="selection-editor-label" for="citation-content">${t('selectionEditor.inTextCitation')}</label>
1069
1098
  <div
1070
1099
  id="citation-content"
1071
1100
  class="selection-editor-input selection-editor-citation-content selection-editor-citation-pm"
1072
1101
  role="textbox"
1073
- aria-label="Raw citation text (supports bold and italic)"
1102
+ aria-label=${t('selectionEditor.inTextCitationAriaLabel')}
1074
1103
  ${ref((el) => {
1075
1104
  if (el) {
1076
1105
  this.mountCitationContentEditor(el);
@@ -1082,7 +1111,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1082
1111
  ></div>
1083
1112
  </div>
1084
1113
  <div class="selection-editor-attr-field">
1085
- <span class="selection-editor-label">Semantic reference:</span>
1114
+ <span class="selection-editor-label">${t('selectionEditor.semanticReference')}</span>
1086
1115
  <div
1087
1116
  class="citation-source-adapter-root"
1088
1117
  ${ref((el) => {
@@ -1103,9 +1132,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1103
1132
  class="selection-editor-button selection-editor-button--primary"
1104
1133
  @click=${this.applyCitationDraft}
1105
1134
  >
1106
- Update
1135
+ ${t('selectionEditor.update')}
1107
1136
  </button>
1108
- <span class="selection-editor-citation-hint">Ctrl+Enter to apply</span>
1137
+ <span class="selection-editor-citation-hint">${t('selectionEditor.updateHint')}</span>
1109
1138
  </div>
1110
1139
  </div>
1111
1140
  `;
@@ -1150,22 +1179,19 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1150
1179
  <div
1151
1180
  class="selection-editor-container selection-editor-citation"
1152
1181
  role="form"
1153
- aria-label="Edit cross-reference"
1182
+ aria-label=${t('selectionEditor.editCrossReference')}
1154
1183
  @keydown=${this.handleCrossReferenceKeydown}
1155
1184
  >
1156
1185
  ${this.renderSelectionControls()}
1186
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel(this.elementInfo.type)}</p>
1157
1187
  <div class="selection-editor-field">
1158
- <span class="selection-editor-label">Type:</span>
1159
- <code class="selection-editor-value">cross-reference</code>
1160
- </div>
1161
- <div class="selection-editor-field">
1162
- <label class="selection-editor-label" for="crossref-content">Display text</label>
1188
+ <label class="selection-editor-label" for="crossref-content">${t('selectionEditor.displayText')}</label>
1163
1189
  <input
1164
1190
  id="crossref-content"
1165
1191
  type="text"
1166
1192
  class="selection-editor-input"
1167
1193
  .value=${d.content}
1168
- placeholder="e.g. Chapter 1, Figure 1, Abstract"
1194
+ placeholder=${t('selectionEditor.displayTextPlaceholder')}
1169
1195
  @input=${(e) => {
1170
1196
  this.crossReferenceDraft = {
1171
1197
  ...this.crossReferenceDraft,
@@ -1175,7 +1201,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1175
1201
  />
1176
1202
  </div>
1177
1203
  <div class="selection-editor-field">
1178
- <label class="selection-editor-label" for="crossref-target">Target</label>
1204
+ <label class="selection-editor-label" for="crossref-target">${t('selectionEditor.target')}</label>
1179
1205
  <select
1180
1206
  id="crossref-target"
1181
1207
  class="selection-editor-input selection-editor-select"
@@ -1199,9 +1225,9 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1199
1225
  class="selection-editor-button selection-editor-button--primary"
1200
1226
  @click=${this.applyCrossReferenceDraft}
1201
1227
  >
1202
- Update
1228
+ ${t('selectionEditor.update')}
1203
1229
  </button>
1204
- <span class="selection-editor-citation-hint">Ctrl+Enter to apply</span>
1230
+ <span class="selection-editor-citation-hint">${t('selectionEditor.updateHint')}</span>
1205
1231
  </div>
1206
1232
  </div>
1207
1233
  `;
@@ -1234,6 +1260,117 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1234
1260
  this.applyCrossReferenceDraft();
1235
1261
  }
1236
1262
  }
1263
+ /**
1264
+ * Render the hyperlink (anchor mark) sidebar: URL input, Apply and Remove buttons.
1265
+ */
1266
+ renderHyperlinkSidebar() {
1267
+ const d = this.hyperlinkDraft;
1268
+ return html `
1269
+ <div
1270
+ class="selection-editor-container selection-editor-citation"
1271
+ role="form"
1272
+ aria-label=${t('selectionEditor.editHyperlink')}
1273
+ @keydown=${this.handleHyperlinkKeydown}
1274
+ >
1275
+ ${this.renderSelectionControls()}
1276
+ <p class="selection-editor-type-header">${this.getNodeTypeLabel('anchor')}</p>
1277
+ <div class="selection-editor-field">
1278
+ <label class="selection-editor-label" for="hyperlink-href">${t('selectionEditor.url')}</label>
1279
+ <input
1280
+ id="hyperlink-href"
1281
+ type="url"
1282
+ class="selection-editor-input"
1283
+ .value=${d.href}
1284
+ placeholder=${t('selectionEditor.urlPlaceholder')}
1285
+ @input=${(e) => {
1286
+ this.hyperlinkDraft = { href: e.target.value };
1287
+ }}
1288
+ />
1289
+ </div>
1290
+ <div class="selection-editor-citation-actions">
1291
+ <button
1292
+ type="button"
1293
+ class="selection-editor-button selection-editor-button--primary"
1294
+ @click=${this.applyHyperlinkDraft}
1295
+ >
1296
+ ${t('selectionEditor.update')}
1297
+ </button>
1298
+ <button
1299
+ type="button"
1300
+ class="selection-editor-button"
1301
+ @click=${this.removeHyperlink}
1302
+ >
1303
+ ${t('selectionEditor.removeLink')}
1304
+ </button>
1305
+ <span class="selection-editor-citation-hint">${t('selectionEditor.updateHint')}</span>
1306
+ </div>
1307
+ </div>
1308
+ `;
1309
+ }
1310
+ applyHyperlinkDraft() {
1311
+ const view = this.resolvedEditor?.editorView;
1312
+ if (!view || !this.hyperlinkDraft)
1313
+ return;
1314
+ const { state } = view;
1315
+ const anchorMarkType = state.schema.marks['anchor'];
1316
+ if (!anchorMarkType)
1317
+ return;
1318
+ const newMark = anchorMarkType.create({ href: this.hyperlinkDraft.href, title: null, id: null });
1319
+ // Find a document position known to be inside the anchor mark so that
1320
+ // getMarkRange can resolve the full extent, regardless of where the
1321
+ // selection boundaries fall relative to the marked text.
1322
+ const { from, to, empty } = state.selection;
1323
+ let posInsideMark = null;
1324
+ if (!empty) {
1325
+ state.doc.nodesBetween(from, to, (node, pos) => {
1326
+ if (posInsideMark === null && node.isText && node.marks.some((m) => m.type === anchorMarkType)) {
1327
+ posInsideMark = pos + 1; // step into the text node
1328
+ }
1329
+ return posInsideMark === null;
1330
+ });
1331
+ }
1332
+ const resolvedPos = posInsideMark !== null
1333
+ ? state.doc.resolve(posInsideMark)
1334
+ : state.selection.$from;
1335
+ const range = getMarkRange(resolvedPos, anchorMarkType, null);
1336
+ if (!range) {
1337
+ // Mark exists only in storedMarks; update it in place.
1338
+ view.dispatch(state.tr.addStoredMark(newMark));
1339
+ return;
1340
+ }
1341
+ const tr = state.tr
1342
+ .removeMark(range.from, range.to, anchorMarkType)
1343
+ .addMark(range.from, range.to, newMark);
1344
+ view.dispatch(tr);
1345
+ }
1346
+ removeHyperlink() {
1347
+ const view = this.resolvedEditor?.editorView;
1348
+ if (!view)
1349
+ return;
1350
+ const { state } = view;
1351
+ const anchorMarkType = state.schema.marks['anchor'];
1352
+ if (!anchorMarkType)
1353
+ return;
1354
+ const { from, to, empty } = state.selection;
1355
+ if (empty) {
1356
+ const range = getMarkRange(state.selection.$from, anchorMarkType, null);
1357
+ if (!range) {
1358
+ // Mark exists only in storedMarks; clear it.
1359
+ view.dispatch(state.tr.removeStoredMark(anchorMarkType));
1360
+ return;
1361
+ }
1362
+ view.dispatch(state.tr.removeMark(range.from, range.to, anchorMarkType));
1363
+ }
1364
+ else {
1365
+ view.dispatch(state.tr.removeMark(from, to, anchorMarkType));
1366
+ }
1367
+ }
1368
+ handleHyperlinkKeydown(e) {
1369
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
1370
+ e.preventDefault();
1371
+ this.applyHyperlinkDraft();
1372
+ }
1373
+ }
1237
1374
  resetDraftAttrs() {
1238
1375
  this.draftAttrs = null;
1239
1376
  this.draftDirty = false;
@@ -1291,16 +1428,16 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1291
1428
  class="selection-editor-button selection-editor-button--primary"
1292
1429
  @click=${this.applyDraftAttrs}
1293
1430
  >
1294
- Apply
1431
+ ${t('selectionEditor.apply')}
1295
1432
  </button>
1296
1433
  <button
1297
1434
  type="button"
1298
1435
  class="selection-editor-button"
1299
1436
  @click=${this.revertDraftAttrs}
1300
1437
  >
1301
- Revert
1438
+ ${t('selectionEditor.revert')}
1302
1439
  </button>
1303
- <span class="selection-editor-hint">Ctrl+Enter to apply, Escape to revert</span>
1440
+ <span class="selection-editor-hint">${t('selectionEditor.draftHint')}</span>
1304
1441
  </div>
1305
1442
  `;
1306
1443
  }
@@ -1313,7 +1450,7 @@ let SciFlowSelectionEditorElement = class SciFlowSelectionEditorElement extends
1313
1450
  @click=${this.togglePinSelection}
1314
1451
  aria-pressed=${this.pinSelection ? 'true' : 'false'}
1315
1452
  >
1316
- ${this.pinSelection ? 'Unpin selection' : 'Pin selection'}
1453
+ ${this.pinSelection ? t('selectionEditor.unpinSelection') : t('selectionEditor.pinSelection')}
1317
1454
  </button>
1318
1455
  </div>
1319
1456
  `;
@@ -1387,6 +1524,9 @@ __decorate([
1387
1524
  __decorate([
1388
1525
  state()
1389
1526
  ], SciFlowSelectionEditorElement.prototype, "crossReferenceDraft", void 0);
1527
+ __decorate([
1528
+ state()
1529
+ ], SciFlowSelectionEditorElement.prototype, "hyperlinkDraft", void 0);
1390
1530
  __decorate([
1391
1531
  state()
1392
1532
  ], SciFlowSelectionEditorElement.prototype, "draftAttrs", void 0);
@@ -1396,6 +1536,9 @@ __decorate([
1396
1536
  __decorate([
1397
1537
  state()
1398
1538
  ], SciFlowSelectionEditorElement.prototype, "pinSelection", void 0);
1539
+ __decorate([
1540
+ state()
1541
+ ], SciFlowSelectionEditorElement.prototype, "showAllAttrs", void 0);
1399
1542
  SciFlowSelectionEditorElement = __decorate([
1400
1543
  customElement('sciflow-selection-editor')
1401
1544
  ], SciFlowSelectionEditorElement);