glib-web 4.39.5 → 4.39.6
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/.claude/settings.local.json +3 -1
- package/app.vue +26 -13
- package/components/_message.vue +2 -12
- package/components/fields/richText2.vue +20 -22
- package/components/fields/textarea.vue +7 -1
- package/package.json +1 -1
- package/templates/thumbnail.vue +25 -8
- package/utils/format.js +35 -2
package/app.vue
CHANGED
|
@@ -385,14 +385,6 @@ body,
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
.v-input__details {
|
|
389
|
-
min-height: 0;
|
|
390
|
-
margin-bottom: 0;
|
|
391
|
-
|
|
392
|
-
.v-messages {
|
|
393
|
-
min-height: 0;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
388
|
|
|
397
389
|
.v-label {
|
|
398
390
|
opacity: 1;
|
|
@@ -407,13 +399,34 @@ body,
|
|
|
407
399
|
pointer-events: none;
|
|
408
400
|
}
|
|
409
401
|
|
|
402
|
+
/* 1) Make input a positioning root */
|
|
403
|
+
.v-input {
|
|
404
|
+
position: relative;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* 2) Move details out of the layout flow */
|
|
410
408
|
.v-input .v-input__details {
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
position: absolute;
|
|
410
|
+
top: 100%;
|
|
411
|
+
left: 0;
|
|
412
|
+
right: 0;
|
|
413
413
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
414
|
+
/* remove Vuetify spacing */
|
|
415
|
+
height: auto !important;
|
|
416
|
+
min-height: 0 !important;
|
|
417
|
+
margin: 0 !important;
|
|
418
|
+
padding-top: 2px !important;
|
|
419
|
+
|
|
420
|
+
/* avoid layout shift */
|
|
421
|
+
pointer-events: none;
|
|
422
|
+
/* optional */
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* 3) Allow counter & error text to be readable */
|
|
426
|
+
.v-input__details * {
|
|
427
|
+
pointer-events: auto;
|
|
428
|
+
/* let user select/copy text */
|
|
429
|
+
background: transparent;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
432
|
.v-btn--disabled {
|
package/components/_message.vue
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
<!-- TODO: Deprecate this in favor of supporting `body` in dialogs, banners, and sheets -->
|
|
2
1
|
<template>
|
|
3
|
-
<div
|
|
4
|
-
<div v-else class="unformatted">{{this.spec.message}}</div>
|
|
2
|
+
<div class="unformatted">{{this.spec.message}}</div>
|
|
5
3
|
</template>
|
|
6
4
|
|
|
7
5
|
<script>
|
|
8
6
|
export default {
|
|
9
|
-
props: ["spec"]
|
|
10
|
-
computed: {
|
|
11
|
-
compiledText() {
|
|
12
|
-
return Utils.format.markdown(this.spec.message);
|
|
13
|
-
},
|
|
14
|
-
isMarkdown() {
|
|
15
|
-
return this.spec.message_format == 'markdown'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
7
|
+
props: ["spec"]
|
|
18
8
|
};
|
|
19
9
|
</script>
|
|
20
10
|
|
|
@@ -76,14 +76,8 @@ export default defineComponent({
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
function sanitizedValue() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// if (g1) {
|
|
82
|
-
// return `src="{{image${++index}}}"`;
|
|
83
|
-
// } else if (g2) {
|
|
84
|
-
// return `href="{{image${++index}}}"`;
|
|
85
|
-
// }
|
|
86
|
-
// });
|
|
79
|
+
if (!quill) return props.spec.value || '';
|
|
80
|
+
|
|
87
81
|
const value = quill.root.innerHTML;
|
|
88
82
|
|
|
89
83
|
return produce === 'markdown' ? format.htmlToMarkdown(value) : value;
|
|
@@ -152,28 +146,32 @@ export default defineComponent({
|
|
|
152
146
|
["link"],
|
|
153
147
|
]
|
|
154
148
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
const mentionList = props.spec.mentionList || [];
|
|
150
|
+
|
|
151
|
+
if (mentionList.length > 0) {
|
|
152
|
+
const simpleSource = (searchTerm, renderList) => {
|
|
153
|
+
const normalizedTerm = (searchTerm || '').toLowerCase();
|
|
154
|
+
const matches = mentionList.toSorted((a, b) => a.localeCompare(b)).map((v, index) => ({ id: index + 1, value: v }));
|
|
158
155
|
|
|
159
|
-
if (
|
|
156
|
+
if (normalizedTerm.length === 0) {
|
|
160
157
|
renderList(matches, searchTerm);
|
|
161
|
-
|
|
162
|
-
renderList(matches.filter((v) => v.value.toLowerCase().includes(searchTerm)), searchTerm);
|
|
158
|
+
return;
|
|
163
159
|
}
|
|
164
|
-
};
|
|
165
160
|
|
|
166
|
-
|
|
161
|
+
renderList(matches.filter((v) => v.value.toLowerCase().includes(normalizedTerm)), searchTerm);
|
|
162
|
+
};
|
|
167
163
|
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
164
|
+
const advancedSource = (searchTerm, renderList) => {
|
|
165
|
+
const compareFn = (a, b) => a.value.localeCompare(b.value);
|
|
166
|
+
const options = mentionList.toSorted(compareFn);
|
|
167
|
+
const normalizedTerm = (searchTerm || '').toLowerCase();
|
|
168
|
+
const isGrouped = !!mentionList[0].group;
|
|
171
169
|
let matches;
|
|
172
170
|
|
|
173
|
-
if (
|
|
171
|
+
if (normalizedTerm.length === 0) {
|
|
174
172
|
matches = options;
|
|
175
173
|
} else {
|
|
176
|
-
matches = options.filter((v) => v.value.toLowerCase().includes(
|
|
174
|
+
matches = options.filter((v) => v.value.toLowerCase().includes(normalizedTerm));
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
if (isGrouped) {
|
|
@@ -191,7 +189,7 @@ export default defineComponent({
|
|
|
191
189
|
renderList(matches, searchTerm);
|
|
192
190
|
};
|
|
193
191
|
|
|
194
|
-
const source = typeof
|
|
192
|
+
const source = typeof mentionList[0] === 'object' ? advancedSource : simpleSource;
|
|
195
193
|
|
|
196
194
|
Quill.register({ "blots/mention": MentionBlot, "modules/mention": Mention });
|
|
197
195
|
modules.mention = {
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
:placeholder="spec.placeholder" :maxlength="spec.maxLength || 255" :readonly="spec.readOnly" :height="height"
|
|
5
5
|
:rules="$validation()" counter :outlined="$classes().includes('outlined')" :disabled="inputDisabled"
|
|
6
6
|
:no-resize="$classes().includes('no-resize')" validate-on="blur" :variant="variant" :density="density"
|
|
7
|
-
@update:modelValue="onChange()" persistent-placeholder :clearable="spec.clearable"
|
|
7
|
+
@update:modelValue="onChange()" persistent-placeholder :clearable="spec.clearable">
|
|
8
|
+
|
|
9
|
+
<template #counter="{ counter }">
|
|
10
|
+
<span v-if="!spec.readOnly">{{ counter }}</span>
|
|
11
|
+
</template>
|
|
12
|
+
</v-textarea>
|
|
13
|
+
|
|
8
14
|
</div>
|
|
9
15
|
</template>
|
|
10
16
|
|
package/package.json
CHANGED
package/templates/thumbnail.vue
CHANGED
|
@@ -49,15 +49,20 @@
|
|
|
49
49
|
|
|
50
50
|
<common-badge :spec="spec">
|
|
51
51
|
<div>
|
|
52
|
-
<v-list-item-title class="title">
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
<v-list-item-title class="title">
|
|
53
|
+
<div v-if="isMarkdown" v-html="formattedTitle"></div>
|
|
54
|
+
<template v-else>{{ spec.title }}</template>
|
|
55
|
+
</v-list-item-title>
|
|
56
|
+
|
|
57
|
+
<div class="subtitle" v-if="spec.subtitle">
|
|
58
|
+
<div v-if="isMarkdown" v-html="formattedSubtitle"></div>
|
|
59
|
+
<template v-else>{{ spec.subtitle }}</template>
|
|
60
|
+
</div>
|
|
57
61
|
|
|
58
|
-
<v-list-item-subtitle class="subsubtitle" v-if="spec.subsubtitle">
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
<v-list-item-subtitle class="subsubtitle" v-if="spec.subsubtitle">
|
|
63
|
+
<div v-if="isMarkdown" v-html="formattedSubsubtitle"></div>
|
|
64
|
+
<template v-else>{{ spec.subsubtitle }}</template>
|
|
65
|
+
</v-list-item-subtitle>
|
|
61
66
|
<div v-if="hasChips" class="chips">
|
|
62
67
|
<template v-for="(item, index) in chips" :key="index">
|
|
63
68
|
<common-chip :spec="item" />
|
|
@@ -127,6 +132,18 @@ export default {
|
|
|
127
132
|
return Object.assign({ view: 'avatar' }, val);
|
|
128
133
|
});
|
|
129
134
|
},
|
|
135
|
+
isMarkdown() {
|
|
136
|
+
return this.spec.textFormat == 'markdown';
|
|
137
|
+
},
|
|
138
|
+
formattedTitle() {
|
|
139
|
+
return Utils.format.markdown(this.spec.title);
|
|
140
|
+
},
|
|
141
|
+
formattedSubtitle() {
|
|
142
|
+
return Utils.format.markdown(this.spec.subtitle);
|
|
143
|
+
},
|
|
144
|
+
formattedSubsubtitle() {
|
|
145
|
+
return Utils.format.markdown(this.spec.subsubtitle);
|
|
146
|
+
},
|
|
130
147
|
clickCondition() {
|
|
131
148
|
if (this.spec.onClick || this.spec.onLongPress) {
|
|
132
149
|
// This will show the clickable indication
|
package/utils/format.js
CHANGED
|
@@ -2,6 +2,9 @@ import { marked } from "marked";
|
|
|
2
2
|
import { gfm } from "turndown-plugin-gfm";
|
|
3
3
|
import TurndownService from "turndown";
|
|
4
4
|
|
|
5
|
+
// Shared Marked configuration so Markdown ↔ Quill keeps GFM features and avoids extra IDs.
|
|
6
|
+
const markedOptions = { gfm: true, breaks: true, headerIds: false, mangle: false };
|
|
7
|
+
|
|
5
8
|
export default class {
|
|
6
9
|
static markdownForEditor(text) {
|
|
7
10
|
|
|
@@ -23,12 +26,12 @@ export default class {
|
|
|
23
26
|
marked.use({ renderer });
|
|
24
27
|
|
|
25
28
|
return marked.parse(
|
|
26
|
-
text,
|
|
29
|
+
text, markedOptions
|
|
27
30
|
);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
static markdown(text) {
|
|
31
|
-
return marked.parse(text);
|
|
34
|
+
return marked.parse(text, markedOptions);
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
static htmlToMarkdown(text) {
|
|
@@ -47,6 +50,36 @@ export default class {
|
|
|
47
50
|
return "```\n" + content + "```";
|
|
48
51
|
},
|
|
49
52
|
});
|
|
53
|
+
|
|
54
|
+
// Quill renders every list inside <ol> and differentiates via data-list attributes.
|
|
55
|
+
// Ensure items marked as "bullet" remain unordered when converted back to Markdown.
|
|
56
|
+
turndownService.addRule("quillBulletList", {
|
|
57
|
+
filter(node) {
|
|
58
|
+
return node.nodeName === "LI" && node.getAttribute("data-list") === "bullet";
|
|
59
|
+
},
|
|
60
|
+
replacement(content, node, options) {
|
|
61
|
+
const normalized = content
|
|
62
|
+
.replace(/^\n+/, "")
|
|
63
|
+
.replace(/\n+$/, "\n")
|
|
64
|
+
.replace(/\n/gm, "\n ");
|
|
65
|
+
const suffix = node.nextSibling && !/\n$/.test(normalized) ? "\n" : "";
|
|
66
|
+
return `${options.bulletListMarker} ${normalized}${suffix}`;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
turndownService.addRule("mention", {
|
|
71
|
+
filter(node) {
|
|
72
|
+
return node.nodeName === "SPAN" && node.classList && node.classList.contains("mention");
|
|
73
|
+
},
|
|
74
|
+
replacement(content, node) {
|
|
75
|
+
const dataset = node.dataset || {};
|
|
76
|
+
const denotation = dataset.denotationChar || "@";
|
|
77
|
+
const rawValue = dataset.value || node.textContent || "";
|
|
78
|
+
const normalizedValue = rawValue.trim();
|
|
79
|
+
return normalizedValue.startsWith(denotation) ? normalizedValue : `${denotation}${normalizedValue}`;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
50
83
|
return turndownService.turndown(text);
|
|
51
84
|
}
|
|
52
85
|
|