react-os-shell 4.7.2 → 4.9.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-26KXGB6N.js → Browser-7AQ7Y4Z7.js} +6 -6
- package/dist/{Browser-26KXGB6N.js.map → Browser-7AQ7Y4Z7.js.map} +1 -1
- package/dist/{Calculator-APBKAUZ5.js → Calculator-B7KJQ2NF.js} +5 -5
- package/dist/{Calculator-APBKAUZ5.js.map → Calculator-B7KJQ2NF.js.map} +1 -1
- package/dist/ConfirmDialog-U5H3MP3M.js +4 -0
- package/dist/{ConfirmDialog-LO5WHLMD.js.map → ConfirmDialog-U5H3MP3M.js.map} +1 -1
- package/dist/{CurrencyConverter-YOWLTKY7.js → CurrencyConverter-AILEZUIU.js} +5 -5
- package/dist/{CurrencyConverter-YOWLTKY7.js.map → CurrencyConverter-AILEZUIU.js.map} +1 -1
- package/dist/{Documents-ZYVQKDBZ.js → Documents-IX6JISOV.js} +6 -6
- package/dist/{Documents-ZYVQKDBZ.js.map → Documents-IX6JISOV.js.map} +1 -1
- package/dist/{Files-BBXCHRQ5.js → Files-O7WK4TQG.js} +8 -8
- package/dist/{Files-BBXCHRQ5.js.map → Files-O7WK4TQG.js.map} +1 -1
- package/dist/{Notepad-XDOIP42K.js → Notepad-LO7FRU34.js} +6 -6
- package/dist/{Notepad-XDOIP42K.js.map → Notepad-LO7FRU34.js.map} +1 -1
- package/dist/{PomodoroTimer-NA3LPTTY.js → PomodoroTimer-DSVXJ7D4.js} +6 -6
- package/dist/{PomodoroTimer-NA3LPTTY.js.map → PomodoroTimer-DSVXJ7D4.js.map} +1 -1
- package/dist/{Preview-2A3CTDLY.js → Preview-QCAN5EU3.js} +6 -6
- package/dist/{Preview-2A3CTDLY.js.map → Preview-QCAN5EU3.js.map} +1 -1
- package/dist/{Sidebar-E553SOXC.js → Sidebar-ITLRERXE.js} +6 -5
- package/dist/Sidebar-ITLRERXE.js.map +1 -0
- package/dist/{Spreadsheet-4DTKZJHG.js → Spreadsheet-SUP2H2B4.js} +6 -6
- package/dist/{Spreadsheet-4DTKZJHG.js.map → Spreadsheet-SUP2H2B4.js.map} +1 -1
- package/dist/{Stock-OUVV3A7U.js → Stock-UT2JMEVS.js} +5 -5
- package/dist/{Stock-OUVV3A7U.js.map → Stock-UT2JMEVS.js.map} +1 -1
- package/dist/{Weather-INO44ZWL.js → Weather-37TNIYWI.js} +5 -5
- package/dist/{Weather-INO44ZWL.js.map → Weather-37TNIYWI.js.map} +1 -1
- package/dist/{WorldClock-K5IOWZ62.js → WorldClock-EESGFXPN.js} +5 -5
- package/dist/{WorldClock-K5IOWZ62.js.map → WorldClock-EESGFXPN.js.map} +1 -1
- package/dist/apps/index.js +18 -18
- package/dist/{chunk-ITZB7RDX.js → chunk-BOTXJSDK.js} +3 -3
- package/dist/{chunk-ITZB7RDX.js.map → chunk-BOTXJSDK.js.map} +1 -1
- package/dist/{chunk-CKY62ZRU.js → chunk-G6AUDFI4.js} +4 -4
- package/dist/{chunk-CKY62ZRU.js.map → chunk-G6AUDFI4.js.map} +1 -1
- package/dist/{chunk-QAKURGOG.js → chunk-H5NDQZ4A.js} +3 -3
- package/dist/{chunk-QAKURGOG.js.map → chunk-H5NDQZ4A.js.map} +1 -1
- package/dist/{chunk-LBY7KH2W.js → chunk-HC6N2OXO.js} +3 -3
- package/dist/{chunk-LBY7KH2W.js.map → chunk-HC6N2OXO.js.map} +1 -1
- package/dist/{chunk-3NZSR3ZG.js → chunk-KILLK5UH.js} +3 -3
- package/dist/{chunk-3NZSR3ZG.js.map → chunk-KILLK5UH.js.map} +1 -1
- package/dist/chunk-NQTQPOT4.js +7 -0
- package/dist/{chunk-A24AESZY.js.map → chunk-NQTQPOT4.js.map} +1 -1
- package/dist/{chunk-DKDD5KXG.js → chunk-RVT2A2MO.js} +2 -2
- package/dist/chunk-RVT2A2MO.js.map +1 -0
- package/dist/{chunk-4AQTXUSO.js → chunk-TIKW3XWE.js} +5 -5
- package/dist/{chunk-4AQTXUSO.js.map → chunk-TIKW3XWE.js.map} +1 -1
- package/dist/{chunk-2Y6XUY65.js → chunk-UH3H3KAN.js} +3 -3
- package/dist/{chunk-2Y6XUY65.js.map → chunk-UH3H3KAN.js.map} +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +179 -136
- package/dist/index.js.map +1 -1
- package/dist/markup/index.d.ts +71 -15
- package/dist/markup/index.js +4 -9
- package/dist/markup/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/ConfirmDialog-LO5WHLMD.js +0 -4
- package/dist/Sidebar-E553SOXC.js.map +0 -1
- package/dist/chunk-A24AESZY.js +0 -7
- package/dist/chunk-DKDD5KXG.js.map +0 -1
package/dist/markup/index.d.ts
CHANGED
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* therefore adds a few hundred bytes of first-party code to a bundle and nothing
|
|
12
12
|
* else — see the subpath's own tsup entry.
|
|
13
13
|
*
|
|
14
|
+
* It is also the ONE subpath whose `exports` entry carries a `default`
|
|
15
|
+
* condition alongside `import`, and that is deliberate rather than untidy. The
|
|
16
|
+
* root and `./apps` are React component entries; a bundler resolves them and
|
|
17
|
+
* nothing else has any business to. This module is consumed by build scripts
|
|
18
|
+
* too — the storefront's media-manifest generator reaches it through its Puck
|
|
19
|
+
* config — and those run under `tsx`, which resolves through Node's CJS
|
|
20
|
+
* loader. With only `types` and `import` declared, that resolution fails with
|
|
21
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED, which reads like a missing file rather than a
|
|
22
|
+
* missing condition. A module this portable should answer to any resolver.
|
|
23
|
+
*
|
|
14
24
|
* WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token
|
|
15
25
|
* list its own way, because it must: the web uses CSS classes bound to theme
|
|
16
26
|
* tokens, email must inline every style (mail clients drop classes). Two
|
|
@@ -45,6 +55,22 @@
|
|
|
45
55
|
* (an intraword underscore is literal) and it is checked with plain character
|
|
46
56
|
* tests rather than a lookbehind, because a lookbehind is a parse error on
|
|
47
57
|
* Safari below 16.4 and this code ships to a public storefront.
|
|
58
|
+
*
|
|
59
|
+
* ## One KNOWN divergence from the standard named above
|
|
60
|
+
*
|
|
61
|
+
* `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;
|
|
62
|
+
* this grammar has a single `_` rule, so the outer pair matches, the inner
|
|
63
|
+
* underscores are ordinary characters, and `__all__` comes back as `_all_`.
|
|
64
|
+
*
|
|
65
|
+
* Left alone deliberately, and recorded rather than fixed. A census of stored
|
|
66
|
+
* copy found 14 rows in this shape and every one of them is the literal link
|
|
67
|
+
* key `__all__`, which no renderer is handed — so nothing published is being
|
|
68
|
+
* mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how
|
|
69
|
+
* a stored `_` pair parses for the benefit of no existing string, and this
|
|
70
|
+
* module's promise is that the grammar grows while nothing published moves.
|
|
71
|
+
* Bold is `**phrase**` in every product; that is the form the toolbar writes
|
|
72
|
+
* and the form to reach for. Pinned by a test, so it stays a decision rather
|
|
73
|
+
* than becoming a discovery.
|
|
48
74
|
*/
|
|
49
75
|
/** A style a toolbar button can apply. The first five are inline marks; the
|
|
50
76
|
* last four are the composer-only affordances (a link and the three line
|
|
@@ -82,18 +108,6 @@ interface MarkupTool {
|
|
|
82
108
|
}
|
|
83
109
|
/** Every button, in the order a writer reaches for them. */
|
|
84
110
|
declare const MARKUP_TOOLS: readonly MarkupTool[];
|
|
85
|
-
/**
|
|
86
|
-
* The four buttons a COPY FIELD offers — a storefront section's copy box and a
|
|
87
|
-
* campaign block's copy box show exactly these, so a merchant meets one toolbar
|
|
88
|
-
* wherever they type. Listed here rather than per product for the obvious
|
|
89
|
-
* reason: two lists would drift.
|
|
90
|
-
*
|
|
91
|
-
* The other five are absent on purpose. A storefront section and an email block
|
|
92
|
-
* render inline runs only — no lists, no block quotes, no anchor. Offering a
|
|
93
|
-
* button whose output the renderer prints as literal text is worse than not
|
|
94
|
-
* offering it, so a field advertises exactly what its renderer can draw.
|
|
95
|
-
*/
|
|
96
|
-
declare const COPY_FIELD_TOOLS: readonly MarkStyle[];
|
|
97
111
|
/** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}
|
|
98
112
|
* order. Keeps a host from hand-rebuilding a descriptor (and drifting on a
|
|
99
113
|
* tooltip) just to pick a subset. */
|
|
@@ -112,9 +126,21 @@ interface InlineRule {
|
|
|
112
126
|
/** CommonMark's `_` rule: neither delimiter may touch a word character, so
|
|
113
127
|
* `snake_case` and `{{first_name}}` stay literal. */
|
|
114
128
|
readonly wordBoundary?: boolean;
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
117
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Characters that may not FOLLOW the close. A run whose closing delimiter
|
|
131
|
+
* butts up against one of these does not fire, and both delimiters stay
|
|
132
|
+
* literal.
|
|
133
|
+
*
|
|
134
|
+
* Two legacy rules need it, for the same reason: a delimiter that is also
|
|
135
|
+
* ordinary punctuation will otherwise claim text that was never markup. `#`
|
|
136
|
+
* must not pair across rank markers ("Ranked #1 and #2"), and `[` must not
|
|
137
|
+
* claim the label of a markdown link ("[terms](https://…)"), where the URL
|
|
138
|
+
* would be stranded outside the run and printed to the reader.
|
|
139
|
+
*
|
|
140
|
+
* A character SET rather than a flag per case, so the next collision is one
|
|
141
|
+
* more rule field and no new branch in {@link tokenizeInline}.
|
|
142
|
+
*/
|
|
143
|
+
readonly notBefore?: string;
|
|
118
144
|
}
|
|
119
145
|
/**
|
|
120
146
|
* The standard-markdown marks, shared by every product.
|
|
@@ -128,6 +154,27 @@ interface InlineRule {
|
|
|
128
154
|
* what actually does the work.
|
|
129
155
|
*/
|
|
130
156
|
declare const STANDARD_MARKUP: readonly InlineRule[];
|
|
157
|
+
/**
|
|
158
|
+
* The buttons a COPY FIELD offers — a storefront section's copy box and a
|
|
159
|
+
* campaign block's copy box show exactly these, so a merchant meets one toolbar
|
|
160
|
+
* wherever they type.
|
|
161
|
+
*
|
|
162
|
+
* DERIVED, not listed. A copy field renders inline runs only — no lists, no
|
|
163
|
+
* block quote, no anchor — so the set it may advertise is precisely the tools
|
|
164
|
+
* whose output this grammar parses back. Writing that as a literal list was the
|
|
165
|
+
* bug's hiding place: the list and the grammar were two hand-maintained facts
|
|
166
|
+
* that agreed by luck, and nothing failed when a tool wrote a delimiter the
|
|
167
|
+
* parser read as something else. Now a style with no rule cannot appear here,
|
|
168
|
+
* whatever anyone types into the array.
|
|
169
|
+
*
|
|
170
|
+
* It resolves to bold, italic, strike and highlight. `code` is the one that
|
|
171
|
+
* surprises: it is an inline mark and it has a fence, but no rule reads a
|
|
172
|
+
* backtick, so a copy field would print `` `word` `` verbatim — and a button
|
|
173
|
+
* whose output the renderer draws as literal text is worse than no button.
|
|
174
|
+
*
|
|
175
|
+
* Declared here, below the rules, because it now depends on them.
|
|
176
|
+
*/
|
|
177
|
+
declare const COPY_FIELD_TOOLS: readonly MarkStyle[];
|
|
131
178
|
/**
|
|
132
179
|
* The storefront's rule set: standard markdown plus the ONE legacy run it has
|
|
133
180
|
* published — `*phrase*` in the theme accent. Delete that last entry once
|
|
@@ -140,6 +187,15 @@ declare const STOREFRONT_MARKUP: readonly InlineRule[];
|
|
|
140
187
|
* runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard
|
|
141
188
|
* is the one the email parser already carried: a `#` followed by a digit does
|
|
142
189
|
* not close a run, so "Ranked #1 and #2" is literal while "Save #20%#" bolds.
|
|
190
|
+
*
|
|
191
|
+
* The italic guard exists because `[phrase]` collides with the one piece of
|
|
192
|
+
* markdown syntax every writer already knows. Without it, "Read [our
|
|
193
|
+
* terms](https://…) first" italicises the label AND prints the bare URL to the
|
|
194
|
+
* reader — the words come back wearing a mark nobody chose. It is not a
|
|
195
|
+
* toolbar problem: a merchant typing an ordinary link into a copy box hits it,
|
|
196
|
+
* which is why the guard is on the RULE and not on which buttons a field
|
|
197
|
+
* offers. `[phrase]` on its own is untouched, so every published line still
|
|
198
|
+
* reads exactly as it did.
|
|
143
199
|
*/
|
|
144
200
|
declare const CAMPAIGN_MARKUP: readonly InlineRule[];
|
|
145
201
|
/** A parsed run. `text` is the inner text with the delimiters removed, so a
|
package/dist/markup/index.js
CHANGED
|
@@ -67,12 +67,6 @@ var MARKUP_TOOLS = [
|
|
|
67
67
|
{ style: "number", label: "1.", title: "Numbered list" },
|
|
68
68
|
{ style: "quote", label: "\u275D", title: "Quote" }
|
|
69
69
|
];
|
|
70
|
-
var COPY_FIELD_TOOLS = [
|
|
71
|
-
"bold",
|
|
72
|
-
"italic",
|
|
73
|
-
"strike",
|
|
74
|
-
"highlight"
|
|
75
|
-
];
|
|
76
70
|
function markupTools(styles) {
|
|
77
71
|
return MARKUP_TOOLS.filter((t) => styles.includes(t.style));
|
|
78
72
|
}
|
|
@@ -82,6 +76,7 @@ var STANDARD_MARKUP = [
|
|
|
82
76
|
{ open: "~~", close: "~~", kind: "strike" },
|
|
83
77
|
{ open: "_", close: "_", kind: "italic", wordBoundary: true }
|
|
84
78
|
];
|
|
79
|
+
var COPY_FIELD_TOOLS = MARKUP_TOOLS.map((tool) => tool.style).filter((style) => STANDARD_MARKUP.some((rule) => rule.kind === style));
|
|
85
80
|
var STOREFRONT_MARKUP = [
|
|
86
81
|
...STANDARD_MARKUP,
|
|
87
82
|
{ open: "*", close: "*", kind: "accent" }
|
|
@@ -89,8 +84,8 @@ var STOREFRONT_MARKUP = [
|
|
|
89
84
|
var CAMPAIGN_MARKUP = [
|
|
90
85
|
...STANDARD_MARKUP,
|
|
91
86
|
{ open: "*", close: "*", kind: "accent" },
|
|
92
|
-
{ open: "#", close: "#", kind: "bold",
|
|
93
|
-
{ open: "[", close: "]", kind: "italic" }
|
|
87
|
+
{ open: "#", close: "#", kind: "bold", notBefore: "0123456789" },
|
|
88
|
+
{ open: "[", close: "]", kind: "italic", notBefore: "(" }
|
|
94
89
|
];
|
|
95
90
|
function wordish(ch) {
|
|
96
91
|
return !/\s/.test(ch) && !/[!-\/:-@[-`{-~]/.test(ch);
|
|
@@ -125,7 +120,7 @@ function matchAt(value, i, rules) {
|
|
|
125
120
|
if (inner.includes(rule.close[0])) continue;
|
|
126
121
|
const end = closeAt + rule.close.length;
|
|
127
122
|
const next = value[end];
|
|
128
|
-
if (rule.
|
|
123
|
+
if (rule.notBefore && next !== void 0 && rule.notBefore.includes(next)) continue;
|
|
129
124
|
if (rule.wordBoundary && next !== void 0 && wordish(next)) continue;
|
|
130
125
|
return { rule, inner, end };
|
|
131
126
|
}
|
package/dist/markup/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/markup/index.ts"],"names":[],"mappings":";AAiEA,IAAM,MAAA,GAAiE;AAAA,EACrE,IAAA,EAAM,EAAE,KAAA,EAAO,IAAA,EAAM,aAAa,WAAA,EAAY;AAAA,EAC9C,MAAA,EAAQ,EAAE,KAAA,EAAO,GAAA,EAAK,aAAa,aAAA,EAAc;AAAA,EACjD,MAAA,EAAQ,EAAE,KAAA,EAAO,IAAA,EAAM,aAAa,YAAA,EAAa;AAAA,EACjD,IAAA,EAAM,EAAE,KAAA,EAAO,GAAA,EAAK,aAAa,MAAA,EAAO;AAAA,EACxC,SAAA,EAAW,EAAE,KAAA,EAAO,IAAA,EAAM,aAAa,kBAAA;AACzC,CAAA;AAEA,IAAM,WAAA,GAAqD;AAAA,EACzD,QAAQ,MAAM,IAAA;AAAA,EACd,MAAA,EAAQ,CAAC,CAAA,KAAM,CAAA,EAAG,IAAI,CAAC,CAAA,EAAA,CAAA;AAAA,EACvB,OAAO,MAAM;AACf,CAAA;AAkBO,SAAS,SAAA,CACd,IAAA,EAAc,KAAA,EAAe,GAAA,EAAa,KAAA,EAC9B;AACZ,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,KAAA,EAAO,GAAG,CAAA;AAEtC,EAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,IAAA,MAAM,QAAQ,QAAA,IAAY,WAAA;AAC1B,IAAA,MAAM,OAAA,GAAU,IAAI,KAAK,CAAA,WAAA,CAAA;AACzB,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,KAAK,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAAA;AAAA;AAAA,MAGrD,cAAA,EAAgB,KAAA,GAAQ,KAAA,CAAM,MAAA,GAAS,CAAA;AAAA,MACvC,YAAA,EAAc,KAAA,GAAQ,OAAA,CAAQ,MAAA,GAAS;AAAA,KACzC;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,YAAY,KAAK,CAAA;AAChC,EAAA,IAAI,MAAA,EAAQ;AAEV,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,IAAA,EAAM,IAAA,CAAK,IAAI,KAAA,GAAQ,CAAA,EAAG,CAAC,CAAC,CAAA,GAAI,CAAA;AAC9D,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA;AACpC,IAAA,MAAM,EAAA,GAAK,KAAA,KAAU,EAAA,GAAK,IAAA,CAAK,MAAA,GAAS,KAAA;AACxC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,EAAE,CAAA;AACjC,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAC5D,IAAA,MAAM,IAAA,GAAO,MACV,GAAA,CAAI,CAAC,GAAG,CAAA,KAAO,MAAA,GAAS,EAAE,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAAE,MAAM,IAAI,MAAA,CAAO,CAAC,IAAI,CAAE,CAAA,CAClE,KAAK,IAAI,CAAA;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,KAAK,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,GAAI,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,EAAE,CAAA;AAAA,MAChD,cAAA,EAAgB,IAAA;AAAA,MAChB,YAAA,EAAc,OAAO,IAAA,CAAK;AAAA,KAC5B;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,WAAA,EAAY,GAAI,OAAO,KAAK,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,GAAQ,KAAA,CAAM,MAAM,CAAA,EAAG,KAAK,CAAA;AAClE,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK,GAAA,GAAM,MAAM,MAAM,CAAA;AAChD,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,KAAA,KAAU,KAAA,EAAO;AACvC,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,KAAA,GAAQ,KAAA,CAAM,MAAM,CAAA,GAAI,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,MAAM,MAAM,CAAA;AAAA,MACpF,cAAA,EAAgB,QAAQ,KAAA,CAAM,MAAA;AAAA,MAC9B,YAAA,EAAc,KAAA,GAAQ,KAAA,CAAM,MAAA,GAAS,QAAA,CAAS;AAAA,KAChD;AAAA,EACF;AACA,EAAA,MAAM,QAAQ,QAAA,IAAY,WAAA;AAC1B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,KAAA,GAAQ,KAAA,GAAQ,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAAA,IACnE,cAAA,EAAgB,QAAQ,KAAA,CAAM,MAAA;AAAA,IAC9B,YAAA,EAAc,KAAA,GAAQ,KAAA,CAAM,MAAA,GAAS,KAAA,CAAM;AAAA,GAC7C;AACF;AAaO,IAAM,YAAA,GAAsC;AAAA,EACjD,EAAE,OAAO,MAAA,EAAQ,KAAA,EAAO,KAAK,KAAA,EAAO,gBAAA,EAAa,UAAU,GAAA,EAAI;AAAA,EAC/D,EAAE,OAAO,QAAA,EAAU,KAAA,EAAO,KAAK,KAAA,EAAO,kBAAA,EAAe,UAAU,GAAA,EAAI;AAAA,EACnE,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,GAAA,EAAK,OAAO,eAAA,EAAgB;AAAA,EACtD,EAAE,KAAA,EAAO,WAAA,EAAa,KAAA,EAAO,QAAA,EAAK,OAAO,+BAAA,EAAgC;AAAA,EACzE,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,cAAA,EAAM,OAAO,MAAA,EAAO;AAAA,EAC5C,EAAE,OAAO,MAAA,EAAQ,KAAA,EAAO,aAAM,KAAA,EAAO,gBAAA,EAAa,UAAU,GAAA,EAAI;AAAA,EAChE,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,QAAA,EAAK,OAAO,eAAA,EAAgB;AAAA,EACtD,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,IAAA,EAAM,OAAO,eAAA,EAAgB;AAAA,EACvD,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,QAAA,EAAK,OAAO,OAAA;AACvC;AAaO,IAAM,gBAAA,GAAyC;AAAA,EACpD,MAAA;AAAA,EAAQ,QAAA;AAAA,EAAU,QAAA;AAAA,EAAU;AAC9B;AAKO,SAAS,YAAY,MAAA,EAA4C;AACtE,EAAA,OAAO,YAAA,CAAa,OAAO,CAAC,CAAA,KAAM,OAAO,QAAA,CAAS,CAAA,CAAE,KAAK,CAAC,CAAA;AAC5D;AAkCO,IAAM,eAAA,GAAyC;AAAA,EACpD,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,MAAM,MAAA,EAAO;AAAA,EACxC,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,MAAM,WAAA,EAAY;AAAA,EAC7C,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,MAAM,QAAA,EAAS;AAAA,EAC1C,EAAE,MAAM,GAAA,EAAK,KAAA,EAAO,KAAK,IAAA,EAAM,QAAA,EAAU,cAAc,IAAA;AACzD;AAQO,IAAM,iBAAA,GAA2C;AAAA,EACtD,GAAG,eAAA;AAAA,EACH,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,GAAA,EAAK,MAAM,QAAA;AACjC;AAQO,IAAM,eAAA,GAAyC;AAAA,EACpD,GAAG,eAAA;AAAA,EACH,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,GAAA,EAAK,MAAM,QAAA,EAAS;AAAA,EACxC,EAAE,MAAM,GAAA,EAAK,KAAA,EAAO,KAAK,IAAA,EAAM,MAAA,EAAQ,gBAAgB,IAAA,EAAK;AAAA,EAC5D,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,GAAA,EAAK,MAAM,QAAA;AACjC;AAeA,SAAS,QAAQ,EAAA,EAAqB;AACpC,EAAA,OAAO,CAAC,KAAK,IAAA,CAAK,EAAE,KAAK,CAAC,iBAAA,CAAkB,KAAK,EAAE,CAAA;AACrD;AAeO,SAAS,cAAA,CACd,OAAe,KAAA,EACA;AACf,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,IAAI,CAAA,GAAI,CAAA;AAER,EAAA,OAAO,CAAA,GAAI,MAAM,MAAA,EAAQ;AACvB,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,EAAO,CAAA,EAAG,KAAK,CAAA;AACnC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,GAAA,CAAI,KAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,KAAK,CAAA;AACpC,MAAA,GAAA,GAAM,EAAA;AACN,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,GAAA,CAAI,KAAK,IAAA,EAAM,IAAA,EAAM,GAAA,CAAI,KAAA,EAAO,CAAA;AACjD,MAAA,CAAA,GAAI,GAAA,CAAI,GAAA;AACR,MAAA;AAAA,IACF;AACA,IAAA,GAAA,IAAO,MAAM,CAAC,CAAA;AACd,IAAA,CAAA,IAAK,CAAA;AAAA,EACP;AACA,EAAA,GAAA,CAAI,KAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,KAAK,CAAA;AACpC,EAAA,OAAO,GAAA;AACT;AAGA,SAAS,OAAA,CAAQ,KAAA,EAAe,CAAA,EAAW,KAAA,EAA8B;AACvE,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,CAAC,KAAA,CAAM,UAAA,CAAW,IAAA,CAAK,IAAA,EAAM,CAAC,CAAA,EAAG;AACrC,IAAA,IAAI,IAAA,CAAK,gBAAgB,CAAA,GAAI,CAAA,IAAK,QAAQ,KAAA,CAAM,CAAA,GAAI,CAAC,CAAC,CAAA,EAAG;AAEzD,IAAA,MAAM,IAAA,GAAO,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,MAAA;AAC3B,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,OAAO,IAAI,CAAA;AAC9C,IAAA,IAAI,OAAA,GAAU,OAAO,CAAA,EAAG;AAIxB,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,IAAA,EAAM,OAAO,CAAA;AACvC,IAAA,IAAI,MAAM,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG;AAEnC,IAAA,MAAM,GAAA,GAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,MAAA;AACjC,IAAA,MAAM,IAAA,GAAO,MAAM,GAAG,CAAA;AACtB,IAAA,IAAI,KAAK,cAAA,IAAkB,IAAA,KAAS,UAAa,IAAA,IAAQ,GAAA,IAAO,QAAQ,GAAA,EAAK;AAC7E,IAAA,IAAI,KAAK,YAAA,IAAgB,IAAA,KAAS,MAAA,IAAa,OAAA,CAAQ,IAAI,CAAA,EAAG;AAE9D,IAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,GAAA,EAAI;AAAA,EAC5B;AACA,EAAA,OAAO,IAAA;AACT;AAaO,SAAS,WAAA,CAAY,OAAe,KAAA,EAAsC;AAC/E,EAAA,OAAO,cAAA,CAAe,KAAA,EAAO,KAAK,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA;AAChE","file":"index.js","sourcesContent":["/**\n * The editorial markup rule — ONE grammar, shared by every product that lets a\n * human type formatted copy into a plain text box.\n *\n * Why this lives in react-os-shell, and why behind its own subpath:\n * the rule is consumed by the admin portal (agent messages + the campaign\n * designer) AND by the public storefront, which has ten dependencies in total\n * and cannot afford the shell's peer graph (a 3D viewer, a PDF renderer, a DXF\n * viewer, an xlsx parser, a Word converter). So this module is deliberately\n * FRAMEWORK-FREE: no React, no JSX, no DOM, no imports at all. `react-os-shell/markup`\n * therefore adds a few hundred bytes of first-party code to a bundle and nothing\n * else — see the subpath's own tsup entry.\n *\n * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token\n * list its own way, because it must: the web uses CSS classes bound to theme\n * tokens, email must inline every style (mail clients drop classes). Two\n * renderers, one grammar, one writer ({@link applyMark}) — so a toolbar button\n * and a parser can never disagree about what a delimiter means.\n *\n * ## The delimiters\n *\n * Standard markdown, in every product:\n *\n * **phrase** bold\n * _phrase_ italic\n * ~~phrase~~ strikethrough\n * ==phrase== highlight — the brand accent colour, applied to a selection\n *\n * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /\n * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly\n * as it does today. Those legacy rules are the only part of this grammar that is\n * meant to be deleted: once stored content has been converted, drop the rule\n * from the product's rule list and nothing else changes.\n *\n * ## Two deliberate delimiter choices\n *\n * 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign\n * designer a single asterisk already means the ACCENT colour, on 704 live\n * instances. Standard markdown says italic. Both cannot be true, so the\n * asterisk keeps its existing meaning and italic takes markdown's OTHER\n * italic marker. Nothing published moves.\n * 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line\n * holding `{{first_name}}` and `{{last_name}}` would italicise everything\n * between the two underscores. The guard is CommonMark's own rule for `_`\n * (an intraword underscore is literal) and it is checked with plain character\n * tests rather than a lookbehind, because a lookbehind is a parse error on\n * Safari below 16.4 and this code ships to a public storefront.\n */\n\n/** A style a toolbar button can apply. The first five are inline marks; the\n * last four are the composer-only affordances (a link and the three line\n * prefixes), which only a renderer that draws block content can honour. */\nexport type MarkStyle =\n | 'bold' | 'italic' | 'strike' | 'code' | 'highlight'\n | 'link' | 'bullet' | 'number' | 'quote';\n\n/** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —\n * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;\n * both resolve to the same paint, which is what makes converting stored copy\n * from one to the other invisible. */\nexport type InlineKind =\n | 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent';\n\n// ── Writing: the toolbar's half of the rule ────────────────────────────────\n\nconst INLINE: Record<string, { fence: string; placeholder: string }> = {\n bold: { fence: '**', placeholder: 'bold text' },\n italic: { fence: '_', placeholder: 'italic text' },\n strike: { fence: '~~', placeholder: 'struck out' },\n code: { fence: '`', placeholder: 'code' },\n highlight: { fence: '==', placeholder: 'highlighted text' },\n};\n\nconst LINE_PREFIX: Record<string, (i: number) => string> = {\n bullet: () => '- ',\n number: (i) => `${i + 1}. `,\n quote: () => '> ',\n};\n\nexport interface WrapResult {\n text: string;\n /** Where the caret should land — inside the new marks, or after them. */\n selectionStart: number;\n selectionEnd: number;\n}\n\n/**\n * Apply a style to `text[start:end]`, returning the new text and where the\n * selection should sit. Toggling is deliberate: pressing Bold on already-bold\n * text unwraps it, so the shortcut behaves like every other editor.\n *\n * Unwrapping only ever recognises the style's OWN fence. An italic button that\n * also unwrapped `*phrase*` would silently strip an accent run in the storefront\n * and the campaign designer, where the asterisk means colour, not slant.\n */\nexport function applyMark(\n text: string, start: number, end: number, style: MarkStyle,\n): WrapResult {\n const selected = text.slice(start, end);\n\n if (style === 'link') {\n const label = selected || 'link text';\n const snippet = `[${label}](https://)`;\n return {\n text: text.slice(0, start) + snippet + text.slice(end),\n // Land the caret on the URL — the label is usually already right.\n // Offset spans \"[\", the label, \"](\" — three characters plus the label.\n selectionStart: start + label.length + 3,\n selectionEnd: start + snippet.length - 1,\n };\n }\n\n const prefix = LINE_PREFIX[style];\n if (prefix) {\n // Line styles apply to every line the selection touches.\n const from = text.lastIndexOf('\\n', Math.max(start - 1, 0)) + 1;\n const toIdx = text.indexOf('\\n', end);\n const to = toIdx === -1 ? text.length : toIdx;\n const block = text.slice(from, to);\n const lines = block.split('\\n');\n const marked = lines.every((l, i) => l.startsWith(prefix(i)));\n const next = lines\n .map((l, i) => (marked ? l.slice(prefix(i).length) : prefix(i) + l))\n .join('\\n');\n return {\n text: text.slice(0, from) + next + text.slice(to),\n selectionStart: from,\n selectionEnd: from + next.length,\n };\n }\n\n const { fence, placeholder } = INLINE[style];\n const before = text.slice(Math.max(0, start - fence.length), start);\n const after = text.slice(end, end + fence.length);\n if (before === fence && after === fence) { // already marked → unwrap\n return {\n text: text.slice(0, start - fence.length) + selected + text.slice(end + fence.length),\n selectionStart: start - fence.length,\n selectionEnd: start - fence.length + selected.length,\n };\n }\n const inner = selected || placeholder;\n return {\n text: text.slice(0, start) + fence + inner + fence + text.slice(end),\n selectionStart: start + fence.length,\n selectionEnd: start + fence.length + inner.length,\n };\n}\n\n/** One toolbar button. `label` is a text glyph for hosts that draw type\n * (the message composer); hosts with an icon set map `style` to their own.\n * `shortcut` is the bare key the host combines with its platform modifier. */\nexport interface MarkupTool {\n style: MarkStyle;\n label: string;\n title: string;\n shortcut?: string;\n}\n\n/** Every button, in the order a writer reaches for them. */\nexport const MARKUP_TOOLS: readonly MarkupTool[] = [\n { style: 'bold', label: 'B', title: 'Bold (⌘B)', shortcut: 'b' },\n { style: 'italic', label: 'I', title: 'Italic (⌘I)', shortcut: 'i' },\n { style: 'strike', label: 'S', title: 'Strikethrough' },\n { style: 'highlight', label: '◆', title: 'Highlight in the brand colour' },\n { style: 'code', label: '‹›', title: 'Code' },\n { style: 'link', label: '🔗', title: 'Link (⌘K)', shortcut: 'k' },\n { style: 'bullet', label: '•', title: 'Bulleted list' },\n { style: 'number', label: '1.', title: 'Numbered list' },\n { style: 'quote', label: '❝', title: 'Quote' },\n];\n\n/**\n * The four buttons a COPY FIELD offers — a storefront section's copy box and a\n * campaign block's copy box show exactly these, so a merchant meets one toolbar\n * wherever they type. Listed here rather than per product for the obvious\n * reason: two lists would drift.\n *\n * The other five are absent on purpose. A storefront section and an email block\n * render inline runs only — no lists, no block quotes, no anchor. Offering a\n * button whose output the renderer prints as literal text is worse than not\n * offering it, so a field advertises exactly what its renderer can draw.\n */\nexport const COPY_FIELD_TOOLS: readonly MarkStyle[] = [\n 'bold', 'italic', 'strike', 'highlight',\n];\n\n/** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}\n * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a\n * tooltip) just to pick a subset. */\nexport function markupTools(styles: readonly MarkStyle[]): MarkupTool[] {\n return MARKUP_TOOLS.filter((t) => styles.includes(t.style));\n}\n\n// ── Reading: the parser's half of the rule ─────────────────────────────────\n\n/**\n * One delimited run. `open`/`close` are literal strings; a run's inner text may\n * never contain the first character of `close`, which is what keeps this\n * equivalent to the \"asterisk, run of non-asterisks, asterisk\" regexes the\n * products used before — the same leftmost match, the same single close\n * candidate, so today's stored copy parses exactly as it always has.\n */\nexport interface InlineRule {\n readonly open: string;\n readonly close: string;\n readonly kind: Exclude<InlineKind, 'text'>;\n /** CommonMark's `_` rule: neither delimiter may touch a word character, so\n * `snake_case` and `{{first_name}}` stay literal. */\n readonly wordBoundary?: boolean;\n /** A close followed by a digit does not close, so rank markers (\"Ranked #1\n * and #2\") stay literal instead of pairing up. */\n readonly notBeforeDigit?: boolean;\n}\n\n/**\n * The standard-markdown marks, shared by every product.\n *\n * `**` and `*` cannot collide even though they share a prefix: a `*` followed\n * immediately by another `*` is an EMPTY accent run, which no rule matches, so\n * at any one position exactly one of the two can fire. Order between them is\n * therefore not observable — which is worth stating, because the obvious\n * assumption (\"longest delimiter must be listed first\") would make a future\n * reader think this array's order is load-bearing when the empty-run rule is\n * what actually does the work.\n */\nexport const STANDARD_MARKUP: readonly InlineRule[] = [\n { open: '**', close: '**', kind: 'bold' },\n { open: '==', close: '==', kind: 'highlight' },\n { open: '~~', close: '~~', kind: 'strike' },\n { open: '_', close: '_', kind: 'italic', wordBoundary: true },\n];\n\n/**\n * The storefront's rule set: standard markdown plus the ONE legacy run it has\n * published — `*phrase*` in the theme accent. Delete that last entry once\n * stored copy has been converted to `==phrase==`; both kinds already paint the\n * same, so the deletion is invisible.\n */\nexport const STOREFRONT_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n];\n\n/**\n * The campaign designer's rule set: standard markdown plus its three legacy\n * runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard\n * is the one the email parser already carried: a `#` followed by a digit does\n * not close a run, so \"Ranked #1 and #2\" is literal while \"Save #20%#\" bolds.\n */\nexport const CAMPAIGN_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n { open: '#', close: '#', kind: 'bold', notBeforeDigit: true },\n { open: '[', close: ']', kind: 'italic' },\n];\n\n/** A parsed run. `text` is the inner text with the delimiters removed, so a\n * caller that only wants words ({@link stripInline}) joins the texts and a\n * caller that paints ({@link InlineKind}) switches on the kind. */\nexport interface InlineToken {\n kind: InlineKind;\n text: string;\n}\n\n/** True for a character that a `_` may not touch. Whitespace and ASCII\n * punctuation are safe neighbours; everything else — letters, digits, and any\n * non-ASCII script — counts as part of a word, so `汉_字_` stays literal too.\n * Deliberately conservative: a character wrongly called word-ish only ever\n * leaves an underscore rendering as it does today. */\nfunction wordish(ch: string): boolean {\n return !/\\s/.test(ch) && !/[!-\\/:-@[-`{-~]/.test(ch);\n}\n\n/**\n * Split authored text into its runs.\n *\n * The returned tokens ALTERNATE, starting and ending with a `text` token that\n * may be empty — exactly the shape `String.split` with one capture group\n * produced in the parsers this replaces. That contract is load-bearing, not\n * cosmetic: the storefront wraps every segment (including the empty ones) in a\n * `<span>`, so preserving the empty tokens is what keeps a published page's\n * markup byte-for-byte identical.\n *\n * A lone delimiter with no partner stays literal — the products' copy contains\n * real stray asterisks, and swallowing one would silently eat a word.\n */\nexport function tokenizeInline(\n value: string, rules: readonly InlineRule[],\n): InlineToken[] {\n const out: InlineToken[] = [];\n let buf = '';\n let i = 0;\n\n while (i < value.length) {\n const hit = matchAt(value, i, rules);\n if (hit) {\n out.push({ kind: 'text', text: buf });\n buf = '';\n out.push({ kind: hit.rule.kind, text: hit.inner });\n i = hit.end;\n continue;\n }\n buf += value[i];\n i += 1;\n }\n out.push({ kind: 'text', text: buf });\n return out;\n}\n\n/** The first rule that opens at `i` AND finds its close, or null. */\nfunction matchAt(value: string, i: number, rules: readonly InlineRule[]) {\n for (const rule of rules) {\n if (!value.startsWith(rule.open, i)) continue;\n if (rule.wordBoundary && i > 0 && wordish(value[i - 1])) continue;\n\n const from = i + rule.open.length;\n const closeAt = value.indexOf(rule.close, from);\n if (closeAt < from + 1) continue; // no close, or an empty run\n\n // The inner text may not contain the close's leading character: one close\n // candidate per opening, which is what the regexes this replaces did.\n const inner = value.slice(from, closeAt);\n if (inner.includes(rule.close[0])) continue;\n\n const end = closeAt + rule.close.length;\n const next = value[end];\n if (rule.notBeforeDigit && next !== undefined && next >= '0' && next <= '9') continue;\n if (rule.wordBoundary && next !== undefined && wordish(next)) continue;\n\n return { rule, inner, end };\n }\n return null;\n}\n\n/**\n * The plain words of an authored string — every paired run unwrapped to its\n * inner text, every unpaired delimiter left alone.\n *\n * This is the ONLY correct way to fill an `alt`, an `aria-label` or a\n * structured-data field from authored copy. Deleting a delimiter character by\n * hand — \"replace every asterisk with nothing\" — breaks the moment the grammar\n * grows a marker, and it disagrees with the renderer about a stray asterisk,\n * which matters: search engines expect structured data to match the text a\n * visitor actually sees.\n */\nexport function stripInline(value: string, rules: readonly InlineRule[]): string {\n return tokenizeInline(value, rules).map((t) => t.text).join('');\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/markup/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The editorial markup rule — ONE grammar, shared by every product that lets a\n * human type formatted copy into a plain text box.\n *\n * Why this lives in react-os-shell, and why behind its own subpath:\n * the rule is consumed by the admin portal (agent messages + the campaign\n * designer) AND by the public storefront, which has ten dependencies in total\n * and cannot afford the shell's peer graph (a 3D viewer, a PDF renderer, a DXF\n * viewer, an xlsx parser, a Word converter). So this module is deliberately\n * FRAMEWORK-FREE: no React, no JSX, no DOM, no imports at all. `react-os-shell/markup`\n * therefore adds a few hundred bytes of first-party code to a bundle and nothing\n * else — see the subpath's own tsup entry.\n *\n * It is also the ONE subpath whose `exports` entry carries a `default`\n * condition alongside `import`, and that is deliberate rather than untidy. The\n * root and `./apps` are React component entries; a bundler resolves them and\n * nothing else has any business to. This module is consumed by build scripts\n * too — the storefront's media-manifest generator reaches it through its Puck\n * config — and those run under `tsx`, which resolves through Node's CJS\n * loader. With only `types` and `import` declared, that resolution fails with\n * ERR_PACKAGE_PATH_NOT_EXPORTED, which reads like a missing file rather than a\n * missing condition. A module this portable should answer to any resolver.\n *\n * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token\n * list its own way, because it must: the web uses CSS classes bound to theme\n * tokens, email must inline every style (mail clients drop classes). Two\n * renderers, one grammar, one writer ({@link applyMark}) — so a toolbar button\n * and a parser can never disagree about what a delimiter means.\n *\n * ## The delimiters\n *\n * Standard markdown, in every product:\n *\n * **phrase** bold\n * _phrase_ italic\n * ~~phrase~~ strikethrough\n * ==phrase== highlight — the brand accent colour, applied to a selection\n *\n * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /\n * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly\n * as it does today. Those legacy rules are the only part of this grammar that is\n * meant to be deleted: once stored content has been converted, drop the rule\n * from the product's rule list and nothing else changes.\n *\n * ## Two deliberate delimiter choices\n *\n * 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign\n * designer a single asterisk already means the ACCENT colour, on 704 live\n * instances. Standard markdown says italic. Both cannot be true, so the\n * asterisk keeps its existing meaning and italic takes markdown's OTHER\n * italic marker. Nothing published moves.\n * 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line\n * holding `{{first_name}}` and `{{last_name}}` would italicise everything\n * between the two underscores. The guard is CommonMark's own rule for `_`\n * (an intraword underscore is literal) and it is checked with plain character\n * tests rather than a lookbehind, because a lookbehind is a parse error on\n * Safari below 16.4 and this code ships to a public storefront.\n *\n * ## One KNOWN divergence from the standard named above\n *\n * `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;\n * this grammar has a single `_` rule, so the outer pair matches, the inner\n * underscores are ordinary characters, and `__all__` comes back as `_all_`.\n *\n * Left alone deliberately, and recorded rather than fixed. A census of stored\n * copy found 14 rows in this shape and every one of them is the literal link\n * key `__all__`, which no renderer is handed — so nothing published is being\n * mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how\n * a stored `_` pair parses for the benefit of no existing string, and this\n * module's promise is that the grammar grows while nothing published moves.\n * Bold is `**phrase**` in every product; that is the form the toolbar writes\n * and the form to reach for. Pinned by a test, so it stays a decision rather\n * than becoming a discovery.\n */\n\n/** A style a toolbar button can apply. The first five are inline marks; the\n * last four are the composer-only affordances (a link and the three line\n * prefixes), which only a renderer that draws block content can honour. */\nexport type MarkStyle =\n | 'bold' | 'italic' | 'strike' | 'code' | 'highlight'\n | 'link' | 'bullet' | 'number' | 'quote';\n\n/** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —\n * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;\n * both resolve to the same paint, which is what makes converting stored copy\n * from one to the other invisible. */\nexport type InlineKind =\n | 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent';\n\n// ── Writing: the toolbar's half of the rule ────────────────────────────────\n\nconst INLINE: Record<string, { fence: string; placeholder: string }> = {\n bold: { fence: '**', placeholder: 'bold text' },\n italic: { fence: '_', placeholder: 'italic text' },\n strike: { fence: '~~', placeholder: 'struck out' },\n code: { fence: '`', placeholder: 'code' },\n highlight: { fence: '==', placeholder: 'highlighted text' },\n};\n\nconst LINE_PREFIX: Record<string, (i: number) => string> = {\n bullet: () => '- ',\n number: (i) => `${i + 1}. `,\n quote: () => '> ',\n};\n\nexport interface WrapResult {\n text: string;\n /** Where the caret should land — inside the new marks, or after them. */\n selectionStart: number;\n selectionEnd: number;\n}\n\n/**\n * Apply a style to `text[start:end]`, returning the new text and where the\n * selection should sit. Toggling is deliberate: pressing Bold on already-bold\n * text unwraps it, so the shortcut behaves like every other editor.\n *\n * Unwrapping only ever recognises the style's OWN fence. An italic button that\n * also unwrapped `*phrase*` would silently strip an accent run in the storefront\n * and the campaign designer, where the asterisk means colour, not slant.\n */\nexport function applyMark(\n text: string, start: number, end: number, style: MarkStyle,\n): WrapResult {\n const selected = text.slice(start, end);\n\n if (style === 'link') {\n const label = selected || 'link text';\n const snippet = `[${label}](https://)`;\n return {\n text: text.slice(0, start) + snippet + text.slice(end),\n // Land the caret on the URL — the label is usually already right.\n // Offset spans \"[\", the label, \"](\" — three characters plus the label.\n selectionStart: start + label.length + 3,\n selectionEnd: start + snippet.length - 1,\n };\n }\n\n const prefix = LINE_PREFIX[style];\n if (prefix) {\n // Line styles apply to every line the selection touches.\n const from = text.lastIndexOf('\\n', Math.max(start - 1, 0)) + 1;\n const toIdx = text.indexOf('\\n', end);\n const to = toIdx === -1 ? text.length : toIdx;\n const block = text.slice(from, to);\n const lines = block.split('\\n');\n const marked = lines.every((l, i) => l.startsWith(prefix(i)));\n const next = lines\n .map((l, i) => (marked ? l.slice(prefix(i).length) : prefix(i) + l))\n .join('\\n');\n return {\n text: text.slice(0, from) + next + text.slice(to),\n selectionStart: from,\n selectionEnd: from + next.length,\n };\n }\n\n const { fence, placeholder } = INLINE[style];\n const before = text.slice(Math.max(0, start - fence.length), start);\n const after = text.slice(end, end + fence.length);\n if (before === fence && after === fence) { // already marked → unwrap\n return {\n text: text.slice(0, start - fence.length) + selected + text.slice(end + fence.length),\n selectionStart: start - fence.length,\n selectionEnd: start - fence.length + selected.length,\n };\n }\n const inner = selected || placeholder;\n return {\n text: text.slice(0, start) + fence + inner + fence + text.slice(end),\n selectionStart: start + fence.length,\n selectionEnd: start + fence.length + inner.length,\n };\n}\n\n/** One toolbar button. `label` is a text glyph for hosts that draw type\n * (the message composer); hosts with an icon set map `style` to their own.\n * `shortcut` is the bare key the host combines with its platform modifier. */\nexport interface MarkupTool {\n style: MarkStyle;\n label: string;\n title: string;\n shortcut?: string;\n}\n\n/** Every button, in the order a writer reaches for them. */\nexport const MARKUP_TOOLS: readonly MarkupTool[] = [\n { style: 'bold', label: 'B', title: 'Bold (⌘B)', shortcut: 'b' },\n { style: 'italic', label: 'I', title: 'Italic (⌘I)', shortcut: 'i' },\n { style: 'strike', label: 'S', title: 'Strikethrough' },\n { style: 'highlight', label: '◆', title: 'Highlight in the brand colour' },\n { style: 'code', label: '‹›', title: 'Code' },\n { style: 'link', label: '🔗', title: 'Link (⌘K)', shortcut: 'k' },\n { style: 'bullet', label: '•', title: 'Bulleted list' },\n { style: 'number', label: '1.', title: 'Numbered list' },\n { style: 'quote', label: '❝', title: 'Quote' },\n];\n\n/** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}\n * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a\n * tooltip) just to pick a subset. */\nexport function markupTools(styles: readonly MarkStyle[]): MarkupTool[] {\n return MARKUP_TOOLS.filter((t) => styles.includes(t.style));\n}\n\n// ── Reading: the parser's half of the rule ─────────────────────────────────\n\n/**\n * One delimited run. `open`/`close` are literal strings; a run's inner text may\n * never contain the first character of `close`, which is what keeps this\n * equivalent to the \"asterisk, run of non-asterisks, asterisk\" regexes the\n * products used before — the same leftmost match, the same single close\n * candidate, so today's stored copy parses exactly as it always has.\n */\nexport interface InlineRule {\n readonly open: string;\n readonly close: string;\n readonly kind: Exclude<InlineKind, 'text'>;\n /** CommonMark's `_` rule: neither delimiter may touch a word character, so\n * `snake_case` and `{{first_name}}` stay literal. */\n readonly wordBoundary?: boolean;\n /**\n * Characters that may not FOLLOW the close. A run whose closing delimiter\n * butts up against one of these does not fire, and both delimiters stay\n * literal.\n *\n * Two legacy rules need it, for the same reason: a delimiter that is also\n * ordinary punctuation will otherwise claim text that was never markup. `#`\n * must not pair across rank markers (\"Ranked #1 and #2\"), and `[` must not\n * claim the label of a markdown link (\"[terms](https://…)\"), where the URL\n * would be stranded outside the run and printed to the reader.\n *\n * A character SET rather than a flag per case, so the next collision is one\n * more rule field and no new branch in {@link tokenizeInline}.\n */\n readonly notBefore?: string;\n}\n\n/**\n * The standard-markdown marks, shared by every product.\n *\n * `**` and `*` cannot collide even though they share a prefix: a `*` followed\n * immediately by another `*` is an EMPTY accent run, which no rule matches, so\n * at any one position exactly one of the two can fire. Order between them is\n * therefore not observable — which is worth stating, because the obvious\n * assumption (\"longest delimiter must be listed first\") would make a future\n * reader think this array's order is load-bearing when the empty-run rule is\n * what actually does the work.\n */\nexport const STANDARD_MARKUP: readonly InlineRule[] = [\n { open: '**', close: '**', kind: 'bold' },\n { open: '==', close: '==', kind: 'highlight' },\n { open: '~~', close: '~~', kind: 'strike' },\n { open: '_', close: '_', kind: 'italic', wordBoundary: true },\n];\n\n/**\n * The buttons a COPY FIELD offers — a storefront section's copy box and a\n * campaign block's copy box show exactly these, so a merchant meets one toolbar\n * wherever they type.\n *\n * DERIVED, not listed. A copy field renders inline runs only — no lists, no\n * block quote, no anchor — so the set it may advertise is precisely the tools\n * whose output this grammar parses back. Writing that as a literal list was the\n * bug's hiding place: the list and the grammar were two hand-maintained facts\n * that agreed by luck, and nothing failed when a tool wrote a delimiter the\n * parser read as something else. Now a style with no rule cannot appear here,\n * whatever anyone types into the array.\n *\n * It resolves to bold, italic, strike and highlight. `code` is the one that\n * surprises: it is an inline mark and it has a fence, but no rule reads a\n * backtick, so a copy field would print `` `word` `` verbatim — and a button\n * whose output the renderer draws as literal text is worse than no button.\n *\n * Declared here, below the rules, because it now depends on them.\n */\nexport const COPY_FIELD_TOOLS: readonly MarkStyle[] = MARKUP_TOOLS\n .map((tool) => tool.style)\n .filter((style) => STANDARD_MARKUP.some((rule) => rule.kind === style));\n\n/**\n * The storefront's rule set: standard markdown plus the ONE legacy run it has\n * published — `*phrase*` in the theme accent. Delete that last entry once\n * stored copy has been converted to `==phrase==`; both kinds already paint the\n * same, so the deletion is invisible.\n */\nexport const STOREFRONT_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n];\n\n/**\n * The campaign designer's rule set: standard markdown plus its three legacy\n * runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard\n * is the one the email parser already carried: a `#` followed by a digit does\n * not close a run, so \"Ranked #1 and #2\" is literal while \"Save #20%#\" bolds.\n *\n * The italic guard exists because `[phrase]` collides with the one piece of\n * markdown syntax every writer already knows. Without it, \"Read [our\n * terms](https://…) first\" italicises the label AND prints the bare URL to the\n * reader — the words come back wearing a mark nobody chose. It is not a\n * toolbar problem: a merchant typing an ordinary link into a copy box hits it,\n * which is why the guard is on the RULE and not on which buttons a field\n * offers. `[phrase]` on its own is untouched, so every published line still\n * reads exactly as it did.\n */\nexport const CAMPAIGN_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n { open: '#', close: '#', kind: 'bold', notBefore: '0123456789' },\n { open: '[', close: ']', kind: 'italic', notBefore: '(' },\n];\n\n/** A parsed run. `text` is the inner text with the delimiters removed, so a\n * caller that only wants words ({@link stripInline}) joins the texts and a\n * caller that paints ({@link InlineKind}) switches on the kind. */\nexport interface InlineToken {\n kind: InlineKind;\n text: string;\n}\n\n/** True for a character that a `_` may not touch. Whitespace and ASCII\n * punctuation are safe neighbours; everything else — letters, digits, and any\n * non-ASCII script — counts as part of a word, so `汉_字_` stays literal too.\n * Deliberately conservative: a character wrongly called word-ish only ever\n * leaves an underscore rendering as it does today. */\nfunction wordish(ch: string): boolean {\n return !/\\s/.test(ch) && !/[!-\\/:-@[-`{-~]/.test(ch);\n}\n\n/**\n * Split authored text into its runs.\n *\n * The returned tokens ALTERNATE, starting and ending with a `text` token that\n * may be empty — exactly the shape `String.split` with one capture group\n * produced in the parsers this replaces. That contract is load-bearing, not\n * cosmetic: the storefront wraps every segment (including the empty ones) in a\n * `<span>`, so preserving the empty tokens is what keeps a published page's\n * markup byte-for-byte identical.\n *\n * A lone delimiter with no partner stays literal — the products' copy contains\n * real stray asterisks, and swallowing one would silently eat a word.\n */\nexport function tokenizeInline(\n value: string, rules: readonly InlineRule[],\n): InlineToken[] {\n const out: InlineToken[] = [];\n let buf = '';\n let i = 0;\n\n while (i < value.length) {\n const hit = matchAt(value, i, rules);\n if (hit) {\n out.push({ kind: 'text', text: buf });\n buf = '';\n out.push({ kind: hit.rule.kind, text: hit.inner });\n i = hit.end;\n continue;\n }\n buf += value[i];\n i += 1;\n }\n out.push({ kind: 'text', text: buf });\n return out;\n}\n\n/** The first rule that opens at `i` AND finds its close, or null. */\nfunction matchAt(value: string, i: number, rules: readonly InlineRule[]) {\n for (const rule of rules) {\n if (!value.startsWith(rule.open, i)) continue;\n if (rule.wordBoundary && i > 0 && wordish(value[i - 1])) continue;\n\n const from = i + rule.open.length;\n const closeAt = value.indexOf(rule.close, from);\n if (closeAt < from + 1) continue; // no close, or an empty run\n\n // The inner text may not contain the close's leading character: one close\n // candidate per opening, which is what the regexes this replaces did.\n const inner = value.slice(from, closeAt);\n if (inner.includes(rule.close[0])) continue;\n\n const end = closeAt + rule.close.length;\n const next = value[end];\n if (rule.notBefore && next !== undefined && rule.notBefore.includes(next)) continue;\n if (rule.wordBoundary && next !== undefined && wordish(next)) continue;\n\n return { rule, inner, end };\n }\n return null;\n}\n\n/**\n * The plain words of an authored string — every paired run unwrapped to its\n * inner text, every unpaired delimiter left alone.\n *\n * This is the ONLY correct way to fill an `alt`, an `aria-label` or a\n * structured-data field from authored copy. Deleting a delimiter character by\n * hand — \"replace every asterisk with nothing\" — breaks the moment the grammar\n * grows a marker, and it disagrees with the renderer about a stray asterisk,\n * which matters: search engines expect structured data to match the text a\n * visitor actually sees.\n */\nexport function stripInline(value: string, rules: readonly InlineRule[]): string {\n return tokenizeInline(value, rules).map((t) => t.text).join('');\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-os-shell",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Desktop-style React UI shell — windows, taskbar, start menu, sticky notes, frosted glass theming, and bundled apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Victor Y. Mau",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"./markup": {
|
|
32
32
|
"types": "./dist/markup/index.d.ts",
|
|
33
|
-
"import": "./dist/markup/index.js"
|
|
33
|
+
"import": "./dist/markup/index.js",
|
|
34
|
+
"default": "./dist/markup/index.js"
|
|
34
35
|
},
|
|
35
36
|
"./styles.css": "./dist/styles.css",
|
|
36
37
|
"./themes.css": "./dist/themes.css"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/Sidebar.tsx"],"names":["navSections","navIcons","sectionIcons"],"mappings":"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","file":"Sidebar-E553SOXC.js","sourcesContent":["/**\n * Sidebar — persistent left strip used when `prefs.layout_mode === 'sidebar'`.\n *\n * Same configuration surface as <StartMenu> (navSections, navIcons,\n * sectionIcons, categories) but rendered inline:\n * - No flyouts. Sections expand/collapse accordion-style below their\n * header, indented one step.\n * - No taskbar-anchored positioning — fixed full-height left strip,\n * width pulled from `--sidebar-width`.\n * - Right edge `rounded-r-2xl` so it matches windowed cards on the\n * right (which use `rounded-2xl`).\n *\n * Designed for small-screen layouts where flyouts would clip and where\n * keeping the menu always-visible saves a tap to switch apps.\n */\n\nimport { useEffect, useMemo, useRef, useState, isValidElement, cloneElement, type ReactElement, type ReactNode } from 'react';\nimport {\n navSections as defaultNavSections,\n navIcons as defaultNavIcons,\n sectionIcons as defaultSectionIcons,\n startMenuCategories as defaultCategories,\n isSection,\n type NavSection,\n type NavItem,\n type StartMenuCategories,\n type VirtualSection,\n} from '../shell-config/nav';\nimport { visibleChildren, isReachable } from './nav-types';\nimport { useAuth } from '../contexts/AuthContext';\nimport { glassStyle, GLASS_INPUT_BG } from '../utils/glass';\n\ninterface SidebarProps {\n width: number;\n openPage: (path: string) => void;\n profile: any;\n user: any;\n onLogout: () => void;\n onNavigate: (path: string) => void;\n navSections?: (NavSection | NavItem)[];\n navIcons?: Record<string, ReactNode>;\n sectionIcons?: Record<string, ReactNode>;\n categories?: StartMenuCategories;\n productName?: string;\n productIcon?: string;\n}\n\nexport default function Sidebar({\n width,\n openPage,\n profile,\n user,\n onLogout,\n onNavigate,\n navSections = defaultNavSections,\n navIcons = defaultNavIcons,\n sectionIcons = defaultSectionIcons,\n categories = defaultCategories,\n productName,\n productIcon,\n}: SidebarProps) {\n const { hasAnyPerm } = useAuth();\n const erpLabels = new Set(categories.erp);\n const systemLabels = new Set(categories.system);\n const footerLabels = new Set(categories.footer ?? []);\n // Flat rows pinned to the footer (next to the profile) — rendered inline,\n // not as an accordion section. Mirrors StartMenu's `footerItems`.\n const footerItems = (categories.footerItems ?? []).filter(it => !it.perms || hasAnyPerm(it.perms));\n const virtualSections = categories.virtual ?? [];\n\n const [search, setSearch] = useState('');\n const [expanded, setExpanded] = useState<Set<string>>(new Set());\n const searchRef = useRef<HTMLInputElement>(null);\n\n const toggleExpanded = (label: string) => {\n setExpanded(prev => {\n const next = new Set(prev);\n if (next.has(label)) next.delete(label);\n else next.add(label);\n return next;\n });\n };\n\n // Top-level items vs sections, mirroring StartMenu's split.\n const topItems = navSections.filter(item => !isSection(item)) as NavItem[];\n const erpSections = navSections.filter(item => isSection(item) && erpLabels.has((item as NavSection).label)) as NavSection[];\n const systemSections = navSections.filter(item => isSection(item) && systemLabels.has((item as NavSection).label)) as NavSection[];\n const footerSections = navSections.filter(item => isSection(item) && footerLabels.has((item as NavSection).label)) as NavSection[];\n\n const getVisibleItems = (section: { items: NavItem[]; perms?: string[] }) => {\n if (section.perms && !hasAnyPerm(section.perms)) return [];\n return section.items\n .filter(it => !it.perms || hasAnyPerm(it.perms))\n // Same rule as <StartMenu>: a group whose children are all\n // permission-hidden has nothing to expand, and its `to` is a synthetic\n // key that never navigates — so drop it rather than leave an inert row.\n .filter(it => isReachable(it, hasAnyPerm));\n };\n\n // Search across all items + sections (same flat list StartMenu uses).\n // Walks 3rd-level children too so nested entries are still discoverable.\n const matchTree = (it: NavItem, q: string): NavItem[] => {\n if (it.perms && !hasAnyPerm(it.perms)) return [];\n if (!isReachable(it, hasAnyPerm)) return [];\n const hits: NavItem[] = [];\n if (it.label.toLowerCase().includes(q)) hits.push(it);\n for (const c of visibleChildren(it, hasAnyPerm)) hits.push(...matchTree(c, q));\n return hits;\n };\n const searchResults = useMemo(() => {\n if (search.length < 2) return [] as NavItem[];\n const q = search.toLowerCase();\n return [\n ...navSections.flatMap((entry) => {\n if (isSection(entry)) {\n return getVisibleItems(entry).flatMap(it => matchTree(it, q));\n }\n return matchTree(entry as NavItem, q);\n }),\n ...footerItems.flatMap(it => matchTree(it, q)),\n ];\n }, [search, navSections, footerItems, hasAnyPerm]);\n\n // Esc collapses any expanded section + clears search; '/' focuses search.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setExpanded(new Set());\n setSearch('');\n } else if (e.key === '/' && document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {\n e.preventDefault();\n searchRef.current?.focus();\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, []);\n\n const handleClick = (path: string) => {\n onNavigate(path);\n onPageOpenedReset();\n };\n\n // Reset search after navigating so the next visit starts clean.\n const onPageOpenedReset = () => {\n setSearch('');\n };\n\n const itemCls = 'w-full flex items-center gap-2 rounded-lg px-3 py-2 text-sm';\n const menuGlass = glassStyle();\n\n // Helper that returns the (possibly recolored) per-route icon.\n const iconEl = (to: string) => {\n const icon = navIcons[to];\n if (icon && isValidElement(icon)) {\n return cloneElement(icon as ReactElement<{ className?: string }>, {\n className: 'h-4 w-4 shrink-0 text-gray-500',\n });\n }\n return <span className=\"h-4 w-4 shrink-0\" />;\n };\n\n const secIcon = (label: string) => {\n const icon = sectionIcons[label];\n if (icon && isValidElement(icon)) {\n return cloneElement(icon as ReactElement<{ className?: string }>, {\n className: 'h-4 w-4 shrink-0 text-gray-500',\n });\n }\n return <span className=\"h-4 w-4 shrink-0\" />;\n };\n\n const renderItem = (item: NavItem) => (\n <div key={item.to}>\n <button\n onClick={() => handleClick(item.to)}\n className={`${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`}\n >\n {iconEl(item.to)}\n <span className=\"truncate\">{item.label}</span>\n </button>\n {item.dividerAfter && <div className=\"border-t border-white/20 my-1.5 mx-2\" />}\n </div>\n );\n\n // 3rd-level: when a NavItem inside an accordion has children, render the\n // parent as its own mini-accordion (further indented). Expansion key is\n // `child:<to>` so it doesn't clash with the section labels in `expanded`.\n const renderNestedItem = (item: NavItem) => {\n const kids = visibleChildren(item, hasAnyPerm);\n if (kids.length === 0) return renderItem(item);\n const key = `child:${item.to}`;\n const isOpen = expanded.has(key);\n return (\n <div key={item.to}>\n <button\n onClick={() => toggleExpanded(key)}\n aria-expanded={isOpen}\n className={`${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`}\n >\n {iconEl(item.to)}\n <span className=\"truncate\">{item.label}</span>\n <svg\n className={`h-3.5 w-3.5 ml-auto text-gray-500 transition-transform ${isOpen ? 'rotate-90' : ''}`}\n fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n {isOpen && (\n <div className=\"pl-4 mt-0.5 mb-1 space-y-0.5\">\n {kids.map(c => (\n <button\n key={c.to}\n onClick={() => handleClick(c.to)}\n className={`${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`}\n >\n {iconEl(c.to)}\n <span className=\"truncate\">{c.label}</span>\n </button>\n ))}\n </div>\n )}\n {item.dividerAfter && <div className=\"border-t border-white/20 my-1.5 mx-2\" />}\n </div>\n );\n };\n\n const renderSectionAccordion = (section: NavSection | VirtualSection, isErp: boolean) => {\n const items = 'perms' in section\n ? getVisibleItems(section as NavSection)\n : (section as VirtualSection).items;\n if (items.length === 0) return null;\n const isOpen = expanded.has(section.label);\n return (\n <div key={section.label}>\n <button\n onClick={() => toggleExpanded(section.label)}\n aria-expanded={isOpen}\n className={`${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`}\n >\n {'icon' in section && section.icon\n ? section.icon\n : secIcon(section.label)}\n <span className={`truncate ${isErp ? 'font-medium' : ''}`}>{section.label}</span>\n <svg\n className={`h-3.5 w-3.5 ml-auto text-gray-500 transition-transform ${isOpen ? 'rotate-90' : ''}`}\n fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n {isOpen && (\n <div className=\"pl-4 mt-0.5 mb-1 space-y-0.5\">\n {items.map(it => renderNestedItem(it))}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <div\n className=\"fixed top-0 left-0 bottom-0 z-[260] flex flex-col rounded-r-2xl overflow-hidden\"\n style={{ width, ...menuGlass }}\n >\n {/* Brand */}\n <div className=\"flex items-center gap-2 px-4 py-3 border-b border-white/15 shrink-0\">\n {productIcon && <img src={productIcon} alt=\"\" className=\"h-5 w-5 shrink-0 opacity-80\" />}\n <span className=\"text-sm font-semibold text-gray-800 truncate\">{productName ?? 'Apps'}</span>\n </div>\n\n {/* Search */}\n <div className=\"px-3 pt-3 pb-2 shrink-0\">\n <div className={`flex items-center gap-2 ${GLASS_INPUT_BG} rounded-lg px-2.5 py-1.5`}>\n <svg className=\"h-3.5 w-3.5 text-gray-400 shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z\" />\n </svg>\n <input\n ref={searchRef}\n value={search}\n onChange={e => setSearch(e.target.value)}\n placeholder=\"Search...\"\n className=\"flex-1 bg-transparent text-xs outline-none placeholder-gray-400\"\n />\n {search && (\n <button onClick={() => setSearch('')} className=\"text-gray-400 hover:text-gray-600 text-xs\">×</button>\n )}\n </div>\n </div>\n\n {/* Body */}\n <div className=\"flex-1 overflow-y-auto px-1 pb-1\">\n {search.length >= 2 ? (\n // Search results take over the body.\n <div>\n {searchResults.length === 0 ? (\n <div className=\"px-3 py-6 text-center text-xs text-gray-400\">No matches</div>\n ) : (\n searchResults.map(r => (\n <button\n key={r.to}\n onClick={() => handleClick(r.to)}\n className={`${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`}\n >\n {iconEl(r.to)}\n <span className=\"truncate\">{r.label}</span>\n </button>\n ))\n )}\n </div>\n ) : (\n <>\n {/* Top-level apps */}\n {topItems.map(renderItem)}\n\n {/* Divider only when both sides have content — with every top-group\n entry moved into the footer group the top group can be empty, and\n an unconditional rule would collapse against the search box\n (mirrors StartMenu's hasAppsGroup condition). */}\n {topItems.length > 0 && (erpSections.length > 0 || systemSections.length > 0 || virtualSections.length > 0) && (\n <div className=\"border-t border-white/15 my-1.5 mx-2\" />\n )}\n\n {/* ERP sections then system sections — same order as StartMenu's vertical layout. */}\n {erpSections.map(s => renderSectionAccordion(s, true))}\n {systemSections.map(s => renderSectionAccordion(s, false))}\n {virtualSections.map(v => renderSectionAccordion(v, false))}\n {/* Footer items + sections: pinned just above the profile, divided from the rest. */}\n {(footerSections.length > 0 || footerItems.length > 0) && <div className=\"border-t border-white/15 my-1.5 mx-2\" />}\n {footerSections.map(s => renderSectionAccordion(s, false))}\n {footerItems.map(renderItem)}\n </>\n )}\n </div>\n\n {/* Profile + Sign out at the bottom — mirrors StartMenu's user row. */}\n <div className=\"border-t border-white/15 p-1 shrink-0\">\n <div\n onClick={() => handleClick('/profile')}\n className=\"rounded-lg px-2 py-1.5 flex items-center gap-2.5 hover:bg-blue-50 hover:text-blue-700 transition-colors cursor-pointer\"\n >\n {profile?.avatar_url ? (\n <img src={profile.avatar_url} alt=\"\" className=\"h-8 w-8 rounded-full object-cover border border-white/20 shrink-0\" />\n ) : (\n <div className=\"h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center text-sm font-bold text-blue-700 shrink-0\">\n {(profile?.first_name?.charAt(0) || user?.email?.charAt(0) || '?').toUpperCase()}\n </div>\n )}\n <p className=\"flex-1 min-w-0 text-sm font-medium text-gray-900 truncate\">\n {profile?.first_name ? `${profile.first_name} ${profile.last_name || ''}`.trim() : user?.email}\n </p>\n <button\n onClick={e => { e.stopPropagation(); onLogout(); }}\n title=\"Sign Out\"\n className=\"shrink-0 p-1.5 rounded-md text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors\"\n >\n <svg className=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
|
package/dist/chunk-A24AESZY.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/nav-types.ts","../src/shell-config/nav.tsx","../src/shell/ShellAuth.tsx","../src/utils/glass.ts"],"names":[],"mappings":"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","file":"chunk-DKDD5KXG.js","sourcesContent":["/**\n * Nav data types used by <Layout> + <StartMenu>. The actual sections, icons,\n * and categories are consumer-supplied via Layout props — the package never\n * ships nav DATA, only the shape it expects.\n */\nimport type { ReactNode } from 'react';\n\nexport interface NavItem {\n to: string;\n label: string;\n perms?: string[];\n dividerAfter?: boolean;\n /** Optional 3rd-level sub-items. Hovering the parent in <StartMenu> opens\n * a nested flyout; in <Sidebar> the parent becomes an inline sub-accordion. */\n children?: NavItem[];\n}\n\nexport interface NavSection {\n label: string;\n items: NavItem[];\n perms?: string[];\n /** Optional landing route for clicks on the section title itself\n * (e.g. R&D's `/rd` dashboard). */\n to?: string;\n}\n\nexport interface VirtualSection {\n label: string;\n items: NavItem[];\n icon?: ReactNode;\n}\n\nexport interface StartMenuCategories {\n /** Section labels rendered in the \"ERP\" group (bold, with section icon). */\n erp: string[];\n /** Section labels rendered in the \"system\" group. */\n system: string[];\n /** Optional virtual flyouts (e.g. a \"Utilities\" tray). */\n virtual?: VirtualSection[];\n /** Optional section labels pinned to the bottom of the menu, next to the user\n * profile, separated from the ERP group by a divider. */\n footer?: string[];\n /** Optional flat top-level items pinned to the bottom of the menu, next to\n * the user profile, separated from the ERP group by a divider. Unlike\n * `footer` (section labels rendered as flyouts), these render as direct\n * clickable rows — use for standalone destinations like System Preferences\n * or a bug-report link. */\n footerItems?: NavItem[];\n}\n\nexport function isSection(item: NavSection | NavItem): item is NavSection {\n return 'items' in item;\n}\n\n/** The host app's permission predicate — `useAuth().hasAnyPerm`. */\ntype HasAnyPerm = (perms: string[]) => boolean;\n\n/**\n * The sub-items of `item` this user may actually see.\n *\n * Every affordance for a nested group has to be derived from THIS list — the\n * chevron that advertises it, the hover handler that opens it, and the flyout\n * itself. Reading the raw `item.children` for the chevron while filtering only\n * for the flyout put an arrow on groups whose children were all\n * permission-hidden: hovering one highlighted the row and opened nothing.\n */\nexport function visibleChildren(item: NavItem, hasAnyPerm: HasAnyPerm): NavItem[] {\n return (item.children ?? []).filter(c => !c.perms || hasAnyPerm(c.perms));\n}\n\n/**\n * Whether a nav row leads anywhere for this user: either it's a plain\n * destination, or it's a group with at least one visible child. A group whose\n * children are all hidden has nothing to open — and by convention a group's\n * `to` is a synthetic key that never navigates, so there is nothing to fall\n * back to either. Such a row is dropped rather than rendered inert.\n */\nexport function isReachable(item: NavItem, hasAnyPerm: HasAnyPerm): boolean {\n return !item.children?.length || visibleChildren(item, hasAnyPerm).length > 0;\n}\n","/**\n * INTERNAL stub — package-side compatibility for files that legacy-imported\n * default nav data from `shell-config/nav`. The package never ships nav\n * DATA; consumers always supply `navSections` / `navIcons` / `sectionIcons` /\n * `categories` as Layout props.\n *\n * Empty defaults exported here so the copied files compile during the\n * extraction transition. Each consumer-facing field is also re-exported as\n * a TYPE alias for code that only needed the shape.\n */\nimport type { ReactNode } from 'react';\nimport type { NavItem, NavSection, StartMenuCategories, VirtualSection } from '../shell/nav-types';\n\n// Live proxy: WindowManager reads window-title icons from this module-level\n// map. Consumers register their full icon set once at app startup so the\n// title bars show the same glyphs as the start menu.\nconst _navIcons: Record<string, ReactNode> = {};\nexport const navIcons: Record<string, ReactNode> = new Proxy(_navIcons, {\n get(_t, k: string) { return _navIcons[k]; },\n has(_t, k: string) { return k in _navIcons; },\n ownKeys() { return Object.keys(_navIcons); },\n getOwnPropertyDescriptor(_t, k: string) {\n if (k in _navIcons) return { configurable: true, enumerable: true, value: _navIcons[k] };\n return undefined;\n },\n});\nexport function setShellNavIcons(icons: Record<string, ReactNode>): void {\n for (const k of Object.keys(_navIcons)) delete _navIcons[k];\n Object.assign(_navIcons, icons);\n}\n\nexport const sectionIcons: Record<string, ReactNode> = {};\nexport const navSections: (NavSection | NavItem)[] = [];\nexport const startMenuCategories: StartMenuCategories = { erp: [], system: [], virtual: [], footer: [] };\n\nexport { isSection } from '../shell/nav-types';\nexport type { NavItem, NavSection, StartMenuCategories, VirtualSection };\n","/**\n * Shell-side auth surface — JUST permission checking. The shell never\n * authenticates anyone or holds tokens; it only asks \"is the current user\n * allowed to see this nav item?\".\n *\n * Consumer wraps the app:\n *\n * <ShellAuthProvider value={{ hasAnyPerm: (perms) => myAuth.has(...perms) }}>\n * <Layout ... />\n * </ShellAuthProvider>\n *\n * When no provider is mounted, every permission check returns true — useful\n * for demos and consumers that don't gate their UI.\n */\nimport { createContext, useContext, type ReactNode } from 'react';\n\nexport interface ShellAuth {\n /** Returns true if the current user has any of the listed permission codes. */\n hasAnyPerm: (perms: string[]) => boolean;\n}\n\nconst DEFAULT: ShellAuth = { hasAnyPerm: () => true };\n\nconst ShellAuthContext = createContext<ShellAuth>(DEFAULT);\n\nexport function ShellAuthProvider({ value, children }: { value: ShellAuth; children: ReactNode }) {\n return <ShellAuthContext.Provider value={value}>{children}</ShellAuthContext.Provider>;\n}\n\nexport function useShellAuth(): ShellAuth {\n return useContext(ShellAuthContext);\n}\n","import type { CSSProperties } from 'react';\n\n/** Read the system menu opacity from CSS custom property set by Layout */\nfunction getMenuOpacity(): number {\n try {\n const val = getComputedStyle(document.documentElement).getPropertyValue('--menu-opacity')?.trim();\n if (val) return parseFloat(val);\n } catch {}\n return 0.95;\n}\n\nfunction isDarkTheme(): boolean {\n try {\n return document.documentElement.getAttribute('data-theme') === 'dark';\n } catch {\n return false;\n }\n}\n\n/** Whether the \"Reduce transparency\" preference is active (root class set by\n * Layout). When on, glass surfaces render solid + blur-free — the blur is the\n * GPU-expensive part, and dropping it is the point of the setting. */\nfunction reduceTransparency(): boolean {\n try {\n return document.documentElement.classList.contains('rosh-reduce-transparency');\n } catch {\n return false;\n }\n}\n\n/** Frosted glass style — shared across all menus, popups, and glass UI elements.\n * Reads --menu-opacity CSS variable set by the theme system, and adapts the\n * base tint to dark mode so menus don't stay light-cream when text is light. */\nexport function glassStyle(opacity?: number): CSSProperties {\n const o = opacity ?? getMenuOpacity();\n if (reduceTransparency()) {\n // Reduce-transparency: a fully opaque, blur-free surface. No\n // backdrop-filter (the expensive part on low-end GPUs) and a solid\n // background so menus/popups stay legible over any wallpaper. The global\n // stylesheet rule also strips backdrop-filter, but returning a solid\n // background here is what keeps the surface from reading as washed-out\n // translucent glass with the blur gone.\n return isDarkTheme()\n ? {\n background: '#1e1e2e',\n border: '1px solid rgba(255,255,255,0.12)',\n boxShadow: '0 8px 24px rgba(0,0,0,0.5)',\n }\n : {\n background: '#ffffff',\n border: '1px solid rgba(0,0,0,0.12)',\n boxShadow: '0 8px 24px rgba(0,0,0,0.18)',\n };\n }\n if (isDarkTheme()) {\n // Dark frosted glass — Catppuccin-aligned base (#1e1e2e / 30,30,46) with\n // a subtle gradient and lighter inner highlight.\n return {\n background: `linear-gradient(135deg, rgba(30,30,46,${o * 0.85}) 0%, rgba(24,24,37,${o * 0.75}) 50%, rgba(30,30,46,${o * 0.85}) 100%)`,\n backdropFilter: 'blur(40px) saturate(1.6)',\n WebkitBackdropFilter: 'blur(40px) saturate(1.6)',\n border: '1px solid rgba(255,255,255,0.08)',\n boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -1px 0 rgba(0,0,0,0.4), 0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3)',\n };\n }\n return {\n background: `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`,\n backdropFilter: 'blur(40px) saturate(1.8)',\n WebkitBackdropFilter: 'blur(40px) saturate(1.8)',\n border: '1px solid rgba(255,255,255,0.35)',\n boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(255,255,255,0.1), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08)',\n };\n}\n\n/** Glass divider border color */\nexport const GLASS_DIVIDER = 'border-white/20';\n\n/** Glass input/search bar background — declared in styles.css so it can adapt\n * to dark mode (a flat `bg-white/15` reads as a too-bright tile on the dark\n * glass gradient). */\nexport const GLASS_INPUT_BG = 'glass-input-bg';\n"]}
|