Last active
July 16, 2023 07:12
-
-
Save knoopx/dea143840ba00ba566a914818aaf3d4b to your computer and use it in GitHub Desktop.
logseq config: ui tweaks, minimalist and colorful
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url("https://fonts.googleapis.com/css2?family=Fira+Sans:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"); | |
@import url("https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"); | |
:root { | |
--ls-font-family: "Fira Sans"; | |
} | |
/* font weights */ | |
b, | |
strong, | |
h1.title, | |
.ls-page-title input, | |
.ls, | |
.font-bold, | |
.embed-header, | |
.page-property-key, | |
.editor-inner .multiline-block:is(.h1, .h2, .h3, .h4, .h5, .h6)::first-line, | |
.editor-inner .uniline-block:is(.h1, .h2, .h3, .h4, .h5, .h6), | |
.ls-block :is(h1, h2, h3, h4, h5, h6) { | |
font-weight: 500 !important; | |
} | |
/* page title */ | |
h1.title, | |
.ls-page-title input { | |
font-family: "Inter Tight" !important; | |
} | |
.ls-page-title.editing { | |
background-color: inherit; | |
} | |
/* todos */ | |
.canceled, | |
.cancelled, | |
.done { | |
opacity: 0.2; | |
} | |
.form-checkbox { | |
border-radius: 3px; | |
background-color: transparent; | |
border: 1px solid var(--ls-page-checkbox-color, #6093a0); | |
} | |
/* scheduled and deadlines */ | |
.timestamp .text-sm { | |
font-size: 0.75rem; | |
} | |
.timestamp a { | |
margin-left: calc(1rem + 5px); | |
color: var(--ls-primary-text-color, #24292e); | |
opacity: 0.6; | |
} | |
.timestamp-label { | |
display: none; | |
} | |
/* tags */ | |
a.tag { | |
background: var(--ls-tag-text-color); | |
border-radius: 3px 3px 3px 3px; | |
padding: 0px 3px; | |
color: white; | |
} | |
a.tag:hover { | |
opacity: 1; | |
color: white; | |
} | |
/* priority */ | |
.priority { | |
font-size: 0; | |
opacity: 1; | |
} | |
a.priority[href="#/page/A"]:before { | |
content: "🔴"; | |
} | |
a.priority[href="#/page/B"]:before { | |
content: "🟡"; | |
} | |
a.priority[href="#/page/C"]:before { | |
content: "🟢"; | |
} | |
a.priority:before { | |
font-size: 0.9rem; | |
margin: 0 2px; | |
} | |
/* views */ | |
[data-ref="sidenote"] { | |
display: none !important; | |
} | |
.ls-block[data-refs-self*="sidenote"] { | |
float: right; | |
width: 40%; | |
z-index: 9; | |
} | |
/* clutter */ | |
a.TODO { | |
display: none; | |
} | |
#head .button.text-sm.font-medium.block, | |
.cp__sidebar-help-btn { | |
display: none !important; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NOTE: Only works with default logseq date format | |
const enabledFeatures = [ | |
// replaces `Person/Bob` with `Bob` | |
stripNamespaces, | |
// display SCHEDULED and DEADLINE dates as relative time | |
relativeTimeStamps, | |
// auto-colorize tags with unique color. [over `text` or `background`] | |
coloredTags(), | |
// codemirror fix | |
codemirror("dracula"), | |
] | |
// END OF CONFIGURATION | |
function stripNamespaces(target) { | |
const deepChildNodes = (node) => [ | |
node, | |
...Array.from(node.childNodes).flatMap((x) => deepChildNodes(x)), | |
] | |
const patch = (cb) => (node) => { | |
deepChildNodes(node) | |
.filter((x) => x.nodeName === "#text") | |
.forEach((textNode) => { | |
if (textNode.parentElement.classList.contains("x-strip-ns")) return | |
if (cb(textNode)) { | |
textNode.parentElement.classList.add("x-strip-ns") | |
} | |
}) | |
} | |
match( | |
target, | |
'[data-ref*="/"]', | |
patch((textNode) => { | |
const segments = textNode.textContent.split("/") | |
if (segments.length <= 1) return | |
const content = | |
segments.length > 2 | |
? segments.slice(segments.length - 2, segments.length).join("/") | |
: segments[segments.length - 1] | |
if (textNode.textContent != content) { | |
textNode.parentElement.setAttribute("title", textNode.textContent) | |
textNode.textContent = content | |
return true | |
} | |
}), | |
) | |
} | |
function relativeTimeStamps(target) { | |
const patch = (cb) => (node) => { | |
const match = node.innerText.match( | |
/(?<dateTime>(?<year>\d{4})\-(?<month>\d{1,2})\-(?<day>\d{1,2}) (\w{3})( (?<hour>\d{1,2}):(?<minute>\d{2}))?)(?: [\.\+]+(?<repetition>\d.))?/, | |
) | |
if (match) { | |
const { | |
dateTime, | |
year, | |
month, | |
day, | |
hour = 0, | |
minute = 0, | |
repetition, | |
} = match.groups | |
const date = new Date(year, month - 1, day, hour, minute) | |
if (date instanceof Date && !isNaN(date)) { | |
cb(node, date, { dateTime, repetition }) | |
node.classList.add("x-time") | |
} | |
} | |
} | |
match( | |
target, | |
"time:not(.x-time)", | |
patch((node, date, { dateTime, repetition }) => { | |
// TODO: figure out a way to jump to the specified date | |
const ref = formatDate(date) | |
const go = document.createElement("a") | |
go.innerHTML = "▸" | |
go.style.marginLeft = "8px" | |
go.href = `#/page/${ref}` | |
// go.dataset.ref = ref | |
node.closest("a").after(go) | |
const span = `<span title="${repetition}">🔁︎</span>` | |
node.innerHTML = `${toRelativeTime(date)}${repetition ? ` ${span}` : ""}` | |
node.setAttribute("title", ref) | |
// is a pending todo from the past? | |
const parent = node.closest("[blockid]") | |
if (parent && parent.querySelector(".todo, .doing")) { | |
if (date.getTime() < Date.now()) { | |
const timestampNode = node.closest("a") | |
timestampNode.style.color = "rgba(239, 68, 68)" | |
timestampNode.classList.add("font-bold") | |
} | |
} | |
}), | |
) | |
} | |
function coloredTags(mode = "background") { | |
return (target) => { | |
match(target, ".tag:not(.colored)", (node) => { | |
if (mode === "background") { | |
const backgroundColor = stringToColor(node.innerText) | |
node.style.color = contrastingTextColor(backgroundColor) | |
node.style.backgroundColor = backgroundColor | |
} else { | |
node.style.color = stringToColor(node.innerText) | |
} | |
node.classList.add("colored") | |
}) | |
} | |
} | |
function codemirror(theme) { | |
return (target) => | |
match(target, ".cm-s-solarized", (node) => { | |
node.classList.remove("cm-s-solarized") | |
node.classList.remove("cm-s-light") | |
node.classList.add(`cm-s-${theme}`) | |
}) | |
} | |
// helpers | |
const match = (parent, selector, cb) => | |
Array.from(parent.querySelectorAll(selector)).forEach(cb) | |
const wrapArray = (val) => (Array.isArray(val) ? val : [val]) | |
const MINUTE = 60, | |
HOUR = MINUTE * 60, | |
DAY = HOUR * 24, | |
WEEK = DAY * 7, | |
MONTH = DAY * 30, | |
YEAR = DAY * 365 | |
const toRelativeTime = (date) => { | |
const deltaSeconds = Math.round((date.getTime() - Date.now()) / 1000) | |
const absSeconds = Math.abs(deltaSeconds) | |
let divisor | |
let unit = "" | |
if (absSeconds < MINUTE) { | |
;[divisor, unit] = [1, "seconds"] | |
} else if (absSeconds < HOUR) { | |
;[divisor, unit] = [MINUTE, "minutes"] | |
} else if (absSeconds < DAY) { | |
;[divisor, unit] = [HOUR, "hours"] | |
} else if (absSeconds < WEEK) { | |
;[divisor, unit] = [DAY, "days"] | |
} else if (absSeconds < MONTH) { | |
;[divisor, unit] = [WEEK, "weeks"] | |
} else if (absSeconds < YEAR) { | |
;[divisor, unit] = [MONTH, "months"] | |
} else { | |
;[divisor, unit] = [YEAR, "years"] | |
} | |
const formatter = new Intl.RelativeTimeFormat("en-US", { | |
numeric: "auto", | |
}) | |
return formatter.format(Math.ceil(deltaSeconds / divisor), unit) | |
} | |
const parseColor = (hex) => { | |
return hex.match(/\w\w/g).map((x) => parseInt(x, 16)) | |
} | |
const contrastingTextColor = (str) => { | |
const [r, g, b] = parseColor(str) | |
return (r * 299 + g * 587 + b * 114) / 1000 > 160 ? "black" : "white" | |
} | |
const hsv2rgb = (h, s, v) => { | |
let f = (n, k = (n + h / 60) % 6) => | |
v - v * s * Math.max(Math.min(k, 4 - k, 1), 0) | |
return [f(5), f(3), f(1)] | |
} | |
const stringToHue = (str) => { | |
var hash = 0 | |
if (str.length === 0) return hash | |
for (var i = 0; i < str.length; i++) { | |
hash = str.charCodeAt(i) + ((hash << 5) - hash) | |
hash = hash & hash | |
} | |
return hash % 360 | |
} | |
const stringToHSL = (str) => { | |
const range = (hash, min, max) => { | |
const diff = max - min | |
const x = ((hash % diff) + diff) % diff | |
return x + min | |
} | |
let hash = 0 | |
if (str.length === 0) return hash | |
for (let i = 0; i < str.length; i++) { | |
hash = str.charCodeAt(i) + ((hash << 5) - hash) | |
hash = hash & hash | |
} | |
h = range(hash, 0, 360) | |
s = range(hash, 75, 100) | |
l = range(hash, 40, 60) | |
return [h, s, l] | |
} | |
const ordinal = (n) => { | |
return ( | |
n + | |
(n > 0 | |
? ["th", "st", "nd", "rd"][(n > 3 && n < 21) || n % 10 > 3 ? 0 : n % 10] | |
: "") | |
) | |
} | |
const formatDate = (date) => { | |
const monthName = date | |
.toLocaleString("default", { month: "long" }) | |
.slice(0, 3) | |
const day = ordinal(date.getDate()) | |
const year = date.getFullYear() | |
return `${monthName} ${day}, ${year}` | |
} | |
const stringToColor = (str) => | |
[ | |
"#", | |
hsv2rgb(...stringToHSL(str)) | |
.map((x) => ("00" + Math.floor(x * 255).toString(16)).substr(-2)) | |
.join(""), | |
].join("") | |
setTimeout(() => { | |
const observer = new MutationObserver((mutations) => { | |
for (let mutation of mutations) { | |
enabledFeatures.forEach((t) => t(mutation.target)) | |
} | |
}) | |
const rootNode = document.getElementById("app-container") | |
enabledFeatures.forEach((t) => t(rootNode)) | |
observer.observe(rootNode, { | |
childList: true, | |
subtree: true, | |
attributes: true, | |
}) | |
}, 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment