erudit 3.0.0-dev.1 → 3.0.0-dev.10

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