@lionrapid/core 0.2.1 → 0.3.0-beta.0
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.
- package/dist/browser/auto-translator.d.ts.map +1 -1
- package/dist/browser/auto-translator.js +15 -4
- package/dist/browser/auto-translator.js.map +1 -1
- package/dist/browser/block-extractor.d.ts.map +1 -1
- package/dist/browser/block-extractor.js +6 -0
- package/dist/browser/block-extractor.js.map +1 -1
- package/dist/browser/editor/editor-api.d.ts +31 -1
- package/dist/browser/editor/editor-api.d.ts.map +1 -1
- package/dist/browser/editor/editor-api.js +43 -1
- package/dist/browser/editor/editor-api.js.map +1 -1
- package/dist/browser/editor/editor-init.js +25 -0
- package/dist/browser/editor/editor-init.js.map +1 -1
- package/dist/browser/editor/editor-styles.d.ts +1 -1
- package/dist/browser/editor/editor-styles.d.ts.map +1 -1
- package/dist/browser/editor/editor-styles.js +55 -0
- package/dist/browser/editor/editor-styles.js.map +1 -1
- package/dist/browser/editor/live-preview.d.ts.map +1 -1
- package/dist/browser/editor/live-preview.js +20 -3
- package/dist/browser/editor/live-preview.js.map +1 -1
- package/dist/browser/editor/sidebar.d.ts +20 -1
- package/dist/browser/editor/sidebar.d.ts.map +1 -1
- package/dist/browser/editor/sidebar.js +147 -9
- package/dist/browser/editor/sidebar.js.map +1 -1
- package/dist/browser/lionrapid.auto.dev.js +125 -311
- package/dist/browser/lionrapid.auto.dev.js.map +3 -3
- package/dist/browser/lionrapid.auto.js +125 -311
- package/dist/browser/lionrapid.auto.js.map +3 -3
- package/dist/browser/lionrapid.auto.min.js +3 -3
- package/dist/browser/lionrapid.auto.min.js.map +4 -4
- package/dist/browser/lionrapid.editor.js +274 -181
- package/dist/browser/lionrapid.editor.js.map +3 -3
- package/dist/browser/lionrapid.editor.min.js +58 -3
- package/dist/browser/lionrapid.editor.min.js.map +4 -4
- package/dist/browser/lionrapid.esm.js +108 -294
- package/dist/browser/lionrapid.esm.js.map +3 -3
- package/dist/browser/lionrapid.esm.min.js +2 -2
- package/dist/browser/lionrapid.esm.min.js.map +4 -4
- package/dist/browser/lionrapid.iife.dev.js +108 -294
- package/dist/browser/lionrapid.iife.dev.js.map +3 -3
- package/dist/browser/lionrapid.iife.js +108 -294
- package/dist/browser/lionrapid.iife.js.map +3 -3
- package/dist/browser/lionrapid.iife.min.js +2 -2
- package/dist/browser/lionrapid.iife.min.js.map +4 -4
- package/dist/browser/lionrapid.umd.js +108 -294
- package/dist/browser/lionrapid.umd.js.map +3 -3
- package/dist/browser/lionrapid.umd.min.js +2 -2
- package/dist/browser/lionrapid.umd.min.js.map +4 -4
- package/dist/browser/markup-parser.d.ts +9 -5
- package/dist/browser/markup-parser.d.ts.map +1 -1
- package/dist/browser/markup-parser.js +19 -23
- package/dist/browser/markup-parser.js.map +1 -1
- package/dist/content/canonical-tag.d.ts +34 -0
- package/dist/content/canonical-tag.d.ts.map +1 -0
- package/dist/content/canonical-tag.js +57 -0
- package/dist/content/canonical-tag.js.map +1 -0
- package/dist/content/index.d.ts +1 -4
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +3 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/parser/MarkupParser.d.ts +9 -49
- package/dist/content/parser/MarkupParser.d.ts.map +1 -1
- package/dist/content/parser/MarkupParser.js +37 -157
- package/dist/content/parser/MarkupParser.js.map +1 -1
- package/dist/content/serializer/HTMLSerializer.d.ts +10 -43
- package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -1
- package/dist/content/serializer/HTMLSerializer.js +29 -69
- package/dist/content/serializer/HTMLSerializer.js.map +1 -1
- package/dist/content/types.d.ts +21 -12
- package/dist/content/types.d.ts.map +1 -1
- package/dist/content/validator/ContentValidator.d.ts +2 -2
- package/dist/content/validator/ContentValidator.js +2 -2
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lionRapid.d.ts +1 -1
- package/dist/lionRapid.js +4 -4
- package/dist/lionRapid.js.map +1 -1
- package/dist/repositories/network-repository.d.ts +14 -0
- package/dist/repositories/network-repository.d.ts.map +1 -1
- package/dist/repositories/network-repository.js +26 -4
- package/dist/repositories/network-repository.js.map +1 -1
- package/package.json +1 -1
- package/dist/content/safe-href.d.ts +0 -22
- package/dist/content/safe-href.d.ts.map +0 -1
- package/dist/content/safe-href.js +0 -48
- package/dist/content/safe-href.js.map +0 -1
- package/dist/content/style-mapper/DefaultStyleMapper.d.ts +0 -59
- package/dist/content/style-mapper/DefaultStyleMapper.d.ts.map +0 -1
- package/dist/content/style-mapper/DefaultStyleMapper.js +0 -132
- package/dist/content/style-mapper/DefaultStyleMapper.js.map +0 -1
- package/dist/content/style-mapper/IStyleMapper.d.ts +0 -50
- package/dist/content/style-mapper/IStyleMapper.d.ts.map +0 -1
- package/dist/content/style-mapper/IStyleMapper.js +0 -6
- package/dist/content/style-mapper/IStyleMapper.js.map +0 -1
|
@@ -226,6 +226,60 @@ var LionRapidEditor = (() => {
|
|
|
226
226
|
background: #fef2f2;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
.lr-wf-note {
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
color: #6b7280;
|
|
232
|
+
font-style: italic;
|
|
233
|
+
padding: 4px 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Workflow context strip (per-row 'you are here' indicator at the top of
|
|
237
|
+
the workflow actions block). Width-flexible: stages wrap on narrow sidebars. */
|
|
238
|
+
.lr-wf-strip-host {
|
|
239
|
+
width: 100%;
|
|
240
|
+
}
|
|
241
|
+
.lr-wf-strip {
|
|
242
|
+
display: flex;
|
|
243
|
+
flex-direction: column;
|
|
244
|
+
gap: 4px;
|
|
245
|
+
margin-bottom: 8px;
|
|
246
|
+
padding: 6px 8px;
|
|
247
|
+
background: #f9fafb;
|
|
248
|
+
border: 1px solid #e5e7eb;
|
|
249
|
+
border-radius: 4px;
|
|
250
|
+
}
|
|
251
|
+
.lr-wf-strip-stages {
|
|
252
|
+
display: flex;
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 4px;
|
|
256
|
+
font-size: 11px;
|
|
257
|
+
font-weight: 500;
|
|
258
|
+
}
|
|
259
|
+
.lr-wf-strip-stage {
|
|
260
|
+
padding: 2px 8px;
|
|
261
|
+
border-radius: 3px;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
}
|
|
264
|
+
.lr-wf-strip-stage.is-active {
|
|
265
|
+
background: #2563eb;
|
|
266
|
+
color: white;
|
|
267
|
+
}
|
|
268
|
+
.lr-wf-strip-stage.is-muted {
|
|
269
|
+
background: #e5e7eb;
|
|
270
|
+
color: #9ca3af;
|
|
271
|
+
}
|
|
272
|
+
.lr-wf-strip-sep {
|
|
273
|
+
color: #d1d5db;
|
|
274
|
+
font-size: 12px;
|
|
275
|
+
user-select: none;
|
|
276
|
+
}
|
|
277
|
+
.lr-wf-strip-caption {
|
|
278
|
+
font-size: 11px;
|
|
279
|
+
color: #4b5563;
|
|
280
|
+
font-weight: 500;
|
|
281
|
+
}
|
|
282
|
+
|
|
229
283
|
/* Snapshot viewer sandbox wrapper */
|
|
230
284
|
#__lr_snapshot_root {
|
|
231
285
|
isolation: isolate;
|
|
@@ -470,6 +524,7 @@ var LionRapidEditor = (() => {
|
|
|
470
524
|
.lr-status-dot.ready { background: #22c55e; }
|
|
471
525
|
.lr-status-dot.locked { background: #a855f7; }
|
|
472
526
|
.lr-status-dot.unsaved { background: #f59e0b; }
|
|
527
|
+
.lr-status-dot.readonly { background: #9ca3af; opacity: 0.6; } /* grey + faded \u2014 past my stage */
|
|
473
528
|
|
|
474
529
|
/* \u2500\u2500 Locked textarea \u2500\u2500 */
|
|
475
530
|
.lr-textarea-locked {
|
|
@@ -501,131 +556,47 @@ var LionRapidEditor = (() => {
|
|
|
501
556
|
}
|
|
502
557
|
`;
|
|
503
558
|
|
|
504
|
-
// src/content/
|
|
505
|
-
var
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return
|
|
559
|
+
// src/content/canonical-tag.ts
|
|
560
|
+
var VOID_TAGS = /* @__PURE__ */ new Set([
|
|
561
|
+
"area",
|
|
562
|
+
"base",
|
|
563
|
+
"br",
|
|
564
|
+
"col",
|
|
565
|
+
"embed",
|
|
566
|
+
"hr",
|
|
567
|
+
"img",
|
|
568
|
+
"input",
|
|
569
|
+
"link",
|
|
570
|
+
"meta",
|
|
571
|
+
"param",
|
|
572
|
+
"source",
|
|
573
|
+
"track",
|
|
574
|
+
"wbr"
|
|
575
|
+
]);
|
|
576
|
+
function escapeAttrValue(value) {
|
|
577
|
+
return value.replace(/"/g, """);
|
|
523
578
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
strike: "strikethrough",
|
|
541
|
-
code: "code",
|
|
542
|
-
mark: "highlight",
|
|
543
|
-
sub: "subscript",
|
|
544
|
-
sup: "superscript"
|
|
545
|
-
});
|
|
546
|
-
/**
|
|
547
|
-
* Style → Element mapping (reverse)
|
|
548
|
-
* Uses simple, familiar elements for developer convenience
|
|
549
|
-
* Also accepts element names directly (strong, em, etc.)
|
|
550
|
-
*/
|
|
551
|
-
__publicField(this, "styleToElement", {
|
|
552
|
-
// Semantic style names → simple elements
|
|
553
|
-
bold: "b",
|
|
554
|
-
italic: "i",
|
|
555
|
-
underline: "u",
|
|
556
|
-
strikethrough: "s",
|
|
557
|
-
code: "code",
|
|
558
|
-
highlight: "mark",
|
|
559
|
-
subscript: "sub",
|
|
560
|
-
superscript: "sup",
|
|
561
|
-
// Element names (for servers that store styles as element names)
|
|
562
|
-
strong: "b",
|
|
563
|
-
b: "b",
|
|
564
|
-
em: "i",
|
|
565
|
-
i: "i",
|
|
566
|
-
u: "u",
|
|
567
|
-
s: "s",
|
|
568
|
-
del: "s",
|
|
569
|
-
strike: "s",
|
|
570
|
-
mark: "mark",
|
|
571
|
-
sub: "sub",
|
|
572
|
-
sup: "sup"
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Convert HTML element to style properties
|
|
577
|
-
*
|
|
578
|
-
* @param element - Tag name (e.g., "b", "a", "strong")
|
|
579
|
-
* @param attrs - Element attributes (e.g., { href: "/url" })
|
|
580
|
-
* @returns Array of style strings
|
|
581
|
-
*/
|
|
582
|
-
mapElementToStyles(element, attrs) {
|
|
583
|
-
const styles = [];
|
|
584
|
-
const tag = element.toLowerCase();
|
|
585
|
-
const mappedStyle = this.elementToStyles[tag];
|
|
586
|
-
if (mappedStyle) {
|
|
587
|
-
styles.push(mappedStyle);
|
|
588
|
-
}
|
|
589
|
-
if (tag === "a" && attrs.href) {
|
|
590
|
-
styles.push(`hyperlink:${attrs.href}`);
|
|
591
|
-
}
|
|
592
|
-
return styles;
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Convert style properties to HTML element
|
|
596
|
-
*
|
|
597
|
-
* Priority order:
|
|
598
|
-
* 1. Hyperlink (becomes wrapper element with href)
|
|
599
|
-
* 2. First recognized style property
|
|
600
|
-
* 3. Fallback to span
|
|
601
|
-
*
|
|
602
|
-
* @param styles - Array of style strings
|
|
603
|
-
* @returns Element info for serialization
|
|
604
|
-
*/
|
|
605
|
-
mapStylesToElement(styles) {
|
|
606
|
-
for (const style of styles) {
|
|
607
|
-
if (style.startsWith("hyperlink:")) {
|
|
608
|
-
return {
|
|
609
|
-
element: "a",
|
|
610
|
-
attrs: { href: safeHref(style.slice("hyperlink:".length)) }
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
for (const style of styles) {
|
|
615
|
-
const element = this.styleToElement[style];
|
|
616
|
-
if (element) {
|
|
617
|
-
return {
|
|
618
|
-
element,
|
|
619
|
-
attrs: {}
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return { element: "span", attrs: {} };
|
|
579
|
+
function serializeOpeningTag(tagName, attrs) {
|
|
580
|
+
const tag = tagName.toLowerCase();
|
|
581
|
+
let result = `<${tag}`;
|
|
582
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
583
|
+
result += ` ${name.toLowerCase()}="${escapeAttrValue(value)}"`;
|
|
584
|
+
}
|
|
585
|
+
result += ">";
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
function extractTagName(rawOpenTag) {
|
|
589
|
+
const end = rawOpenTag.search(/[\s>]/);
|
|
590
|
+
return rawOpenTag.slice(1, end === -1 ? rawOpenTag.length : end).toLowerCase();
|
|
591
|
+
}
|
|
592
|
+
function readRawStyle(styles) {
|
|
593
|
+
for (const s of styles) {
|
|
594
|
+
if (s.startsWith("raw:")) return s.slice(4);
|
|
624
595
|
}
|
|
625
|
-
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
626
598
|
|
|
627
599
|
// src/browser/markup-parser.ts
|
|
628
|
-
var styleMapper = new DefaultStyleMapper();
|
|
629
600
|
function parseBlockElement(element) {
|
|
630
601
|
const styles = {};
|
|
631
602
|
let tagIndex = 0;
|
|
@@ -643,22 +614,17 @@ var LionRapidEditor = (() => {
|
|
|
643
614
|
const attr = el.attributes[i];
|
|
644
615
|
attrs[attr.name.toLowerCase()] = attr.value;
|
|
645
616
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
let inner = "";
|
|
652
|
-
for (let i = 0; i < node.childNodes.length; i++) {
|
|
653
|
-
inner += walk(node.childNodes[i]);
|
|
654
|
-
}
|
|
655
|
-
return `<${idx}>${inner}</${idx}>`;
|
|
617
|
+
tagIndex++;
|
|
618
|
+
const idx = tagIndex;
|
|
619
|
+
styles[String(idx)] = [`raw:${serializeOpeningTag(tag, attrs)}`];
|
|
620
|
+
if (VOID_TAGS.has(tag)) {
|
|
621
|
+
return `<${idx}/>`;
|
|
656
622
|
}
|
|
657
|
-
let
|
|
623
|
+
let inner = "";
|
|
658
624
|
for (let i = 0; i < node.childNodes.length; i++) {
|
|
659
|
-
|
|
625
|
+
inner += walk(node.childNodes[i]);
|
|
660
626
|
}
|
|
661
|
-
return
|
|
627
|
+
return `<${idx}>${inner}</${idx}>`;
|
|
662
628
|
}
|
|
663
629
|
let text = "";
|
|
664
630
|
for (let i = 0; i < element.childNodes.length; i++) {
|
|
@@ -1088,16 +1054,6 @@ var LionRapidEditor = (() => {
|
|
|
1088
1054
|
|
|
1089
1055
|
// src/content/serializer/HTMLSerializer.ts
|
|
1090
1056
|
var HTMLSerializer = class {
|
|
1091
|
-
constructor(options = {}) {
|
|
1092
|
-
__publicField(this, "styleMapper");
|
|
1093
|
-
this.styleMapper = options.styleMapper ?? new DefaultStyleMapper();
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Serialize ContentUnit to HTML string
|
|
1097
|
-
*
|
|
1098
|
-
* @param unit - ContentUnit with indexed tags and styles
|
|
1099
|
-
* @returns HTML string with semantic elements
|
|
1100
|
-
*/
|
|
1101
1057
|
serialize(unit) {
|
|
1102
1058
|
const { text, styles } = unit;
|
|
1103
1059
|
const tagPattern = /<(\/?)?(\d+)(\/)?>/g;
|
|
@@ -1106,40 +1062,25 @@ var LionRapidEditor = (() => {
|
|
|
1106
1062
|
let match;
|
|
1107
1063
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
1108
1064
|
result += text.slice(lastIndex, match.index);
|
|
1109
|
-
const [fullMatch, closingSlash, indexStr
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1112
|
-
if (
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1065
|
+
const [fullMatch, closingSlash, indexStr] = match;
|
|
1066
|
+
const styleList = styles[indexStr] ?? [];
|
|
1067
|
+
const rawOpen = readRawStyle(styleList);
|
|
1068
|
+
if (rawOpen) {
|
|
1069
|
+
const tagName = extractTagName(rawOpen);
|
|
1070
|
+
const isVoid = VOID_TAGS.has(tagName);
|
|
1071
|
+
if (closingSlash) {
|
|
1072
|
+
if (!isVoid) result += `</${tagName}>`;
|
|
1073
|
+
} else {
|
|
1074
|
+
result += rawOpen;
|
|
1075
|
+
}
|
|
1119
1076
|
} else {
|
|
1120
|
-
|
|
1121
|
-
const attrs = this.formatAttributes(mapping.attrs);
|
|
1122
|
-
result += `<${mapping.element}${attrs}>`;
|
|
1077
|
+
result += fullMatch;
|
|
1123
1078
|
}
|
|
1124
1079
|
lastIndex = match.index + fullMatch.length;
|
|
1125
1080
|
}
|
|
1126
1081
|
result += text.slice(lastIndex);
|
|
1127
1082
|
return result;
|
|
1128
1083
|
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Format attributes object into HTML attribute string
|
|
1131
|
-
*/
|
|
1132
|
-
formatAttributes(attrs) {
|
|
1133
|
-
const entries = Object.entries(attrs);
|
|
1134
|
-
if (entries.length === 0) return "";
|
|
1135
|
-
return " " + entries.map(([key, value]) => `${key}="${this.escapeAttributeValue(value)}"`).join(" ");
|
|
1136
|
-
}
|
|
1137
|
-
/**
|
|
1138
|
-
* Escape special characters in attribute values
|
|
1139
|
-
*/
|
|
1140
|
-
escapeAttributeValue(value) {
|
|
1141
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1142
|
-
}
|
|
1143
1084
|
};
|
|
1144
1085
|
|
|
1145
1086
|
// src/browser/editor/live-preview.ts
|
|
@@ -1154,10 +1095,12 @@ var LionRapidEditor = (() => {
|
|
|
1154
1095
|
const fullKey = `${locale}:${namespace}:${key}`;
|
|
1155
1096
|
cache.set(fullKey, text, styles);
|
|
1156
1097
|
}
|
|
1157
|
-
|
|
1098
|
+
const hasIndexedMarkers = /<\/?[0-9]+\/?>/.test(text);
|
|
1099
|
+
const sourceHasStyles = styles && Object.keys(styles).length > 0;
|
|
1100
|
+
if (hasIndexedMarkers) {
|
|
1158
1101
|
const unit = { text, styles };
|
|
1159
1102
|
element.innerHTML = htmlSerializer.serialize(unit);
|
|
1160
|
-
} else {
|
|
1103
|
+
} else if (!sourceHasStyles) {
|
|
1161
1104
|
element.textContent = text;
|
|
1162
1105
|
}
|
|
1163
1106
|
element.setAttribute("data-lr-block", key);
|
|
@@ -1239,8 +1182,14 @@ var LionRapidEditor = (() => {
|
|
|
1239
1182
|
// Server already translated
|
|
1240
1183
|
"data-lionrapid-client",
|
|
1241
1184
|
// Client already translated
|
|
1242
|
-
"data-no-translate"
|
|
1185
|
+
"data-no-translate",
|
|
1243
1186
|
// Explicitly marked to skip
|
|
1187
|
+
// Edge proxy already rendered this block with the correct translation
|
|
1188
|
+
// and styles. Skip in normal extraction so the client-side auto-translator
|
|
1189
|
+
// can't overwrite the styled DOM with a stale (or plain-text) cache entry.
|
|
1190
|
+
// The editor passes includeTranslated=true to bypass this and re-extract
|
|
1191
|
+
// edge-rendered blocks for inspection.
|
|
1192
|
+
"data-lr-block"
|
|
1244
1193
|
];
|
|
1245
1194
|
var BlockExtractor = class {
|
|
1246
1195
|
constructor(excludePatterns = {}, excludeSelectors = [], minTextLength = 2) {
|
|
@@ -1364,6 +1313,10 @@ var LionRapidEditor = (() => {
|
|
|
1364
1313
|
__publicField(this, "lastFetchTime", 0);
|
|
1365
1314
|
__publicField(this, "lastFlushTime", 0);
|
|
1366
1315
|
__publicField(this, "userRole");
|
|
1316
|
+
// Project workflow context — fetched once on first render, cached for the
|
|
1317
|
+
// sidebar's lifetime. Workflow shape is stable per editor session.
|
|
1318
|
+
__publicField(this, "workflowInfo", null);
|
|
1319
|
+
__publicField(this, "workflowInfoFetched", false);
|
|
1367
1320
|
this.api = api;
|
|
1368
1321
|
this.locale = locale;
|
|
1369
1322
|
this.namespace = namespace;
|
|
@@ -1565,9 +1518,14 @@ var LionRapidEditor = (() => {
|
|
|
1565
1518
|
const isReady = !!status?.isReady;
|
|
1566
1519
|
const isLocked = !!status?.isLocked;
|
|
1567
1520
|
const isConfirmed = !!status?.isConfirmed;
|
|
1521
|
+
const canEdit = status?.canEdit !== false;
|
|
1568
1522
|
const composite = `${this.locale}:${this.namespace}:${key}`;
|
|
1569
1523
|
const wrap = document.createElement("div");
|
|
1570
1524
|
wrap.className = "lr-workflow-actions";
|
|
1525
|
+
const stripHost = document.createElement("div");
|
|
1526
|
+
stripHost.className = "lr-wf-strip-host";
|
|
1527
|
+
wrap.appendChild(stripHost);
|
|
1528
|
+
this.renderWorkflowStrip(stripHost, status);
|
|
1571
1529
|
const addBtn = (label, action, variant = "secondary") => {
|
|
1572
1530
|
const btn = document.createElement("button");
|
|
1573
1531
|
btn.className = `lr-wf-btn lr-wf-${variant}`;
|
|
@@ -1582,12 +1540,17 @@ var LionRapidEditor = (() => {
|
|
|
1582
1540
|
});
|
|
1583
1541
|
wrap.appendChild(btn);
|
|
1584
1542
|
};
|
|
1585
|
-
if (!isLocked && !isReady) {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1543
|
+
if (canEdit && !isLocked && !isReady) {
|
|
1544
|
+
if (!isConfirmed) {
|
|
1545
|
+
addBtn("Confirm", "confirm", "primary");
|
|
1546
|
+
addBtn("Reject", "reject", "danger");
|
|
1547
|
+
} else {
|
|
1548
|
+
const isPerFile = this.workflowInfo?.workflowMode === "per_file";
|
|
1549
|
+
if (!isPerFile) {
|
|
1550
|
+
addBtn("Advance", "advance", "primary");
|
|
1551
|
+
}
|
|
1552
|
+
addBtn("Unconfirm", "unconfirm");
|
|
1553
|
+
}
|
|
1591
1554
|
}
|
|
1592
1555
|
if (isPM) {
|
|
1593
1556
|
if (!isReady) {
|
|
@@ -1596,10 +1559,88 @@ var LionRapidEditor = (() => {
|
|
|
1596
1559
|
addBtn("Unmark Ready", "unmark-ready", "danger");
|
|
1597
1560
|
}
|
|
1598
1561
|
}
|
|
1562
|
+
if (!canEdit && !isPM && !isReady && !isLocked) {
|
|
1563
|
+
const note = document.createElement("span");
|
|
1564
|
+
note.className = "lr-wf-note";
|
|
1565
|
+
note.textContent = typeof status?.stageIndex === "number" && status.stageIndex > 0 ? `Row already advanced (stage ${status.stageIndex + 1}) \u2014 out of your scope.` : "You are not assigned to this row at the current stage.";
|
|
1566
|
+
wrap.appendChild(note);
|
|
1567
|
+
}
|
|
1599
1568
|
if (wrap.children.length > 0) {
|
|
1600
1569
|
this.bodyEl.appendChild(wrap);
|
|
1601
1570
|
}
|
|
1602
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Render the workflow context strip ('Workflow: A → B → C — you are at stage X').
|
|
1574
|
+
* Highlights the user's POV stage and greys the rest.
|
|
1575
|
+
*
|
|
1576
|
+
* Highlight rule:
|
|
1577
|
+
* - PM/admin: highlight = row's current stage_index (deep-dive view)
|
|
1578
|
+
* - Non-PM: highlight = first userAssignedStages entry (fixed POV)
|
|
1579
|
+
*
|
|
1580
|
+
* Workflow info is fetched lazily on first call and cached for the sidebar's
|
|
1581
|
+
* lifetime. While the fetch is in flight, the host stays empty (no flash).
|
|
1582
|
+
*/
|
|
1583
|
+
renderWorkflowStrip(host, status) {
|
|
1584
|
+
if (this.workflowInfo) {
|
|
1585
|
+
const node = this.buildWorkflowStripNode(this.workflowInfo, status);
|
|
1586
|
+
host.replaceChildren(...node ? [node] : []);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (this.workflowInfoFetched) return;
|
|
1590
|
+
this.workflowInfoFetched = true;
|
|
1591
|
+
void this.api.fetchWorkflowInfo().then((info) => {
|
|
1592
|
+
this.workflowInfo = info;
|
|
1593
|
+
if (!this.current) return;
|
|
1594
|
+
const fresh = this.statusMap.get(this.current.key) ?? status ?? null;
|
|
1595
|
+
const node = info ? this.buildWorkflowStripNode(info, fresh) : null;
|
|
1596
|
+
if (host.isConnected) host.replaceChildren(...node ? [node] : []);
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
buildWorkflowStripNode(info, status) {
|
|
1600
|
+
if (!info.stages || info.stages.length === 0) return null;
|
|
1601
|
+
let highlightIndex = null;
|
|
1602
|
+
if (info.isPmAdmin) {
|
|
1603
|
+
highlightIndex = typeof status?.stageIndex === "number" ? status.stageIndex : null;
|
|
1604
|
+
} else if (info.userAssignedStages.length > 0) {
|
|
1605
|
+
highlightIndex = info.userAssignedStages[0];
|
|
1606
|
+
}
|
|
1607
|
+
const strip = document.createElement("div");
|
|
1608
|
+
strip.className = "lr-wf-strip";
|
|
1609
|
+
const stagesRow = document.createElement("div");
|
|
1610
|
+
stagesRow.className = "lr-wf-strip-stages";
|
|
1611
|
+
info.stages.forEach((stage, i) => {
|
|
1612
|
+
const item = document.createElement("span");
|
|
1613
|
+
item.className = "lr-wf-strip-stage";
|
|
1614
|
+
if (highlightIndex !== null && stage.index === highlightIndex) {
|
|
1615
|
+
item.classList.add("is-active");
|
|
1616
|
+
} else {
|
|
1617
|
+
item.classList.add("is-muted");
|
|
1618
|
+
}
|
|
1619
|
+
item.textContent = stage.name;
|
|
1620
|
+
stagesRow.appendChild(item);
|
|
1621
|
+
if (i < info.stages.length - 1) {
|
|
1622
|
+
const sep = document.createElement("span");
|
|
1623
|
+
sep.className = "lr-wf-strip-sep";
|
|
1624
|
+
sep.textContent = "\u2192";
|
|
1625
|
+
stagesRow.appendChild(sep);
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
strip.appendChild(stagesRow);
|
|
1629
|
+
const caption = document.createElement("div");
|
|
1630
|
+
caption.className = "lr-wf-strip-caption";
|
|
1631
|
+
if (highlightIndex !== null) {
|
|
1632
|
+
const stage = info.stages.find((s) => s.index === highlightIndex);
|
|
1633
|
+
const name = stage?.name ?? `Stage ${highlightIndex + 1}`;
|
|
1634
|
+
const label = info.isPmAdmin ? "Row currently at" : "You are here";
|
|
1635
|
+
caption.textContent = `${label}: stage ${highlightIndex + 1} of ${info.stages.length} \u2014 ${name}`;
|
|
1636
|
+
} else if (!info.isPmAdmin && info.userAssignedStages.length === 0) {
|
|
1637
|
+
caption.textContent = "You have no stage assignment on this project.";
|
|
1638
|
+
} else {
|
|
1639
|
+
caption.textContent = `${info.stages.length} stages`;
|
|
1640
|
+
}
|
|
1641
|
+
strip.appendChild(caption);
|
|
1642
|
+
return strip;
|
|
1643
|
+
}
|
|
1603
1644
|
/** Re-fetch status for one key and re-render the current view. */
|
|
1604
1645
|
async refreshStatusForKey(key) {
|
|
1605
1646
|
const fresh = await this.api.fetchStatus([key], this.locale, this.namespace);
|
|
@@ -1622,13 +1663,18 @@ var LionRapidEditor = (() => {
|
|
|
1622
1663
|
this.bodyEl.appendChild(field);
|
|
1623
1664
|
}
|
|
1624
1665
|
/**
|
|
1625
|
-
* Get CSS class for the status dot based on actual row state
|
|
1666
|
+
* Get CSS class for the status dot based on actual row state, with one
|
|
1667
|
+
* viewer-relative override: rows that have advanced past the viewer's
|
|
1668
|
+
* stage assignment render as 'readonly' (greyed) — same data for everyone,
|
|
1669
|
+
* different signal per viewer. Avoids the new-color-per-stage explosion the
|
|
1670
|
+
* user was originally considering; reuses existing semantics.
|
|
1626
1671
|
*/
|
|
1627
1672
|
getDotClass(key) {
|
|
1628
1673
|
if (this.unsavedKeys.has(key)) return "unsaved";
|
|
1629
1674
|
const s = this.statusMap.get(key);
|
|
1630
1675
|
if (!s) return "missing";
|
|
1631
1676
|
if (s.isLocked) return "locked";
|
|
1677
|
+
if (s.canEdit === false && typeof s.stageIndex === "number" && s.stageIndex > 0) return "readonly";
|
|
1632
1678
|
if (s.isReady) return "ready";
|
|
1633
1679
|
if (s.isConfirmed) return "confirmed";
|
|
1634
1680
|
if (s.rowState === "edited") return "edited";
|
|
@@ -1735,6 +1781,9 @@ var LionRapidEditor = (() => {
|
|
|
1735
1781
|
__publicField(this, "onFlushed", () => {
|
|
1736
1782
|
});
|
|
1737
1783
|
__publicField(this, "onSessionExpired", null);
|
|
1784
|
+
// Workflow info is project-scoped and stable for the editor session lifetime.
|
|
1785
|
+
// Cached as a Promise so concurrent callers share one fetch.
|
|
1786
|
+
__publicField(this, "workflowInfoPromise", null);
|
|
1738
1787
|
__publicField(this, "statusAbort", null);
|
|
1739
1788
|
__publicField(this, "handleVisibility", () => {
|
|
1740
1789
|
if (document.visibilityState === "hidden") {
|
|
@@ -1873,6 +1922,37 @@ var LionRapidEditor = (() => {
|
|
|
1873
1922
|
get pendingCount() {
|
|
1874
1923
|
return this.queue.length;
|
|
1875
1924
|
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Fetch workflow context for the project (stages, user's stage assignments,
|
|
1927
|
+
* PM/admin flag). Cached for the editor session lifetime — workflow shape
|
|
1928
|
+
* doesn't change mid-session and the sidebar re-uses it on every row click.
|
|
1929
|
+
* Returns null if the fetch fails; callers should hide the strip in that case.
|
|
1930
|
+
*/
|
|
1931
|
+
async fetchWorkflowInfo() {
|
|
1932
|
+
if (this.workflowInfoPromise) return this.workflowInfoPromise;
|
|
1933
|
+
this.workflowInfoPromise = (async () => {
|
|
1934
|
+
try {
|
|
1935
|
+
const response = await fetch(`${this.apiBase}/api/integrations/editor/workflow`, {
|
|
1936
|
+
headers: { "Authorization": `Bearer ${this.sessionToken}` }
|
|
1937
|
+
});
|
|
1938
|
+
if (response.status === 401) {
|
|
1939
|
+
this.handleSessionExpired();
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
if (!response.ok) return null;
|
|
1943
|
+
const data = await response.json();
|
|
1944
|
+
return {
|
|
1945
|
+
stages: Array.isArray(data.stages) ? data.stages : [],
|
|
1946
|
+
userAssignedStages: Array.isArray(data.user_assigned_stages) ? data.user_assigned_stages : [],
|
|
1947
|
+
isPmAdmin: Boolean(data.is_pm_admin),
|
|
1948
|
+
workflowMode: data.workflow_mode === "per_file" ? "per_file" : "per_row"
|
|
1949
|
+
};
|
|
1950
|
+
} catch {
|
|
1951
|
+
return null;
|
|
1952
|
+
}
|
|
1953
|
+
})();
|
|
1954
|
+
return this.workflowInfoPromise;
|
|
1955
|
+
}
|
|
1876
1956
|
// ============================================
|
|
1877
1957
|
// Session renewal
|
|
1878
1958
|
// ============================================
|
|
@@ -1910,6 +1990,7 @@ var LionRapidEditor = (() => {
|
|
|
1910
1990
|
token: this.sessionToken,
|
|
1911
1991
|
exp: payload.exp || 0
|
|
1912
1992
|
}));
|
|
1993
|
+
document.cookie = "__lr_editor_active=1; path=/; SameSite=Lax";
|
|
1913
1994
|
}
|
|
1914
1995
|
} catch {
|
|
1915
1996
|
}
|
|
@@ -1928,6 +2009,7 @@ var LionRapidEditor = (() => {
|
|
|
1928
2009
|
}
|
|
1929
2010
|
}
|
|
1930
2011
|
handleSessionExpired() {
|
|
2012
|
+
document.cookie = "__lr_editor_active=; path=/; SameSite=Lax; Max-Age=0";
|
|
1931
2013
|
this.onStatus("error", "Session expired. Please reopen the editor from the dashboard.");
|
|
1932
2014
|
if (this.onSessionExpired) {
|
|
1933
2015
|
this.onSessionExpired();
|
|
@@ -2369,6 +2451,13 @@ var LionRapidEditor = (() => {
|
|
|
2369
2451
|
|
|
2370
2452
|
// src/browser/editor/editor-init.ts
|
|
2371
2453
|
var SESSION_KEY = "__lr_editor_session";
|
|
2454
|
+
var PREVIEW_COOKIE = "__lr_editor_active";
|
|
2455
|
+
function setPreviewCookie() {
|
|
2456
|
+
document.cookie = `${PREVIEW_COOKIE}=1; path=/; SameSite=Lax`;
|
|
2457
|
+
}
|
|
2458
|
+
function clearPreviewCookie() {
|
|
2459
|
+
document.cookie = `${PREVIEW_COOKIE}=; path=/; SameSite=Lax; Max-Age=0`;
|
|
2460
|
+
}
|
|
2372
2461
|
function getStoredSession() {
|
|
2373
2462
|
try {
|
|
2374
2463
|
const raw = sessionStorage.getItem(SESSION_KEY);
|
|
@@ -2376,8 +2465,10 @@ var LionRapidEditor = (() => {
|
|
|
2376
2465
|
const parsed = JSON.parse(raw);
|
|
2377
2466
|
if (parsed.exp * 1e3 < Date.now() + 3e4) {
|
|
2378
2467
|
sessionStorage.removeItem(SESSION_KEY);
|
|
2468
|
+
clearPreviewCookie();
|
|
2379
2469
|
return null;
|
|
2380
2470
|
}
|
|
2471
|
+
setPreviewCookie();
|
|
2381
2472
|
return parsed.token;
|
|
2382
2473
|
} catch {
|
|
2383
2474
|
return null;
|
|
@@ -2391,11 +2482,13 @@ var LionRapidEditor = (() => {
|
|
|
2391
2482
|
const payload = JSON.parse(atob(parts[1]));
|
|
2392
2483
|
const session = { token, exp: payload.exp || 0 };
|
|
2393
2484
|
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
|
|
2485
|
+
setPreviewCookie();
|
|
2394
2486
|
} catch {
|
|
2395
2487
|
}
|
|
2396
2488
|
}
|
|
2397
2489
|
function clearSession() {
|
|
2398
2490
|
sessionStorage.removeItem(SESSION_KEY);
|
|
2491
|
+
clearPreviewCookie();
|
|
2399
2492
|
}
|
|
2400
2493
|
function extractSessionRole(token) {
|
|
2401
2494
|
try {
|