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