Skip to content

Instantly share code, notes, and snippets.

@ninapavlich
Last active September 17, 2019 16:16
Show Gist options
  • Save ninapavlich/8c680361667f17717951f207f16a553c to your computer and use it in GitHub Desktop.
Save ninapavlich/8c680361667f17717951f207f16a553c to your computer and use it in GitHub Desktop.
Edit Parenting Item
javascript: (async function() {
var bookmarkletVersion = "1.0.0";
var apiRoot = "https://parenting.nytimes.com/api/v1";
var cmsRoot = "https://nyt-parentingcms-prd.appspot.com";
console.log("Running Parenting Edit Bookmarlet v" + bookmarkletVersion);
/*
Generic function for requesting URLs
*/
async function makeRequest(method, url) {
console.log(method + ":" + url);
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function() {
if (this.status >= 200 && this.status < 300) {
resolve({
success: true,
status: this.status,
statusText: xhr.statusText,
data: JSON.parse(xhr.response)
});
} else {
resolve({
success: false,
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function() {
resolve({
success: false,
status: this.status,
statusText: xhr.statusText
});
};
xhr.send();
});
}
/*
Given a URL path, determine the corresponding admin URL
*/
async function getAdminURL(path) {
/*const admin_type_paths = {
ARTICLE: "/admin/scoop-content/",
ESSAY: "/admin/scoop-content/",
MILESTONE: "/admin/scoop-content/",
LIST: "/admin/scoop-content/",
GUIDE: "/admin/guides/",
CHILD: "/admin/topics/",
PARENT: "/admin/topics/",
STAGE: "/admin/topics/",
SERIES: "/admin/collections/"
};*/
let pathPieces = path.replace(/^\/|\/$/g, "").split("/");
let testAPIURL;
if (path.indexOf("archive") >= 0 && pathPieces.length === 3) {
/* its a topic */
testAPIURL = "/topics/" + pathPieces[1];
} else if (pathPieces.length == 1) {
/* It might be a topic... if not it's a page */
testAPIURL = "/topics/" + pathPieces[0];
} else if (path.indexOf("series") >= 0 && pathPieces.length === 2) {
/* its a collection - TODO -- make this not hardcoded */
testAPIURL = "/collections/" + pathPieces[1];
} else if (pathPieces.length == 2) {
/* It's either a guide or a story or a collection */
testAPIURL = "/stories/" + pathPieces[1];
}
let responseData = await makeRequest("GET", apiRoot + testAPIURL);
if (responseData.success) {
if (responseData.data.data.topic) {
return cmsRoot + "/admin/topics/" + responseData.data.data.topic.id;
} else if (responseData.data.data.story) {
if (responseData.data.data.story.type === "GUIDE") {
return cmsRoot + "/admin/guides/" + responseData.data.data.story.id;
} else {
return (
cmsRoot + "/admin/scoop-content/" + responseData.data.data.story.id
);
}
} else if (responseData.data.data.collection) {
return (
cmsRoot + "/admin/collections/" + responseData.data.data.collection.id
);
}
} else {
return null;
}
}
/*
Given a URL, return the path portion
*/
function getPath(url) {
if (url === null) {
return null;
}
var link = document.createElement("a");
link.href = url;
return link.pathname;
}
/*
Given a clicked target, work up the element tree until we find an item
with an href
*/
function getLinkFromTarget(element) {
if (element.href) {
return element.href;
} else if (element.parentElement) {
return getLinkFromTarget(element.parentElement);
}
return null;
}
/*
If the window has already been opened, switch to that tab.
Otherwise open a new window with the admin link
*/
function openAdminWindow(adminLink) {
if (
!window.opened_windows[adminLink] ||
window.opened_windows[adminLink].closed
) {
var win = window.open(adminLink, "_blank");
window.opened_windows[adminLink] = win;
}
window.opened_windows[adminLink].focus();
}
/*
If we are in edit mode:
- get the link path
- determine the corresponding admin link
- open or focus that link
*/
async function handleLinkClick(event) {
if (!document.body.classList.contains("editing")) {
return;
}
const path = getPath(getLinkFromTarget(event.target));
if (path) {
event.preventDefault();
let adminLink = await getAdminURL(path);
if (adminLink) {
openAdminWindow(adminLink);
} else {
alert("Couldn't find edit link for " + path);
}
}
}
/*
Turn On Edit Mode:
Add "editing" class to body
Get All Links on the Page:
- If a link is in the sitemap:
--- add the class "editable"
--- add a click listener (only do that once though)
*/
function turnOnEditMode() {
console.log("-- Turn On Edit Bookmarlet");
document.body.classList.add("editing");
for (var i = 0; i < document.links.length; i++) {
var link = document.links[i];
if (window.sitemap_links.indexOf(link.pathname)) {
link.classList.add("editable");
if (!link.classList.contains("editable-inited")) {
link.classList.add("editable-inited");
link.addEventListener("click", handleLinkClick);
}
}
}
var pageLink = document.getElementById("editPageLink");
if (!pageLink) {
pageLink = document.createElement("a");
pageLink.id = "editPageLink";
pageLink.innerHTML = "EDIT<br />THIS PAGE";
pageLink.addEventListener("click", handleLinkClick);
document.body.appendChild(pageLink);
}
pageLink.setAttribute("href", window.location.href);
}
/*
Turn Off Edit Mode:
Remove "editing" class from body
*/
function turnOffEditMode() {
console.log("-- Turn Off Edit Bookmarlet");
document.body.classList.remove("editing");
}
if (!window.edit_bookmarklet_inited) {
/*
If not inited, INIT:
- Load sitemap
- Add dynamic styles to page
*/
const sitemapResponse = await makeRequest("GET", apiRoot + "/sitemap");
console.log("-- Sitemap loaded: " + sitemapResponse.success);
if (sitemapResponse.success) {
window.sitemap_links = sitemapResponse.data.data.map(function callback(
currentValue
) {
return getPath(currentValue.loc);
});
} else {
window.sitemap_links = [];
}
var style = document.createElement("style");
var styleContent = document.createTextNode(
`
.editing:before{
content: "EDIT\\AMODE ON";
white-space: pre-wrap;
text-align: center;
position: fixed;
top: 0;
left: 0;
font-family: monospace;
z-index: 10000;
background: #d35f0ae0;
color: #fff;
padding: 1em;
padding: 4em 4em 1em 4em;
transform: rotate(-45deg) translate(-21px, -69px);
}
.editing #editPageLink{
position: fixed;
top: 0;
right: 0;
font-family: monospace;
z-index: 10000;
background: #d35f0ae0;
color: #fff;
padding: 1em;
text-align: center;
padding: 5em 5em 1em 5em;
transform: rotate(45deg) translate(28px, -90px);
text-decoration:none;
display:none;
}
.editing #editPageLink{
display:block;
}
.editing .editable:hover{
cursor: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='32px' height='32px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3E %3Cpath d='M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z'/%3E %3C/svg%3E") 0 50, auto;
}
`
);
style.appendChild(styleContent);
var caput = document.getElementsByTagName("head");
caput[0].appendChild(style);
window.opened_windows = {};
window.edit_bookmarklet_inited = true;
console.log("-- Edit Bookmarlet inited...");
}
/*
Toggle Edit Mode:
- Determine if edit mode is on or off and toggle it
*/
if (document.body.classList.contains("editing")) {
turnOffEditMode();
} else {
turnOnEditMode();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment