From e1e05527e2b475b3877b115a82c67d0a0a7c554f Mon Sep 17 00:00:00 2001 From: yoyurec Date: Wed, 13 Jul 2022 03:02:12 +0300 Subject: [PATCH] Refactoring, banner Y position --- package.json | 2 +- src/main.css | 61 ++++++++ src/main.tsx | 399 ++++++++++++++++++++------------------------------- 3 files changed, 214 insertions(+), 248 deletions(-) create mode 100644 src/main.css diff --git a/package.json b/package.json index 27a7616..cce5a19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logseq-banners-plugin", - "version": "1.8.0", + "version": "1.9.0", "main": "dist/index.html", "scripts": { "dev": "vite", diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..68785b1 --- /dev/null +++ b/src/main.css @@ -0,0 +1,61 @@ +body:is([data-page="page"], [data-page="home"]).is-banner-active #main-content-container { + flex-wrap: wrap; + align-content: flex-start; +} + +body:is([data-page="page"], [data-page="home"]) .content { + position: relative; +} + +body:is([data-page="page"], [data-page="home"]).is-banner-active #main-content-container::before { + display: block; + content: ""; + width: 100%; + height: var(--bannerHeight); + margin: 0 auto; + background-image: var(--pageBanner); + background-repeat: no-repeat; + background-size: cover; + background-position-x: 50%; + background-position-y: var(--bannerAlign); +} + +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active .journal-item:first-of-type>.page>.flex-col>.content>.flex-1::before, +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active .page>.relative>.flex-row>.flex-1::before { + content: var(--pageIcon); + font-size: var(--iconWidth); + font-weight: normal; + position: absolute; + left: -7px; + z-index: 2; + transform: translateY(-55%); + line-height: initial; +} + +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active :is(.ls-page-title, .page-title, .journal-title) { + margin-top: 35px; +} + +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active #journals .journal-item:first-child { + margin-top: 0; +} + +/* small icons for other home journal items */ +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active #journals .journal-item h1.title::before { + content: var(--pageIcon); + margin-right: 8px; + font-size: 0.9em; + font-weight: normal; +} + +body:is([data-page="page"], [data-page="home"]).is-banner-active.is-icon-active #journals .journal-item:first-of-type h1.title::before { + display: none; +} + +.cp__right-sidebar-inner .resizer { + z-index: 9; +} + +.page-title .page-icon { + display: none; +} diff --git a/src/main.tsx b/src/main.tsx index f31308c..a5c2d80 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,35 +3,34 @@ import "@logseq/libs"; import { logseq as PL } from "../package.json"; import { SettingSchemaDesc, PageEntity, BlockEntity } from "@logseq/libs/dist/LSPlugin.user"; +import mainStyles from "./main.css?raw"; + const pluginId = PL.id; let root: HTMLElement; let body: HTMLElement; -type AssetsRecord = { - [prop: string]: Assets; +type AssetDataList = { + [prop: string]: AssetData; } -type Assets = { - iconHeight?: string, - bannerHeight?: string; +type AssetData = { banner?: string; + bannerHeight?: string; + bannerAlign?: string; pageIcon?: string; + iconWidth?: string; + icon?: string; } -type UseDefaultType = { - banner: boolean; - pageIcon: boolean; +enum AssetType { + banner = "banner", + pageIcon = "pageIcon" } -type AssetType = keyof UseDefaultType; - let pageType: string; -let isJournal: boolean; -let currentPageProps: PageEntity | null; -let useDefault: UseDefaultType; -let defaultConfig: AssetsRecord; -let customPropsConfig: AssetsRecord; +let defaultConfig: AssetDataList; +let customPropsConfig: AssetDataList; let timeout: number; let hidePluginProps: boolean; @@ -39,17 +38,17 @@ const settingsDefaultPageBanner = "https://wallpaperaccess.com/full/1146672.jpg" const settingsDefaultJournalBanner = "https://images.unsplash.com/photo-1646026371686-79950ceb6daa?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1034&q=80"; const settingsArray: SettingSchemaDesc[] = [ { - key: "bannerHeading", - title: "Banner settings", + key: "journalHeading", + title: "Journal and home settings", //@ts-expect-error type: "heading" }, { - key: "pageBannerHeight", - title: "Banner height for common page", + key: "defaultJournalBanner", + title: "Default banner for journal and home page", type: "string", description: "", - default: "200px", + default: settingsDefaultJournalBanner, }, { key: "journalBannerHeight", @@ -59,52 +58,52 @@ const settingsArray: SettingSchemaDesc[] = [ default: "300px", }, { - key: "useDefaultBanner", - title: "", - type: "boolean", - description: "Use default banner (settings below) when 'banner::' property is not set?", - default: true, + key: "journalBannerAlign", + title: "Default banner vertical align for journal and home page", + type: "string", + description: "", + default: "50%" }, { - key: "defaultPageBanner", - title: "Default banner for common page", + key: "defaultJournalIcon", + title: "Default icon (emoji) for journal and home page", type: "string", description: "", - default: settingsDefaultPageBanner, + default: "📅", }, { - key: "defaultJournalBanner", - title: "Default banner for journal and home page", + key: "journalIconWidth", + title: "Icon height for journal & home page (in px)", type: "string", description: "", - default: settingsDefaultJournalBanner, + default: "50px", }, { - key: "iconHeading", - title: "Icon settings", + key: "pageHeading", + title: "Common page settings", //@ts-expect-error type: "heading" }, { - key: "pageIconHeight", - title: "Icon height for common page (in px)", + key: "defaultPageBanner", + title: "Default banner for common page", type: "string", description: "", - default: "40px", + default: settingsDefaultPageBanner, }, { - key: "journalIconHeight", - title: "Icon height for journal & home page (in px)", + key: "pageBannerHeight", + title: "Banner height for common page", type: "string", description: "", - default: "50px", + default: "200px", }, { - key: "useDefaultIcon", - title: "", - type: "boolean", - description: "Use default icon (settings below) when 'icon::' or 'page-icon::' property is not set?", - default: true, + key: "pageBannerAlign", + title: "Default banner vertical align for common page", + type: "string", + description: "", + default: "50%" }, { key: "defaultPageIcon", @@ -114,11 +113,11 @@ const settingsArray: SettingSchemaDesc[] = [ default: "📄", }, { - key: "defaultJournalIcon", - title: "Default icon (emoji) for journal and home page", + key: "pageIconWidth", + title: "Icon height for common page (in px)", type: "string", description: "", - default: "📅", + default: "40px", }, { key: "otherHeading", @@ -142,11 +141,15 @@ const settingsArray: SettingSchemaDesc[] = [ "pageType": { "evrgrn": { "pageIcon": "🌳", - "banner": "https://images.unsplash.com/photo-1502082553048-f009c37129b9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "banner": "https://images.unsplash.com/photo-1502082553048-f009c37129b9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80", + "bannerHeight": "200px", + "bannerAlign": "50%" }, "seed": { "pageIcon": "🌱", - "banner": "https://images.unsplash.com/photo-1631949454967-6c6d07fb59cd?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "banner": "https://images.unsplash.com/photo-1631949454967-6c6d07fb59cd?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80", + "bannerHeight": "200px", + "bannerAlign": "50%" } }, "anotherCamalCasedPropName": {} @@ -162,66 +165,11 @@ const settingsArray: SettingSchemaDesc[] = [ ] const initStyles = () => { - logseq.provideStyle(` - body:is([data-page="page"],[data-page="home"]).is-banner-active #main-content-container { - flex-wrap: wrap; - align-content: flex-start; - } - body:is([data-page="page"],[data-page="home"]) .content { - position: relative; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active #main-content-container::before { - display: block; - content: ""; - width: 100%; - height: var(--bannerHeight); - margin: 0 auto; - background-image: var(--pageBanner); - background-repeat: no-repeat; - background-size: cover; - background-position: 50%; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active .journal-item:first-of-type > .page > .flex-col > .content > .flex-1::before, - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active .page > .relative > .flex-row > .flex-1::before { - content: var(--pageIcon); - font-size: var(--iconHeight); - font-weight: normal; - position: absolute; - left: -7px; - z-index:2; - transform: translateY(-55%); - line-height: initial; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active :is(.ls-page-title, .page-title, .journal-title) { - margin-top: 35px; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active #journals .journal-item:first-child { - margin-top: 0; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active #journals .journal-item h1.title::before { - content: "${defaultConfig.journal.pageIcon}"; - margin-right: 8px; - font-size: 0.9em; - font-weight: normal; - } - body:is([data-page="page"],[data-page="home"]).is-banner-active.is-icon-active #journals .journal-item:first-of-type h1.title::before { - display: none; - } - .cp__right-sidebar-inner .resizer { - z-index:9; - } - .page-title .page-icon { - display: none; - } - `) + logseq.provideStyle(mainStyles); } // Read settings const readPluginSettings = () => { - useDefault = { - banner: false, - pageIcon: false - }; defaultConfig = { page: {}, journal: {} @@ -230,22 +178,50 @@ const readPluginSettings = () => { if (pluginSettings) { ({ hidePluginProps, - useDefaultBanner: useDefault.banner, - useDefaultIcon: useDefault.pageIcon, - pageBannerHeight: defaultConfig.page.bannerHeight, - pageIconHeight: defaultConfig.page.iconHeight, defaultPageBanner: defaultConfig.page.banner, + pageBannerHeight: defaultConfig.page.bannerHeight, + pageBannerAlign: defaultConfig.page.bannerAlign, defaultPageIcon: defaultConfig.page.pageIcon, - journalBannerHeight: defaultConfig.journal.bannerHeight, - journalIconHeight: defaultConfig.journal.iconHeight, + pageIconWidth: defaultConfig.page.iconWidth, defaultJournalBanner: defaultConfig.journal.banner, + journalBannerHeight: defaultConfig.journal.bannerHeight, + journalBannerAlign: defaultConfig.journal.bannerAlign, defaultJournalIcon: defaultConfig.journal.pageIcon, + journalIconWidth: defaultConfig.journal.iconWidth, customPropsConfig, timeout } = pluginSettings); } - if (useDefault.banner) { - encodeDefaultBanners(); + encodeDefaultBanners(); +} + +// Generate Base64 from image URL +const getBase64FromUrl = async (url: string): Promise => { + let data; + try { + data = await fetch(url); + } catch (error) { + return ""; + } + const blob = await data.blob(); + return new Promise((resolve) => { + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => { + const base64data = reader.result as string; + resolve(base64data); + } + }); +} + +// Get and encode default banners for caching +// skip caching if random image from Unsplash API used +const encodeDefaultBanners = async () => { + if (defaultConfig.page.banner && !(defaultConfig.page.banner?.includes("source.unsplash.com"))) { + defaultConfig.page.banner = await getBase64FromUrl(defaultConfig.page.banner); + } + if (defaultConfig.journal.banner && !(defaultConfig.journal.banner?.includes("source.unsplash.com"))) { + defaultConfig.journal.banner = await getBase64FromUrl(defaultConfig.journal.banner); } } @@ -264,25 +240,16 @@ const hideProps = () => { // Render const render = async () => { hideProps(); - // "Delete" icon on ever render start if no default allowed - if (!useDefault.pageIcon) { - clearIcon(); - } - // "Delete" banner on ever render start if no default allowed - if (!useDefault.banner) { - clearBanner(); - } pageType = getPageType(); if (!isPageTypeOk()) { clearIcon(); clearBanner(); return; } - currentPageProps = null; - if (pageType !== "home") { - currentPageProps = await getPageProps(); + const pageAssetsData: AssetData = await getPageAssetsData(); + if (pageAssetsData) { + renderBanner(pageAssetsData); } - renderBanner(); } // Get page type @@ -298,8 +265,37 @@ const isPageTypeOk = () => { return false; } -const getPageProps = async () => { - let currentPageData: PageEntity | BlockEntity | null = null; +const getPageAssetsData = async (): Promise => { + let pageAssetsData = defaultConfig.journal; + let currentPageProps: any = {}; + // home = journal page? + if (pageType === "home") { + console.info(`#${pluginId}: Homepage`); + return pageAssetsData; + } + // journal page? + const currentPageData = await getPageData(); + if (currentPageData["journal?"]) { + console.info(`#${pluginId}: Journal page`); + return pageAssetsData; + } + // common page? + console.info(`#${pluginId}: Trying page props`); + currentPageProps = currentPageData.properties; + if (currentPageProps) { + // get custom config, override with high proirity page props + const customAssetData = getCustomAssetData(currentPageProps); + pageAssetsData = { ...defaultConfig.page, ...customAssetData, ...currentPageProps } + } else { + console.info(`#${pluginId}: Default page`); + pageAssetsData = defaultConfig.page; + } + console.info(`#${pluginId}: pageAssetsData -- `, pageAssetsData); + return pageAssetsData; +} + +const getPageData = async (): Promise => { + let currentPageData = null; currentPageData = await logseq.Editor.getCurrentPage(); if (currentPageData) { // Check if page is a child and get parent ID @@ -309,151 +305,57 @@ const getPageProps = async () => { currentPageData = null; currentPageData = await logseq.Editor.getPage(currentPageId); } - if (currentPageData) { - //@ts-expect-error - currentPageProps = currentPageData.properties; - } } - isJournal = currentPageData?.["journal?"]; - return currentPageProps; + return currentPageData; } -// Generate Base64 from image URL -const getBase64FromUrl = async (url: string): Promise => { - let data; - try { - data = await fetch(url); - } catch (error) { - return ""; - } - const blob = await data.blob(); - return new Promise((resolve) => { - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => { - const base64data = reader.result as string; - resolve(base64data); - } - }); -} - -// Get and encode default banners for caching -// skip caching if random image from Unsplash API used -const encodeDefaultBanners = async () => { - if (defaultConfig.page.banner && !(defaultConfig.page.banner?.includes("source.unsplash.com"))) { - defaultConfig.page.banner = await getBase64FromUrl(defaultConfig.page.banner); - } - if (defaultConfig.journal.banner && !(defaultConfig.journal.banner?.includes("source.unsplash.com"))) { - defaultConfig.journal.banner = await getBase64FromUrl(defaultConfig.journal.banner); - } -} - -// Read from cusrom page props config -const getCustomPropsAsset = (assetType: AssetType) => { - console.info(`#${pluginId}: Trying ${assetType} from custom JSON settings`); - let customPropsAsset = ""; +// Read custom defaults config +const getCustomAssetData = (currentPageProps: any) => { + console.info(`#${pluginId}: Trying custom JSON settings`); + let customAssetData = {}; for (const key of Object.keys(customPropsConfig)) { - //@ts-expect-error const pageCustomProp = currentPageProps?.[key]; if (pageCustomProp) { const pageCustomPropValue = Array.isArray(pageCustomProp) ? pageCustomProp[0] : pageCustomProp; if (pageCustomPropValue) { //@ts-expect-error - customPropsAsset = customPropsConfig?.[key]?.[pageCustomPropValue]?.[assetType]; + customAssetData = customPropsConfig?.[key]?.[pageCustomPropValue]; } } } - return customPropsAsset; + return customAssetData; } -// Read banner from page props -const getPropsAsset = async (assetType: AssetType) => { - console.info(`#${pluginId}: Trying ${assetType} from page props`); - let propsAsset = ""; - //@ts-expect-error - propsAsset = currentPageProps?.[assetType]; - if (propsAsset && assetType === "banner") { +// Render banner +const renderBanner = async (pageAssetsData: AssetData) => { + if (pageAssetsData.banner) { //remove surrounding quotations if present - propsAsset = propsAsset.replace(/^"(.*)"$/, '$1'); + pageAssetsData.banner = pageAssetsData.banner.replace(/^"(.*)"$/, '$1'); // if local image from assets folder - if (propsAsset.startsWith("../")) { + if (pageAssetsData.banner.startsWith("../")) { const graphPath = (await logseq.App.getCurrentGraph())?.path; - propsAsset = encodeURI("assets://" + graphPath + propsAsset.replace("..", "")); + pageAssetsData.banner = encodeURI("assets://" + graphPath + pageAssetsData.banner.replace("..", "")); } - } - if (!propsAsset && assetType === "pageIcon") { - console.info(`#${pluginId}: Trying icon from Logseq native icon prop`); - //@ts-expect-error - propsAsset = currentPageProps?.icon; - } - return propsAsset; -} - -// Check journals home page -const getHomeAsset = (assetType: AssetType): string => { - let homeAsset = ""; - if (useDefault[assetType]) { - console.info(`#${pluginId}: Trying ${assetType} from default settings (journal), home page`); - //@ts-expect-error - homeAsset = defaultConfig.journal[assetType]; - } - return homeAsset; -} - -// Default page or journal banner? -const chooseDefaultAsset = (assetType: AssetType) => { - let defaultAsset = ""; - console.info(`#${pluginId}: Trying ${assetType} from default settings`); - //@ts-expect-error - defaultAsset = isJournal ? defaultConfig.journal[assetType] : defaultConfig.page[assetType]; - return defaultAsset; -} - - -// Get asset -const getAsset = async (assetType: AssetType): Promise => { - let asset = ""; - // Check journals home page - if (pageType === "home") { - isJournal = true; - asset = getHomeAsset(assetType); - return asset; - } - // Read from page props - asset = await getPropsAsset(assetType); - // Read from custom props - if (!asset && customPropsConfig) { - asset = getCustomPropsAsset(assetType); - } - // Use default if no props and allows default - if (!asset && useDefault[assetType]) { - return chooseDefaultAsset(assetType); - } - console.info(`#${pluginId}: ${assetType} - ${asset}`); - return asset; -} - -// Set banner CSS variable -const renderBanner = async () => { - const pageBanner = await getAsset("banner"); - if (pageBanner) { - renderIcon(); + // Set banner CSS variable body.classList.add("is-banner-active"); - const bannerHeight = isJournal ? defaultConfig.journal.bannerHeight : defaultConfig.page.bannerHeight - root.style.setProperty("--bannerHeight", `${bannerHeight}`); - root.style.setProperty("--pageBanner", `url(${pageBanner})`); + root.style.setProperty("--pageBanner", `url(${pageAssetsData.banner})`); + root.style.setProperty("--bannerHeight", `${pageAssetsData.bannerHeight}`); + root.style.setProperty("--bannerAlign", `${pageAssetsData.bannerAlign}`); + + // render icon only if banner exists + renderIcon(pageAssetsData); } else { clearBanner(); } } -// Set icon CSS variable -const renderIcon = async () => { - const pageIcon = await getAsset("pageIcon"); +// render icon +const renderIcon = async (pageAssetsData: AssetData) => { + const pageIcon = pageAssetsData.icon || pageAssetsData.pageIcon; if (pageIcon) { + // Set icon CSS variable body.classList.add("is-icon-active"); - const iconHeight = isJournal ? defaultConfig.journal.iconHeight : defaultConfig.page.iconHeight - root.style.setProperty("--iconHeight", `${iconHeight}`); + root.style.setProperty("--iconWidth", `${pageAssetsData.iconWidth}`); root.style.setProperty("--pageIcon", `"${pageIcon}"`); } else { clearIcon(); @@ -478,7 +380,7 @@ const propsChangedObserverInit = () => { return; } //@ts-expect-error - if (mutationsList[i]?.target?.offsetParent.classList.contains("pre-block") && mutationsList[i].oldValue === "editor-wrapper"){ + if (mutationsList[i]?.target?.offsetParent?.classList.contains("pre-block") && mutationsList[i].oldValue === "editor-wrapper"){ console.info(`#${pluginId}: page props - edited or added`); render(); } @@ -500,12 +402,15 @@ const propsChangedObserverStop = () => { const clearBanner = () => { body.classList.remove("is-banner-active"); root.style.setProperty("--pageBanner", ""); + root.style.setProperty("--bannerHeight", ""); + root.style.setProperty("--bannerAlign", ""); } // Hide icon element const clearIcon = () => { body.classList.remove("is-icon-active"); root.style.setProperty("--pageIcon", ""); + root.style.setProperty("--iconWidth", ""); } // Page changed