@seliseblocks/mailcraft 0.2.9 → 0.2.11
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/CHANGELOG.md +45 -0
- package/DOCS.md +5 -5
- package/README.md +3 -0
- package/README.md.txt +3 -0
- package/dist/mailcraft-editor.bundle.js +75 -60
- package/dist/mailcraft-editor.bundle.js.map +3 -3
- package/package.json +2 -2
- package/src/core/blocks.js +30 -11
- package/src/core/css-cascade.js +120 -117
- package/src/core/editor-core.js +74 -27
- package/src/core/export.js +83 -10
- package/src/core/i18n/index.js +83 -83
- package/src/core/icons.js +0 -1
- package/src/core/ids.js +1 -1
- package/src/core/import-html.js +604 -48
- package/src/core/layout-style.js +100 -100
- package/src/core/parse.js +10 -10
- package/src/core/placeholder.js +15 -15
- package/src/core/sanitize.js +11 -1
- package/src/core/variables.js +11 -11
- package/src/mailcraft-editor.js +25 -2
- package/src/render/block-body.js +17 -5
- package/src/render/canvas.js +14 -0
- package/src/render/focus-preserve.js +158 -158
- package/src/render/story.js +415 -415
- package/src/render/style.js +7 -0
- package/types/index.d.ts +10 -3
package/src/core/export.js
CHANGED
|
@@ -136,9 +136,38 @@ export function decorateLogicTags(html) {
|
|
|
136
136
|
return s;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export function buildHtml(state, root, boxCss) {
|
|
139
|
+
export function buildHtml(state, root, boxCss, opts) {
|
|
140
140
|
const d = state.doc; const t = d.theme;
|
|
141
141
|
const logic = logicPlan(d);
|
|
142
|
+
/*
|
|
143
|
+
* Fidelity markers. A handful of blocks render into markup that cannot be
|
|
144
|
+
* read back into the block it came from: a countdown bakes its digits, a
|
|
145
|
+
* video is just a linked image, a section box and a code sample are styled
|
|
146
|
+
* divs like any other, a raw-CSS block is a bare <style>, and a flex/grid
|
|
147
|
+
* row is a div no table walker can re-shape. For exactly those, the export
|
|
148
|
+
* stamps a compact attribute layer -- `data-mc` (the type), `data-mcp`
|
|
149
|
+
* (the props the DOM itself does not carry), `data-mcr` (a CSS-layout
|
|
150
|
+
* row's settings) and the inert `mc-keep` class -- which the importer
|
|
151
|
+
* trusts when present and ignores otherwise. Mail clients ignore unknown
|
|
152
|
+
* attributes wholesale, and everything else round-trips from its rendered
|
|
153
|
+
* shape alone, so the layer stays this small. Attribute names deliberately
|
|
154
|
+
* sit outside `grab()`'s `data-mc-[a-z-]+` strip (they live on exporter
|
|
155
|
+
* scaffolding, but belt and braces). A host that wants pristine HTML can
|
|
156
|
+
* pass `exportHtml({ markers: false })` and accept the lossy reload.
|
|
157
|
+
*/
|
|
158
|
+
const markers = !(opts && opts.markers === false);
|
|
159
|
+
const attrEsc = (v) => String(v).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
160
|
+
const MARKED = { countdown: 1, video: 1, box: 1, codeblock: 1 };
|
|
161
|
+
const marker = (b) => {
|
|
162
|
+
if (!markers || !MARKED[b.type]) return '';
|
|
163
|
+
const payload = { ...b.props };
|
|
164
|
+
// Content the rendered DOM already carries is not duplicated into the
|
|
165
|
+
// payload: a box's html IS the grabbed markup, a code sample's text IS
|
|
166
|
+
// the <pre>'s own.
|
|
167
|
+
if (b.type === 'box') delete payload.html;
|
|
168
|
+
if (b.type === 'codeblock') delete payload.code;
|
|
169
|
+
return ' data-mc="' + b.type + '" data-mcp="' + attrEsc(JSON.stringify(payload)) + '"';
|
|
170
|
+
};
|
|
142
171
|
const grab = (id) => {
|
|
143
172
|
const el = root.querySelector('[data-mc-content="' + id + '"]');
|
|
144
173
|
if (!el) return '';
|
|
@@ -174,7 +203,11 @@ export function buildHtml(state, root, boxCss) {
|
|
|
174
203
|
*/
|
|
175
204
|
const mobilePlan = (rp, cols) => {
|
|
176
205
|
const mode = rp.mobileCols === undefined ? 1 : rp.mobileCols;
|
|
177
|
-
if (cols < 2
|
|
206
|
+
if (cols < 2) return { row: '', cell: '' };
|
|
207
|
+
// `keep` exports NO functional class, which made it indistinguishable
|
|
208
|
+
// from foreign HTML (where stacking is the safer default) -- so it ships
|
|
209
|
+
// as the inert `mc-keep`, a marker with no stylesheet rule behind it.
|
|
210
|
+
if (mode === 'keep') return { row: markers ? 'mc-keep' : '', cell: '' };
|
|
178
211
|
const rowCls = [];
|
|
179
212
|
// Two-up and reverse both need the row to become a flex container, which
|
|
180
213
|
// is safe precisely because it only ever runs inside the media query: a
|
|
@@ -208,11 +241,15 @@ export function buildHtml(state, root, boxCss) {
|
|
|
208
241
|
return blocksOf.map((b) => logic.emit.get(b.id) || '').filter(Boolean).map((s) => ' ' + s).join('\n');
|
|
209
242
|
}
|
|
210
243
|
const colInner = (c) => c.blocks.map((b) => {
|
|
211
|
-
if (b.type === 'css') return '<style>' + (b.props.code || '') + '</style>';
|
|
244
|
+
if (b.type === 'css') return '<style' + (markers ? ' data-mc="css"' + (b.props.note ? ' data-mcn="' + attrEsc(b.props.note) + '"' : '') : '') + '>' + (b.props.code || '') + '</style>';
|
|
212
245
|
if (b.type === 'html') return b.props.code || '';
|
|
213
|
-
|
|
246
|
+
// The same width-carrying span the canvas draws (block-body.js), so the
|
|
247
|
+
// Width slider reaches the sent mail and the importer can read it back
|
|
248
|
+
// -- exported bare, the drawing shipped at its intrinsic size and the
|
|
249
|
+
// prop reset to 100 on every reload.
|
|
250
|
+
if (b.type === 'svg') return '<div style="text-align:' + b.props.align + ';padding:' + b.props.py + 'px 0"><span style="display:inline-block;width:' + (b.props.width || 100) + '%">' + (b.props.code || '') + '</span></div>';
|
|
214
251
|
if (b.type === 'condition' || b.type === 'loop') return logic.emit.get(b.id) || '';
|
|
215
|
-
return '<div' + visClass(b.props) + ' style="' + boxCss(b.props) + '">' + grab(b.id) + '</div>';
|
|
252
|
+
return '<div' + marker(b) + visClass(b.props) + ' style="' + boxCss(b.props) + '">' + grab(b.id) + '</div>';
|
|
216
253
|
}).filter(Boolean).join('\n ') || ' ';
|
|
217
254
|
const cells = r.cols.map((c) => {
|
|
218
255
|
// Column-level styling (bg/radius/inner padding) renders as a wrapper
|
|
@@ -228,9 +265,12 @@ export function buildHtml(state, root, boxCss) {
|
|
|
228
265
|
// for every wide client.
|
|
229
266
|
const wrapCls = [plan.cell ? 'mc-stack' : '', plan.row].filter(Boolean).join(' ');
|
|
230
267
|
const stackWrap = wrapCls ? ' class="' + wrapCls + '"' : '';
|
|
268
|
+
const mcr = markers && rp.layout && rp.layout !== 'columns'
|
|
269
|
+
? ' data-mcr="' + attrEsc(JSON.stringify({ layout: rp.layout, flexDir: rp.flexDir || 'row', justify: rp.justify || 'flex-start', alignItems: rp.alignItems || 'stretch', wrap: rp.wrap !== false, gridCols: rp.gridCols || 2, gap: rp.gap || 0, spans: r.cols.map((c) => c.span) })) + '"'
|
|
270
|
+
: '';
|
|
231
271
|
const cssBody = rp.layout === 'grid'
|
|
232
|
-
? '<div' + stackWrap + ' style="display:grid;grid-template-columns:repeat(' + (rp.gridCols || 2) + ',minmax(0,1fr));gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div>' + colInner(c) + '</div>').join('\n ') + '\n </div>'
|
|
233
|
-
: '<div' + stackWrap + ' style="display:flex;flex-direction:' + (rp.flexDir || 'row') + ';justify-content:' + (rp.justify || 'flex-start') + ';align-items:' + (rp.alignItems || 'stretch') + ';flex-wrap:' + (rp.wrap ? 'wrap' : 'nowrap') + ';gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div style="flex:' + c.span + ' 1 auto;min-width:0">' + colInner(c) + '</div>').join('\n ') + '\n </div>';
|
|
272
|
+
? '<div' + mcr + stackWrap + ' style="display:grid;grid-template-columns:repeat(' + (rp.gridCols || 2) + ',minmax(0,1fr));gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div>' + colInner(c) + '</div>').join('\n ') + '\n </div>'
|
|
273
|
+
: '<div' + mcr + stackWrap + ' style="display:flex;flex-direction:' + (rp.flexDir || 'row') + ';justify-content:' + (rp.justify || 'flex-start') + ';align-items:' + (rp.alignItems || 'stretch') + ';flex-wrap:' + (rp.wrap ? 'wrap' : 'nowrap') + ';gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div style="flex:' + c.span + ' 1 auto;min-width:0">' + colInner(c) + '</div>').join('\n ') + '\n </div>';
|
|
234
274
|
const body = rp.layout && rp.layout !== 'columns'
|
|
235
275
|
? cssBody
|
|
236
276
|
// The `<tr>` is what becomes the flex container for two-up and reverse;
|
|
@@ -244,8 +284,21 @@ export function buildHtml(state, root, boxCss) {
|
|
|
244
284
|
: 'background:' + (rp.bg || t.contentBg || 'transparent') + ';';
|
|
245
285
|
const tdBorder = rowBorderCss(rp)
|
|
246
286
|
+ (rp.radius ? 'border-radius:' + rp.radius + 'px;' : '')
|
|
247
|
-
+ (rp.shadow ? 'box-shadow:' + rp.shadow + ';' : '')
|
|
248
|
-
|
|
287
|
+
+ (rp.shadow ? 'box-shadow:' + rp.shadow + ';' : '');
|
|
288
|
+
// Outside margins and Max width need an element mail clients actually
|
|
289
|
+
// honour them on: margins on a <td> are ignored by every major client
|
|
290
|
+
// (the old shape wrote them there -- inert in sent mail AND never read
|
|
291
|
+
// back), and max-width was canvas-only. Both now ride a wrapper div
|
|
292
|
+
// INSIDE the cell, carrying the row's paint and padding with them so
|
|
293
|
+
// the margin sits outside the painted box, exactly as the canvas draws
|
|
294
|
+
// it. Rows using neither keep the old markup byte for byte.
|
|
295
|
+
const boxed = rp.mt || rp.mr || rp.mb || rp.ml || rp.my || (rp.maxW && rp.maxW < 100);
|
|
296
|
+
if (boxed) {
|
|
297
|
+
const cap = rp.maxW && rp.maxW < 100 ? 'max-width:' + rp.maxW + '%;' : '';
|
|
298
|
+
// centerEmpty only when capped: an uncapped box spans the cell anyway,
|
|
299
|
+
// and `auto` there would only read back as a lost slider value.
|
|
300
|
+
return ' <tr>\n <td style="padding:0;">\n <div style="' + cap + 'margin:' + rowMargin(rp, !!cap) + ';padding:' + rowPad(rp) + ';' + tdBg + tdBorder + '">\n ' + body + '\n </div>\n </td>\n </tr>';
|
|
301
|
+
}
|
|
249
302
|
return ' <tr>\n <td style="padding:' + rowPad(rp) + ';' + tdBg + tdBorder + '">\n ' + body + '\n </td>\n </tr>';
|
|
250
303
|
}).join('\n') + (logic.tail ? '\n ' + logic.tail : '');
|
|
251
304
|
// The page section -- the full-width area the content column sits on. Its
|
|
@@ -353,6 +406,26 @@ export function buildHtml(state, root, boxCss) {
|
|
|
353
406
|
// `text-size-adjust` at 100%, never `none`: both stop a mobile client
|
|
354
407
|
// inflating the type, but `none` also blocks legitimate scaling and leaves
|
|
355
408
|
// text unreadably small on some Android clients.
|
|
409
|
+
/*
|
|
410
|
+
* theme.link, made real: mail clients have no stylesheet to inherit a link
|
|
411
|
+
* color from (Gmail strips <style>; the sheet rule that paints the canvas
|
|
412
|
+
* never ships), so the value must ride every anchor inline. Stamped only
|
|
413
|
+
* where no inline color already stands -- a button label, a menu item, or
|
|
414
|
+
* a link the user recolored by hand keeps its own -- and skipped entirely
|
|
415
|
+
* when the theme has no link color. The canvas strips restated stamps at
|
|
416
|
+
* render time (overrideLinkColor), which is what keeps a reloaded document
|
|
417
|
+
* inheriting: export -> import -> export re-stamps the same bytes.
|
|
418
|
+
*/
|
|
419
|
+
const stampLinks = (html) => {
|
|
420
|
+
if (!t.link) return html;
|
|
421
|
+
return html.replace(/<a\b([^>]*)>/g, (m0, attrs) => {
|
|
422
|
+
if (/(?:[;"\s])color\s*:/.test(attrs)) return m0;
|
|
423
|
+
if (/style="/.test(attrs)) {
|
|
424
|
+
return '<a' + attrs.replace(/style="([^"]*)"/, (s0, css) => 'style="' + css + (!css.trim() || css.trim().endsWith(';') ? '' : ';') + 'color:' + t.link + ';"') + '>';
|
|
425
|
+
}
|
|
426
|
+
return '<a' + attrs + ' style="color:' + t.link + ';">';
|
|
427
|
+
});
|
|
428
|
+
};
|
|
356
429
|
const bodyStyle = 'margin:0;padding:0;background:' + pageBg + ';font-family:' + t.font.replace(/"/g, "'") + ';color:' + t.text + ';-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;text-size-adjust:100%;';
|
|
357
|
-
return msoHarden('<!doctype html>\n<html lang="en" xmlns:o="urn:schemas-microsoft-com:office:office">\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width,initial-scale=1">\n<meta name="color-scheme" content="light">\n<meta name="supported-color-schemes" content="light">\n<title>' + 'Email' + '</title>' + msoHead + stackCss + '\n</head>\n<body style="' + bodyStyle + '">\n<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:' + pageBg + ';">\n <tr><td align="center" style="padding:' + pagePad + ';">\n ' + ghostOpen + '\n ' + shell + '\n ' + ghostClose + '\n </td></tr>\n</table>\n</body>\n</html>');
|
|
430
|
+
return msoHarden(stampLinks('<!doctype html>\n<html lang="en" xmlns:o="urn:schemas-microsoft-com:office:office">\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width,initial-scale=1">\n<meta name="color-scheme" content="light">\n<meta name="supported-color-schemes" content="light">\n<title>' + 'Email' + '</title>' + msoHead + stackCss + '\n</head>\n<body style="' + bodyStyle + '">\n<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:' + pageBg + ';">\n <tr><td align="center" style="padding:' + pagePad + ';">\n ' + ghostOpen + '\n ' + shell + '\n ' + ghostClose + '\n </td></tr>\n</table>\n</body>\n</html>'));
|
|
358
431
|
}
|
package/src/core/i18n/index.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { EN as ENBase } from './en.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Builds a translator. `overrides` is whatever a host passes as `.messages`
|
|
5
|
-
* on the element -- a host's own table, an imported locale, or both merged
|
|
6
|
-
* via `defineMessages` below.
|
|
7
|
-
*
|
|
8
|
-
* Three deliberate properties:
|
|
9
|
-
* 1. English always resolves. A locale is an overlay, never a replacement,
|
|
10
|
-
* so a partial or missing translation shows English rather than a gap.
|
|
11
|
-
* 2. Params interpolate `{name}`.
|
|
12
|
-
* 3. A truly missing key (not in `overrides`, not in `EN`) renders as the
|
|
13
|
-
* key itself, not an empty string -- a visible `toast.deleted` in the UI
|
|
14
|
-
* is obviously wrong and names the exact key to add, where blank text
|
|
15
|
-
* just looks like a broken build.
|
|
16
|
-
*/
|
|
17
|
-
export function createTranslator(overrides) {
|
|
18
|
-
const table = overrides || {};
|
|
19
|
-
return function t(key, params) {
|
|
20
|
-
const template = table[key] ?? ENBase[key] ?? key;
|
|
21
|
-
if (!params) return template;
|
|
22
|
-
return template.replace(/\{(\w+)\}/g, (whole, name) => (name in params ? String(params[name]) : whole));
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Merges a locale over a base, for a host assembling its own table -- the documented way to combine a shipped locale with a few product-specific overrides. */
|
|
27
|
-
export function defineMessages(base, overrides) {
|
|
28
|
-
return Object.assign({}, base, overrides);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Keys in `base` that `locale` does not translate. What a translator has left to do. */
|
|
32
|
-
export function missingKeys(locale, base) {
|
|
33
|
-
const source = base || ENBase;
|
|
34
|
-
return Object.keys(source).filter((key) => locale[key] === undefined).sort();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Every locale that ships, for a host building a language switcher.
|
|
39
|
-
* Metadata only -- no message tables -- so listing the locales never pulls
|
|
40
|
-
* every translation file into a consumer's bundle; a host deep-imports the
|
|
41
|
-
* one it wants, e.g. `mailcraft-editor/src/core/i18n/bn.js`.
|
|
42
|
-
*/
|
|
43
|
-
export const LOCALES = [
|
|
44
|
-
{ tag: 'en', name: 'English' },
|
|
45
|
-
{ tag: 'ar', name: 'Arabic', rtl: true },
|
|
46
|
-
{ tag: 'bn', name: 'Bangla' },
|
|
47
|
-
{ tag: 'dz', name: 'Dzongkha' },
|
|
48
|
-
{ tag: 'bg', name: 'Bulgarian' },
|
|
49
|
-
{ tag: 'ca', name: 'Catalan' },
|
|
50
|
-
{ tag: 'cs', name: 'Czech' },
|
|
51
|
-
{ tag: 'da', name: 'Danish' },
|
|
52
|
-
{ tag: 'de', name: 'German' },
|
|
53
|
-
{ tag: 'de-CH', name: 'Swiss German' },
|
|
54
|
-
{ tag: 'el', name: 'Greek' },
|
|
55
|
-
{ tag: 'es', name: 'Spanish' },
|
|
56
|
-
{ tag: 'et', name: 'Estonian' },
|
|
57
|
-
{ tag: 'fi', name: 'Finnish' },
|
|
58
|
-
{ tag: 'fr', name: 'French' },
|
|
59
|
-
{ tag: 'hr', name: 'Croatian' },
|
|
60
|
-
{ tag: 'hu', name: 'Hungarian' },
|
|
61
|
-
{ tag: 'it', name: 'Italian' },
|
|
62
|
-
{ tag: 'lt', name: 'Lithuanian' },
|
|
63
|
-
{ tag: 'lv', name: 'Latvian' },
|
|
64
|
-
{ tag: 'nb', name: 'Norwegian Bokmål' },
|
|
65
|
-
{ tag: 'nl', name: 'Dutch' },
|
|
66
|
-
{ tag: 'pl', name: 'Polish' },
|
|
67
|
-
{ tag: 'pt', name: 'Portuguese' },
|
|
68
|
-
{ tag: 'ro', name: 'Romanian' },
|
|
69
|
-
{ tag: 'ru', name: 'Russian' },
|
|
70
|
-
{ tag: 'sk', name: 'Slovak' },
|
|
71
|
-
{ tag: 'sl', name: 'Slovenian' },
|
|
72
|
-
{ tag: 'sv', name: 'Swedish' },
|
|
73
|
-
{ tag: 'tr', name: 'Turkish' },
|
|
74
|
-
{ tag: 'uk', name: 'Ukrainian' },
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
/** `true` when the tag is written right to left. Metadata only -- `dir` is still what actually flips the layout. */
|
|
78
|
-
export function isRtl(tag) {
|
|
79
|
-
const entry = LOCALES.find((l) => l.tag === tag);
|
|
80
|
-
return entry ? entry.rtl === true : false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export { EN, MESSAGE_KEYS } from './en.js';
|
|
1
|
+
import { EN as ENBase } from './en.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds a translator. `overrides` is whatever a host passes as `.messages`
|
|
5
|
+
* on the element -- a host's own table, an imported locale, or both merged
|
|
6
|
+
* via `defineMessages` below.
|
|
7
|
+
*
|
|
8
|
+
* Three deliberate properties:
|
|
9
|
+
* 1. English always resolves. A locale is an overlay, never a replacement,
|
|
10
|
+
* so a partial or missing translation shows English rather than a gap.
|
|
11
|
+
* 2. Params interpolate `{name}`.
|
|
12
|
+
* 3. A truly missing key (not in `overrides`, not in `EN`) renders as the
|
|
13
|
+
* key itself, not an empty string -- a visible `toast.deleted` in the UI
|
|
14
|
+
* is obviously wrong and names the exact key to add, where blank text
|
|
15
|
+
* just looks like a broken build.
|
|
16
|
+
*/
|
|
17
|
+
export function createTranslator(overrides) {
|
|
18
|
+
const table = overrides || {};
|
|
19
|
+
return function t(key, params) {
|
|
20
|
+
const template = table[key] ?? ENBase[key] ?? key;
|
|
21
|
+
if (!params) return template;
|
|
22
|
+
return template.replace(/\{(\w+)\}/g, (whole, name) => (name in params ? String(params[name]) : whole));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Merges a locale over a base, for a host assembling its own table -- the documented way to combine a shipped locale with a few product-specific overrides. */
|
|
27
|
+
export function defineMessages(base, overrides) {
|
|
28
|
+
return Object.assign({}, base, overrides);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Keys in `base` that `locale` does not translate. What a translator has left to do. */
|
|
32
|
+
export function missingKeys(locale, base) {
|
|
33
|
+
const source = base || ENBase;
|
|
34
|
+
return Object.keys(source).filter((key) => locale[key] === undefined).sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Every locale that ships, for a host building a language switcher.
|
|
39
|
+
* Metadata only -- no message tables -- so listing the locales never pulls
|
|
40
|
+
* every translation file into a consumer's bundle; a host deep-imports the
|
|
41
|
+
* one it wants, e.g. `mailcraft-editor/src/core/i18n/bn.js`.
|
|
42
|
+
*/
|
|
43
|
+
export const LOCALES = [
|
|
44
|
+
{ tag: 'en', name: 'English' },
|
|
45
|
+
{ tag: 'ar', name: 'Arabic', rtl: true },
|
|
46
|
+
{ tag: 'bn', name: 'Bangla' },
|
|
47
|
+
{ tag: 'dz', name: 'Dzongkha' },
|
|
48
|
+
{ tag: 'bg', name: 'Bulgarian' },
|
|
49
|
+
{ tag: 'ca', name: 'Catalan' },
|
|
50
|
+
{ tag: 'cs', name: 'Czech' },
|
|
51
|
+
{ tag: 'da', name: 'Danish' },
|
|
52
|
+
{ tag: 'de', name: 'German' },
|
|
53
|
+
{ tag: 'de-CH', name: 'Swiss German' },
|
|
54
|
+
{ tag: 'el', name: 'Greek' },
|
|
55
|
+
{ tag: 'es', name: 'Spanish' },
|
|
56
|
+
{ tag: 'et', name: 'Estonian' },
|
|
57
|
+
{ tag: 'fi', name: 'Finnish' },
|
|
58
|
+
{ tag: 'fr', name: 'French' },
|
|
59
|
+
{ tag: 'hr', name: 'Croatian' },
|
|
60
|
+
{ tag: 'hu', name: 'Hungarian' },
|
|
61
|
+
{ tag: 'it', name: 'Italian' },
|
|
62
|
+
{ tag: 'lt', name: 'Lithuanian' },
|
|
63
|
+
{ tag: 'lv', name: 'Latvian' },
|
|
64
|
+
{ tag: 'nb', name: 'Norwegian Bokmål' },
|
|
65
|
+
{ tag: 'nl', name: 'Dutch' },
|
|
66
|
+
{ tag: 'pl', name: 'Polish' },
|
|
67
|
+
{ tag: 'pt', name: 'Portuguese' },
|
|
68
|
+
{ tag: 'ro', name: 'Romanian' },
|
|
69
|
+
{ tag: 'ru', name: 'Russian' },
|
|
70
|
+
{ tag: 'sk', name: 'Slovak' },
|
|
71
|
+
{ tag: 'sl', name: 'Slovenian' },
|
|
72
|
+
{ tag: 'sv', name: 'Swedish' },
|
|
73
|
+
{ tag: 'tr', name: 'Turkish' },
|
|
74
|
+
{ tag: 'uk', name: 'Ukrainian' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/** `true` when the tag is written right to left. Metadata only -- `dir` is still what actually flips the layout. */
|
|
78
|
+
export function isRtl(tag) {
|
|
79
|
+
const entry = LOCALES.find((l) => l.tag === tag);
|
|
80
|
+
return entry ? entry.rtl === true : false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { EN, MESSAGE_KEYS } from './en.js';
|
package/src/core/icons.js
CHANGED
|
@@ -67,7 +67,6 @@ const ICONS = {
|
|
|
67
67
|
hero: [['rect', 3, 4, 18, 16], 'M6 11h12', 'M6 15h6', ['rect', 14.5, 13.5, 4, 3]],
|
|
68
68
|
stats: ['M5 19v-7', 'M12 19V5', 'M19 19v-4', 'M3 19h18'],
|
|
69
69
|
gallery: [['rect', 3, 4, 7.5, 7.5], ['rect', 13.5, 4, 7.5, 7.5], ['rect', 3, 12.5, 7.5, 7.5], ['rect', 13.5, 12.5, 7.5, 7.5]],
|
|
70
|
-
embed: [['rect', 2.5, 5, 19, 14], 'M2.5 9h19', 'M10.5 11.5l3.5 2-3.5 2z'],
|
|
71
70
|
css: ['M5 4h14l-1.5 15L12 21l-5.5-2z', 'M8.5 9h7', 'M9 13h5'],
|
|
72
71
|
codeblock: [['rect', 3, 4, 18, 16], 'M9.5 10l-2 2 2 2', 'M14.5 10l2 2-2 2'],
|
|
73
72
|
data: ['M4 6c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z', 'M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6', 'M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3'],
|
package/src/core/ids.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const uid = () => Math.random().toString(36).slice(2, 9);
|
|
1
|
+
export const uid = () => Math.random().toString(36).slice(2, 9);
|