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.
@@ -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": []
@@ -10,7 +10,7 @@ export default class {
10
10
  if (loaderViews) {
11
11
  vueApp.sheet = {
12
12
  show: true,
13
- spec: { childViews: loaderViews },
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
- spec: Object.assign({}, response.body, { key: strandom() }),
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
- spec: {
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
- min-height: fit-content;
411
- padding-top: 0px;
409
+ position: absolute;
410
+ top: 100%;
411
+ left: 0;
412
+ right: 0;
412
413
 
413
- .v-messages__message {
414
- padding-top: 6px;
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 {
@@ -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/components/p.vue CHANGED
@@ -25,6 +25,9 @@ p.muted {
25
25
  opacity: 0.7;
26
26
  }
27
27
 
28
+ </style>
29
+
30
+ <style lang="scss">
28
31
  .tooltip {
29
32
  padding: 4px 8px;
30
33
  color: white;
package/nav/appBar.vue CHANGED
@@ -71,8 +71,12 @@ export default {
71
71
  // }
72
72
  // },
73
73
  isPermanent() {
74
- const display = this.$vuetify.display.name;
75
- return display == "lg" || display == "xl";
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
- $mounted() {
129
- bus.$on("glib-drawers/toggle", (bool) => {
130
- const data = isPresent(bool) ? bool : !this.state;
131
- this.updateState(data);
132
- });
145
+ registerDrawerToggleListener() {
146
+ this.unregisterDrawerToggleListener();
147
+ bus.$on("glib-drawers/toggle", this.handleDrawerToggle);
133
148
  },
134
- $unmounted() {
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
@@ -48,6 +48,8 @@ export default {
48
48
  this.vertical = [this.spec.styleClasses].flat().includes('vertical');
49
49
  },
50
50
  click: function (spec) {
51
+ const { onClick } = spec;
52
+ Action.execute(onClick, this);
51
53
  this.show = false;
52
54
  }
53
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.39.4",
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/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, { 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