mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(web): scope html artifact link rewrites
This commit is contained in:
parent
ac0adced65
commit
9d29ef35d3
3 changed files with 49 additions and 10 deletions
|
|
@ -81,12 +81,21 @@ function buildLatestHtmlFileIndex(projectFiles: readonly HtmlLinkProjectFile[]):
|
|||
if (!isHtmlProjectFile(file)) continue;
|
||||
const key = htmlFileFamilyKey(file.name);
|
||||
if (!key) continue;
|
||||
if (!htmlFileManifestMatchesFamily(file, key)) continue;
|
||||
const current = latest.get(key);
|
||||
if (!current || file.mtime > current.mtime) latest.set(key, file);
|
||||
}
|
||||
return new Map(Array.from(latest, ([key, file]) => [key, file.name]));
|
||||
}
|
||||
|
||||
function htmlFileManifestMatchesFamily(file: HtmlLinkProjectFile, familyKey: string): boolean {
|
||||
const identifier = file.artifactManifest?.metadata?.identifier;
|
||||
if (typeof identifier !== 'string') return false;
|
||||
const normalizedIdentifier = normalizeArtifactIdentifier(identifier);
|
||||
if (!normalizedIdentifier) return false;
|
||||
return normalizedIdentifier === htmlFileFamilyIdentifier(familyKey);
|
||||
}
|
||||
|
||||
function isHtmlProjectFile(file: HtmlLinkProjectFile): boolean {
|
||||
const name = file.name.toLowerCase();
|
||||
return (
|
||||
|
|
@ -151,6 +160,21 @@ function htmlFileFamilyKey(pathname: string): string | null {
|
|||
return `${directory}${familyStem}${ext}`.replace(/^\.\//, '');
|
||||
}
|
||||
|
||||
function htmlFileFamilyIdentifier(familyKey: string): string {
|
||||
const slash = familyKey.lastIndexOf('/');
|
||||
const fileName = slash >= 0 ? familyKey.slice(slash + 1) : familyKey;
|
||||
const dot = fileName.lastIndexOf('.');
|
||||
const stem = dot > 0 ? fileName.slice(0, dot) : fileName;
|
||||
return normalizeArtifactIdentifier(stem);
|
||||
}
|
||||
|
||||
function normalizeArtifactIdentifier(value: string): string {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function preserveRelativePrefix(originalPathname: string, latestName: string): string {
|
||||
if (originalPathname.startsWith('./') && !latestName.startsWith('./')) {
|
||||
return `./${latestName}`;
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ describe('rewriteHtmlLinksToCurrentProjectFiles', () => {
|
|||
'</body></html>';
|
||||
|
||||
const out = rewriteHtmlLinksToCurrentProjectFiles(html, [
|
||||
htmlFile('about.html', 10),
|
||||
htmlFile('about-2.html', 30),
|
||||
htmlFile('contact.html', 20),
|
||||
htmlFile('contact-2.html', 40),
|
||||
htmlFile('about.html', 10, { artifactIdentifier: 'about' }),
|
||||
htmlFile('about-2.html', 30, { artifactIdentifier: 'about' }),
|
||||
htmlFile('contact.html', 20, { artifactIdentifier: 'contact' }),
|
||||
htmlFile('contact-2.html', 40, { artifactIdentifier: 'contact' }),
|
||||
]);
|
||||
|
||||
expect(out).toContain('href="about-2.html"');
|
||||
|
|
@ -109,13 +109,28 @@ describe('rewriteHtmlLinksToCurrentProjectFiles', () => {
|
|||
'</body></html>';
|
||||
|
||||
const out = rewriteHtmlLinksToCurrentProjectFiles(html, [
|
||||
htmlFile('index.html', 10),
|
||||
htmlFile('index-2.html', 40),
|
||||
htmlFile('index.html', 10, { artifactIdentifier: 'index' }),
|
||||
htmlFile('index-2.html', 40, { artifactIdentifier: 'index' }),
|
||||
]);
|
||||
|
||||
expect(out).toContain('href="index-2.html"');
|
||||
expect(out).toContain('href="./index-2.html#top"');
|
||||
});
|
||||
|
||||
it('does not rewrite to an unrelated newer numbered html file', () => {
|
||||
const html =
|
||||
'<!doctype html><html><body>' +
|
||||
'<a href="about.html">About</a>' +
|
||||
'</body></html>';
|
||||
|
||||
const out = rewriteHtmlLinksToCurrentProjectFiles(html, [
|
||||
htmlFile('about.html', 10, { artifactIdentifier: 'about' }),
|
||||
htmlFile('about-2.html', 40, { artifactIdentifier: 'other-about' }),
|
||||
]);
|
||||
|
||||
expect(out).toContain('href="about.html"');
|
||||
expect(out).not.toContain('href="about-2.html"');
|
||||
});
|
||||
});
|
||||
|
||||
function htmlFile(
|
||||
|
|
|
|||
|
|
@ -561,8 +561,8 @@ describe('ProjectView API empty response handling', () => {
|
|||
it('keeps regenerated multipage artifact entry at index.html and rewrites child links', async () => {
|
||||
mockedFetchProjectFiles.mockResolvedValue([
|
||||
htmlProjectFile('index.html', 10, { artifactIdentifier: 'index' }),
|
||||
htmlProjectFile('about.html', 20),
|
||||
htmlProjectFile('about-2.html', 40),
|
||||
htmlProjectFile('about.html', 20, { artifactIdentifier: 'about' }),
|
||||
htmlProjectFile('about-2.html', 40, { artifactIdentifier: 'about' }),
|
||||
] as never);
|
||||
mockedWriteProjectTextFile.mockResolvedValue(htmlProjectFile('index.html', 50) as never);
|
||||
const artifact =
|
||||
|
|
@ -596,8 +596,8 @@ describe('ProjectView API empty response handling', () => {
|
|||
it('does not overwrite an unrelated existing index.html when saving a multipage artifact', async () => {
|
||||
mockedFetchProjectFiles.mockResolvedValue([
|
||||
htmlProjectFile('index.html', 10),
|
||||
htmlProjectFile('about.html', 20),
|
||||
htmlProjectFile('about-2.html', 40),
|
||||
htmlProjectFile('about.html', 20, { artifactIdentifier: 'about' }),
|
||||
htmlProjectFile('about-2.html', 40, { artifactIdentifier: 'about' }),
|
||||
] as never);
|
||||
mockedWriteProjectTextFile.mockResolvedValue(htmlProjectFile('index-2.html', 50) as never);
|
||||
const artifact =
|
||||
|
|
|
|||
Loading…
Reference in a new issue