erudit 3.0.0-dev.2 → 3.0.0-dev.20

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