decorators to tags. This patch enables decorations for user specified tags - starting with handling only a single decoration - a 'prefix' to be added to the page name. Prefix is handled in the top bar title, page navigator, wiki-links appearing within a page as well as page autocomplete suggestions.pull/951/head
parent
f3a84e35c0
commit
850c06fd70
|
@ -33,3 +33,9 @@ export type ActionButton = {
|
|||
export type EmojiConfig = {
|
||||
aliases: string[][];
|
||||
};
|
||||
|
||||
export type Decoration = {
|
||||
tag: string;
|
||||
prefix: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -7,9 +7,20 @@ import {
|
|||
import { listFilesCached } from "../federation/federation.ts";
|
||||
import { queryObjects } from "../index/plug_api.ts";
|
||||
import { folderName } from "$sb/lib/resolve.ts";
|
||||
import { readSetting } from "$sb/lib/settings_page.ts";
|
||||
import { editor } from "$sb/syscalls.ts"
|
||||
import type { Decoration } from "$lib/web.ts";
|
||||
|
||||
let decorations: Decoration[] = [];
|
||||
|
||||
// Completion
|
||||
export async function pageComplete(completeEvent: CompleteEvent) {
|
||||
try {
|
||||
await updateDecoratorConfig();
|
||||
} catch (err: any) {
|
||||
await editor.flashNotification(err.message, "error");
|
||||
}
|
||||
|
||||
// Try to match [[wikilink]]
|
||||
let isWikilink = true;
|
||||
let match = /\[\[([^\]@$#:\{}]*)$/.exec(completeEvent.linePrefix);
|
||||
|
@ -82,10 +93,17 @@ export async function pageComplete(completeEvent: CompleteEvent) {
|
|||
from: completeEvent.pos - match[1].length,
|
||||
options: allPages.map((pageMeta) => {
|
||||
const completions: any[] = [];
|
||||
let namePrefix = "";
|
||||
const decor = decorations.find(d => pageMeta.tags?.some((t: any) => d.tag === t));
|
||||
if (decor) {
|
||||
namePrefix = decor.prefix;
|
||||
}
|
||||
if (isWikilink) {
|
||||
if (pageMeta.displayName) {
|
||||
const decoratedName = namePrefix + pageMeta.displayName;
|
||||
completions.push({
|
||||
label: `${pageMeta.displayName}`,
|
||||
displayLabel: decoratedName,
|
||||
boost: new Date(pageMeta.lastModified).getTime(),
|
||||
apply: pageMeta.tag === "template"
|
||||
? pageMeta.name
|
||||
|
@ -96,8 +114,10 @@ export async function pageComplete(completeEvent: CompleteEvent) {
|
|||
}
|
||||
if (Array.isArray(pageMeta.aliases)) {
|
||||
for (const alias of pageMeta.aliases) {
|
||||
const decoratedName = namePrefix + alias;
|
||||
completions.push({
|
||||
label: `${alias}`,
|
||||
displayLabel: decoratedName,
|
||||
boost: new Date(pageMeta.lastModified).getTime(),
|
||||
apply: pageMeta.tag === "template"
|
||||
? pageMeta.name
|
||||
|
@ -107,8 +127,10 @@ export async function pageComplete(completeEvent: CompleteEvent) {
|
|||
});
|
||||
}
|
||||
}
|
||||
const decoratedName = namePrefix + pageMeta.name;
|
||||
completions.push({
|
||||
label: `${pageMeta.name}`,
|
||||
displayLabel: decoratedName,
|
||||
boost: new Date(pageMeta.lastModified).getTime(),
|
||||
type: "page",
|
||||
});
|
||||
|
@ -135,6 +157,7 @@ export async function pageComplete(completeEvent: CompleteEvent) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta {
|
||||
const name = fileMeta.name.substring(0, fileMeta.name.length - 3);
|
||||
return {
|
||||
|
@ -146,3 +169,17 @@ function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta {
|
|||
lastModified: new Date(fileMeta.lastModified).toISOString(),
|
||||
} as PageMeta;
|
||||
}
|
||||
|
||||
let lastConfigUpdate = 0;
|
||||
|
||||
async function updateDecoratorConfig() {
|
||||
// Update at most every 5 seconds
|
||||
if (Date.now() < lastConfigUpdate + 5000) return;
|
||||
lastConfigUpdate = Date.now();
|
||||
const decoratorConfig = await readSetting("decorations");
|
||||
if (!decoratorConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
decorations = decoratorConfig;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { encodePageRef, parsePageRef } from "$sb/lib/page_ref.ts";
|
||||
import { Fragment, renderHtml, Tag } from "./html_render.ts";
|
||||
import { isLocalPath } from "$sb/lib/resolve.ts";
|
||||
import { PageMeta } from "$sb/types.ts";
|
||||
|
||||
export type MarkdownRenderOptions = {
|
||||
failOnUnknown?: true;
|
||||
|
@ -554,20 +555,29 @@ function traverseTag(
|
|||
export function renderMarkdownToHtml(
|
||||
t: ParseTree,
|
||||
options: MarkdownRenderOptions = {},
|
||||
allPages: PageMeta[] = [],
|
||||
) {
|
||||
preprocess(t);
|
||||
const htmlTree = posPreservingRender(t, options);
|
||||
if (htmlTree && options.translateUrls) {
|
||||
if (htmlTree) {
|
||||
traverseTag(htmlTree, (t) => {
|
||||
if (typeof t === "string") {
|
||||
return;
|
||||
}
|
||||
if (t.name === "img") {
|
||||
if (t.name === "img" && options.translateUrls) {
|
||||
t.attrs!.src = options.translateUrls!(t.attrs!.src!, "image");
|
||||
}
|
||||
|
||||
if (t.name === "a" && t.attrs!.href) {
|
||||
if (options.translateUrls) {
|
||||
t.attrs!.href = options.translateUrls!(t.attrs!.href, "link");
|
||||
}
|
||||
if (t.attrs!["data-ref"]?.length) {
|
||||
const pageMeta = allPages.find(p => t.attrs!["data-ref"]!.startsWith(p.name));
|
||||
if (pageMeta) {
|
||||
t.body = [(pageMeta.pageDecorations?.prefix ?? "") + t.body]
|
||||
}
|
||||
}
|
||||
if (t.body.length === 0) {
|
||||
t.body = [t.attrs!.href];
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { defaultSettings } from "$common/settings.ts";
|
|||
import {
|
||||
ActionButton,
|
||||
EmojiConfig,
|
||||
Decoration,
|
||||
FilterOption,
|
||||
Notification,
|
||||
PanelMode,
|
||||
|
@ -24,6 +25,7 @@ export type BuiltinSettings = {
|
|||
// Format: compatible with docker ignore
|
||||
spaceIgnore?: string;
|
||||
emoji?: EmojiConfig;
|
||||
decorations?: Decoration[];
|
||||
};
|
||||
|
||||
export type PanelConfig = {
|
||||
|
|
|
@ -109,7 +109,7 @@ export class MarkdownWidget extends WidgetType {
|
|||
return url;
|
||||
},
|
||||
preserveAttributes: true,
|
||||
});
|
||||
}, this.client.ui.viewState.allPages);
|
||||
|
||||
if (cachedHtml === html) {
|
||||
// HTML still same as in cache, no need to re-render
|
||||
|
|
|
@ -66,9 +66,9 @@ export function cleanWikiLinkPlugin(client: Client) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const pageMeta = client.ui.viewState.allPages.find(p => p.name == url);
|
||||
const linkText = alias ||
|
||||
(url.includes("/") ? url.split("/").pop()! : url);
|
||||
(pageMeta?.pageDecorations.prefix ?? "") + (url.includes("/") ? url.split("/").pop()! : url);
|
||||
|
||||
// And replace it with a widget
|
||||
widgets.push(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FilterList } from "./filter.tsx";
|
||||
import { FilterOption } from "$lib/web.ts";
|
||||
import { FilterOption, Decoration } from "$lib/web.ts";
|
||||
import { CompletionContext, CompletionResult } from "@codemirror/autocomplete";
|
||||
import { PageMeta } from "../../plug-api/types.ts";
|
||||
import { isFederationPath } from "$sb/lib/resolve.ts";
|
||||
|
@ -65,6 +65,7 @@ export function PageNavigator({
|
|||
}
|
||||
options.push({
|
||||
...pageMeta,
|
||||
name: (pageMeta.pageDecorations?.prefix ?? "") + pageMeta.name,
|
||||
description,
|
||||
orderId: orderId,
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ export function TopBar({
|
|||
lhs,
|
||||
onClick,
|
||||
rhs,
|
||||
pageNamePrefix,
|
||||
}: {
|
||||
pageName?: string;
|
||||
unsavedChanges: boolean;
|
||||
|
@ -46,6 +47,7 @@ export function TopBar({
|
|||
actionButtons: ActionButton[];
|
||||
lhs?: ComponentChildren;
|
||||
rhs?: ComponentChildren;
|
||||
pageNamePrefix?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
|
@ -57,6 +59,7 @@ export function TopBar({
|
|||
<div className="main">
|
||||
<div className="inner">
|
||||
<div className="wrapper">
|
||||
<div className="sb-page-prefix">{pageNamePrefix}</div>
|
||||
<span
|
||||
id="sb-current-page"
|
||||
className={isLoading
|
||||
|
|
|
@ -314,6 +314,7 @@ export class MainUI {
|
|||
style={{ flex: viewState.panels.lhs.mode }}
|
||||
/>
|
||||
)}
|
||||
pageNamePrefix={viewState.currentPageMeta?.pageDecorations?.prefix ?? ""}
|
||||
/>
|
||||
<div id="sb-main">
|
||||
{!!viewState.panels.lhs.mode && (
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { PageMeta } from "../plug-api/types.ts";
|
||||
import { Action, AppViewState } from "../type/web.ts";
|
||||
import { PageState } from "./navigator.ts";
|
||||
|
||||
export default function reducer(
|
||||
state: AppViewState,
|
||||
|
@ -20,6 +22,18 @@ export default function reducer(
|
|||
};
|
||||
case "page-loaded": {
|
||||
const mouseDetected = window.matchMedia("(pointer:fine)").matches;
|
||||
const pageMeta = state.allPages.find(p => p.name == action.meta.name);
|
||||
const decor = state.settings.decorations?.filter(d => pageMeta?.tags?.some(t => d.tag === t));
|
||||
if (decor && decor.length > 0) {
|
||||
const mergedDecorations = decor.reduceRight((accumulator, el) => {
|
||||
accumulator = {...accumulator, ...el};
|
||||
return accumulator;
|
||||
});
|
||||
if (mergedDecorations) {
|
||||
const { tag, ...currPageDecorations } = mergedDecorations;
|
||||
action.meta.pageDecorations = currPageDecorations;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
|
@ -58,16 +72,37 @@ export default function reducer(
|
|||
const oldPageMeta = new Map(
|
||||
[...state.allPages].map((pm) => [pm.name, pm]),
|
||||
);
|
||||
let currPageMeta = oldPageMeta.get(state.currentPage!);
|
||||
if (currPageMeta === undefined) {
|
||||
currPageMeta = {} as PageMeta;
|
||||
}
|
||||
for (const pageMeta of action.allPages) {
|
||||
const oldPageMetaItem = oldPageMeta.get(pageMeta.name);
|
||||
if (oldPageMetaItem && oldPageMetaItem.lastOpened) {
|
||||
pageMeta.lastOpened = oldPageMetaItem.lastOpened;
|
||||
}
|
||||
const decor = state.settings.decorations?.filter(d => pageMeta.tags?.some((t: any) => d.tag === t));
|
||||
// Page can have multiple decorations applied via different tags, accumulate them.
|
||||
// The decorations higher in the decorations list defined in SETTINGS gets
|
||||
// higher precedence.
|
||||
if (decor && decor.length > 0) {
|
||||
const mergedDecorations = decor.reduceRight((accumulator, el) => {
|
||||
accumulator = {...accumulator, ...el};
|
||||
return accumulator;
|
||||
});
|
||||
if (mergedDecorations) {
|
||||
const { tag, ...currPageDecorations} = mergedDecorations;
|
||||
pageMeta.pageDecorations = currPageDecorations;
|
||||
if (pageMeta.name === state.currentPage) {
|
||||
currPageMeta!.pageDecorations = currPageDecorations;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
allPages: action.allPages,
|
||||
currentPageMeta: currPageMeta,
|
||||
};
|
||||
}
|
||||
case "start-navigate": {
|
||||
|
|
|
@ -207,6 +207,16 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.sb-page-prefix {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex: 0 0 auto;
|
||||
text-align: left;
|
||||
padding: 1px;
|
||||
margin-right: 3px;
|
||||
font-family: var(--ui-font);
|
||||
}
|
||||
|
||||
.sb-panel {
|
||||
iframe {
|
||||
background-color: var(--root-background-color);
|
||||
|
|
Loading…
Reference in New Issue