mirror of
https://github.com/10h30/wordpress-export-to-markdown.git
synced 2026-06-05 15:09:59 +09:00
165 lines
4.5 KiB
JavaScript
165 lines
4.5 KiB
JavaScript
const chalk = require('chalk');
|
|
const fs = require('fs');
|
|
const luxon = require('luxon');
|
|
const path = require('path');
|
|
const requestPromiseNative = require('request-promise-native');
|
|
|
|
const shared = require('./shared');
|
|
const settings = require('./settings');
|
|
|
|
async function writeFilesPromise(posts, config) {
|
|
await writeMarkdownFilesPromise(posts, config);
|
|
await writeImageFilesPromise(posts, config);
|
|
}
|
|
|
|
async function processPayloadsPromise(payloads, loadFunc, config) {
|
|
const promises = payloads.map(payload => new Promise((resolve, reject) => {
|
|
setTimeout(async () => {
|
|
try {
|
|
const data = await loadFunc(payload.item, config);
|
|
await writeFile(payload.destinationPath, data);
|
|
console.log(chalk.green('[OK]') + ' ' + payload.name);
|
|
resolve();
|
|
} catch (ex) {
|
|
console.log(chalk.red('[FAILED]') + ' ' + payload.name + ' ' + chalk.red('(' + ex.toString() + ')'));
|
|
reject();
|
|
}
|
|
}, payload.delay);
|
|
}));
|
|
|
|
const results = await Promise.allSettled(promises);
|
|
const failedCount = results.filter(result => result.status === 'rejected').length;
|
|
if (failedCount === 0) {
|
|
console.log('Done, got them all!');
|
|
} else {
|
|
console.log('Done, but with ' + chalk.red(failedCount + ' failed') + '.');
|
|
}
|
|
}
|
|
|
|
async function writeFile(destinationPath, data) {
|
|
await fs.promises.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
await fs.promises.writeFile(destinationPath, data);
|
|
}
|
|
|
|
async function writeMarkdownFilesPromise(posts, config ) {
|
|
// package up posts into payloads
|
|
const payloads = posts.map((post, index) => ({
|
|
item: post,
|
|
name: post.meta.slug,
|
|
destinationPath: getPostPath(post, config),
|
|
delay: index * settings.markdown_file_write_delay
|
|
}));
|
|
|
|
console.log('\nSaving posts...');
|
|
await processPayloadsPromise(payloads, loadMarkdownFilePromise, config);
|
|
}
|
|
|
|
async function loadMarkdownFilePromise(post) {
|
|
let output = '---\n';
|
|
|
|
Object.entries(post.frontmatter).forEach(([key, value]) => {
|
|
let outputValue;
|
|
if (Array.isArray(value)) {
|
|
if (value.length > 0) {
|
|
// array of one or more strings
|
|
outputValue = value.reduce((list, item) => `${list}\n - "${item}"`, '');
|
|
}
|
|
} else {
|
|
// single string value
|
|
const escapedValue = (value || '').replace(/"/g, '\\"');
|
|
outputValue = `"${escapedValue}"`;
|
|
}
|
|
|
|
if (outputValue !== undefined) {
|
|
output += `${key}: ${outputValue}\n`;
|
|
}
|
|
});
|
|
|
|
output += `---\n\n${post.content}\n`;
|
|
return output;
|
|
}
|
|
|
|
async function writeImageFilesPromise(posts, config) {
|
|
// collect image data from all posts into a single flattened array of payloads
|
|
let delay = 0;
|
|
const payloads = posts.flatMap(post => {
|
|
const postPath = getPostPath(post, config);
|
|
const imagesDir = path.join(path.dirname(postPath), 'images');
|
|
return post.meta.imageUrls.map(imageUrl => {
|
|
const filename = shared.getFilenameFromUrl(imageUrl);
|
|
const payload = {
|
|
item: imageUrl,
|
|
name: filename,
|
|
destinationPath: path.join(imagesDir, filename),
|
|
delay
|
|
};
|
|
delay += settings.image_file_request_delay;
|
|
return payload;
|
|
});
|
|
});
|
|
|
|
if (payloads.length > 0) {
|
|
console.log('\nDownloading and saving images...');
|
|
await processPayloadsPromise(payloads, loadImageFilePromise);
|
|
} else {
|
|
console.log('\nNo images to download and save...');
|
|
}
|
|
}
|
|
|
|
async function loadImageFilePromise(imageUrl) {
|
|
let buffer;
|
|
try {
|
|
buffer = await requestPromiseNative.get({
|
|
url: encodeURI(imageUrl),
|
|
encoding: null, // preserves binary encoding
|
|
headers: {
|
|
'User-Agent': 'wordpress-export-to-markdown'
|
|
}
|
|
});
|
|
} catch (ex) {
|
|
if (ex.name === 'StatusCodeError') {
|
|
// these errors contain a lot of noise, simplify to just the status code
|
|
ex.message = ex.statusCode;
|
|
}
|
|
throw ex;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
function getPostPath(post, config) {
|
|
const dt = luxon.DateTime.fromISO(post.frontmatter.date);
|
|
|
|
// start with base output dir
|
|
const pathSegments = [config.output];
|
|
|
|
// create fragment for post type if we're dealing with more than just "post"
|
|
if (config.includeOtherTypes) {
|
|
pathSegments.push(post.meta.type);
|
|
}
|
|
|
|
if (config.yearFolders) {
|
|
pathSegments.push(dt.toFormat('yyyy'));
|
|
}
|
|
|
|
if (config.monthFolders) {
|
|
pathSegments.push(dt.toFormat('LL'));
|
|
}
|
|
|
|
// create slug fragment, possibly date prefixed
|
|
let slugFragment = post.meta.slug;
|
|
if (config.prefixDate) {
|
|
slugFragment = dt.toFormat('yyyy-LL-dd') + '-' + slugFragment;
|
|
}
|
|
|
|
// use slug fragment as folder or filename as specified
|
|
if (config.postFolders) {
|
|
pathSegments.push(slugFragment, 'index.md');
|
|
} else {
|
|
pathSegments.push(slugFragment + '.md');
|
|
}
|
|
|
|
return path.join(...pathSegments);
|
|
}
|
|
|
|
exports.writeFilesPromise = writeFilesPromise;
|