erudit 3.0.0-dev.15 → 3.0.0-dev.16

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 (243) hide show
  1. package/app/app.vue +191 -195
  2. package/app/components/Loading.vue +23 -23
  3. package/app/components/SiteAside.vue +393 -393
  4. package/app/components/SiteMain.vue +32 -32
  5. package/app/components/ads/Ads.vue +35 -0
  6. package/app/components/ads/AdsBannerAside.vue +61 -0
  7. package/app/components/ads/AdsBannerBottom.vue +22 -0
  8. package/app/components/ads/AdsProviderCustom.vue +35 -0
  9. package/app/components/ads/AdsProviderYandex.vue +54 -0
  10. package/app/components/ads/AdsReplacer.vue +73 -0
  11. package/app/components/aside/AsideListItem.vue +74 -74
  12. package/app/components/aside/AsideMajor.vue +56 -56
  13. package/app/components/aside/AsideMinor.vue +73 -71
  14. package/app/components/aside/major/PaneContentScroll.vue +23 -23
  15. package/app/components/aside/major/PaneSwitch.vue +54 -54
  16. package/app/components/aside/major/PaneSwitchButton.vue +63 -63
  17. package/app/components/aside/major/SiteInfo.vue +85 -85
  18. package/app/components/aside/major/panes/Language.vue +79 -79
  19. package/app/components/aside/major/panes/Pages.vue +34 -34
  20. package/app/components/aside/major/panes/Search.vue +11 -11
  21. package/app/components/aside/major/panes/nav/Nav.vue +92 -92
  22. package/app/components/aside/major/panes/nav/NavBook.vue +95 -95
  23. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -24
  24. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -16
  25. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -105
  26. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -32
  27. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -40
  28. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -60
  29. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -34
  30. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -80
  31. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -24
  32. package/app/components/aside/major/panes/other/ItemContent.vue +29 -29
  33. package/app/components/aside/major/panes/other/ItemGenerator.vue +13 -13
  34. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -54
  35. package/app/components/aside/major/panes/other/Other.vue +16 -16
  36. package/app/components/aside/minor/AsideMinorContributor.vue +5 -5
  37. package/app/components/aside/minor/AsideMinorNews.vue +11 -11
  38. package/app/components/aside/minor/AsideMinorPane.vue +15 -15
  39. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -67
  40. package/app/components/aside/minor/Contribute.vue +145 -145
  41. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -92
  42. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -32
  43. package/app/components/aside/minor/topic/TopicContributors.vue +177 -177
  44. package/app/components/aside/minor/topic/TopicNav.vue +49 -49
  45. package/app/components/aside/minor/topic/TopicToc.vue +214 -214
  46. package/app/components/aside/minor/topic/TopicTocItem.vue +32 -32
  47. package/app/components/aside/utils/AsideOverlayPane.vue +40 -40
  48. package/app/components/bitran/BitranContent.vue +91 -91
  49. package/app/components/bitran/RenderWrapper.vue +10 -10
  50. package/app/components/contributor/ContributorAvatar.vue +45 -45
  51. package/app/components/contributor/ContributorListItem.vue +35 -35
  52. package/app/components/main/topic/MainTopic.vue +88 -78
  53. package/app/components/main/topic/TopicPartSwitch.vue +118 -118
  54. package/app/components/main/utils/Breadcrumb.vue +74 -70
  55. package/app/components/main/utils/ContentDecoration.vue +29 -29
  56. package/app/components/main/utils/ContentDescription.vue +19 -19
  57. package/app/components/main/utils/ContentPopover.vue +188 -188
  58. package/app/components/main/utils/ContentPopovers.vue +111 -111
  59. package/app/components/main/utils/ContentReferences.vue +70 -70
  60. package/app/components/main/utils/ContentSection.vue +45 -45
  61. package/app/components/main/utils/ContentTitle.vue +63 -63
  62. package/app/components/main/utils/reference/ReferenceGroup.vue +38 -38
  63. package/app/components/main/utils/reference/ReferenceItem.vue +70 -70
  64. package/app/components/main/utils/reference/ReferenceSource.vue +120 -118
  65. package/app/components/preview/Preview.vue +186 -186
  66. package/app/components/preview/PreviewDisplay.vue +139 -139
  67. package/app/components/preview/PreviewFooterAction.vue +73 -73
  68. package/app/components/preview/PreviewLoading.vue +14 -14
  69. package/app/components/preview/PreviewScreen.vue +141 -141
  70. package/app/components/preview/display/Alert.vue +50 -50
  71. package/app/components/preview/display/Custom.vue +18 -18
  72. package/app/components/preview/display/GenericLink.vue +48 -48
  73. package/app/components/preview/display/PageLink.vue +22 -20
  74. package/app/components/preview/display/Unique.vue +55 -55
  75. package/app/components/transition/Fade.vue +19 -19
  76. package/app/components/tree/TreeContainer.vue +11 -11
  77. package/app/components/tree/TreeItem.vue +89 -89
  78. package/app/composables/bitran.ts +108 -108
  79. package/app/composables/bitranContent.ts +92 -92
  80. package/app/composables/bitranLocation.ts +7 -7
  81. package/app/composables/contentData.ts +38 -38
  82. package/app/composables/contentPage.ts +158 -158
  83. package/app/composables/contentRoute.ts +45 -45
  84. package/app/composables/darkMagic.ts +24 -24
  85. package/app/composables/externalApi.ts +69 -63
  86. package/app/composables/favicon.ts +8 -8
  87. package/app/composables/formatText.ts +99 -99
  88. package/app/composables/majorPane.ts +60 -60
  89. package/app/composables/phrases.ts +65 -65
  90. package/app/composables/theme.ts +29 -29
  91. package/app/composables/url.ts +33 -33
  92. package/app/pages/_test/preview.vue +110 -110
  93. package/app/pages/article/[...articleId].vue +3 -3
  94. package/app/pages/book/[...bookId].vue +47 -47
  95. package/app/pages/group/[...groupId].vue +66 -64
  96. package/app/pages/index.vue +32 -32
  97. package/app/pages/members.vue +6 -6
  98. package/app/pages/practice/[...practice].vue +3 -3
  99. package/app/pages/summary/[...summaryId].vue +3 -3
  100. package/app/plugins/analytics.ts +87 -0
  101. package/app/plugins/prerender.server.ts +22 -22
  102. package/app/scripts/_immediate.js +9 -9
  103. package/app/scripts/aside/index.ts +59 -59
  104. package/app/scripts/aside/major/nav.ts +26 -26
  105. package/app/scripts/aside/minor/state.ts +37 -37
  106. package/app/scripts/aside/minor/topic.ts +3 -3
  107. package/app/scripts/flag.ts +28 -28
  108. package/app/scripts/og.ts +27 -27
  109. package/app/scripts/preview/build.ts +76 -76
  110. package/app/scripts/preview/data/alert.ts +19 -19
  111. package/app/scripts/preview/data/custom.ts +8 -8
  112. package/app/scripts/preview/data/genericLink.ts +24 -24
  113. package/app/scripts/preview/data/pageLink.ts +23 -23
  114. package/app/scripts/preview/data/unique.ts +72 -72
  115. package/app/scripts/preview/data.ts +24 -24
  116. package/app/scripts/preview/display.ts +37 -37
  117. package/app/scripts/preview/footer.ts +9 -9
  118. package/app/scripts/preview/request.ts +51 -51
  119. package/app/scripts/preview/state.ts +63 -63
  120. package/app/styles/_immediate.css +7 -7
  121. package/app/styles/_util.scss +43 -43
  122. package/app/styles/_utils.scss +44 -44
  123. package/app/styles/app.scss +91 -91
  124. package/app/styles/def/_bp.scss +27 -27
  125. package/app/styles/def/_size.scss +7 -7
  126. package/app/styles/def/_z.scss +5 -5
  127. package/app/styles/normalize.scss +49 -49
  128. package/app/styles/partials/_darkMagic.scss +5 -5
  129. package/app/styles/partials/_fnav.scss +15 -15
  130. package/app/styles/partials/_preview.scss +5 -5
  131. package/bin/erudit.mjs +2 -2
  132. package/const.ts +4 -4
  133. package/globalPath.ts +21 -21
  134. package/globals/bitran.ts +1 -1
  135. package/globals/content.ts +27 -27
  136. package/globals/contributor.ts +5 -5
  137. package/globals/erudit.ts +5 -5
  138. package/globals/register.ts +18 -18
  139. package/languages/en.ts +94 -94
  140. package/languages/ru.ts +98 -98
  141. package/module/bitran.ts +66 -66
  142. package/module/config.ts +35 -35
  143. package/module/imports.ts +67 -67
  144. package/module/index.ts +47 -49
  145. package/module/logger.ts +10 -10
  146. package/module/paths.ts +22 -22
  147. package/module/restart.ts +61 -61
  148. package/nuxt.config.ts +131 -134
  149. package/package.json +8 -7
  150. package/server/api/aside/major/nav/bookIds.ts +5 -5
  151. package/server/api/aside/major/nav/bookNav/[...bookId].ts +20 -20
  152. package/server/api/aside/major/nav/global.ts +7 -7
  153. package/server/api/aside/minor/news.ts +7 -7
  154. package/server/api/aside/minor/path.ts +82 -82
  155. package/server/api/bitran/content/[...location].ts +10 -10
  156. package/server/api/bitran/toc/[...location].ts +9 -9
  157. package/server/api/content/data.ts +75 -75
  158. package/server/api/contributor/count.ts +6 -6
  159. package/server/api/fake/content.ts +11 -11
  160. package/server/api/fake/shared/languages.ts +12 -12
  161. package/server/api/language/functions.ts +12 -12
  162. package/server/api/language/phrase/[phraseId].ts +19 -19
  163. package/server/api/language/phraseIds.ts +8 -8
  164. package/server/api/prerender.ts +120 -120
  165. package/server/api/preview/page/[...parts].ts +78 -78
  166. package/server/api/preview/unique/[...location].ts +61 -61
  167. package/server/api/problem/generator/[...path].ts +26 -0
  168. package/server/plugin/bitran/content.ts +190 -190
  169. package/server/plugin/bitran/elements/include.ts +229 -229
  170. package/server/plugin/bitran/location.ts +39 -39
  171. package/server/plugin/bitran/toc.ts +94 -94
  172. package/server/plugin/bitran/transpiler.ts +18 -18
  173. package/server/plugin/build/close.ts +12 -12
  174. package/server/plugin/build/jobs/content/builderArgs.ts +8 -8
  175. package/server/plugin/build/jobs/content/generic.ts +191 -191
  176. package/server/plugin/build/jobs/content/parse.ts +113 -113
  177. package/server/plugin/build/jobs/content/path.ts +6 -6
  178. package/server/plugin/build/jobs/content/type/book.ts +9 -9
  179. package/server/plugin/build/jobs/content/type/group.ts +37 -37
  180. package/server/plugin/build/jobs/content/type/topic.ts +36 -36
  181. package/server/plugin/build/jobs/contributors.ts +66 -66
  182. package/server/plugin/build/jobs/language.ts +36 -36
  183. package/server/plugin/build/jobs/nav.ts +345 -265
  184. package/server/plugin/build/process.ts +32 -32
  185. package/server/plugin/build/rebuild.ts +66 -66
  186. package/server/plugin/build/setup.ts +19 -19
  187. package/server/plugin/content/context.ts +119 -119
  188. package/server/plugin/db/entities/Book.ts +7 -7
  189. package/server/plugin/db/entities/Content.ts +45 -45
  190. package/server/plugin/db/entities/Contribution.ts +10 -10
  191. package/server/plugin/db/entities/Contributor.ts +16 -16
  192. package/server/plugin/db/entities/File.ts +10 -10
  193. package/server/plugin/db/entities/Group.ts +14 -14
  194. package/server/plugin/db/entities/Hash.ts +15 -15
  195. package/server/plugin/db/entities/Topic.ts +20 -20
  196. package/server/plugin/db/entities/Unique.ts +21 -21
  197. package/server/plugin/db/reset.ts +12 -12
  198. package/server/plugin/db/setup.ts +49 -49
  199. package/server/plugin/global.ts +16 -16
  200. package/server/plugin/importer.ts +16 -16
  201. package/server/plugin/index.ts +9 -9
  202. package/server/plugin/logger.ts +23 -23
  203. package/server/plugin/nav/node.ts +27 -27
  204. package/server/plugin/nav/utils.ts +175 -175
  205. package/server/plugin/repository/book.ts +21 -21
  206. package/server/plugin/repository/content.ts +240 -240
  207. package/server/plugin/repository/contentId.ts +40 -40
  208. package/server/plugin/repository/contributor.ts +8 -8
  209. package/server/plugin/repository/file.ts +10 -10
  210. package/server/plugin/repository/frontNav.ts +145 -145
  211. package/server/plugin/repository/topic.ts +35 -35
  212. package/server/tsconfig.json +9 -9
  213. package/shared/aside/minor.ts +51 -51
  214. package/shared/asset.ts +22 -22
  215. package/shared/bitran/contentId.ts +56 -56
  216. package/shared/bitran/stringContent.ts +6 -6
  217. package/shared/bitran/toc.ts +8 -8
  218. package/shared/content/bookId.ts +12 -12
  219. package/shared/content/context.ts +9 -9
  220. package/shared/content/data/base.ts +32 -32
  221. package/shared/content/data/index.ts +5 -5
  222. package/shared/content/data/type/book.ts +5 -5
  223. package/shared/content/data/type/group.ts +6 -6
  224. package/shared/content/data/type/topic.ts +11 -11
  225. package/shared/content/previousNext.ts +9 -9
  226. package/shared/contributor.ts +5 -5
  227. package/shared/frontNav.ts +41 -41
  228. package/shared/icons.ts +38 -38
  229. package/shared/image.ts +5 -5
  230. package/shared/link.ts +28 -28
  231. package/shared/popover.ts +8 -8
  232. package/shared/types/language.ts +74 -74
  233. package/shared/utils/objectsEqual.ts +4 -4
  234. package/shared/utils/stringColor.ts +9 -9
  235. package/test/contentId.test.ts +91 -91
  236. package/tsconfig.json +8 -8
  237. package/utils/contentPath.ts +67 -67
  238. package/utils/slash.ts +11 -11
  239. package/utils/stress.ts +9 -9
  240. package/app/components/ads/BannerTemplate.vue +0 -51
  241. package/app/components/ads/BottomBanner.vue +0 -45
  242. package/app/components/ads/LeftBanner.vue +0 -50
  243. package/module/problemGenerators.ts +0 -46
@@ -1,214 +1,214 @@
1
- <script lang="ts" setup>
2
- import { headingName } from '@erudit-js/bitran-elements/heading/shared';
3
-
4
- import { stringifyBitranLocation } from '@erudit-js/cog/schema';
5
- import type { TocItem } from '@erudit/shared/bitran/toc';
6
- import { topicLocation } from '@app/scripts/aside/minor/topic';
7
- import { injectAsideData } from '@app/scripts/aside/minor/state';
8
- import type { AsideMinorTopic } from '@shared/aside/minor';
9
-
10
- import TopicTocItem from './TopicTocItem.vue';
11
-
12
- interface RuntimeTocItem extends TocItem {
13
- /**
14
- * * `0` — Not active
15
- * * `1` — Active for `window` events
16
- * * `2` — Active for Intersection Observer and currently in viewport
17
- */
18
- _active: 0 | 1 | 2;
19
- _position: number;
20
- }
21
-
22
- type RuntimeToc = RuntimeTocItem[];
23
-
24
- const topicData = injectAsideData<AsideMinorTopic>();
25
- const phrase = await usePhrases('empty_toc');
26
- const runtimeToc = ref<RuntimeToc>([]);
27
- const tocStateKey = ref(0);
28
-
29
- watch(topicData, setupRuntimeToc);
30
- setupRuntimeToc();
31
-
32
- function setupRuntimeToc(): void {
33
- const _newToc: RuntimeToc = [];
34
-
35
- for (let i = 0; i < topicData.value.toc.length; i++) {
36
- _newToc.push({
37
- ...topicData.value.toc[i]!,
38
- _active: 0,
39
- _position: i,
40
- });
41
- }
42
-
43
- runtimeToc.value = _newToc;
44
- tocStateKey.value++;
45
- }
46
-
47
- //
48
- // Live TOC
49
- //
50
-
51
- let observer: IntersectionObserver | null = null;
52
- let id2TocItemIndex: Record<string, number> = {};
53
-
54
- const windowEvents = ['DOMContentLoaded', 'load', 'resize', 'scroll'] as const;
55
- let headings: RuntimeTocItem[] = [];
56
- let closestAboveHeading: RuntimeTocItem | null = null;
57
-
58
- function disableLiveToc(): void {
59
- // Skip if not in client-side
60
- if (import.meta.server) return;
61
-
62
- // Live TOC heading with `window` events
63
- for (const event of windowEvents) {
64
- window.removeEventListener(event, updateActiveTopHeading);
65
- }
66
-
67
- headings = [];
68
- closestAboveHeading = null;
69
-
70
- // Live TOC with Intersection Observer
71
- id2TocItemIndex = {};
72
-
73
- if (observer) {
74
- observer.disconnect();
75
- observer = null;
76
- }
77
-
78
- // Reset active state
79
- if (runtimeToc.value?.length) {
80
- for (const tocItem of runtimeToc.value) {
81
- tocItem._active = 0;
82
- }
83
- }
84
- }
85
-
86
- function enableLiveToc(): void {
87
- // Skip if not in client-side
88
- if (import.meta.server) return;
89
- if (!runtimeToc.value?.length) return;
90
-
91
- // Live TOC heading with `window` events
92
- headings = runtimeToc.value.filter(
93
- (item) => item.productName === headingName,
94
- );
95
-
96
- for (const event of windowEvents) {
97
- window.addEventListener(event, updateActiveTopHeading);
98
- }
99
-
100
- updateActiveTopHeading();
101
-
102
- // Live TOC with Intersection Observer
103
- observer = new IntersectionObserver(intersectionTrigger);
104
- id2TocItemIndex = {};
105
-
106
- for (const tocItem of runtimeToc.value) {
107
- const id = tocItem.id;
108
- id2TocItemIndex[id] = tocItem._position;
109
- const element = document.getElementById(id);
110
- if (element) {
111
- observer.observe(element);
112
- }
113
- }
114
- }
115
-
116
- function updateActiveTopHeading(): void {
117
- function getBottom(id: string): number {
118
- const defaultBottom = 1;
119
- const element = document.getElementById(id);
120
- return element ? element.getBoundingClientRect().bottom : defaultBottom;
121
- }
122
-
123
- if (closestAboveHeading) {
124
- closestAboveHeading._active = closestAboveHeading._active === 2 ? 2 : 0;
125
- closestAboveHeading = null;
126
- }
127
-
128
- if (!runtimeToc.value?.length || !headings.length) return;
129
-
130
- let topIndex = 0;
131
- let bottomIndex = headings.length;
132
- let targetIndex = 0;
133
-
134
- while (topIndex < bottomIndex) {
135
- const middleIndex = ((topIndex + bottomIndex) / 2) | 0;
136
- const middleHeading = headings[middleIndex]!;
137
- const middleHeadingTop = getBottom(middleHeading.id);
138
-
139
- if (middleHeadingTop <= 0) {
140
- targetIndex = middleIndex;
141
- topIndex = middleIndex + 1;
142
- } else {
143
- bottomIndex = middleIndex;
144
- }
145
- }
146
-
147
- closestAboveHeading = headings[targetIndex]!;
148
- if (closestAboveHeading && closestAboveHeading._active < 2) {
149
- closestAboveHeading._active =
150
- getBottom(closestAboveHeading.id) <= 0 ? 1 : 0;
151
- }
152
- }
153
-
154
- function intersectionTrigger(entries: IntersectionObserverEntry[]): void {
155
- for (const entry of entries) {
156
- const itemIndex = id2TocItemIndex[entry.target.id]!;
157
- const tocItem = runtimeToc.value?.[itemIndex];
158
-
159
- if (tocItem) {
160
- tocItem._active = entry.isIntersecting
161
- ? 2
162
- : tocItem._active === 1
163
- ? 1
164
- : 0;
165
- }
166
- }
167
- }
168
-
169
- onMounted(() => {
170
- watch(
171
- [topicData, topicLocation],
172
- () => {
173
- disableLiveToc();
174
-
175
- if (!topicData.value.location || !topicLocation.value) return;
176
-
177
- enableLiveToc();
178
- },
179
- { immediate: true },
180
- );
181
- });
182
-
183
- onUnmounted(() => {
184
- disableLiveToc();
185
- });
186
- </script>
187
-
188
- <template>
189
- <section :class="$style.topicToc">
190
- <TreeContainer v-if="runtimeToc?.length > 0" :key="tocStateKey">
191
- <TopicTocItem
192
- v-for="tocItem of runtimeToc"
193
- v-memo="[tocStateKey, tocItem.id, tocItem._active]"
194
- :active="!!tocItem._active"
195
- :tocItem
196
- />
197
- </TreeContainer>
198
- <div v-else :class="$style.tocEmpty">{{ phrase.empty_toc }}</div>
199
- </section>
200
- </template>
201
-
202
- <style lang="scss" module>
203
- .topicToc {
204
- flex: 1;
205
- overflow: auto;
206
- @include scroll();
207
- }
208
-
209
- .tocEmpty {
210
- padding: var(--gap);
211
- text-align: center;
212
- color: var(--textMuted);
213
- }
214
- </style>
1
+ <script lang="ts" setup>
2
+ import { headingName } from '@erudit-js/bitran-elements/heading/shared';
3
+
4
+ import { stringifyBitranLocation } from '@erudit-js/cog/schema';
5
+ import type { TocItem } from '@erudit/shared/bitran/toc';
6
+ import { topicLocation } from '@app/scripts/aside/minor/topic';
7
+ import { injectAsideData } from '@app/scripts/aside/minor/state';
8
+ import type { AsideMinorTopic } from '@shared/aside/minor';
9
+
10
+ import TopicTocItem from './TopicTocItem.vue';
11
+
12
+ interface RuntimeTocItem extends TocItem {
13
+ /**
14
+ * * `0` — Not active
15
+ * * `1` — Active for `window` events
16
+ * * `2` — Active for Intersection Observer and currently in viewport
17
+ */
18
+ _active: 0 | 1 | 2;
19
+ _position: number;
20
+ }
21
+
22
+ type RuntimeToc = RuntimeTocItem[];
23
+
24
+ const topicData = injectAsideData<AsideMinorTopic>();
25
+ const phrase = await usePhrases('empty_toc');
26
+ const runtimeToc = ref<RuntimeToc>([]);
27
+ const tocStateKey = ref(0);
28
+
29
+ watch(topicData, setupRuntimeToc);
30
+ setupRuntimeToc();
31
+
32
+ function setupRuntimeToc(): void {
33
+ const _newToc: RuntimeToc = [];
34
+
35
+ for (let i = 0; i < topicData.value.toc.length; i++) {
36
+ _newToc.push({
37
+ ...topicData.value.toc[i]!,
38
+ _active: 0,
39
+ _position: i,
40
+ });
41
+ }
42
+
43
+ runtimeToc.value = _newToc;
44
+ tocStateKey.value++;
45
+ }
46
+
47
+ //
48
+ // Live TOC
49
+ //
50
+
51
+ let observer: IntersectionObserver | null = null;
52
+ let id2TocItemIndex: Record<string, number> = {};
53
+
54
+ const windowEvents = ['DOMContentLoaded', 'load', 'resize', 'scroll'] as const;
55
+ let headings: RuntimeTocItem[] = [];
56
+ let closestAboveHeading: RuntimeTocItem | null = null;
57
+
58
+ function disableLiveToc(): void {
59
+ // Skip if not in client-side
60
+ if (import.meta.server) return;
61
+
62
+ // Live TOC heading with `window` events
63
+ for (const event of windowEvents) {
64
+ window.removeEventListener(event, updateActiveTopHeading);
65
+ }
66
+
67
+ headings = [];
68
+ closestAboveHeading = null;
69
+
70
+ // Live TOC with Intersection Observer
71
+ id2TocItemIndex = {};
72
+
73
+ if (observer) {
74
+ observer.disconnect();
75
+ observer = null;
76
+ }
77
+
78
+ // Reset active state
79
+ if (runtimeToc.value?.length) {
80
+ for (const tocItem of runtimeToc.value) {
81
+ tocItem._active = 0;
82
+ }
83
+ }
84
+ }
85
+
86
+ function enableLiveToc(): void {
87
+ // Skip if not in client-side
88
+ if (import.meta.server) return;
89
+ if (!runtimeToc.value?.length) return;
90
+
91
+ // Live TOC heading with `window` events
92
+ headings = runtimeToc.value.filter(
93
+ (item) => item.productName === headingName,
94
+ );
95
+
96
+ for (const event of windowEvents) {
97
+ window.addEventListener(event, updateActiveTopHeading);
98
+ }
99
+
100
+ updateActiveTopHeading();
101
+
102
+ // Live TOC with Intersection Observer
103
+ observer = new IntersectionObserver(intersectionTrigger);
104
+ id2TocItemIndex = {};
105
+
106
+ for (const tocItem of runtimeToc.value) {
107
+ const id = tocItem.id;
108
+ id2TocItemIndex[id] = tocItem._position;
109
+ const element = document.getElementById(id);
110
+ if (element) {
111
+ observer.observe(element);
112
+ }
113
+ }
114
+ }
115
+
116
+ function updateActiveTopHeading(): void {
117
+ function getBottom(id: string): number {
118
+ const defaultBottom = 1;
119
+ const element = document.getElementById(id);
120
+ return element ? element.getBoundingClientRect().bottom : defaultBottom;
121
+ }
122
+
123
+ if (closestAboveHeading) {
124
+ closestAboveHeading._active = closestAboveHeading._active === 2 ? 2 : 0;
125
+ closestAboveHeading = null;
126
+ }
127
+
128
+ if (!runtimeToc.value?.length || !headings.length) return;
129
+
130
+ let topIndex = 0;
131
+ let bottomIndex = headings.length;
132
+ let targetIndex = 0;
133
+
134
+ while (topIndex < bottomIndex) {
135
+ const middleIndex = ((topIndex + bottomIndex) / 2) | 0;
136
+ const middleHeading = headings[middleIndex]!;
137
+ const middleHeadingTop = getBottom(middleHeading.id);
138
+
139
+ if (middleHeadingTop <= 0) {
140
+ targetIndex = middleIndex;
141
+ topIndex = middleIndex + 1;
142
+ } else {
143
+ bottomIndex = middleIndex;
144
+ }
145
+ }
146
+
147
+ closestAboveHeading = headings[targetIndex]!;
148
+ if (closestAboveHeading && closestAboveHeading._active < 2) {
149
+ closestAboveHeading._active =
150
+ getBottom(closestAboveHeading.id) <= 0 ? 1 : 0;
151
+ }
152
+ }
153
+
154
+ function intersectionTrigger(entries: IntersectionObserverEntry[]): void {
155
+ for (const entry of entries) {
156
+ const itemIndex = id2TocItemIndex[entry.target.id]!;
157
+ const tocItem = runtimeToc.value?.[itemIndex];
158
+
159
+ if (tocItem) {
160
+ tocItem._active = entry.isIntersecting
161
+ ? 2
162
+ : tocItem._active === 1
163
+ ? 1
164
+ : 0;
165
+ }
166
+ }
167
+ }
168
+
169
+ onMounted(() => {
170
+ watch(
171
+ [topicData, topicLocation],
172
+ () => {
173
+ disableLiveToc();
174
+
175
+ if (!topicData.value.location || !topicLocation.value) return;
176
+
177
+ enableLiveToc();
178
+ },
179
+ { immediate: true },
180
+ );
181
+ });
182
+
183
+ onUnmounted(() => {
184
+ disableLiveToc();
185
+ });
186
+ </script>
187
+
188
+ <template>
189
+ <section :class="$style.topicToc">
190
+ <TreeContainer v-if="runtimeToc?.length > 0" :key="tocStateKey">
191
+ <TopicTocItem
192
+ v-for="tocItem of runtimeToc"
193
+ v-memo="[tocStateKey, tocItem.id, tocItem._active]"
194
+ :active="!!tocItem._active"
195
+ :tocItem
196
+ />
197
+ </TreeContainer>
198
+ <div v-else :class="$style.tocEmpty">{{ phrase.empty_toc }}</div>
199
+ </section>
200
+ </template>
201
+
202
+ <style lang="scss" module>
203
+ .topicToc {
204
+ flex: 1;
205
+ overflow: auto;
206
+ @include scroll();
207
+ }
208
+
209
+ .tocEmpty {
210
+ padding: var(--gap);
211
+ text-align: center;
212
+ color: var(--textMuted);
213
+ }
214
+ </style>
@@ -1,32 +1,32 @@
1
- <script lang="ts" setup>
2
- import type { TocItem } from '@erudit/shared/bitran/toc';
3
-
4
- const props = defineProps<{ tocItem: TocItem; active: boolean }>();
5
- const productName = props.tocItem.productName;
6
- const productIcon = await useBitranElementIcon(productName);
7
- const productPhrase = await useBitranElementLanguage(productName);
8
-
9
- const pretty = useFormatText();
10
-
11
- function tocItemClick(event: MouseEvent): void {
12
- navigateTo({
13
- query: {
14
- element: props.tocItem.id,
15
- },
16
- //hash: props.tocItem.id, // Apply page hash as requested in the comment
17
- });
18
- event.preventDefault();
19
- event.stopPropagation();
20
- }
21
- </script>
22
-
23
- <template>
24
- <TreeItem
25
- :label="pretty(tocItem.title || productPhrase('_element_title'))"
26
- :level="tocItem.level"
27
- :link="`?element=${tocItem.id}`"
28
- :svg="productIcon"
29
- :active
30
- @click="tocItemClick"
31
- />
32
- </template>
1
+ <script lang="ts" setup>
2
+ import type { TocItem } from '@erudit/shared/bitran/toc';
3
+
4
+ const props = defineProps<{ tocItem: TocItem; active: boolean }>();
5
+ const productName = props.tocItem.productName;
6
+ const productIcon = await useBitranElementIcon(productName);
7
+ const productPhrase = await useBitranElementLanguage(productName);
8
+
9
+ const pretty = useFormatText();
10
+
11
+ function tocItemClick(event: MouseEvent): void {
12
+ navigateTo({
13
+ query: {
14
+ element: props.tocItem.id,
15
+ },
16
+ //hash: props.tocItem.id, // Apply page hash as requested in the comment
17
+ });
18
+ event.preventDefault();
19
+ event.stopPropagation();
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <TreeItem
25
+ :label="pretty(tocItem.title || productPhrase('_element_title'))"
26
+ :level="tocItem.level"
27
+ :link="`?element=${tocItem.id}`"
28
+ :svg="productIcon"
29
+ :active
30
+ @click="tocItemClick"
31
+ />
32
+ </template>
@@ -1,40 +1,40 @@
1
- <script lang="ts" setup>
2
- const props = defineProps<{ stick?: 'top' | 'bottom' }>();
3
- const style = useCssModule();
4
-
5
- const classes = computed(() => {
6
- const _classes: string[] = [style.asideOverlayPane];
7
-
8
- if (props.stick === 'bottom') _classes.push(style.stickBottom);
9
-
10
- return _classes;
11
- });
12
- </script>
13
-
14
- <template>
15
- <section :class="classes">
16
- <slot></slot>
17
- </section>
18
- </template>
19
-
20
- <style lang="scss" module>
21
- @use '$/def/bp';
22
-
23
- .asideOverlayPane {
24
- position: absolute;
25
- z-index: 10;
26
- top: 0;
27
- left: 0;
28
- width: 100%;
29
- height: 100%;
30
- background: var(--bgAside);
31
-
32
- display: flex;
33
- flex-direction: column;
34
- justify-content: start;
35
-
36
- &.stickBottom {
37
- justify-content: end;
38
- }
39
- }
40
- </style>
1
+ <script lang="ts" setup>
2
+ const props = defineProps<{ stick?: 'top' | 'bottom' }>();
3
+ const style = useCssModule();
4
+
5
+ const classes = computed(() => {
6
+ const _classes: string[] = [style.asideOverlayPane];
7
+
8
+ if (props.stick === 'bottom') _classes.push(style.stickBottom);
9
+
10
+ return _classes;
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <section :class="classes">
16
+ <slot></slot>
17
+ </section>
18
+ </template>
19
+
20
+ <style lang="scss" module>
21
+ @use '$/def/bp';
22
+
23
+ .asideOverlayPane {
24
+ position: absolute;
25
+ z-index: 10;
26
+ top: 0;
27
+ left: 0;
28
+ width: 100%;
29
+ height: 100%;
30
+ background: var(--bgAside);
31
+
32
+ display: flex;
33
+ flex-direction: column;
34
+ justify-content: start;
35
+
36
+ &.stickBottom {
37
+ justify-content: end;
38
+ }
39
+ }
40
+ </style>