initial
This commit is contained in:
commit
3c7c5971c2
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
yarn.lock
|
||||
*.log
|
||||
dist/
|
||||
.env
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "mobiledoc-wp-renderer"]
|
||||
path = mobiledoc-wp-renderer
|
||||
url = git@github.com:bij1/mobiledoc-wp-renderer.git
|
|
@ -0,0 +1,26 @@
|
|||
# wingspress
|
||||
|
||||
Tool for migrating Wings content to WordPress.
|
||||
|
||||
## `yarn fetch`
|
||||
|
||||
Fetches all content from Wings and saves it to `content.json`,
|
||||
requires the following environment variables.
|
||||
|
||||
```
|
||||
WINGS_ENDPOINT
|
||||
WINGS_PROJECT
|
||||
WINGS_APP_KEY
|
||||
```
|
||||
|
||||
## `yarn migrate`
|
||||
|
||||
Converts all articles and pages that do not exist in WordPress yet and
|
||||
publishes them along with their media, requires the following
|
||||
environment variables.
|
||||
|
||||
```
|
||||
WP_ENDPOINT
|
||||
WP_USER
|
||||
WP_PASSWORD
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
require('browser-env')();
|
||||
|
||||
window.matchMedia = function () {
|
||||
return {
|
||||
matches: false,
|
||||
addListener: function () {},
|
||||
removeListener: function () {},
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d4176bfadad693a3155a021ad521891b91dde4e1
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "wingspress",
|
||||
"scripts": {
|
||||
"fetch": "babel-node src/fetch",
|
||||
"migrate": "babel-node -r ./browser-env src/migrate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wordpress/block-library": "^2.27.2",
|
||||
"@wordpress/blocks": "^6.25.1",
|
||||
"browser-env": "^3.3.0",
|
||||
"got": "^11.8.1",
|
||||
"mobiledoc-wp-renderer": "./mobiledoc-wp-renderer",
|
||||
"simple-dom": "^1.4.0",
|
||||
"wpapi": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
import { createBlock as createWPBlock,
|
||||
registerBlockType } from '@wordpress/blocks';
|
||||
import { registerCoreBlocks } from '@wordpress/block-library';
|
||||
import { Document, HTMLSerializer } from 'simple-dom';
|
||||
import Renderer from 'mobiledoc-wp-renderer';
|
||||
import { RENDER_TYPE } from 'mobiledoc-wp-renderer';
|
||||
|
||||
registerCoreBlocks()
|
||||
|
||||
registerBlockType("acf/cta", {
|
||||
title: 'CTA',
|
||||
attributes: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
data: { type: 'object' }
|
||||
}
|
||||
})
|
||||
|
||||
registerBlockType("acf/insight", {
|
||||
title: 'Insight',
|
||||
attributes: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
data: { type: 'object' }
|
||||
}
|
||||
})
|
||||
|
||||
const createMissing = ({payload, env: {name}}) => {
|
||||
console.error(`TODO block ${name}`)
|
||||
console.error(JSON.stringify(payload, null, 2))
|
||||
return createBlock("core/missing", {
|
||||
originalName: name,
|
||||
originalContent: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
|
||||
export const cards = [
|
||||
{
|
||||
name: 'ImageCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
src,
|
||||
url,
|
||||
caption,
|
||||
alt
|
||||
}}) => createBlock("core/image", {
|
||||
url: src,
|
||||
alt: alt,
|
||||
caption: caption,
|
||||
href: url
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'QuoteCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
text,
|
||||
source,
|
||||
sourceUrl,
|
||||
type
|
||||
}}) => createBlock(type === 1 ? "core/pullquote" : "core/quote", {
|
||||
value: `<p>${text}</p>`,
|
||||
citation: sourceUrl ?
|
||||
`<a href="${sourceUrl}">${source}</a>` : source
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'InsightCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
text,
|
||||
}}) => createBlock("acf/insight", {
|
||||
id: 'generated',
|
||||
name: 'acf/insight',
|
||||
data: {
|
||||
titel: text,
|
||||
achtergrond: 'zwart',
|
||||
textsize: 'groot'
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'EmbedCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
src
|
||||
}}) => createBlock("core/embed", {
|
||||
align: "wide",
|
||||
url: src
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'CTACard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
title,
|
||||
actionText,
|
||||
text,
|
||||
actionUrl,
|
||||
type
|
||||
}}) => createBlock("acf/cta", {
|
||||
id: 'generated',
|
||||
name: 'acf/cta',
|
||||
data: {
|
||||
cta_title: title,
|
||||
cta_text: text,
|
||||
cta_link: {
|
||||
title: actionText,
|
||||
url: actionUrl
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'CollectionCard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO is commonly used
|
||||
},
|
||||
{
|
||||
name: 'CampaignCard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO uses external API
|
||||
},
|
||||
{
|
||||
name: 'NodesCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {
|
||||
type
|
||||
}}) => createBlock("core/latest-posts", {
|
||||
// TODO configure
|
||||
})
|
||||
},
|
||||
{
|
||||
name: 'DataCard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO? might not be used
|
||||
},
|
||||
{
|
||||
name: 'TestimonialCard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO? might not be used
|
||||
},
|
||||
{
|
||||
name: 'ChapterCard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO? might not be used
|
||||
},
|
||||
{
|
||||
name: 'QACard',
|
||||
type: RENDER_TYPE,
|
||||
render: createMissing // TODO is commonly used
|
||||
},
|
||||
{
|
||||
name: 'TextCard',
|
||||
type: RENDER_TYPE,
|
||||
render: ({payload: {content}, env: {dom, serializer}}) => {
|
||||
var rendered = renderer.render(JSON.parse(content));
|
||||
return createBlock("core/group", {}, rendered.result)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const createBlock = (name, attributes, inner) => {
|
||||
const block = createWPBlock(name, attributes, inner);
|
||||
if (block.name === "core/image")
|
||||
media.push(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
export const media = []
|
||||
|
||||
export const renderer = new Renderer({
|
||||
cards: cards,
|
||||
dom: new Document(),
|
||||
serializer: new HTMLSerializer([]),
|
||||
createBlock: createBlock
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import got from "got";
|
||||
import fs from "fs";
|
||||
|
||||
import query from "./query";
|
||||
|
||||
got.post(process.env.WINGS_ENDPOINT || 'https://api.wings.dev', {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + process.env.WINGS_APP_KEY,
|
||||
"X-Wings-Project": process.env.WINGS_PROJECT
|
||||
},
|
||||
json: {query}
|
||||
}).json().then((data) => {
|
||||
fs.writeFileSync("content.json", JSON.stringify(data, null, 2));
|
||||
console.log("fetched content");
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import WPAPI from 'wpapi';
|
||||
import { serialize } from '@wordpress/blocks';
|
||||
import got from 'got';
|
||||
import url from "url";
|
||||
import path from "path";
|
||||
|
||||
import { renderer, media } from './blocks';
|
||||
import { data } from '../content';
|
||||
|
||||
const wp = new WPAPI({
|
||||
endpoint: process.env.WP_ENDPOINT,
|
||||
username: process.env.WP_USER,
|
||||
password: process.env.WP_PASSWORD
|
||||
});
|
||||
|
||||
const migrate = async (node, resource) => {
|
||||
if (!node.content) return
|
||||
|
||||
const [existing] = await resource().slug(node.slug);
|
||||
if (existing) {
|
||||
console.log(`skipping "${node.slug}"`)
|
||||
return
|
||||
}
|
||||
|
||||
const mobiledoc = JSON.parse(node.content)
|
||||
const rendered = renderer.render(mobiledoc);
|
||||
const mediaIds = await Promise.all(media.map(async (block) => {
|
||||
try {
|
||||
const { id, source_url } = await wp.media()
|
||||
.file(... await download(block.attributes.href))
|
||||
.create({
|
||||
alt_text: block.attributes.alt || "",
|
||||
caption: block.attributes.caption || ""
|
||||
});
|
||||
block.attributes.id = id
|
||||
block.attributes.url = source_url
|
||||
block.attributes.href = source_url;
|
||||
return id;
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`failed to upload "${block.attributes.href}"`)
|
||||
}
|
||||
}));
|
||||
media.length = 0;
|
||||
|
||||
let featured = null;
|
||||
const { url, alt, caption } = node.image ||
|
||||
node.featured.image || {}
|
||||
try {
|
||||
if (url) {
|
||||
featured = await wp.media()
|
||||
.file(... await download(url))
|
||||
.create({
|
||||
alt_text: alt || "",
|
||||
caption: caption || ""
|
||||
});
|
||||
mediaIds.push(featured.id);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`failed to upload featured ${url}`)
|
||||
}
|
||||
|
||||
console.log(`creating "${node.slug}"`);
|
||||
const { id } = await resource().create({
|
||||
title: node.title,
|
||||
excerpt: node.featured.description,
|
||||
slug: node.slug,
|
||||
featured_media: featured && featured.id,
|
||||
content: serialize(rendered.result),
|
||||
date_gmt: new Date(node.publishedAt),
|
||||
status: 'publish'
|
||||
})
|
||||
|
||||
await Promise.all(mediaIds.map(async (mediaId) => {
|
||||
if (mediaId) wp.media().id(mediaId).update({
|
||||
post: id,
|
||||
date_gmt: new Date(node.publishedAt),
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
const download = async (location) => {
|
||||
console.log(`downloading "${location}"`);
|
||||
const result = await got.get(location).buffer();
|
||||
const { pathname } = url.parse(location)
|
||||
return [result, decodeURIComponent(path.basename(pathname))];
|
||||
}
|
||||
|
||||
data.articles.edges.reduce(async (previous, {node}) => {
|
||||
await previous;
|
||||
return migrate(node, () => wp.posts());
|
||||
}, Promise.resolve());
|
||||
|
||||
data.pages.edges.reduce(async (previous, {node}) => {
|
||||
await previous;
|
||||
return migrate(node, () => wp.pages());
|
||||
}, Promise.resolve());
|
|
@ -0,0 +1,234 @@
|
|||
export default `
|
||||
fragment NodeFields on Node {
|
||||
id
|
||||
title
|
||||
resourceType
|
||||
slug
|
||||
featured {
|
||||
title
|
||||
description
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
locale {
|
||||
id
|
||||
name
|
||||
primary
|
||||
}
|
||||
publishedAt
|
||||
image {
|
||||
id
|
||||
name
|
||||
caption
|
||||
alt
|
||||
key
|
||||
url
|
||||
}
|
||||
meta {
|
||||
key
|
||||
value
|
||||
}
|
||||
data {
|
||||
key
|
||||
data
|
||||
}
|
||||
menu {
|
||||
id
|
||||
name
|
||||
items {
|
||||
text
|
||||
url
|
||||
items {
|
||||
text
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
status
|
||||
nodeType
|
||||
platforms {
|
||||
search {
|
||||
title
|
||||
description
|
||||
}
|
||||
facebook {
|
||||
title
|
||||
description
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
twitter {
|
||||
title
|
||||
description
|
||||
image {
|
||||
url
|
||||
}
|
||||
}
|
||||
whatsapp {
|
||||
text
|
||||
}
|
||||
meta {
|
||||
tag
|
||||
attributes {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment CampaignFields on Campaign {
|
||||
intro
|
||||
description
|
||||
submissionSchema
|
||||
settings {
|
||||
legal {
|
||||
terms {
|
||||
url
|
||||
}
|
||||
privacyPolicy {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
articles: entries(selector: { typeId: { eq: "article" } }, first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
content
|
||||
type {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pages: entries(selector: { typeId: { eq: "page" } }, first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
content
|
||||
type {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events(first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
...CampaignFields
|
||||
schedule {
|
||||
start
|
||||
end
|
||||
}
|
||||
location {
|
||||
name
|
||||
street
|
||||
city
|
||||
zip
|
||||
country
|
||||
}
|
||||
fee {
|
||||
amount {
|
||||
amount
|
||||
currency {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
attendeeCount
|
||||
}
|
||||
}
|
||||
}
|
||||
signups(first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
...CampaignFields
|
||||
}
|
||||
}
|
||||
}
|
||||
petitions(first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
...CampaignFields
|
||||
signatureCount
|
||||
signatureGoal
|
||||
}
|
||||
}
|
||||
}
|
||||
fundraisers(first: 0) {
|
||||
edges {
|
||||
node {
|
||||
...NodeFields
|
||||
...CampaignFields
|
||||
target {
|
||||
amount
|
||||
currency {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
}
|
||||
}
|
||||
amounts {
|
||||
options {
|
||||
amount {
|
||||
amount
|
||||
currency {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
raised {
|
||||
amount
|
||||
currency {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
}
|
||||
}
|
||||
paymentMethods {
|
||||
id
|
||||
title
|
||||
icons {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currentApp {
|
||||
... on WebApp {
|
||||
home {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
menu {
|
||||
id
|
||||
name
|
||||
items {
|
||||
text
|
||||
url
|
||||
items {
|
||||
text
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
Loading…
Reference in New Issue