sprintify-ui 0.12.24 → 0.12.25

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.
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'dompurify';
2
+ import type { Directive } from 'vue';
3
+ export declare const SAFE_HTML_CONFIG: Config;
4
+ export declare const vSafeHtml: Directive<HTMLElement, string | null | undefined>;
@@ -6,6 +6,7 @@ import { useSystemAlertStore } from './stores/systemAlerts';
6
6
  import { useClickOutside } from './composables/clickOutside';
7
7
  import { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE, useLayoutHeaderHeight } from './composables/layoutHeaderHeight';
8
8
  import { useField } from './composables/field';
9
+ import { vSafeHtml, SAFE_HTML_CONFIG } from './directives/safeHtml';
9
10
  declare const messages: {
10
11
  en: {
11
12
  cancel: string;
@@ -274,3 +275,5 @@ export { useLayoutHeaderHeight };
274
275
  export { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE };
275
276
  export { useField };
276
277
  export { SettingsStorage };
278
+ export { vSafeHtml };
279
+ export { SAFE_HTML_CONFIG };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.12.24",
3
+ "version": "0.12.25",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "generate-llm-txt": "node scripts/generate-llm-txt.js",
@@ -44,6 +44,7 @@
44
44
  "@tiptap/extension-subscript": "^3.20.4",
45
45
  "@tiptap/extension-superscript": "^3.20.4",
46
46
  "color2k": "^2.0.3",
47
+ "dompurify": "^3.4.14",
47
48
  "tailwind-merge": "^2.4.0"
48
49
  },
49
50
  "devDependencies": {
@@ -69,8 +69,8 @@
69
69
  name="tooltip"
70
70
  >
71
71
  <div
72
+ v-safe-html="tooltip"
72
73
  class="text-xs max-w-xs leading-snug rounded-md pt-1.5 pb-2 px-3 bg-slate-900 text-white"
73
- v-html="tooltip"
74
74
  />
75
75
  </slot>
76
76
  </transition>
@@ -79,6 +79,7 @@
79
79
  </template>
80
80
 
81
81
  <script lang="ts" setup>
82
+ import { vSafeHtml } from '@/directives/safeHtml';
82
83
  import { ClassNameValue, twMerge } from 'tailwind-merge';
83
84
  import { RouteLocationRaw } from 'vue-router';
84
85
  import { BaseIcon } from '.';
@@ -43,9 +43,9 @@
43
43
  @change="onVisibleColumnChange($event, element.newKey)"
44
44
  >
45
45
  <div
46
+ v-safe-html="element.label"
46
47
  class="text-xs grow leading-tight"
47
48
  :class="[element.toggle ? 'text-slate-700' : 'text-slate-400']"
48
- v-html="element.label"
49
49
  >
50
50
  </div>
51
51
  </label>
@@ -57,6 +57,7 @@
57
57
  </template>
58
58
 
59
59
  <script lang="ts" setup>
60
+ import { vSafeHtml } from '@/directives/safeHtml';
60
61
  import { cloneDeep } from 'lodash';
61
62
  import { PropType } from 'vue';
62
63
  import BaseDataTableTemplate from './BaseDataTableTemplate.vue';
@@ -58,12 +58,12 @@
58
58
  @click="sort(column, undefined, $event as any)"
59
59
  >
60
60
  <div
61
+ v-safe-html="column.label"
61
62
  class="whitespace-nowrap text-slate-600"
62
63
  :class="{
63
64
  'text-[12px]': size == 'sm',
64
65
  'text-xs': size == 'md',
65
66
  }"
66
- v-html="column.label"
67
67
  />
68
68
  <!-- h-4 is used to make sure the tooltip icon is always smaller than label and avoid alignment issues when icon is not present -->
69
69
  <div
@@ -197,6 +197,7 @@
197
197
  </template>
198
198
 
199
199
  <script lang="ts" setup>
200
+ import { vSafeHtml } from '@/directives/safeHtml';
200
201
  import { PropType, ref } from 'vue';
201
202
  import { BaseTableColumnData, CollectionItem, Row } from '@/types';
202
203
  import SlotComponent from './SlotComponent';
@@ -47,8 +47,8 @@
47
47
  <div class="mt-2">
48
48
  <div
49
49
  v-if="html"
50
+ v-safe-html="message"
50
51
  class="text-slate-500 prose-sm prose-p:leading-snug prose-a:text-blue-600 prose-a:underline"
51
- v-html="message"
52
52
  />
53
53
  <p
54
54
  v-else
@@ -118,6 +118,7 @@
118
118
  </template>
119
119
 
120
120
  <script lang="ts" setup>
121
+ import { vSafeHtml } from '@/directives/safeHtml';
121
122
  import { PropType } from 'vue';
122
123
  import { Icon as BaseIcon } from '@iconify/vue';
123
124
  import { t } from '@/i18n';
@@ -10,9 +10,9 @@
10
10
  />
11
11
  <div class="min-w-0 flex-1">
12
12
  <div
13
+ v-safe-html="notification.text"
13
14
  class="text-sm leading-tight"
14
15
  :class="textClasses"
15
- v-html="notification.text"
16
16
  />
17
17
  <BaseDisplayRelativeTime
18
18
  v-if="notification.created_at"
@@ -32,6 +32,7 @@
32
32
  </template>
33
33
 
34
34
  <script lang="ts" setup>
35
+ import { vSafeHtml } from '@/directives/safeHtml';
35
36
  import { Notification } from '@/types/Notification';
36
37
  import BaseDisplayRelativeTime from './BaseDisplayRelativeTime.vue';
37
38
 
@@ -32,8 +32,8 @@
32
32
  name="tooltip"
33
33
  >
34
34
  <div
35
+ v-safe-html="tooltip"
35
36
  class="text-xs max-w-xs leading-snug rounded-md pt-1.5 pb-2 px-3 bg-slate-900 text-white"
36
- v-html="tooltip"
37
37
  />
38
38
  </slot>
39
39
  </transition>
@@ -42,6 +42,7 @@
42
42
  </template>
43
43
 
44
44
  <script lang="ts" setup>
45
+ import { vSafeHtml } from '@/directives/safeHtml';
45
46
  import { useTooltip } from '@/composables/tooltip';
46
47
  import { cellClasses } from '@/services/table/classes';
47
48
  import { CellConfig, TableProps } from '@/services/table/types';
@@ -28,11 +28,11 @@
28
28
  >
29
29
  <div class="relative filter drop-shadow-[0_1px_2px_rgba(0,0,0,0.1)]">
30
30
  <div
31
+ v-safe-html="text"
31
32
  class="text-xs max-w-xs leading-snug rounded-md pt-1.5 pb-2 px-3"
32
33
  :class="[
33
34
  dark ? 'bg-slate-900 text-white' : 'bg-white text-slate-900',
34
35
  ]"
35
- v-html="text"
36
36
  />
37
37
  <div
38
38
  ref="arrowRef"
@@ -51,6 +51,7 @@
51
51
  </template>
52
52
 
53
53
  <script lang="ts" setup>
54
+ import { vSafeHtml } from '@/directives/safeHtml';
54
55
  import { useTooltip } from '@/composables/tooltip';
55
56
  import { unrefElement } from '@vueuse/core';
56
57
  import { UseFloatingOptions } from '@floating-ui/vue';
@@ -0,0 +1,28 @@
1
+ import DOMPurify from 'dompurify';
2
+ import type { Config } from 'dompurify';
3
+ import type { Directive } from 'vue';
4
+
5
+ // Must keep BaseTipTap output intact: links (href/target), images,
6
+ // highlights (<mark style="background-color">), sub/superscript.
7
+ export const SAFE_HTML_CONFIG: Config = {
8
+ USE_PROFILES: { html: true },
9
+ ADD_ATTR: ['target'],
10
+ FORBID_TAGS: ['style', 'form', 'input', 'textarea', 'select', 'button'],
11
+ };
12
+
13
+ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
14
+ if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {
15
+ node.setAttribute('rel', 'noopener noreferrer');
16
+ }
17
+ });
18
+
19
+ export const vSafeHtml: Directive<HTMLElement, string | null | undefined> = {
20
+ mounted: (el, { value }) => {
21
+ el.innerHTML = DOMPurify.sanitize(value ?? '', SAFE_HTML_CONFIG);
22
+ },
23
+ updated: (el, { value, oldValue }) => {
24
+ if (value !== oldValue) {
25
+ el.innerHTML = DOMPurify.sanitize(value ?? '', SAFE_HTML_CONFIG);
26
+ }
27
+ },
28
+ };
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  useLayoutHeaderHeight,
14
14
  } from './composables/layoutHeaderHeight';
15
15
  import { useField } from './composables/field';
16
+ import { vSafeHtml, SAFE_HTML_CONFIG } from './directives/safeHtml';
16
17
 
17
18
  const messages = { en, fr };
18
19
 
@@ -76,6 +77,8 @@ function install(app: App, options?: Options) {
76
77
  app.component(key, components[key]);
77
78
  }
78
79
 
80
+ app.directive('safe-html', vSafeHtml);
81
+
79
82
  if (options?.http) {
80
83
  config.http = options.http;
81
84
  }
@@ -142,3 +145,5 @@ export { useLayoutHeaderHeight };
142
145
  export { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE };
143
146
  export { useField };
144
147
  export { SettingsStorage };
148
+ export { vSafeHtml };
149
+ export { SAFE_HTML_CONFIG };