Compare commits
1 Commits
ghost-impo
...
d78b403dff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d78b403dff |
@@ -1,22 +0,0 @@
|
||||
# Cross-platform formatting config
|
||||
# See https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.{js,json,css,scss,html,jsx},.babelrc}]
|
||||
# Set default charset
|
||||
charset = utf-8
|
||||
# 4 space indentation
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
3
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
/out/
|
||||
/node_modules/
|
||||
/_site/
|
||||
/node_modules/
|
||||
53
README.md
@@ -1,53 +0,0 @@
|
||||
# WhenWe
|
||||
|
||||
Whenwe is dead. Long love Whenwe?
|
||||
|
||||
A "when-we" is a nostagic reminisce. This is an archive of when-wes, currently.
|
||||
|
||||
However, it may grow into a database of mini-biographies. Time will tell...
|
||||
|
||||
## Structure
|
||||
|
||||
- `src/` - the website content and templates
|
||||
- `bin/` - helper scripts
|
||||
|
||||
Everything in the top directory are configs and documentation, like this.
|
||||
|
||||
## Requirements
|
||||
|
||||
The site is built using [Eleventy][eleventy]. We assume you have Git, NodeJS and NPM installed.
|
||||
|
||||
## Installation for development or deployment
|
||||
|
||||
git clone $whenwe_repo_url
|
||||
cd whenwe
|
||||
npm install
|
||||
|
||||
## Development
|
||||
|
||||
npm run server
|
||||
|
||||
Now you should be able to visit the development site in your browser at http://localhost:8080
|
||||
|
||||
If you edit the content in `src/` - it should rebuild the site. Your browser should refresh automatically, but if not, refresh it manually.
|
||||
|
||||
See the [README.md](./src/) in `src/` for more information.
|
||||
|
||||
## Deployment
|
||||
|
||||
Currently the destination is configured in package.json, as part of the definition of the `deploy` run-script. To change the destination, change that.
|
||||
|
||||
`rsync` is used for deploying the site. Therefore you need to have that installed and on the path.
|
||||
|
||||
You will need `ssh` access to the destination. Setting that up is outside the scope of this document, but if you have your own web space, you will probably know about this already, and if not, you will need the assistance of someone who does. Setting up your ssh client depends on your OS - there are guides online.
|
||||
|
||||
But given that, the deploy process goes like this:
|
||||
|
||||
npm run build
|
||||
npm run deploy
|
||||
|
||||
## Issues and questions
|
||||
|
||||
These can be submitted via the issue tracker attached to this repository. You may need to create an account.
|
||||
|
||||
[eleventy]: https://www.11ty.dev/
|
||||
@@ -4,17 +4,11 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rm -rf _site && eleventy --pathprefix '~nick/whenwe/'",
|
||||
"deploy": "bin/deploy nick@mixian.noodlefactory.co.uk:public_html/whenwe/",
|
||||
"server": "eleventy --serve",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"node-html-markdown": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
328
to-ghost.js
@@ -1,328 +0,0 @@
|
||||
const data = require("./whenwe-tidied.json");
|
||||
const fs = require("fs");
|
||||
//const sh = require("sanitize-html");
|
||||
const {
|
||||
NodeHtmlMarkdown,
|
||||
NodeHtmlMarkdownOptions,
|
||||
} = require("node-html-markdown");
|
||||
const nhm = new NodeHtmlMarkdown({
|
||||
useLinkReferenceDefinitions: true,
|
||||
useInlineLinks: true,
|
||||
});
|
||||
function date(datestr) {
|
||||
if (datestr) {
|
||||
const [Y, M, D, h, m, s] = datestr.split(/[^0-9]/);
|
||||
return new Date(Date.UTC.call(null, Y, M - 1, D, h, m, s)).toISOString();
|
||||
} else return "";
|
||||
}
|
||||
|
||||
const tag_index = {
|
||||
// 1: { id: 1, name: "", slug: "" },
|
||||
};
|
||||
|
||||
const user_index = {
|
||||
"6975f732f0a00f00018346d1": {
|
||||
id: "6975f732f0a00f00018346d1",
|
||||
name: "Janet Woolley",
|
||||
slug: "janet",
|
||||
},
|
||||
};
|
||||
|
||||
const meta_index = {};
|
||||
|
||||
const author_index = {};
|
||||
|
||||
const ghost_data = {
|
||||
meta: {
|
||||
exported_on: new Date().valueOf(),
|
||||
version: "5.0.0", // Ghost version the import is valid for
|
||||
},
|
||||
data: {
|
||||
posts: [],
|
||||
// Optionally define post metadata
|
||||
posts_meta: [
|
||||
/*
|
||||
{
|
||||
post_id: "1234", // This must be the same as the post it references
|
||||
feature_image_alt: "A group of people waving at the camera",
|
||||
feature_image_caption: "The team says hello!",
|
||||
},
|
||||
*/
|
||||
],
|
||||
// Define the tags
|
||||
tags: [],
|
||||
// Relate posts to tags
|
||||
posts_tags: [],
|
||||
// Define the users
|
||||
/*
|
||||
users: [
|
||||
{
|
||||
id: "5678", // Unique ID for this author
|
||||
name: "Jo Bloggs",
|
||||
slug: "jo-blogs",
|
||||
email: "jo@example.com",
|
||||
profile_image: "/content/images/2025/scenic-background.jpg",
|
||||
roles: [
|
||||
"Contributor", // Contributor | Author| Editor | Administrator
|
||||
],
|
||||
},
|
||||
],
|
||||
*/
|
||||
// Relate posts to authors
|
||||
},
|
||||
};
|
||||
|
||||
function convertCase(
|
||||
str, //: string,
|
||||
format, // 'camel' | 'pascal' | 'snake' | 'kebab'
|
||||
) {
|
||||
const sanitiseString = (str) =>
|
||||
str
|
||||
.trim()
|
||||
.replace(/[^a-zA-Z0-9\s]/g, "")
|
||||
.replace(/\s+/g, " ");
|
||||
const formatted = sanitiseString(str);
|
||||
switch (format) {
|
||||
case "camel":
|
||||
return formatted
|
||||
.toLowerCase()
|
||||
.replace(/ (\\w)/g, (_, char) => char.toUpperCase());
|
||||
case "pascal":
|
||||
return formatted.replace(/(?:^| )(\w)/g, (_, char) => char.toUpperCase());
|
||||
case "snake":
|
||||
return formatted.toLowerCase().replace(/\s+/g, "_");
|
||||
case "kebab":
|
||||
return formatted.toLowerCase().replace(/\s+/g, "-");
|
||||
default:
|
||||
throw new Error("Unsupported format type");
|
||||
}
|
||||
}
|
||||
|
||||
function mk_tag(name, id, slug) {
|
||||
id ??= convertCase(name, "kebab");
|
||||
slug ??= id;
|
||||
tag_index[id] ??= { id, name, slug };
|
||||
return id;
|
||||
}
|
||||
|
||||
function mk_author(post_id, author_id) {
|
||||
author_index[post_id] = { post_id, author_id };
|
||||
}
|
||||
|
||||
function mk_meta(post_id, feature_image_caption, feature_image_alt) {
|
||||
if (feature_image_alt || feature_image_caption)
|
||||
meta_index[post_id] = {
|
||||
feature_image_alt,
|
||||
feature_image_caption,
|
||||
post_id,
|
||||
};
|
||||
}
|
||||
|
||||
function img_path(filename) {
|
||||
if (!filename) throw new Error("No filename");
|
||||
return "content/images/" + filename.trim();
|
||||
}
|
||||
|
||||
function img(filename, height, width, title, alt) {
|
||||
if (!filename) return undefined;
|
||||
if (typeof filename !== "string")
|
||||
throw new Error("not a string: " + filename);
|
||||
return {
|
||||
row: 0,
|
||||
src: img_path(filename),
|
||||
width: width ?? 100,
|
||||
height: height ?? 100,
|
||||
filename: filename,
|
||||
};
|
||||
}
|
||||
|
||||
function sanitize(body) {
|
||||
return nhm
|
||||
.translate(body)
|
||||
.replaceAll(/https?:\/[^"]*?\/public\//g, "content/images/2026/01/")
|
||||
.replaceAll(/[?]itok=[A-Za-z0-9_-]*/g, "");
|
||||
}
|
||||
|
||||
function sanitize_html(body) {
|
||||
return body
|
||||
.replaceAll(/https?:\/[^"]*?\/public\//g, "content/images/2026/01/")
|
||||
.replaceAll(/[?]itok=[^ ]*/g, "");
|
||||
}
|
||||
|
||||
for (const node of data) {
|
||||
let body = sanitize_html(node.body.und[0].safe_value);
|
||||
const id = Number(node.nid);
|
||||
const lexical = {
|
||||
root: {
|
||||
children: [],
|
||||
direction: "ltr",
|
||||
format: "",
|
||||
indent: 0,
|
||||
type: "root",
|
||||
version: 1,
|
||||
},
|
||||
};
|
||||
let author = node.field_original_author?.und?.[0]?.value;
|
||||
let feature_image = node.field_featured_image?.und?.[0]?.filename;
|
||||
|
||||
let tags = [];
|
||||
mk_meta(
|
||||
id,
|
||||
node.field_featured_image?.und?.[0]?.title,
|
||||
node.field_featured_image?.und?.[0]?.alt,
|
||||
);
|
||||
mk_author(id, "6975f732f0a00f00018346d1");
|
||||
|
||||
const category_id = node.field_category?.und?.[0]?.tid;
|
||||
if (category_id) {
|
||||
tags.push(
|
||||
mk_tag(
|
||||
"category-" + category_id,
|
||||
"Category " + category_id,
|
||||
"category-" + category_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
/*
|
||||
lexical.root.children.push({
|
||||
children: [
|
||||
{
|
||||
type: "markdown",
|
||||
version: 1,
|
||||
markdown: sanitize(body),
|
||||
},
|
||||
],
|
||||
direction: "ltr",
|
||||
format: "",
|
||||
indent: 0,
|
||||
type: "paragraph",
|
||||
version: 1,
|
||||
});
|
||||
*/
|
||||
switch (node.type) {
|
||||
case "article":
|
||||
{
|
||||
tags.push(mk_tag("Story", "story", "story"));
|
||||
|
||||
let images = node.field_basic_image_image?.und;
|
||||
if (images) {
|
||||
// console.error(">>", images);
|
||||
/*
|
||||
images = images.map((image) =>
|
||||
img(image.filename, img.height, image.width, img.title, img.alt),
|
||||
);
|
||||
*/
|
||||
|
||||
images = images.map(
|
||||
(image) =>
|
||||
`
|
||||
<div class="kg-gallery-image">
|
||||
<img src="${img_path(image.filename)}" width="${image.width}" height="${image.height}" loading="lazy" alt="${image.alt}" title="${image.title}">
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
|
||||
body += `
|
||||
<hr>
|
||||
<figure class="kg-card kg-gallery-card kg-width-wide">
|
||||
<div class="kg-gallery-container">
|
||||
<div class="kg-gallery-row">
|
||||
${images.join("")}
|
||||
</div>
|
||||
</div>
|
||||
<figcaption></figcaption>
|
||||
</figure>
|
||||
`;
|
||||
/*
|
||||
lexical.root.children.push({
|
||||
type: "gallery",
|
||||
version: 1,
|
||||
images,
|
||||
caption: "",
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "person":
|
||||
{
|
||||
const surname_at_birth = node.field_surname_at_birth?.und?.[0]?.value;
|
||||
const other_surnames = node.field_other_surnames?.und?.[0]?.value;
|
||||
|
||||
tags.push(mk_tag("Person", "person", "person"));
|
||||
if (surname_at_birth) {
|
||||
tags.push(
|
||||
mk_tag(
|
||||
surname_at_birth,
|
||||
"surname-" + convertCase(surname_at_birth, "kebab"),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (other_surnames) {
|
||||
tags.push(
|
||||
mk_tag(
|
||||
other_surnames,
|
||||
"surname-" + convertCase(other_surnames, "kebab"),
|
||||
),
|
||||
);
|
||||
}
|
||||
/*
|
||||
forename_at_birth: node.field_forename_at_birth?.und?.[0]?.value,
|
||||
other_forenames: node.field_other_forenames?.und?.[0]?.value,
|
||||
title: node.field_title?.und?.[0]?.value,
|
||||
date_of_birth: date(node.field_date_of_birth?.und?.[0]?.value),
|
||||
date_of_death: date(node.field_date_of_death?.und?.[0]?.value),
|
||||
parent_of: node.field_parent_of?.und?.[0]?.value,
|
||||
child_of: node.field_child_of?.und?.[0]?.value,
|
||||
partner_of: node.field_partner_of?.und?.[0]?.value,
|
||||
// lifetime: node.field_lifetime?.und?.[0]?.value,
|
||||
*/
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (const tag_id of tags) {
|
||||
ghost_data.data.posts_tags.push({
|
||||
post_id: id,
|
||||
tag_id,
|
||||
});
|
||||
}
|
||||
|
||||
ghost_data.data.posts.push({
|
||||
id,
|
||||
type: "post",
|
||||
title: node.title,
|
||||
slug: node.path.alias.replace(/^.*[/]/, ""),
|
||||
html: body,
|
||||
feature_image: img(feature_image)?.src,
|
||||
created_at: new Date(Number(node.created) * 1000).toISOString(),
|
||||
updated_at: new Date(Number(node.changed) * 1000).toISOString(),
|
||||
status: "draft",
|
||||
});
|
||||
|
||||
// const author = node.field_original_author?.und?.[0]?.value;
|
||||
|
||||
/*
|
||||
{
|
||||
id: "1234", // The post ID, which is refered to in other places in this file
|
||||
title: "My Blog Post Title",
|
||||
slug: "my-blog-post-title",
|
||||
html: "<p>Hello world, this is an article</p>", // You could use `lexical` instead to to represent your content
|
||||
comment_id: "1234-old-cms-post-id", // The ID from the old CMS, which can be output in the theme
|
||||
feature_image: "/content/images/2024/waving.jpg",
|
||||
type: "post", // post | page
|
||||
status: "published", // published | draft
|
||||
visibility: "public", // public | members | paid
|
||||
created_at: "2025-06-30 15:31:36",
|
||||
updated_at: "2025-07-02 08:22:14",
|
||||
published_at: "2025-06-30 15:35:36",
|
||||
custom_excerpt: "My custom excerpt",
|
||||
},*/
|
||||
}
|
||||
|
||||
ghost_data.data.tags = Object.values(tag_index);
|
||||
ghost_data.data.posts_authors = Object.values(author_index);
|
||||
ghost_data.data.posts_meta = Object.values(meta_index);
|
||||
|
||||
console.log(JSON.stringify(ghost_data, null, 2));
|
||||
69
unpack-whenwe-json.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const data = require('./whenwe.json');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { NodeHtmlMarkdown, NodeHtmlMarkdownOptions } = require('node-html-markdown');
|
||||
const nhm = new NodeHtmlMarkdown();
|
||||
|
||||
function toYaml(data, body) {
|
||||
const frontmatter = Object.keys(data).sort().map(key => key+': '+(data[key] ?? '')).join("\n");
|
||||
return frontmatter + "\n---\n" + nhm.translate(body);
|
||||
}
|
||||
|
||||
function date(datestr) {
|
||||
if (datestr) {
|
||||
const [Y,M,D,h,m,s] = datestr.split(/[^0-9]/)
|
||||
return new Date(Date.UTC.call(null, Y,M-1,D,h,m,s)).toISOString();
|
||||
}
|
||||
else
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
data.forEach((node, ix) => {
|
||||
const lang = 'und';
|
||||
const filepath = path.join('out', 'node.type');
|
||||
const body = node.body.und[0].value;
|
||||
const filename = `${node.uuid}.yml`;
|
||||
|
||||
fs.mkdirSync(path.join(__dirname, filepath), { recursive: true });
|
||||
const item = {
|
||||
ix: ix,
|
||||
nid: Number(node.nid),
|
||||
type: node.type,
|
||||
title: node.title,
|
||||
uuid: node.uuid,
|
||||
created: new Date(Number(node.created)*1000).toISOString(),
|
||||
changed: new Date(Number(node.changed)*1000).toISOString(),
|
||||
path: node.path.alias,
|
||||
comment_count: node.comment_count,
|
||||
};
|
||||
switch(node.type) {
|
||||
case 'article':
|
||||
Object.assign(item, {
|
||||
original_author: node.field_original_author?.und?.[0]?.value,
|
||||
featured_image: node.field_featured_image?.und?.[0]?.filename,
|
||||
images: node.field_basic_image_image?.und?.map(item => item.filename),
|
||||
category: node.field_category?.und?.[0]?.tid,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'person':
|
||||
Object.assign(item, {
|
||||
forename_at_birth: node.field_forename_at_birth?.und?.[0]?.value,
|
||||
surname_at_birth: node.field_surname_at_birth?.und?.[0]?.value,
|
||||
other_surnames: node.field_other_surnames?.und?.[0]?.value,
|
||||
other_forenames: node.field_other_forenames?.und?.[0]?.value,
|
||||
title: node.field_title?.und?.[0]?.value,
|
||||
date_of_birth: date(node.field_date_of_birth?.und?.[0]?.value),
|
||||
date_of_death: date(node.field_date_of_death?.und?.[0]?.value),
|
||||
parent_of: node.field_parent_of?.und?.[0]?.value,
|
||||
child_of: node.field_child_of?.und?.[0]?.value,
|
||||
partner_of: node.field_partner_of?.und?.[0]?.value,
|
||||
// lifetime: node.field_lifetime?.und?.[0]?.value,
|
||||
featured_image: node.field_featured_image?.und?.[0]?.filename,
|
||||
});
|
||||
break;
|
||||
}
|
||||
fs.writeFileSync(path.join(filepath, filename), toYaml(item, body));
|
||||
console.log(item.title);
|
||||
});
|
||||
14818
whenwe-tidied.json
1159
zip/content.json
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 829 KiB |
|
Before Width: | Height: | Size: 931 KiB |
|
Before Width: | Height: | Size: 769 KiB |
|
Before Width: | Height: | Size: 813 KiB |
|
Before Width: | Height: | Size: 670 KiB |
|
Before Width: | Height: | Size: 802 KiB |
|
Before Width: | Height: | Size: 693 KiB |
|
Before Width: | Height: | Size: 631 KiB |
|
Before Width: | Height: | Size: 945 KiB |
|
Before Width: | Height: | Size: 840 KiB |
|
Before Width: | Height: | Size: 662 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 581 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 662 KiB |
|
Before Width: | Height: | Size: 908 KiB |
|
Before Width: | Height: | Size: 807 KiB |
|
Before Width: | Height: | Size: 717 KiB |
|
Before Width: | Height: | Size: 911 KiB |
|
Before Width: | Height: | Size: 627 KiB |
|
Before Width: | Height: | Size: 695 KiB |
|
Before Width: | Height: | Size: 625 KiB |
|
Before Width: | Height: | Size: 830 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 943 KiB |
|
Before Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 994 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 699 KiB |
|
Before Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 743 KiB |
|
Before Width: | Height: | Size: 614 KiB |
|
Before Width: | Height: | Size: 628 KiB |
|
Before Width: | Height: | Size: 985 KiB |
|
Before Width: | Height: | Size: 976 KiB |
|
Before Width: | Height: | Size: 846 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 829 KiB |
|
Before Width: | Height: | Size: 792 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 878 KiB |
|
Before Width: | Height: | Size: 741 KiB |
|
Before Width: | Height: | Size: 805 KiB |
|
Before Width: | Height: | Size: 705 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 896 KiB |
|
Before Width: | Height: | Size: 999 KiB |
|
Before Width: | Height: | Size: 826 KiB |
|
Before Width: | Height: | Size: 707 KiB |
|
Before Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 996 KiB |
|
Before Width: | Height: | Size: 673 KiB |
|
Before Width: | Height: | Size: 855 KiB |
|
Before Width: | Height: | Size: 1018 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 980 KiB |
|
Before Width: | Height: | Size: 994 KiB |
|
Before Width: | Height: | Size: 938 KiB |
|
Before Width: | Height: | Size: 757 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 831 KiB |
|
Before Width: | Height: | Size: 563 KiB |
|
Before Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 821 KiB |
|
Before Width: | Height: | Size: 562 KiB |
|
Before Width: | Height: | Size: 431 KiB |
|
Before Width: | Height: | Size: 801 KiB |
|
Before Width: | Height: | Size: 583 KiB |
|
Before Width: | Height: | Size: 758 KiB |
|
Before Width: | Height: | Size: 745 KiB |
|
Before Width: | Height: | Size: 497 KiB |
|
Before Width: | Height: | Size: 776 KiB |
|
Before Width: | Height: | Size: 830 KiB |
|
Before Width: | Height: | Size: 797 KiB |
|
Before Width: | Height: | Size: 663 KiB |
|
Before Width: | Height: | Size: 612 KiB |
|
Before Width: | Height: | Size: 618 KiB |
|
Before Width: | Height: | Size: 941 KiB |
|
Before Width: | Height: | Size: 1001 KiB |
|
Before Width: | Height: | Size: 670 KiB |