glib-web 4.39.4 → 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/actions/sheets/open.js +3 -3
- package/app.vue +27 -13
- package/components/_message.vue +2 -12
- package/components/fields/richText2.vue +20 -22
- package/components/fields/textarea.vue +7 -1
- package/components/p.vue +3 -0
- package/nav/appBar.vue +6 -2
- package/nav/drawer.vue +26 -8
- package/nav/snackbar.vue +2 -0
- package/package.json +1 -1
- package/templates/thumbnail.vue +25 -8
- package/utils/eventBus.js +3 -2
- package/utils/format.js +35 -2
package/actions/sheets/open.js
CHANGED
|
@@ -10,7 +10,7 @@ export default class {
|
|
|
10
10
|
if (loaderViews) {
|
|
11
11
|
vueApp.sheet = {
|
|
12
12
|
show: true,
|
|
13
|
-
|
|
13
|
+
body: { childViews: loaderViews },
|
|
14
14
|
placement
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -21,14 +21,14 @@ export default class {
|
|
|
21
21
|
component,
|
|
22
22
|
(response) => {
|
|
23
23
|
vueApp.sheet = {
|
|
24
|
-
|
|
24
|
+
body: Object.assign({}, response.body, { key: strandom() }),
|
|
25
25
|
show: true,
|
|
26
26
|
placement
|
|
27
27
|
};
|
|
28
28
|
},
|
|
29
29
|
() => {
|
|
30
30
|
vueApp.sheet = {
|
|
31
|
-
|
|
31
|
+
body: {
|
|
32
32
|
childViews: [
|
|
33
33
|
{ view: 'p', text: 'Failed to load' },
|
|
34
34
|
{ view: 'spacer', height: 8 },
|
package/app.vue
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
<!-- <glib-component v-for="(rbSpec, index) in sheet.spec.childViews" :spec="rbSpec"
|
|
44
44
|
:key="`${vueApp.sheet.spec.key}-${index}`"></glib-component> -->
|
|
45
45
|
|
|
46
|
+
<!-- TODO: fix this -->
|
|
46
47
|
<panels-responsive :spec="sheet.body"></panels-responsive>
|
|
47
48
|
</v-sheet>
|
|
48
49
|
</Transition>
|
|
@@ -384,14 +385,6 @@ body,
|
|
|
384
385
|
}
|
|
385
386
|
}
|
|
386
387
|
|
|
387
|
-
.v-input__details {
|
|
388
|
-
min-height: 0;
|
|
389
|
-
margin-bottom: 0;
|
|
390
|
-
|
|
391
|
-
.v-messages {
|
|
392
|
-
min-height: 0;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
388
|
|
|
396
389
|
.v-label {
|
|
397
390
|
opacity: 1;
|
|
@@ -406,13 +399,34 @@ body,
|
|
|
406
399
|
pointer-events: none;
|
|
407
400
|
}
|
|
408
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 */
|
|
409
408
|
.v-input .v-input__details {
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
position: absolute;
|
|
410
|
+
top: 100%;
|
|
411
|
+
left: 0;
|
|
412
|
+
right: 0;
|
|
412
413
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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;
|
|
416
430
|
}
|
|
417
431
|
|
|
418
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/components/p.vue
CHANGED
package/nav/appBar.vue
CHANGED
|
@@ -71,8 +71,12 @@ export default {
|
|
|
71
71
|
// }
|
|
72
72
|
// },
|
|
73
73
|
isPermanent() {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (this.navBar.collapseButtonsOnMobile) {
|
|
75
|
+
// const display = this.$vuetify.display.name;
|
|
76
|
+
// return display == "lg" || display == "xl";
|
|
77
|
+
return !this.$vuetify.display.smAndDown;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
76
80
|
},
|
|
77
81
|
showDrawerMenu() {
|
|
78
82
|
return this.page.leftDrawer && !this.isPermanent;
|
package/nav/drawer.vue
CHANGED
|
@@ -89,6 +89,12 @@ export default {
|
|
|
89
89
|
openedGroups: openedGroups
|
|
90
90
|
};
|
|
91
91
|
},
|
|
92
|
+
mounted() {
|
|
93
|
+
this.registerDrawerToggleListener();
|
|
94
|
+
},
|
|
95
|
+
beforeUnmount() {
|
|
96
|
+
this.unregisterDrawerToggleListener();
|
|
97
|
+
},
|
|
92
98
|
computed: {
|
|
93
99
|
cssClasses() {
|
|
94
100
|
// To make sure a css class name is assigned to this component
|
|
@@ -123,16 +129,29 @@ export default {
|
|
|
123
129
|
trigger: function (val, oldVal) {
|
|
124
130
|
this.state = !this.state;
|
|
125
131
|
},
|
|
132
|
+
spec: {
|
|
133
|
+
handler() {
|
|
134
|
+
// When the page reloads reactively, the event bus listeners are cleared.
|
|
135
|
+
// Re-register the drawer toggle so future events still work.
|
|
136
|
+
if (bus.hasListener("glib-drawers/toggle", this.handleDrawerToggle)) {
|
|
137
|
+
// Listener is still active; skip re-registering to avoid duplicate toggles.
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.registerDrawerToggleListener();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
126
143
|
},
|
|
127
144
|
methods: {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.updateState(data);
|
|
132
|
-
});
|
|
145
|
+
registerDrawerToggleListener() {
|
|
146
|
+
this.unregisterDrawerToggleListener();
|
|
147
|
+
bus.$on("glib-drawers/toggle", this.handleDrawerToggle);
|
|
133
148
|
},
|
|
134
|
-
|
|
135
|
-
bus.$off("glib-drawers/toggle");
|
|
149
|
+
unregisterDrawerToggleListener() {
|
|
150
|
+
bus.$off("glib-drawers/toggle", this.handleDrawerToggle);
|
|
151
|
+
},
|
|
152
|
+
handleDrawerToggle(bool) {
|
|
153
|
+
const data = isPresent(bool) ? bool : !this.state;
|
|
154
|
+
this.updateState(data);
|
|
136
155
|
},
|
|
137
156
|
updateState(state) {
|
|
138
157
|
this.state = state;
|
|
@@ -172,7 +191,6 @@ export default {
|
|
|
172
191
|
align-items: center;
|
|
173
192
|
background-color: white;
|
|
174
193
|
}
|
|
175
|
-
|
|
176
194
|
</style>
|
|
177
195
|
|
|
178
196
|
<style lang="scss">
|
package/nav/snackbar.vue
CHANGED
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/eventBus.js
CHANGED
|
@@ -9,5 +9,6 @@ export default {
|
|
|
9
9
|
$off: (...args) => emitter.off(...args),
|
|
10
10
|
$emit: (...args) => emitter.emit(...args),
|
|
11
11
|
$removeAllListener: (...args) => emitter.removeAllListeners(...args),
|
|
12
|
-
currentListenerCount: () => emitter.listeners().length
|
|
13
|
-
|
|
12
|
+
currentListenerCount: () => emitter.listeners().length,
|
|
13
|
+
hasListener: (event, listener) => emitter.listeners(event).includes(listener)
|
|
14
|
+
};
|
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
|
|