Skip to content

Instantly share code, notes, and snippets.

@agamm
Created February 1, 2024 07:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agamm/db7f567ad8fcbdb703f266cf04863d91 to your computer and use it in GitHub Desktop.
Save agamm/db7f567ad8fcbdb703f266cf04863d91 to your computer and use it in GitHub Desktop.
Ghost Editor Link Fixer and ?ref= adder
/*
This script helps fix links that have a prefix whitespace (sometimes when copying from google).
Also it allows to add a ?ref=yoursite to all links in the newsletter issue.
Worked with the latest ghost version up to now (Feb 2024)
*/
const GhostAdminAPI = require("@tryghost/admin-api");
const path = require("path");
const BASE_URL = "unzip.dev"; // your domain
// Your API config
const api = new GhostAdminAPI({
url: `https://${BASE_URL}`,
version: "v5.0",
key: "your-ghost-api-key-here",
});
const POST_ID = "your-post-id-you-want-to-fix";
function addRefToUrls(obj) {
const propertiesToModify = ["text", "calloutText", "url"];
// Helper function to append 'ref=unzip' to a URL using the URL object
const appendRef = (urlString) => {
try {
let url = new URL(urlString);
const queryParams = new URLSearchParams(url.search);
// Check if 'ref' or any 'utm*' parameter already exists
const hasRefOrUtm =
queryParams.has("ref") ||
Array.from(queryParams.keys()).some((key) => key.startsWith("utm"));
if (!hasRefOrUtm) {
queryParams.set("ref", "unzip");
// Reconstruct the URL
url.search = queryParams.toString();
}
return url.toString();
} catch (error) {
console.error("Invalid URL encountered:", urlString);
return urlString; // Return the original string if it's not a valid URL
}
};
// Helper function to modify URLs within a string
const modifyUrlsInString = (str) => {
return str.replace(/(https?:\/\/[^ "']+)/g, (match) => appendRef(match));
};
// Recursive function to traverse the object and modify URLs
const traverseAndModifyUrls = (node) => {
if (node === null || typeof node !== "object") {
return; // Early exit for non-objects
}
Object.keys(node).forEach((key) => {
if (propertiesToModify.includes(key) && typeof node[key] === "string") {
// Modify URLs in the specified properties
node[key] = modifyUrlsInString(node[key]);
} else if (typeof node[key] === "object") {
// Recursively apply for objects
traverseAndModifyUrls(node[key]);
}
});
};
traverseAndModifyUrls(obj);
}
function adjustWhitespaceInLinks(rootObj) {
const adjustChildren = (children) => {
let lastTextIndex = -1;
children.forEach((child, index) => {
if (child.type === "extended-text") {
lastTextIndex = index; // Update the last text index for non-link nodes
}
if (
child.type === "link" &&
child.children &&
child.children.length > 0
) {
// Scenario 1: The first child of a link is whitespace-only extended-text
if (
child.children[0].type === "extended-text" &&
!child.children[0].text.trim()
) {
if (lastTextIndex !== -1) {
children[lastTextIndex].text += child.children[0].text;
}
child.children.shift();
}
// Scenario 2: The link text starts with a whitespace
else if (
child.children[0].type === "extended-text" &&
child.children[0].text.startsWith(" ") &&
lastTextIndex !== -1
) {
children[lastTextIndex].text += " ";
child.children[0].text = child.children[0].text.trimStart();
}
}
// Recursively adjust any further nested children arrays
if (child.children && Array.isArray(child.children)) {
adjustChildren(child.children);
}
});
};
if (rootObj && rootObj.root && Array.isArray(rootObj.root.children)) {
adjustChildren(rootObj.root.children);
}
}
(async () => {
const res = await api.posts.read({ id: POST_ID });
let lexical = JSON.parse(res.lexical);
addRefToUrls(lexical);
adjustWhitespaceInLinks(lexical);
// console.log(JSON.stringify(lexical.root.children[13], null, 2));
// return;
lexical = JSON.stringify(lexical);
await api.posts.edit({
id: POST_ID,
lexical,
updated_at: res.updated_at,
});
console.log("Updated links successfully.");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment