25 changed files with 36 additions and 5600 deletions
File diff suppressed because it is too large
@ -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); |
|||
} |
|||
|
@ -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("") |
|||
.then(() => { |
|||
console.log("Image URL copied to clipboard:", imageUrl); |
|||
}) |
|||
.catch(error => { |
|||
console.error("Error copying image URL:", error); |
|||
}); |
|||
} |
|||
}); |
|||
|
File diff suppressed because one or more lines are too long
@ -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() |
@ -1,4 +0,0 @@ |
|||
from constants import Constants |
|||
|
|||
|
|||
|
@ -1,5 +0,0 @@ |
|||
browser-cookie3>=0.19.0 |
|||
rich>=13.8.0 |
|||
requests>=2.32.0 |
|||
uuid>=1.30 |
|||
click>=8.0.0 |
@ -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' |
|||
], |
|||
}, |
|||
) |
@ -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) |
@ -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(); |
|||
|
|||
|
|||
})(); |
@ -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", |
|||
} |
@ -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()) |
|||
|
|||
|
|||
|
@ -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", |
|||
}) |
@ -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) |
@ -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.") |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -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() |
@ -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) |
|||
|
@ -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; |
|||
}); |
|||
} |
|||
|
|||
// }}}
|
|||
|
|||
})(); |
File diff suppressed because it is too large
@ -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…
Reference in new issue