@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 CHANGED
@@ -19,7 +19,7 @@
19
19
  "postinstall": "srl prepare"
20
20
  },
21
21
  "dependencies": {
22
- "@simple-reporting/base": "^1.0.22",
22
+ "@simple-reporting/base": "^1.0.23",
23
23
  "axios": "^1.12.2",
24
24
  "chalk": "^5.6.2",
25
25
  "exceljs": "^4.4.0",
@@ -7,6 +7,7 @@
7
7
  $font-weight-bold: map.get(srl.$meta, font, weight, bold);
8
8
 
9
9
  body {
10
+ background: srl.colors-white-1000();
10
11
  color: srl.colors-black-1000();
11
12
  }
12
13
 
@@ -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
  */
@@ -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
+ })
@@ -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
- <div class="srl-cv">
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">
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
- </p>
15
- <p class="srl-title-h4">
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
- </p>
20
- <p class="srl-paragraph" doc-editable="cv-text-excerpt">
21
- Swiss, born 1956 <br />
22
- Appointed in 2018
23
- </p>
24
- </div>
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
- </div>
27
- <div class="srl-cv__content" doc-container="cv-content"></div>
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
- </div>
40
- </div>
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
- <div class="srl-accordion">
1
+ <srl-article-accordion class="srl-accordion" data-replace-tag="div">
2
2
  <div class="srl-accordion__inner">
3
- <div class="srl-accordion__head" doc-container="header"></div>
4
- <div class="srl-accordion__content">
5
- <div class="srl-accordion__content-inner" doc-container="content"></div>
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
- </div>
10
+ </srl-article-accordion>
@@ -6,7 +6,7 @@
6
6
  justify-content: space-between;
7
7
  border-top: srl.system-size-unit(1pt) solid srl.colors-black-1000();
8
8
  text-decoration: none;
9
- @include srl.typography-title-h5();
9
+ @include srl.typography-toc-item();
10
10
  }
11
11
 
12
12
  .srl-pdf-toc__text {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-reporting/base",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Manage srl templates, build and publish",
5
5
  "bin": {
6
6
  "srl": "cli.js"
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(';\n@use ')
913
- : '' + `;\n@use "@simple-reporting/base/scss/xbrl-core-styles.scss" as *;\n`,
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
- loadContent();
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.open();
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
- class="srl-dialog-button"
38
- type="button"
39
- :aria-controls="id"
40
- aria-haspopup="dialog"
41
- :aria-expanded="dialog?.dialogState ?? false"
42
- @click="open"
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
- v-if="content"
48
- :id="id"
49
- ref="dialog"
50
- :content="content"
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
- close() :
10
- open()
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
- await setConfig();
13
- const i18n = initI18n();
14
- const app = (window.app = createApp(SrlPageApp));
15
- app.use(i18n);
16
- app.use(router);
17
- app.use(srlVuePlugin);
18
- return app;
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
  }
@@ -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
- return Object.entries(attributes)
7
- .map(([key, value]) => (value !== null ? `${key}="${value}"` : key))
8
- .join(' ');
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
- const articles = useArticles();
13
- const locale = useLocale();
12
+ const articles = useArticles();
13
+ const locale = useLocale();
14
14
 
15
- const regex = /<a\s+([^>]+)>(.*?)<\/a>/gis;
16
- text = text.replace(regex, (match, attrString, innerText) => {
15
+ const regex = /<a\s+([^>]+)>(.*?)<\/a>/gis;
16
+ text = text.replace(regex, (match, attrString, innerText) => {
17
17
 
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
- });
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
- if (
25
- attrObj['data-note-target'] &&
26
- attrObj['data-note-target'] === 'popup'
27
- ) {
28
- attrObj.uuid = attrObj.href;
29
- delete attrObj.href;
30
- const attrs = attributesToString(attrObj);
31
- return `<srl-article-dialog-button ${attrs}>${innerText}</srl-article-dialog-button>`;
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
- if (attrObj.href) {
35
- const arrLink = attrObj.href.split('#');
43
+ if (attrObj.href) {
44
+ const arrLink = attrObj.href.split('#');
36
45
 
37
- if (isRouterPath(arrLink[0])) {
38
- delete attrObj.href;
39
- if (arrLink[0].startsWith('./')) {
40
- arrLink[0] = arrLink[0].substring(1);
41
- }
42
- if (arrLink[0] === `/${locale.value}/home`) {
43
- arrLink[0] = `/${locale.value}`;
44
- }
45
- attrObj.to = arrLink[0];
46
- if (arrLink[1]) {
47
- attrObj.to += `#${arrLink[1]}`;
48
- }
49
- const attrs = attributesToString(attrObj);
50
- return `<router-link ${attrs}>${innerText}</router-link>`;
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
- const a = articles.value.find((i) => i.uuid === arrLink[0]);
54
- if (a) {
55
- delete attrObj.href;
56
- attrObj.to = a.index
57
- ? `/${locale.value}`
58
- : `/${locale.value}/${a.slug}`;
59
- if (arrLink[1]) {
60
- attrObj.to += `#${arrLink[1]}`;
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
- return match;
68
- });
76
+ return match;
77
+ });
69
78
 
70
- text = text.replace(
71
- /<template-([a-z]+)>([\s\S]*?)<\/template-\1>/g,
72
- (_match, name, content) => `<template #${name}>${content}</template>`
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
- text = text.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match, p1) => {
76
- addCssStyles(p1);
77
- return '';
78
- });
84
+ text = text.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match, p1) => {
85
+ addCssStyles(p1);
86
+ return '';
87
+ });
79
88
 
80
- return text;
89
+ return text;
81
90
  }
82
91
 
83
92
  export default {
84
- prepareHtmlContent,
93
+ prepareHtmlContent,
85
94
  };
@@ -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 { isRouterPath, isExternalPath, camelCase, prepareHtmlContent };
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