Browse Source

lazygit

main
Gregory Leeman 1 week ago
parent
commit
bc59406e6c
  1. 1324
      workflowy-helper/archive.css
  2. 640
      workflowy-helper/archive.js
  3. 19
      workflowy-helper/clip.js
  4. 2
      workflowy-helper/diary.opml
  5. 67
      workflowy-helper/diary.py
  6. 8
      workflowy-helper/main.js
  7. 4
      workflowy-helper/main.py
  8. 19
      workflowy-helper/popup.js
  9. 5
      workflowy-helper/requirements.txt
  10. 18
      workflowy-helper/setup.py
  11. 36
      workflowy-helper/storage.py
  12. 19
      workflowy-helper/style.css
  13. 410
      workflowy-helper/temp.js
  14. 0
      workflowy-helper/wf/__init__.py
  15. 17
      workflowy-helper/wf/archive/constants.py
  16. 32
      workflowy-helper/wf/archive/helpers.py
  17. 0
      workflowy-helper/wf/archive/main.py
  18. 36
      workflowy-helper/wf/archive/theme.py
  19. 9
      workflowy-helper/wf/console.py
  20. 70
      workflowy-helper/wf/main.py
  21. 932
      workflowy-helper/wf/script.py
  22. 32
      workflowy-helper/workflowy_calendar.py
  23. 294
      workflowy-helper/~main.js
  24. 1297
      workflowy-helper/~style.css
  25. 346
      workflowy-helper/~~style.css

1324
workflowy-helper/archive.css

File diff suppressed because it is too large

640
workflowy-helper/archive.js

@ -1,640 +0,0 @@
function g(){return((1+Math.random())*65536|0).toString(16).substring(1)}
function generateUUID(){return g()+g()+"-"+g()+"-"+g()+"-"+g()+"-"+g()+g()+g()}
function createTweetIframe(node, url) { // {{{
var existing = node.querySelectorAll("iframe");
if (existing.length === 0) {
var iframe = document.createElement('iframe');
iframe.setAttribute('border', '0');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('height', '250');
iframe.setAttribute('width', '300');
iframe.setAttribute('id', "tweet_" + generateUUID());
iframe.setAttribute('src', 'https://twitframe.com/show?url=' + encodeURIComponent(url) + '&theme=dark')
iframe.addEventListener('load', function() {
this.contentWindow.postMessage({ element: this.id, query: "height" }, "https://twitframe.com");
});
node.appendChild(iframe);
}
}
// }}}
function describeTimeElementDate(element) { // {{{
if (!(element instanceof HTMLTimeElement)) {
return "unknown";
}
const startYear = element.getAttribute('startyear');
const startMonth = element.getAttribute('startmonth');
const startDay = element.getAttribute('startday');
if (!startYear || !startMonth || !startDay || isNaN(startYear) || isNaN(startMonth) || isNaN(startDay)) {
return 'Invalid date attributes on the <time> element';
}
const timeElementDate = new Date(startYear, startMonth - 1, startDay);
timeElementDate.setHours(0, 0, 0, 0);
const today = new Date();
today.setHours(0, 0, 0, 0);
// const startOfWeek = new Date(today);
// startOfWeek.setDate(today.getDate() - today.getDay()); // set to the nearest past Sunday
// const endOfWeek = new Date(startOfWeek);
// endOfWeek.setDate(startOfWeek.getDate() + 6); // set to the next Saturday
// const startOfLastWeek = new Date(startOfWeek);
// startOfLastWeek.setDate(startOfWeek.getDate() - 7);
// const endOfNextWeek = new Date(startOfWeek);
// endOfNextWeek.setDate(endOfWeek.getDate() + 7);
const dayDifference = Math.round((timeElementDate - today) / (1000 * 60 * 60 * 24));
const daysOfWeek = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const dayOfWeek = daysOfWeek[timeElementDate.getDay()];
const monthsOfYear = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const month = monthsOfYear[timeElementDate.getMonth()];
var ret = [dayOfWeek, month];
if (dayDifference === 0) {
ret.push('today');
} else if (dayDifference === 1) {
ret.push('tomorrow');
} else if (dayDifference === -1) {
ret.push('yesterday');
} else {
if (dayDifference < 0) {
if (dayDifference > -8) {
ret.push('last');
} else {
ret.push('older');
}
} else {
if (dayDifference < 8) {
ret.push('next');
} else {
ret.push('later');
}
}
}
// if (timeElementDate >= startOfWeek && timeElementDate <= endOfWeek) {
// ret.push('this');
// } else if (timeElementDate > endOfWeek && timeElementDate <= endOfNextWeek) {
// ret.push('next');
// } else if (timeElementDate >= startOfLastWeek && timeElementDate < startOfWeek ) {
// ret.push('last');
// } else if (timeElementDate < startOfLastWeek) {
// ret.push('older');
// } else if (timeElementDate > endOfNextWeek) {
// ret.push('later');
// }
return ret;
}
// }}}
function getTime(element) { // {{{
if (!(element instanceof HTMLTimeElement)) {
return "unknown";
}
var startHour = element.getAttribute('starthour');
var startMinute = element.getAttribute('startminute');
const startDay = element.getAttribute('startday'); // If you don't need the day, you can remove this line
if (!startDay || isNaN(startDay)) {
return 'Invalid date attributes on the <time> element';
}
if (!startHour || isNaN(startHour)) {
startHour = 0;
} else {
startHour = parseInt(startHour);
}
if (!startMinute || isNaN(startMinute)) {
startMinute = 0;
} else {
startMinute = parseInt(startMinute);
}
if (startMinute === 0 && startHour === 0) {
return "";
}
// Formatting the hour and minute to ensure two digits
startHour = startHour.toString().padStart(2, '0');
startMinute = startMinute.toString().padStart(2, '0');
return " " + `${startHour}:${startMinute}`;
}
// }}}
function createYoutubeIframe(node, id) { // {{{
var existing = node.querySelectorAll("iframe");
if (existing.length === 0) {
var iframe = document.createElement('iframe');
iframe.setAttribute('width', '300');
iframe.setAttribute('height', '168');
iframe.setAttribute('src', 'https://www.youtube.com/embed/' + id);
iframe.setAttribute('title', 'YouTube video player');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;');
iframe.setAttribute('allowfullscreen', '');
// iframe.addEventListener('load', function() {
// this.contentWindow.postMessage({ element: this.id, query: "height" }, "https://twitframe.com");
// });
node.appendChild(iframe);
}
}
// }}}
function updateProject(projectNode) { // {{{
projectNode.spellcheck = false;
removeClassesStartingWith(projectNode, "linked");
removeClassesStartingWith(projectNode, "tagged");
removeClassesStartingWith(projectNode, "backlinked");
removeClassesStartingWith(projectNode, "timed");
removeClassesStartingWith(projectNode, "empt");
removeClassesStartingWith(projectNode, "workflow");
const breadcrumbNodes = [];
const names = [];
const notes = [];
for (let i = 0; i < projectNode.children.length; i++) {
const child = projectNode.children[i];
if (child.classList.contains('name')) {
names.push(child);
}
if (child.classList.contains('notes')) {
notes.push(child);
}
if (child.classList.contains('_1zok2')) {
breadcrumbNodes.push(child.querySelector('.breadcrumbs'));
}
}
var markdownImageRegex = /\!\[.*\]\((.+)\)/;
var twitterUrlRegex = /https?:\/\/twitter\.com\/([A-Za-z0-9_]+)\/status\/(\d+)/g;
var youtubeUrlRegex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/;
var spotifyUrlRegex = /(?:https?:\/\/)?open\.spotify\.com\/album\/([a-zA-Z0-9_-]+)/;
[notes, names].forEach(function(n) {
if (n.length > 0) {
const text = safeGetContent(n[0], ".content > .innerContentContainer");
var matcher = text.match(markdownImageRegex);
if (matcher !== null) {
var imgSrc = matcher[1];
createImageNodeAfterNode(projectNode, imgSrc);
}
// var matcher = text.match(twitterUrlRegex);
// if (matcher !== null) {
// var url = matcher[0];
// createTweetIframe(projectNode, url);
// }
var matcher = text.match(youtubeUrlRegex);
if (matcher !== null) {
var id = matcher[1];
createYoutubeIframe(projectNode, id);
}
}
});
if (names.length > 0) {
const name = names[0];
const tags = name.querySelectorAll('.contentTag');
const times = name.querySelectorAll('time');
var content = name.querySelector('.content');
if (content.childNodes[0].nodeType === Node.TEXT_NODE) {
projectNode.classList.add('empty');
}
tags.forEach(tag => {
var tagText = safeGetContent(tag, ".contentTagText").trim();
if (tagText !== "") {
var className = "tagged-" + tagText;
projectNode.classList.add("tagged");
projectNode.classList.add(className);
}
});
times.forEach(time => {
var relatives = describeTimeElementDate(time);
// var existing = time.querySelectorAll("span.timetime");
// if (existing.length === 0) {
// var timetime = getTime(time);
// var timespan = document.createElement('span');
// timespan.classList.add("timetime");
// timespan.innerHTML = timetime;
// time.appendChild(timespan);
// }
projectNode.classList.add("timed");
relatives.forEach(relative => {
projectNode.classList.add("timed-" + relative);
});
});
const links = name.querySelectorAll('.contentLink');
links.forEach(link => {
link.spellcheck = false;
const nonLetterRegex = /[^a-zA-Z]/g;
var linkFull = link.innerText.trim().replace(nonLetterRegex, '');
if (linkFull.startsWith("x")) {
var className = "linked-" + linkFull;
var linkClassName = "link-" + linkFull;
projectNode.classList.add("linked");
projectNode.classList.add(className);
link.classList.add("link");
link.classList.add(linkClassName);
}
if ( linkFull.startsWith("z")) {
link.classList.add("people");
}
if ( linkFull.startsWith("q")) {
var className = "linked-" + linkFull;
projectNode.classList.add("contexted");
projectNode.classList.add(className);
link.classList.add("context");
}
});
}
if (breadcrumbNodes.length > 0) {
const links = breadcrumbNodes[0].querySelectorAll('a');
for (let i = 0; i < links.length; i++) {
var link = links[i];
if (link.innerHTML == "🌎 Life") {
if (link !== undefined) {
var hotspot = links[i + 1].innerText.split(' ')[1];
var className = "backlinked-" + hotspot;
projectNode.classList.add("backlinked");
projectNode.classList.add(className);
break;
}
}
}
}
}
// }}}
function update() { // {{{
const projectNodes = document.querySelectorAll('.project');
projectNodes.forEach(projectNode => {
updateProject(projectNode);
});
const mainContent = document.querySelector("#app");
const observer = new MutationObserver(function(mutations) {
for (const mutation of mutations) {
var nodes = null;
if (mutation.addedNodes.length > 0) {
nodes = mutation.addedNodes;
} else if (mutation.removedNodes.length > 0) {
nodes = mutation.addedNodes;
}
if ( nodes !== null ) {
//var node = nodes.item(0);
var node = mutation.target;
if ( node !== null ) {
var projectNode = node.closest('.project');
if ( projectNode !== null && projectNode !== node ) {
updateProject(projectNode);
}
}
}
if (mutation.type === "childList") {
//pass
}
}
});
observer.observe(mainContent, { childList: true, subtree: true });
}
// }}}
window.addEventListener('message', function(e) {
if (e.origin !== "https://twitframe.com") return;
var data = e.data;
if (data.height && data.element.startsWith('tweet_')) {
var iframe = document.getElementById(data.element);
if (iframe) {
iframe.style.height = parseInt(data.height) + "px";
}
}
});
function createTag(innerContentContainer, tagType, defaultText) { // {{{
var timeElement = innerContentContainer.querySelector('time');
if (timeElement !== null) {
const previousSibling = timeElement.previousSibling;
if (previousSibling && previousSibling.nodeType === 3) { // If it's a text node
previousSibling.textContent = previousSibling.textContent.replace(/\s+$/, '');
}
timeElement.remove();
innerContentContainer.appendChild(document.createTextNode(' '));
}
const tagContainer = document.createElement('span');
tagContainer.className = 'contentTag explosive';
tagContainer.setAttribute('title', 'Filter #' + tagType);
tagContainer.setAttribute('data-val', '#' + tagType);
const tagText = document.createElement('span');
tagText.className = 'contentTagText';
tagText.innerText = defaultText || tagType;
const tagNub = document.createElement('span');
tagNub.className = 'contentTagNub';
tagContainer.appendChild(document.createTextNode('#'));
tagContainer.appendChild(tagText);
tagContainer.appendChild(tagNub);
addSpaceIfNeeded(innerContentContainer);
innerContentContainer.appendChild(tagContainer);
if (timeElement !== null) {
addSpaceIfNeeded(innerContentContainer);
innerContentContainer.appendChild(timeElement);
}
}
// }}}
function removeTag(tagContainer) { // {{{
if (tagContainer) {
const previousSibling = tagContainer.previousSibling;
if (previousSibling && previousSibling.nodeType === 3) { // If it's a text node
// Remove the space before the tag if it exists
previousSibling.textContent = previousSibling.textContent.replace(/\s+$/, '');
}
tagContainer.remove();
}
}
// }}}
function addSpaceIfNeeded(container) { // {{{
if (!/\s$/.test(container.textContent)) {
container.appendChild(document.createTextNode(' '));
}
}
// }}}
function updateTimes(parentElements, step) { // {{{
for (let i = 0; i < parentElements.length; i++) {
var parentElement = parentElements[i];
var innerContentContainer = parentElement.querySelector('.innerContentContainer')
var timeElement = innerContentContainer.querySelector('time');
var projectNode = parentElement.closest('.project');
if (step === -2) {
if (timeElement !== null) {
const previousSibling = timeElement.previousSibling;
if (previousSibling && previousSibling.nodeType === 3) { // If it's a text node
previousSibling.textContent = previousSibling.textContent.replace(/\s+$/, '');
}
timeElement.remove();
updateProject(projectNode);
}
continue;
}
if ( timeElement === null ) {
// Create a new time element with today's date
const today = new Date();
timeElement = document.createElement('time');
timeElement.setAttribute('startyear', today.getFullYear());
timeElement.setAttribute('startmonth', today.getMonth() + 1); // Convert to 1-indexed
timeElement.setAttribute('startday', today.getDate());
timeElement.style.textDecoration = "underline rgb(220, 224, 226)";
const options = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
const formattedDate = today.toLocaleDateString('en-US', options);
timeElement.textContent = formattedDate;
addSpaceIfNeeded(innerContentContainer);
innerContentContainer.appendChild(timeElement);
updateProject(projectNode);
continue;
}
if ( step === 0 ) {
var date = new Date();
} else {
var date = new Date(
parseInt(timeElement.getAttribute('startyear'), 10),
parseInt(timeElement.getAttribute('startmonth'), 10) - 1, // Months are 0-indexed in JavaScript
parseInt(timeElement.getAttribute('startday'), 10)
);
date.setDate(date.getDate() + step);
}
const options = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
const formattedDate = date.toLocaleDateString('en-US', options);
timeElement.textContent = formattedDate;
timeElement.setAttribute('startyear', date.getFullYear());
timeElement.setAttribute('startmonth', date.getMonth() + 1); // Convert back to 1-indexed
timeElement.setAttribute('startday', date.getDate());
updateProject(projectNode);
}
}
// }}}
function toggleTag(parentElements, tag) { // {{{
for (let i = 0; i < parentElements.length; i++) {
var parentElement = parentElements[i];
var innerContentContainer = parentElement.querySelector('.innerContentContainer');
if (!innerContentContainer) return;
var projectNode = parentElement.closest('.project');
var tagContainer = innerContentContainer.querySelector('.contentTag[data-val="#' + tag + '"]');
if (tagContainer) {
removeTag(tagContainer);
} else {
createTag(innerContentContainer, tag);
}
}
}
// }}}
function updateDoneTags(parentElements, command) { // {{{
for (let i = 0; i < parentElements.length; i++) {
var parentElement = parentElements[i];
var innerContentContainer = parentElement.querySelector('.innerContentContainer');
if (!innerContentContainer) return;
var projectNode = parentElement.closest('.project');
var doneTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#done"]');
var missedTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#missed"]');
var underwayTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#underway"]');
var naTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#na"]');
if (command === 'done') {
removeTag(missedTagContainer);
removeTag(underwayTagContainer);
removeTag(naTagContainer);
if (doneTagContainer) {
removeTag(doneTagContainer);
} else {
createTag(innerContentContainer, 'done');
}
} else if (command === 'missed') {
removeTag(doneTagContainer);
removeTag(underwayTagContainer);
removeTag(naTagContainer);
if (missedTagContainer) {
removeTag(missedTagContainer);
} else {
createTag(innerContentContainer, 'missed');
}
} else if (command === 'underway') {
removeTag(doneTagContainer);
removeTag(missedTagContainer);
removeTag(naTagContainer);
if (underwayTagContainer) {
removeTag(underwayTagContainer);
} else {
createTag(innerContentContainer, 'underway');
}
} else if (command === 'na') {
removeTag(doneTagContainer);
removeTag(missedTagContainer);
removeTag(underwayTagContainer);
if (naTagContainer) {
removeTag(naTagContainer);
} else {
createTag(innerContentContainer, 'na');
}
} else if (command === '0') {
removeTag(doneTagContainer);
removeTag(missedTagContainer);
removeTag(naTagContainer);
removeTag(underwayTagContainer);
}
updateProject(projectNode);
}
}
// }}}
function updatePriorityTags(parentElements, command) { // {{{
for (let i = 0; i < parentElements.length; i++) {
var parentElement = parentElements[i];
var innerContentContainer = parentElement.querySelector('.innerContentContainer');
if (!innerContentContainer) return;
var projectNode = parentElement.closest('.project');
var aTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#a"]');
var bTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#b"]');
var cTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#c"]');
var dTagContainer = innerContentContainer.querySelector('.contentTag[data-val="#d"]');
if (command === 'a') {
removeTag(bTagContainer);
removeTag(cTagContainer);
removeTag(dTagContainer);
if (aTagContainer) {
removeTag(aTagContainer);
} else {
createTag(innerContentContainer, 'a');
}
} else if (command === 'b') {
removeTag(aTagContainer);
removeTag(cTagContainer);
removeTag(dTagContainer);
if (bTagContainer) {
removeTag(bTagContainer);
} else {
createTag(innerContentContainer, 'b');
}
} else if (command === 'c') {
removeTag(aTagContainer);
removeTag(bTagContainer);
removeTag(dTagContainer);
if (cTagContainer) {
removeTag(cTagContainer);
} else {
createTag(innerContentContainer, 'c');
}
} else if (command === 'd') {
removeTag(aTagContainer);
removeTag(bTagContainer);
removeTag(cTagContainer);
if (dTagContainer) {
removeTag(dTagContainer);
} else {
createTag(innerContentContainer, 'd');
}
} else if (command === '0') {
removeTag(aTagContainer);
removeTag(bTagContainer);
removeTag(cTagContainer);
removeTag(dTagContainer);
}
updateProject(projectNode);
}
}
// }}}
const keyCommands = {
'ArrowRight': focused => updateTimes(focused, 1),
'ArrowLeft': focused => updateTimes(focused, -1),
'Backspace': focused => updateTimes(focused, -2),
'ArrowDown': focused => updateTimes(focused, 0),
'≈': focused => updateDoneTags(focused, 'done'),
'√': focused => updateDoneTags(focused, 'underway'),
'¬': focused => updateDoneTags(focused, 'missed'),
'œ': focused => updateDoneTags(focused, 'na'),
'å': focused => updatePriorityTags(focused, 'a'),
'∫': focused => updatePriorityTags(focused, 'b'),
'ç': focused => updatePriorityTags(focused, 'c'),
'∂': focused => updatePriorityTags(focused, 'd'),
'®': focused => toggleTag(focused, 'review'),
'†': focused => toggleTag(focused, 'todo')
};
document.addEventListener('keydown', function(event) {
console.log(mutations);
if (event.altKey && keyCommands[event.key]) {
const focused = document.querySelectorAll('.name--focused');
if (focused.length > 0) {
event.preventDefault();
keyCommands[event.key](focused);
}
}
});
const observer = new MutationObserver((mutations) => {
console.log(mutations);
updateTaskListItems();
});
window.onload = function() {
const checkLoaded = setInterval(function() {
if (document.querySelector("#app > div")) {
clearInterval(checkLoaded);
update();
}
}, 100);
}

19
workflowy-helper/clip.js

@ -1,19 +0,0 @@
chrome.contextMenus.create({
id: "copyImageLink",
title: "Copy Image Link",
contexts: ["image"],
onclick: (info, tab) => {
// Get the URL of the clicked image
const imageUrl = info.srcUrl;
// Copy the image URL to the clipboard
navigator.clipboard.writeText("![](" + imageUrl + ")")
.then(() => {
console.log("Image URL copied to clipboard:", imageUrl);
})
.catch(error => {
console.error("Error copying image URL:", error);
});
}
});

2
workflowy-helper/diary.opml

File diff suppressed because one or more lines are too long

67
workflowy-helper/diary.py

@ -1,67 +0,0 @@
import xml.etree.ElementTree as ET
import datetime
def ordinal(n):
if 11 <= n % 100 <= 13:
suffix = 'th'
else:
suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')
return str(n) + suffix
def generate_weekly_opml(week_num, year=2025):
start_date = datetime.date(year, 1, 1) + datetime.timedelta(weeks=week_num-1)
start_date = start_date - datetime.timedelta(days=start_date.weekday())
# start_date = start_date - datetime.timedelta(days=start_date.weekday() + 1)
day_date = start_date
weekday = day_date.strftime('%A')
week_outline = ET.Element(
"outline",
text=f"2025 Week {week_num}",
_note=f"<time startYear='2025' startMonth='{day_date.month}' startDay='{day_date.day}'> {weekday[:3]}, {day_date.strftime('%b')} {day_date.day}, 2025</time>"
)
empty = ET.SubElement(week_outline, "outline", text="")
for i in range(7):
day_date = start_date + datetime.timedelta(days=i)
weekday = day_date.strftime('%A')
day_outline = ET.SubElement(
week_outline,
"outline",
text=f"{weekday} {ordinal(day_date.day)} {day_date.strftime('%b')}",
_note=f"<time startYear='2025' startMonth='{day_date.month}' startDay='{day_date.day}'> {weekday[:3]}, {day_date.strftime('%b')} {day_date.day}, 2025</time>"
)
if weekday in ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']:
outline = ET.SubElement(day_outline, "outline", text="")
return week_outline
def get_color(weekday):
colors = {
"Sunday": "purple",
"Monday": "red",
"Tuesday": "teal",
"Wednesday": "pink",
"Thursday": "green",
"Friday": "yellow",
"Saturday": "sky"
}
return colors.get(weekday, "gray")
def create_opml_for_year_and_first_week_2026():
root_opml = ET.Element("opml", version="2.0")
head = ET.SubElement(root_opml, "head")
owner_email = ET.SubElement(head, "ownerEmail")
owner_email.text = "[email protected]"
body = ET.SubElement(root_opml, "body")
for week_num in range(1, 53):
week_opml = generate_weekly_opml(week_num)
body.append(week_opml)
week_2026_opml = generate_weekly_opml(1, year=2026)
body.append(week_2026_opml)
tree = ET.ElementTree(root_opml)
return tree
def save_opml_to_file(filename="diary.opml"):
tree = create_opml_for_year_and_first_week_2026()
tree.write(filename, encoding="utf-8", xml_declaration=True)
save_opml_to_file()

8
workflowy-helper/main.js

@ -235,8 +235,9 @@
// }); // });
} }
const tags = n.querySelectorAll('.contentTag'); const tags = n.querySelectorAll('.contentTag');
if (text.startsWith("!")) { console.log(text);
console.log("found ! in text"); if (text.includes("!!")) {
console.log("found !! in text");
const links = [...n.querySelectorAll('a.contentLink')]; const links = [...n.querySelectorAll('a.contentLink')];
links.forEach(link => { links.forEach(link => {
link.classList.add("imgLink"); link.classList.add("imgLink");
@ -244,7 +245,6 @@
const imgSrcs = links.map(link => { const imgSrcs = links.map(link => {
return link.href; return link.href;
}); });
console.log({imgSrcs});
// const imgSrcs = links.forEach(link => { // const imgSrcs = links.forEach(link => {
// // return link.href; // // return link.href;
@ -264,7 +264,7 @@
// } // }
if (imgSrcs.length > 0) { if (imgSrcs.length > 0) {
createImages(n, imgSrcs); createImages(projectNode, imgSrcs);
} }
} }
tags.forEach(tag => { tags.forEach(tag => {

4
workflowy-helper/main.py

@ -1,4 +0,0 @@
from constants import Constants

19
workflowy-helper/popup.js

@ -50,12 +50,29 @@ const contentTypes = {
}, },
workflowyFormat: function(data, tab) { workflowyFormat: function(data, tab) {
return { return {
name: "<span class='colored c-gray'>" + data.title.trim() + "</span>", name: data.title.trim(),
description: data.artist + "\n\n" + "<i>" + data.tab + "</i>" description: data.artist + "\n\n" + "<i>" + data.tab + "</i>"
}; };
} }
} }
], ],
"movie": [
{
urlPattern: /^https?:\/\/www.imdb.com\/title\//,
fields: {
title: { selector: 'h1', attribute: 'innerText' },
director: { selector: 'a[href^="/name/"]', attribute: 'textContent' },
year: { selector: 'a[href*="/releaseinfo"]', attribute: 'textContent' },
cover: { selector: 'img.ipc-image', attribute: 'src' },
},
workflowyFormat: function(data, tab) {
return {
name: data.title.trim(),
description: data.year + " " + data.director + "\n" + "!!" + data.cover
};
}
}
],
}; };
function initConfigPopup(tab) { // {{{ function initConfigPopup(tab) { // {{{

5
workflowy-helper/requirements.txt

@ -1,5 +0,0 @@
browser-cookie3>=0.19.0
rich>=13.8.0
requests>=2.32.0
uuid>=1.30
click>=8.0.0

18
workflowy-helper/setup.py

@ -1,18 +0,0 @@
from setuptools import setup
with open('requirements.txt') as f:
requirements = f.read().splitlines()
setup(
name='wf',
packages=['wf'],
install_requires=requirements,
description='A command line interface for using WorkFlowy.',
author='Gregory Leeman',
author_email='[email protected]',
entry_points={
'console_scripts': [
'wf = wf.script:cli'
],
},
)

36
workflowy-helper/storage.py

@ -1,36 +0,0 @@
import json
import os
from .logging import logger
from .constants import Constants
def load_storage():
if os.path.exists(Constants.STORAGE):
logger.debug(f"Loading storage from {Constants.STORAGE}")
with open(Constants.STORAGE, "r") as f:
return json.load(f)
return {}
def save_storage(data):
with open(Constants.STORAGE, "w") as f:
json.dump(data, f)
def save_to_storage(key, value):
storage = load_storage()
storage[key] = value
save_storage(storage)
def load_from_storage(key):
storage = load_storage()
if key in storage:
return storage[key]
return {}
def clear_storage():
if os.path.exists(Constants.STORAGE):
os.remove(Constants.STORAGE)

19
workflowy-helper/style.css

@ -710,7 +710,11 @@ body::-webkit-scrollbar {
.boardCard { .boardCard {
width: var(--card) !important; /* width: max-content !important; */
/* max-width: var(--card) !important; */
width: -webkit-fill-available;
max-width: var(--card) !important;
/* min-width: max-content; */
} }
.boardCard .name, .boardCard .name,
@ -757,8 +761,11 @@ body::-webkit-scrollbar {
} }
.boardColumn > .name { .boardColumn > .name {
min-width: var(--card) !important; /* max-width: var(--card) !important; */
width: 100% !important; /* width: 100% !important; */
width: -webkit-fill-available;
min-width: max-content;
max-width: initial !important;
margin: 8px 32px 0px 32px; margin: 8px 32px 0px 32px;
padding: 0; padding: 0;
border-bottom: 1px solid var(--grey) !important; border-bottom: 1px solid var(--grey) !important;
@ -787,7 +794,7 @@ body::-webkit-scrollbar {
.boardColumn > .name > .content { .boardColumn > .name > .content {
padding: 0 0 4px 0 !important; padding: 0 0 4px 0 !important;
width: 100% !important; /* width: 100% !important; */
} }
.boardColumn > .children { .boardColumn > .children {
@ -805,4 +812,6 @@ body::-webkit-scrollbar {
background: none !important; background: none !important;
} }
.inject {
padding-left: 24px !important;
}

410
workflowy-helper/temp.js

@ -1,410 +0,0 @@
(function() {
'use strict';
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'webm'];
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'];
function describeTimeElementDate(element) { // {{{
if (!(element instanceof HTMLTimeElement)) {
return "unknown";
}
const startYear = element.getAttribute('startyear');
const startMonth = element.getAttribute('startmonth');
const startDay = element.getAttribute('startday');
if (!startYear || !startMonth || !startDay || isNaN(startYear) || isNaN(startMonth) || isNaN(startDay)) {
return 'Invalid date attributes on the <time> element';
}
const timeElementDate = new Date(startYear, startMonth - 1, startDay);
timeElementDate.setHours(0, 0, 0, 0);
const today = new Date();
today.setHours(0, 0, 0, 0);
const dayDifference = Math.round((timeElementDate - today) / (1000 * 60 * 60 * 24));
const daysOfWeek = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const dayOfWeek = daysOfWeek[timeElementDate.getDay()];
const monthsOfYear = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const month = monthsOfYear[timeElementDate.getMonth()];
var ret = [dayOfWeek, month];
if (dayDifference === 0) {
ret.push('today');
} else if (dayDifference === 1) {
ret.push('tomorrow');
} else if (dayDifference === -1) {
ret.push('yesterday');
} else {
if (dayDifference < 0) {
ret.push('late');
// if (dayDifference > -8) {
// ret.push('last');
// } else {
// ret.push('older');
// }
} else {
ret.push('future');
// if (dayDifference < 8) {
// ret.push('next');
// } else {
// ret.push('later');
// }
}
}
return ret;
}
// }}}
function safeGetContent(parent, selector) { // {{{
var element = parent.querySelector(selector);
if (element !== null) {
var content = element.innerText;
if (content !== null) {
return content;
}
}
return ""
}
// }}}
function removeClassesStartingWith(element, prefix) { // {{{
const classes = element.classList;
for (const className of classes) {
if (className.startsWith(prefix)) {
element.classList.remove(className);
}
}
}
// }}}
function addImageToAlt(node, imgSrc) { // {{{
var alt = node.getAttribute("alt");
if (alt === null) {
alt = imgSrc;
node.setAttribute("alt", alt);
node.classList.add("image");
}
}
// }}}
function createImageNodeAfterNode(node, imgSrc) { // {{{
var injected = node.getElementsByClassName("inject");
if (injected.length === 0) {
var extension = imgSrc.split('.').pop().toLowerCase();
if (videoExtensions.includes(extension)) {
var link = document.createElement("a");
link.className = 'inject';
link.href = imgSrc;
link.target = "_blank"
var video = document.createElement("video");
video.src = imgSrc;
link.appendChild(video)
node.appendChild(link);
}
// else if (imageExtensions.includes(extension)) {
else {
var link = document.createElement("a");
link.className = 'inject';
link.href = imgSrc;
link.target = "_blank"
var img = document.createElement("img");
img.src = imgSrc;
img.draggable = false;
link.appendChild(img)
node.appendChild(link);
}
}
}
// }}}
function removeImagesFromNode(node) { // {{{
var existingImages = Array.from(node.getElementsByClassName("inject"));
existingImages.forEach(function(imgDiv) {
imgDiv.parentNode.removeChild(imgDiv);
});
}
// }}}
const markdownImageRegex = /\!\[.*\]\((.+)\)/;
function updateProject(projectNode) { // {{{
removeClassesStartingWith(projectNode, "backlinked");
removeClassesStartingWith(projectNode, "link");
removeClassesStartingWith(projectNode, "tag");
removeClassesStartingWith(projectNode, "time");
const names = [];
const notes = [];
const breadcrumbNodes = [];
for (let i = 0; i < projectNode.children.length; i++) {
const child = projectNode.children[i];
if (child.classList.contains('name')) {
names.push(child);
}
if (child.classList.contains('notes')) {
notes.push(child);
}
if (child.classList.contains('_1zok2')) {
breadcrumbNodes.push(child.querySelector('.breadcrumbs'));
}
}
[notes, names].forEach(function(ns) {
if (ns.length > 0) {
const n = ns[0];
const text = safeGetContent(n, ".content > .innerContentContainer");
var matcher = text.match(markdownImageRegex);
if (matcher !== null) {
var imgSrc = matcher[1];
var nameText = names[0].querySelector('.innerContentContainer');
if (nameText.dataset.imgSrc === undefined ) {
nameText.classList.add("image");
nameText.dataset.imgSrc = imgSrc;
nameText.addEventListener('mouseover', function(e) {
const tooltip = document.getElementById('tooltip');
tooltip.style.display = 'block';
tooltip.style.left = e.pageX + 'px';
tooltip.style.top = e.pageY + 'px';
tooltip.src = imgSrc;
nameText.addEventListener('mousemove', function(e) {
tooltip.style.left = e.pageX + 5 + 'px';
tooltip.style.top = e.pageY + 5 + 'px';
});
nameText.addEventListener('mouseleave', function() {
tooltip.style.display = 'none';
});
});
}
}
const tags = n.querySelectorAll('.contentTag');
tags.forEach(tag => {
var tagText = safeGetContent(tag, ".contentTagText").trim();
if (tagText !== "") {
projectNode.classList.add("tagged");
projectNode.classList.add("tagged-" + tagText);
}
});
const links = n.querySelectorAll('.contentLink');
links.forEach(link => {
link.spellcheck = false;
const nonLetterRegex = /[^a-zA-Z0-9]/g;
var linkFull = link.innerText.trim().replace(nonLetterRegex, '');
if (linkFull.startsWith("x") || linkFull.startsWith("y") || linkFull.startsWith("z")) {
var className = "linked-" + linkFull;
var linkClassName = "link-" + linkFull;
projectNode.classList.add("linked");
projectNode.classList.add(className);
link.classList.add("link");
link.classList.add(linkClassName);
}
});
const times = n.querySelectorAll('time');
times.forEach(time => {
var relatives = describeTimeElementDate(time);
projectNode.classList.add("timed");
relatives.forEach(relative => {
projectNode.classList.add("timed-" + relative);
});
});
}
});
if (breadcrumbNodes.length > 0) {
const links = breadcrumbNodes[0].querySelectorAll('a');
for (let i = 0; i < links.length; i++) {
var link = links[i];
if (link.innerHTML == "🌎 planner") {
if (link !== undefined) {
var hotspot = links[i + 1].innerText.split(' ')[1];
var className = "backlinked-" + hotspot;
projectNode.classList.add("backlinked");
projectNode.classList.add(className);
break;
}
}
}
}
}
// }}}
function updateAll() {
const projectNodes = document.querySelectorAll('.project');
projectNodes.forEach(projectNode => {
updateProject(projectNode);
});
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
var nodes = null;
if (mutation.addedNodes.length > 0) {
nodes = mutation.addedNodes;
} else if (mutation.removedNodes.length > 0) {
nodes = mutation.addedNodes;
}
if ( nodes !== null ) {
var node = mutation.target;
if ( node !== null ) {
var projectNode = node.closest('.project');
if ( projectNode !== null && projectNode !== node ) {
updateProject(projectNode);
}
}
}
}
});
function addTooltip() {
const tooltip = document.createElement('img');
tooltip.style.position = 'absolute';
tooltip.style.zIndex = '1000';
tooltip.id = 'tooltip';
document.body.appendChild(tooltip);
return tooltip;
}
function getContainerNode(node) {
if (!node.classList) {
node = node.parentNode;
}
if (node.classList.contains('innerContentContainer')) {
return node;
} else if (node.querySelector('.innerContentContainer')) {
return node.querySelector('.innerContentContainer');
} else {
return node.closest('.innerContentContainer');
}
console.log(node);
return null;
}
function getTagNodes(containerNode) {
const tags = containerNode.querySelectorAll('.contentTag');
return tags;
}
function getLinkNodes(containerNode) {
const links = containerNode.querySelectorAll('.contentLink');
return links;
}
function getTagNode(containerNode, tagName) {
const tags = getTagNodes(containerNode);
for (const existingTag of tags) {
if (existingTag.innerText === '@' + tagName) {
return existingTag;
}
}
return null;
}
function getLinkNode(containerNode, linkName) {
const links = getLinkNodes(containerNode);
for (const existingLink of links) {
if (existingLink.innerText === linkName) {
return existingLink;
}
}
return null;
}
function toggleTag(containerNode, tagName) {
const tagNode = getTagNode(containerNode, tagName);
if (tagNode) {
tagNode.remove();
} else {
addTagNode(containerNode, tagName);
}
}
function toggleLink(containerNode, linkName, linkUrl) {
const linkNode = getLinkNode(containerNode, linkName);
if (linkNode) {
linkNode.remove();
} else {
addLinkNode(containerNode, linkName, linkUrl);
}
}
function addTagNode(containerNode, tagName) {
containerNode.innerHTML = containerNode.innerHTML.trim() + ' <span class="contentTag explosive" title="Filter @' + tagName + '" data-val="@' + tagName + '">@<span class="contentTagText">' + tagName + '</span><span class="contentTagNub"></span></span>';
}
function addLinkNode(containerNode, linkName, linkUrl) {
// <a class="contentLink link link-xtask" target="_blank" rel="noreferrer" href="https://workflowy.com/#/b732f37515f4" spellcheck="false">xtask</a>
containerNode.innerHTML = containerNode.innerHTML.trim() + ' <a class="contentLink link link-' + linkName + '" target="_blank" rel="noreferrer" href="' + linkUrl + '" spellcheck="false">' + linkName + '</a>';
}
const quickTags = {
'd': 'done',
'm': 'missed',
'n': 'na',
};
const quickLinks = {
'†': {'name': 'xtask', 'url': 'https://workflowy.com/#/b732f37515f4'}
};
document.addEventListener("keydown", (e) => {
if (e.ctrlKey) {
console.log(e.key);
if (e.key in quickTags) {
e.preventDefault();
const selection = window.getSelection();
const node = selection.anchorNode;
if (node && node.parentNode) {
const containerNode = getContainerNode(node);
console.log({containerNode});
if (containerNode) {
toggleTag(containerNode, quickTags[e.key]);
let lastTextNode = containerNode.lastChild;
while (lastTextNode && lastTextNode.nodeType !== Node.TEXT_NODE) {
lastTextNode = lastTextNode.previousSibling;
}
if (lastTextNode && lastTextNode.nodeType === Node.TEXT_NODE) {
selection.collapse(lastTextNode, lastTextNode.textContent.length);
}
updateProject(containerNode.closest('.project'));
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
addTooltip();
updateAll();
})();

0
workflowy-helper/wf/__init__.py

17
workflowy-helper/wf/archive/constants.py

@ -1,17 +0,0 @@
import os
class Constants:
STORAGE = os.path.join(os.path.expanduser('~'), 'wf.json')
INBOX_ID = "13859f14-79ab-b758-7224-8dad0236f1e2"
TASKS_ID = "042d7e11-f613-856c-fb97-c0e6ee7ece05"
DIARY_ID = "01205285-f5b1-8026-0cfa-942f514e297e"
LIFE_IDS = {
"life": "f6f993a3-c696-c070-296e-6e5055fc834f",
"admin": "d3911123-138e-8eb3-f2ba-2495d8169660",
"art": "c2e9f4b2-59ce-d127-4da6-949e54ec1442",
"health": "4fc929b8-f04b-0dde-9a1a-9d889a13316d",
"mind": "c6db70a1-72e3-dbcc-f4b9-bfb10a1b4280",
"social": "60e3d667-eb40-3d26-cc3f-6b151cc5efa4",
"church": "5c68fad5-ad2b-8018-6535-b1462aed1277",
"work": "4c4970fc-9023-f861-1392-dbf88dd89187",
}

32
workflowy-helper/wf/archive/helpers.py

@ -1,32 +0,0 @@
import json
import os
import uuid
from datetime import datetime, timedelta
from constants import Constants
from logging import logger
def get_ordinal(n):
if 10 <= n % 100 <= 20:
suffix = 'th'
else:
suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')
return str(n) + suffix
def get_today():
now = datetime.now()
return now.strftime("%a, %b %d, %Y")
def get_sunday():
now = datetime.now()
sunday = now - timedelta(days=now.weekday()) - timedelta(days=1)
return sunday.strftime("%a, %b %d, %Y")
def generate_uuid():
return str(uuid.uuid4())

0
workflowy-helper/wf/archive/main.py

36
workflowy-helper/wf/archive/theme.py

@ -1,36 +0,0 @@
from rich.theme import Theme
solarized_theme = Theme({
"base03": "bright_black",
"base02": "black",
"base01": "bright_green",
"base00": "bright_yellow",
"base0": "bright_blue",
"base1": "bright_cyan",
"base2": "white",
"base3": "bright_white",
"orange": "bright_red",
"violet": "bright_magenta",
"red": "red",
"bold red": "bold red",
"underline base03": "underline bright_black",
"underline base02": "underline black",
"underline base01": "underline bright_green",
"underline base00": "underline bright_yellow",
"underline base0": "underline bright_blue",
"underline base1": "underline bright_cyan",
"underline base2": "underline white",
"underline base3": "underline bright_white",
"underline orange": "bright_red underline",
"underline violet": "bright_magenta underline",
"bold base03": "bold bright_black",
"bold base02": "bold black",
"bold base01": "bold bright_green",
"bold base00": "bold bright_yellow",
"bold base0": "bold bright_blue",
"bold base1": "bold bright_cyan",
"bold base2": "bold white",
"bold base3": "bold bright_white",
"bold orange": "bright_red bold",
"bold violet": "bright_magenta bold",
})

9
workflowy-helper/wf/console.py

@ -1,9 +0,0 @@
import logging
from rich.console import Console
from rich.logging import RichHandler
from theme import solarized_theme
console = Console(highlight=False, theme=solarized_theme)
logging.basicConfig(handlers=[RichHandler(level="NOTSET", console=console)])
logger = logging.getLogger('rich')
logger.setLevel(logging.INFO)

70
workflowy-helper/wf/main.py

@ -1,70 +0,0 @@
# main.py
import os
import json
import requests
import browser_cookie3
from .console import console, logger
class WF:
def __init__(self):
self._data = {}
self._path = None
@property
def _cookie(self):
return self._data.get('cookie', None)
def _load(self):
if os.path.exists(self.path):
logger.info(f'Loading data from {self.path}')
with open(self.path, 'r') as f:
self._data = json.load(f)
else:
logger.warning(f'No data found at {self.path}')
self._data = {}
def _save(self):
logger.info(f'Saving data to {self.path}')
with open(self.path, 'w') as f:
json.dump(self._data, f)
def _refresh_cookie(self):
logger.info('Refreshing cookies')
cookies = browser_cookie3.chrome()
for cookie in cookies:
if cookie.name == "sessionid" and "workflowy.com" in cookie.domain:
self._data['cookie'] = cookie.value
break
logger.warning('No cookie found')
def _refresh_data(self):
if not self._cookie:
logger.warning('No cookie found')
return
logger.info('Refreshing data')
url = "https://workflowy.com/get_initialization_data?client_version=15"
headers = {"Cookie": f"sessionid={self._cookie}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
data_globals = {item[0]: item[1] for item in data["globals"]}
self._data["userid"] = data_globals["USER_ID"]
self._data["joined"] = data["projectTreeData"]["mainProjectTreeInfo"]["dateJoinedTimestampInSeconds"]
self._data["transid"] = data["projectTreeData"]["mainProjectTreeInfo"]["initialMostRecentOperationTransactionId"]
self._data["pollid"] = generate_uuid() # Simulate g() + g()
storage["root"] = {"nm": "root", "ch": data["projectTreeData"]["mainProjectTreeInfo"]["rootProjectChildren"]}
save_storage(storage)
console.log("Successfully refreshed and saved Workflowy data.")

932
workflowy-helper/wf/script.py

@ -1,932 +0,0 @@
#!/Users/gl6/env/bin/python
import os
import json
import requests
import uuid
import browser_cookie3
import time
from datetime import datetime, timedelta
import re
import click
import logging
from rich.console import Console
from rich.theme import Theme
from rich.logging import RichHandler
STORAGE_FILE = os.path.join(os.path.expanduser('~'), '.wf.json')
INBOX_ID = "13859f14-79ab-b758-7224-8dad0236f1e2"
TASKS_ID = "042d7e11-f613-856c-fb97-c0e6ee7ece05"
DIARY_ID = "01205285-f5b1-8026-0cfa-942f514e297e"
PLANNER_IDS = {
"life": "f6f993a3-c696-c070-296e-6e5055fc834f",
"admin": "d3911123-138e-8eb3-f2ba-2495d8169660",
"art": "c2e9f4b2-59ce-d127-4da6-949e54ec1442",
"health": "4fc929b8-f04b-0dde-9a1a-9d889a13316d",
"mind": "c6db70a1-72e3-dbcc-f4b9-bfb10a1b4280",
"social": "60e3d667-eb40-3d26-cc3f-6b151cc5efa4",
"church": "5c68fad5-ad2b-8018-6535-b1462aed1277",
"work": "4c4970fc-9023-f861-1392-dbf88dd89187",
}
solarized_theme = Theme({
"base03": "bright_black",
"base02": "black",
"base01": "bright_green",
"base00": "bright_yellow",
"base0": "bright_blue",
"base1": "bright_cyan",
"base2": "white",
"base3": "bright_white",
"orange": "bright_red",
"violet": "bright_magenta",
"red": "red",
"bold red": "bold red",
"underline base03": "underline bright_black",
"underline base02": "underline black",
"underline base01": "underline bright_green",
"underline base00": "underline bright_yellow",
"underline base0": "underline bright_blue",
"underline base1": "underline bright_cyan",
"underline base2": "underline white",
"underline base3": "underline bright_white",
"underline orange": "bright_red underline",
"underline violet": "bright_magenta underline",
"bold base03": "bold bright_black",
"bold base02": "bold black",
"bold base01": "bold bright_green",
"bold base00": "bold bright_yellow",
"bold base0": "bold bright_blue",
"bold base1": "bold bright_cyan",
"bold base2": "bold white",
"bold base3": "bold bright_white",
"bold orange": "bright_red bold",
"bold violet": "bright_magenta bold",
})
console = Console(highlight=False, theme=solarized_theme)
logging.basicConfig(handlers=[RichHandler(level="NOTSET", console=console)])
logger = logging.getLogger('rich')
logger.setLevel(logging.INFO)
# helpers {{{
def get_ordinal(n):
if 10 <= n % 100 <= 20:
suffix = 'th'
else:
suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')
return str(n) + suffix
def get_today():
now = datetime.now()
return now.strftime("%a, %b %d, %Y")
# return now.strftime(f"%a {get_ordinal(now.day)} %b")
def get_sunday():
now = datetime.now()
sunday = now - timedelta(days=now.weekday()) - timedelta(days=1)
return sunday.strftime("%a, %b %d, %Y")
# return now.strftime(f"%a {get_ordinal(now.day)} %b")
def generate_uuid():
return str(uuid.uuid4())
def load_storage():
if os.path.exists(STORAGE_FILE):
logger.debug(f"Loading storage from {STORAGE_FILE}")
with open(STORAGE_FILE, "r") as f:
return json.load(f)
return {}
def save_storage(data):
with open(STORAGE_FILE, "w") as f:
json.dump(data, f)
def save_to_storage(key, value):
storage = load_storage()
storage[key] = value
save_storage(storage)
def load_from_storage(key):
storage = load_storage()
if key in storage:
return storage[key]
return {}
def clear_storage():
if os.path.exists(STORAGE_FILE):
os.remove(STORAGE_FILE)
# }}}
def refresh_cookie(): # {{{
logger.debug("Refreshing session cookie")
cookies = browser_cookie3.chrome()
session_cookie = None
for cookie in cookies:
if cookie.name == "sessionid" and "workflowy.com" in cookie.domain:
session_cookie = cookie.value
break
if session_cookie:
logger.debug(f"Found session cookie: {session_cookie}")
save_to_storage("session_cookie", session_cookie)
return True
else:
logger.error("Session cookie not found. Are you logged into Workflowy?")
return False
# }}}
def check_cookie(): # {{{
session_cookie = load_from_storage("session_cookie")
if session_cookie:
logger.debug(f"Session cookie found: {session_cookie}")
else:
logger.error("Session cookie not found. Run refresh_cookie() first.")
return False
url = "https://workflowy.com/get_initialization_data?client_version=15"
headers = {"Cookie": f"sessionid={session_cookie}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
logger.debug("Session cookie is valid.")
return True
else:
logger.error(f"Session cookie is invalid. Status code {response.status_code}")
return False
# }}}
def refresh_workflowy_data(): # {{{
session_cookie = load_from_storage("session_cookie")
if not session_cookie:
console.log("Session cookie not found. Run refresh_cookie() first.")
return
url = "https://workflowy.com/get_initialization_data?client_version=15"
headers = {"Cookie": f"sessionid={session_cookie}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
try:
data = response.json()
globals_data = {item[0]: item[1] for item in data["globals"]}
storage = load_storage()
storage["userid"] = globals_data["USER_ID"]
storage["joined"] = data["projectTreeData"]["mainProjectTreeInfo"][
"dateJoinedTimestampInSeconds"
]
storage["transid"] = data["projectTreeData"]["mainProjectTreeInfo"][
"initialMostRecentOperationTransactionId"
]
storage["pollid"] = generate_uuid() # Simulate g() + g()
storage["root"] = {"nm": "root", "ch": data["projectTreeData"]["mainProjectTreeInfo"]["rootProjectChildren"]}
save_storage(storage)
console.log("Successfully refreshed and saved Workflowy data.")
return True
except Exception as e:
console.log(f"Error parsing response: {e}")
return False
else:
console.log(f"Error fetching Workflowy data: Status code {response.status_code}")
return False
# }}}
def check_workflowy_data(): # {{{
storage = load_storage()
if not storage:
console.log("Workflowy data is not initialized. Run the initialization first.")
return False
if not storage.get("userid") or not storage.get("transid") or not storage.get(
"pollid"
):
console.log("Workflowy data is incomplete. Run the initialization again.")
return False
return True
# }}}
def clip_to_workflowy(name, description, parent_id): # {{{
storage = load_storage()
if not storage:
console.log("Workflowy data is not initialized. Run the initialization first.")
return
new_uuid = generate_uuid()
timestamp = int(time.time()) - storage.get("joined", 0)
request = [
{
"most_recent_operation_transaction_id": storage.get("transid"),
"operations": [
{
"type": "create",
"data": {
"projectid": new_uuid,
"parentid": parent_id,
"priority": 9999,
},
"client_timestamp": timestamp,
"undo_data": {},
},
{
"type": "edit",
"data": {
"projectid": new_uuid,
"name": name,
"description": description,
},
"client_timestamp": timestamp,
"undo_data": {
"previous_last_modified": timestamp,
"previous_name": "",
"previous_description": "",
},
},
],
}
]
data = {
"client_id": "2015-11-17 19:25:15.397732",
"client_version": 15,
"push_poll_id": storage.get("pollid"),
"push_poll_data": json.dumps(request),
"crosscheck_user_id": storage.get("userid"),
}
headers = {"Cookie": f"sessionid={storage.get('session_cookie')}"}
response = requests.post("https://workflowy.com/push_and_poll", data=data, headers=headers)
if response.status_code == 200:
resp_obj = response.json()
if resp_obj.get("logged_out"):
console.log("Error: Logged out of Workflowy!")
elif not resp_obj.get("results"):
console.log("Error: Unknown error!")
else:
storage["transid"] = resp_obj["results"][0][
"new_most_recent_operation_transaction_id"
]
save_storage(storage)
console.print("[green]Successfully clipped to Workflowy![/green]")
else:
console.log(f"Error: Failed with status code {response.status_code}")
# }}}
def simplify_project(project_data, full_data, follow_mirrors=False): # {{{
if project_data.get("metadata", {}).get("mirror", {}).get("originalId"):
if follow_mirrors:
originalId = project_data["metadata"]["mirror"]["originalId"]
project_data, full_data = find_project_by_id(full_data, full_data, originalId)
else:
return None, full_data
if project_data.get("metadata", {}).get("isReferencesRoot"):
if project_data.get("ch"):
if len(project_data["ch"]) > 0:
project_data["nm"] = "backlinks"
project_data["metadata"]["layoutMode"] = "h1"
else:
return None, full_data
else:
return None, full_data
if project_data:
simplified_project = {
"name": project_data.get("nm", ""),
"id": project_data.get("id", ""),
"children": [],
"description": project_data.get("no", "").rstrip().replace("\n$", ""),
"format": project_data.get("metadata", {}).get("layoutMode", None)
}
children = project_data.get("ch", [])
for child in children:
simplified_child, full_data = simplify_project(child, full_data, follow_mirrors=follow_mirrors)
if simplified_child:
simplified_project["children"].append(simplified_child)
return simplified_project, full_data
return None, full_data
# }}}
def flatten(children): # {{{
flattened = []
for child in children:
try:
grand_children = child.get("children", [])
except Exception as e:
print(f"child: {child} {e}")
grand_children = []
child["children"] = []
flattened.append(child)
flattened.extend(flatten(grand_children))
return flattened
# }}}
def flatten_project(project_data): # {{{
project_data["children"] = flatten(project_data.get("children", []))
return project_data
# }}}
def filter_project_any(project_data, filters, include_headers=False, all_children=False): # {{{
include = False
if include_headers:
if project_data["format"] == "h1" or project_data["format"] == "h2":
include = True
for filter_text in filters:
if filter_text in project_data["name"]:
include = True
break
children = []
for child in project_data.get("children", []):
if all_children and include:
logger.debug(f"Not filtering children of {project_data['name']}")
pass
else:
child = filter_project_any(child, filters, include_headers=include_headers, all_children=all_children)
if child:
children.append(child)
project_data["children"] = children
if include or children:
return project_data
else:
return None
# }}}
def filter_project_all(project_data, filters, include_headers=False, all_children=False): # {{{
include = True
if include_headers and (project_data["format"] == "h1" or project_data["format"] == "h2"):
pass
else:
for filter_text in filters:
if filter_text not in project_data["name"]:
if filter_text not in project_data.get("description", ""):
include = False
break
if include:
logger.debug(f"Including {project_data['name']}")
logger.debug(f"all_children: {all_children}")
children = []
logger.debug(f"children: {project_data.get('children', [])}")
for child in project_data.get("children", []):
if all_children and include:
logger.debug(f"Not filtering children of {project_data['name']}")
pass
else:
child = filter_project_all(child, filters, include_headers=include_headers, all_children=all_children)
if child:
children.append(child)
project_data["children"] = children
if include or children:
return project_data
else:
return None
# }}}
def replace(project_data, regex, replacement): # {{{
project_data["name"] = re.sub(regex, replacement, project_data["name"])
children = project_data.get("children", [])
for child in children:
replace(child, regex, replacement)
return project_data
# }}}
def strip(project_data, regex): # {{{
project_data = replace(project_data, regex, "")
return project_data
# }}}
def remove_double_spaces(project_data): # {{{
# project_data["name"] = re.sub(r"\s+", " ", project_data["name"])
project_data["name"] = project_data["name"].replace(" ", " ")
children = project_data.get("children", [])
for child in children:
remove_double_spaces(child)
return project_data
# }}}
highlights = {
"@done": "green",
"@missed": "red",
"@na": "blue",
"#WORK": "red",
}
def highlight(project_data): # {{{
for key, value in highlights.items():
regex = f"{key}\\b"
project_data["name"] = re.sub(regex, f"[{value}]{key}[/]", project_data["name"])
children = project_data.get("children", [])
for child in children:
highlight(child)
return project_data
# }}}
colors1 = [
[["xTASK", "#READY"], "blue", True],
[["xTASK", "#MAYBE"], "violet", True],
[["xTASK", "#WAITING"], "cyan", True],
[["xTASK", "#DAILY"], "green", True],
[["xTASK", "#WEEKLY"], "green", True],
[["xTASK", "#IRREGULAR"], "green", True],
[["xPROJECT", "#ACTIVE"], "magenta", True],
[["xPROJECT", "#STALLED"], "cyan", True],
[["xPROJECT", "#PLANT"], "orange", True],
[["xSOMEDAY"], "violet", True],
[["xHABIT"], "orange", True],
[["xSTORY"], "cyan", True],
[["xGOAL"], "yellow", True],
[["xVIS"], "red", True],
[["xRESPONSIBILITY"], "red", True],
[["Sunday"], "underline violet", False],
[["Monday"], "underline red", False],
[["Tuesday"], "underline cyan", False],
[["Wednesday"], "underline magenta", False],
[["Thursday"], "underline green", False],
[["Friday"], "underline yellow", False],
[["Saturday"], "underline blue", False],
[["#r"], "black on blue", False],
[["#g"], "black on yellow", False],
[["#w"], "red", False],
[["#p"], "green", False],
]
def recolor(project_data, colors): # {{{
for rule in colors:
keywords = rule[0]
color = rule[1]
hide = rule[2]
match = True
for keyword in keywords:
if keyword not in project_data["name"]:
match = False
break
if match:
project_data["name"] = f"[{color}]{project_data['name']}[/]"
if hide:
for keyword in keywords:
project_data["name"] = project_data["name"].replace(keyword, "")
project_data["name"] = project_data["name"].rstrip() + f" [base01]{" ".join(keywords)}[/]"
children = project_data.get("children", [])
for child in children:
recolor(child, colors)
return project_data
# }}}
def print_pretty(data, indent=0, color="grey", show_description=True, show_id=False): # {{{
try:
for item in data["children"]:
if item["name"] == "backlinks":
# console.print(" " * indent + "[base01]• backlinks[/]")
continue
console.print(" " * indent + f"[base3]•[/] [{color}]{item['name']}[/][base01]{' ' + item['id'].split('-')[4] if show_id else ''}[/]")
if item["description"] and show_description:
console.print(" " * (indent + 1) + f"[base01]{item['description'].replace('\n', '\n' + ' ' * (indent + 1))}[/]")
if item["children"]:
print_pretty(item, indent + 1, color, show_description=show_description, show_id=show_id)
except Exception as e:
console.log(f"Error: {e}")
# }}}
def generate_d3_mindmap_html(data, show_description=True, show_id=False): # {{{
import json
import re
# Map of Rich tag colors to hex values or CSS color names
color_map = {
"base3": "#073642",
"base2": "#002b36",
"base1": "#586e75",
"base0": "#657b83",
"base00": "#839496",
"base01": "#93a1a1",
"base02": "#eee8d5",
"base03": "#fdf6e3",
"yellow": "#b58900",
"orange": "#cb4b16",
"red": "#dc322f",
"magenta": "#d33682",
"violet": "#6c71c4",
"blue": "#268bd2",
"cyan": "#2aa198",
"green": "#859900",
}
def parse_rich_tags(text):
text = re.sub(r"\[(underline)? ?([a-zA-Z0-9]+)?\](.*?)\[/\]",
lambda m: f'<tspan style="{"text-decoration: underline;" if m.group(1) else ""}'
f'{"fill: " + color_map[m.group(2)] + ";" if m.group(2) else ""}">{m.group(3)}</tspan>',
text)
return text
def remove_rich_tags(text):
text = re.sub(r"\[(underline)? ?([a-zA-Z0-9]+)?\](.*?)\[/\]", r"\3", text)
return text
# Recursively transform data to add the parsed Rich-style text
def transform_data(item):
node = {
"name": remove_rich_tags(parse_rich_tags(item["name"])), # Parse Rich tags in name
"id": item["id"].split("-")[4] if show_id else "",
"description": parse_rich_tags(item["description"]) if show_description else ""
}
if item["children"]:
node["children"] = [transform_data(child) for child in item["children"]]
return node
transformed_data = transform_data(data)
# Create the HTML content with D3.js code
html_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mind Map</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
body {{
font-family: Arial, sans-serif;
background-color: #073642;
color: #fdf6e3;
}}
.node {{
cursor: pointer;
}}
.node circle {{
fill: #586e75;
stroke: #586e75;
stroke-width: 3px;
}}
.node text {{
# color: #fdf6e3 !important;
fill: #fdf6e3;
font: 12px sans-serif;
}}
.link {{
fill: none;
stroke: #586e75;
stroke-width: 2px;
}}
</style>
</head>
<body>
<svg width="100vw" height="100vh"></svg>
<script>
var data = {json.dumps(transformed_data)};
var svg = d3.select("svg")
.attr("width", "100vw")
.attr("height", "100vh");
var zoom = d3.zoom()
.scaleExtent([0.5, 5])
.on("zoom", function(event) {{
g.attr("transform", "translate(" + event.transform.x + "," + event.transform.y + ") scale(" + event.transform.k + ") rotate(" + currentRotation + ")");
currentTransform = event.transform;
}});
svg.call(zoom);
var g = svg.append("g");
// .attr("transform", "translate(" + window.innerWidth / 2 + "," + window.innerHeight / 2 + ")");
var currentRotation = 0;
var currentTransform = d3.zoomIdentity;
var isRotating = false;
var lastX = 0;
document.addEventListener("keydown", function(event) {{
if (event.key === "r" || event.key === "R") {{
isRotating = !isRotating;
}}
}});
svg.on("mousedown", function(event) {{
if (isRotating) {{
lastX = event.clientX;
}}
}});
svg.on("mousemove", function(event) {{
if (isRotating) {{
var dx = event.clientX - lastX;
currentRotation += dx * 0.1;
g.attr("transform", "translate(" + currentTransform.x + "," + currentTransform.y + ") scale(" + currentTransform.k + ") rotate(" + currentRotation + ")");
lastX = event.clientX;
}}
}});
svg.on("mouseup", function() {{
isRotating = false;
}});
var tree = d3.tree()
.size([360, Math.min(window.innerWidth, window.innerHeight) / 2 - 100])
.separation(function(a, b) {{ return (a.parent === b.parent ? 1 : 2) / a.depth; }});
var root = d3.hierarchy(data);
tree(root);
var link = g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle(function(d) {{ return d.x / 180 * Math.PI; }})
.radius(function(d) {{ return d.y; }}));
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {{ return "translate(" + radialPoint(d.x, d.y) + ")"; }});
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dy", ".31em")
.attr("x", 10)
.attr("text-anchor", "start")
.attr("transform", function(d) {{
return "rotate(" + (d.x - 90) + ") translate(10, 0)";
}})
.html(function(d) {{
return d.data.name;
}});
function radialPoint(x, y) {{
return [(y = +y) * Math.cos((x - 90) / 180 * Math.PI), y * Math.sin((x - 90) / 180 * Math.PI)];
}}
</script>
</body>
</html>
"""
# Write the HTML content to a file
with open("d3_mindmap.html", "w") as f:
f.write(html_content)
print("D3.js mind map HTML generated as 'd3_mindmap.html'.")
# }}}
def find_project_by_id(project_data, full_data, target_id): # {{{
if project_data.get("id"):
if target_id in project_data.get("id"):
return project_data, full_data
for child in project_data.get("ch", []):
result, full_data = find_project_by_id(child, full_data, target_id)
if result:
return result, full_data
return None, full_data
# }}}
def show(parent_id, flat=False, filters_all=None, filters_any=None, color="grey", follow_mirrors=False, include_headers=False, show_description=True, show_id=False, all_children=False): # {{{
root_data = load_from_storage("root")
project_data, root_data = find_project_by_id(root_data, root_data, parent_id)
project_data, root_data = simplify_project(project_data, root_data, follow_mirrors=follow_mirrors)
if flat:
project_data = flatten_project(project_data)
if filters_all is not None:
project_data = filter_project_all(project_data, filters_all, include_headers=include_headers, all_children=all_children)
if filters_any is not None:
project_data = filter_project_any(project_data, filters_any, include_headers=include_headers, all_children=all_children)
project_data = replace(project_data, r" *<", "<")
project_data = replace(project_data, r" *$", "")
project_data = recolor(project_data, colors1)
project_data = strip(project_data, r"<a href=\".*\">.*</a>")
project_data = strip(project_data, r"<time .*</time>")
project_data = strip(project_data, r"<[^>]*>")
project_data = strip(project_data, r"</[^>]*>")
project_data = highlight(project_data)
# project_data = recolorBacklinks(project_data)
project_data = remove_double_spaces(project_data)
# console.print(f"\n[base3][bold]{project_data['name']}[/][/]")
# if project_data.get("description") and show_description:
# console.print(f"[base01]{project_data['description']}[/]")
console.print("")
print_pretty(project_data, color=color, show_description=show_description, show_id=show_id)
console.print("")
# generate_d3_mindmap_html(project_data, show_description=show_description, show_id=show_id)
return True
# }}}
def dump(): # {{{
storage = load_storage()
print(json.dumps(storage, indent=2))
# }}}
@click.group(invoke_without_command=True)
@click.option("--refresh", is_flag=True, help="Refresh session cookie and Workflowy data")
@click.option("--debug", is_flag=True, help="Enable debug mode")
@click.pass_context
def cli(ctx, refresh, debug):
"""
Workdlowy CLI
"""
if debug:
logger.setLevel(logging.DEBUG)
logger.debug("Debug mode enabled")
if refresh:
refresh_cookie()
refresh_workflowy_data()
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
@cli.command()
@click.argument("name", nargs=-1)
def inbox(name):
"""Inbox commands"""
if name:
name_text = ' '.join(name)
clip_to_workflowy(name_text, "", INBOX_ID)
else:
show(INBOX_ID)
@cli.command()
@click.option("--hide-comments", is_flag=True, help="Do not show comments")
@click.option("--hide-headers", is_flag=True, help="Hide headers")
@click.option("--show-id", is_flag=True, help="Show item id")
@click.argument("filter", nargs=-1)
def tasks(filter, hide_comments, hide_headers, show_id):
"""Tasks commands"""
if filter:
show(
TASKS_ID,
filters_all=filter,
flat=True,
follow_mirrors=True,
include_headers=True,
show_description=not hide_comments,
show_id=show_id,
)
else:
show(
TASKS_ID,
follow_mirrors=True,
show_description=not hide_comments,
show_id=show_id,
)
@cli.command()
@click.argument("filter", nargs=-1)
def today(filter):
"""Today commands"""
t = get_today()
logger.debug(f"Today: {t}")
if filter:
show(
TASKS_ID,
filters_all=[t] + list(filter),
flat=False,
follow_mirrors=True,
include_headers=True,
show_description=True,
show_id=False,
)
else:
show(
TASKS_ID,
filters_all=[t],
flat=False,
follow_mirrors=True,
include_headers=True,
show_description=True,
show_id=False,
)
@cli.command()
def week():
"""Today commands"""
sunday = get_sunday()
logger.debug(f"Sunday: {sunday}")
show(
DIARY_ID,
filters_all=[sunday],
flat=False,
follow_mirrors=True,
include_headers=True,
show_description=False,
show_id=False,
all_children=True
)
@cli.command(name="dump")
def dump_cmd():
"""Dump storage"""
dump()
@cli.command(name="refresh")
def refresh_cmd():
"""Refresh session cookie and Workflowy data"""
refresh_cookie()
refresh_workflowy_data()
def create_planner_command(planner_name, planner_id):
@click.command(name=planner_name, help=f"{planner_name.capitalize()} commands")
@click.option("--hide-comments", is_flag=True, help="Do not show comments")
@click.option("--flat", is_flag=True, help="Show flat list")
@click.option("--hide-headers", is_flag=True, help="Hide headers")
@click.option("--show-id", is_flag=True, help="Show item id")
@click.argument("filter", nargs=-1)
def planner(filter, hide_comments, flat, hide_headers, show_id):
if filter:
show(
planner_id,
filters_all=filter,
show_description=not hide_comments,
flat=flat,
include_headers=not hide_headers,
show_id=show_id,
)
else:
show(
planner_id,
show_description=not hide_comments,
flat=flat,
include_headers=not hide_headers,
show_id=show_id,
)
return planner
for planner_name, planner_id in PLANNER_IDS.items():
cli.add_command(create_planner_command(planner_name, planner_id))
if __name__ == "__main__":
cli()

32
workflowy-helper/workflowy_calendar.py

@ -1,32 +0,0 @@
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta
root = ET.Element("opml", version="2.0")
head = ET.SubElement(root, "head")
ET.SubElement(head, "ownerEmail").text = "[email protected]"
body = ET.SubElement(root, "body")
# Day colors for each weekday
day_colors = ["red", "teal", "pink", "green", "yellow", "sky", "purple"]
# Adjust start date to ensure weeks start on Sunday
start_date = datetime(2025, 1, 5)
for week_number in range(1, 53):
week_outline = ET.SubElement(body, "outline", text=f"2025 Week {week_number}")
ET.SubElement(week_outline, "outline", text="") # Empty bullet before the first day
for day in range(7):
current_date = start_date + timedelta(days=(week_number-1)*7 + day)
day_name = current_date.strftime('%A')
color = day_colors[current_date.weekday()]
day_text = f"<b><span class=\"colored bc-{color}\">{day_name}</span></b>"
note_text = (f"<time startYear=\"2025\" startMonth=\"{current_date.month}\" "
f"startDay=\"{current_date.day}\">"
f"{current_date.strftime('%a, %b %d, %Y')}</time>")
day_outline = ET.SubElement(week_outline, "outline", text=day_text, _note=note_text)
ET.SubElement(day_outline, "outline", text="")
# Output the XML as a string and print it
xml_str = ET.tostring(root, encoding="unicode")
print(xml_str)

294
workflowy-helper/~main.js

@ -1,294 +0,0 @@
(function() {
'use strict';
// update nodes {{{
function describeTimeElementDate(element) { // {{{
if (!(element instanceof HTMLTimeElement)) {
return "unknown";
}
const startYear = element.getAttribute('startyear');
const startMonth = element.getAttribute('startmonth');
const startDay = element.getAttribute('startday');
if (!startYear || !startMonth || !startDay || isNaN(startYear) || isNaN(startMonth) || isNaN(startDay)) {
return 'Invalid date attributes on the <time> element';
}
const timeElementDate = new Date(startYear, startMonth - 1, startDay);
timeElementDate.setHours(0, 0, 0, 0);
const today = new Date();
today.setHours(0, 0, 0, 0);
const dayDifference = Math.round((timeElementDate - today) / (1000 * 60 * 60 * 24));
const daysOfWeek = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const dayOfWeek = daysOfWeek[timeElementDate.getDay()];
const monthsOfYear = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const month = monthsOfYear[timeElementDate.getMonth()];
var ret = [dayOfWeek, month];
if (dayDifference === 0) {
ret.push('today');
} else if (dayDifference === 1) {
ret.push('tomorrow');
} else if (dayDifference === -1) {
ret.push('yesterday');
} else {
if (dayDifference < 0) {
ret.push('late');
// if (dayDifference > -8) {
// ret.push('last');
// } else {
// ret.push('older');
// }
} else {
ret.push('future');
// if (dayDifference < 8) {
// ret.push('next');
// } else {
// ret.push('later');
// }
}
}
return ret;
}
// }}}
function safeGetContent(parent, selector) { // {{{
var element = parent.querySelector(selector);
if (element !== null) {
var content = element.innerText;
if (content !== null) {
return content;
}
}
return ""
}
// }}}
function removeClassesStartingWith(element, prefix) { // {{{
const classes = element.classList;
for (const className of classes) {
if (className.startsWith(prefix)) {
element.classList.remove(className);
}
}
}
// }}}
function createImages(node, imgSrcs) { // {{{
var inject = node.querySelector('.inject');
if (inject === null) {
var inject = document.createElement("div");
inject.className = 'inject';
node.appendChild(inject);
}
var imgs = inject.querySelectorAll('img');
if (imgs.length != imgSrcs.length) {
inject.innerHTML = '';
imgSrcs.forEach(imgSrc => {
var img = document.createElement("img");
img.src = imgSrc;
img.style.display = "none";
img.onload = function () {
const maxDimension = 100;
const aspectRatio = img.naturalWidth / img.naturalHeight;
if (aspectRatio > 1) {
img.style.width = `${maxDimension}px`;
img.style.height = `${maxDimension / aspectRatio}px`;
} else {
img.style.height = `${maxDimension}px`;
img.style.width = `${maxDimension * aspectRatio}px`;
}
img.style.display = "block";
};
img.onclick = function () {
// open link in new tab
window.open(imgSrc, '_blank');
};
inject.appendChild(img);
});
}
}
// }}}
function updateProject(projectNode) { // {{{
removeClassesStartingWith(projectNode, "backlinked");
removeClassesStartingWith(projectNode, "link");
removeClassesStartingWith(projectNode, "tag");
removeClassesStartingWith(projectNode, "time");
const names = [];
const notes = [];
const breadcrumbNodes = [];
for (let i = 0; i < projectNode.children.length; i++) {
const child = projectNode.children[i];
if (child.classList.contains('name')) {
names.push(child);
}
if (child.classList.contains('notes')) {
notes.push(child);
}
if (child.classList.contains('_1zok2')) {
breadcrumbNodes.push(child.querySelector('.breadcrumbs'));
}
}
[notes, names].forEach(function(ns) {
if (ns.length > 0) {
const n = ns[0];
const text = safeGetContent(n, ".content > .innerContentContainer");
if (text.startsWith("!")) {
const links = n.querySelectorAll('a.contentLink');
const imgSrcs = [];
for (var i = 0; i < links.length; i++) {
var link = links[i];
link.textContent = "link";
var imgSrc = link.href;
imgSrcs.push(imgSrc);
}
if (imgSrcs.length > 0) {
createImages(n, imgSrcs);
}
}
const tags = n.querySelectorAll('.contentTag');
tags.forEach(tag => {
var tagText = safeGetContent(tag, ".contentTagText").trim();
if (tagText !== "") {
projectNode.classList.add("tagged");
projectNode.classList.add("tagged-" + tagText);
}
});
const links = n.querySelectorAll('.contentLink');
links.forEach(link => {
link.spellcheck = false;
const nonLetterRegex = /[^a-zA-Z0-9]/g;
var linkFull = link.innerText.trim().replace(nonLetterRegex, '');
if (linkFull.startsWith("x") || linkFull.startsWith("y") || linkFull.startsWith("z")) {
var className = "linked-" + linkFull;
var linkClassName = "link-" + linkFull;
projectNode.classList.add("linked");
projectNode.classList.add(className);
link.classList.add("link");
link.classList.add(linkClassName);
}
});
const times = n.querySelectorAll('time');
times.forEach(time => {
var relatives = describeTimeElementDate(time);
projectNode.classList.add("timed");
relatives.forEach(relative => {
projectNode.classList.add("timed-" + relative);
});
});
if (isIosSafari()) {
const content = n.querySelector('.content');
removeKeyboard(content);
}
}
});
}
// }}}
function updateAll() {
const projectNodes = document.querySelectorAll('.project');
projectNodes.forEach(projectNode => {
updateProject(projectNode);
});
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
var nodes = null;
if (mutation.addedNodes.length > 0) {
nodes = mutation.addedNodes;
} else if (mutation.removedNodes.length > 0) {
nodes = mutation.addedNodes;
}
if ( nodes !== null ) {
var node = mutation.target;
if ( node !== null ) {
var projectNode = node.closest('.project');
if ( projectNode !== null && projectNode !== node ) {
updateProject(projectNode);
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
updateAll();
// }}}
// hide keyboard on iOS Safari {{{
function isIosSafari() {
const ua = navigator.userAgent;
return (
/iPhone|iPad|iPod/.test(ua) &&
/Safari/.test(ua)
);
}
function removeKeyboard(contentElement) {
if (!('allowEditing' in contentElement)) {
contentElement.allowEditing = false;
}
contentElement.addEventListener('focus', (e) => {
if (!contentElement.allowEditing) {
e.preventDefault();
contentElement.blur();
}
});
contentElement.addEventListener('dblclick', (e) => {
contentElement.allowEditing = true;
// Small delay to ensure focus is handled correctly
setTimeout(() => {
contentElement.focus();
}, 0);
});
contentElement.addEventListener('blur', () => {
contentElement.allowEditing = false;
});
}
// }}}
})();

1297
workflowy-helper/~style.css

File diff suppressed because it is too large

346
workflowy-helper/~~style.css

@ -1,346 +0,0 @@
:root {
--grey: rgb(158, 161, 162);
--white: rgb(255, 255, 255);
--dark-grey: rgb(92, 96, 98);
--black: rgb(42, 49, 53);
--highlight: rgb(47, 55, 60);
--yellow: rgb(227, 160, 8);
--orange: rgb(255, 138, 76);
--red: #F74E5D; /* rgb(249, 128, 128); */
--magenta: rgb(244, 114, 182);
--violet: rgb(172, 148, 250);
--blue: rgb(56, 189, 248);
--cyan: rgb(45, 212, 191);
--green: rgb(49, 196, 141);
}
body {
--wf-tag-text-gray: var(--white) !important;
--wf-tag-background-gray: var(--white) !important;
--wf-tag-text-red: var(--red) !important;
--wf-tag-background-red: var(--red) !important;
--wf-tag-text-orange: var(--orange) !important;
--wf-tag-background-orange: var(--orange) !important;
--wf-tag-text-yellow: var(--yellow) !important;
--wf-tag-background-yellow: var(--yellow) !important;
--wf-tag-text-green: var(--green) !important;
--wf-tag-background-green: var(--green) !important;
--wf-tag-text-blue: var(--dark-grey) !important;
--wf-tag-background-blue: var(--dark-grey) !important;
--wf-tag-text-purple: var(--violet) !important;
--wf-tag-background-purple: var(--violet) !important;
--wf-tag-text-teal: var(--cyan) !important;
--wf-tag-background-teal: var(--cyan) !important;
--wf-tag-text-sky: var(--blue) !important;
--wf-tag-background-sky: var(--blue) !important;
--wf-tag-text-pink: var(--magenta) !important;
--wf-tag-background-pink: var(--magenta) !important;
--wf-tag-text: var(--black) !important;
--wf-color-bullet: var(--white) !important;
color: var(--grey) !important;
}
/* .root:not(.board) > .children, */
/* .root:not(.board) > .name { */
/* /1* max-width: 800px; *1/ */
/* } */
body::-webkit-scrollbar {
display: none;
}
.pageContainer {
max-width: 100vw !important;
overflow-x: hidden;
}
.page {
margin-right: 0 !important;
margin-left: 0 !important;
}
.mainTreeRoot > .name {
height: 0 !important;
}
.name > .content > .innerContentContainer {
overflow: hidden;
text-overflow: ellipsis;
}
.link,
.contentTag {
color: var(--dark-grey) !important;
font-weight: normal !important;
}
.homeButton > svg {
display: none;
}
.homeButton::after {
content: "✞";
font-size: 2em;
}
.header > :first-child {
display: none !important;
}
.metaMatches * .innerContentContainer,
.contentMatch {
background: none !important;
}
.project > .text-sm,
.paragraph {
margin: 0 !important;
}
.project .breadcrumbs {
position: absolute;
z-index: 20;
left: 25px;
top: -3px;
}
.project .breadcrumbs > a,
.project .breadcrumbs > svg,
.project .breadcrumbs > .menu {
display: none;
}
.project * .breadcrumbs > a:last-child {
font-size: 0;
display: initial !important;
}
.project * .breadcrumbs > a:last-child::after {
font-size: 12px;
content: "→";
}
.linked-xGOAL > .name .innerContentContainer,
.linked-xGOAL > .notes .innerContentContainer {
color: var(--yellow);
}
.linked-xGOAL > .name .innerContentContainer {
font-weight: bold;
}
.linked-xRESPONSIBILITY > .name .innerContentContainer,
.linked-xRESPONSIBILITY > .notes .innerContentContainer {
color: var(--red);
}
.linked-xRESPONSIBILITY > .name .innerContentContainer {
font-weight: bold;
}
.linked-xVISUALISATION > .name .innerContentContainer,
.linked-xVISUALISATION > .notes .innerContentContainer {
color: var(--green);
}
.linked-xVISUALISATION > .name .innerContentContainer {
font-weight: bold;
}
.linked-xPROJECT > .name .innerContentContainer {
font-weight: bold;
}
.linked-xPROJECT.tagged-ACTIVE > .name .innerContentContainer,
.linked-xPROJECT.tagged-ACTIVE > .notes .innerContentContainer {
color: var(--magenta);
}
.linked-xPROJECT.tagged-PLANT > .name .innerContentContainer,
.linked-xPROJECT.tagged-PLANT > .notes .innerContentContainer {
color: var(--orange);
}
.linked-xPROJECT.tagged-STALLED > .name .innerContentContainer,
.linked-xPROJECT.tagged-STALLED > .notes .innerContentContainer {
color: var(--cyan);
}
.linked-xPROJECT.tagged-SOMEDAY > .name .innerContentContainer,
.linked-xPROJECT.tagged-SOMEDAY > .notes .innerContentContainer {
color: var(--violet);
}
.linked-xHABIT > .name .innerContentContainer,
.linked-xHABIT > .notes .innerContentContainer {
color: var(--orange);
}
.linked-xTASK.tagged-READY > .name .innerContentContainer,
.linked-xTASK.tagged-READY > .notes .innerContentContainer {
color: var(--blue);
}
.linked-xTASK.tagged-DAILY > .name .innerContentContainer,
.linked-xTASK.tagged-DAILY > .notes .innerContentContainer,
.linked-xTASK.tagged-WEEKLY > .name .innerContentContainer,
.linked-xTASK.tagged-WEEKLY > .notes .innerContentContainer,
.linked-xTASK.tagged-MONTHLY > .name .innerContentContainer,
.linked-xTASK.tagged-MONTHLY > .notes .innerContentContainer,
.linked-xTASK.tagged-IRREGULAR > .name .innerContentContainer,
.linked-xTASK.tagged-IRREGULAR > .notes .innerContentContainer {
color: var(--green);
}
.linked-xTASK.tagged-WAITING > .name .innerContentContainer,
.linked-xTASK.tagged-WAITING > .notes .innerContentContainer {
color: var(--cyan);
}
.linked-xTASK.tagged-BLOCKED > .name .innerContentContainer,
.linked-xTASK.tagged-BLOCKED > .notes .innerContentContainer {
color: var(--red);
}
.linked-xTASK.tagged-MAYBE > .name .innerContentContainer,
.linked-xTASK.tagged-MAYBE > .notes .innerContentContainer {
color: var(--violet);
}
.tag-DAILY {
color: var(--blue) !important;
}
.tag-WEEKLY {
color: var(--green) !important;
}
.tag-MONTHLY {
color: var(--green) !important;
}
.tag-IRREGULAR {
color: var(--green) !important;
}
.tag-WORK {
color: var(--red) !important;
}
.tag-RELAX {
color: var(--yellow) !important;
}
.linked > .name .time > .content-wrapper {
color: var(--grey) !important;
}
.linked > .name .time-today > .content-wrapper {
color: var(--yellow) !important;
}
.project:not(.linked) > .notes .time-today > .content-wrapper {
background-color: var(--white) !important;
color: var(--black);
}
.linked > .name .time-tomorrow > .content-wrapper {
color: var(--violet) !important;
}
.linked > .name .time-late > .content-wrapper,
.linked > .name .time-yesterday > .content-wrapper {
color: var(--red) !important;
}
.matching-node {
display: none !important;
}
.project.collapsed:not(.linked) > .name,
.project.open:not(.linked) > .name,
.root > .name {
color: var(--white) !important;
}
.tagged-done > .name > .content,
.tagged-done > .notes > .content,
.tagged-done > .name time > span,
.tagged-missed > .name > .content,
.tagged-missed > .notes > .content,
.tagged-missed > .name time > span,
.tagged-na > .name > .content,
.tagged-na > .notes > .content,
.tagged-na > .name time > span {
/* opacity: 0.5 !important; */
/* text-decoration: none !important; */
text-decoration: line-through !important;
text-decoration-thickness: 2px !important;
}
.tagged-done > .name > .content,
.tagged-done > .name time > span {
text-decoration-color: var(--green) !important;
}
.tagged-missed > .name > .content,
.tagged-missed > .name time > span {
text-decoration-color: var(--red) !important;
}
.tagged-na > .name > .content,
.tagged-na > .name time > span {
text-decoration-color: var(--blue) !important;
}
.tag-new {
color: var(--blue) !important;
}
.tagged-event > .name > .content > .innerContentContainer,
.tagged-event > .name > .content > .innerContentContainer > .contentTag {
background-color: var(--red) !important;
color: var(--black) !important;
}
.tagged-reminder > .name > .content > .innerContentContainer,
.tagged-reminder > .name > .content > .innerContentContainer > .contentTag {
background-color: var(--blue) !important;
color: var(--black) !important;
}
.tagged-gym > .name > .content > .innerContentContainer,
.tagged-gym > .name > .content > .innerContentContainer > .contentTag {
background-color: var(--yellow) !important;
color: var(--black) !important;
}
.tagged-meeting > .name > .content > .innerContentContainer,
.tagged-meeting > .name > .content > .innerContentContainer > .contentTag {
background-color: var(--magenta) !important;
color: var(--black) !important;
}
.tagged-work > .name > .content > .innerContentContainer {
color: var(--red) !important;
}
.tagged-plan > .name > .content > .innerContentContainer {
color: var(--green) !important;
}
.half-open.tagged-a > .name > .bullet,
.collapsed.tagged-a > .name > .bullet,
.tagged-a > .name > .bullet {
color: var(--red) !important;
}
.half-open.tagged-b > .name > .bullet,
.collapsed.tagged-b > .name > .bullet,
.tagged-b > .name > .bullet {
color: var(--blue) !important;
}
.half-open.tagged-c > .name > .bullet,
.collapsed.tagged-c > .name > .bullet,
.tagged-c > .name > .bullet {
color: var(--green) !important;
}
.half-open.tagged-d > .name > .bullet,
.collapsed.tagged-d > .name > .bullet,
.tagged-d > .name > .bullet {
color: var(--violet) !important;
}
.timed-late:not(.linked) > .name,
.timed-late:not(.linked) > .notes,
.timed-late:not(.linked) > .children > .project:not(.timed) {
opacity: .5 !important;
}
Loading…
Cancel
Save