Kyattsukuro 1162a6676a
Some checks failed
Release / release (push) Has been cancelled
added sorting pages by provided index, fixed overlaping svg
2025-08-20 15:34:13 +02:00

369 lines
9.7 KiB
TypeScript

import "@logseq/libs"
import { setup, t } from "logseq-l10n"
import { render } from "preact"
import { throttle } from "rambdax"
import FavList from "./comps/FavList"
import { allExpansionKeys, removeExpansionState } from "./libs/storage"
import { hash, queryForSubItems, setLanguage, waitForEl, queryPropertyAndSort } from "./libs/utils"
import zhCN from "./translations/zh-CN.json"
const CLEAN_WAIT = 3000
let dragHandle: HTMLElement | null = null
async function main() {
const { preferredLanguage: lang } = await logseq.App.getUserConfigs()
setLanguage(logseq.settings?.sortingLocale || lang)
await setup({ builtinTranslations: { "zh-CN": zhCN } })
provideStyles()
logseq.useSettingsSchema([
{
key: "hierarchyProperty",
title: "",
type: "string",
default: "tags",
description: t(
"It controls which property is used to decide a tag's hierarchy.",
),
},
{
key: "sortByProperty",
title: "",
type: "string",
default: "index",
description: t(
"It controls which property is used to sort favorite items.",
),
},
{
key: "filterIcon",
title: "",
type: "string",
default: "🔍",
description: t("Define an icon for quick filters."),
},
{
key: "hoverArrow",
title: "",
type: "boolean",
default: false,
description: t("Show arrows only when hovered."),
},
{
key: "taggedPageLimit",
title: "",
type: "number",
default: 30,
description: t(
"Maximum number of tagged pages to display on each level for favorites.",
),
},
{
key: "sortingLocale",
title: "",
type: "string",
default: "",
description: t(
"Locale used in sorting hierarchical favorites. E.g, zh-CN. Keep it empty to use Logseq's language setting.",
),
},
])
const favoritesObserver = new MutationObserver(async (mutationList) => {
const mutation = mutationList[0]
if (mutation?.target == null) return
const target = mutation.target as HTMLElement
if (
target.classList?.contains("nav-content-item-inner") ||
target.classList?.contains("favorites")
) {
await processFavorites()
}
})
const favoritesEl = await waitForEl("#left-sidebar .favorites", 300)
if (favoritesEl != null) {
favoritesObserver.observe(favoritesEl, { childList: true, subtree: true })
}
const transactionOff = logseq.DB.onChanged(onTransaction)
const graphOff = logseq.App.onCurrentGraphChanged(async () => {
;(window as any).storage = logseq.Assets.makeSandboxStorage()
await adjustLeftBarWidth()
})
await waitForEl("#left-sidebar .favorite-item", 1000)
;(window as any).storage = logseq.Assets.makeSandboxStorage()
const readKeys = new Set<string>()
await processFavorites(readKeys)
setTimeout(async () => {
const keys = await allExpansionKeys()
const notReadKeys = keys.filter((key: any) => !readKeys.has(key))
for (const key of notReadKeys) {
await removeExpansionState(key)
}
}, CLEAN_WAIT)
await adjustLeftBarWidth()
logseq.provideUI({
key: "kef-ft-drag-handle",
path: "#left-sidebar",
template: `<div class="kef-ft-drag-handle"></div>`,
})
setTimeout(() => {
dragHandle = parent.document.querySelector(
"#left-sidebar .kef-ft-drag-handle",
)!
dragHandle.addEventListener("pointerdown", onPointerDown)
}, 0)
logseq.beforeunload(async () => {
graphOff()
transactionOff()
favoritesObserver.disconnect()
dragHandle?.removeEventListener("pointerdown", onPointerDown)
})
console.log("#favorite-tree loaded")
}
function provideStyles() {
logseq.provideStyle({
key: "kef-ft-fav",
style: `
.kef-ft-fav-list {
padding-left: 24px;
display: none;
}
.kef-ft-fav-expanded {
display: block;
}
.kef-ft-fav-arrow {
flex: 0 0 auto;
padding: 4px 20px 4px 10px;
margin-right: -20px;
opacity: ${logseq.settings?.hoverArrow ? 0 : 1};
transition: opacity 0.3s;
}
:is(.favorite-item, .recent-item):hover > a > .kef-ft-fav-arrow,
.kef-ft-fav-item:hover > .kef-ft-fav-arrow {
opacity: 1;
}
.kef-ft-fav-arrow svg {
transform: rotate(90deg) scale(0.8);
transition: transform 0.04s linear;
}
.kef-ft-fav-arrow-expanded svg {
transform: rotate(0deg) scale(0.8);
}
.kef-ft-fav-item {
display: flex;
align-items: center;
padding: 0 24px;
line-height: 28px;
color: var(--ls-header-button-background);
cursor: pointer;
}
.kef-ft-fav-item:hover {
background-color: var(--ls-quaternary-background-color);
}
.kef-ft-fav-item-icon {
flex: 0 0 auto;
margin-right: 5px;
width: 16px;
text-align: center;
}
.kef-ft-fav-item-name {
flex: 1 1 auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.kef-ft-drag-handle {
content: "";
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 4px;
z-index: 10;
}
.kef-ft-drag-handle:hover,
.kef-ft-dragging .kef-ft-drag-handle {
cursor: col-resize;
background: var(--ls-active-primary-color);
}
.kef-ft-dragging {
cursor: col-resize;
}
.kef-ft-dragging :is(#left-sidebar, #main-content-container) {
pointer-events: none;
}
`,
})
}
async function processFavorites(readKeys?: Set<string>) {
const favorites = parent.document.querySelectorAll<HTMLElement>(
`#left-sidebar .favorite-item`,
)
//console.log("Processing favorites...")
//console.log(favorites)
// We do not want, to sort the base level
for (const fav of favorites) {
/*
items: [{displayName: "Eng",
name: "school/eng"
original-name: "School/Eng"
uuid: "68a2f289-0043-4718-be51-7b7687abbc2 }]
*/
const items = await queryForSubItems(fav.dataset.ref!)
//console.log(items)
if (items?.length > 0) {
injectList(fav, items, readKeys)
}
}
}
async function injectList(
el: HTMLElement,
items: any[],
readKeys?: Set<string>,
) {
const key = `kef-ft-f-${await hash(el.dataset.ref!)}`
const arrowContainer = el.querySelector("a")!
const arrow = arrowContainer.querySelector(".kef-ft-fav-arrow")
if (arrow != null) {
arrow.remove()
}
if (parent.document.getElementById(key) == null) {
logseq.provideUI({
key,
path: `.favorite-item[data-ref="${el.dataset.ref}"]`,
template: `<div id="${key}"></div>`,
})
}
setTimeout(() => {
renderList(key, items, arrowContainer, el.dataset.ref!, readKeys)
}, 0)
}
function renderList(
key: string,
items: any[],
arrowContainer: HTMLElement,
name: string,
readKeys?: Set<string>,
) {
const el = parent.document.getElementById(key)!
render(
<FavList
items={items}
arrowContainer={arrowContainer}
name={name}
readKeys={readKeys}
/>,
el,
)
}
async function onTransaction({ blocks, txData, txMeta }: any) {
if (needsProcessing(txData)) {
await processFavorites()
}
}
async function adjustLeftBarWidth() {
const graph = (await logseq.App.getCurrentGraph())!
const storedWidth = parent.localStorage.getItem(`kef-ft-lsw-${graph.name}`)
if (storedWidth) {
parent.document.documentElement.style.setProperty(
"--ls-left-sidebar-width",
`${+storedWidth}px`,
)
}
}
function needsProcessing(txData: any[]) {
const hierarchyProperty = logseq.settings?.hierarchyProperty ?? "tags"
let oldProperty, newProperty
let oldQuickFilters, newQuickFilters
for (const [_e, attr, val, _tx, added] of txData) {
if (attr === "originalName") return true
if (hierarchyProperty === "tags" && attr === "tags") return true
if (attr === "properties") {
if (val[hierarchyProperty]) {
if (added) {
newProperty = val[hierarchyProperty]
} else {
oldProperty = val[hierarchyProperty]
}
}
if (val.quickFilters) {
if (added) {
newQuickFilters = val.quickFilters
} else {
oldQuickFilters = val.quickFilters
}
}
}
}
if (
(!oldProperty && !newProperty && !oldQuickFilters && !newQuickFilters) ||
(oldProperty?.toString() === newProperty?.toString() &&
oldQuickFilters === newQuickFilters)
)
return false
return true
}
function onPointerDown(e: Event) {
e.preventDefault()
parent.document.documentElement.classList.add("kef-ft-dragging")
parent.document.addEventListener("pointermove", onPointerMove)
parent.document.addEventListener("pointerup", onPointerUp)
parent.document.addEventListener("pointercancel", onPointerUp)
}
function onPointerUp(e: MouseEvent) {
e.preventDefault()
parent.document.removeEventListener("pointermove", onPointerMove)
parent.document.removeEventListener("pointerup", onPointerUp)
parent.document.removeEventListener("pointercancel", onPointerUp)
parent.document.documentElement.classList.remove("kef-ft-dragging")
const pos = Math.max(150, e.clientX)
parent.document.documentElement.style.setProperty(
"--ls-left-sidebar-width",
`${pos}px`,
)
;(async () => {
const graph = (await logseq.App.getCurrentGraph())!
parent.localStorage.setItem(`kef-ft-lsw-${graph.name}`, `${pos}`)
})()
}
function onPointerMove(e: MouseEvent) {
e.preventDefault()
move(Math.max(150, e.clientX))
}
const move = throttle((pos) => {
parent.document.documentElement.style.setProperty(
"--ls-left-sidebar-width",
`${pos}px`,
)
}, 12)
logseq.ready(main).catch(console.error)