@bitcobblers/wod-wiki-ui 0.35.2116 → 0.35.2119

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,6 +7,7 @@ var state = require('@codemirror/state');
7
7
  var lint = require('@codemirror/lint');
8
8
  var wodWikiLang = require('@bitcobblers/wod-wiki-lang');
9
9
  var wodWikiCore = require('@bitcobblers/wod-wiki-core');
10
+ var commands = require('@codemirror/commands');
10
11
  var wodWikiWql = require('@bitcobblers/wod-wiki-wql');
11
12
  var autocomplete = require('@codemirror/autocomplete');
12
13
  var client = require('react-dom/client');
@@ -18,7 +19,6 @@ var tailwindMerge = require('tailwind-merge');
18
19
  var recharts = require('recharts');
19
20
  var reactDom = require('react-dom');
20
21
  var server = require('react-dom/server');
21
- var commands = require('@codemirror/commands');
22
22
  var solid = require('@heroicons/react/20/solid');
23
23
  var common = require('@lezer/common');
24
24
  var lr = require('@lezer/lr');
@@ -386,255 +386,202 @@ var linkOpen = [
386
386
  ctrlClickPlugin,
387
387
  linkCursorTheme
388
388
  ];
389
- var VALID_DIALECTS = ["time", "log"];
389
+ var sectionIdentity = state.Facet.define({ combine: (values2) => values2[0] });
390
+ var registeredSectionWidgets = state.Facet.define({ combine: (values2) => values2.length ? values2.flat() : void 0 });
390
391
  var forceSectionParse = state.StateEffect.define();
391
- function generateSectionId(type, cmStartLine, content) {
392
- return wodWikiCore.generateSectionId(type, cmStartLine - 1, content);
393
- }
394
- function mapIdentities(oldSections, newSections) {
395
- return newSections.map((nSec) => {
396
- const match = oldSections.find(
397
- (oSec) => oSec.type === nSec.type && Math.abs(oSec.startLine - nSec.startLine) <= 2
398
- );
399
- return match ? { ...nSec, id: match.id } : nSec;
400
- });
401
- }
402
- function matchDialectFence(trimmed) {
403
- const match = wodWikiCore.matchDialectFence(trimmed);
404
- if (!match) return null;
405
- return { dialect: match.dialect, sport: match.sport };
406
- }
407
- function matchWidgetFence(trimmed) {
408
- const match = trimmed.match(/^```\s*widget:([\w-]+)\s*$/);
409
- return match ? match[1] : null;
410
- }
411
- function matchContentFence(trimmed) {
412
- const match = trimmed.match(/^```\s*query(?:[:\s](.*))?$/i);
413
- if (!match) return null;
414
- const rawSuffix = (match[1] ?? "").trim();
415
- if (rawSuffix === "") return { kind: "query" };
416
- const parsed = wodWikiWql.parseQueryWidgetSuffix(rawSuffix.replace(/^:/, ""));
417
- return {
418
- kind: "query",
419
- widgetType: parsed.type,
420
- widgetError: parsed.error
421
- };
422
- }
423
- function matchGenericFence(trimmed) {
424
- const match = trimmed.match(/^```\s*(\S+)\s*$/);
425
- if (!match) return null;
426
- const tag = match[1].toLowerCase();
427
- if (VALID_DIALECTS.includes(tag)) return null;
428
- if (tag.startsWith("widget:")) return null;
429
- if (tag.startsWith("query")) return null;
430
- return match[1];
431
- }
432
- function matchMarkdownEmbed(trimmed) {
433
- const imgMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
434
- if (imgMatch) {
435
- const label = imgMatch[1];
436
- const url = imgMatch[2];
437
- return { type: "image", url, label };
438
- }
439
- const linkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
440
- if (linkMatch) {
441
- const label = linkMatch[1];
442
- const url = linkMatch[2];
443
- return { type: "link", url, label };
392
+ var sectionSaved = state.StateEffect.define();
393
+ var sectionRestore = state.StateEffect.define();
394
+ var sectionMove = state.StateEffect.define();
395
+ var restoreHistoryIdentity = state.StateEffect.define({
396
+ map(value, changes) {
397
+ const blocks = {};
398
+ for (const block of Object.values(value.blocks)) {
399
+ const from = changes.mapPos(block.from, 1);
400
+ const to = changes.mapPos(block.to, -1);
401
+ if (from < to) blocks[block.id] = { ...block, from, to };
402
+ }
403
+ return { rawContent: value.rawContent, blocks };
444
404
  }
445
- return null;
405
+ });
406
+ function sourceCoordinates(rawContent) {
407
+ const rawStarts = [0];
408
+ const editorStarts = [0];
409
+ let removed = 0;
410
+ for (const match of rawContent.matchAll(/\r\n|\r|\n/g)) {
411
+ removed += match[0].length - 1;
412
+ rawStarts.push(match.index + match[0].length);
413
+ editorStarts.push(match.index + match[0].length - removed);
414
+ }
415
+ return { rawStarts, editorStarts };
416
+ }
417
+ function toSourcePosition(state, rawStarts, position) {
418
+ const line = state.doc.lineAt(position);
419
+ return rawStarts[line.number - 1] + position - line.from;
420
+ }
421
+ function toEditorBlocks(state) {
422
+ const blocks = {};
423
+ for (const block of Object.values(state.noteBlocks)) {
424
+ const section = state.sections.find((section2) => section2.id === block.id);
425
+ if (section) blocks[block.id] = { ...block, from: section.from, to: section.to };
426
+ }
427
+ return blocks;
428
+ }
429
+ function deriveState(state, rawContent, sourceRevision, lineage, savedVersions, previousSections = []) {
430
+ const registeredWidgets = state.facet(registeredSectionWidgets);
431
+ const noteBlocks = wodWikiCore.reconcileNoteBlocks(rawContent, { sourceRevision, blocks: lineage, registeredWidgets });
432
+ const coordinates = sourceCoordinates(rawContent);
433
+ const sections = wodWikiCore.parseDocumentSections(rawContent, { registeredWidgets }).map((section) => {
434
+ const start = coordinates.editorStarts[section.startLine];
435
+ const end = coordinates.editorStarts[section.endLine] + section.to - coordinates.rawStarts[section.endLine];
436
+ const block = wodWikiCore.actionableSectionKind(section) ? Object.values(noteBlocks).find((block2) => block2.from === section.from && block2.to === section.to) : void 0;
437
+ const prior = previousSections.find((prior2) => prior2.from === start && prior2.to === end && prior2.type === (section.type === "title" ? "markdown" : section.type));
438
+ const query = section.querySuffix ? wodWikiWql.parseQueryWidgetSuffix(section.querySuffix) : void 0;
439
+ const subtype = section.subtype;
440
+ return {
441
+ id: block?.id ?? prior?.id ?? section.id,
442
+ type: section.type === "title" ? "markdown" : section.type,
443
+ ...subtype === "heading" || subtype === "paragraph" || subtype === "list" || subtype === "blockquote" || subtype === "table" ? { subtype } : {},
444
+ from: start,
445
+ to: end,
446
+ startLine: section.startLine + 1,
447
+ endLine: section.endLine + 1,
448
+ ...section.contentFrom !== void 0 ? { contentFrom: coordinates.editorStarts[section.startLine + 1] ?? end } : {},
449
+ ...section.contentTo !== void 0 ? { contentTo: coordinates.editorStarts[section.endLine] - (section.type === "frontmatter" ? 1 : 0) } : {},
450
+ sport: section.sport,
451
+ widgetName: section.widgetName,
452
+ contentId: section.contentId,
453
+ queryType: query?.type,
454
+ queryError: query?.error,
455
+ embed: section.embed
456
+ };
457
+ });
458
+ for (const block of Object.values(noteBlocks)) block.version = savedVersions[block.id] ?? block.version;
459
+ return { sections, rawContent, sourceRevision, noteBlocks, savedVersions };
446
460
  }
447
- function detectMarkdownSubtype(lines) {
448
- const first = lines.find((l) => l.trim().length > 0)?.trim() ?? "";
449
- if (/^#{1,6}\s/.test(first)) return "heading";
450
- if (/^[-*+]\s|^\d+\.\s/.test(first)) return "list";
451
- if (/^>\s?/.test(first)) return "blockquote";
452
- if (first.startsWith("|") && lines.some((l) => /^\|[\s:]*-+/.test(l.trim()))) return "table";
453
- return "paragraph";
454
- }
455
- function scanFenced(state, openLineNum) {
456
- const openLine = state.doc.line(openLineNum);
457
- const contentFrom = openLine.to + 1;
458
- for (let i = openLineNum + 1; i <= state.doc.lines; i++) {
459
- const l = state.doc.line(i);
460
- if (l.text.trim().startsWith("```")) {
461
- return { closeLine: i, contentFrom, contentTo: l.from };
462
- }
463
- }
464
- const last = state.doc.line(state.doc.lines);
465
- return { closeLine: state.doc.lines, contentFrom, contentTo: last.to };
466
- }
467
- function parseSections(state) {
468
- const sections = [];
469
- let currentMdLines = [];
470
- let mdStartLine = 1;
471
- let mdStartPos = 0;
472
- function flushMarkdown(endLine, endPos) {
473
- if (currentMdLines.length === 0) return;
474
- const content = currentMdLines.join("\n");
475
- if (content.trim().length > 0) {
476
- const embed = currentMdLines.length === 1 ? matchMarkdownEmbed(content.trim()) : null;
477
- if (embed) {
478
- sections.push({
479
- id: generateSectionId("embed", mdStartLine, content),
480
- type: "embed",
481
- from: mdStartPos,
482
- to: endPos,
483
- startLine: mdStartLine,
484
- endLine,
485
- embed
486
- });
487
- } else {
488
- sections.push({
489
- id: generateSectionId("markdown", mdStartLine, content),
490
- type: "markdown",
491
- subtype: detectMarkdownSubtype(currentMdLines),
492
- from: mdStartPos,
493
- to: endPos,
494
- startLine: mdStartLine,
495
- endLine
496
- });
497
- }
498
- }
499
- currentMdLines = [];
500
- }
501
- let lineNum = 1;
502
- while (lineNum <= state.doc.lines) {
503
- const line = state.doc.line(lineNum);
504
- const trimmed = line.text.trim();
505
- const dialectMatch = matchDialectFence(trimmed);
506
- const widgetMatch = matchWidgetFence(trimmed);
507
- const contentMatch = matchContentFence(trimmed);
508
- const genericFence = matchGenericFence(trimmed);
509
- if (dialectMatch || widgetMatch || contentMatch || genericFence) {
510
- flushMarkdown(lineNum - 1, line.from);
511
- const { closeLine, contentFrom, contentTo } = scanFenced(state, lineNum);
512
- const closeLineObj = state.doc.line(closeLine);
513
- const innerContent = state.doc.sliceString(contentFrom, contentTo);
514
- if (dialectMatch) {
515
- sections.push({
516
- id: generateSectionId(dialectMatch.dialect, lineNum, innerContent),
517
- type: dialectMatch.dialect,
518
- from: line.from,
519
- to: closeLineObj.to,
520
- startLine: lineNum,
521
- endLine: closeLine,
522
- contentFrom,
523
- contentTo,
524
- sport: dialectMatch.sport,
525
- contentId: wodWikiCore.blockContentId(innerContent)
526
- });
527
- } else if (widgetMatch) {
528
- sections.push({
529
- id: generateSectionId("widget", lineNum, innerContent),
530
- type: "widget",
531
- widgetName: widgetMatch,
532
- from: line.from,
533
- to: closeLineObj.to,
534
- startLine: lineNum,
535
- endLine: closeLine,
536
- contentFrom,
537
- contentTo
538
- });
539
- } else if (contentMatch) {
540
- sections.push({
541
- id: generateSectionId("query", lineNum, innerContent),
542
- type: "query",
543
- from: line.from,
544
- to: closeLineObj.to,
545
- startLine: lineNum,
546
- endLine: closeLine,
547
- contentFrom,
548
- contentTo,
549
- queryType: contentMatch.widgetType,
550
- queryError: contentMatch.widgetError
551
- });
552
- } else if (genericFence) {
553
- sections.push({
554
- id: generateSectionId("code", lineNum, innerContent),
555
- type: "code",
556
- from: line.from,
557
- to: closeLineObj.to,
558
- startLine: lineNum,
559
- endLine: closeLine,
560
- contentFrom,
561
- contentTo
562
- });
563
- }
564
- lineNum = closeLine + 1;
565
- mdStartLine = lineNum;
566
- mdStartPos = lineNum <= state.doc.lines ? state.doc.line(lineNum).from : state.doc.length;
567
- continue;
568
- }
569
- if (trimmed === "---") {
570
- const lineCount = state.doc.lines;
571
- const nextLineText = lineNum < lineCount ? state.doc.line(lineNum + 1).text.trim() : "";
572
- if (nextLineText !== "") {
573
- let foundClose = false;
574
- let closeLine = lineNum;
575
- let hasKeyValueLine = false;
576
- for (let j = lineNum + 1; j <= lineCount; j++) {
577
- const jTrimmed = state.doc.line(j).text.trim();
578
- if (matchDialectFence(jTrimmed) || matchGenericFence(jTrimmed)) break;
579
- if (jTrimmed === "---") {
580
- closeLine = j;
581
- foundClose = true;
582
- break;
583
- }
584
- if (/^[A-Za-z_][\w-]*\s*:/.test(jTrimmed)) {
585
- hasKeyValueLine = true;
461
+ var sectionField = state.StateField.define({
462
+ create(state) {
463
+ const initial = state.facet(sectionIdentity);
464
+ const rawContent = initial?.rawContent ?? state.doc.toString();
465
+ if (rawContent.replace(/\r\n|\r/g, "\n") !== state.doc.toString()) throw new Error("Editor source and identity map disagree");
466
+ if (initial) wodWikiCore.validateNoteBlocks(rawContent, initial.blocks, initial.sourceRevision, { registeredWidgets: state.facet(registeredSectionWidgets) });
467
+ const savedVersions = Object.fromEntries(Object.values(initial?.blocks ?? {}).map((block) => [block.id, block.version]));
468
+ return deriveState(state, rawContent, initial?.sourceRevision ?? 1, initial?.blocks ?? {}, savedVersions);
469
+ },
470
+ update(value, tr) {
471
+ let rawContent = value.rawContent;
472
+ let lineage = value.noteBlocks;
473
+ let sourceRevision = value.sourceRevision;
474
+ let savedVersions = value.savedVersions;
475
+ if (tr.docChanged) {
476
+ const { rawStarts } = sourceCoordinates(rawContent);
477
+ const changes = [];
478
+ const lineBreak = rawContent.includes("\r\n") ? "\r\n" : rawContent.includes("\r") ? "\r" : "\n";
479
+ tr.changes.iterChanges((fromA, toA, fromB, toB) => changes.push({
480
+ from: toSourcePosition(tr.startState, rawStarts, fromA),
481
+ to: toSourcePosition(tr.startState, rawStarts, toA),
482
+ insert: tr.newDoc.sliceString(fromB, toB, lineBreak)
483
+ }));
484
+ lineage = wodWikiCore.mapNoteBlockChanges(lineage, changes);
485
+ for (let i = changes.length - 1; i >= 0; i--) {
486
+ const change = changes[i];
487
+ rawContent = rawContent.slice(0, change.from) + change.insert + rawContent.slice(change.to);
488
+ }
489
+ if (tr.isUserEvent("move.drop")) {
490
+ const moves = [];
491
+ tr.changes.iterChanges((fromA, toA, fromB, toB) => moves.push({ fromA, toA, fromB, toB }));
492
+ const deletion = moves.filter((change) => change.toA > change.fromA && change.fromB === change.toB);
493
+ const insertion = moves.filter((change) => change.fromA === change.toA && change.toB > change.fromB);
494
+ if (deletion.length === 1 && insertion.length === 1) {
495
+ const removed = deletion[0];
496
+ const added = insertion[0];
497
+ if (tr.startState.doc.sliceString(removed.fromA, removed.toA) === tr.newDoc.sliceString(added.fromB, added.toB)) {
498
+ const currentStarts = sourceCoordinates(rawContent).rawStarts;
499
+ for (const section of value.sections) {
500
+ const block = value.noteBlocks[section.id];
501
+ if (block && section.from >= removed.fromA && section.to <= removed.toA) {
502
+ lineage[block.id] = {
503
+ ...block,
504
+ from: toSourcePosition(tr.state, currentStarts, added.fromB + section.from - removed.fromA),
505
+ to: toSourcePosition(tr.state, currentStarts, added.fromB + section.to - removed.fromA)
506
+ };
507
+ }
508
+ }
586
509
  }
587
510
  }
588
- if (foundClose && hasKeyValueLine) {
589
- flushMarkdown(lineNum - 1, line.from);
590
- const closeLineObj = state.doc.line(closeLine);
591
- const content = state.doc.sliceString(line.from, closeLineObj.to);
592
- sections.push({
593
- id: generateSectionId("frontmatter", lineNum, content),
594
- type: "frontmatter",
595
- from: line.from,
596
- to: closeLineObj.to,
597
- startLine: lineNum,
598
- endLine: closeLine,
599
- contentFrom: line.to + 1,
600
- contentTo: closeLineObj.from - 1
601
- });
602
- lineNum = closeLine + 1;
603
- mdStartLine = lineNum;
604
- mdStartPos = lineNum <= lineCount ? state.doc.line(lineNum).from : state.doc.length;
605
- continue;
511
+ }
512
+ if (tr.isUserEvent("move.line")) {
513
+ const currentStarts = sourceCoordinates(rawContent).rawStarts;
514
+ for (let i = 0; i < tr.startState.selection.ranges.length; i++) {
515
+ const before = tr.startState.selection.ranges[i];
516
+ const after = tr.newSelection.ranges[i];
517
+ if (!after) continue;
518
+ const firstLine = tr.startState.doc.lineAt(before.from);
519
+ const lastLine = tr.startState.doc.lineAt(before.to > before.from && tr.startState.doc.lineAt(before.to).from === before.to ? before.to - 1 : before.to);
520
+ const delta = after.anchor - before.anchor;
521
+ for (const section of value.sections) {
522
+ const block = value.noteBlocks[section.id];
523
+ if (block && section.from >= firstLine.from && section.to <= lastLine.to) {
524
+ lineage[block.id] = {
525
+ ...block,
526
+ from: toSourcePosition(tr.state, currentStarts, section.from + delta),
527
+ to: toSourcePosition(tr.state, currentStarts, section.to + delta)
528
+ };
529
+ }
530
+ }
606
531
  }
607
532
  }
608
533
  }
609
- if (currentMdLines.length === 0) {
610
- mdStartLine = lineNum;
611
- mdStartPos = line.from;
612
- }
613
- currentMdLines.push(line.text);
614
- lineNum++;
615
- }
616
- flushMarkdown(state.doc.lines, state.doc.length);
617
- return sections;
618
- }
619
- var sectionField = state.StateField.define({
620
- create(state) {
621
- return { sections: parseSections(state) };
622
- },
623
- update(value, tr) {
624
- if (tr.docChanged || tr.effects.some((e) => e.is(forceSectionParse))) {
625
- const newSections = parseSections(tr.state);
626
- return { sections: mapIdentities(value.sections, newSections) };
534
+ for (const effect of tr.effects) {
535
+ if (effect.is(sectionSaved)) {
536
+ sourceRevision = Math.max(sourceRevision, effect.value.sourceRevision);
537
+ savedVersions = { ...savedVersions };
538
+ for (const block of Object.values(effect.value.blocks)) savedVersions[block.id] = Math.max(savedVersions[block.id] ?? 1, block.version);
539
+ }
540
+ if (effect.is(sectionRestore)) {
541
+ if (effect.value.rawContent.replace(/\r\n|\r/g, "\n") !== tr.newDoc.toString()) throw new Error("Restored source does not match editor");
542
+ wodWikiCore.validateNoteBlocks(effect.value.rawContent, effect.value.blocks, effect.value.sourceRevision, { registeredWidgets: tr.state.facet(registeredSectionWidgets) });
543
+ rawContent = effect.value.rawContent;
544
+ lineage = effect.value.blocks;
545
+ sourceRevision = effect.value.sourceRevision;
546
+ savedVersions = { ...savedVersions, ...Object.fromEntries(Object.values(lineage).map((block) => [block.id, block.version])) };
547
+ }
548
+ if (effect.is(restoreHistoryIdentity)) {
549
+ const exactHistory = tr.effects.some((candidate) => candidate.is(restoreHistoryIdentity) && candidate.value.rawContent.replace(/\r\n|\r/g, "\n") === tr.newDoc.toString());
550
+ if (exactHistory && effect.value.rawContent.replace(/\r\n|\r/g, "\n") !== tr.newDoc.toString()) continue;
551
+ if (effect.value.rawContent.replace(/\r\n|\r/g, "\n") === tr.newDoc.toString()) rawContent = effect.value.rawContent;
552
+ const { rawStarts } = sourceCoordinates(rawContent);
553
+ lineage = Object.fromEntries(Object.values(effect.value.blocks).map((block) => [block.id, {
554
+ ...block,
555
+ from: toSourcePosition(tr.state, rawStarts, block.from),
556
+ to: toSourcePosition(tr.state, rawStarts, block.to)
557
+ }]));
558
+ }
559
+ if (effect.is(sectionMove)) {
560
+ const prior = value.noteBlocks[effect.value.id];
561
+ if (!prior || lineage[prior.id]) throw new Error("Explicit move must remove the original occurrence");
562
+ const { rawStarts } = sourceCoordinates(rawContent);
563
+ const from = toSourcePosition(tr.state, rawStarts, effect.value.from);
564
+ const to = toSourcePosition(tr.state, rawStarts, effect.value.to);
565
+ if (rawContent.slice(from, to) !== value.rawContent.slice(prior.from, prior.to)) throw new Error("Explicit move changed source");
566
+ lineage = { ...lineage, [prior.id]: { ...prior, from, to } };
567
+ }
568
+ }
569
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(forceSectionParse) || effect.is(sectionRestore) || effect.is(sectionSaved) || effect.is(restoreHistoryIdentity))) {
570
+ const previous = value.sections.map((section) => ({ ...section, from: tr.changes.mapPos(section.from, 1), to: tr.changes.mapPos(section.to, -1) }));
571
+ return deriveState(tr.state, rawContent, sourceRevision, lineage, savedVersions, previous);
627
572
  }
628
573
  return value;
629
574
  }
630
575
  });
576
+ var sectionIdentityHistory = commands.invertedEffects.of((tr) => {
577
+ const value = tr.startState.field(sectionField, false);
578
+ return value && tr.docChanged ? [restoreHistoryIdentity.of({ rawContent: value.rawContent, blocks: toEditorBlocks(value) })] : [];
579
+ });
631
580
  function sectionAtPos(state, pos) {
632
- const { sections } = state.field(sectionField);
633
- return sections.find((s) => pos >= s.from && pos <= s.to) ?? null;
581
+ return state.field(sectionField).sections.find((section) => pos >= section.from && pos <= section.to) ?? null;
634
582
  }
635
583
  function activeCursorSection(state) {
636
- const pos = state.selection.main.head;
637
- return sectionAtPos(state, pos);
584
+ return sectionAtPos(state, state.selection.main.head);
638
585
  }
639
586
 
640
587
  // src/extensions/whiteboard-linter.ts
@@ -2508,18 +2455,9 @@ function dispatchExpandRuntimePanel(view, sectionId, expanded) {
2508
2455
  function sessionQueryWql(resultId) {
2509
2456
  return `:session{result:${resultId}}`;
2510
2457
  }
2511
- function sessionQueryInsert(state, blockId, resultId, runBlock) {
2458
+ function sessionQueryInsert(state, blockId, resultId) {
2512
2459
  const sections = state.field(sectionField).sections;
2513
- let section = sections.find((s) => s.id === blockId);
2514
- if (!section && runBlock?.contentId) {
2515
- section = sections.find((s) => s.contentId === runBlock.contentId);
2516
- }
2517
- if (!section && runBlock?.id) {
2518
- section = sections.find((s) => s.id === runBlock.id);
2519
- }
2520
- if (!section && runBlock) {
2521
- section = sections.find((s) => s.type === "time" || s.type === "log");
2522
- }
2460
+ const section = sections.find((s) => s.id === blockId);
2523
2461
  if (!section || section.type !== "time" && section.type !== "log") {
2524
2462
  return null;
2525
2463
  }
@@ -3220,14 +3158,25 @@ function RowsResultsChrome({
3220
3158
  }
3221
3159
  )
3222
3160
  ] }),
3223
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
3224
- RpeChip,
3225
- {
3226
- resultId,
3227
- events: sessionRun?.events,
3228
- onCapture: handleCapture
3229
- }
3230
- ) })
3161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3162
+ /* @__PURE__ */ jsxRuntime.jsx(
3163
+ "a",
3164
+ {
3165
+ href: `/results/${encodeURIComponent(resultId)}`,
3166
+ className: "text-[11px] font-medium text-primary hover:underline inline-flex items-center gap-0.5",
3167
+ "data-testid": "view-in-results-link",
3168
+ children: "View in Results \u2192"
3169
+ }
3170
+ ),
3171
+ /* @__PURE__ */ jsxRuntime.jsx(
3172
+ RpeChip,
3173
+ {
3174
+ resultId,
3175
+ events: sessionRun?.events,
3176
+ onCapture: handleCapture
3177
+ }
3178
+ )
3179
+ ] })
3231
3180
  ] }),
3232
3181
  /* @__PURE__ */ jsxRuntime.jsx(
3233
3182
  RowsTable,
@@ -4689,7 +4638,7 @@ var STATIC_OPTIONS = {
4689
4638
  unit: UNIT_OPTIONS,
4690
4639
  intensity: wodWikiWql.WQL_INTENSITY_TIERS.map((v) => ({ value: v, label: v }))
4691
4640
  };
4692
- var CANONICAL_BLOCK_TYPES = ["wod", "movement", "workout"];
4641
+ var CANONICAL_BLOCK_TYPES = ["wod", "query", "widget", "embed"];
4693
4642
  function canonicalSuggestionItems(values2) {
4694
4643
  return values2.map((value) => ({ value, label: value.charAt(0).toUpperCase() + value.slice(1) }));
4695
4644
  }
@@ -12204,6 +12153,7 @@ function editorPreset(optionsOrDialect = "markdown") {
12204
12153
  extensions.push(
12205
12154
  markdown({ codeLanguages: resolveCodeLanguage }),
12206
12155
  sectionField,
12156
+ sectionIdentityHistory,
12207
12157
  previewDecorations,
12208
12158
  wodLinter,
12209
12159
  wodAutocompletion,
@@ -12263,12 +12213,18 @@ exports.navigationFacet = navigationFacet;
12263
12213
  exports.previewDecorations = previewDecorations;
12264
12214
  exports.queryBlockPreview = queryBlockPreview;
12265
12215
  exports.registerCustomCompletionSource = registerCustomCompletionSource;
12216
+ exports.registeredSectionWidgets = registeredSectionWidgets;
12266
12217
  exports.removeRuntimePanel = removeRuntimePanel;
12267
12218
  exports.runtimePanelField = runtimePanelField;
12268
12219
  exports.saveBlockQuerySource = saveBlockQuerySource;
12269
12220
  exports.sectionAtPos = sectionAtPos;
12270
12221
  exports.sectionField = sectionField;
12271
12222
  exports.sectionGeometry = sectionGeometry;
12223
+ exports.sectionIdentity = sectionIdentity;
12224
+ exports.sectionIdentityHistory = sectionIdentityHistory;
12225
+ exports.sectionMove = sectionMove;
12226
+ exports.sectionRestore = sectionRestore;
12227
+ exports.sectionSaved = sectionSaved;
12272
12228
  exports.sessionQueryInsert = sessionQueryInsert;
12273
12229
  exports.sessionQueryWql = sessionQueryWql;
12274
12230
  exports.setGutterHighlights = setGutterHighlights;