erudit 4.0.0-dev.1 → 4.0.0-dev.2
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.
- package/app/app.vue +1 -2
- package/app/components/FancyBold.vue +0 -1
- package/app/components/FancyCard.vue +1 -2
- package/app/components/ads/AdsBannerAside.vue +1 -2
- package/app/components/ads/AdsReplacer.vue +2 -2
- package/app/components/aside/AsideListItem.vue +1 -1
- package/app/components/aside/AsideSwitch.vue +18 -8
- package/app/components/aside/major/PaneSwitcher.vue +1 -1
- package/app/components/aside/minor/AsideMinorPlainHeader.vue +2 -3
- package/app/components/aside/minor/content/AsideMinorContentTopic.vue +1 -4
- package/app/components/aside/minor/content/ButtonPaneContributions.vue +2 -4
- package/app/components/aside/minor/content/ButtonPaneImprove.vue +2 -3
- package/app/components/aside/minor/content/Contribution.vue +1 -1
- package/app/components/aside/minor/content/TocItem.vue +35 -21
- package/app/components/aside/minor/news/AsideMinorNews.vue +1 -2
- package/app/components/aside/minor/news/NewsItem.vue +2 -2
- package/app/components/aside/minor/news/elements/Ref.vue +1 -1
- package/app/components/main/MainContentChild.vue +2 -3
- package/app/components/main/MainDescription.vue +1 -1
- package/app/components/main/MainQuickLink.vue +20 -5
- package/app/components/main/MainQuickLinks.vue +1 -3
- package/app/components/main/MainQuote.vue +3 -6
- package/app/components/main/MainSection.vue +6 -21
- package/app/components/main/MainTitle.vue +1 -2
- package/app/components/main/MainTopicPartSwitch.vue +1 -1
- package/app/components/main/connections/Deps.vue +1 -1
- package/app/components/main/connections/Externals.vue +92 -34
- package/app/components/main/connections/MainConnections.vue +61 -8
- package/app/components/main/connections/MainConnectionsButton.vue +3 -2
- package/app/components/main/connections/ScrollPane.vue +2 -3
- package/app/components/main/contentStats/Item.vue +1 -2
- package/app/components/main/contentStats/MainContentStats.vue +6 -3
- package/app/components/preview/Preview.vue +1 -2
- package/app/components/preview/PreviewScreen.vue +1 -2
- package/app/components/site/SiteAside.vue +2 -4
- package/app/components/site/SiteMain.vue +1 -4
- package/app/components/tree/TreeItem.vue +1 -1
- package/app/composables/og.ts +19 -2
- package/app/pages/contributor/[contributorId].vue +1 -2
- package/app/pages/index.vue +1 -4
- package/app/styles/main.css +0 -1
- package/package.json +4 -4
- package/server/erudit/cameos/build.ts +77 -27
- package/server/erudit/content/global/build.ts +27 -1
- package/server/erudit/content/nav/build.ts +36 -4
- package/server/erudit/content/repository/elementSnippets.ts +51 -11
- package/server/erudit/content/repository/externals.ts +38 -9
- package/server/erudit/content/resolve/index.ts +172 -21
- package/server/erudit/content/resolve/topic.ts +93 -32
- package/server/erudit/content/resolve/utils/insertContentResolved.ts +48 -9
- package/server/erudit/content/search.ts +31 -3
- package/server/erudit/contributors/build.ts +106 -51
- package/server/erudit/db/repository/pushFile.ts +7 -4
- package/server/erudit/db/schema/content.ts +2 -2
- package/server/erudit/db/schema/contentSnippets.ts +3 -4
- package/server/erudit/language/list/en.ts +2 -0
- package/server/erudit/language/list/ru.ts +2 -0
- package/server/erudit/news/build.ts +85 -48
- package/server/erudit/sponsors/build.ts +77 -26
- package/shared/types/contentConnections.ts +2 -2
- package/shared/types/elementSnippet.ts +9 -3
- package/shared/types/language.ts +2 -0
|
@@ -1,19 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { eq, like } from 'drizzle-orm';
|
|
2
|
+
import type { ContentExternals } from '@erudit-js/core/content/externals';
|
|
3
3
|
|
|
4
4
|
export async function getContentExternals(
|
|
5
5
|
fullId: string,
|
|
6
|
-
): Promise<
|
|
7
|
-
const externals:
|
|
6
|
+
): Promise<ContentExternals> {
|
|
7
|
+
const externals: ContentExternals = [];
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
columns: {
|
|
9
|
+
const dbOwnContentItem = await ERUDIT.db.query.content.findFirst({
|
|
10
|
+
columns: { externals: true },
|
|
11
11
|
where: eq(ERUDIT.db.schema.content.fullId, fullId),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (dbOwnContentItem?.externals) {
|
|
15
|
+
externals.push({
|
|
16
|
+
type: 'own',
|
|
17
|
+
items: dbOwnContentItem.externals,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const parts = fullId.split('/');
|
|
21
|
+
const dbParentContentItems = [];
|
|
22
|
+
|
|
23
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
24
|
+
const parentId = parts.slice(0, i).join('/');
|
|
25
|
+
const dbParentContentItem = await ERUDIT.db.query.content.findFirst({
|
|
26
|
+
columns: {
|
|
27
|
+
fullId: true,
|
|
28
|
+
externals: true,
|
|
29
|
+
title: true,
|
|
30
|
+
},
|
|
31
|
+
where: eq(ERUDIT.db.schema.content.fullId, parentId),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (dbParentContentItem) {
|
|
35
|
+
dbParentContentItems.push(dbParentContentItem);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const dbParentContentItem of dbParentContentItems) {
|
|
40
|
+
if (dbParentContentItem?.externals) {
|
|
41
|
+
externals.push({
|
|
42
|
+
type: 'parent',
|
|
43
|
+
title: dbParentContentItem.title,
|
|
44
|
+
items: dbParentContentItem.externals,
|
|
45
|
+
});
|
|
17
46
|
}
|
|
18
47
|
}
|
|
19
48
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { inArray, or } from 'drizzle-orm';
|
|
2
3
|
import { contentPathToId } from '@erudit-js/core/content/path';
|
|
3
4
|
|
|
@@ -8,35 +9,38 @@ import { resolveTopic } from './topic';
|
|
|
8
9
|
|
|
9
10
|
let initialResolve = true;
|
|
10
11
|
|
|
12
|
+
const contentRoot = () => `${ERUDIT.config.paths.project}/content`;
|
|
13
|
+
|
|
11
14
|
export async function resolveContent() {
|
|
12
15
|
ERUDIT.log.debug.start('Resolving content...');
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
toResolveContentIds.add(node.fullId);
|
|
31
|
-
}, navNode);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
17
|
+
const isInitial = initialResolve;
|
|
18
|
+
initialResolve = false;
|
|
19
|
+
|
|
20
|
+
const toResolveContentIds = collectContentIdsToResolve(isInitial);
|
|
21
|
+
|
|
22
|
+
if (!toResolveContentIds.size) {
|
|
23
|
+
ERUDIT.log.info(
|
|
24
|
+
isInitial
|
|
25
|
+
? 'Skipping content — no content found.'
|
|
26
|
+
: 'Skipping content — nothing changed.',
|
|
27
|
+
);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!isInitial) {
|
|
32
|
+
ERUDIT.log.info(renderChangedContentTree(toResolveContentIds));
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
await clearOldContentData(Array.from(toResolveContentIds));
|
|
37
36
|
|
|
38
37
|
for (const contentId of toResolveContentIds) {
|
|
39
|
-
const navNode = ERUDIT.contentNav.
|
|
38
|
+
const navNode = ERUDIT.contentNav.getNode(contentId);
|
|
39
|
+
|
|
40
|
+
if (!navNode) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
switch (navNode.type) {
|
|
41
45
|
case 'book':
|
|
42
46
|
await resolveBook(navNode);
|
|
@@ -52,6 +56,153 @@ export async function resolveContent() {
|
|
|
52
56
|
break;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
59
|
+
|
|
60
|
+
ERUDIT.log.success(
|
|
61
|
+
isInitial
|
|
62
|
+
? `Content resolved! (${ERUDIT.log.stress(toResolveContentIds.size)})`
|
|
63
|
+
: `Content updated! (${ERUDIT.log.stress(
|
|
64
|
+
toResolveContentIds.size,
|
|
65
|
+
)})`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function collectContentIdsToResolve(isInitial: boolean): Set<string> {
|
|
70
|
+
if (isInitial) {
|
|
71
|
+
return new Set(ERUDIT.contentNav.id2Node.keys());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!hasContentChanges()) {
|
|
75
|
+
return new Set();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ids = new Set<string>();
|
|
79
|
+
const changedFiles = ERUDIT.changedFiles || new Set<string>();
|
|
80
|
+
|
|
81
|
+
for (const changedFile of changedFiles.values()) {
|
|
82
|
+
if (!changedFile.startsWith(`${contentRoot()}/`)) continue;
|
|
83
|
+
|
|
84
|
+
const contentId =
|
|
85
|
+
contentPathToId(changedFile, ERUDIT.config.paths.project, 'full') ||
|
|
86
|
+
deriveContentIdFromPath(changedFile);
|
|
87
|
+
|
|
88
|
+
if (!contentId) continue;
|
|
89
|
+
|
|
90
|
+
const navNode = ERUDIT.contentNav.getNode(contentId);
|
|
91
|
+
|
|
92
|
+
if (navNode) {
|
|
93
|
+
ERUDIT.contentNav.walkSync((node) => {
|
|
94
|
+
ids.add(node.fullId);
|
|
95
|
+
}, navNode);
|
|
96
|
+
|
|
97
|
+
ERUDIT.contentNav.walkUpSync((node) => {
|
|
98
|
+
ids.add(node.fullId);
|
|
99
|
+
}, navNode);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
addAncestorIds(ids, contentId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return ids;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function hasContentChanges() {
|
|
110
|
+
for (const file of (ERUDIT.changedFiles || new Set<string>()).values()) {
|
|
111
|
+
if (file.startsWith(`${contentRoot()}/`)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function addAncestorIds(target: Set<string>, contentId: string) {
|
|
120
|
+
const parts = contentId.split('/').filter(Boolean);
|
|
121
|
+
|
|
122
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
123
|
+
target.add(parts.slice(0, i).join('/'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function deriveContentIdFromPath(path: string): string | undefined {
|
|
128
|
+
if (!path.startsWith(`${contentRoot()}/`)) return;
|
|
129
|
+
|
|
130
|
+
const rel = path.slice(contentRoot().length + 1);
|
|
131
|
+
const segments = rel.split('/');
|
|
132
|
+
|
|
133
|
+
// Drop filename if present
|
|
134
|
+
if (segments.length && segments[segments.length - 1].includes('.')) {
|
|
135
|
+
segments.pop();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!segments.length) return;
|
|
139
|
+
|
|
140
|
+
const idParts: string[] = [];
|
|
141
|
+
|
|
142
|
+
for (const seg of segments) {
|
|
143
|
+
const match = seg.match(/^(\d+)[+-](.+)$/);
|
|
144
|
+
if (!match) return;
|
|
145
|
+
const [, , idPart] = match;
|
|
146
|
+
if (!idPart) return;
|
|
147
|
+
idParts.push(idPart);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return idParts.join('/');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderChangedContentTree(ids: Set<string>): string {
|
|
154
|
+
const sorted = Array.from(ids).sort((a, b) => {
|
|
155
|
+
const al = a.split('/').length;
|
|
156
|
+
const bl = b.split('/').length;
|
|
157
|
+
if (al !== bl) return al - bl;
|
|
158
|
+
return a.localeCompare(b);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
type Node = { name: string; children: Map<string, Node> };
|
|
162
|
+
const root: Node = { name: '', children: new Map() };
|
|
163
|
+
|
|
164
|
+
const ensureNode = (parent: Node, part: string): Node => {
|
|
165
|
+
let next = parent.children.get(part);
|
|
166
|
+
if (!next) {
|
|
167
|
+
next = { name: part, children: new Map() };
|
|
168
|
+
parent.children.set(part, next);
|
|
169
|
+
}
|
|
170
|
+
return next;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
for (const id of sorted) {
|
|
174
|
+
let cursor = root;
|
|
175
|
+
for (const part of id.split('/')) {
|
|
176
|
+
cursor = ensureNode(cursor, part);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const lines: string[] = [];
|
|
181
|
+
|
|
182
|
+
const walkTree = (node: Node, depth: number) => {
|
|
183
|
+
if (node.name) {
|
|
184
|
+
const indent = ' '.repeat(Math.max(0, depth - 1));
|
|
185
|
+
lines.push(`${indent}- ${chalk.cyan(node.name)}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const children = Array.from(node.children.values()).sort((a, b) =>
|
|
189
|
+
a.name.localeCompare(b.name),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
children.forEach((child) => {
|
|
193
|
+
walkTree(child, depth + 1);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const roots = Array.from(root.children.values()).sort((a, b) =>
|
|
198
|
+
a.name.localeCompare(b.name),
|
|
199
|
+
);
|
|
200
|
+
roots.forEach((child) => {
|
|
201
|
+
walkTree(child, 1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const header = chalk.gray('Changed content:');
|
|
205
|
+
return [header, ...lines].join('\n');
|
|
55
206
|
}
|
|
56
207
|
|
|
57
208
|
export async function clearOldContentData(contentIds: string[]) {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm';
|
|
2
2
|
import {
|
|
3
3
|
isDocument,
|
|
4
|
-
isProseElement,
|
|
5
4
|
isRawElement,
|
|
6
5
|
walkElements,
|
|
7
6
|
type AnyDocument,
|
|
8
7
|
type AnySchema,
|
|
9
|
-
type ProseElement,
|
|
10
|
-
type RawElement,
|
|
11
8
|
} from '@jsprose/core';
|
|
12
9
|
import {
|
|
13
10
|
topicParts,
|
|
@@ -118,41 +115,105 @@ export async function resolveTopic(topicNode: ContentNavNode) {
|
|
|
118
115
|
},
|
|
119
116
|
);
|
|
120
117
|
|
|
118
|
+
let finalTocItems = resolvedTopicPart.tocItems;
|
|
119
|
+
|
|
121
120
|
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
practiceProblemsTocItems.length)
|
|
121
|
+
topicPart === 'practice' &&
|
|
122
|
+
practiceProblemsTocItems.length
|
|
125
123
|
) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
seen.add(item.elementId);
|
|
143
|
-
return true;
|
|
124
|
+
// Map elementId -> TocItem for both headings and problems
|
|
125
|
+
const itemMap = new Map<string, ResolvedTocItem>();
|
|
126
|
+
|
|
127
|
+
// Collect all existing TOC items (headings) recursively
|
|
128
|
+
const collectItems = (items: ResolvedTocItem[]) => {
|
|
129
|
+
for (const item of items) {
|
|
130
|
+
if (item.elementId) {
|
|
131
|
+
itemMap.set(item.elementId, item);
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
item.type === 'heading' &&
|
|
135
|
+
item.children?.length
|
|
136
|
+
) {
|
|
137
|
+
collectItems(item.children);
|
|
144
138
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
collectItems(resolvedTopicPart.tocItems || []);
|
|
142
|
+
|
|
143
|
+
// Add practice problems to the map
|
|
144
|
+
practiceProblemsTocItems.forEach((p) => {
|
|
145
|
+
if (p.elementId) {
|
|
146
|
+
itemMap.set(p.elementId, p);
|
|
147
|
+
}
|
|
153
148
|
});
|
|
149
|
+
|
|
150
|
+
// Rebuild TOC in document order using walkElements
|
|
151
|
+
const result: ResolvedTocItem[] = [];
|
|
152
|
+
const stack: ResolvedTocItem[] = [];
|
|
153
|
+
|
|
154
|
+
await walkElements(
|
|
155
|
+
resolvedTopicPart.proseElement,
|
|
156
|
+
(element) => {
|
|
157
|
+
if (element.id && itemMap.has(element.id)) {
|
|
158
|
+
const item = itemMap.get(element.id)!;
|
|
159
|
+
|
|
160
|
+
if (item.type === 'heading') {
|
|
161
|
+
// Pop headings at same or deeper level
|
|
162
|
+
while (stack.length > 0) {
|
|
163
|
+
const last = stack[stack.length - 1];
|
|
164
|
+
if (
|
|
165
|
+
last.type === 'heading' &&
|
|
166
|
+
last.level >= item.level
|
|
167
|
+
) {
|
|
168
|
+
stack.pop();
|
|
169
|
+
} else {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create new heading with empty children array
|
|
175
|
+
const newItem: ResolvedTocItem = {
|
|
176
|
+
...item,
|
|
177
|
+
children: [],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Add to parent heading or root
|
|
181
|
+
if (stack.length > 0) {
|
|
182
|
+
const parent = stack[stack.length - 1];
|
|
183
|
+
if (parent.type === 'heading') {
|
|
184
|
+
parent.children.push(newItem);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
result.push(newItem);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
stack.push(newItem);
|
|
191
|
+
} else {
|
|
192
|
+
// Non-heading item (problems, etc.)
|
|
193
|
+
if (stack.length > 0) {
|
|
194
|
+
const parent = stack[stack.length - 1];
|
|
195
|
+
if (parent.type === 'heading') {
|
|
196
|
+
parent.children.push(item);
|
|
197
|
+
} else {
|
|
198
|
+
result.push(item);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
result.push(item);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
finalTocItems = result;
|
|
154
209
|
}
|
|
155
210
|
|
|
211
|
+
await ERUDIT.db.insert(ERUDIT.db.schema.contentToc).values({
|
|
212
|
+
fullId: topicNode.fullId,
|
|
213
|
+
topicPart,
|
|
214
|
+
toc: finalTocItems,
|
|
215
|
+
});
|
|
216
|
+
|
|
156
217
|
await ERUDIT.db
|
|
157
218
|
.update(ERUDIT.db.schema.topics)
|
|
158
219
|
.set({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ResolvedRawElement } from '@jsprose/core';
|
|
2
2
|
import type { ContentProseType } from '@erudit-js/core/content/prose';
|
|
3
3
|
import type { ResolvedEruditRawElement } from '@erudit-js/prose';
|
|
4
|
+
import { sql } from 'drizzle-orm';
|
|
4
5
|
|
|
5
6
|
export async function insertContentResolved(
|
|
6
7
|
contentFullId: string,
|
|
@@ -30,18 +31,22 @@ export async function insertContentResolved(
|
|
|
30
31
|
contentProseType,
|
|
31
32
|
elementId: snippet.elementId,
|
|
32
33
|
schemaName: snippet.schemaName,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
typeof snippet.search === 'object'
|
|
38
|
-
? snippet.search.synonyms
|
|
39
|
-
: undefined,
|
|
40
|
-
quick: Boolean(snippet.quick),
|
|
41
|
-
seo: Boolean(snippet.seo),
|
|
34
|
+
snippetData: snippet.snippetData,
|
|
35
|
+
search: !!snippet.snippetData.search,
|
|
36
|
+
quick: !!snippet.snippetData.quick,
|
|
37
|
+
seo: !!snippet.snippetData.seo,
|
|
42
38
|
});
|
|
43
39
|
}
|
|
44
40
|
|
|
41
|
+
// Deduplicate search flags for topic snippets
|
|
42
|
+
if (
|
|
43
|
+
contentProseType === 'article' ||
|
|
44
|
+
contentProseType === 'summary' ||
|
|
45
|
+
contentProseType === 'practice'
|
|
46
|
+
) {
|
|
47
|
+
await deduplicateTopicSnippetsSearch(contentFullId);
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
for (const problemScript of resolveResult.problemScripts) {
|
|
46
51
|
await ERUDIT.repository.db.pushProblemScript(
|
|
47
52
|
problemScript,
|
|
@@ -91,3 +96,37 @@ function globalContentToNavNode(globalContentPath: string) {
|
|
|
91
96
|
|
|
92
97
|
return navNode;
|
|
93
98
|
}
|
|
99
|
+
|
|
100
|
+
async function deduplicateTopicSnippetsSearch(contentFullId: string) {
|
|
101
|
+
// Disable search flag for duplicate snippets,
|
|
102
|
+
// keeping the highest-priority one per (title, schemaName)
|
|
103
|
+
await ERUDIT.db.run(sql`
|
|
104
|
+
UPDATE contentSnippets
|
|
105
|
+
SET search = 0
|
|
106
|
+
WHERE snippetId IN (
|
|
107
|
+
SELECT snippetId
|
|
108
|
+
FROM (
|
|
109
|
+
SELECT
|
|
110
|
+
snippetId,
|
|
111
|
+
ROW_NUMBER() OVER (
|
|
112
|
+
PARTITION BY
|
|
113
|
+
LOWER(TRIM(json_extract(snippetData, '$.title'))),
|
|
114
|
+
schemaName
|
|
115
|
+
ORDER BY
|
|
116
|
+
CASE contentProseType
|
|
117
|
+
WHEN 'article' THEN 0
|
|
118
|
+
WHEN 'summary' THEN 1
|
|
119
|
+
WHEN 'practice' THEN 2
|
|
120
|
+
ELSE 99
|
|
121
|
+
END
|
|
122
|
+
) AS rn
|
|
123
|
+
FROM contentSnippets
|
|
124
|
+
WHERE
|
|
125
|
+
contentFullId = ${contentFullId}
|
|
126
|
+
AND json_extract(snippetData, '$.title') IS NOT NULL
|
|
127
|
+
AND search = 1
|
|
128
|
+
)
|
|
129
|
+
WHERE rn > 1
|
|
130
|
+
);
|
|
131
|
+
`);
|
|
132
|
+
}
|
|
@@ -121,13 +121,41 @@ export async function searchIndexSnippets(): Promise<SearchEntriesList[]> {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
let searchTitle = dbSnippet.snippetData.title!;
|
|
125
|
+
let searchDescription = dbSnippet.snippetData.description;
|
|
126
|
+
if (
|
|
127
|
+
typeof dbSnippet.snippetData.search === 'object' &&
|
|
128
|
+
!Array.isArray(dbSnippet.snippetData.search)
|
|
129
|
+
) {
|
|
130
|
+
if (dbSnippet.snippetData.search.title) {
|
|
131
|
+
searchTitle = dbSnippet.snippetData.search.title;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (dbSnippet.snippetData.search.description) {
|
|
135
|
+
searchDescription = dbSnippet.snippetData.search.description;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let searchSynonyms: string[] | undefined = undefined;
|
|
140
|
+
if (Array.isArray(dbSnippet.snippetData.search)) {
|
|
141
|
+
searchSynonyms = dbSnippet.snippetData.search;
|
|
142
|
+
} else {
|
|
143
|
+
if (typeof dbSnippet.snippetData.search === 'object') {
|
|
144
|
+
searchSynonyms = dbSnippet.snippetData.search.synonyms;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
entryLists.get(dbSnippet.schemaName)!.entries.push({
|
|
125
149
|
category: 'element:' + dbSnippet.schemaName,
|
|
126
|
-
title:
|
|
127
|
-
description:
|
|
150
|
+
title: searchTitle,
|
|
151
|
+
description: searchDescription,
|
|
128
152
|
link,
|
|
129
153
|
location: locationTitle,
|
|
130
|
-
synonyms:
|
|
154
|
+
synonyms: searchSynonyms
|
|
155
|
+
? searchSynonyms.length > 0
|
|
156
|
+
? searchSynonyms
|
|
157
|
+
: undefined
|
|
158
|
+
: undefined,
|
|
131
159
|
});
|
|
132
160
|
}
|
|
133
161
|
|