erudit 3.0.0-dev.2 → 3.0.0-dev.21

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