erudit 3.0.0-dev.15 → 3.0.0-dev.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/app/app.vue +191 -195
  2. package/app/components/Loading.vue +23 -23
  3. package/app/components/SiteAside.vue +393 -393
  4. package/app/components/SiteMain.vue +32 -32
  5. package/app/components/ads/Ads.vue +35 -0
  6. package/app/components/ads/AdsBannerAside.vue +61 -0
  7. package/app/components/ads/AdsBannerBottom.vue +22 -0
  8. package/app/components/ads/AdsProviderCustom.vue +35 -0
  9. package/app/components/ads/AdsProviderYandex.vue +54 -0
  10. package/app/components/ads/AdsReplacer.vue +73 -0
  11. package/app/components/aside/AsideListItem.vue +74 -74
  12. package/app/components/aside/AsideMajor.vue +56 -56
  13. package/app/components/aside/AsideMinor.vue +73 -71
  14. package/app/components/aside/major/PaneContentScroll.vue +23 -23
  15. package/app/components/aside/major/PaneSwitch.vue +54 -54
  16. package/app/components/aside/major/PaneSwitchButton.vue +63 -63
  17. package/app/components/aside/major/SiteInfo.vue +85 -85
  18. package/app/components/aside/major/panes/Language.vue +79 -79
  19. package/app/components/aside/major/panes/Pages.vue +34 -34
  20. package/app/components/aside/major/panes/Search.vue +11 -11
  21. package/app/components/aside/major/panes/nav/Nav.vue +92 -92
  22. package/app/components/aside/major/panes/nav/NavBook.vue +95 -95
  23. package/app/components/aside/major/panes/nav/NavBookLoading.vue +24 -24
  24. package/app/components/aside/major/panes/nav/NavGlobal.vue +16 -16
  25. package/app/components/aside/major/panes/nav/fnav/FNav.vue +105 -105
  26. package/app/components/aside/major/panes/nav/fnav/FNavBook.vue +32 -32
  27. package/app/components/aside/major/panes/nav/fnav/FNavFlags.vue +40 -40
  28. package/app/components/aside/major/panes/nav/fnav/FNavFolder.vue +60 -60
  29. package/app/components/aside/major/panes/nav/fnav/FNavItem.vue +34 -34
  30. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +80 -80
  31. package/app/components/aside/major/panes/nav/fnav/FNavTopic.vue +24 -24
  32. package/app/components/aside/major/panes/other/ItemContent.vue +29 -29
  33. package/app/components/aside/major/panes/other/ItemGenerator.vue +13 -13
  34. package/app/components/aside/major/panes/other/ItemTheme.vue +54 -54
  35. package/app/components/aside/major/panes/other/Other.vue +16 -16
  36. package/app/components/aside/minor/AsideMinorContributor.vue +5 -5
  37. package/app/components/aside/minor/AsideMinorNews.vue +11 -11
  38. package/app/components/aside/minor/AsideMinorPane.vue +15 -15
  39. package/app/components/aside/minor/AsideMinorTopLink.vue +67 -67
  40. package/app/components/aside/minor/Contribute.vue +145 -145
  41. package/app/components/aside/minor/content/AsideMinorContent.vue +92 -92
  42. package/app/components/aside/minor/topic/AsideMinorTopic.vue +32 -32
  43. package/app/components/aside/minor/topic/TopicContributors.vue +177 -177
  44. package/app/components/aside/minor/topic/TopicNav.vue +49 -49
  45. package/app/components/aside/minor/topic/TopicToc.vue +214 -214
  46. package/app/components/aside/minor/topic/TopicTocItem.vue +32 -32
  47. package/app/components/aside/utils/AsideOverlayPane.vue +40 -40
  48. package/app/components/bitran/BitranContent.vue +91 -91
  49. package/app/components/bitran/RenderWrapper.vue +10 -10
  50. package/app/components/contributor/ContributorAvatar.vue +45 -45
  51. package/app/components/contributor/ContributorListItem.vue +35 -35
  52. package/app/components/main/topic/MainTopic.vue +88 -78
  53. package/app/components/main/topic/TopicPartSwitch.vue +118 -118
  54. package/app/components/main/utils/Breadcrumb.vue +74 -70
  55. package/app/components/main/utils/ContentDecoration.vue +29 -29
  56. package/app/components/main/utils/ContentDescription.vue +19 -19
  57. package/app/components/main/utils/ContentPopover.vue +188 -188
  58. package/app/components/main/utils/ContentPopovers.vue +111 -111
  59. package/app/components/main/utils/ContentReferences.vue +70 -70
  60. package/app/components/main/utils/ContentSection.vue +45 -45
  61. package/app/components/main/utils/ContentTitle.vue +63 -63
  62. package/app/components/main/utils/reference/ReferenceGroup.vue +38 -38
  63. package/app/components/main/utils/reference/ReferenceItem.vue +70 -70
  64. package/app/components/main/utils/reference/ReferenceSource.vue +120 -118
  65. package/app/components/preview/Preview.vue +186 -186
  66. package/app/components/preview/PreviewDisplay.vue +139 -139
  67. package/app/components/preview/PreviewFooterAction.vue +73 -73
  68. package/app/components/preview/PreviewLoading.vue +14 -14
  69. package/app/components/preview/PreviewScreen.vue +141 -141
  70. package/app/components/preview/display/Alert.vue +50 -50
  71. package/app/components/preview/display/Custom.vue +18 -18
  72. package/app/components/preview/display/GenericLink.vue +48 -48
  73. package/app/components/preview/display/PageLink.vue +22 -20
  74. package/app/components/preview/display/Unique.vue +55 -55
  75. package/app/components/transition/Fade.vue +19 -19
  76. package/app/components/tree/TreeContainer.vue +11 -11
  77. package/app/components/tree/TreeItem.vue +89 -89
  78. package/app/composables/bitran.ts +108 -108
  79. package/app/composables/bitranContent.ts +92 -92
  80. package/app/composables/bitranLocation.ts +7 -7
  81. package/app/composables/contentData.ts +38 -38
  82. package/app/composables/contentPage.ts +158 -158
  83. package/app/composables/contentRoute.ts +45 -45
  84. package/app/composables/darkMagic.ts +24 -24
  85. package/app/composables/externalApi.ts +69 -63
  86. package/app/composables/favicon.ts +8 -8
  87. package/app/composables/formatText.ts +99 -99
  88. package/app/composables/majorPane.ts +60 -60
  89. package/app/composables/phrases.ts +65 -65
  90. package/app/composables/theme.ts +29 -29
  91. package/app/composables/url.ts +33 -33
  92. package/app/pages/_test/preview.vue +110 -110
  93. package/app/pages/article/[...articleId].vue +3 -3
  94. package/app/pages/book/[...bookId].vue +47 -47
  95. package/app/pages/group/[...groupId].vue +66 -64
  96. package/app/pages/index.vue +32 -32
  97. package/app/pages/members.vue +6 -6
  98. package/app/pages/practice/[...practice].vue +3 -3
  99. package/app/pages/summary/[...summaryId].vue +3 -3
  100. package/app/plugins/analytics.ts +87 -0
  101. package/app/plugins/prerender.server.ts +22 -22
  102. package/app/scripts/_immediate.js +9 -9
  103. package/app/scripts/aside/index.ts +59 -59
  104. package/app/scripts/aside/major/nav.ts +26 -26
  105. package/app/scripts/aside/minor/state.ts +37 -37
  106. package/app/scripts/aside/minor/topic.ts +3 -3
  107. package/app/scripts/flag.ts +28 -28
  108. package/app/scripts/og.ts +27 -27
  109. package/app/scripts/preview/build.ts +76 -76
  110. package/app/scripts/preview/data/alert.ts +19 -19
  111. package/app/scripts/preview/data/custom.ts +8 -8
  112. package/app/scripts/preview/data/genericLink.ts +24 -24
  113. package/app/scripts/preview/data/pageLink.ts +23 -23
  114. package/app/scripts/preview/data/unique.ts +72 -72
  115. package/app/scripts/preview/data.ts +24 -24
  116. package/app/scripts/preview/display.ts +37 -37
  117. package/app/scripts/preview/footer.ts +9 -9
  118. package/app/scripts/preview/request.ts +51 -51
  119. package/app/scripts/preview/state.ts +63 -63
  120. package/app/styles/_immediate.css +7 -7
  121. package/app/styles/_util.scss +43 -43
  122. package/app/styles/_utils.scss +44 -44
  123. package/app/styles/app.scss +91 -91
  124. package/app/styles/def/_bp.scss +27 -27
  125. package/app/styles/def/_size.scss +7 -7
  126. package/app/styles/def/_z.scss +5 -5
  127. package/app/styles/normalize.scss +49 -49
  128. package/app/styles/partials/_darkMagic.scss +5 -5
  129. package/app/styles/partials/_fnav.scss +15 -15
  130. package/app/styles/partials/_preview.scss +5 -5
  131. package/bin/erudit.mjs +2 -2
  132. package/const.ts +4 -4
  133. package/globalPath.ts +21 -21
  134. package/globals/bitran.ts +1 -1
  135. package/globals/content.ts +27 -27
  136. package/globals/contributor.ts +5 -5
  137. package/globals/erudit.ts +5 -5
  138. package/globals/register.ts +18 -18
  139. package/languages/en.ts +94 -94
  140. package/languages/ru.ts +98 -98
  141. package/module/bitran.ts +66 -66
  142. package/module/config.ts +35 -35
  143. package/module/imports.ts +67 -67
  144. package/module/index.ts +47 -49
  145. package/module/logger.ts +10 -10
  146. package/module/paths.ts +22 -22
  147. package/module/restart.ts +61 -61
  148. package/nuxt.config.ts +131 -134
  149. package/package.json +8 -7
  150. package/server/api/aside/major/nav/bookIds.ts +5 -5
  151. package/server/api/aside/major/nav/bookNav/[...bookId].ts +20 -20
  152. package/server/api/aside/major/nav/global.ts +7 -7
  153. package/server/api/aside/minor/news.ts +7 -7
  154. package/server/api/aside/minor/path.ts +82 -82
  155. package/server/api/bitran/content/[...location].ts +10 -10
  156. package/server/api/bitran/toc/[...location].ts +9 -9
  157. package/server/api/content/data.ts +75 -75
  158. package/server/api/contributor/count.ts +6 -6
  159. package/server/api/fake/content.ts +11 -11
  160. package/server/api/fake/shared/languages.ts +12 -12
  161. package/server/api/language/functions.ts +12 -12
  162. package/server/api/language/phrase/[phraseId].ts +19 -19
  163. package/server/api/language/phraseIds.ts +8 -8
  164. package/server/api/prerender.ts +120 -120
  165. package/server/api/preview/page/[...parts].ts +78 -78
  166. package/server/api/preview/unique/[...location].ts +61 -61
  167. package/server/api/problem/generator/[...path].ts +26 -0
  168. package/server/plugin/bitran/content.ts +190 -190
  169. package/server/plugin/bitran/elements/include.ts +229 -229
  170. package/server/plugin/bitran/location.ts +39 -39
  171. package/server/plugin/bitran/toc.ts +94 -94
  172. package/server/plugin/bitran/transpiler.ts +18 -18
  173. package/server/plugin/build/close.ts +12 -12
  174. package/server/plugin/build/jobs/content/builderArgs.ts +8 -8
  175. package/server/plugin/build/jobs/content/generic.ts +191 -191
  176. package/server/plugin/build/jobs/content/parse.ts +113 -113
  177. package/server/plugin/build/jobs/content/path.ts +6 -6
  178. package/server/plugin/build/jobs/content/type/book.ts +9 -9
  179. package/server/plugin/build/jobs/content/type/group.ts +37 -37
  180. package/server/plugin/build/jobs/content/type/topic.ts +36 -36
  181. package/server/plugin/build/jobs/contributors.ts +66 -66
  182. package/server/plugin/build/jobs/language.ts +36 -36
  183. package/server/plugin/build/jobs/nav.ts +345 -265
  184. package/server/plugin/build/process.ts +32 -32
  185. package/server/plugin/build/rebuild.ts +66 -66
  186. package/server/plugin/build/setup.ts +19 -19
  187. package/server/plugin/content/context.ts +119 -119
  188. package/server/plugin/db/entities/Book.ts +7 -7
  189. package/server/plugin/db/entities/Content.ts +45 -45
  190. package/server/plugin/db/entities/Contribution.ts +10 -10
  191. package/server/plugin/db/entities/Contributor.ts +16 -16
  192. package/server/plugin/db/entities/File.ts +10 -10
  193. package/server/plugin/db/entities/Group.ts +14 -14
  194. package/server/plugin/db/entities/Hash.ts +15 -15
  195. package/server/plugin/db/entities/Topic.ts +20 -20
  196. package/server/plugin/db/entities/Unique.ts +21 -21
  197. package/server/plugin/db/reset.ts +12 -12
  198. package/server/plugin/db/setup.ts +49 -49
  199. package/server/plugin/global.ts +16 -16
  200. package/server/plugin/importer.ts +16 -16
  201. package/server/plugin/index.ts +9 -9
  202. package/server/plugin/logger.ts +23 -23
  203. package/server/plugin/nav/node.ts +27 -27
  204. package/server/plugin/nav/utils.ts +175 -175
  205. package/server/plugin/repository/book.ts +21 -21
  206. package/server/plugin/repository/content.ts +240 -240
  207. package/server/plugin/repository/contentId.ts +40 -40
  208. package/server/plugin/repository/contributor.ts +8 -8
  209. package/server/plugin/repository/file.ts +10 -10
  210. package/server/plugin/repository/frontNav.ts +145 -145
  211. package/server/plugin/repository/topic.ts +35 -35
  212. package/server/tsconfig.json +9 -9
  213. package/shared/aside/minor.ts +51 -51
  214. package/shared/asset.ts +22 -22
  215. package/shared/bitran/contentId.ts +56 -56
  216. package/shared/bitran/stringContent.ts +6 -6
  217. package/shared/bitran/toc.ts +8 -8
  218. package/shared/content/bookId.ts +12 -12
  219. package/shared/content/context.ts +9 -9
  220. package/shared/content/data/base.ts +32 -32
  221. package/shared/content/data/index.ts +5 -5
  222. package/shared/content/data/type/book.ts +5 -5
  223. package/shared/content/data/type/group.ts +6 -6
  224. package/shared/content/data/type/topic.ts +11 -11
  225. package/shared/content/previousNext.ts +9 -9
  226. package/shared/contributor.ts +5 -5
  227. package/shared/frontNav.ts +41 -41
  228. package/shared/icons.ts +38 -38
  229. package/shared/image.ts +5 -5
  230. package/shared/link.ts +28 -28
  231. package/shared/popover.ts +8 -8
  232. package/shared/types/language.ts +74 -74
  233. package/shared/utils/objectsEqual.ts +4 -4
  234. package/shared/utils/stringColor.ts +9 -9
  235. package/test/contentId.test.ts +91 -91
  236. package/tsconfig.json +8 -8
  237. package/utils/contentPath.ts +67 -67
  238. package/utils/slash.ts +11 -11
  239. package/utils/stress.ts +9 -9
  240. package/app/components/ads/BannerTemplate.vue +0 -51
  241. package/app/components/ads/BottomBanner.vue +0 -45
  242. package/app/components/ads/LeftBanner.vue +0 -50
  243. package/module/problemGenerators.ts +0 -46
@@ -1,91 +1,91 @@
1
- import {
2
- toAbsoluteContentPath,
3
- resolveContentPath,
4
- } from '@erudit/shared/bitran/contentId';
5
-
6
- describe('resolveContentPath', () => {
7
- it('should handle empty paths', () => {
8
- expect(resolveContentPath('')).toBe('');
9
- });
10
-
11
- it('should not change paths without special symbols', () => {
12
- expect(resolveContentPath('foo/bar/baz')).toBe('foo/bar/baz');
13
- });
14
-
15
- it('should skip empty path parts', () => {
16
- expect(resolveContentPath('/foo///bar/baz/')).toBe('foo/bar/baz');
17
- });
18
-
19
- it('should handle ".." parts', () => {
20
- const testCases = [
21
- ['foo/bar/../baz', 'foo/baz'],
22
- ['../../../foo/bar/baz', 'foo/bar/baz'],
23
- ['foo/../../../bar/baz', 'bar/baz'],
24
- ['../../..', ''],
25
- ];
26
-
27
- for (const [input, expected] of testCases) {
28
- expect(resolveContentPath(input!)).toBe(expected);
29
- }
30
- });
31
-
32
- it('should handle "." parts', () => {
33
- const testCases = [
34
- ['.', ''],
35
- ['./foo/bar/baz', 'foo/bar/baz'],
36
- ['./foo/./bar', 'foo/bar'],
37
- ['foo/./././bar', 'foo/bar'],
38
- ];
39
-
40
- for (const [input, expected] of testCases) {
41
- expect(resolveContentPath(input!)).toBe(expected);
42
- }
43
- });
44
-
45
- it('should handle paths with both "." and ".." parts', () => {
46
- const testCases = [
47
- ['./../.', ''],
48
- ['foo/./../bar', 'foo/bar'],
49
- ];
50
-
51
- for (const [input, expected] of testCases) {
52
- expect(resolveContentPath(input!)).toBe(expected);
53
- }
54
- });
55
- });
56
-
57
- describe('toAbsoluteContentPath', () => {
58
- const bookIds = ['combinatorics', 'group/book'];
59
-
60
- it('should not use context on absolute paths', () => {
61
- expect(
62
- toAbsoluteContentPath('/foo/bar/../baz', 'qux/vaz', bookIds),
63
- ).toBe('foo/baz');
64
- });
65
-
66
- it('should fallback to absolute path if not inside book', () => {
67
- expect(
68
- toAbsoluteContentPath('~/foo/bar', 'unknown-book', bookIds),
69
- ).toBe('foo/bar');
70
- });
71
-
72
- it('should correctly handle book-relative paths', () => {
73
- expect(toAbsoluteContentPath('~/foo/bar', 'group/book', bookIds)).toBe(
74
- 'group/book/foo/bar',
75
- );
76
- });
77
-
78
- it('should by default append context before given content id', () => {
79
- const testCases = [
80
- ['baz/qux', 'foo/bar', 'foo/bar/baz/qux'],
81
- ['./baz/qux', 'foo/bar', 'foo/bar/baz/qux'],
82
- ['../baz/qux', 'foo/bar', 'foo/baz/qux'],
83
- ];
84
-
85
- for (const [contentId, context, expected] of testCases) {
86
- expect(toAbsoluteContentPath(contentId!, context!, bookIds)).toBe(
87
- expected,
88
- );
89
- }
90
- });
91
- });
1
+ import {
2
+ toAbsoluteContentPath,
3
+ resolveContentPath,
4
+ } from '@erudit/shared/bitran/contentId';
5
+
6
+ describe('resolveContentPath', () => {
7
+ it('should handle empty paths', () => {
8
+ expect(resolveContentPath('')).toBe('');
9
+ });
10
+
11
+ it('should not change paths without special symbols', () => {
12
+ expect(resolveContentPath('foo/bar/baz')).toBe('foo/bar/baz');
13
+ });
14
+
15
+ it('should skip empty path parts', () => {
16
+ expect(resolveContentPath('/foo///bar/baz/')).toBe('foo/bar/baz');
17
+ });
18
+
19
+ it('should handle ".." parts', () => {
20
+ const testCases = [
21
+ ['foo/bar/../baz', 'foo/baz'],
22
+ ['../../../foo/bar/baz', 'foo/bar/baz'],
23
+ ['foo/../../../bar/baz', 'bar/baz'],
24
+ ['../../..', ''],
25
+ ];
26
+
27
+ for (const [input, expected] of testCases) {
28
+ expect(resolveContentPath(input!)).toBe(expected);
29
+ }
30
+ });
31
+
32
+ it('should handle "." parts', () => {
33
+ const testCases = [
34
+ ['.', ''],
35
+ ['./foo/bar/baz', 'foo/bar/baz'],
36
+ ['./foo/./bar', 'foo/bar'],
37
+ ['foo/./././bar', 'foo/bar'],
38
+ ];
39
+
40
+ for (const [input, expected] of testCases) {
41
+ expect(resolveContentPath(input!)).toBe(expected);
42
+ }
43
+ });
44
+
45
+ it('should handle paths with both "." and ".." parts', () => {
46
+ const testCases = [
47
+ ['./../.', ''],
48
+ ['foo/./../bar', 'foo/bar'],
49
+ ];
50
+
51
+ for (const [input, expected] of testCases) {
52
+ expect(resolveContentPath(input!)).toBe(expected);
53
+ }
54
+ });
55
+ });
56
+
57
+ describe('toAbsoluteContentPath', () => {
58
+ const bookIds = ['combinatorics', 'group/book'];
59
+
60
+ it('should not use context on absolute paths', () => {
61
+ expect(
62
+ toAbsoluteContentPath('/foo/bar/../baz', 'qux/vaz', bookIds),
63
+ ).toBe('foo/baz');
64
+ });
65
+
66
+ it('should fallback to absolute path if not inside book', () => {
67
+ expect(
68
+ toAbsoluteContentPath('~/foo/bar', 'unknown-book', bookIds),
69
+ ).toBe('foo/bar');
70
+ });
71
+
72
+ it('should correctly handle book-relative paths', () => {
73
+ expect(toAbsoluteContentPath('~/foo/bar', 'group/book', bookIds)).toBe(
74
+ 'group/book/foo/bar',
75
+ );
76
+ });
77
+
78
+ it('should by default append context before given content id', () => {
79
+ const testCases = [
80
+ ['baz/qux', 'foo/bar', 'foo/bar/baz/qux'],
81
+ ['./baz/qux', 'foo/bar', 'foo/bar/baz/qux'],
82
+ ['../baz/qux', 'foo/bar', 'foo/baz/qux'],
83
+ ];
84
+
85
+ for (const [contentId, context, expected] of testCases) {
86
+ expect(toAbsoluteContentPath(contentId!, context!, bookIds)).toBe(
87
+ expected,
88
+ );
89
+ }
90
+ });
91
+ });
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
- {
2
- "extends": "../../playground/.erudit/nuxt/.nuxt/tsconfig.json",
3
- "compilerOptions": {
4
- "experimentalDecorators": true,
5
- "emitDecoratorMetadata": true,
6
- "types": ["@types/jest"]
7
- }
8
- }
1
+ {
2
+ "extends": "../../playground/.erudit/nuxt/.nuxt/tsconfig.json",
3
+ "compilerOptions": {
4
+ "experimentalDecorators": true,
5
+ "emitDecoratorMetadata": true,
6
+ "types": ["@types/jest"]
7
+ }
8
+ }
@@ -1,67 +1,67 @@
1
- import { readdirSync, statSync } from 'fs';
2
-
3
- // A directory is considered a "content directory" if it contains book, group, or topic with a file having a .js or .ts extension.
4
- // When a directory qualifies as a content directory, its name is simplified by removing any leading digits and a '+' or '-' (e.g., "29+foo" becomes "foo").
5
-
6
- const contentFileRegex = /^(book|group|topic)\.(js|ts)$/i;
7
-
8
- function normalizePath(path: string): string {
9
- return path.replace(/\\/g, '/');
10
- }
11
-
12
- export function isContentDirectory(directory: string): boolean {
13
- if (!statSync(directory).isDirectory()) {
14
- return false;
15
- }
16
-
17
- return readdirSync(directory).some((file) => {
18
- return contentFileRegex.test(file);
19
- });
20
- }
21
-
22
- export function getContentPath(path: string, cwd?: string): string {
23
- path = normalizePath(path);
24
- cwd = cwd ? normalizePath(cwd) : undefined;
25
-
26
- const unresolvedParts = path.split('/').filter(Boolean);
27
- const resolvedParts: string[] = [];
28
-
29
- let currentPath = cwd || '';
30
- while (unresolvedParts.length) {
31
- const currentPart = unresolvedParts.shift()!;
32
- currentPath += '/' + currentPart;
33
- if (isContentDirectory(currentPath)) {
34
- resolvedParts.push(currentPart.replace(/^\d+(?:\+|-)/, ''));
35
- } else {
36
- resolvedParts.push(currentPart);
37
- }
38
- }
39
-
40
- return resolvedParts.join('/');
41
- }
42
-
43
- export function scanContentPaths(directory: string): Record<string, string> {
44
- directory = normalizePath(directory);
45
-
46
- const map: Record<string, string> = {};
47
-
48
- function traverse(currentPath: string) {
49
- const files = readdirSync(currentPath);
50
-
51
- for (const file of files) {
52
- const fullPath = currentPath + '/' + file;
53
- const relativePath = fullPath.slice(directory.length + 1);
54
- const stat = statSync(fullPath);
55
-
56
- if (stat.isDirectory()) {
57
- traverse(fullPath);
58
- } else {
59
- map[getContentPath(relativePath, directory)] = relativePath;
60
- }
61
- }
62
- }
63
-
64
- traverse(directory);
65
-
66
- return map;
67
- }
1
+ import { readdirSync, statSync } from 'fs';
2
+
3
+ // A directory is considered a "content directory" if it contains book, group, or topic with a file having a .js or .ts extension.
4
+ // When a directory qualifies as a content directory, its name is simplified by removing any leading digits and a '+' or '-' (e.g., "29+foo" becomes "foo").
5
+
6
+ const contentFileRegex = /^(book|group|topic)\.(js|ts)$/i;
7
+
8
+ function normalizePath(path: string): string {
9
+ return path.replace(/\\/g, '/');
10
+ }
11
+
12
+ export function isContentDirectory(directory: string): boolean {
13
+ if (!statSync(directory).isDirectory()) {
14
+ return false;
15
+ }
16
+
17
+ return readdirSync(directory).some((file) => {
18
+ return contentFileRegex.test(file);
19
+ });
20
+ }
21
+
22
+ export function getContentPath(path: string, cwd?: string): string {
23
+ path = normalizePath(path);
24
+ cwd = cwd ? normalizePath(cwd) : undefined;
25
+
26
+ const unresolvedParts = path.split('/').filter(Boolean);
27
+ const resolvedParts: string[] = [];
28
+
29
+ let currentPath = cwd || '';
30
+ while (unresolvedParts.length) {
31
+ const currentPart = unresolvedParts.shift()!;
32
+ currentPath += '/' + currentPart;
33
+ if (isContentDirectory(currentPath)) {
34
+ resolvedParts.push(currentPart.replace(/^\d+(?:\+|-)/, ''));
35
+ } else {
36
+ resolvedParts.push(currentPart);
37
+ }
38
+ }
39
+
40
+ return resolvedParts.join('/');
41
+ }
42
+
43
+ export function scanContentPaths(directory: string): Record<string, string> {
44
+ directory = normalizePath(directory);
45
+
46
+ const map: Record<string, string> = {};
47
+
48
+ function traverse(currentPath: string) {
49
+ const files = readdirSync(currentPath);
50
+
51
+ for (const file of files) {
52
+ const fullPath = currentPath + '/' + file;
53
+ const relativePath = fullPath.slice(directory.length + 1);
54
+ const stat = statSync(fullPath);
55
+
56
+ if (stat.isDirectory()) {
57
+ traverse(fullPath);
58
+ } else {
59
+ map[getContentPath(relativePath, directory)] = relativePath;
60
+ }
61
+ }
62
+ }
63
+
64
+ traverse(directory);
65
+
66
+ return map;
67
+ }
package/utils/slash.ts CHANGED
@@ -1,11 +1,11 @@
1
- export function trailingSlash(path: string, add: boolean): string {
2
- if (path === '/') {
3
- return '/';
4
- }
5
-
6
- if (add) {
7
- return path.endsWith('/') ? path : `${path}/`;
8
- }
9
-
10
- return path.endsWith('/') ? path.slice(0, -1) : path;
11
- }
1
+ export function trailingSlash(path: string, add: boolean): string {
2
+ if (path === '/') {
3
+ return '/';
4
+ }
5
+
6
+ if (add) {
7
+ return path.endsWith('/') ? path : `${path}/`;
8
+ }
9
+
10
+ return path.endsWith('/') ? path.slice(0, -1) : path;
11
+ }
package/utils/stress.ts CHANGED
@@ -1,9 +1,9 @@
1
- import chalk, { type ChalkInstance } from 'chalk';
2
-
3
- export function stress(message: string, contentChalk?: ChalkInstance) {
4
- return (
5
- chalk.dim(`'`) +
6
- (contentChalk ? contentChalk(message) : chalk.cyanBright(message)) +
7
- chalk.dim(`'`)
8
- );
9
- }
1
+ import chalk, { type ChalkInstance } from 'chalk';
2
+
3
+ export function stress(message: string, contentChalk?: ChalkInstance) {
4
+ return (
5
+ chalk.dim(`'`) +
6
+ (contentChalk ? contentChalk(message) : chalk.cyanBright(message)) +
7
+ chalk.dim(`'`)
8
+ );
9
+ }
@@ -1,51 +0,0 @@
1
- <script lang="ts" setup>
2
- const props = defineProps<{
3
- bannerId: string;
4
- }>();
5
-
6
- const { getElementId, registerBanner } = useDarkMagic();
7
- const phrase = await usePhrases('ads_replacer', '_language_code');
8
-
9
- onMounted(() => {
10
- registerBanner(props.bannerId);
11
- });
12
- </script>
13
-
14
- <template>
15
- <section :class="$style.darkMagicContainer">
16
- <div :id="getElementId(bannerId)">
17
- <div :class="$style.darkMagicReplacer">
18
- <MyIcon name="pirate" />
19
- <div v-html="phrase.ads_replacer"></div>
20
- </div>
21
- </div>
22
- </section>
23
- </template>
24
-
25
- <style lang="scss" module>
26
- .darkMagicContainer {
27
- --maxHeight: 300px;
28
-
29
- > div {
30
- width: 100%;
31
- max-height: var(--maxHeight);
32
- }
33
- }
34
-
35
- .darkMagicReplacer {
36
- max-height: var(--maxHeight);
37
- height: var(--maxHeight);
38
- width: 100%;
39
- background: var(--bgAccent);
40
- box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1) inset;
41
- color: var(--textMuted);
42
-
43
- @include dark {
44
- box-shadow: 0px 0px 3px 1px rgba(white, 0.025) inset;
45
- }
46
-
47
- strong {
48
- color: inherit;
49
- }
50
- }
51
- </style>
@@ -1,45 +0,0 @@
1
- <script lang="ts" setup>
2
- import eruditConfig from '#erudit/config';
3
- </script>
4
-
5
- <template>
6
- <AdsBannerTemplate
7
- :bannerId="eruditConfig.ads!.bottomBlockId!"
8
- :class="$style.darkMagicBottom"
9
- />
10
- </template>
11
-
12
- <style lang="scss" module>
13
- @use '$/def/bp';
14
- @use '$/partials/darkMagic';
15
-
16
- .darkMagicBottom {
17
- --maxHeight: 300px;
18
-
19
- padding: var(--_pMainY) var(--_pMainX);
20
- padding-top: var(--gapBig);
21
-
22
- @include darkMagic.replacer {
23
- display: flex;
24
- justify-content: center;
25
- align-items: center;
26
- gap: var(--gapBig);
27
- font-size: 22px;
28
-
29
- [my-icon] {
30
- font-size: 60px;
31
- }
32
-
33
- @include bp.below('mobile') {
34
- flex-direction: column;
35
- gap: var(--gap);
36
- font-size: 16px;
37
- text-align: center;
38
-
39
- [my-icon] {
40
- font-size: 68px;
41
- }
42
- }
43
- }
44
- }
45
- </style>
@@ -1,50 +0,0 @@
1
- <script lang="ts" setup>
2
- import eruditConfig from '#erudit/config';
3
- </script>
4
-
5
- <template>
6
- <AdsBannerTemplate
7
- :bannerId="eruditConfig.ads!.leftBlockId!"
8
- :class="$style.darkMagicLeft"
9
- />
10
- </template>
11
-
12
- <style lang="scss" module>
13
- @use '$/partials/darkMagic';
14
-
15
- .darkMagicLeft {
16
- --maxHeight: 300px;
17
-
18
- padding: var(--gap);
19
- border-top: 1px solid var(--border);
20
-
21
- @media (max-height: 1080px) {
22
- --maxHeight: 200px;
23
- }
24
-
25
- @media (max-height: 700px) {
26
- --maxHeight: 100px;
27
- [my-icon] {
28
- display: none;
29
- }
30
- }
31
-
32
- @media (max-height: 350px) {
33
- display: none;
34
- }
35
-
36
- @include darkMagic.replacer {
37
- display: flex;
38
- flex-direction: column;
39
- justify-content: center;
40
- align-items: center;
41
- gap: var(--gap);
42
- text-align: center;
43
- font-size: 0.95em;
44
-
45
- [my-icon] {
46
- font-size: 46px;
47
- }
48
- }
49
- }
50
- </style>
@@ -1,46 +0,0 @@
1
- import { addTemplate } from 'nuxt/kit';
2
- import type { Nuxt } from 'nuxt/schema';
3
- import { globSync } from 'glob';
4
-
5
- import { eruditEndNuxtPath, projectPath } from '@erudit/globalPath';
6
- import { getContentPath } from '@erudit/utils/contentPath';
7
-
8
- export async function setupProblemGenerators(_nuxt: Nuxt) {
9
- const templateName = '#erudit/problemGenerators.ts';
10
-
11
- addTemplate({
12
- filename: templateName,
13
- write: true,
14
- async getContents() {
15
- const generatorFiles = findGenerators();
16
-
17
- return `
18
- export const loaders = {
19
- ${generatorFiles
20
- .map((file) => {
21
- const filePath = projectPath('content/' + file);
22
- const contentPath = getContentPath(
23
- file,
24
- projectPath('content'),
25
- ).replace(/\.gen\.(js|ts)$/, '');
26
- return ` '${contentPath}': async () => (await import('${filePath}')).default,`;
27
- })
28
- .join('\n')}
29
- }
30
- `.trim();
31
- },
32
- });
33
-
34
- const alias = (_nuxt.options.alias ||= {});
35
- alias['#erudit/problemGenerators'] = eruditEndNuxtPath(
36
- `.nuxt/${templateName}`,
37
- );
38
- }
39
-
40
- function findGenerators() {
41
- const generatorFiles = globSync('**/*.gen.{js,ts}', {
42
- cwd: projectPath('content'),
43
- posix: true,
44
- });
45
- return generatorFiles;
46
- }