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.
@@ -8,7 +8,9 @@
8
8
  "Bash(npx cypress run:*)",
9
9
  "Read(//home/hgani/workspace/glib-web/**)",
10
10
  "Bash(curl:*)",
11
- "Bash(pkill:*)"
11
+ "Bash(pkill:*)",
12
+ "Bash(gh pr list:*)",
13
+ "WebSearch"
12
14
  ],
13
15
  "deny": [],
14
16
  "ask": []
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
- min-height: fit-content;
412
- padding-top: 0px;
409
+ position: absolute;
410
+ top: 100%;
411
+ left: 0;
412
+ right: 0;
413
413
 
414
- .v-messages__message {
415
- padding-top: 6px;
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 {
@@ -1,20 +1,10 @@
1
- <!-- TODO: Deprecate this in favor of supporting `body` in dialogs, banners, and sheets -->
2
1
  <template>
3
- <div v-if="isMarkdown" v-html="compiledText"></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
- // let index = 0;
80
- // const value = quill.root.innerHTML.replace(/src="([^"]+)"|href="([^"]+)"/g, function (_, g1, g2) {
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
- if (props.spec.mentionList) {
156
- const simpleSource = (searchTerm, renderList, mentionChar) => {
157
- const matches = props.spec.mentionList.toSorted().map((v, index) => ({ id: index + 1, value: v }));
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 (searchTerm.length === 0) {
156
+ if (normalizedTerm.length === 0) {
160
157
  renderList(matches, searchTerm);
161
- } else {
162
- renderList(matches.filter((v) => v.value.toLowerCase().includes(searchTerm)), searchTerm);
158
+ return;
163
159
  }
164
- };
165
160
 
166
- const advancedSource = (searchTerm, renderList, mentionChar) => {
161
+ renderList(matches.filter((v) => v.value.toLowerCase().includes(normalizedTerm)), searchTerm);
162
+ };
167
163
 
168
- const compareFn = (a, b) => a.value > b.value;
169
- const options = props.spec.mentionList.toSorted(compareFn);
170
- const isGrouped = !!props.spec.mentionList[0].group;
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 (searchTerm.length === 0) {
171
+ if (normalizedTerm.length === 0) {
174
172
  matches = options;
175
173
  } else {
176
- matches = options.filter((v) => v.value.toLowerCase().includes(searchTerm));
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 props.spec.mentionList[0] == 'object' ? advancedSource : simpleSource;
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"></v-textarea>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.39.5",
3
+ "version": "4.39.6",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -49,15 +49,20 @@
49
49
 
50
50
  <common-badge :spec="spec">
51
51
  <div>
52
- <v-list-item-title class="title">{{ spec.title }}</v-list-item-title>
53
-
54
- <div class="subtitle" v-if="spec.subtitle">{{
55
- spec.subtitle
56
- }}</div>
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
- spec.subsubtitle
60
- }}</v-list-item-subtitle>
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, { break: true }
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