Skip to content

Instantly share code, notes, and snippets.

@knoopx
Last active July 16, 2023 07:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save knoopx/dea143840ba00ba566a914818aaf3d4b to your computer and use it in GitHub Desktop.
Save knoopx/dea143840ba00ba566a914818aaf3d4b to your computer and use it in GitHub Desktop.
logseq config: ui tweaks, minimalist and colorful
@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;
}
// 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}">🔁&#xFE0E;</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