@bagelink/vue 1.15.417 → 1.15.425
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/admin.css +1 -1
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/core.css +1 -1
- package/dist/form-flow/FormFlowPlayer.vue.d.ts +12 -2
- package/dist/form-flow/FormFlowPlayer.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +2 -0
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/form-flow/structure.d.ts +2 -0
- package/dist/form-flow/structure.d.ts.map +1 -1
- package/dist/index.cjs +41 -41
- package/dist/index.css +1 -1
- package/dist/index.mjs +7373 -7323
- package/dist/ploni.css +1 -1
- package/dist/style.css +2 -2
- package/dist/utils/index.d.ts +15 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Avatar.vue +1 -1
- package/src/components/form/inputs/ArrayInput.vue +4 -11
- package/src/components/form/inputs/RadioGroup.vue +4 -3
- package/src/composables/useSchemaField.ts +19 -1
- package/src/form-flow/FormFlowPlayer.vue +20 -3
- package/src/form-flow/form-flow.ts +1 -0
- package/src/form-flow/schema-fields.ts +4 -0
- package/src/form-flow/structure.ts +2 -0
- package/src/styles/fonts/Ploni.css +32 -0
- package/src/utils/index.ts +48 -1
package/dist/utils/index.d.ts
CHANGED
|
@@ -2,15 +2,29 @@ import { Attributes, BglFormSchemaT, Path } from '../types/BagelForm';
|
|
|
2
2
|
export declare function debounce(fn: () => void, wait?: number): void;
|
|
3
3
|
export declare function slugify(str: string): string;
|
|
4
4
|
export declare function keyToLabel(key?: string): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Convert stored rich-text HTML to compact plain text for tables, labels, and
|
|
7
|
+
* other read-only summaries. Block elements are separated so paragraphs do not
|
|
8
|
+
* run together.
|
|
9
|
+
*/
|
|
10
|
+
export declare function richTextToPlainText(value: string): string;
|
|
11
|
+
export declare function containsHtml(value: string): boolean;
|
|
5
12
|
export declare function copyText(text: string, cb?: (msg: string) => void): Promise<void>;
|
|
6
13
|
export declare function initials(...strArr: string[]): string;
|
|
14
|
+
export interface SanitizeHtmlOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Remove inline presentation and layout attributes while preserving semantic
|
|
17
|
+
* markup such as paragraphs, lists, emphasis, and links.
|
|
18
|
+
*/
|
|
19
|
+
resetFormatting?: boolean;
|
|
20
|
+
}
|
|
7
21
|
/**
|
|
8
22
|
* Strip script-capable content from an HTML string so it is safe for v-html.
|
|
9
23
|
* Removes <script>/<style>/<iframe>/<object>/<embed> elements, on* event
|
|
10
24
|
* handler attributes, and javascript: / data: URLs in href/src.
|
|
11
25
|
* Allowlist-free by design — formatting tags (b, i, a, span…) pass through.
|
|
12
26
|
*/
|
|
13
|
-
export declare function sanitizeHtml(html: string): string;
|
|
27
|
+
export declare function sanitizeHtml(html: string, options?: SanitizeHtmlOptions): string;
|
|
14
28
|
export declare function useEscape(event: KeyboardEvent, closeModel: () => void): void;
|
|
15
29
|
export declare function classify(fieldVal?: any, row?: any, ...classes: any[]): string;
|
|
16
30
|
export declare function bindAttrs<T, P extends Path<T>>(attrs?: Attributes<T, P>, fieldVal?: any, row?: T): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAK1E,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,IAAI,GAAE,MAAY,QAO1D;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,UAMlC;AAED,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ3D;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,iBAGtE;AAED,wBAAgB,QAAQ,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,UAG3C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -61,7 +61,7 @@ const themed = computed(() => props.color
|
|
|
61
61
|
>
|
|
62
62
|
<Image v-if="src" :src="src" :alt="alt || name" />
|
|
63
63
|
<Icon v-else-if="icon" :icon="icon" :size="resolvedSize / 40" />
|
|
64
|
-
<p v-else :style="{ 'line-height':
|
|
64
|
+
<p v-else :style="{ 'line-height': '1', 'font-size': `calc(1.5rem * ${resolvedSize} / 50)` }" class="">
|
|
65
65
|
{{ (fallback || initials(name || '')).toUpperCase() }}
|
|
66
66
|
</p>
|
|
67
67
|
</div>
|
|
@@ -3,7 +3,7 @@ import type { Ref, WritableComputedRef } from 'vue'
|
|
|
3
3
|
|
|
4
4
|
import type { DraggableEvent } from '../../draggable/useDraggable'
|
|
5
5
|
|
|
6
|
-
import { Btn, resolveI18n, useI18n, pathKeyToURL } from '@bagelink/vue'
|
|
6
|
+
import { Btn, resolveI18n, useI18n, pathKeyToURL, richTextToPlainText } from '@bagelink/vue'
|
|
7
7
|
import { computed, ref, watch, useSlots, type VNode } from 'vue'
|
|
8
8
|
import Draggable from '../../draggable/Draggable.vue'
|
|
9
9
|
import type { BagelInputShellProps } from './bagelInputShell'
|
|
@@ -48,26 +48,19 @@ const itemLabelPrefix = computed(() => {
|
|
|
48
48
|
return $t('arrayInput.item')
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
const IDENTIFIER_FIELDS = ['title', 'name', 'label', 'displayName', 'display_name', 'heading', 'caption', 'text']
|
|
51
|
+
const IDENTIFIER_FIELDS = ['title', 'name', 'key', 'label', 'displayName', 'display_name', 'heading', 'caption', 'text']
|
|
52
52
|
const IMAGE_FIELDS = ['image', 'thumbnail', 'avatar', 'photo', 'picture', 'profilePic', 'profile_pic', 'icon', 'logo']
|
|
53
53
|
|
|
54
|
-
/** Rich-text fields often store HTML; collapse to plain text for the collapsed row label. */
|
|
55
|
-
function toPlainText(value: string): string {
|
|
56
|
-
if (!value.includes('<') && !value.includes('&')) return value.trim()
|
|
57
|
-
const doc = new DOMParser().parseFromString(value, 'text/html')
|
|
58
|
-
return (doc.body.textContent || '').replace(/\s+/g, ' ').trim()
|
|
59
|
-
}
|
|
60
|
-
|
|
61
54
|
function getItemIdentifier(item: T): string | null {
|
|
62
55
|
if (typeof item === 'string' && item.trim().length > 0) {
|
|
63
|
-
const plain =
|
|
56
|
+
const plain = richTextToPlainText(item)
|
|
64
57
|
return plain.length > 0 ? plain : null
|
|
65
58
|
}
|
|
66
59
|
if (item !== null && item !== undefined && typeof item === 'object') {
|
|
67
60
|
for (const field of IDENTIFIER_FIELDS) {
|
|
68
61
|
const val = (item as Record<string, unknown>)[field]
|
|
69
62
|
if (typeof val === 'string' && val.trim().length > 0) {
|
|
70
|
-
const plain =
|
|
63
|
+
const plain = richTextToPlainText(val)
|
|
71
64
|
if (plain.length > 0) return plain
|
|
72
65
|
}
|
|
73
66
|
}
|
|
@@ -38,6 +38,7 @@ const props = withDefaults(
|
|
|
38
38
|
invertedActive?: boolean
|
|
39
39
|
align?: 'start' | 'center' | 'end' | 'top' | 'bottom'
|
|
40
40
|
hideRadio?: boolean
|
|
41
|
+
hideLabelWhenImage?: boolean
|
|
41
42
|
bgColor?: string
|
|
42
43
|
activeBgColor?: string
|
|
43
44
|
activeTextColor?: string
|
|
@@ -189,10 +190,10 @@ function handleChange() {
|
|
|
189
190
|
<div class="flex w-100 gap-1 flex-wrap m_gap-05 m_gap-row-025"
|
|
190
191
|
:class="{ 'txt-center justify-content-center mx-auto': textAlign === 'center', 'radio-option-content--centered-image': textAlign === 'center' && opt.imgSrc, 'txt-end justify-content-end': textAlign === 'right', 'txt-start justify-content-start': textAlign === 'left' }"
|
|
191
192
|
:style="{ color: textColor }">
|
|
192
|
-
<img v-if="opt.imgSrc" class="py-025 radius-05 m_w40 object-fit" height="40" width="40" :src="opt.imgSrc" :alt="opt.imgAlt || opt.label || ''">
|
|
193
|
+
<img v-if="opt.imgSrc" class="py-025 radius-05 m_w40 object-fit" height="40" width="40" :src="opt.imgSrc" :alt="(hideLabelWhenImage ? opt.label || opt.imgAlt : opt.imgAlt || opt.label) || ''">
|
|
193
194
|
<Icon v-else-if="opt.icon" :icon="opt.icon" :size="1" />
|
|
194
|
-
<div
|
|
195
|
-
<div v-if="opt.label" class="m-0 m_txt-14 line-height-14" v-html="sanitizeHtml(opt.label)" />
|
|
195
|
+
<div v-if="(opt.label && !(hideLabelWhenImage && opt.imgSrc)) || opt.subLabel">
|
|
196
|
+
<div v-if="opt.label && !(hideLabelWhenImage && opt.imgSrc)" class="m-0 m_txt-14 line-height-14" v-html="sanitizeHtml(opt.label)" />
|
|
196
197
|
<p v-if="opt.subLabel" class="txt-gray txt-12 m-0 pt-025">{{ opt.subLabel }}</p>
|
|
197
198
|
</div>
|
|
198
199
|
<slot name="radioItem" v-bind="opt" />
|
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
ColorInput,
|
|
19
19
|
RangeInput,
|
|
20
20
|
EmailInput,
|
|
21
|
-
|
|
21
|
+
containsHtml,
|
|
22
|
+
getNestedValue,
|
|
23
|
+
richTextToPlainText
|
|
22
24
|
} from '@bagelink/vue'
|
|
23
25
|
import { h, isVNode } from 'vue'
|
|
24
26
|
|
|
@@ -388,6 +390,22 @@ export function useSchemaField<T extends { [key: string]: any }>(optns: UseSchem
|
|
|
388
390
|
])
|
|
389
391
|
}
|
|
390
392
|
|
|
393
|
+
if (!slotContent && mode === 'table' && typeof transformedValue === 'string') {
|
|
394
|
+
const plainText = richTextToPlainText(transformedValue)
|
|
395
|
+
if (containsHtml(transformedValue)) {
|
|
396
|
+
return h('div', { class: 'ellipsis-1', title: plainText }, plainText)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!slotContent && mode === 'table' && Component === RichText) {
|
|
401
|
+
const plainText = richTextToPlainText(String(transformedValue ?? ''))
|
|
402
|
+
return h(
|
|
403
|
+
'div',
|
|
404
|
+
{ class: 'ellipsis-1', title: plainText },
|
|
405
|
+
plainText
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
391
409
|
return slotContent || h(Component as any, props, componentSlots)
|
|
392
410
|
}
|
|
393
411
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* button is always "Submit").
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { FormStructure } from './structure'
|
|
18
|
+
import type { FormStep, FormStructure } from './structure'
|
|
19
19
|
import type { Slots } from 'vue'
|
|
20
20
|
import { computed, ref, useSlots, watch } from 'vue'
|
|
21
21
|
import { useI18n } from '../i18n'
|
|
@@ -53,6 +53,15 @@ export interface Props {
|
|
|
53
53
|
hideCounter?: boolean
|
|
54
54
|
/** Optional async submit handler; awaited before `complete` is emitted. */
|
|
55
55
|
onComplete?: (data: Record<string, any>) => Promise<void> | void
|
|
56
|
+
/**
|
|
57
|
+
* Runs after the current step validates but before advancing or submitting.
|
|
58
|
+
* Return false when the host handled navigation and the player should stop.
|
|
59
|
+
*/
|
|
60
|
+
beforePrimary?: (context: {
|
|
61
|
+
data: Record<string, any>
|
|
62
|
+
currentStep: FormStep
|
|
63
|
+
isTerminal: boolean
|
|
64
|
+
}) => Promise<boolean | void> | boolean | void
|
|
56
65
|
/** Resolve declarative x-options-source fields in stored JSON Schemas. */
|
|
57
66
|
loadOptionsSource?: JSONSchemaOptionsSourceLoader
|
|
58
67
|
}
|
|
@@ -182,9 +191,16 @@ function validateStep(): boolean {
|
|
|
182
191
|
async function onPrimary() {
|
|
183
192
|
if (!validateStep()) return
|
|
184
193
|
|
|
194
|
+
const step = currentStep.value
|
|
195
|
+
const proceed = await props.beforePrimary?.({
|
|
196
|
+
data: { ...formData.value },
|
|
197
|
+
currentStep: step!,
|
|
198
|
+
isTerminal: isTerminal.value,
|
|
199
|
+
})
|
|
200
|
+
if (proceed === false) return
|
|
201
|
+
|
|
185
202
|
if (isTerminal.value) {
|
|
186
203
|
submitting.value = true
|
|
187
|
-
const step = currentStep.value
|
|
188
204
|
try {
|
|
189
205
|
// `skipSubmit` terminals (e.g. disqualification leaves) end the flow
|
|
190
206
|
// without creating a submission — the host's onComplete is bypassed,
|
|
@@ -331,6 +347,7 @@ defineExpose({ formData, currentKey, nextKey, isTerminal, progress, validateStep
|
|
|
331
347
|
:step-position="stepPosition"
|
|
332
348
|
:progress="progress"
|
|
333
349
|
:current-key="currentKey"
|
|
350
|
+
:primary-label="currentStep?.primaryLabel"
|
|
334
351
|
:auto-advances="autoAdvances"
|
|
335
352
|
>
|
|
336
353
|
<div class="ffp-nav flex flex-center gap-1">
|
|
@@ -353,7 +370,7 @@ defineExpose({ formData, currentKey, nextKey, isTerminal, progress, validateStep
|
|
|
353
370
|
<Btn
|
|
354
371
|
v-if="isTerminal"
|
|
355
372
|
class="ffp-nav-btn"
|
|
356
|
-
:value="$t?.('multiStep.complete')
|
|
373
|
+
:value="currentStep?.primaryLabel || $t?.('multiStep.complete') || 'Submit'"
|
|
357
374
|
:loading="submitting"
|
|
358
375
|
@click="onPrimary"
|
|
359
376
|
>
|
|
@@ -1259,6 +1259,7 @@ function applyDesignerConfig(config: Record<string, any>, prop: JSONSchemaProper
|
|
|
1259
1259
|
if (p['x-radio-inline']) config.inline = true
|
|
1260
1260
|
if (p['x-radio-thin']) config.thin = true
|
|
1261
1261
|
if (p['x-radio-hide-radio']) config.hideRadio = true
|
|
1262
|
+
if (p['x-radio-hide-label-when-image']) config.hideLabelWhenImage = true
|
|
1262
1263
|
set('align', 'x-radio-align')
|
|
1263
1264
|
set('textAlign', 'x-radio-text-align')
|
|
1264
1265
|
// Select
|
|
@@ -99,6 +99,7 @@ export interface FormField {
|
|
|
99
99
|
rangeMulti?: boolean
|
|
100
100
|
radioThin?: boolean
|
|
101
101
|
radioHideRadio?: boolean
|
|
102
|
+
radioHideLabelWhenImage?: boolean
|
|
102
103
|
radioAlign?: 'start' | 'center' | 'end'
|
|
103
104
|
/** Lay radio options out horizontally (inline) instead of stacked. */
|
|
104
105
|
radioInline?: boolean
|
|
@@ -165,6 +166,7 @@ export interface JSONSchemaProperty {
|
|
|
165
166
|
'x-range-multi'?: boolean
|
|
166
167
|
'x-radio-thin'?: boolean
|
|
167
168
|
'x-radio-hide-radio'?: boolean
|
|
169
|
+
'x-radio-hide-label-when-image'?: boolean
|
|
168
170
|
'x-radio-align'?: 'start' | 'center' | 'end'
|
|
169
171
|
'x-radio-inline'?: boolean
|
|
170
172
|
'x-radio-size'?: 's' | 'm' | 'l'
|
|
@@ -293,6 +295,7 @@ export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[
|
|
|
293
295
|
rangeMulti: prop['x-range-multi'],
|
|
294
296
|
radioThin: prop['x-radio-thin'],
|
|
295
297
|
radioHideRadio: prop['x-radio-hide-radio'],
|
|
298
|
+
radioHideLabelWhenImage: prop['x-radio-hide-label-when-image'],
|
|
296
299
|
radioAlign: prop['x-ui']?.radioAlign ?? prop['x-radio-align'],
|
|
297
300
|
radioInline: prop['x-radio-inline'],
|
|
298
301
|
radioSize: prop['x-radio-size'],
|
|
@@ -457,6 +460,7 @@ export function fieldsToSchema(
|
|
|
457
460
|
if (field.rangeMulti === true) prop['x-range-multi'] = true
|
|
458
461
|
if (field.radioThin === true) prop['x-radio-thin'] = true
|
|
459
462
|
if (field.radioHideRadio === true) prop['x-radio-hide-radio'] = true
|
|
463
|
+
if (field.radioHideLabelWhenImage === true) prop['x-radio-hide-label-when-image'] = true
|
|
460
464
|
if (field.radioAlign) prop['x-ui'] = { ...prop['x-ui'], radioAlign: field.radioAlign }
|
|
461
465
|
if (field.radioInline === true) prop['x-radio-inline'] = true
|
|
462
466
|
if (field.radioSize) prop['x-radio-size'] = field.radioSize
|
|
@@ -31,6 +31,8 @@ export interface FormStep {
|
|
|
31
31
|
* (final step or an early-termination leaf). Empty => no redirect.
|
|
32
32
|
*/
|
|
33
33
|
redirectUrl?: string | null
|
|
34
|
+
/** Optional primary-button label for this step (for example, "Next"). */
|
|
35
|
+
primaryLabel?: string | null
|
|
34
36
|
/**
|
|
35
37
|
* Terminal step that ends the flow *without* creating a submission — e.g. a
|
|
36
38
|
* disqualification / "not a fit" leaf. The step's own content (title + fields)
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
font-weight: 200;
|
|
5
5
|
font-stretch: 100%;
|
|
6
6
|
font-display: swap;
|
|
7
|
+
size-adjust: 110%;
|
|
8
|
+
ascent-override: 68%;
|
|
9
|
+
descent-override: 32%;
|
|
10
|
+
line-gap-override: 0%;
|
|
7
11
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultralight-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultralight-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultralight-aaa.eot) format('eot')
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -13,6 +17,10 @@
|
|
|
13
17
|
font-weight: 300;
|
|
14
18
|
font-stretch: 100%;
|
|
15
19
|
font-display: swap;
|
|
20
|
+
size-adjust: 110%;
|
|
21
|
+
ascent-override: 68%;
|
|
22
|
+
descent-override: 32%;
|
|
23
|
+
line-gap-override: 0%;
|
|
16
24
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-light-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-light-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-light-aaa.eot) format('eot')
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -22,6 +30,10 @@
|
|
|
22
30
|
font-weight: 400;
|
|
23
31
|
font-stretch: 100%;
|
|
24
32
|
font-display: swap;
|
|
33
|
+
size-adjust: 110%;
|
|
34
|
+
ascent-override: 68%;
|
|
35
|
+
descent-override: 32%;
|
|
36
|
+
line-gap-override: 0%;
|
|
25
37
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-regular-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-regular-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-regular-aaa.eot) format('eot')
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -31,6 +43,10 @@
|
|
|
31
43
|
font-weight: 500;
|
|
32
44
|
font-stretch: 100%;
|
|
33
45
|
font-display: swap;
|
|
46
|
+
size-adjust: 110%;
|
|
47
|
+
ascent-override: 68%;
|
|
48
|
+
descent-override: 32%;
|
|
49
|
+
line-gap-override: 0%;
|
|
34
50
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-medium-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-medium-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-medium-aaa.eot) format('eot')
|
|
35
51
|
}
|
|
36
52
|
|
|
@@ -40,6 +56,10 @@
|
|
|
40
56
|
font-weight: 600;
|
|
41
57
|
font-stretch: 100%;
|
|
42
58
|
font-display: swap;
|
|
59
|
+
size-adjust: 110%;
|
|
60
|
+
ascent-override: 68%;
|
|
61
|
+
descent-override: 32%;
|
|
62
|
+
line-gap-override: 0%;
|
|
43
63
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-demibold-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-demibold-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-demibold-aaa.eot) format('eot')
|
|
44
64
|
}
|
|
45
65
|
|
|
@@ -49,6 +69,10 @@
|
|
|
49
69
|
font-weight: 700;
|
|
50
70
|
font-stretch: 100%;
|
|
51
71
|
font-display: swap;
|
|
72
|
+
size-adjust: 110%;
|
|
73
|
+
ascent-override: 68%;
|
|
74
|
+
descent-override: 32%;
|
|
75
|
+
line-gap-override: 0%;
|
|
52
76
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-bold-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-bold-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-bold-aaa.eot) format('eot')
|
|
53
77
|
}
|
|
54
78
|
|
|
@@ -58,6 +82,10 @@
|
|
|
58
82
|
font-weight: 800;
|
|
59
83
|
font-stretch: 100%;
|
|
60
84
|
font-display: swap;
|
|
85
|
+
size-adjust: 110%;
|
|
86
|
+
ascent-override: 68%;
|
|
87
|
+
descent-override: 32%;
|
|
88
|
+
line-gap-override: 0%;
|
|
61
89
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultrabold-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultrabold-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-ultrabold-aaa.eot) format('eot')
|
|
62
90
|
}
|
|
63
91
|
|
|
@@ -67,5 +95,9 @@
|
|
|
67
95
|
font-weight: 900;
|
|
68
96
|
font-stretch: 100%;
|
|
69
97
|
font-display: swap;
|
|
98
|
+
size-adjust: 110%;
|
|
99
|
+
ascent-override: 68%;
|
|
100
|
+
descent-override: 32%;
|
|
101
|
+
line-gap-override: 0%;
|
|
70
102
|
src: url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-black-aaa.woff2) format('woff2'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-black-aaa.woff) format('woff'), url(https://bagel.sfo2.cdn.digitaloceanspaces.com/Fonts/ploni/ploni-black-aaa.eot) format('eot')
|
|
71
103
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -30,6 +30,40 @@ export function keyToLabel(key?: string): string | undefined {
|
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const plainTextCache = new Map<string, string>()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert stored rich-text HTML to compact plain text for tables, labels, and
|
|
37
|
+
* other read-only summaries. Block elements are separated so paragraphs do not
|
|
38
|
+
* run together.
|
|
39
|
+
*/
|
|
40
|
+
export function richTextToPlainText(value: string): string {
|
|
41
|
+
if (!value.includes('<') && !value.includes('&')) return value.trim()
|
|
42
|
+
|
|
43
|
+
const cached = plainTextCache.get(value)
|
|
44
|
+
if (cached !== undefined) return cached
|
|
45
|
+
|
|
46
|
+
if (typeof DOMParser === 'undefined') {
|
|
47
|
+
return value.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const doc = new DOMParser().parseFromString(value, 'text/html')
|
|
51
|
+
doc.body
|
|
52
|
+
.querySelectorAll('p, div, li, br, h1, h2, h3, h4, h5, h6')
|
|
53
|
+
.forEach(element => element.after(' '))
|
|
54
|
+
const result = (doc.body.textContent || '')
|
|
55
|
+
.replace(/\u00a0/g, ' ')
|
|
56
|
+
.replace(/\s+/g, ' ')
|
|
57
|
+
.trim()
|
|
58
|
+
|
|
59
|
+
plainTextCache.set(value, result)
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function containsHtml(value: string): boolean {
|
|
64
|
+
return /<\/?[a-z][\s\S]*>/i.test(value)
|
|
65
|
+
}
|
|
66
|
+
|
|
33
67
|
export async function copyText(text: string, cb?: (msg: string) => void) {
|
|
34
68
|
await navigator.clipboard.writeText(text)
|
|
35
69
|
if (cb) { cb('Copied to clipboard') }
|
|
@@ -40,13 +74,21 @@ export function initials(...strArr: string[]) {
|
|
|
40
74
|
return strArr.map(str => str.charAt(0)).join('')
|
|
41
75
|
}
|
|
42
76
|
|
|
77
|
+
export interface SanitizeHtmlOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Remove inline presentation and layout attributes while preserving semantic
|
|
80
|
+
* markup such as paragraphs, lists, emphasis, and links.
|
|
81
|
+
*/
|
|
82
|
+
resetFormatting?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
43
85
|
/**
|
|
44
86
|
* Strip script-capable content from an HTML string so it is safe for v-html.
|
|
45
87
|
* Removes <script>/<style>/<iframe>/<object>/<embed> elements, on* event
|
|
46
88
|
* handler attributes, and javascript: / data: URLs in href/src.
|
|
47
89
|
* Allowlist-free by design — formatting tags (b, i, a, span…) pass through.
|
|
48
90
|
*/
|
|
49
|
-
export function sanitizeHtml(html: string): string {
|
|
91
|
+
export function sanitizeHtml(html: string, options: SanitizeHtmlOptions = {}): string {
|
|
50
92
|
if (!html || typeof document === 'undefined') { return html }
|
|
51
93
|
const template = document.createElement('template')
|
|
52
94
|
template.innerHTML = html
|
|
@@ -64,6 +106,11 @@ export function sanitizeHtml(html: string): string {
|
|
|
64
106
|
|| ((name === 'href' || name === 'src' || name === 'xlink:href')
|
|
65
107
|
&& (value.startsWith('javascript:') || value.startsWith('data:')))) {
|
|
66
108
|
child.removeAttribute(attr.name)
|
|
109
|
+
} else if (
|
|
110
|
+
options.resetFormatting
|
|
111
|
+
&& ['style', 'class', 'width', 'height', 'align', 'bgcolor', 'color', 'face', 'size'].includes(name)
|
|
112
|
+
) {
|
|
113
|
+
child.removeAttribute(attr.name)
|
|
67
114
|
}
|
|
68
115
|
}
|
|
69
116
|
walk(child)
|