From 5e94c3e8137f3ce6c5411dbb6dd24948855098e5 Mon Sep 17 00:00:00 2001 From: "WANGYQ\\wangyq" Date: Wed, 30 Oct 2024 10:52:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A7=BB=E5=8A=A8=E6=AE=B5?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quartz.config.ts | 2 +- quartz.layout.ts | 3 + quartz/extra/components/OverlayExplorer.tsx | 218 ++++++++++++++++++ quartz/extra/components/PageIcon.tsx | 23 ++ quartz/extra/components/index.ts | 7 + .../scripts/overlayexplorer.inline.ts | 108 +++++++++ .../components/styles/overlayexplorer.scss | 164 +++++++++++++ quartz/extra/components/styles/pageicon.scss | 6 + quartz/extra/static/icon.png | Bin 0 -> 4949 bytes quartz/extra/static/og-image.png | Bin 0 -> 8126 bytes quartz/extra/static/page_icon.png | Bin 0 -> 6876 bytes quartz/extra/styles/callouts.css | 14 ++ 12 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 quartz/extra/components/OverlayExplorer.tsx create mode 100644 quartz/extra/components/PageIcon.tsx create mode 100644 quartz/extra/components/index.ts create mode 100644 quartz/extra/components/scripts/overlayexplorer.inline.ts create mode 100644 quartz/extra/components/styles/overlayexplorer.scss create mode 100644 quartz/extra/components/styles/pageicon.scss create mode 100644 quartz/extra/static/icon.png create mode 100644 quartz/extra/static/og-image.png create mode 100644 quartz/extra/static/page_icon.png create mode 100644 quartz/extra/styles/callouts.css diff --git a/quartz.config.ts b/quartz.config.ts index b3c48762a..c69e8deac 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -8,7 +8,7 @@ import * as Plugin from "./quartz/plugins" */ const config: QuartzConfig = { configuration: { - pageTitle: "🪴 cris的obsidian分享", + pageTitle: "🪴 cris' blog", pageTitleSuffix: "", enableSPA: true, enablePopovers: true, diff --git a/quartz.layout.ts b/quartz.layout.ts index 1876f0e66..6cb74dddb 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -1,5 +1,6 @@ import { PageLayout, SharedLayout } from "./quartz/cfg" import * as Component from "./quartz/components" +import * as ExtraComponent from "./quartz/extra/components" // components shared across all pages export const sharedPageComponents: SharedLayout = { @@ -52,6 +53,7 @@ export const defaultContentPageLayout: PageLayout = { left: [ Component.PageTitle(), Component.MobileOnly(Component.Spacer()), + Component.MobileOnly(ExtraComponent.OverlayExplorer()), Component.Search(), Component.Darkmode(), Component.DesktopOnly( @@ -93,6 +95,7 @@ export const defaultListPageLayout: PageLayout = { left: [ Component.PageTitle(), Component.MobileOnly(Component.Spacer()), + Component.MobileOnly(ExtraComponent.OverlayExplorer()), Component.Search(), Component.Darkmode(), Component.DesktopOnly(Component.Explorer()), diff --git a/quartz/extra/components/OverlayExplorer.tsx b/quartz/extra/components/OverlayExplorer.tsx new file mode 100644 index 000000000..420cb3802 --- /dev/null +++ b/quartz/extra/components/OverlayExplorer.tsx @@ -0,0 +1,218 @@ +// Nothing yet +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../components/types" +import overlayexplorerStyle from "./styles/overlayexplorer.scss" + +// @ts-ignore +import script from "./scripts/overlayexplorer.inline" +import { FileNode, Options } from "../components/ExplorerNode" +import { QuartzPluginData } from "../plugins/vfile" +import { classNames } from "../util/lang" +import { i18n } from "../i18n" +import { joinSegments, resolveRelative } from "../util/path" + +interface OlOptions extends Omit { + folderClickBehavior: "collapse" | "link" | "mixed" +} + +const defaultOptions = { + folderClickBehavior: "mixed", + folderDefaultState: "collapsed", + useSavedState: true, + mapFn: (node) => { + return node + }, + sortFn: (a, b) => { + // Sort order: folders first, then files. Sort folders and files alphabetically + if ((!a.file && !b.file) || (a.file && b.file)) { + // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" + // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A + return a.displayName.localeCompare(b.displayName, undefined, { + numeric: true, + sensitivity: "base", + }) + } + + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, + filterFn: (node) => node.name !== "tags", + order: ["filter", "map", "sort"], +} satisfies OlOptions + +type OlExplorerNodeProps = { + node: FileNode + opts: OlOptions + fileData: QuartzPluginData + fullPath?: string +} + +function OverlayExplorerNode({node, opts, fullPath, fileData}: OlExplorerNodeProps) { + + // Calculate current folderPath + const folderPath = node.name !== "" ? joinSegments(fullPath ?? "", node.name) : "" + const href = resolveRelative(fileData.slug!, folderPath as SimpleSlug) + "/" + + return ( + <> + {node.file ? ( +
  • + + {node.displayName} + +
  • + ) : ( +
  • + {node.name !== "" && ( +
    + + + + {opts.folderClickBehavior === "link" ? ( + + {node.displayName} + + ) : ( + <> + + {opts.folderClickBehavior === "mixed" && ( + + + + + + + + )} + + )} +
    + )} +
    +
      + {node.children.map((childNode, i) => ( + + ))} +
    +
    +
  • + )} + + ) +} + +export default ((userOpts?: Partial) => { + // Parse config + const opts: OlOptions = { ...defaultOptions, ...userOpts } + + // memoized + let fileTree: FileNode + let lastBuildId: string = "" + + function constructFileTree(allFiles: QuartzPluginData[]) { + // Construct tree from allFiles + fileTree = new FileNode("") + allFiles.forEach((file) => fileTree.add(file)) + + // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) + if (opts.order) { + // Order is important, use loop with index instead of order.map() + for (let i = 0; i < opts.order.length; i++) { + const functionName = opts.order[i] + if (functionName === "map") { + fileTree.map(opts.mapFn) + } else if (functionName === "sort") { + fileTree.sort(opts.sortFn) + } else if (functionName === "filter") { + fileTree.filter(opts.filterFn) + } + } + } + } + + const OverlayExplorer: QuartzComponent = ({ + ctx, + cfg, + allFiles, + displayClass, + fileData, + }: QuartzComponentProps) => { + if (ctx.buildId !== lastBuildId) { + lastBuildId = ctx.buildId + constructFileTree(allFiles) + } + + return ( +
    + +
    +
    +
    +
      + +
    +
    +
    +
    +
    + ) + } + + OverlayExplorer.css = overlayexplorerStyle + OverlayExplorer.afterDOMLoaded = script + return OverlayExplorer +}) satisfies QuartzComponentConstructor diff --git a/quartz/extra/components/PageIcon.tsx b/quartz/extra/components/PageIcon.tsx new file mode 100644 index 000000000..19aeb116b --- /dev/null +++ b/quartz/extra/components/PageIcon.tsx @@ -0,0 +1,23 @@ +import { FullSlug, joinSegments, pathToRoot } from "../util/path" +import pageiconStyle from "./styles/pageicon.scss" + +import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../components/types" + + +export default (() => { + const PageIcon: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzComponentProps) => { + const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title + const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!) + const iconPath = joinSegments(baseDir, "static/page_icon.png") + + return ( + + {title} + + ) + } + + PageIcon.css = pageiconStyle + + return PageIcon +}) satisfies QuartzComponentConstructor diff --git a/quartz/extra/components/index.ts b/quartz/extra/components/index.ts new file mode 100644 index 000000000..9f9f3e3ea --- /dev/null +++ b/quartz/extra/components/index.ts @@ -0,0 +1,7 @@ +import OverlayExplorer from "./OverlayExplorer" +import PageIcon from "./PageIcon" + +export { + OverlayExplorer, + PageIcon, +} diff --git a/quartz/extra/components/scripts/overlayexplorer.inline.ts b/quartz/extra/components/scripts/overlayexplorer.inline.ts new file mode 100644 index 000000000..b3206baf6 --- /dev/null +++ b/quartz/extra/components/scripts/overlayexplorer.inline.ts @@ -0,0 +1,108 @@ +// Nothing yet + +import { registerEscapeHandler } from "../../components/scripts/util" + +type MaybeHTMLElement = HTMLElement | undefined + +function setFolder(folderPath: string, open: boolean) { + const childrenList = document.querySelector( + `[data-ol-children-for='${folderPath}']` + ) as MaybeHTMLElement + if (!childrenList) return + + const folderEntry = document.querySelector( + `[data-ol-selector-for='${folderPath}']` + ) as MaybeHTMLElement + if (!folderEntry) return + + const collapseIcon = folderEntry.getElementsByTagName( + "svg" + )[0] as MaybeHTMLElement + if (!collapseIcon) return + + if (open) { + childrenList.classList.add("open") + collapseIcon.classList.add("open") + } else { + childrenList.classList.remove("open") + collapseIcon.classList.remove("open") + } +} + +function setupOverlayExplorer() { + const openButton = document.getElementById("overlay-explorer-button") + const container = document.getElementById("overlay-explorer-container") + + const useSaveState = openButton?.dataset.olsavestate === "true" + + let folderOpenMap: Map + + if (useSaveState) { + const fromStorage = localStorage.getItem("olFileTree") + folderOpenMap = new Map(fromStorage ? JSON.parse(fromStorage) : []) + + for (let [key, value] of folderOpenMap) { + setFolder(key, value) + } + } + + function showExplorer() { + container?.classList.add("active") + } + + function hideExplorer() { + container?.classList.remove("active") + } + + function toggleFolder(evt: MouseEvent) { + evt.stopPropagation() + const target = evt.target as MaybeHTMLElement + if (!target) return + + const folderPath = target.parentNode.getAttribute("data-ol-selector-for") + const childrenList = document.querySelector( + `[data-ol-children-for='${folderPath}']` + ) as MaybeHTMLElement + if (!childrenList) return + + const collapseIcon = target.parentNode.getElementsByTagName( + "svg" + )[0] as MaybeHTMLElement + if (!collapseIcon) return + + childrenList.classList.toggle("open") + collapseIcon.classList.toggle("open") + + if (useSaveState) { + folderOpenMap.set(folderPath, collapseIcon.classList.contains("open")) + localStorage.setItem( + "olFileTree", + JSON.stringify(Array.from(folderOpenMap.entries())) + ) + } + } + + openButton.addEventListener("click", showExplorer) + window.addCleanup(() => openButton.removeEventListener("click", showExplorer)) + + // Set up click handlers for each folder (click handler on folder "icon") + for (const item of document.getElementsByClassName( + "ol-folder-icon", + ) as HTMLCollectionOf) { + item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) + } + + for (const item of document.getElementsByClassName( + "ol-folder-button", + ) as HTMLCollectionOf) { + item.addEventListener("click", toggleFolder) + window.addCleanup(() => item.removeEventListener("click", toggleFolder)) + } + + registerEscapeHandler(container, hideExplorer) +} + +document.addEventListener("nav", () => { + setupOverlayExplorer() +}) diff --git a/quartz/extra/components/styles/overlayexplorer.scss b/quartz/extra/components/styles/overlayexplorer.scss new file mode 100644 index 000000000..321c901b2 --- /dev/null +++ b/quartz/extra/components/styles/overlayexplorer.scss @@ -0,0 +1,164 @@ +// Nothing yet +@use "../../styles/variables.scss" as *; + +.ol-folder-icon { + margin-right: 5px; + color: var(--secondary); + cursor: pointer; + transition: transform 0.3s ease; + backface-visibility: visible; + transform: rotate(-90deg); +} + +.ol-folder-icon.open { + transform: rotate(0deg); +} + +.ol-folder-outer { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.3s ease-in-out; +} + +.ol-folder-outer.open { + grid-template-rows: 1fr; +} + +.ol-folder-outer > ul { + overflow: hidden; +} + +.ol-folder-entry { + flex-direction: row; + display: flex; + align-items: center; + user-select: none; +} + +.ol-folder-button { + color: var(--dark); + background-color: transparent; + border: none; + text-align: left; + cursor: pointer; + padding-left: 0; + padding-right: 0; + display: flex; + align-items: center; + font-family: var(--headerFont); + + margin-right: 5px; + + & span { + font-size: 0.95rem; + display: inline-block; + color: var(--secondary); + font-weight: $semiBoldWeight; + margin: 0; + line-height: 1.5rem; + pointer-events: none; + } +} + +svg { + pointer-events: all; + + & > polyline { + pointer-events: none; + } +} + +.overlay-explorer { + padding: 0 0.5rem; + border: none; + + & > #overlay-explorer-button { + background-color: var(--lightgray); + border: none; + border-radius: 4px; + font-family: inherit; + font-size: inherit; + height: 2rem; + padding: 0; + display: flex; + align-items: center; + text-align: inherit; + cursor: pointer; + white-space: nowrap; + width: 100%; + justify-content: space-between; + + & > p { + display: inline; + padding: 0 1rem; + } + + & svg { + width: 18px; + height: 18px; + min-width: 18px; + margin: 0 0.5rem; + } + } + + & >#overlay-explorer-container { + position: fixed; + contain: layout; + z-index: 999; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + overflow-y: auto; + display: none; + backdrop-filter: blur(4px); + + &.active { + display: inline-block; + } + + & > #overlay-explorer-space { + width: 65%; + margin-top: 12vh; + margin-left: auto; + margin-right: auto; + + @media all and (max-width: $pageWidth) { + max-width: 500px; + width: 90%; + } + + & > * { + width: 100%; + border-radius: 7px; + background: var(--light); + box-shadow: + 0 14px 50px rgba(27, 33, 48, 0.12), + 0 10px 30px rgba(27, 33, 48, 0.16); + margin-bottom: 2em; + } + + & > #overlay-explorer-content { + box-sizing: border-box; + padding: 0.5em 1em; + border: 1px solid var(--lightgray); + } + + & ul { + list-style: none; + margin: 0.08rem 0; + padding: 0; + transition: + max-height 0.35s ease, + transform 0.35s ease, + opacity 0.2s ease; + & li > a { + color: var(--dark); + opacity: 0.75; + pointer-events: all; + } + } + } + + } +} diff --git a/quartz/extra/components/styles/pageicon.scss b/quartz/extra/components/styles/pageicon.scss new file mode 100644 index 000000000..d3e2d67b6 --- /dev/null +++ b/quartz/extra/components/styles/pageicon.scss @@ -0,0 +1,6 @@ +@use "../../styles/variables.scss" as *; + +.pageicon { + width: 180; + height: 180; +} diff --git a/quartz/extra/static/icon.png b/quartz/extra/static/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..91a4803c812ef3bf4682e5634016655153229b52 GIT binary patch literal 4949 zcmeHLX;c(f7H&Wkafm1=Q8ei(8O5c#s;l>gmbHZzn?XQ86vyhSYN*f~y`u>_M4}Gr z!~iaVEI2VC?o5=U1W`~V=p4m~L1V-f;}(}u3^+ucSJlwKnVd7nb0+_&cOLK6efNIv z-tXOap;()g80Y6Z(U-&F_^IM!lfk%`=|023&*Bd@*ucVU=S#r;9-sDm z#2`)mNgo3IE2Lv$l2kDtVu|FDS2ipdp$S)Uz$`G=zM{5K|b z-Dp4D+#VL0leX2+6~bvZ?V2}Z`pw5XlRL)YKK^5(=HWH6X{YF68}=?=)x9j}*20Ec zFD7|qHfST`p+xjs^BT#TZ1LohC$sZP%f_wmu5dPP8#OkYQ*yN7_vn&sKRsRP=(2VF{BWy%MTB6Xd41!AZ%2R( zbkeCA)(rJLIbqcEaMGxucuu{EIWA6kgwuo*xs;Wwp|WX%0($uM5r|8Z3MgHqM%AVm zDu<3=W~Ne>C8iR~atRp;Ma=dMcgg{Pp0eUxr(S2U$ejv^#g&6OBSs)Dt76SnKp7y5 zVvJ^rE941zD6DkSb_|;B%MCY^T6uEpoL&gbL!*$7`IlOZUE zU>FQEU`xKiiaTM0WeNk~!ic3Tgqb#3X`_M5VB#91&8mPPu+HuCPj6DI-S7rWFAIPV z#EF{_J`Y9odSsx5#j3OekY0y=)xwgRZ=#T7%3`#c2})_F4Av-gs|>W% zYI&@YurcnbVigdxznnA@G%07NB8;z$(TN{T2MEavmYun;9kSR)`Luuw{9C=sq9MOx_qh(DTXkd?S@ zpjQkO37}9)M2aL55sV6@z#Bp)g#iQ~c*EyQB)EW&VqzAGB;<39W<3s$lh)(e6k;-D zvkMI2@~9-00>XG`za&YATeZMI0nMWgHfR4rDy^qdtT@9aUxLa6Qm~IyBEdl9{Yq(+ z*#a_=!DK5ZyTTlc90UW1#hE+>0IVFuB9AdsxYcM*H5zpa$X#>Y%W80)NZg9Y;#LZP zx~p(%UlpE-qH@&b00XBslC(Dee`zz9hZ_#`!I8()7O;OlD{|ea6e`d4=6co9>{a4& z*-IhE30DXf+)k0Kp8(6XMC9OxYzkD5-gNDg)4!4m7>Z-0KrDeVR7S!=j6h)xfn%_Y zFUBd965<%D8H{c*YON03Ohsh_9syS%J=xz++)(zQrVrM37w@+(N<4=8 zdnXmvzJxdX1@fCthhMVPM+sZL`zHOwo%2D!8a)1vL*6~%pPadMsRgJ0+}K`?&BCRL8ADYxltqc*^3G6SmHR3#-fG+z7d;7wntAy8SS1+|hN9 zrH77ht;-Ov1W4xExbu*>da3OqUi@)W`7fG)lAu$=8NUK+o-4Ph8+Ob{Y)`mUnkDI% zfBFC!c_-w?5@kYq=Pmj8F!zf1O}g!3DpiB)bsYF0IF ztp4UoR;ckp(T5qC2@(5SiYYH2#0T+RRA<^hJu6_;*#~2F;P9BXZC}|UnZ%oTyy`ND?Ezx!2%sU+xVg4940RzPkLYG%U;|tdK v1~a|^?}EF^q{@GED4kKDG5^$ZJy#S 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 YVmHYqY16=+dqVb4*;!q0zx&Jo3wdAG761SM literal 0 HcmV?d00001 diff --git a/quartz/extra/static/page_icon.png b/quartz/extra/static/page_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0934543e7bc821165d542a140015285e04123178 GIT binary patch literal 6876 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

    H0@NsZ_ncq&5^BEr)nA3$;KV^S`@RUID z0dQICo0#@aN=cC)*Il#7VUzI;Ew%+X^!+AydU-)dd#QdC7jM6vIoYc-90WiQg+dd* zULQKdE&m4v+1PZ>&MPY7(*)|Zxmh7v+HkE&UtOIjrM0$pcy$94rx07(+?*lU1|W*r zd+N&9Is+r4 zbe7koPj3M(m%_QYy57{77#WeC53GdJSsBef69O3bxwAxR8TikD~Kl zvdH`LnXSYzoH*8)B327hT$cAlm}c5{F)IJx4MzDqFb$o_BWIZ^IK5P1p?^NirrWPpXvf-@gm-@4N{3wVz&(r!vaU*g`=&kc53~=xlur)kC%1-*jqH>I-@kt^lm*-)Q|!TJ$Y~Qs zJmUFt?{B<1S^B;tCECdO=c4`n{f5guu`RVAt4Pbl$fF|Tntx+QQMy7H|>(s~&4kxkI)RU!XX4phYb*8fB8t{1hWP_KNS7o;%!qixE z@`X`r9w_1ux3zh@UkgjfOWJo=*kU}rRN0?;y+sFXw(trl1?*5%)zyt~Pxv;->DZ)8 zy1b609-=~j0v=gcT(yKd{cTD<0Rhb&kF(-c+0|_Ql@xg(ps>e+|4MJU5NBp)=2m#P zkf>wq;J~|I2L#fYZznmm@<`q$=1eMpfM|P8Cd9*IzhF#xq1I>JFn!gq#Q1ZvTGvs& z`Q$647*N^@@X3jZ?h|^H8yhHhlU`a{(HLJ~24X<9&keZ){LCbSCHPEGihNmFnQy0} zXL?`kSDxB05#%i5hZDbd*Hh!;7vD2nA;S87i(~P{JDwgdD?gS#S?EkWljrv1K+ML; zgDwO6>nUa-*7&gp=uCO?26t;~4)`(fccQeL{I;yI_3z$wt{tM7GLF}bJ|>t?w?$A? zy?8-_^#FngJ{IKJ-8Ur@dI#gJ2K<<+oM;mh6S~34k0CP)Qx@7>dZ6!gQkIpM#~tzT&N-Y5;+x6k$Gc0RIq-M~p$ufl_}(GbF8`Amr3t3GCOj18Ub6 z5kI_qW5cbB{N1!5FMgJkfot?@RKZR5;a8$+<0_!Bn|!;)EO;wi$Ha@5vlaXYFyk+PR7^jK zl!s%D&G9QLQ~paz*jl>jk>O#}N!-J{{yqRf4Z5KfBQTgv$nn;26ru1MTI^`UPsU+UoQ9@me<~fHEfL z1>91i?Wz9bRmvYT0&YktKVIF?=w`_4=(t;T*<=Ty_^{dU6~HddM~@zziT=pQ$fV?C zwVIk5L)_8+$J0`woQbM<`KrpWC@-ByZYimtFhrF0W7pOaB-I?4Rh%g7?Cjio2N{oW zW=;EgxX1dYQUavCuPAEvXF@DZRN%v3jwD@%ja?5wAFmpGJj_aNZM`1PrYsV`do@{; z+vU!MdDi8n@9DWnct$ExLJYpSp|M#slUXnnT`OAW*Zq}^d8;{SvOuW0x4V0OV09Pa z?CP4oI*3eHR2COc(A3i6PKF={`@uIXb268Jt60xEZ5jZB!w26%k+v6|WdwB`+)k`k zr=vT2^$-CA*Un;BEuOO4RG3A<@8wlsZO6&bGKtQ+M}vO!5x?Q_qp$B)SXdY*(`}`L zvUJnZLg0P&rAfMER1Ua|RocN8yJ*+2Sk|%XU$>_L7w8)qL9zq4FS(6=cmlkQ7Ac*A zG{7gNW_5Z72Fac3F99{^8yKhohlME;5ilPsEt4ZHDS3NE^vwVUSh-8#t$'); +} + +.callout[data-callout="system"] { + ---color: 255, 255, 255; + --callout-icon: url('data:image/svg+xml; utf8, '); +} + +.callout[data-callout="module"] { + ---color: 255, 255, 255; + --callout-icon: url('data:image/svg+xml; utf8, '); +}