@geekity/plugin-wordpress 0.1.1 → 0.2.0
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/README.md +171 -9
- package/dist/bundle/index.js +24337 -2098
- package/dist/bundle/plugin.json +2 -2
- package/dist/comments-import.d.ts +3 -0
- package/dist/comments-import.d.ts.map +1 -0
- package/dist/comments-import.js +155 -0
- package/dist/comments-import.js.map +1 -0
- package/dist/content-command.d.ts +3 -0
- package/dist/content-command.d.ts.map +1 -0
- package/dist/content-command.js +100 -0
- package/dist/content-command.js.map +1 -0
- package/dist/content-import.d.ts +53 -0
- package/dist/content-import.d.ts.map +1 -0
- package/dist/content-import.js +251 -0
- package/dist/content-import.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/media-import.d.ts +3 -0
- package/dist/media-import.d.ts.map +1 -0
- package/dist/media-import.js +89 -0
- package/dist/media-import.js.map +1 -0
- package/dist/media-redirect.d.ts +4 -0
- package/dist/media-redirect.d.ts.map +1 -0
- package/dist/media-redirect.js +28 -0
- package/dist/media-redirect.js.map +1 -0
- package/dist/media.d.ts +14 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +102 -0
- package/dist/media.js.map +1 -0
- package/dist/posts-import.d.ts +34 -0
- package/dist/posts-import.d.ts.map +1 -0
- package/dist/posts-import.js +301 -0
- package/dist/posts-import.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wordpress-html.d.ts +7 -0
- package/dist/wordpress-html.d.ts.map +1 -0
- package/dist/wordpress-html.js +360 -0
- package/dist/wordpress-html.js.map +1 -0
- package/dist/wxr.d.ts +80 -0
- package/dist/wxr.d.ts.map +1 -0
- package/dist/wxr.js +212 -0
- package/dist/wxr.js.map +1 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { definePlugin } from '@geekity/cms/plugin';
|
|
11
11
|
import { importCommand } from './command.js';
|
|
12
|
+
import { contentImportCommand } from './content-command.js';
|
|
12
13
|
import { wordPressFederation } from './federation.js';
|
|
14
|
+
import { WORDPRESS_UPLOADS_ROUTE, wordPressUploadsRedirect } from './media-redirect.js';
|
|
13
15
|
import { wordPressRecords } from './records.js';
|
|
14
16
|
import { wordPressScreen } from './screen.js';
|
|
15
17
|
import { VERSION } from './version.js';
|
|
@@ -20,13 +22,16 @@ export default definePlugin({
|
|
|
20
22
|
version: VERSION,
|
|
21
23
|
label: 'WordPress',
|
|
22
24
|
description: 'Answers the inbox and collection paths the WordPress ActivityPub plugin published, ' +
|
|
23
|
-
'for a site that moved here from it, until every follower has refetched the actor
|
|
25
|
+
'for a site that moved here from it, until every follower has refetched the actor, ' +
|
|
26
|
+
'and sends its old /wp-content/uploads/ media URLs to the copies under /uploads/.',
|
|
24
27
|
hostApi: 1,
|
|
25
28
|
register(host) {
|
|
26
29
|
const records = wordPressRecords(host.data);
|
|
27
30
|
host.federation(wordPressFederation(records));
|
|
28
31
|
host.screen(wordPressScreen(records));
|
|
29
32
|
host.command(importCommand(records));
|
|
33
|
+
host.command(contentImportCommand(host.data));
|
|
34
|
+
host.get(WORDPRESS_UPLOADS_ROUTE, wordPressUploadsRedirect(host.data));
|
|
30
35
|
},
|
|
31
36
|
});
|
|
32
37
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQ9F,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAQtB,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,WAAW;IAClB,WAAW,EACT,qFAAqF;QACrF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQ9F,OAAO,EACL,WAAW,EACX,2BAA2B,EAC3B,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAQtB,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,WAAW;IAClB,WAAW,EACT,qFAAqF;QACrF,oFAAoF;QACpF,kFAAkF;IACpF,OAAO,EAAE,CAAC;IACV,QAAQ,CAAC,IAAI;QACX,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC,uBAAuB,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-import.d.ts","sourceRoot":"","sources":["../src/media-import.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAKV,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAS7B,eAAO,MAAM,WAAW,EAAE,iBAGzB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { IMPORT_REDIRECTS_FILE } from './content-import.js';
|
|
4
|
+
import { uploadUrl, wordPressMedia } from './media.js';
|
|
5
|
+
import { homeOf } from './posts-import.js';
|
|
6
|
+
const MEDIA_FILE = '_data/media.json';
|
|
7
|
+
export const attachments = {
|
|
8
|
+
postTypes: ['attachment'],
|
|
9
|
+
import: importAttachments,
|
|
10
|
+
};
|
|
11
|
+
async function importAttachments(exported, context) {
|
|
12
|
+
const media = wordPressMedia(exported, context.options['origins']);
|
|
13
|
+
const given = context.options['uploads'];
|
|
14
|
+
if (given === true) {
|
|
15
|
+
throw new Error('--uploads needs the directory, a copy of wp-content/uploads.');
|
|
16
|
+
}
|
|
17
|
+
const wanted = new Map(media.originals);
|
|
18
|
+
for (const item of exported.items) {
|
|
19
|
+
if (item.type !== 'post' && item.type !== 'page')
|
|
20
|
+
continue;
|
|
21
|
+
for (const upload of media.references(item.content)) {
|
|
22
|
+
const original = media.originalOf(upload);
|
|
23
|
+
if (!wanted.has(original))
|
|
24
|
+
wanted.set(original, item);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const files = [];
|
|
28
|
+
const notes = [];
|
|
29
|
+
const entries = [];
|
|
30
|
+
const uploadsDir = given === undefined ? undefined : path.resolve(context.cwd, given);
|
|
31
|
+
const home = homeOf(exported);
|
|
32
|
+
for (const [upload, item] of wanted) {
|
|
33
|
+
if (uploadsDir === undefined) {
|
|
34
|
+
notes.push({
|
|
35
|
+
item,
|
|
36
|
+
outcome: 'warned',
|
|
37
|
+
why: 'not copied: give --uploads <directory>, a copy of wp-content/uploads',
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const bytes = await readIfPresent(path.join(uploadsDir, ...upload.split('/')));
|
|
42
|
+
if (bytes === undefined) {
|
|
43
|
+
notes.push({
|
|
44
|
+
item,
|
|
45
|
+
outcome: 'warned',
|
|
46
|
+
why: `${upload} is not in ${uploadsDir}; ${uploadUrl(upload)} will not answer until it is there`,
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const check = context.site.checkUpload(path.posix.basename(upload), bytes);
|
|
51
|
+
if (!check.accepted) {
|
|
52
|
+
notes.push({ item, outcome: 'warned', why: `${upload} was not copied: ${check.why}` });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
files.push({ item, path: `uploads/${upload}`, contents: check.bytes });
|
|
56
|
+
if (item.type !== 'attachment')
|
|
57
|
+
continue;
|
|
58
|
+
const alt = item.meta.find((meta) => meta.key === '_wp_attachment_image_alt')?.value.trim();
|
|
59
|
+
if (alt !== undefined && alt !== '') {
|
|
60
|
+
entries.push({ item, file: MEDIA_FILE, key: upload, value: { alt } });
|
|
61
|
+
}
|
|
62
|
+
for (const from of attachmentPages(item, home)) {
|
|
63
|
+
entries.push({ item, file: IMPORT_REDIRECTS_FILE, key: from, value: uploadUrl(upload) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { files, notes, entries };
|
|
67
|
+
}
|
|
68
|
+
function attachmentPages(item, home) {
|
|
69
|
+
const pages = new Set([
|
|
70
|
+
`${home.pathname}?attachment_id=${String(item.id)}`,
|
|
71
|
+
`${home.pathname}?p=${String(item.id)}`,
|
|
72
|
+
]);
|
|
73
|
+
const link = URL.parse(item.link);
|
|
74
|
+
if (link !== null && link.host === new URL(home.href).host && link.pathname !== home.pathname) {
|
|
75
|
+
pages.add(`${link.pathname}${link.search}`);
|
|
76
|
+
}
|
|
77
|
+
return [...pages];
|
|
78
|
+
}
|
|
79
|
+
async function readIfPresent(file) {
|
|
80
|
+
try {
|
|
81
|
+
return new Uint8Array(await readFile(file));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code === 'ENOENT')
|
|
85
|
+
return undefined;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=media-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-import.js","sourceRoot":"","sources":["../src/media-import.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PluginDataFolder, PluginRouteHandler } from '@geekity/cms/plugin';
|
|
2
|
+
export declare const WORDPRESS_UPLOADS_ROUTE = "/wp-content/uploads/*";
|
|
3
|
+
export declare function wordPressUploadsRedirect(data: PluginDataFolder): PluginRouteHandler;
|
|
4
|
+
//# sourceMappingURL=media-redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-redirect.d.ts","sourceRoot":"","sources":["../src/media-redirect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAKhF,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAE/D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAyBnF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IMPORT_RECORD_FILE } from './content-import.js';
|
|
2
|
+
import { decoded, uploadUrl, variantCandidates, WORDPRESS_UPLOADS } from './media.js';
|
|
3
|
+
export const WORDPRESS_UPLOADS_ROUTE = `${WORDPRESS_UPLOADS}*`;
|
|
4
|
+
export function wordPressUploadsRedirect(data) {
|
|
5
|
+
let cached = {
|
|
6
|
+
text: undefined,
|
|
7
|
+
copied: new Set(),
|
|
8
|
+
};
|
|
9
|
+
const copied = () => {
|
|
10
|
+
const text = data.read(IMPORT_RECORD_FILE);
|
|
11
|
+
if (cached.text !== text) {
|
|
12
|
+
const files = text === undefined ? {} : JSON.parse(text).files;
|
|
13
|
+
cached = { text, copied: new Set(Object.keys(files ?? {})) };
|
|
14
|
+
}
|
|
15
|
+
return cached.copied;
|
|
16
|
+
};
|
|
17
|
+
return ({ request }) => {
|
|
18
|
+
const url = new URL(request.url);
|
|
19
|
+
const upload = decoded(url.pathname.slice(WORDPRESS_UPLOADS.length));
|
|
20
|
+
const record = copied();
|
|
21
|
+
const target = variantCandidates(upload).find((candidate) => record.has(`uploads/${candidate}`)) ?? upload;
|
|
22
|
+
return new Response(null, {
|
|
23
|
+
status: 301,
|
|
24
|
+
headers: { location: `${uploadUrl(target)}${url.search}` },
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=media-redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-redirect.js","sourceRoot":"","sources":["../src/media-redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,iBAAiB,GAAG,CAAC;AAE/D,MAAM,UAAU,wBAAwB,CAAC,IAAsB;IAC7D,IAAI,MAAM,GAA8D;QACtE,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,IAAI,GAAG,EAAE;KAClB,CAAC;IACF,MAAM,MAAM,GAAG,GAAwB,EAAE;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC,KAAK,CAAC;YACvF,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,MAAM,MAAM,GACV,iBAAiB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,SAAS,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC;QAC9F,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE;SAC3D,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WordPressExport, WordPressItem } from './wxr.ts';
|
|
2
|
+
export declare const WORDPRESS_UPLOADS = "/wp-content/uploads/";
|
|
3
|
+
export type UploadPath = string;
|
|
4
|
+
export declare function variantCandidates(upload: UploadPath): UploadPath[];
|
|
5
|
+
export declare function uploadUrl(upload: UploadPath): string;
|
|
6
|
+
export interface WordPressMedia {
|
|
7
|
+
readonly originals: ReadonlyMap<UploadPath, WordPressItem>;
|
|
8
|
+
originalOf(upload: UploadPath): UploadPath;
|
|
9
|
+
references(text: string): UploadPath[];
|
|
10
|
+
rewrite(text: string, origin?: string): string;
|
|
11
|
+
}
|
|
12
|
+
export declare function wordPressMedia(exported: WordPressExport, origins: string | true | undefined): WordPressMedia;
|
|
13
|
+
export declare function decoded(text: string): string;
|
|
14
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE/D,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAIxD,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAQhC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,EAAE,CAUlE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAC3D,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC;IAC3C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACjC,cAAc,CA0ChB;AA4CD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C"}
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const WORDPRESS_UPLOADS = '/wp-content/uploads/';
|
|
2
|
+
const UPLOADS = '/uploads/';
|
|
3
|
+
/**
|
|
4
|
+
* A WordPress size variant's name: `photo-1024x575.png`, or `photo-scaled.png`
|
|
5
|
+
* for the copy WordPress serves in place of a very large original.
|
|
6
|
+
*/
|
|
7
|
+
const VARIANT = /^(.*)-(?:\d+x\d+|scaled)(\.[^./]+)$/i;
|
|
8
|
+
export function variantCandidates(upload) {
|
|
9
|
+
const candidates = [upload];
|
|
10
|
+
for (let match = VARIANT.exec(upload); match !== null; match = VARIANT.exec(candidates.at(-1) ?? '')) {
|
|
11
|
+
candidates.push(`${match[1] ?? ''}${match[2] ?? ''}`);
|
|
12
|
+
}
|
|
13
|
+
return candidates;
|
|
14
|
+
}
|
|
15
|
+
export function uploadUrl(upload) {
|
|
16
|
+
return `${UPLOADS}${encodeURI(upload)}`;
|
|
17
|
+
}
|
|
18
|
+
export function wordPressMedia(exported, origins) {
|
|
19
|
+
const originals = new Map();
|
|
20
|
+
for (const item of exported.items) {
|
|
21
|
+
if (item.type !== 'attachment')
|
|
22
|
+
continue;
|
|
23
|
+
const attached = attachedFile(item);
|
|
24
|
+
if (attached !== undefined)
|
|
25
|
+
originals.set(withoutScaled(attached), item);
|
|
26
|
+
}
|
|
27
|
+
const site = new URL(exported.site.baseSiteUrl || exported.site.link);
|
|
28
|
+
const hosts = new Set([exported.site.link, exported.site.baseSiteUrl, exported.site.baseBlogUrl]
|
|
29
|
+
.filter((url) => URL.canParse(url))
|
|
30
|
+
.map((url) => new URL(url).host.toLowerCase()));
|
|
31
|
+
for (const host of otherHosts(origins))
|
|
32
|
+
hosts.add(host);
|
|
33
|
+
const pattern = uploadsPattern(`${site.pathname.replace(/\/+$/, '')}${WORDPRESS_UPLOADS}`);
|
|
34
|
+
const originalOf = (upload) => variantCandidates(upload).find((candidate) => originals.has(candidate)) ?? upload;
|
|
35
|
+
const each = (text, found) => text.replace(pattern, (match, host, written) => {
|
|
36
|
+
if (host !== undefined && !hosts.has(host.toLowerCase()))
|
|
37
|
+
return match;
|
|
38
|
+
const trimmed = written.replace(/[.,;:!]+$/, '');
|
|
39
|
+
const upload = decoded(trimmed);
|
|
40
|
+
return `${found(upload, match)}${written.slice(trimmed.length)}`;
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
originals,
|
|
44
|
+
originalOf,
|
|
45
|
+
references(text) {
|
|
46
|
+
const found = new Set();
|
|
47
|
+
each(text, (upload, match) => {
|
|
48
|
+
found.add(upload);
|
|
49
|
+
return match;
|
|
50
|
+
});
|
|
51
|
+
return [...found];
|
|
52
|
+
},
|
|
53
|
+
rewrite: (text, origin = '') => each(text, (upload) => `${origin}${uploadUrl(originalOf(upload))}`),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function attachedFile(item) {
|
|
57
|
+
const meta = item.meta.find((entry) => entry.key === '_wp_attached_file')?.value;
|
|
58
|
+
if (meta !== undefined && meta !== '')
|
|
59
|
+
return meta;
|
|
60
|
+
const url = item.attachmentUrl;
|
|
61
|
+
if (url === undefined || !URL.canParse(url))
|
|
62
|
+
return undefined;
|
|
63
|
+
const { pathname } = new URL(url);
|
|
64
|
+
const at = pathname.indexOf(WORDPRESS_UPLOADS);
|
|
65
|
+
return at === -1 ? undefined : decoded(pathname.slice(at + WORDPRESS_UPLOADS.length));
|
|
66
|
+
}
|
|
67
|
+
function withoutScaled(upload) {
|
|
68
|
+
return upload.replace(/-scaled(\.[^./]+)$/i, '$1');
|
|
69
|
+
}
|
|
70
|
+
function otherHosts(origins) {
|
|
71
|
+
if (origins === undefined)
|
|
72
|
+
return [];
|
|
73
|
+
if (origins === true) {
|
|
74
|
+
throw new Error('--origins needs the other origins, such as https://staging.example.com.');
|
|
75
|
+
}
|
|
76
|
+
return origins
|
|
77
|
+
.split(',')
|
|
78
|
+
.map((origin) => origin.trim())
|
|
79
|
+
.filter((origin) => origin !== '')
|
|
80
|
+
.map((origin) => {
|
|
81
|
+
const url = URL.parse(origin.includes('://') ? origin : `https://${origin}`);
|
|
82
|
+
if (url === null)
|
|
83
|
+
throw new Error(`--origins: ${origin} is not an origin.`);
|
|
84
|
+
return url.host.toLowerCase();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const CAPTURED_HOST = String.raw `(?:https?:)?//([^/\s"'<>()]+)`;
|
|
88
|
+
const NOT_INSIDE_A_LONGER_PATH = String.raw `(?<![\w/.:-])`;
|
|
89
|
+
const CAPTURED_UPLOAD_PATH = String.raw `([^\s"'<>()\[\]\\?#]+)`;
|
|
90
|
+
function uploadsPattern(prefix) {
|
|
91
|
+
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
92
|
+
return new RegExp(`(?:${CAPTURED_HOST}|${NOT_INSIDE_A_LONGER_PATH})${escapedPrefix}${CAPTURED_UPLOAD_PATH}`, 'gi');
|
|
93
|
+
}
|
|
94
|
+
export function decoded(text) {
|
|
95
|
+
try {
|
|
96
|
+
return decodeURI(text);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return text;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WordPressImporter } from './content-import.ts';
|
|
2
|
+
import type { WordPressExport, WordPressItem } from './wxr.ts';
|
|
3
|
+
type Disposition = {
|
|
4
|
+
readonly kind: 'import';
|
|
5
|
+
readonly draft: boolean;
|
|
6
|
+
readonly servedByWordPress: boolean;
|
|
7
|
+
readonly migrated: boolean;
|
|
8
|
+
readonly warning?: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'skip';
|
|
11
|
+
readonly why: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const postsAndPages: WordPressImporter;
|
|
14
|
+
type Placement = {
|
|
15
|
+
readonly item: WordPressItem;
|
|
16
|
+
readonly disposition: Extract<Disposition, {
|
|
17
|
+
kind: 'skip';
|
|
18
|
+
}>;
|
|
19
|
+
} | {
|
|
20
|
+
readonly item: WordPressItem;
|
|
21
|
+
readonly disposition: Extract<Disposition, {
|
|
22
|
+
kind: 'import';
|
|
23
|
+
}>;
|
|
24
|
+
readonly permalink: string;
|
|
25
|
+
readonly frontPage: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function placements(exported: WordPressExport): Placement[];
|
|
28
|
+
export interface Home {
|
|
29
|
+
readonly href: string;
|
|
30
|
+
readonly pathname: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function homeOf(exported: WordPressExport): Home;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=posts-import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts-import.d.ts","sourceRoot":"","sources":["../src/posts-import.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAKV,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE/D,KAAK,WAAW,GACZ;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA0CpD,eAAO,MAAM,aAAa,EAAE,iBAG3B,CAAC;AAEF,KAAK,SAAS,GACV;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAC9F;IACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEN,wBAAgB,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,EAAE,CAqBjE;AA8DD,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAItD"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { dump } from 'js-yaml';
|
|
2
|
+
import { IMPORT_REDIRECTS_FILE } from './content-import.js';
|
|
3
|
+
import { wordPressMedia } from './media.js';
|
|
4
|
+
import { convertBody } from './wordpress-html.js';
|
|
5
|
+
function dispositionOf(item) {
|
|
6
|
+
switch (item.status) {
|
|
7
|
+
case 'publish':
|
|
8
|
+
return item.password === ''
|
|
9
|
+
? { kind: 'import', draft: false, servedByWordPress: true, migrated: true }
|
|
10
|
+
: {
|
|
11
|
+
kind: 'import',
|
|
12
|
+
draft: true,
|
|
13
|
+
servedByWordPress: true,
|
|
14
|
+
migrated: true,
|
|
15
|
+
warning: 'password-protected on WordPress; imported as a draft, since this site has no post passwords',
|
|
16
|
+
};
|
|
17
|
+
case 'future':
|
|
18
|
+
return { kind: 'import', draft: false, servedByWordPress: false, migrated: false };
|
|
19
|
+
case 'draft':
|
|
20
|
+
case 'pending':
|
|
21
|
+
return { kind: 'import', draft: true, servedByWordPress: false, migrated: true };
|
|
22
|
+
case 'private':
|
|
23
|
+
return {
|
|
24
|
+
kind: 'import',
|
|
25
|
+
draft: true,
|
|
26
|
+
servedByWordPress: false,
|
|
27
|
+
migrated: true,
|
|
28
|
+
warning: 'private on WordPress; imported as a draft, since this site has no private posts',
|
|
29
|
+
};
|
|
30
|
+
case 'trash':
|
|
31
|
+
return { kind: 'skip', why: 'in the WordPress trash' };
|
|
32
|
+
case 'auto-draft':
|
|
33
|
+
return { kind: 'skip', why: 'an empty draft WordPress saved when the editor opened' };
|
|
34
|
+
default:
|
|
35
|
+
return {
|
|
36
|
+
kind: 'skip',
|
|
37
|
+
why: `WordPress status "${item.status}", which this import does not know`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const SITE_JSON = '_data/site.json';
|
|
42
|
+
export const postsAndPages = {
|
|
43
|
+
postTypes: ['post', 'page'],
|
|
44
|
+
import: importPostsAndPages,
|
|
45
|
+
};
|
|
46
|
+
export function placements(exported) {
|
|
47
|
+
const items = exported.items.filter((item) => item.type === 'post' || item.type === 'page');
|
|
48
|
+
const home = homeOf(exported);
|
|
49
|
+
const structure = inferredPermalinkStructure(items, home);
|
|
50
|
+
const pagesById = new Map(items.filter((item) => item.type === 'page').map((item) => [item.id, item]));
|
|
51
|
+
return items.map((item) => {
|
|
52
|
+
const disposition = dispositionOf(item);
|
|
53
|
+
if (disposition.kind === 'skip')
|
|
54
|
+
return { item, disposition };
|
|
55
|
+
const frontPage = item.type === 'page' && sameUrl(item.link, home.href);
|
|
56
|
+
const permalink = item.type === 'page'
|
|
57
|
+
? frontPage || !isPretty(item.link, home)
|
|
58
|
+
? pagePermalink(item, pagesById)
|
|
59
|
+
: decodedPath(item.link)
|
|
60
|
+
: isPretty(item.link, home)
|
|
61
|
+
? decodedPath(item.link)
|
|
62
|
+
: structure(item);
|
|
63
|
+
return { item, disposition, permalink, frontPage };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function importPostsAndPages(exported, context) {
|
|
67
|
+
const home = homeOf(exported);
|
|
68
|
+
const users = new Set(context.site.users().map((user) => user.username));
|
|
69
|
+
const media = wordPressMedia(exported, context.options['origins']);
|
|
70
|
+
const files = [];
|
|
71
|
+
const notes = [];
|
|
72
|
+
const entries = [];
|
|
73
|
+
const taken = new Set();
|
|
74
|
+
for (const placement of placements(exported)) {
|
|
75
|
+
if (!('permalink' in placement)) {
|
|
76
|
+
notes.push({ item: placement.item, outcome: 'skipped', why: placement.disposition.why });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const { item, disposition, permalink, frontPage } = placement;
|
|
80
|
+
if (disposition.warning !== undefined) {
|
|
81
|
+
notes.push({ item, outcome: 'warned', why: disposition.warning });
|
|
82
|
+
}
|
|
83
|
+
const author = users.has(item.creator) ? item.creator : undefined;
|
|
84
|
+
if (author === undefined) {
|
|
85
|
+
notes.push({
|
|
86
|
+
item,
|
|
87
|
+
outcome: 'warned',
|
|
88
|
+
why: `written by "${item.creator}", who has no user on this site; imported with no author`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const body = convertBody(item.content);
|
|
92
|
+
const front = frontMatter(item, disposition, {
|
|
93
|
+
permalink,
|
|
94
|
+
formerly: formerPermalinks(item, permalink),
|
|
95
|
+
author,
|
|
96
|
+
inReplyTo: body.inReplyTo,
|
|
97
|
+
home,
|
|
98
|
+
});
|
|
99
|
+
files.push({
|
|
100
|
+
item,
|
|
101
|
+
path: unique(taken, fileName(item), item),
|
|
102
|
+
contents: document(front, media.rewrite(body.markdown)),
|
|
103
|
+
});
|
|
104
|
+
if (frontPage && !disposition.draft) {
|
|
105
|
+
entries.push({ item, file: SITE_JSON, key: 'homepage', value: lastSegment(permalink) });
|
|
106
|
+
}
|
|
107
|
+
if (item.status === 'publish' && !disposition.draft) {
|
|
108
|
+
const ids = item.type === 'page' ? ['p', 'page_id'] : ['p'];
|
|
109
|
+
for (const name of ids) {
|
|
110
|
+
const from = `${home.pathname}?${name}=${String(item.id)}`;
|
|
111
|
+
entries.push({ item, file: IMPORT_REDIRECTS_FILE, key: from, value: permalink });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { files, notes, entries };
|
|
116
|
+
}
|
|
117
|
+
export function homeOf(exported) {
|
|
118
|
+
const base = exported.site.baseBlogUrl || exported.site.link;
|
|
119
|
+
const url = new URL(base.endsWith('/') ? base : `${base}/`);
|
|
120
|
+
return { href: url.href, pathname: url.pathname };
|
|
121
|
+
}
|
|
122
|
+
function sameUrl(a, b) {
|
|
123
|
+
const slash = (url) => (url.endsWith('/') ? url : `${url}/`);
|
|
124
|
+
return slash(a) === slash(b);
|
|
125
|
+
}
|
|
126
|
+
/** Whether WordPress gave the item a pretty URL rather than its `?p=` or `?page_id=` one. */
|
|
127
|
+
function isPretty(link, home) {
|
|
128
|
+
if (!URL.canParse(link))
|
|
129
|
+
return false;
|
|
130
|
+
const url = new URL(link);
|
|
131
|
+
return url.search === '' && url.pathname !== home.pathname;
|
|
132
|
+
}
|
|
133
|
+
function decodedPath(link) {
|
|
134
|
+
return new URL(link).pathname.split('/').map(decodedSegment).join('/');
|
|
135
|
+
}
|
|
136
|
+
function decodedSegment(slug) {
|
|
137
|
+
try {
|
|
138
|
+
return decodeURIComponent(slug);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return slug;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function slugOf(item) {
|
|
145
|
+
const slug = decodedSegment(item.slug).replaceAll('/', '-');
|
|
146
|
+
if (slug !== '')
|
|
147
|
+
return slug;
|
|
148
|
+
const fromTitle = item.title
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
151
|
+
.replace(/^-+|-+$/g, '');
|
|
152
|
+
return fromTitle === '' ? String(item.id) : fromTitle;
|
|
153
|
+
}
|
|
154
|
+
function lastSegment(permalink) {
|
|
155
|
+
return (permalink
|
|
156
|
+
.split('/')
|
|
157
|
+
.filter((segment) => segment !== '')
|
|
158
|
+
.at(-1) ?? '');
|
|
159
|
+
}
|
|
160
|
+
function pagePermalink(item, pages) {
|
|
161
|
+
const slugs = [slugOf(item)];
|
|
162
|
+
const seen = new Set([item.id]);
|
|
163
|
+
for (let parent = pages.get(item.parent); parent !== undefined && !seen.has(parent.id); parent = pages.get(parent.parent)) {
|
|
164
|
+
seen.add(parent.id);
|
|
165
|
+
slugs.unshift(slugOf(parent));
|
|
166
|
+
}
|
|
167
|
+
return `/${slugs.join('/')}/`;
|
|
168
|
+
}
|
|
169
|
+
function localDateOf(item) {
|
|
170
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(item.date);
|
|
171
|
+
if (match === null || match[1] === '0000')
|
|
172
|
+
return undefined;
|
|
173
|
+
return { year: match[1] ?? '', month: match[2] ?? '', day: match[3] ?? '' };
|
|
174
|
+
}
|
|
175
|
+
const TOKENS = ['%year%', '%monthnum%', '%day%', '%postname%'];
|
|
176
|
+
function inferredPermalinkStructure(items, home) {
|
|
177
|
+
const counts = new Map();
|
|
178
|
+
for (const item of items) {
|
|
179
|
+
if (item.type !== 'post' || !isPretty(item.link, home))
|
|
180
|
+
continue;
|
|
181
|
+
const date = localDateOf(item);
|
|
182
|
+
const values = [date?.year, date?.month, date?.day, slugOf(item)];
|
|
183
|
+
const template = decodedPath(item.link)
|
|
184
|
+
.split('/')
|
|
185
|
+
.map((segment) => {
|
|
186
|
+
const index = values.indexOf(segment);
|
|
187
|
+
return index === -1 ? segment : TOKENS[index];
|
|
188
|
+
})
|
|
189
|
+
.join('/');
|
|
190
|
+
if (template.includes('%postname%'))
|
|
191
|
+
counts.set(template, (counts.get(template) ?? 0) + 1);
|
|
192
|
+
}
|
|
193
|
+
let structure = '/%year%/%monthnum%/%postname%/';
|
|
194
|
+
let best = 0;
|
|
195
|
+
for (const [template, count] of counts) {
|
|
196
|
+
if (count > best) {
|
|
197
|
+
structure = template;
|
|
198
|
+
best = count;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return (item) => {
|
|
202
|
+
const date = localDateOf(item) ?? localDateOf({ ...item, date: item.dateGmt ?? '' });
|
|
203
|
+
const values = [date?.year ?? '', date?.month ?? '', date?.day ?? '', slugOf(item)];
|
|
204
|
+
return TOKENS.reduce((path, token, index) => path.replaceAll(token, values[index] ?? ''), structure);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function formerPermalinks(item, permalink) {
|
|
208
|
+
const date = localDateOf(item);
|
|
209
|
+
const slug = slugOf(item);
|
|
210
|
+
const values = [date?.year, date?.month, date?.day, slug];
|
|
211
|
+
let next = 0;
|
|
212
|
+
const template = permalink.split('/').map((segment) => {
|
|
213
|
+
const index = values.findIndex((value, at) => at >= next && value === segment);
|
|
214
|
+
if (segment === '' || index === -1)
|
|
215
|
+
return segment;
|
|
216
|
+
next = index + 1;
|
|
217
|
+
return index;
|
|
218
|
+
});
|
|
219
|
+
const metaValues = (key) => item.meta.filter((meta) => meta.key === key).map((meta) => meta.value);
|
|
220
|
+
const slugs = [slug, ...metaValues('_wp_old_slug').map(decodedSegment)];
|
|
221
|
+
const dates = [
|
|
222
|
+
date,
|
|
223
|
+
...metaValues('_wp_old_date').map((value) => localDateOf({ ...item, date: value })),
|
|
224
|
+
].filter((entry) => entry !== undefined);
|
|
225
|
+
const former = new Set();
|
|
226
|
+
for (const { year, month, day } of dates) {
|
|
227
|
+
for (const name of slugs) {
|
|
228
|
+
const filled = [year, month, day, name];
|
|
229
|
+
const url = template
|
|
230
|
+
.map((part) => (typeof part === 'number' ? (filled[part] ?? '') : part))
|
|
231
|
+
.join('/');
|
|
232
|
+
if (url !== permalink)
|
|
233
|
+
former.add(url);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return [...former];
|
|
237
|
+
}
|
|
238
|
+
function fileName(item) {
|
|
239
|
+
if (item.type === 'page')
|
|
240
|
+
return `pages/${slugOf(item)}.md`;
|
|
241
|
+
const date = localDateOf(item);
|
|
242
|
+
const prefix = date === undefined ? '' : `${date.year}-${date.month}-${date.day}-`;
|
|
243
|
+
return `posts/${prefix}${slugOf(item)}.md`;
|
|
244
|
+
}
|
|
245
|
+
function unique(taken, path, item) {
|
|
246
|
+
const chosen = taken.has(path) ? path.replace(/\.md$/, `-${String(item.id)}.md`) : path;
|
|
247
|
+
taken.add(chosen);
|
|
248
|
+
return chosen;
|
|
249
|
+
}
|
|
250
|
+
function isAbsoluteUrl(value) {
|
|
251
|
+
if (!URL.canParse(value))
|
|
252
|
+
return false;
|
|
253
|
+
const { protocol } = new URL(value);
|
|
254
|
+
return protocol === 'https:' || protocol === 'http:';
|
|
255
|
+
}
|
|
256
|
+
function frontMatter(item, disposition, derived) {
|
|
257
|
+
const isPost = item.type === 'post';
|
|
258
|
+
const isStatus = item.terms.some((term) => term.taxonomy === 'post_format' && term.slug === 'post-format-status');
|
|
259
|
+
const title = isPost ? (isStatus ? '' : item.title) : item.title || slugOf(item);
|
|
260
|
+
const date = isPost ? item.dateGmt : undefined;
|
|
261
|
+
const terms = (taxonomy) => item.terms.filter((term) => term.taxonomy === taxonomy).map((term) => term.name);
|
|
262
|
+
const federated = item.meta.some((meta) => meta.key === 'activitypub_status' && meta.value === 'federated');
|
|
263
|
+
const shortlink = `${derived.home.href}?p=${String(item.id)}`;
|
|
264
|
+
const objectId = isPost && disposition.servedByWordPress ? shortlink : undefined;
|
|
265
|
+
const guid = isPost && item.guid !== shortlink && isAbsoluteUrl(item.guid) ? item.guid : undefined;
|
|
266
|
+
const front = {};
|
|
267
|
+
if (title !== '')
|
|
268
|
+
front['title'] = title;
|
|
269
|
+
if (date !== undefined)
|
|
270
|
+
front['date'] = date;
|
|
271
|
+
if (date !== undefined && item.modifiedGmt !== undefined && item.modifiedGmt !== date) {
|
|
272
|
+
front['updated'] = item.modifiedGmt;
|
|
273
|
+
}
|
|
274
|
+
front['permalink'] = derived.permalink;
|
|
275
|
+
if (derived.formerly.length > 0)
|
|
276
|
+
front['redirect_from'] = derived.formerly;
|
|
277
|
+
if (terms('post_tag').length > 0)
|
|
278
|
+
front['tags'] = terms('post_tag');
|
|
279
|
+
if (terms('category').length > 0)
|
|
280
|
+
front['categories'] = terms('category');
|
|
281
|
+
if (disposition.draft)
|
|
282
|
+
front['draft'] = true;
|
|
283
|
+
if (derived.author !== undefined)
|
|
284
|
+
front['author'] = derived.author;
|
|
285
|
+
if (derived.inReplyTo !== undefined)
|
|
286
|
+
front['in-reply-to'] = derived.inReplyTo;
|
|
287
|
+
if (objectId !== undefined) {
|
|
288
|
+
front['activitypub'] =
|
|
289
|
+
federated && date !== undefined ? { id: objectId, published: date } : { id: objectId };
|
|
290
|
+
}
|
|
291
|
+
if (guid !== undefined)
|
|
292
|
+
front['guid'] = guid;
|
|
293
|
+
if (disposition.migrated)
|
|
294
|
+
front['migrated'] = true;
|
|
295
|
+
return front;
|
|
296
|
+
}
|
|
297
|
+
function document(front, body) {
|
|
298
|
+
const yaml = dump(front, { lineWidth: -1, noRefs: true });
|
|
299
|
+
return body === '' ? `---\n${yaml}---\n` : `---\n${yaml}---\n\n${body}\n`;
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=posts-import.js.map
|