@simple-reporting/base 1.0.22 → 1.0.23
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/dev/package.json +1 -1
- package/dev/src/assets/scss/general.scss +1 -0
- package/dev/src/assets/scss/pdf.scss +13 -0
- package/dev/src/entries/pdf.ts +14 -1
- package/dev/srl.config.json +21 -0
- package/livingdocs/040.Media/010.table/scss/pdf.scss +20 -0
- package/livingdocs/080.CV/010.cv/cv.html +34 -32
- package/livingdocs/100.Misc/010.anchor/anchor.html +1 -1
- package/livingdocs/100.Misc/020.accordion/accordion.html +7 -5
- package/livingdocs/110.PDF/100.pdf-toc-item/scss/general.scss +1 -1
- package/package.json +1 -1
- package/scripts/build.js +2 -2
- package/scripts/init.js +1 -1
- package/srl/.srl/App.vue +9 -5
- package/srl/.srl/components/Srl/Article/Accordion.vue +106 -0
- package/srl/.srl/components/Srl/Article/Dialog/Button.vue +39 -16
- package/srl/.srl/components/Srl/Category/Accordion.vue +32 -4
- package/srl/.srl/plugins/initProject.ts +12 -7
- package/srl/.srl/utils/html.ts +71 -62
- package/srl/.srl/utils/index.ts +28 -1
- package/srl/.srl/utils/pageState.ts +61 -0
- package/srl/srl/pdf/PDFNotes.ts +71 -0
- package/srl/srl/pdf/PDFSetPageNumbers.ts +25 -0
- /package/srl/srl/{Awesomizr.js → pdf/Awesomizr.js} +0 -0
package/dev/package.json
CHANGED
|
@@ -85,6 +85,19 @@ img {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/*
|
|
89
|
+
Hold titles and the following component on the same page
|
|
90
|
+
*/
|
|
91
|
+
.srl-title-h1,
|
|
92
|
+
.srl-title-h2,
|
|
93
|
+
.srl-title-h3,
|
|
94
|
+
.srl-title-h4,
|
|
95
|
+
.srl-title-h5 {
|
|
96
|
+
& + * {
|
|
97
|
+
break-before: avoid;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
/*
|
|
89
102
|
Layout
|
|
90
103
|
*/
|
package/dev/src/entries/pdf.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import '#imports/pdf.scss'
|
|
2
|
-
import * as Awesomizr from 'srl/Awesomizr.js'
|
|
2
|
+
import * as Awesomizr from 'srl/pdf/Awesomizr.js'
|
|
3
|
+
import * as PDFNotes from 'srl/pdf/PDFNotes.ts'
|
|
4
|
+
import * as PDFSetPageNumbers from 'srl/pdf/PDFSetPageNumbers.ts';
|
|
5
|
+
|
|
6
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7
|
+
new PDFNotes({
|
|
8
|
+
noteClass: '.srl-category-notes_group, .srl-category-notes_holding'
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
new PDFSetPageNumbers({
|
|
12
|
+
tocItemClass: '.srl-pdf-toc__item:not(.srl-pdf-toc__item--blank)',
|
|
13
|
+
tocItemPageNumberClass: '.srl-pdf-toc__number'
|
|
14
|
+
});
|
|
15
|
+
})
|
package/dev/srl.config.json
CHANGED
|
@@ -744,6 +744,27 @@
|
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
746
|
},
|
|
747
|
+
{
|
|
748
|
+
"name": "toc-item",
|
|
749
|
+
"font-family": "Inter",
|
|
750
|
+
"font-size": 14,
|
|
751
|
+
"line-height": 1.5,
|
|
752
|
+
"font-style": "normal",
|
|
753
|
+
"font-weight": 600,
|
|
754
|
+
"color": "black-1000",
|
|
755
|
+
"letter-spacing": 0,
|
|
756
|
+
"media": {
|
|
757
|
+
"print": {
|
|
758
|
+
"font-size": "9pt",
|
|
759
|
+
"line-height": 1.444
|
|
760
|
+
},
|
|
761
|
+
"up": {
|
|
762
|
+
"tablet-pt": {
|
|
763
|
+
"font-size": 16
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
},
|
|
747
768
|
{
|
|
748
769
|
"name": "editor-label-text",
|
|
749
770
|
"font-family": "Inter",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
@use 'srl';
|
|
2
2
|
@use 'sass:map';
|
|
3
3
|
|
|
4
|
+
$table-cells-width-in-mm: false;
|
|
5
|
+
|
|
6
|
+
.srl-table {
|
|
7
|
+
break-inside: avoid;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
.srl-table__container > div {
|
|
5
11
|
display: flex;
|
|
6
12
|
flex-direction: column;
|
|
@@ -15,6 +21,20 @@
|
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
|
|
24
|
+
@if $table-cells-width-in-mm == true {
|
|
25
|
+
.srl-table-container {
|
|
26
|
+
width: 100%;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
align-items: flex-end;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
table {
|
|
33
|
+
width: fit-content;
|
|
34
|
+
min-width: unset;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
tr {
|
|
19
39
|
&[class*="pagebreak"] {
|
|
20
40
|
break-after: always;
|
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
<srl-article-accordion class="srl-cv" data-replace-tag="div">
|
|
2
|
+
<div class="srl-grid srl-cv__header">
|
|
3
|
+
<div class="srl-grid__inner srl-cv__inner">
|
|
4
|
+
<div class="srl-cv__header-portrait">
|
|
5
|
+
<figure class="srl-cv__figure">
|
|
6
|
+
<img class="srl-cv__img" doc-image="image" />
|
|
7
|
+
</figure>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="srl-cv__header-content">
|
|
10
|
+
<p class="srl-title-h3">
|
|
11
11
|
<span class="srl-title-h3__text" doc-editable="cv-title-h3">
|
|
12
12
|
Max Mustermann
|
|
13
13
|
</span>
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
</p>
|
|
15
|
+
<p class="srl-title-h4">
|
|
16
16
|
<span class="srl-title-h4__text" doc-editable="cv-title-h4">
|
|
17
17
|
Chairman of the Group Management, Chief Executive Officer (CEO)
|
|
18
18
|
</span>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
</p>
|
|
20
|
+
<p class="srl-paragraph" doc-editable="cv-text-excerpt">
|
|
21
|
+
Swiss, born 1956 <br />
|
|
22
|
+
Appointed in 2018
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
25
26
|
</div>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<div class="srl-grid srl-cv__button-container">
|
|
29
|
-
<div class="srl-grid__inner srl-flex srl-justify-self-center">
|
|
30
|
-
<button type="button" class="srl-cv__button">
|
|
31
|
-
<span class="srl-cv__button-more" doc-editable="cv-text-more">
|
|
32
|
-
Read more
|
|
33
|
-
</span>
|
|
34
|
-
<span class="srl-cv__button-close" doc-editable="cv-text-close">
|
|
35
|
-
close
|
|
36
|
-
</span>
|
|
37
|
-
</button>
|
|
27
|
+
<div class="srl-cv__content srl-article-accordion__content">
|
|
28
|
+
<div class="srl-article-accordion__wrapper" doc-container="cv-content"></div>
|
|
38
29
|
</div>
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
<div class="srl-grid srl-cv__button-container">
|
|
31
|
+
<div class="srl-grid__inner srl-flex srl-justify-self-center">
|
|
32
|
+
<button type="button" class="srl-cv__button srl-article-accordion__toggle">
|
|
33
|
+
<span class="srl-cv__button-more" doc-editable="cv-text-more">
|
|
34
|
+
Read more
|
|
35
|
+
</span>
|
|
36
|
+
<span class="srl-cv__button-close" doc-editable="cv-text-close">
|
|
37
|
+
close
|
|
38
|
+
</span>
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</srl-article-accordion>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<p class="srl-anchor srl-editor-component" data-is-anchor="true">
|
|
1
|
+
<p class="srl-anchor srl-editor-component" data-remove-from-translate-plus="true" data-remove-from-xhtml="complete" data-is-anchor="true">
|
|
2
2
|
<span class="srl-anchor__text srl-editor-component__text" doc-editable="anchor-text">
|
|
3
3
|
Must start with a letter (A–Z or a–z); a number is not allowed. After the
|
|
4
4
|
first letter, any combination of letters (a–z, A–Z), digits (0–9), hyphens
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
<
|
|
1
|
+
<srl-article-accordion class="srl-accordion" data-replace-tag="div">
|
|
2
2
|
<div class="srl-accordion__inner">
|
|
3
|
-
<div class="srl-accordion__head"
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<div class="srl-accordion__head">
|
|
4
|
+
<button class="srl-accordion__toggle srl-article-accordion__toggle" type="button" data-replace-tag="div" doc-container="header"></button>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="srl-accordion__content srl-article-accordion__content">
|
|
7
|
+
<div class="srl-accordion__content-inner srl-article-accordion__wrapper" doc-container="content"></div>
|
|
6
8
|
</div>
|
|
7
9
|
</div>
|
|
8
|
-
</
|
|
10
|
+
</srl-article-accordion>
|
package/package.json
CHANGED
package/scripts/build.js
CHANGED
|
@@ -909,8 +909,8 @@ async function mapScss() {
|
|
|
909
909
|
await writeFileSync(
|
|
910
910
|
join(folders.srlImports, 'xbrl.scss'),
|
|
911
911
|
output.xbrl.length
|
|
912
|
-
? `@use ` + output.xbrl.join(
|
|
913
|
-
:
|
|
912
|
+
? `@use ` + output.xbrl.join(`;\n@use `)
|
|
913
|
+
: `;\n` + `;\n@use "@simple-reporting/base/scss/xbrl-core-styles.scss" as *;\n`,
|
|
914
914
|
);
|
|
915
915
|
|
|
916
916
|
return true;
|
package/scripts/init.js
CHANGED
|
@@ -58,7 +58,7 @@ async function init(folder, options) {
|
|
|
58
58
|
|
|
59
59
|
await writeFileSync(
|
|
60
60
|
`${projectPath}/.gitignore`,
|
|
61
|
-
`/.output/\n/.srl/\n/node_modules/\n/public/downloads/\n/public/html/\n/public/images/\n/public/json/\n/public/exclude/\n/dev-dist
|
|
61
|
+
`/.output/\n/.srl/\n/node_modules/\n/public/downloads/\n/public/html/\n/public/images/\n/public/json/\n/public/exclude/\n/dev-dist/\n/.DS_Store\n/.idea`,
|
|
62
62
|
);
|
|
63
63
|
console.log(`Project has created`);
|
|
64
64
|
console.log(`cd ${folder}`);
|
package/srl/.srl/App.vue
CHANGED
|
@@ -36,20 +36,24 @@
|
|
|
36
36
|
* to other components in the application.
|
|
37
37
|
*/
|
|
38
38
|
import App from '@/App.vue';
|
|
39
|
-
import { watch } from 'vue'
|
|
39
|
+
import { onMounted, watch } from 'vue'
|
|
40
40
|
import { useCssStyles } from '#composables'
|
|
41
|
+
import { setMounted } from '#utils'
|
|
41
42
|
|
|
42
|
-
const styleElement = document.createElement('style')
|
|
43
|
-
document.head.appendChild(styleElement)
|
|
43
|
+
const styleElement = document.createElement('style')
|
|
44
|
+
document.head.appendChild(styleElement)
|
|
44
45
|
|
|
45
|
-
const styleContent = useCssStyles()
|
|
46
|
+
const styleContent = useCssStyles()
|
|
46
47
|
watch(
|
|
47
48
|
styleContent.value,
|
|
48
49
|
(newStyles) => {
|
|
49
|
-
styleElement.innerHTML = newStyles.join('')
|
|
50
|
+
styleElement.innerHTML = newStyles.join('')
|
|
50
51
|
},
|
|
51
52
|
{ immediate: true },
|
|
52
53
|
);
|
|
54
|
+
onMounted(() => {
|
|
55
|
+
setMounted(true)
|
|
56
|
+
});
|
|
53
57
|
</script>
|
|
54
58
|
|
|
55
59
|
<template>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, onMounted, ref, useId} from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { isAccordionAnchored, setAccordionAnchored } from '#utils'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
toggleSelector?: string
|
|
8
|
+
contentSelector?: string
|
|
9
|
+
wrapperSelector?: string
|
|
10
|
+
openClass?: string
|
|
11
|
+
duration?: number
|
|
12
|
+
}>(), {
|
|
13
|
+
toggleSelector: '.srl-article-accordion__toggle',
|
|
14
|
+
contentSelector: '.srl-article-accordion__content',
|
|
15
|
+
wrapperSelector: '.srl-article-accordion__wrapper',
|
|
16
|
+
openClass: 'open',
|
|
17
|
+
duration: 300,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const route = useRoute()
|
|
21
|
+
|
|
22
|
+
const id = useId()
|
|
23
|
+
const rootEl = ref<HTMLDivElement>()
|
|
24
|
+
const toggle = ref<HTMLButtonElement>()
|
|
25
|
+
const content = ref<HTMLDivElement>()
|
|
26
|
+
const wrapper = ref<HTMLDivElement>()
|
|
27
|
+
const transition = computed<string>(() => {
|
|
28
|
+
return props.duration + 'ms'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function open() {
|
|
32
|
+
toggle.value?.setAttribute('aria-expanded', 'true')
|
|
33
|
+
wrapper.value?.removeAttribute('hidden')
|
|
34
|
+
content.value?.classList.add(props.openClass)
|
|
35
|
+
content.value.focus()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function close() {
|
|
39
|
+
toggle.value?.setAttribute('aria-expanded', 'false')
|
|
40
|
+
content.value?.classList.remove(props.openClass)
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
wrapper.value?.setAttribute('hidden', 'true')
|
|
43
|
+
}, props.duration)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onMounted(() => {
|
|
47
|
+
toggle.value = rootEl.value?.querySelector( props.toggleSelector ) || undefined
|
|
48
|
+
content.value = rootEl.value?.querySelector( props.contentSelector ) || undefined
|
|
49
|
+
wrapper.value = rootEl.value?.querySelector( props.wrapperSelector ) || undefined
|
|
50
|
+
|
|
51
|
+
if (toggle.value && content.value && wrapper.value) {
|
|
52
|
+
wrapper.value.setAttribute('hidden', 'true')
|
|
53
|
+
content.value.id = id
|
|
54
|
+
content.value.setAttribute('tabindex', '-1')
|
|
55
|
+
toggle.value.setAttribute('aria-controls', id)
|
|
56
|
+
|
|
57
|
+
toggle.value.addEventListener('click', () => {
|
|
58
|
+
toggle.value?.getAttribute('aria-expanded') === 'true' ? close() : open()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if (route.hash) {
|
|
62
|
+
if (rootEl.value.id && rootEl.value.id === route.hash) {
|
|
63
|
+
open()
|
|
64
|
+
isAccordionAnchored() || setAccordionAnchored(true)
|
|
65
|
+
} else {
|
|
66
|
+
const targetEl = rootEl.value?.querySelector<HTMLElement>(route.hash)
|
|
67
|
+
if (targetEl) {
|
|
68
|
+
open()
|
|
69
|
+
if (!isAccordionAnchored()) {
|
|
70
|
+
setAccordionAnchored(true)
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
rootEl.value?.scrollIntoView({
|
|
73
|
+
behavior: 'smooth',
|
|
74
|
+
block: 'start',
|
|
75
|
+
})
|
|
76
|
+
}, 200)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<div class="srl-article-accordion" ref="rootEl" tabindex="-1">
|
|
87
|
+
<slot/>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<style lang="scss">
|
|
92
|
+
.srl-article-accordion {
|
|
93
|
+
&__content {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-rows: 0fr;
|
|
96
|
+
transition: grid-template-rows v-bind(transition) ease-out;
|
|
97
|
+
|
|
98
|
+
&.open {
|
|
99
|
+
grid-template-rows: 1fr;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
&__wrapper {
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
</style>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, useId } from 'vue'
|
|
2
|
+
import { ref, useId } from 'vue'
|
|
3
3
|
import { useArticles, useConfig } from '#composables';
|
|
4
|
-
import { prepareHtmlContent } from '#utils';
|
|
4
|
+
import { prepareHtmlContent, isDialogStored, addDialogToStorage, getDialogFromStorage } from '#utils';
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
7
|
uuid: string;
|
|
8
|
+
anchor?: string;
|
|
8
9
|
}>();
|
|
9
10
|
|
|
10
11
|
const config = useConfig();
|
|
@@ -12,6 +13,8 @@ const articles = useArticles();
|
|
|
12
13
|
const id = ref<string>(`srl-page__dialog-${useId()}`);
|
|
13
14
|
const content = ref<string>('');
|
|
14
15
|
const dialog = ref<SrlPageDialog | null>(null);
|
|
16
|
+
let dialogStored = false
|
|
17
|
+
|
|
15
18
|
async function loadContent() {
|
|
16
19
|
const article = articles.value.find((article) => article.uuid === props.uuid);
|
|
17
20
|
if (article) {
|
|
@@ -20,35 +23,55 @@ async function loadContent() {
|
|
|
20
23
|
const req = await fetch(file);
|
|
21
24
|
const text = await req.text();
|
|
22
25
|
content.value = prepareHtmlContent(text);
|
|
26
|
+
// Do something with anchor if provided
|
|
27
|
+
/*
|
|
28
|
+
if (props.anchor) {
|
|
29
|
+
nextTick(() => {
|
|
30
|
+
// Do action
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
*/
|
|
23
34
|
} catch (error) {
|
|
24
35
|
console.error(`Failed to load article content from ${file}:`, error);
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
if (isDialogStored(props.uuid)) {
|
|
41
|
+
dialogStored = true;
|
|
42
|
+
} else {
|
|
43
|
+
addDialogToStorage(props.uuid, dialog);
|
|
44
|
+
loadContent();
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
async function open() {
|
|
31
|
-
dialog.value
|
|
48
|
+
if (dialog.value) {
|
|
49
|
+
dialog.value.open();
|
|
50
|
+
} else {
|
|
51
|
+
const storage = getDialogFromStorage(props.uuid);
|
|
52
|
+
storage ?
|
|
53
|
+
storage.open():
|
|
54
|
+
console.warn(`Dialog with uuid ${props.uuid} not found in storage.`);
|
|
55
|
+
}
|
|
32
56
|
}
|
|
33
57
|
</script>
|
|
34
58
|
|
|
35
59
|
<template>
|
|
36
60
|
<button
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
class="srl-dialog-button"
|
|
62
|
+
type="button"
|
|
63
|
+
:aria-controls="id"
|
|
64
|
+
aria-haspopup="dialog"
|
|
65
|
+
:aria-expanded="dialog?.dialogState ?? false"
|
|
66
|
+
@click="open"
|
|
43
67
|
>
|
|
44
68
|
<slot />
|
|
45
|
-
<Teleport to="body">
|
|
69
|
+
<Teleport v-if="content" to="body">
|
|
46
70
|
<SrlPageDialog
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@close="expanded = false"
|
|
71
|
+
:id="id"
|
|
72
|
+
ref="dialog"
|
|
73
|
+
:content="content"
|
|
74
|
+
@close="expanded = false"
|
|
52
75
|
/>
|
|
53
76
|
</Teleport>
|
|
54
77
|
</button>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, useId } from 'vue'
|
|
2
|
+
import { computed, nextTick, onMounted, ref, useId } from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { isAccordionAnchored, setAccordionAnchored } from '#utils'
|
|
3
5
|
|
|
4
6
|
const rootEl = ref<HTMLElement | null>(null)
|
|
5
7
|
const id = ref(useId())
|
|
6
8
|
const state = ref(false)
|
|
9
|
+
const route = useRoute()
|
|
7
10
|
function toggle() {
|
|
8
11
|
state.value ?
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
close() :
|
|
13
|
+
open()
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
function open() {
|
|
@@ -32,10 +35,35 @@ const accordion = computed(() => {
|
|
|
32
35
|
}
|
|
33
36
|
})
|
|
34
37
|
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
if (route.hash) {
|
|
40
|
+
if (rootEl.value.id && rootEl.value.id === route.hash) {
|
|
41
|
+
open()
|
|
42
|
+
isAccordionAnchored() || setAccordionAnchored(true)
|
|
43
|
+
} else {
|
|
44
|
+
const targetEl = rootEl.value?.querySelector<HTMLElement>(route.hash)
|
|
45
|
+
if (targetEl) {
|
|
46
|
+
open()
|
|
47
|
+
if (!isAccordionAnchored()) {
|
|
48
|
+
setAccordionAnchored(true)
|
|
49
|
+
nextTick(() => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
rootEl.value.scrollIntoView({
|
|
52
|
+
behavior: 'smooth',
|
|
53
|
+
block: 'start',
|
|
54
|
+
})
|
|
55
|
+
}, 200)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
35
63
|
</script>
|
|
36
64
|
|
|
37
65
|
<template>
|
|
38
|
-
<div ref="rootEl">
|
|
66
|
+
<div ref="rootEl" tabindex="-1">
|
|
39
67
|
<slot :accordion="accordion"/>
|
|
40
68
|
</div>
|
|
41
69
|
</template>
|
|
@@ -2,6 +2,7 @@ import { setConfig } from '#composables/config';
|
|
|
2
2
|
import { createApp } from 'vue';
|
|
3
3
|
import { initI18n } from '@/i18n';
|
|
4
4
|
import srlVuePlugin from '#plugins/vueSrlPlugin';
|
|
5
|
+
import { clearPageState } from '#utils'
|
|
5
6
|
|
|
6
7
|
import '#imports/app.scss';
|
|
7
8
|
|
|
@@ -9,11 +10,15 @@ import SrlPageApp from '../App.vue';
|
|
|
9
10
|
import router from '@/router';
|
|
10
11
|
|
|
11
12
|
export default async function initProject() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
router.beforeEach((to, from, next) => {
|
|
14
|
+
clearPageState();
|
|
15
|
+
next();
|
|
16
|
+
});
|
|
17
|
+
await setConfig();
|
|
18
|
+
const i18n = initI18n();
|
|
19
|
+
const app = (window.app = createApp(SrlPageApp));
|
|
20
|
+
app.use(i18n);
|
|
21
|
+
app.use(router);
|
|
22
|
+
app.use(srlVuePlugin);
|
|
23
|
+
return app;
|
|
19
24
|
}
|
package/srl/.srl/utils/html.ts
CHANGED
|
@@ -3,83 +3,92 @@ import { useArticles, useLocale, addCssStyles } from '#composables';
|
|
|
3
3
|
|
|
4
4
|
type AttrObj = { [key: string]: string | null };
|
|
5
5
|
function attributesToString(attributes: Record<string, string | null>): string {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
return Object.entries(attributes)
|
|
7
|
+
.map(([key, value]) => (value !== null ? `${key}="${value}"` : key))
|
|
8
|
+
.join(' ');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function prepareHtmlContent(text: string): string {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const articles = useArticles();
|
|
13
|
+
const locale = useLocale();
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const regex = /<a\s+([^>]+)>(.*?)<\/a>/gis;
|
|
16
|
+
text = text.replace(regex, (match, attrString, innerText) => {
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const attrObj: AttrObj = {};
|
|
19
|
+
attrString.replace(/([a-zA-Z0-9\-_]+)(?:="([^"]*)")?/g, (m, key: string, value: string | null) => {
|
|
20
|
+
attrObj[key] = value || null;
|
|
21
|
+
return m;
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
if (
|
|
25
|
+
attrObj['data-note-target'] &&
|
|
26
|
+
attrObj['data-note-target'] === 'popup'
|
|
27
|
+
) {
|
|
28
|
+
attrObj.uuid = attrObj.href;
|
|
29
|
+
delete attrObj.href;
|
|
30
|
+
if (attrObj['data-article-name']) {
|
|
31
|
+
attrObj.uuid = attrObj['data-article-name'];
|
|
32
|
+
delete attrObj['data-article-name'];
|
|
33
|
+
}
|
|
34
|
+
const arrUuid = attrObj.uuid.split('#');
|
|
35
|
+
attrObj.uuid = arrUuid[0];
|
|
36
|
+
if (arrUuid[1]) {
|
|
37
|
+
attrObj.anchor = arrUuid[1];
|
|
38
|
+
}
|
|
39
|
+
const attrs = attributesToString(attrObj);
|
|
40
|
+
return `<srl-article-dialog-button ${attrs}>${innerText}</srl-article-dialog-button>`;
|
|
41
|
+
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
if (attrObj.href) {
|
|
44
|
+
const arrLink = attrObj.href.split('#');
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
if (isRouterPath(arrLink[0])) {
|
|
47
|
+
delete attrObj.href;
|
|
48
|
+
if (arrLink[0].startsWith('./')) {
|
|
49
|
+
arrLink[0] = arrLink[0].substring(1);
|
|
50
|
+
}
|
|
51
|
+
if (arrLink[0] === `/${locale.value}/home`) {
|
|
52
|
+
arrLink[0] = `/${locale.value}`;
|
|
53
|
+
}
|
|
54
|
+
attrObj.to = arrLink[0];
|
|
55
|
+
if (arrLink[1]) {
|
|
56
|
+
attrObj.to += `#${arrLink[1]}`;
|
|
57
|
+
}
|
|
58
|
+
const attrs = attributesToString(attrObj);
|
|
59
|
+
return `<router-link ${attrs}>${innerText}</router-link>`;
|
|
60
|
+
}
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
const a = articles.value.find((i) => i.uuid === arrLink[0]);
|
|
63
|
+
if (a) {
|
|
64
|
+
delete attrObj.href;
|
|
65
|
+
attrObj.to = a.index
|
|
66
|
+
? `/${locale.value}`
|
|
67
|
+
: `/${locale.value}/${a.slug}`;
|
|
68
|
+
if (arrLink[1]) {
|
|
69
|
+
attrObj.to += `#${arrLink[1]}`;
|
|
70
|
+
}
|
|
71
|
+
const attrs = attributesToString(attrObj);
|
|
72
|
+
return `<router-link ${attrs}>${innerText}</router-link>`;
|
|
73
|
+
}
|
|
61
74
|
}
|
|
62
|
-
const attrs = attributesToString(attrObj);
|
|
63
|
-
return `<router-link ${attrs}>${innerText}</router-link>`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
return match;
|
|
77
|
+
});
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
text = text.replace(
|
|
80
|
+
/<template-([a-z]+)>([\s\S]*?)<\/template-\1>/g,
|
|
81
|
+
(_match, name, content) => `<template #${name}>${content}</template>`
|
|
82
|
+
);
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
text = text.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match, p1) => {
|
|
85
|
+
addCssStyles(p1);
|
|
86
|
+
return '';
|
|
87
|
+
});
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
return text;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
export default {
|
|
84
|
-
|
|
93
|
+
prepareHtmlContent,
|
|
85
94
|
};
|
package/srl/.srl/utils/index.ts
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { isRouterPath, isExternalPath } from './uri';
|
|
2
2
|
import { camelCase } from './camelCase';
|
|
3
3
|
import { prepareHtmlContent } from './html';
|
|
4
|
+
import {
|
|
5
|
+
usePageState,
|
|
6
|
+
clearPageState,
|
|
7
|
+
isDialogStored,
|
|
8
|
+
addDialogToStorage,
|
|
9
|
+
getDialogFromStorage,
|
|
10
|
+
getDialogStorage,
|
|
11
|
+
isAccordionAnchored,
|
|
12
|
+
setAccordionAnchored,
|
|
13
|
+
isMounted,
|
|
14
|
+
setMounted
|
|
15
|
+
} from './pageState.ts';
|
|
4
16
|
|
|
5
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
isRouterPath,
|
|
19
|
+
isExternalPath,
|
|
20
|
+
camelCase,
|
|
21
|
+
prepareHtmlContent,
|
|
22
|
+
usePageState,
|
|
23
|
+
clearPageState,
|
|
24
|
+
isAccordionAnchored,
|
|
25
|
+
setAccordionAnchored,
|
|
26
|
+
isDialogStored,
|
|
27
|
+
addDialogToStorage,
|
|
28
|
+
getDialogFromStorage,
|
|
29
|
+
getDialogStorage,
|
|
30
|
+
setMounted,
|
|
31
|
+
isMounted,
|
|
32
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
type DialogStorage = {
|
|
4
|
+
[key: string]: Ref<SrlPageDialog>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type PageState = {
|
|
8
|
+
isMounted: boolean;
|
|
9
|
+
dialogStorage: DialogStorage;
|
|
10
|
+
accordionAnchored: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const pageState: PageState = {
|
|
14
|
+
isMounted: false,
|
|
15
|
+
dialogStorage: {},
|
|
16
|
+
accordionAnchored: false,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function usePageState() {
|
|
20
|
+
return pageState;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function setMounted(value: boolean): void {
|
|
24
|
+
pageState.isMounted = value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isMounted(): boolean {
|
|
28
|
+
return pageState.isMounted;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function clearPageState() {
|
|
32
|
+
pageState.isMounted = false;
|
|
33
|
+
pageState.dialogStorage = {};
|
|
34
|
+
pageState.accordionAnchored = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isDialogStored(uuid: string): boolean {
|
|
38
|
+
return uuid in pageState.dialogStorage
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function addDialogToStorage(uuid: string, refSrlPageDialog: Ref<SrlPageDialog>): void {
|
|
42
|
+
pageState.dialogStorage[uuid] = refSrlPageDialog
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getDialogFromStorage(uuid: string): SrlPageDialog | null {
|
|
46
|
+
return isDialogStored(uuid)?
|
|
47
|
+
pageState.dialogStorage[uuid].value : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getDialogStorage() : DialogStorage {
|
|
51
|
+
return pageState.dialogStorage
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function setAccordionAnchored(value: boolean): void {
|
|
55
|
+
pageState.accordionAnchored = value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isAccordionAnchored(): boolean {
|
|
59
|
+
return pageState.accordionAnchored;
|
|
60
|
+
}
|
|
61
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface PDFNotesConfig {
|
|
2
|
+
noteClass: string; // kann mehrere Selektoren per Komma enthalten
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default class PDFNotes {
|
|
6
|
+
private notesArticles: NodeListOf<HTMLElement>;
|
|
7
|
+
private selectors: string[];
|
|
8
|
+
private excludedClasses = ['srl-grid', 'srl-linkable'];
|
|
9
|
+
|
|
10
|
+
constructor(config: PDFNotesConfig) {
|
|
11
|
+
const { noteClass } = config;
|
|
12
|
+
|
|
13
|
+
if (!noteClass) {
|
|
14
|
+
console.warn("PDFNotes: 'noteClass' ist erforderlich.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Selektoren sauber splitten
|
|
19
|
+
this.selectors = noteClass.split(',').map(s => s.trim()).filter(Boolean);
|
|
20
|
+
|
|
21
|
+
// Gesamtliste nur wenn du sie sonst brauchst
|
|
22
|
+
this.notesArticles = document.querySelectorAll(noteClass);
|
|
23
|
+
|
|
24
|
+
if (this.notesArticles.length === 0) {
|
|
25
|
+
console.warn(`PDFNotes: Keine Elemente gefunden mit Selektor '${noteClass}'.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.markLastNotePerSelector();
|
|
30
|
+
this.setFirstAndLastNoteClass();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private setFirstAndLastNoteClass(): void {
|
|
34
|
+
this.notesArticles.forEach(container => {
|
|
35
|
+
const children = Array.from(container.children) as HTMLElement[];
|
|
36
|
+
if (children.length === 0) return;
|
|
37
|
+
|
|
38
|
+
const firstChild = children[0];
|
|
39
|
+
const lastChild = children[children.length - 1];
|
|
40
|
+
|
|
41
|
+
const getRelevantClass = (el: HTMLElement): string | null => {
|
|
42
|
+
const classes = Array.from(el.classList);
|
|
43
|
+
const relevant = classes.find(cls => !this.excludedClasses.includes(cls));
|
|
44
|
+
return relevant || null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const firstClass = getRelevantClass(firstChild);
|
|
48
|
+
const lastClass = getRelevantClass(lastChild);
|
|
49
|
+
|
|
50
|
+
if (firstClass) container.classList.add(`${firstClass}-first`);
|
|
51
|
+
if (lastClass && lastClass !== firstClass) container.classList.add(`${lastClass}-last`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Neu: letzte Note je Selektor markieren
|
|
56
|
+
private markLastNotePerSelector(): void {
|
|
57
|
+
if (!this.selectors || this.selectors.length === 0) return;
|
|
58
|
+
|
|
59
|
+
this.selectors.forEach(sel => {
|
|
60
|
+
const nodes = document.querySelectorAll<HTMLElement>(sel);
|
|
61
|
+
if (nodes.length === 0) return;
|
|
62
|
+
|
|
63
|
+
const last = nodes[nodes.length - 1];
|
|
64
|
+
last.classList.add('last-note');
|
|
65
|
+
|
|
66
|
+
// Falls du unterschiedliche Klassen pro Typ willst, entkommentieren:
|
|
67
|
+
// const typeKey = sel.replace(/^[.#]/, '').replace(/[^a-zA-Z0-9_]/g, '_');
|
|
68
|
+
// last.classList.add(`last-note-${typeKey}`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface PDFSetPageNumbersConfig {
|
|
2
|
+
tocItemClass: string;
|
|
3
|
+
tocItemPageNumberClass: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export default class PDFSetPageNumbers {
|
|
7
|
+
private tocItems: NodeListOf<HTMLElement>;
|
|
8
|
+
private pageNumberClass: string;
|
|
9
|
+
|
|
10
|
+
constructor(config: PDFSetPageNumbersConfig) {
|
|
11
|
+
const { tocItemClass, tocItemPageNumberClass } = config;
|
|
12
|
+
|
|
13
|
+
this.tocItems = document.querySelectorAll(tocItemClass);
|
|
14
|
+
this.pageNumberClass = tocItemPageNumberClass;
|
|
15
|
+
|
|
16
|
+
this.tocItems.forEach(item => {
|
|
17
|
+
const link = item.getAttribute('href');
|
|
18
|
+
const num = item.querySelector<HTMLElement>(this.pageNumberClass);
|
|
19
|
+
|
|
20
|
+
if (num && link) {
|
|
21
|
+
num.setAttribute('data-page-number', link);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
File without changes
|