@simple-reporting/base 1.0.14 → 1.0.15

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.
Files changed (28) hide show
  1. package/dev/package.json +1 -1
  2. package/livingdocs/040.Media/010.table/table/download.vue +494 -0
  3. package/livingdocs/040.Media/010.table/table/responsive.vue +104 -0
  4. package/livingdocs/040.Media/010.table/table.html +2 -2
  5. package/livingdocs/040.Media/010.table/table.vue +26 -0
  6. package/package.json +1 -1
  7. package/plugins/viteSrlPlugin.d.ts +2 -0
  8. package/plugins/viteSrlPlugin.js +18 -36
  9. package/scripts/vue/components.d.ts +1 -0
  10. package/scripts/vue/components.js +94 -0
  11. package/srl/components/Srl/Article/{DialogButton.vue → Dialog/Button.vue} +1 -1
  12. package/srl/components/Srl/Menu/Item/Content.vue +7 -14
  13. package/srl/components/Srl/Menu/Item.vue +5 -7
  14. package/srl/components/Srl/{Menu/List.vue → Menu.vue} +2 -3
  15. package/srl/components/Srl/Note/Accordion/Content.vue +1 -1
  16. package/srl/components/Srl/Page/Dialog.vue +1 -1
  17. package/srl/plugins/asyncLdComponent.ts +2 -2
  18. package/srl/plugins/asyncSrlComponents.ts +2 -0
  19. package/srl/plugins/initProject.ts +1 -1
  20. package/srl/plugins/vueSrlPlugin.ts +3 -20
  21. package/srl/types/components.d.ts +3 -17
  22. package/srl/types/global.d.ts +7 -0
  23. package/srl/types/nswow.d.ts +14 -14
  24. /package/srl/components/{Srl/Page/App.vue → App.vue} +0 -0
  25. /package/srl/components/Srl/Menu/Item/Content/{IconAfter.vue → Icon/After.vue} +0 -0
  26. /package/srl/components/Srl/Menu/Item/Content/{IconBefore.vue → Icon/Before.vue} +0 -0
  27. /package/srl/components/Srl/Menu/Item/Content/{ImageAfter.vue → Image/After.vue} +0 -0
  28. /package/srl/components/Srl/Menu/Item/Content/{ImageBefore.vue → Image/Before.vue} +0 -0
package/dev/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "postinstall": "srl prepare"
20
20
  },
21
21
  "dependencies": {
22
- "@simple-reporting/base": "^1.0.14",
22
+ "@simple-reporting/base": "^1.0.15",
23
23
  "axios": "^1.12.2",
24
24
  "chalk": "^5.6.2",
25
25
  "exceljs": "^4.4.0",
@@ -0,0 +1,494 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * This Vue component script enables downloading an HTML table as an Excel file.
4
+ * It uses the `exceljs` library to convert the table into an Excel format and trigger the download.
5
+ *
6
+ * ## Props:
7
+ * - `titleSelector` (string, optional): CSS selector for the table titles. Default: `.srl-title-h1__text, .srl-page-title__overline, .srl-page-title__headline`.
8
+ * - `font` (string, optional): Font used in the Excel file. Default: `Arial`.
9
+ * - `defaultSheetName` (string, optional): Default name of the Excel worksheet. Default: `Table 1`.
10
+ * - `defaultFileName` (string, optional): Default name of the downloaded file. Default: `Table`.
11
+ * - `spacerRowHeight` (number, optional): Height of spacer rows in the Excel file. Default: `25`.
12
+ * - `formatText` (Function, optional): Function to format cell content. Default: Trims text and returns `null` if empty.
13
+ *
14
+ * ## Types:
15
+ * - `FormatText`: Type for the `formatText` function.
16
+ * - `ElementStyles`: Style properties of an HTML element.
17
+ * - `TextData`: Text data with styles.
18
+ * - `CellData`: Data and styles of a table cell.
19
+ * - `CellStyles`: Styles of a table cell.
20
+ * - `TableIdElement`: HTML div element with a `data-tableid` attribute.
21
+ * - `TableMatrix`: Matrix representing the table structure.
22
+ *
23
+ * ## Functions:
24
+ * - `downloadTable()`: Initiates the download of the table as an Excel file.
25
+ * - `parseColumns(worksheet: Worksheet)`: Parses the column widths of the table and sets them in the worksheet.
26
+ * - `parseTitles()`: Extracts the table titles and their styles.
27
+ * - `writeTitles(worksheet: Worksheet, titles: TextData[])`: Writes the titles to the worksheet.
28
+ * - `parseTable()`: Converts the HTML table into a matrix of cells.
29
+ * - `writeTable(worksheet: Worksheet, matrix: TableMatrix)`: Writes the table matrix to the worksheet.
30
+ * - `mergeCells(worksheet: Worksheet, matrix: TableMatrix, excelRows: Row[])`: Merges cells with `rowSpan` or `colSpan`.
31
+ * - `parseFootNotes()`: Extracts footnotes from the table.
32
+ * - `writeFootNotes(worksheet: Worksheet, footNotes: TextData[])`: Writes the footnotes to the worksheet.
33
+ * - `triggerDownload(fileName: string, buffer: ArrayBuffer)`: Triggers the download of the Excel file.
34
+ * - `writeSpacerRow(worksheet: Worksheet)`: Adds a spacer row to the worksheet.
35
+ * - `parseElementStyles(element: HTMLElement)`: Parses the styles of an HTML element.
36
+ * - `rgbToArgb(rgb: string, alpha: number)`: Converts an RGB color to ARGB format.
37
+ * - `getStyles(tableCell: HTMLTableCellElement)`: Extracts the styles of a table cell.
38
+ *
39
+ * ## Usage:
40
+ * This component is used to export an HTML table to an Excel file.
41
+ * It can be integrated into a Vue application and provides a button to trigger the export.
42
+ */
43
+ import { ref, watch } from 'vue'
44
+ import { Workbook } from 'exceljs'
45
+ import type { Worksheet, Row } from 'exceljs/index.d.ts'
46
+
47
+ const props = withDefaults(defineProps<{
48
+ component?: HTMLDivElement
49
+ tableSelector?: string
50
+ titleSelector?: string
51
+ buttonLabel?: string
52
+ buttonTitle?: string
53
+ buttonDescription?: string
54
+ font?: string
55
+ defaultSheetName?: string
56
+ defaultFileName?: string
57
+ spacerRowHeight?: number
58
+ formatText?: FormatText
59
+ }>(),{
60
+ tableSelector: 'table',
61
+ titleSelector: '.srl-title-h1__text, .srl-page-title__overline, .srl-page-title__headline',
62
+ buttonLabel: 'Download',
63
+ buttonDescription: 'Download table as Excel file',
64
+ font: 'Arial',
65
+ defaultSheetName: 'Table 1',
66
+ defaultFileName: 'Table',
67
+ spacerRowHeight: 25,
68
+ formatText: (cell: HTMLTableCellElement) => {
69
+ return cell.innerText !== '' ? cell.innerText.trim() : null
70
+ }
71
+ })
72
+
73
+ type FormatText = (cell: HTMLTableCellElement) => string | null
74
+
75
+ type ElementStyles = {
76
+ name: string,
77
+ size: number,
78
+ color: string,
79
+ bold: boolean
80
+ }
81
+
82
+ interface TextData extends ElementStyles {
83
+ value: string
84
+ }
85
+
86
+ type CellData = {
87
+ value: string | null,
88
+ styles: CellStyles,
89
+ colSpan: number,
90
+ rowSpan: number
91
+ }
92
+
93
+ type CellStyles = {
94
+ alignment: {
95
+ horizontal: string,
96
+ vertical: string,
97
+ wrapText: boolean
98
+ },
99
+ border: {
100
+ top?: { style: string, color: { argb: string } },
101
+ right?: { style: string, color: { argb: string } },
102
+ bottom?: { style: string, color: { argb: string } },
103
+ left?: { style: string, color: { argb: string } }
104
+ },
105
+ font: {
106
+ name: string,
107
+ size: number,
108
+ color: { argb: string },
109
+ bold: boolean
110
+ },
111
+ fill: {
112
+ type?: string,
113
+ pattern?: string,
114
+ fgColor?: { argb: string }
115
+ }
116
+ }
117
+
118
+ type TableIdElement = HTMLDivElement & {
119
+ dataset: {
120
+ tableid: string
121
+ }
122
+ }
123
+
124
+ type TableMatrix = Array<Array<CellData>>
125
+
126
+ const rootElement = ref<HTMLDivElement>()
127
+ const table = ref<HTMLTableElement | null | undefined>(null)
128
+
129
+ watch(
130
+ props,
131
+ to => {
132
+ !to.component || init(to.component)
133
+ }
134
+ )
135
+
136
+ function init(componentEl: HTMLDivElement | undefined) {
137
+ if (!componentEl) return
138
+ rootElement.value = componentEl
139
+ table.value = componentEl.querySelector(props.tableSelector) as HTMLTableElement
140
+ }
141
+
142
+ function getTable(): Promise<HTMLTableElement | null | undefined> {
143
+ return new Promise((resolve, reject) => {
144
+ let attempts = 0;
145
+ const checkTable = () => {
146
+ const table = rootElement.value?.querySelector<HTMLTableElement>('table');
147
+ if (table) {
148
+ resolve(table);
149
+ } else if (attempts >= 30) {
150
+ reject(new Error('Table not found within the maximum number of attempts.'));
151
+ } else {
152
+ attempts++;
153
+ setTimeout(checkTable, 10);
154
+ }
155
+ };
156
+
157
+ checkTable();
158
+ })
159
+ }
160
+
161
+ async function downloadTable() {
162
+ if (!table.value) return
163
+
164
+ const matrix = parseTable()
165
+ const titles = parseTitles()
166
+ const footNotes = parseFootNotes()
167
+ const fileName = titles.map(t => t.value)
168
+
169
+ const headTop: HTMLTableCellElement | null | undefined = rootElement.value?.querySelector('.head_top')
170
+ if (headTop && headTop.textContent) {
171
+ fileName.push(headTop.textContent.trim())
172
+ }
173
+
174
+ const tableIdElement = rootElement.value?.querySelector('[data-tableid]') as TableIdElement | null
175
+
176
+ const workbook = new Workbook()
177
+ const worksheet = workbook.addWorksheet( tableIdElement?.dataset.tableid || props.defaultSheetName )
178
+
179
+ parseColumns(worksheet)
180
+
181
+ writeTitles(worksheet, titles)
182
+ mergeCells(worksheet, matrix, writeTable(worksheet, matrix))
183
+ writeFootNotes(worksheet, footNotes)
184
+
185
+ triggerDownload(
186
+ fileName.length ? fileName.join(' - ').replaceAll(' ', '_') : props.defaultFileName,
187
+ await workbook.xlsx.writeBuffer()
188
+ )
189
+ }
190
+
191
+ function parseColumns(worksheet: Worksheet): void {
192
+ worksheet.columns = Array.from(table.value?.querySelectorAll('col') ?? [])
193
+ .map(col => {
194
+ return {
195
+ width: col.offsetWidth / 7
196
+ }
197
+ })
198
+ }
199
+
200
+ function parseTitles(): TextData[] {
201
+ const titleElements: HTMLElement[] = Array.from(document.querySelectorAll(props.titleSelector)) as HTMLElement[]
202
+ const tableTitle: TextData[] = []
203
+ titleElements.forEach( element => {
204
+ if (!element || !element.textContent) return
205
+ const styles = parseElementStyles(element)
206
+ tableTitle.push(Object.assign({
207
+ value: element.textContent.trim(),
208
+ }, styles))
209
+ })
210
+ return tableTitle
211
+ }
212
+
213
+ function writeTitles( worksheet: Worksheet, titles: TextData[] ): void {
214
+ if (titles.length === 0) return
215
+
216
+ titles.forEach( title => {
217
+ const row = worksheet.addRow([title.value])
218
+ const cell = row.getCell(1)
219
+ cell.font = {
220
+ name: title.name,
221
+ size: title.size,
222
+ color: { argb: title.color },
223
+ bold: title.bold
224
+ }
225
+ cell.alignment = {
226
+ wrapText: true
227
+ }
228
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
229
+ })
230
+
231
+ //writeSpacerRow(worksheet)
232
+ }
233
+
234
+ function parseTable(): TableMatrix {
235
+ if (!table.value) return []
236
+
237
+ const matrix: TableMatrix = []
238
+
239
+ const tableRows = Array.from(table.value.querySelectorAll('tr'))
240
+
241
+ tableRows?.forEach( tableRowElem => {
242
+ const row: CellData[] = []
243
+ const tableCells: HTMLTableCellElement[] = Array.from(tableRowElem.querySelectorAll('th, td'))
244
+
245
+ tableCells.forEach( tableCellElem => {
246
+
247
+ const styles = getStyles(tableCellElem)
248
+
249
+ row.push({
250
+ value: props.formatText(tableCellElem),
251
+ styles: styles,
252
+ colSpan: tableCellElem.colSpan,
253
+ rowSpan: tableCellElem.rowSpan,
254
+ })
255
+
256
+ if (tableCellElem.colSpan > 1) {
257
+ for (let i = 1; i < tableCellElem.colSpan; i++) {
258
+ row.push({
259
+ value: null,
260
+ styles: styles,
261
+ colSpan: 1,
262
+ rowSpan: 1
263
+ })
264
+ }
265
+ }
266
+ })
267
+
268
+ matrix.push(row)
269
+ })
270
+
271
+ matrix.forEach( (row, rowIndex) => {
272
+ row.forEach( (cell) => {
273
+ const cellIndex = row.findIndex(c => c === cell)
274
+ if (cell.rowSpan > 1) {
275
+ for (let i = 1; i < cell.rowSpan; i++) {
276
+ const nextRow = matrix[rowIndex + i]
277
+ if (nextRow) {
278
+ for (let i = 0; i < cell.colSpan; i++) {
279
+ nextRow.splice(cellIndex, 0, {
280
+ value: null,
281
+ styles: cell.styles,
282
+ colSpan: 1,
283
+ rowSpan: 1
284
+ })
285
+ }
286
+ }
287
+ }
288
+ }
289
+ })
290
+ })
291
+
292
+ return matrix
293
+ }
294
+
295
+ function writeTable( worksheet: Worksheet, matrix: TableMatrix ): Row[] {
296
+ const excelRows: Row[] = []
297
+ matrix.forEach( row => {
298
+ const excelRow = worksheet.addRow(row.map(cell => cell.value))
299
+ row.forEach( (cell, index) => {
300
+ const excelCell = excelRow.getCell(index + 1)
301
+
302
+ excelCell.alignment = cell.styles.alignment
303
+ excelCell.font = cell.styles.font
304
+ cell.styles.fill ? excelCell.fill = cell.styles.fill : null
305
+ excelCell.border = cell.styles.border
306
+ })
307
+ excelRows.push(excelRow)
308
+ })
309
+ return excelRows
310
+ }
311
+
312
+ function mergeCells( worksheet: Worksheet, matrix: TableMatrix, excelRows: Row[] ): void {
313
+ matrix.forEach( (row, rowIndex) => {
314
+ row.forEach( (cell, cellIndex) => {
315
+ if (cell.rowSpan > 1 || cell.colSpan > 1) {
316
+ const excelRow = excelRows[rowIndex]
317
+ const startCell = cellIndex + 1
318
+ const endCell = cellIndex + cell.colSpan
319
+ const endRow = excelRow.number + ( cell.rowSpan - 1 )
320
+ worksheet.mergeCells(excelRow.number, startCell, endRow, endCell)
321
+ }
322
+ })
323
+ })
324
+ }
325
+
326
+ function parseFootNotes(): TextData[] {
327
+ const footNoteData: TextData[] = []
328
+ const footNotes = Array.from(rootElement.value?.querySelectorAll('.srl-footnote') ?? []) as HTMLDivElement[]
329
+
330
+ footNotes.forEach(footNote => {
331
+ const key: HTMLSpanElement | null = footNote.querySelector('.srl-footnote__key')
332
+ const text: HTMLSpanElement | null = footNote.querySelector('.srl-footnote__text')
333
+
334
+ if (text && text.textContent) {
335
+ const row: string[] = []
336
+ if (key && key.textContent) {
337
+ row.push(key.textContent.trim())
338
+ }
339
+ row.push(text.textContent.trim())
340
+ const styles = parseElementStyles(text)
341
+ footNoteData.push(Object.assign({
342
+ value: row.join(' '),
343
+ }, styles))
344
+ }
345
+ })
346
+ return footNoteData
347
+ }
348
+
349
+ function writeFootNotes( worksheet: Worksheet, footNotes: TextData[] ): void {
350
+ if (footNotes.length === 0) return
351
+
352
+ writeSpacerRow(worksheet)
353
+
354
+ footNotes.forEach(footNote => {
355
+ const row = worksheet.addRow([footNote.value])
356
+ const cell = row.getCell(1)
357
+ cell.font = {
358
+ name: footNote.name,
359
+ size: footNote.size,
360
+ color: { argb: footNote.color },
361
+ bold: footNote.bold
362
+ }
363
+ cell.alignment = {
364
+ horizontal: 'left',
365
+ vertical: 'top',
366
+ wrapText: true
367
+ }
368
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
369
+ })
370
+ }
371
+
372
+ function triggerDownload( fileName: string, buffer: ArrayBuffer ): void {
373
+ const blob = new Blob([buffer], {
374
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
375
+ })
376
+ const link = document.createElement('a')
377
+ link.href = URL.createObjectURL(blob)
378
+ link.download = fileName + '.xlsx'
379
+ link.click()
380
+ link.remove()
381
+ URL.revokeObjectURL(link.href)
382
+ }
383
+
384
+ function writeSpacerRow(worksheet: Worksheet): void {
385
+ const row = worksheet.addRow([])
386
+ row.height = props.spacerRowHeight
387
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
388
+ }
389
+
390
+ function parseElementStyles(element: HTMLElement): ElementStyles | null {
391
+ const styles = window.getComputedStyle(element)
392
+ return {
393
+ name: props.font,
394
+ size: parseInt(styles.fontSize) * 0.75,
395
+ color: rgbToArgb(styles.color),
396
+ bold: parseInt(styles.fontWeight) > 400
397
+ }
398
+ }
399
+
400
+ function rgbToArgb(rgb: string, alpha: number = 1): string {
401
+ const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/)
402
+ if (!match) {
403
+ throw new Error("Dies ist keine gültige RGB- oder RGBA-Farbe")
404
+ }
405
+
406
+ const r = parseInt(match[1])
407
+ const g = parseInt(match[2])
408
+ const b = parseInt(match[3])
409
+ const a = match[4] !== undefined ? Math.round(parseFloat(match[4]) * 255) : Math.round(alpha * 255)
410
+
411
+ const aHex = a.toString(16).padStart(2, '0')
412
+ const rHex = r.toString(16).padStart(2, '0')
413
+ const gHex = g.toString(16).padStart(2, '0')
414
+ const bHex = b.toString(16).padStart(2, '0')
415
+
416
+ return `${aHex}${rHex}${gHex}${bHex}`
417
+ }
418
+
419
+ function getStyles( tableCell: HTMLTableCellElement ): CellStyles {
420
+ const styles: CellStyles = {
421
+ alignment: {
422
+ horizontal: window.getComputedStyle(tableCell).textAlign,
423
+ vertical: window.getComputedStyle(tableCell).verticalAlign,
424
+ wrapText: true
425
+ },
426
+ border: {},
427
+ font: {
428
+ name: 'Arial',
429
+ size: parseInt(window.getComputedStyle(tableCell).fontSize) * 0.75,
430
+ color: { argb: rgbToArgb(window.getComputedStyle(tableCell).color) },
431
+ bold: parseInt(window.getComputedStyle(tableCell).fontWeight) > 400
432
+ },
433
+ }
434
+
435
+ const brdTopWidth = parseInt(window.getComputedStyle(tableCell).borderTopWidth)
436
+ if (brdTopWidth) {
437
+ styles.border.top = {
438
+ style: brdTopWidth < 3 ? 'thin' : brdTopWidth < 6 ? 'medium' : 'thick',
439
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderTopColor)}
440
+ }
441
+ }
442
+
443
+ const brdRightWidth = parseInt(window.getComputedStyle(tableCell).borderRightWidth)
444
+ if (brdRightWidth) {
445
+ styles.border.right = {
446
+ style: brdRightWidth < 3 ? 'thin' : brdRightWidth < 6 ? 'medium' : 'thick',
447
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderRightColor)}
448
+ }
449
+ }
450
+
451
+ const brdBottomWidth = parseInt(window.getComputedStyle(tableCell).borderBottomWidth)
452
+ if (brdBottomWidth) {
453
+ styles.border.bottom = {
454
+ style: brdBottomWidth < 3 ? 'thin' : brdBottomWidth < 6 ? 'medium' : 'thick',
455
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderBottomColor)}
456
+ }
457
+ }
458
+
459
+ const brdLeftWidth = parseInt(window.getComputedStyle(tableCell).borderLeftWidth)
460
+ if (brdLeftWidth) {
461
+ styles.border.left = {
462
+ style: brdLeftWidth < 3 ? 'thin' : brdLeftWidth < 6 ? 'medium' : 'thick',
463
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderLeftColor)}
464
+ }
465
+ }
466
+
467
+ const bgColor = rgbToArgb(window.getComputedStyle(tableCell).backgroundColor)
468
+ if (bgColor.substring(0,2) !== '00') {
469
+ styles.fill = {
470
+ type: 'pattern',
471
+ pattern: 'solid',
472
+ fgColor: {argb: bgColor}
473
+ }
474
+ }
475
+
476
+ return styles
477
+ }
478
+
479
+ </script>
480
+
481
+ <template>
482
+ <button
483
+ v-if="table"
484
+ type="button"
485
+ :title="$te('table.download.title') ? $t('table.download.title') : props.buttonTitle ?? props.buttonLabel"
486
+ :aria-label="$te('table.download.label') ? $t('table.download.label') : props.buttonLabel"
487
+ :aria-description="$te('table.download.description') ? $t('table.download.description') : props.buttonDescription"
488
+ class="srl-button"
489
+ @click="downloadTable"
490
+ >
491
+ <i class="srl-icon srl-icon-download" aria-hidden="true"></i>
492
+ <span v-text="$te('table.download.label') ? $t('table.download.label') : props.buttonLabel" />
493
+ </button>
494
+ </template>
@@ -0,0 +1,104 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ component?: HTMLDivElement
6
+ containerSelector?: string
7
+ tableSelector?: string
8
+ }>(), {
9
+ containerSelector: '.srl-table__container',
10
+ tableSelector: 'table'
11
+ })
12
+
13
+ const component = ref<HTMLDivElement>()
14
+ const table = ref<HTMLTableElement>()
15
+ const container = ref<HTMLDivElement>()
16
+
17
+ watch(
18
+ props,
19
+ to => {
20
+ !to.component || init(to.component)
21
+ }
22
+ )
23
+
24
+ function init(componentEl: HTMLDivElement | undefined) {
25
+ if (!componentEl) return
26
+ component.value = componentEl
27
+ table.value = componentEl.querySelector(props.tableSelector) as HTMLTableElement
28
+ container.value = componentEl.querySelector(props.containerSelector) as HTMLDivElement
29
+ updateClasses()
30
+ enableDragScroll()
31
+ window.addEventListener('resize', updateClasses)
32
+ }
33
+
34
+ defineExpose({
35
+ init
36
+ })
37
+
38
+ function hasHorizontalScrollbar(): boolean {
39
+ return !table.value || !container.value ?
40
+ false :
41
+ table.value?.clientWidth > container.value?.clientWidth
42
+ }
43
+
44
+ function hasRowSpan() {
45
+ if (!table.value) return false
46
+ const tableCells = table.value.querySelectorAll('td[rowspan]')
47
+ return tableCells.length > 0
48
+ }
49
+
50
+ function updateClasses() {
51
+ const hasScroll = hasHorizontalScrollbar()
52
+ const hasRowspan = hasRowSpan()
53
+
54
+ if (hasScroll && !hasRowspan) {
55
+ component.value?.classList.add('has-shadow', 'responsive-table')
56
+ component.value?.classList.remove('responsive-table-alternative')
57
+ } else if (hasScroll && hasRowspan) {
58
+ component.value?.classList.add('responsive-table-alternative')
59
+ component.value?.classList.remove('has-shadow', 'responsive-table')
60
+ } else {
61
+ component.value?.classList.remove('has-shadow', 'responsive-table', 'responsive-table-alternative')
62
+ }
63
+ }
64
+
65
+ function enableDragScroll() {
66
+ let isDragging: boolean = false;
67
+ let startX: number = 0;
68
+ let scrollLeft: number = 0;
69
+
70
+ if (container.value) {
71
+ container.value.addEventListener('mousedown', (e) => {
72
+ if (container.value) {
73
+ isDragging = true;
74
+ startX = e.pageX - container.value.offsetLeft;
75
+ scrollLeft = container.value.scrollLeft;
76
+ container.value.classList.add('dragging');
77
+ }
78
+ });
79
+
80
+ container.value.addEventListener('mousemove', (e) => {
81
+ if (container.value) {
82
+ if (!isDragging) return;
83
+ e.preventDefault();
84
+ const x = e.pageX - container.value.offsetLeft;
85
+ const walk = (x - startX) * 2; // Adjust speed
86
+ container.value.scrollLeft = scrollLeft - walk;
87
+ }
88
+ });
89
+
90
+ container.value.addEventListener('mouseup', () => {
91
+ isDragging = false;
92
+ container.value?.classList.remove('dragging');
93
+ });
94
+
95
+ container.value.addEventListener('mouseleave', () => {
96
+ isDragging = false;
97
+ container.value?.classList.remove('dragging');
98
+ });
99
+ }
100
+ }
101
+ </script>
102
+
103
+ <template>
104
+ </template>
@@ -1,4 +1,4 @@
1
- <div class="srl-table">
1
+ <srl-ld-table class="srl-table">
2
2
  <div class="srl-table__container" doc-include="nswow-table">
3
3
  <p class="srl-grid srl-paragraph">
4
4
  <span class="srl-grid__inner srl-paragraph__text">
@@ -6,4 +6,4 @@
6
6
  </span>
7
7
  </p>
8
8
  </div>
9
- </div>
9
+ </srl-ld-table>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+
3
+ const props = withDefaults(defineProps<{
4
+ responsive?: boolean
5
+ download?: boolean
6
+ }>(), {
7
+ responsive: false,
8
+ download: false,
9
+ })
10
+
11
+ import { ref } from 'vue'
12
+ import TableResponsive from './table/responsive.vue'
13
+ import TableDownload from './table/download.vue'
14
+
15
+ const component = ref<HTMLDivElement>()
16
+ </script>
17
+
18
+ <template>
19
+ <div ref="component">
20
+ <TableResponsive v-if="props.responsive" :component="component" />
21
+ <slot/>
22
+ <div v-if="props.download" class="srl-table__download">
23
+ <TableDownload :component="component" />
24
+ </div>
25
+ </div>
26
+ </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-reporting/base",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "Manage srl templates, build and publish",
5
5
  "bin": {
6
6
  "srl": "cli.js"
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ declare function viteSrlPlugin(): Plugin;
@@ -1,5 +1,4 @@
1
- import { fileURLToPath, URL } from 'node:url';
2
- import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
2
  import { join, relative } from 'node:path/posix';
4
3
  import { execSync } from 'node:child_process';
5
4
  import folders from '../scripts/folders.js';
@@ -14,6 +13,7 @@ import {
14
13
  mapScss,
15
14
  } from '../scripts/build.js';
16
15
  import chalk from 'chalk';
16
+ import { vueComponents } from '../scripts/vue/components.js';
17
17
 
18
18
 
19
19
  /**
@@ -36,10 +36,8 @@ function printPromptsMessage(messages) {
36
36
  console.log('');
37
37
  }
38
38
 
39
-
40
39
  const data = {
41
- version: null,
42
- components:{}
40
+ version: null
43
41
  };
44
42
  const srlConfig = readConfigJson();
45
43
 
@@ -95,39 +93,9 @@ function checkForUpdates() {
95
93
  }
96
94
  } catch (e) {}
97
95
  }
98
-
99
- function findVueFilesInSrl() {
100
- const componentsPath = join(folders.srlSrc, 'components');
101
- const baseDir = join(componentsPath, 'Srl');
102
- const result = {};
103
-
104
- function search(dir) {
105
- for (const entry of readdirSync(dir)) {
106
- const fullPath = join(dir, entry);
107
- if (statSync(fullPath).isDirectory()) {
108
- search(fullPath);
109
- } else if (entry.endsWith('.vue')) {
110
- const path = relative(componentsPath, fullPath);
111
- const name = path.slice(0, -4)
112
- .replaceAll('/', '')
113
- .replaceAll('\\', '');
114
-
115
- result[name] = `@/components/${path}`;
116
- }
117
- }
118
- }
119
-
120
- if (existsSync(baseDir) && statSync(baseDir).isDirectory()) {
121
- search(baseDir);
122
- }
123
-
124
- return result;
125
- }
126
-
127
96
  async function startActions() {
128
97
  data.version = getPackageVersion();
129
98
  checkForUpdates();
130
- data.components = findVueFilesInSrl();
131
99
  if (!srlConfig || JSON.stringify(srlConfig) !== JSON.stringify(data)) {
132
100
  writeConfigJson(data);
133
101
  }
@@ -139,6 +107,7 @@ async function startActions() {
139
107
  ]);
140
108
  }
141
109
 
110
+ await vueComponents();
142
111
  await beaver(0);
143
112
  await map();
144
113
  await mapJs();
@@ -160,7 +129,6 @@ export default function viteSrlPlugin() {
160
129
  return {
161
130
  name: 'vite-srl-plugin',
162
131
  config(config) {
163
-
164
132
  startActions();
165
133
 
166
134
  config.base = './';
@@ -229,6 +197,13 @@ export default function viteSrlPlugin() {
229
197
  ) {
230
198
  triggerAction(mapLdd);
231
199
  }
200
+
201
+ if (
202
+ path.includes('src/components/Srl') &&
203
+ path.endsWith('.vue')
204
+ ) {
205
+ triggerAction(vueComponents);
206
+ }
232
207
  });
233
208
 
234
209
  server.watcher.on('unlink', (path) => {
@@ -264,6 +239,13 @@ export default function viteSrlPlugin() {
264
239
  ) {
265
240
  triggerAction(mapLdd);
266
241
  }
242
+
243
+ if (
244
+ path.includes('src/components/Srl') &&
245
+ path.endsWith('.vue')
246
+ ) {
247
+ triggerAction(vueComponents);
248
+ }
267
249
  });
268
250
  },
269
251
  };
@@ -0,0 +1 @@
1
+ export function vueComponents(): Promise<void>;
@@ -0,0 +1,94 @@
1
+ import folders from '../folders.js';
2
+ import { join, relative } from 'node:path/posix';
3
+ import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs';
4
+
5
+ function toPascalAfterSlash(str) {
6
+ return str.replace(/\/(.)/g, (_, c) => c.toUpperCase());
7
+ }
8
+ function readVueDir(entryPath, prefix = '') {
9
+ const componentsPath = join(entryPath, 'components');
10
+ const baseDir = join(componentsPath, 'Srl');
11
+ const result = {};
12
+
13
+ function search(dir) {
14
+ for (const entry of readdirSync(dir)) {
15
+ const fullPath = join(dir, entry);
16
+ if (statSync(fullPath).isDirectory()) {
17
+ search(fullPath);
18
+ } else if (entry.endsWith('.vue')) {
19
+ const path = relative(componentsPath, fullPath);
20
+ let name = path.slice(0, -4);
21
+ name = toPascalAfterSlash(name);
22
+ name = name.replaceAll('/', '');
23
+
24
+ result[name] = {
25
+ component: `${prefix}components/${path}`,
26
+ type: relative(folders.srlTypes, fullPath)
27
+ };
28
+ }
29
+ }
30
+ }
31
+
32
+ if (existsSync(baseDir) && statSync(baseDir).isDirectory()) {
33
+ search(baseDir);
34
+ }
35
+
36
+ return result;
37
+ }
38
+
39
+ export async function vueComponents() {
40
+ const srlComponents = readVueDir(folders.srlRoot, '#');
41
+ const appComponents = readVueDir(folders.srlSrc, '@/');
42
+
43
+ const components = []
44
+ const types = []
45
+
46
+ for (const [name, info] of Object.entries(srlComponents)) {
47
+ const componentPath = appComponents[name] ? appComponents[name].component : info.component
48
+ const typePath = appComponents[name] ? appComponents[name].type : info.type
49
+ components.push(
50
+ `app.component('${name}', defineAsyncComponent(() => import('${componentPath}')));`,
51
+ )
52
+ types.push({
53
+ name: name,
54
+ type: ` type ${name} = typeof import('${typePath}')['default'];`,
55
+ })
56
+ }
57
+
58
+ for (const [name, info] of Object.entries(appComponents)) {
59
+ if (!srlComponents[name]) {
60
+ components.push(
61
+ `app.component('${name}', defineAsyncComponent(() => import('${info.component}')));`,
62
+ )
63
+ types.push({
64
+ name: name,
65
+ type: ` type ${name} = typeof import('${info.type}')['default'];`,
66
+ })
67
+ }
68
+ }
69
+
70
+ writeFileSync(join(folders.srlPlugins, 'asyncSrlComponents.ts'),
71
+ `import { defineAsyncComponent } from 'vue';
72
+ export default function asyncSrlComponents(app) {
73
+ ${components.join('\n ')}
74
+ }
75
+ `, 'utf8');
76
+
77
+ const componentsInterface = types.map(t => {
78
+ return ` ${t.name}: ${t.name};`
79
+ })
80
+
81
+ writeFileSync(join(folders.srlTypes, 'components.d.ts'),
82
+ `export {};
83
+ declare global {
84
+ ${types.map(t=>t.type).join("\n")}
85
+ }
86
+
87
+ interface _GlobalComponents {
88
+ ${componentsInterface.join("\n")}
89
+ }
90
+ declare module '@vue/runtime-core' {
91
+ export interface GlobalComponents extends _GlobalComponents {}
92
+ }
93
+ `, 'utf8');
94
+ }
@@ -11,7 +11,7 @@ const config = useConfig();
11
11
  const articles = useArticles();
12
12
  const id = ref<string>(`srl-page__dialog-${useId()}`);
13
13
  const content = ref<string>('');
14
- const dialog = ref<typeof SrlPageDialog | null>(null);
14
+ const dialog = ref<SrlPageDialog | null>(null);
15
15
  async function loadContent() {
16
16
  const article = articles.value.find((article) => article.uuid === props.uuid);
17
17
  if (article) {
@@ -1,11 +1,4 @@
1
1
  <script setup lang="ts">
2
- import MenuItemContentText from './Content/Text.vue';
3
- import MenuItemContentImage from './Content/Image.vue';
4
- import MenuItemContentImageBefore from './Content/ImageBefore.vue';
5
- import MenuItemContentImageAfter from './Content/ImageAfter.vue';
6
- import MenuItemContentIcon from './Content/Icon.vue';
7
- import MenuItemContentIconBefore from './Content/IconBefore.vue';
8
- import MenuItemContentIconAfter from './Content/IconAfter.vue';
9
2
 
10
3
  const props = defineProps<{
11
4
  item: NsWowNavigationItem
@@ -15,43 +8,43 @@ const props = defineProps<{
15
8
  </script>
16
9
 
17
10
  <template>
18
- <MenuItemContentIcon
11
+ <SrlMenuItemContentIcon
19
12
  v-if="props.item.icon"
20
13
  :item="props.item"
21
14
  :depth="props.depth"
22
15
  :disableClasses="props.disableClasses"
23
16
  />
24
- <MenuItemContentIconBefore
17
+ <SrlMenuItemContentIconBefore
25
18
  v-else-if="props.item.iconBefore"
26
19
  :item="props.item"
27
20
  :depth="props.depth"
28
21
  :disableClasses="props.disableClasses"
29
22
  />
30
- <MenuItemContentIconAfter
23
+ <SrlMenuItemContentIconAfter
31
24
  v-else-if="props.item.iconAfter"
32
25
  :item="props.item"
33
26
  :depth="props.depth"
34
27
  :disableClasses="props.disableClasses"
35
28
  />
36
- <MenuItemContentImage
29
+ <SrlMenuItemContentImage
37
30
  v-else-if="props.item.img"
38
31
  :item="props.item"
39
32
  :depth="props.depth"
40
33
  :disableClasses="props.disableClasses"
41
34
  />
42
- <MenuItemContentImageBefore
35
+ <SrlMenuItemContentImageBefore
43
36
  v-else-if="props.item.imgBefore"
44
37
  :item="props.item"
45
38
  :depth="props.depth"
46
39
  :disableClasses="props.disableClasses"
47
40
  />
48
- <MenuItemContentImageAfter
41
+ <SrlMenuItemContentImageAfter
49
42
  v-else-if="props.item.imgAfter"
50
43
  :item="props.item"
51
44
  :depth="props.depth"
52
45
  :disableClasses="props.disableClasses"
53
46
  />
54
- <MenuItemContentText
47
+ <SrlMenuItemContentText
55
48
  v-else
56
49
  :item="props.item"
57
50
  :depth="props.depth"
@@ -1,7 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, nextTick, ref, useId } from 'vue'
3
- import MenuItemContent from './Item/Content.vue'
4
- import MenuList from './List.vue'
5
3
  import type { RouterLink } from 'vue-router'
6
4
  import { isExternalPath } from '#utils/uri'
7
5
 
@@ -187,7 +185,7 @@ const classListItem = computed(() => {
187
185
  @keydown.shift.tab.exact="back"
188
186
  @keydown.esc.stop.prevent="close"
189
187
  >
190
- <MenuItemContent
188
+ <SrlMenuItemContent
191
189
  :item="props.item"
192
190
  :depth="props.depth"
193
191
  :disableClasses="props.disableClasses"
@@ -212,7 +210,7 @@ const classListItem = computed(() => {
212
210
  @keydown.shift.tab.exact="back"
213
211
  @keydown.esc.stop.prevent="close"
214
212
  >
215
- <MenuItemContent
213
+ <SrlMenuItemContent
216
214
  :item="props.item"
217
215
  :depth="props.depth"
218
216
  :disableClasses="props.disableClasses"
@@ -237,7 +235,7 @@ const classListItem = computed(() => {
237
235
  @keydown.shift.tab.exact="back"
238
236
  @keydown.esc.stop.prevent="close"
239
237
  >
240
- <MenuItemContent
238
+ <SrlMenuItemContent
241
239
  :item="props.item"
242
240
  :depth="props.depth"
243
241
  :disableClasses="props.disableClasses"
@@ -265,13 +263,13 @@ const classListItem = computed(() => {
265
263
  @keydown.shift.tab.exact="back"
266
264
  @keydown.esc.stop.prevent="close"
267
265
  >
268
- <MenuItemContent
266
+ <SrlMenuItemContent
269
267
  :item="props.item"
270
268
  :depth="props.depth"
271
269
  :disableClasses="props.disableClasses"
272
270
  />
273
271
  </button>
274
- <MenuList
272
+ <SrlMenu
275
273
  v-if="props.item.children"
276
274
  ref="menu"
277
275
  :id="`${props.name}-${id}`"
@@ -51,7 +51,6 @@
51
51
  * />
52
52
  */
53
53
  import { computed, ref } from 'vue'
54
- import MenuItem from './Item.vue'
55
54
 
56
55
  type BackButtonItem = {
57
56
  title?: string
@@ -108,7 +107,7 @@ const emit = defineEmits([
108
107
  'tab',
109
108
  'back',
110
109
  ])
111
- const items = ref<Array<typeof MenuItem>>([])
110
+ const items = ref<SrlMenuItem[]>([])
112
111
 
113
112
  const opened = defineModel('opened', { type: Boolean, default: true })
114
113
 
@@ -247,7 +246,7 @@ defineExpose({
247
246
  @keydown.left.prevent.stop="focusClickable(menuItems.length - 1)"
248
247
  >
249
248
  <template v-for="(item, index) in menuItems" :key="index">
250
- <MenuItem
249
+ <SrlMenuItem
251
250
  ref="items"
252
251
  :item="item"
253
252
  :name="props.name"
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { onMounted, onUpdated, ref, watch } from 'vue'
2
+ import { onMounted, ref, watch } from 'vue'
3
3
 
4
4
  const props = defineProps<{
5
5
  accordion: {
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from 'vue';
3
- import SrlPageCustomDialog from '@//Dialog.vue';
3
+ import SrlPageCustomDialog from '@/Dialog.vue';
4
4
  import Autoload from '@/Autoload.ts';
5
5
 
6
6
  const props = withDefaults(
@@ -1,2 +1,2 @@
1
- import { defineAsyncComponent } from 'vue';
2
- export default function asyncLdComponent(app) {}
1
+ import { defineAsyncComponent } from 'vue'
2
+ export default function asyncLdComponent(app) {}
@@ -0,0 +1,2 @@
1
+ import { defineAsyncComponent } from 'vue';
2
+ export default function asyncSrlComponents(app) {}
@@ -5,7 +5,7 @@ import srlVuePlugin from '#plugins/vueSrlPlugin';
5
5
 
6
6
  import '#imports/app.scss';
7
7
 
8
- import SrlPageApp from '#components/Srl/Page/App.vue';
8
+ import SrlPageApp from '#components/App.vue';
9
9
  import router from '@/router';
10
10
 
11
11
  export default async function initProject() {
@@ -1,27 +1,10 @@
1
- import { defineComponent, type App, defineAsyncComponent } from 'vue'
2
- import SrlAriaTabChain from '#components/Srl/Aria/TabChain.vue';
3
- import SrlArticleAutoload from '#components/Srl/Article/Autoload.vue';
4
- import SrlArticleRoot from '#components/Srl/Article/Root.vue';
5
- import SrlArticleDialogButton from '#components/Srl/Article/DialogButton.vue';
6
- import SrlMenu from '#components/Srl/Menu/List.vue';
7
- import SrlPageDialog from '#components/Srl/Page/Dialog.vue';
8
-
1
+ import { type App } from 'vue'
9
2
  import asyncLdComponent from './asyncLdComponent.ts';
3
+ import asyncSrlComponents from '#plugins/asyncSrlComponents.ts';
10
4
 
11
5
  export default {
12
6
  install(app: App) {
13
- app.component('SrlAriaTabChain', defineComponent(SrlAriaTabChain));
14
- app.component('SrlArticleAutoload', defineComponent(SrlArticleAutoload));
15
- app.component('SrlArticleRoot', defineComponent(SrlArticleRoot));
16
- app.component(
17
- 'SrlArticleDialogButton',
18
- defineComponent(SrlArticleDialogButton),
19
- );
20
- app.component('SrlPageDialog', defineComponent(SrlPageDialog));
21
- app.component('SrlMenu', defineComponent(SrlMenu));
22
- app.component('SrlNoteAccordion', defineAsyncComponent(() => import('#components/Srl/Note/Accordion.vue')));
23
- app.component('SrlNoteAccordionToggle', defineAsyncComponent(() => import('#components/Srl/Note/Accordion/Toggle.vue')));
24
- app.component('SrlNoteAccordionContent', defineAsyncComponent(() => import('#components/Srl/Note/Accordion/Content.vue')));
7
+ asyncSrlComponents(app)
25
8
  asyncLdComponent(app);
26
9
  },
27
10
  };
@@ -1,20 +1,6 @@
1
- import { Component, App } from 'vue';
2
-
3
- interface window {
4
- app: App;
5
- }
6
- interface _GlobalComponents {
7
- SrlAriaTabChain: (typeof import('../components/Srl/Aria/TabChain.vue'))['default'];
8
- SrlArticleAutoload: (typeof import('../components/Srl/Article/Autoload.vue'))['default'];
9
- SrlArticleDialogButton: (typeof import('../components/Srl/Article/DialogButton.vue'))['default'];
10
- SrlArticleRoot: (typeof import('../components/Srl/Article/Root.vue'))['default'];
11
- SrlMenu: (typeof import('../components/Srl/Menu/List.vue'))['default'];
12
- SrlPageDialog: (typeof import('../components/Srl/Page/Dialog.vue'))['default'];
13
- SrlNoteAccordion: (typeof import('../components/Srl/Note/Accordion.vue'))['default'];
14
- SrlNoteAccordionToggle: (typeof import('../components/Srl/Note/Accordion/Toggle.vue'))['default'];
15
- SrlNoteAccordionContent: (typeof import('../components/Srl/Note/Accordion/Content.vue'))['default'];
16
- }
17
-
1
+ export {}
2
+ declare global {}
3
+ interface _GlobalComponents {}
18
4
  declare module '@vue/runtime-core' {
19
5
  export interface GlobalComponents extends _GlobalComponents {}
20
6
  }
@@ -0,0 +1,7 @@
1
+ import { App } from 'vue'
2
+ export {}
3
+ declare global {
4
+ interface Window {
5
+ app: App;
6
+ }
7
+ }
@@ -1,6 +1,6 @@
1
1
  export {};
2
2
  declare global {
3
- export type NsWowSettings = {
3
+ type NsWowSettings = {
4
4
  languages: string[];
5
5
  defaultLanguage: string;
6
6
  shortBreadcrumb: boolean;
@@ -10,7 +10,7 @@ declare global {
10
10
  categories: string[];
11
11
  };
12
12
 
13
- export interface NsWowArticle {
13
+ interface NsWowArticle {
14
14
  uuid: string;
15
15
  name: string;
16
16
  translatedTitle: string;
@@ -23,11 +23,11 @@ declare global {
23
23
  status: string;
24
24
  }
25
25
 
26
- export interface NsWowArticles {
26
+ interface NsWowArticles {
27
27
  [locale: string]: NsWowArticle[];
28
28
  }
29
29
 
30
- export type NsWowConfig = {
30
+ type NsWowConfig = {
31
31
  locale: string;
32
32
  settings: NsWowSettings;
33
33
  articles: NsWowArticles;
@@ -40,15 +40,15 @@ declare global {
40
40
  };
41
41
  };
42
42
 
43
- export type NsWowTranslations = {
43
+ type NsWowTranslations = {
44
44
  [locale: string]: NsWowTranslation;
45
45
  };
46
46
 
47
- export type NsWowTranslation = {
47
+ type NsWowTranslation = {
48
48
  [key: string]: string;
49
49
  };
50
50
 
51
- export interface NsWowMenu {
51
+ interface NsWowMenu {
52
52
  label: string;
53
53
  type: string;
54
54
  page?: string;
@@ -57,11 +57,11 @@ declare global {
57
57
  submenuEntries?: NsWowMenu[];
58
58
  }
59
59
 
60
- export interface NsWowMenus {
60
+ interface NsWowMenus {
61
61
  [menu: string]: NsWowMenu[];
62
62
  }
63
63
 
64
- export interface NsWowResponseRouting {
64
+ interface NsWowResponseRouting {
65
65
  version: string;
66
66
  pages: NsWowArticle[];
67
67
  menu: {
@@ -69,7 +69,7 @@ declare global {
69
69
  };
70
70
  }
71
71
 
72
- export interface NsWowDownload {
72
+ interface NsWowDownload {
73
73
  type: string;
74
74
  title: string;
75
75
  fileType: string;
@@ -77,19 +77,19 @@ declare global {
77
77
  artifact: string;
78
78
  }
79
79
 
80
- export type NsWowDownloads = {
80
+ type NsWowDownloads = {
81
81
  version?: string;
82
82
  structure: NsWowDownload[];
83
83
  annualReport?: NsWowDownload;
84
84
  };
85
85
 
86
- export type NsWowSearchList = {
86
+ type NsWowSearchList = {
87
87
  article: NsWowArticle;
88
88
  href: string;
89
89
  words: string;
90
90
  };
91
91
 
92
- export type NsWowNavigationItem = {
92
+ type NsWowNavigationItem = {
93
93
  label: string;
94
94
  title?: string;
95
95
  icon?: string;
@@ -116,7 +116,7 @@ declare global {
116
116
  children?: NsWowNavigationItem[];
117
117
  };
118
118
 
119
- export type NsWowPageData = {
119
+ type NsWowPageData = {
120
120
  time: number;
121
121
  article: NsWowArticle | null;
122
122
  content: string;
File without changes