├── nord ├── nord.png └── mod.json ├── tabs ├── tabs.jpg ├── systemMenu.cjs ├── createWindow.cjs ├── main.cjs ├── mod.json ├── client.mjs ├── rendererIndex.cjs └── tabs.css ├── tray ├── tray.jpg ├── client.mjs ├── mod.json ├── createWindow.cjs └── main.cjs ├── dark+ ├── dark+.png ├── mod.json └── theme.mjs ├── light+ ├── light+.png ├── mod.json └── theme.mjs ├── dracula ├── dracula.png ├── mod.json └── app.css ├── neutral ├── neutral.png ├── mod.json └── app.css ├── outliner ├── outliner.png ├── mod.json ├── client.css └── client.mjs ├── icon-sets ├── icon-sets.jpg ├── mod.json └── client.css ├── emoji-sets ├── emoji-sets.jpg ├── client.css ├── mod.json └── client.mjs ├── pinky-boom ├── pinky-boom.png └── mod.json ├── quick-note ├── quick-note.png ├── mod.json └── client.mjs ├── view-scale ├── view-scale.jpg ├── mod.json ├── client.css └── client.mjs ├── cherry-cola ├── cherry-cola.png └── mod.json ├── pastel-dark ├── pastel-dark.png ├── mod.json └── app.css ├── weekly-view ├── weekly-view.jpg ├── mod.json └── client.mjs ├── always-on-top ├── always-on-top.jpg ├── client.mjs ├── menu.mjs ├── button.css ├── mod.json └── button.mjs ├── gruvbox-dark ├── gruvbox-dark.png └── mod.json ├── gruvbox-light ├── gruvbox-light.png └── mod.json ├── right-to-left ├── right-to-left.jpg ├── mod.json ├── client.css └── client.mjs ├── scroll-to-top ├── scroll-to-top.png ├── mod.json └── client.mjs ├── topbar-icons ├── topbar-icons.jpg ├── mod.json └── client.mjs ├── word-counter ├── word-counter.jpg ├── mod.json ├── client.css └── client.mjs ├── material-ocean ├── material-ocean.png └── mod.json ├── playful-purple ├── playful-purple.png └── mod.json ├── calendar-scroll ├── calendar-scroll.png ├── mod.json ├── client.css └── client.mjs ├── truncated-titles ├── truncated-titles.jpg ├── mod.json └── client.mjs ├── code-line-numbers ├── code-line-numbers.png ├── mod.json ├── client.css └── client.mjs ├── global-block-links ├── global-block-links.jpg ├── mod.json ├── client.css └── client.mjs ├── indentation-lines ├── indentation-lines.jpg ├── mod.json ├── client.mjs └── client.css ├── simpler-databases ├── simpler-databases.jpg └── mod.json ├── integrated-titlebar ├── integrated-titlebar.jpg ├── createWindow.cjs ├── menu.mjs ├── frame.mjs ├── buttons.css ├── client.mjs ├── mod.json └── buttons.mjs ├── collapsible-properties ├── collapsible-properties.jpg ├── mod.json ├── client.mjs └── client.css ├── bypass-preview ├── client.css ├── mod.json └── client.mjs ├── menu ├── menu.html ├── menu.css ├── mod.json ├── client.mjs ├── client.css ├── markdown.css └── router.mjs ├── focus-mode ├── client.mjs ├── tabs.mjs ├── tabs.css ├── client.css └── mod.json ├── README.md ├── theming ├── rendererSearch.cjs ├── frame.mjs ├── main.cjs ├── menu.mjs ├── mod.json ├── electronSearch.css ├── client.mjs └── prism.css ├── font-chooser ├── fonts.css ├── fonts.mjs └── mod.json ├── registry.json ├── components └── mod.json ├── LICENSE └── tweaks ├── client.mjs ├── mod.json └── client.css /nord/nord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/nord/nord.png -------------------------------------------------------------------------------- /tabs/tabs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/tabs/tabs.jpg -------------------------------------------------------------------------------- /tray/tray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/tray/tray.jpg -------------------------------------------------------------------------------- /dark+/dark+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/dark+/dark+.png -------------------------------------------------------------------------------- /light+/light+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/light+/light+.png -------------------------------------------------------------------------------- /dracula/dracula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/dracula/dracula.png -------------------------------------------------------------------------------- /neutral/neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/neutral/neutral.png -------------------------------------------------------------------------------- /outliner/outliner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/outliner/outliner.png -------------------------------------------------------------------------------- /icon-sets/icon-sets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/icon-sets/icon-sets.jpg -------------------------------------------------------------------------------- /emoji-sets/emoji-sets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/emoji-sets/emoji-sets.jpg -------------------------------------------------------------------------------- /pinky-boom/pinky-boom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/pinky-boom/pinky-boom.png -------------------------------------------------------------------------------- /quick-note/quick-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/quick-note/quick-note.png -------------------------------------------------------------------------------- /view-scale/view-scale.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/view-scale/view-scale.jpg -------------------------------------------------------------------------------- /cherry-cola/cherry-cola.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/cherry-cola/cherry-cola.png -------------------------------------------------------------------------------- /pastel-dark/pastel-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/pastel-dark/pastel-dark.png -------------------------------------------------------------------------------- /weekly-view/weekly-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/weekly-view/weekly-view.jpg -------------------------------------------------------------------------------- /always-on-top/always-on-top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/always-on-top/always-on-top.jpg -------------------------------------------------------------------------------- /gruvbox-dark/gruvbox-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/gruvbox-dark/gruvbox-dark.png -------------------------------------------------------------------------------- /gruvbox-light/gruvbox-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/gruvbox-light/gruvbox-light.png -------------------------------------------------------------------------------- /right-to-left/right-to-left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/right-to-left/right-to-left.jpg -------------------------------------------------------------------------------- /scroll-to-top/scroll-to-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/scroll-to-top/scroll-to-top.png -------------------------------------------------------------------------------- /topbar-icons/topbar-icons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/topbar-icons/topbar-icons.jpg -------------------------------------------------------------------------------- /word-counter/word-counter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/word-counter/word-counter.jpg -------------------------------------------------------------------------------- /material-ocean/material-ocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/material-ocean/material-ocean.png -------------------------------------------------------------------------------- /playful-purple/playful-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/playful-purple/playful-purple.png -------------------------------------------------------------------------------- /calendar-scroll/calendar-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/calendar-scroll/calendar-scroll.png -------------------------------------------------------------------------------- /truncated-titles/truncated-titles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/truncated-titles/truncated-titles.jpg -------------------------------------------------------------------------------- /code-line-numbers/code-line-numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/code-line-numbers/code-line-numbers.png -------------------------------------------------------------------------------- /global-block-links/global-block-links.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/global-block-links/global-block-links.jpg -------------------------------------------------------------------------------- /indentation-lines/indentation-lines.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/indentation-lines/indentation-lines.jpg -------------------------------------------------------------------------------- /simpler-databases/simpler-databases.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/simpler-databases/simpler-databases.jpg -------------------------------------------------------------------------------- /integrated-titlebar/integrated-titlebar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/integrated-titlebar/integrated-titlebar.jpg -------------------------------------------------------------------------------- /collapsible-properties/collapsible-properties.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notion-enhancer/repo/HEAD/collapsible-properties/collapsible-properties.jpg -------------------------------------------------------------------------------- /bypass-preview/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: bypass preview 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .notion-peek-renderer { 8 | display: none; 9 | } 10 | -------------------------------------------------------------------------------- /menu/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | notion-enhancer menu 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /emoji-sets/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: emoji sets 3 | * (c) 2021 Arecsu 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | [aria-label][role='image'][style*='Apple Color Emoji']:not([data-emoji-sets-unsupported]) { 9 | margin-left: 2.5px !important; 10 | } 11 | -------------------------------------------------------------------------------- /focus-mode/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: focus mode 3 | * (c) 2020 Arecsu 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | 'use strict'; 9 | 10 | export default async function (api, db) { 11 | if (await db.get(['padded'])) document.body.dataset.focusMode = 'padded'; 12 | } 13 | -------------------------------------------------------------------------------- /focus-mode/tabs.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: focus mode 3 | * (c) 2020 Arecsu 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | 'use strict'; 9 | 10 | export default async function (api, db) { 11 | if (await db.get(['tabs'])) document.body.dataset.focusMode = 'hide-tabs'; 12 | } 13 | -------------------------------------------------------------------------------- /focus-mode/tabs.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: focus mode 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | [data-focus-mode='hide-tabs'] header:not(:hover) { 8 | background: var(--theme--bg); 9 | border-bottom-color: transparent; 10 | } 11 | [data-focus-mode='hide-tabs'] header:not(:hover) > * { 12 | opacity: 0; 13 | transition: opacity 200ms ease-in-out; 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notion-enhancer/repo 2 | 3 | The collection of mods run by the notion-enhancer (v0.11.0). 4 | 5 | This repo trialled splitting up the project into submodules that could then be combined with alternate "cores" to enable cross-platform development. An auto-update system pushed commits from these submodules to the parent repositories' in an attempt to keep everything in sync, however, this made conflict-free contributions and setting up local development environments complicated. This repo has now been archived with the return of the notion-enhancer to a monorepo (https://github.com/notion-enhancer/notion-enhancer). 6 | -------------------------------------------------------------------------------- /integrated-titlebar/createWindow.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = function (api, db, __exports, __eval) { 10 | __eval(` 11 | const notionRectFromFocusedWindow = getRectFromFocusedWindow; 12 | getRectFromFocusedWindow = (windowState) => { 13 | const rect = notionRectFromFocusedWindow(windowState); 14 | rect.frame = false; 15 | return rect; 16 | }; 17 | `); 18 | }; 19 | -------------------------------------------------------------------------------- /pinky-boom/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pinky boom", 3 | "id": "b5f43232-391b-415a-9099-4334dc6c7b55", 4 | "version": "0.2.0", 5 | "description": "pinkify your life.", 6 | "preview": "pinky-boom.png", 7 | "tags": ["theme", "light"], 8 | "authors": [ 9 | { 10 | "name": "mugiwarafx", 11 | "homepage": "https://github.com/mugiwarafx", 12 | "avatar": "https://avatars.githubusercontent.com/u/58401248" 13 | } 14 | ], 15 | "css": { 16 | "frame": ["variables.css"], 17 | "client": ["variables.css"], 18 | "menu": ["variables.css"] 19 | }, 20 | "js": {}, 21 | "options": [] 22 | } 23 | -------------------------------------------------------------------------------- /weekly-view/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weekly view", 3 | "id": "4c7acaea-6596-4590-85e5-8ac5a1455e8f", 4 | "version": "0.6.0", 5 | "description": "calendar views named \"weekly\" will show only the current week.", 6 | "preview": "weekly-view.jpg", 7 | "tags": ["extension", "layout"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": {}, 20 | "options": [] 21 | } 22 | -------------------------------------------------------------------------------- /bypass-preview/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bypass preview", 3 | "id": "cb6fd684-f113-4a7a-9423-8f0f0cff069f", 4 | "version": "0.2.0", 5 | "description": "go straight to the normal full view when opening a page.", 6 | "tags": ["extension", "automation"], 7 | "authors": [ 8 | { 9 | "name": "dragonwocky", 10 | "email": "thedragonring.bod@gmail.com", 11 | "homepage": "https://dragonwocky.me/", 12 | "avatar": "https://dragonwocky.me/avatar.jpg" 13 | } 14 | ], 15 | "js": { 16 | "client": ["client.mjs"] 17 | }, 18 | "css": { 19 | "client": ["client.css"] 20 | }, 21 | "options": [] 22 | } 23 | -------------------------------------------------------------------------------- /theming/rendererSearch.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function ({ web, electron }, db, __exports, __eval) { 10 | await web.whenReady(); 11 | web.loadStylesheet('repo/theming/electronSearch.css'); 12 | 13 | electron.onMessage('set-search-theme', (event, theme) => { 14 | for (const [key, value] of theme) { 15 | document.documentElement.style.setProperty(`--theme--${key}`, value); 16 | } 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /always-on-top/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: always on top 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | import { createButton } from './button.mjs'; 8 | 9 | export default async function (api, db) { 10 | const { web } = api, 11 | topbarActionsSelector = '.notion-topbar-action-buttons'; 12 | 13 | await web.whenReady([topbarActionsSelector]); 14 | const $topbarActions = document.querySelector(topbarActionsSelector), 15 | $button = await createButton(api, db); 16 | $topbarActions.after($button); 17 | } 18 | -------------------------------------------------------------------------------- /outliner/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outliner", 3 | "id": "87e077cc-5402-451c-ac70-27cc4ae65546", 4 | "version": "0.4.0", 5 | "description": "adds a table of contents to the side panel.", 6 | "preview": "outliner.png", 7 | "tags": ["extension", "panel"], 8 | "authors": [ 9 | { 10 | "name": "CloudHill", 11 | "email": "rh.cloudhill@gmail.com", 12 | "homepage": "https://github.com/CloudHill", 13 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /right-to-left/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "right to left", 3 | "id": "b28ee2b9-4d34-4e36-be8a-ab5be3d79f51", 4 | "version": "1.5.0", 5 | "description": "enables auto rtl/ltr text direction detection.", 6 | "preview": "right-to-left.jpg", 7 | "tags": ["extension", "usability"], 8 | "authors": [ 9 | { 10 | "name": "obahareth", 11 | "email": "omar@omar.engineer", 12 | "homepage": "https://omar.engineer", 13 | "avatar": "https://avatars.githubusercontent.com/u/3428118" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /theming/frame.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web, storage, electron }, db) { 10 | await web.whenReady(); 11 | 12 | const updateTheme = async () => { 13 | const mode = await storage.get(['theme'], 'light'); 14 | document.documentElement.classList.add(mode); 15 | document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); 16 | }; 17 | electron.onMessage('update-theme', updateTheme); 18 | updateTheme(); 19 | } 20 | -------------------------------------------------------------------------------- /material-ocean/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material ocean", 3 | "id": "69e7ccb2-4aef-484c-876d-3de1b433d2b9", 4 | "version": "0.2.0", 5 | "description": "an oceanic colour palette.", 6 | "preview": "material-ocean.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "blacksuan19", 11 | "email": "abubakaryagob@gmail.com", 12 | "homepage": "http://github.com/blacksuan19", 13 | "avatar": "https://avatars.githubusercontent.com/u/10248473" 14 | } 15 | ], 16 | "css": { 17 | "frame": ["variables.css"], 18 | "client": ["variables.css"], 19 | "menu": ["variables.css"] 20 | }, 21 | "js": {}, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /cherry-cola/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cherry cola", 3 | "id": "ec5c4640-68d4-4d25-aefd-62c7e9737cfb", 4 | "version": "0.2.0", 5 | "description": "a delightfully plummy, cherry cola flavored theme.", 6 | "preview": "cherry-cola.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "runargs", 11 | "email": "alnbaldon@gmail.com", 12 | "homepage": "http://github.com/runargs", 13 | "avatar": "https://avatars.githubusercontent.com/u/39810066" 14 | } 15 | ], 16 | "css": { 17 | "frame": ["variables.css"], 18 | "client": ["variables.css"], 19 | "menu": ["variables.css"] 20 | }, 21 | "js": {}, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /right-to-left/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: right to left 3 | * (c) 2021 obahareth (https://omar.engineer) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-page-content 9 | .notion-table_of_contents-block 10 | > div 11 | > div 12 | > a 13 | > div 14 | > div[style*='margin-left: 24px'] { 15 | margin-inline-start: 24px; 16 | } 17 | 18 | .notion-page-content 19 | .notion-table_of_contents-block 20 | > div 21 | > div 22 | > a 23 | > div 24 | > div[style*='margin-left: 48px'] { 25 | margin-inline-start: 48px; 26 | } 27 | -------------------------------------------------------------------------------- /simpler-databases/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simpler databases", 3 | "id": "752933b5-1258-44e3-b49a-61b4885f8bda", 4 | "version": "0.2.0", 5 | "description": "adds a menu to inline databases to toggle ui elements.", 6 | "preview": "simpler-databases.jpg", 7 | "tags": ["extension", "layout"], 8 | "authors": [ 9 | { 10 | "name": "CloudHill", 11 | "email": "rh.cloudhill@gmail.com", 12 | "homepage": "https://github.com/CloudHill", 13 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /word-counter/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "word counter", 3 | "id": "b99deb52-6955-43d2-a53b-a31540cd19a5", 4 | "version": "0.3.0", 5 | "description": "view word/character/sentence/block count & speaking/reading times in the side panel.", 6 | "preview": "word-counter.jpg", 7 | "tags": ["extension", "panel"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /calendar-scroll/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calendar scroll", 3 | "id": "b1c7db33-dfee-489a-a76c-0dd66f7ed29a", 4 | "version": "0.2.0", 5 | "description": "adds a button to jump down to the current week in fullpage/infinite-scroll calendars.", 6 | "preview": "calendar-scroll.png", 7 | "tags": ["extension", "shortcut"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /neutral/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neutral", 3 | "id": "c4435543-4705-4d68-8cf7-d11c342f8089", 4 | "version": "0.2.0", 5 | "description": "smoother colours and text sizing, designed to be more pleasing to the eye.", 6 | "preview": "neutral.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "arecsu", 11 | "email": "alejandro9r@gmail.com", 12 | "homepage": "https://github.com/Arecsu", 13 | "avatar": "https://avatars.githubusercontent.com/u/12679098" 14 | } 15 | ], 16 | "css": { 17 | "frame": ["variables.css"], 18 | "client": ["variables.css", "app.css"], 19 | "menu": ["variables.css"] 20 | }, 21 | "js": {}, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /pastel-dark/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pastel dark", 3 | "id": "ed48c585-4a0d-4756-a3e6-9ae662732dac", 4 | "version": "0.2.0", 5 | "description": "a smooth-transition true dark theme with a hint of pastel.", 6 | "preview": "pastel-dark.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "zenith_illinois", 11 | "homepage": "https://www.reddit.com/user/zenith_illinois/", 12 | "avatar": "https://www.redditstatic.com/avatars/defaults/v2/avatar_default_5.png" 13 | } 14 | ], 15 | "css": { 16 | "frame": ["variables.css"], 17 | "client": ["variables.css", "app.css"], 18 | "menu": ["variables.css"] 19 | }, 20 | "js": {}, 21 | "options": [] 22 | } 23 | -------------------------------------------------------------------------------- /tray/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tray 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | export default async function ({ electron, env, web }, db) { 8 | const runInBackground = await db.get(['run_in_background']); 9 | if (!runInBackground) return; 10 | 11 | // force new window creation on create new window hotkey 12 | // hotkey is built into notion, so can't be changed, 13 | // but is broken by this mod's window duplication prevention 14 | web.addHotkeyListener([env.name === 'darwin' ? 'Meta' : 'Ctrl', 'Shift', 'N'], () => 15 | electron.sendMessage('create-new-window') 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /collapsible-properties/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collapsible properties", 3 | "id": "4034a578-7dd3-4633-80c6-f47ac5b7b160", 4 | "version": "0.3.0", 5 | "description": "adds a button to quickly collapse/expand page properties that usually push down page content.", 6 | "preview": "collapsible-properties.jpg", 7 | "tags": ["extension", "layout"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "css": { 17 | "client": ["client.css"] 18 | }, 19 | "js": { 20 | "client": ["client.mjs"] 21 | }, 22 | "options": [] 23 | } 24 | -------------------------------------------------------------------------------- /focus-mode/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: focus mode 3 | * (c) 2020 Arecsu 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-sidebar-container[style*='width: 0px;'] ~ div .notion-topbar > :first-child { 9 | opacity: 0 !important; 10 | transition: opacity 200ms ease-in-out !important; 11 | } 12 | .notion-sidebar-container[style*='width: 0px;'] ~ div .notion-topbar > :first-child:hover { 13 | opacity: 1 !important; 14 | } 15 | 16 | [data-focus-mode='padded'] 17 | .notion-sidebar-container[style*='width: 0px;'] 18 | ~ div 19 | .notion-frame { 20 | height: calc(100vh - 90px) !important; 21 | } 22 | -------------------------------------------------------------------------------- /menu/menu.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: menu 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | ::selection { 8 | background: var(--theme--accent_blue-selection); 9 | } 10 | 11 | ::-webkit-scrollbar { 12 | width: 10px; 13 | height: 10px; 14 | background: transparent; 15 | } 16 | ::-webkit-scrollbar-track, 17 | ::-webkit-scrollbar-corner { 18 | background: var(--theme--scrollbar_track) !important; 19 | } 20 | ::-webkit-scrollbar-thumb { 21 | background: var(--theme--scrollbar_thumb) !important; 22 | } 23 | ::-webkit-scrollbar-thumb:hover { 24 | background: var(--theme--scrollbar_thumb-hover) !important; 25 | } 26 | -------------------------------------------------------------------------------- /tabs/systemMenu.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tabs 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function ({}, db, __exports, __eval) { 10 | const notionSetupSystemMenu = __exports.setupSystemMenu; 11 | __exports.setupSystemMenu = (locale) => { 12 | const { Menu } = require('electron'), 13 | template = notionSetupSystemMenu(locale); 14 | for (const category of template) { 15 | category.submenu = category.submenu.filter((item) => item.role !== 'close'); 16 | } 17 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 18 | return template; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /always-on-top/menu.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: always on top 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | import { createButton } from './button.mjs'; 8 | 9 | export default async function (api, db) { 10 | const { web } = api, 11 | sidebarSelector = '.sidebar', 12 | windowButtonsSelector = '.integrated_titlebar--buttons'; 13 | 14 | await web.whenReady([sidebarSelector]); 15 | await new Promise(requestAnimationFrame); 16 | const $sidebar = document.querySelector(sidebarSelector), 17 | $windowButtons = document.querySelector(windowButtonsSelector), 18 | $button = await createButton(api, db); 19 | ($windowButtons || $sidebar).prepend($button); 20 | } 21 | -------------------------------------------------------------------------------- /calendar-scroll/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: calendar scroll 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | #enhancer--calendar-scroll { 8 | background: var(--theme--ui_interactive-hover); 9 | border: 1px solid transparent; 10 | font-size: 14px; 11 | color: var(--theme--text); 12 | height: 24px; 13 | border-radius: 3px; 14 | line-height: 1.2; 15 | padding: 0 0.5em; 16 | margin-right: 5px; 17 | } 18 | #enhancer--calendar-scroll:focus, 19 | #enhancer--calendar-scroll:hover { 20 | background: transparent; 21 | border: 1px solid var(--theme--ui_interactive-hover); 22 | } 23 | #enhancer--calendar-scroll:active { 24 | background: var(--theme--ui_interactive-active); 25 | } 26 | -------------------------------------------------------------------------------- /theming/main.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function ({}, db, __exports, __eval) { 10 | const electron = require('electron'); 11 | electron.ipcMain.on('notion-enhancer:update-theme', () => { 12 | electron.webContents 13 | .getAllWebContents() 14 | .forEach((webContents) => webContents.send('notion-enhancer:update-theme')); 15 | }); 16 | electron.ipcMain.on('notion-enhancer:set-search-theme', (event, theme) => { 17 | electron.webContents 18 | .getAllWebContents() 19 | .forEach((webContents) => webContents.send('notion-enhancer:set-search-theme', theme)); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /nord/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nord", 3 | "id": "d64ad391-1494-4112-80ae-0a3b6f4b0c3f", 4 | "version": "0.2.0", 5 | "description": "an arctic, north-bluish color palette.", 6 | "preview": "nord.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "Artic Ice Studio", 11 | "email": "development@arcticicestudio.com", 12 | "homepage": "https://www.nordtheme.com/", 13 | "avatar": "https://avatars.githubusercontent.com/u/7836623" 14 | }, 15 | { 16 | "name": "MANNNNEN", 17 | "homepage": "https://github.com/MANNNNEN", 18 | "avatar": "https://avatars.githubusercontent.com/u/35939149" 19 | } 20 | ], 21 | "css": { 22 | "frame": ["variables.css"], 23 | "client": ["variables.css"], 24 | "menu": ["variables.css"] 25 | }, 26 | "js": {}, 27 | "options": [] 28 | } 29 | -------------------------------------------------------------------------------- /menu/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "menu", 3 | "id": "a6621988-551d-495a-97d8-3c568bca2e9e", 4 | "version": "0.11.0", 5 | "description": "the enhancer's graphical menu, related buttons and shortcuts.", 6 | "tags": ["core"], 7 | "authors": [ 8 | { 9 | "name": "dragonwocky", 10 | "email": "thedragonring.bod@gmail.com", 11 | "homepage": "https://dragonwocky.me/", 12 | "avatar": "https://dragonwocky.me/avatar.jpg" 13 | } 14 | ], 15 | "css": { 16 | "client": ["client.css"], 17 | "menu": ["menu.css", "markdown.css"] 18 | }, 19 | "js": { 20 | "client": ["client.mjs"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "hotkey", 25 | "key": "hotkey", 26 | "label": "toggle focus hotkey", 27 | "tooltip": "**switches between notion & the enhancer menu**", 28 | "value": "Ctrl+Alt+E" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /font-chooser/fonts.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: font chooser 3 | * (c) 2021 TorchAtlas (https://github.com/torchatlas/) 4 | * (c) 2021 admiraldus (https://github.com/admiraldus) 5 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 6 | * (https://notion-enhancer.github.io/) under the MIT license 7 | */ 8 | 9 | :root { 10 | --theme--font_sans: var(--font_chooser--sans); 11 | --theme--font_serif: var(--font_chooser--serif); 12 | --theme--font_mono: var(--font_chooser--mono); 13 | --theme--font_code: var(--font_chooser--code); 14 | } 15 | 16 | .notion-quote-block { 17 | font-family: var(--font_chooser--quotes, inherit) !important; 18 | } 19 | 20 | [placeholder='Untitled'], 21 | [placeholder='Heading 1'], 22 | [placeholder='Heading 2'], 23 | [placeholder='Heading 3'] { 24 | font-family: var(--font_chooser--headings, inherit) !important; 25 | } 26 | -------------------------------------------------------------------------------- /truncated-titles/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truncated titles", 3 | "id": "1794c0bd-7b96-46ad-aa0b-fc4bd76fc7fb", 4 | "version": "0.2.0", 5 | "description": "see the full text of a truncated title on hover.", 6 | "preview": "truncated-titles.jpg", 7 | "tags": ["extension", "layout"], 8 | "authors": [ 9 | { 10 | "name": "admiraldus", 11 | "homepage": "https://github.com/admiraldus", 12 | "avatar": "https://raw.githubusercontent.com/admiraldus/admiraldus/main/module.gif" 13 | } 14 | ], 15 | "js": { 16 | "client": ["client.mjs"] 17 | }, 18 | "css": {}, 19 | "options": [ 20 | { 21 | "type": "toggle", 22 | "key": "tables", 23 | "label": "table titles", 24 | "value": true 25 | }, 26 | { 27 | "type": "toggle", 28 | "key": "timelines", 29 | "label": "timeline items", 30 | "value": true 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /registry.json: -------------------------------------------------------------------------------- 1 | [ 2 | "menu", 3 | "theming", 4 | "components", 5 | 6 | "tweaks", 7 | "font-chooser", 8 | 9 | "integrated-titlebar", 10 | "tray", 11 | "tabs", 12 | "always-on-top", 13 | "view-scale", 14 | 15 | "outliner", 16 | "scroll-to-top", 17 | "indentation-lines", 18 | "right-to-left", 19 | "simpler-databases", 20 | "emoji-sets", 21 | "bypass-preview", 22 | "topbar-icons", 23 | "word-counter", 24 | "code-line-numbers", 25 | "calendar-scroll", 26 | "collapsible-properties", 27 | "weekly-view", 28 | "truncated-titles", 29 | "focus-mode", 30 | "global-block-links", 31 | 32 | "icon-sets", 33 | "quick-note", 34 | 35 | "material-ocean", 36 | "cherry-cola", 37 | "dark+", 38 | "light+", 39 | "dracula", 40 | "pastel-dark", 41 | "neutral", 42 | "nord", 43 | "gruvbox-dark", 44 | "gruvbox-light", 45 | "playful-purple", 46 | "pinky-boom" 47 | ] 48 | -------------------------------------------------------------------------------- /dracula/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dracula", 3 | "id": "033bff54-50ba-4cec-bdc0-b2ca7e307086", 4 | "version": "0.3.0", 5 | "description": "a theme based on the popular dracula color palette originally by zeno rocha and friends.", 6 | "preview": "dracula.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "dracula", 11 | "email": "zno.rocha@gmail.com", 12 | "homepage": "https://draculatheme.com/", 13 | "avatar": "https://draculatheme.com/static/icons/pack-1/045-dracula.svg" 14 | }, 15 | { 16 | "name": "mimishahzad", 17 | "homepage": "https://github.com/mimishahzad", 18 | "avatar": "https://avatars.githubusercontent.com/u/34081810" 19 | } 20 | ], 21 | "css": { 22 | "frame": ["variables.css"], 23 | "client": ["variables.css", "app.css"], 24 | "menu": ["variables.css"] 25 | }, 26 | "js": {}, 27 | "options": [] 28 | } 29 | -------------------------------------------------------------------------------- /playful-purple/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playful purple", 3 | "id": "ae7862a9-5dbe-4f2c-9931-940f3ba5015d", 4 | "version": "0.2.0", 5 | "description": "a purple-shaded theme with bright highlights.", 6 | "preview": "playful-purple.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "Lizishan", 11 | "homepage": "https://www.reddit.com/user/Lizishan", 12 | "avatar": "https://styles.redditmedia.com/t5_110nz4/styles/profileIcon_h1m3b16exoi51.jpg" 13 | }, 14 | { 15 | "name": "LVL100ShrekCultist", 16 | "homepage": "https://www.reddit.com/user/LVL100ShrekCultist/", 17 | "avatar": "https://styles.redditmedia.com/t5_2js69j/styles/profileIcon_jvnzmo30fyq41.jpg" 18 | } 19 | ], 20 | "css": { 21 | "frame": ["variables.css"], 22 | "client": ["variables.css"], 23 | "menu": ["variables.css"] 24 | }, 25 | "js": {}, 26 | "options": [] 27 | } 28 | -------------------------------------------------------------------------------- /tabs/createWindow.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tabs 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function (api, db, __exports, __eval) { 10 | const notionCreateWindow = __exports.createWindow; 11 | __exports.createWindow = (relativeUrl = '', args) => { 12 | const windows = api.electron.getNotionWindows(); 13 | // '/' is used to create new windows intentionally 14 | if (relativeUrl && relativeUrl !== '/' && windows.length) { 15 | const window = api.electron.getFocusedNotionWindow() || windows[0]; 16 | window.webContents.send('notion-enhancer:open-tab', { 17 | notionUrl: `notion://www.notion.so${relativeUrl}`, 18 | }); 19 | return window; 20 | } 21 | return notionCreateWindow(relativeUrl, args); 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /gruvbox-dark/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gruvbox dark", 3 | "id": "ad0f5c5c-8b62-4b20-8a54-929e663ea368", 4 | "version": "0.3.0", 5 | "description": "a gray, 'retro groove' palette based on the vim theme of the same name.", 6 | "preview": "gruvbox-dark.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "morhetz", 11 | "email": "morhetz@gmail.com", 12 | "homepage": "https://github.com/morhetz", 13 | "avatar": "https://avatars.githubusercontent.com/u/554231" 14 | }, 15 | { 16 | "name": "jordanrobinson", 17 | "email": "me@jordanrobinson.co.uk", 18 | "homepage": "https://jordanrobinson.co.uk/", 19 | "avatar": "https://avatars.githubusercontent.com/u/1202911" 20 | } 21 | ], 22 | "css": { 23 | "frame": ["variables.css"], 24 | "client": ["variables.css"], 25 | "menu": ["variables.css"] 26 | }, 27 | "js": {}, 28 | "options": [] 29 | } 30 | -------------------------------------------------------------------------------- /gruvbox-light/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gruvbox light", 3 | "id": "54f01911-60fc-4c9d-85b5-5c5ca3dd6b81", 4 | "version": "0.3.0", 5 | "description": "a sepia, 'retro groove' palette based on the vim theme of the same name.", 6 | "preview": "gruvbox-light.png", 7 | "tags": ["theme", "light"], 8 | "authors": [ 9 | { 10 | "name": "morhetz", 11 | "email": "morhetz@gmail.com", 12 | "homepage": "https://github.com/morhetz", 13 | "avatar": "https://avatars.githubusercontent.com/u/554231" 14 | }, 15 | { 16 | "name": "jordanrobinson", 17 | "email": "me@jordanrobinson.co.uk", 18 | "homepage": "https://jordanrobinson.co.uk/", 19 | "avatar": "https://avatars.githubusercontent.com/u/1202911" 20 | } 21 | ], 22 | "css": { 23 | "frame": ["variables.css"], 24 | "client": ["variables.css"], 25 | "menu": ["variables.css"] 26 | }, 27 | "js": {}, 28 | "options": [] 29 | } 30 | -------------------------------------------------------------------------------- /theming/menu.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web, registry, storage, electron }, db) { 10 | await web.whenReady(); 11 | 12 | const updateTheme = async () => { 13 | const mode = await storage.get(['theme'], 'light'); 14 | document.documentElement.classList.add(mode); 15 | document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); 16 | }; 17 | document.addEventListener('visibilitychange', updateTheme); 18 | electron.onMessage('update-theme', updateTheme); 19 | updateTheme(); 20 | 21 | for (const mod of await registry.list((mod) => registry.enabled(mod.id))) { 22 | for (const sheet of mod.css?.menu || []) { 23 | web.loadStylesheet(`repo/${mod._dir}/${sheet}`); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code-line-numbers/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code line numbers", 3 | "id": "d61dc8a7-b195-465b-935f-53eea9efe74e", 4 | "version": "0.4.0", 5 | "description": "adds line numbers to code blocks.", 6 | "preview": "code-line-numbers.png", 7 | "tags": ["extension", "usability"], 8 | "authors": [ 9 | { 10 | "name": "CloudHill", 11 | "email": "rh.cloudhill@gmail.com", 12 | "homepage": "https://github.com/CloudHill", 13 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "toggle", 25 | "key": "single_lined", 26 | "label": "number single-lined code blocks", 27 | "value": false 28 | }, 29 | { 30 | "type": "select", 31 | "key": "style", 32 | "label": "line number style", 33 | "values": ["plain", "background", "border"] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /global-block-links/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "global block links", 3 | "id": "74856af4-6970-455d-bd86-0a385a402dd1", 4 | "version": "0.1.0", 5 | "description": "easily copy the global link of a page or block.", 6 | "preview": "global-block-links.jpg", 7 | "tags": ["extension", "shortcut"], 8 | "authors": [ 9 | { 10 | "name": "admiraldus", 11 | "homepage": "https://github.com/admiraldus", 12 | "avatar": "https://raw.githubusercontent.com/admiraldus/admiraldus/main/module.gif" 13 | } 14 | ], 15 | "js": { 16 | "client": ["client.mjs"] 17 | }, 18 | "css": { 19 | "client": ["client.css"] 20 | }, 21 | "options": [ 22 | { 23 | "key": "topbar_copy_icon", 24 | "label": "copy page links from the topbar (icon)", 25 | "type": "toggle", 26 | "value": true 27 | }, 28 | { 29 | "key": "topbar_copy_text", 30 | "label": "copy page links from the topbar (text)", 31 | "type": "toggle", 32 | "value": true 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /integrated-titlebar/menu.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import { createWindowButtons } from './buttons.mjs'; 10 | 11 | export default async function (api, db) { 12 | const { web } = api, 13 | tilingMode = await db.get(['tiling']), 14 | dragareaHeight = await db.get(['dragarea_height']), 15 | sidebarSelector = '.sidebar'; 16 | if (tilingMode) return; 17 | 18 | await web.whenReady([sidebarSelector]); 19 | const $dragarea = web.html`
`; 20 | document.body.prepend($dragarea); 21 | document.documentElement.style.setProperty( 22 | '--integrated_titlebar--dragarea-height', 23 | dragareaHeight + 'px' 24 | ); 25 | 26 | const $sidebar = document.querySelector(sidebarSelector), 27 | $windowButtons = await createWindowButtons(api, db); 28 | $sidebar.prepend($windowButtons); 29 | } 30 | -------------------------------------------------------------------------------- /components/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "__comment": "pseudo-mod to allow configuration of api-provided components", 3 | "name": "components", 4 | "id": "36a2ffc9-27ff-480e-84a7-c7700a7d232d", 5 | "version": "0.2.0", 6 | "description": "shared notion-style elements.", 7 | "tags": ["core"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | }, 15 | { 16 | "name": "CloudHill", 17 | "email": "rh.cloudhill@gmail.com", 18 | "homepage": "https://github.com/CloudHill", 19 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 20 | } 21 | ], 22 | "js": {}, 23 | "css": {}, 24 | "options": [ 25 | { 26 | "type": "hotkey", 27 | "key": "panel.hotkey", 28 | "label": "toggle panel hotkey", 29 | "value": "Ctrl+Alt+\\", 30 | "tooltip": "**opens/closes the side panel in notion** if a mod that uses it has been enabled" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /word-counter/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: word counter 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | #word-counter--notice { 8 | color: var(--theme--text_secondary); 9 | font-size: 14px; 10 | margin-top: 0; 11 | } 12 | 13 | .word-counter--stat { 14 | display: block; 15 | background: var(--theme--ui_interactive-hover); 16 | color: var(--theme--text); 17 | font-size: 14px; 18 | line-height: 1.2; 19 | border: 1px solid transparent; 20 | border-radius: 3px; 21 | padding: 6px 8px; 22 | cursor: pointer; 23 | user-select: none; 24 | } 25 | .word-counter--stat:focus, 26 | .word-counter--stat:hover { 27 | background: transparent; 28 | border: 1px solid var(--theme--ui_interactive-hover); 29 | } 30 | .word-counter--stat:active { 31 | background: var(--theme--ui_interactive-active); 32 | } 33 | 34 | .word-counter--stat svg { 35 | display: inline-block; 36 | height: 1em; 37 | width: 1em; 38 | margin: 0 0.4em -2px 0; 39 | color: var(--theme--icon_secondary); 40 | } 41 | -------------------------------------------------------------------------------- /bypass-preview/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: bypass preview 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | "use strict"; 8 | 9 | export default function ({ web, notion }, db) { 10 | let _openPage = {}; 11 | 12 | const getCurrentPage = () => ({ 13 | type: web.queryParams().get("p") ? "preview" : "page", 14 | id: notion.getPageID(), 15 | }); 16 | 17 | const interceptPreview = () => { 18 | const currentPage = getCurrentPage(); 19 | if ( 20 | currentPage.id !== _openPage.id || 21 | currentPage.type !== _openPage.type 22 | ) { 23 | const $openAsPage = document.querySelector( 24 | ".notion-peek-renderer a > div" 25 | ); 26 | 27 | if ($openAsPage) { 28 | if (currentPage.id === _openPage.id && currentPage.type === "preview") { 29 | history.back(); 30 | } else $openAsPage.click(); 31 | } 32 | 33 | _openPage = getCurrentPage(); 34 | } 35 | }; 36 | web.addDocumentObserver(interceptPreview, [".notion-peek-renderer"]); 37 | } 38 | -------------------------------------------------------------------------------- /quick-note/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick note", 3 | "id": "5d7c1677-6f6d-4fb5-8d6f-5067bcd0e24c", 4 | "version": "0.1.0", 5 | "description": "adds a hotkey & a button in the bottom right corner to jump to a new page in a notes database (target database id must be set).", 6 | "preview": "quick-note.png", 7 | "tags": ["integration", "shortcut"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "css": {}, 17 | "js": { 18 | "client": ["client.mjs"] 19 | }, 20 | "options": [ 21 | { 22 | "type": "text", 23 | "key": "target_db", 24 | "label": "target database id", 25 | "tooltip": "**the uuid of the database in which pages should be created** (the bit after the '/' in the database url, e.g. edbafbd8bb7045cab1408aadaf4edf9d)", 26 | "value": "" 27 | }, 28 | { 29 | "type": "hotkey", 30 | "key": "hotkey", 31 | "label": "hotkey", 32 | "value": "Ctrl+Alt+=" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /theming/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theming", 3 | "id": "0f0bf8b6-eae6-4273-b307-8fc43f2ee082", 4 | "version": "0.11.0", 5 | "description": "the default theme variables, required by other themes & extensions.", 6 | "tags": ["core"], 7 | "authors": [ 8 | { 9 | "name": "dragonwocky", 10 | "email": "thedragonring.bod@gmail.com", 11 | "homepage": "https://dragonwocky.me/", 12 | "avatar": "https://dragonwocky.me/avatar.jpg" 13 | } 14 | ], 15 | "css": { 16 | "frame": ["variables.css"], 17 | "client": ["variables.css", "prism.css", "patches.css"], 18 | "menu": ["variables.css"] 19 | }, 20 | "js": { 21 | "frame": ["frame.mjs"], 22 | "client": ["client.mjs"], 23 | "menu": ["menu.mjs"], 24 | "electron": [ 25 | { "source": "main.cjs", "target": "main/main.js" }, 26 | { "source": "rendererSearch.cjs", "target": "renderer/search.js" } 27 | ] 28 | }, 29 | "options": [ 30 | { 31 | "type": "toggle", 32 | "key": "force_load", 33 | "label": "force load overrides", 34 | "tooltip": "**override notion's colours even if no themes are enabled**", 35 | "value": false 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /focus-mode/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "focus mode", 3 | "id": "5a08598d-bfac-4167-9ae8-2bd0e2ef141e", 4 | "version": "0.3.0", 5 | "description": "hide the titlebar/menubar when the sidebar is closed (returns on hover).", 6 | "tags": ["extension", "layout"], 7 | "authors": [ 8 | { 9 | "name": "arecsu", 10 | "email": "alejandro9r@gmail.com", 11 | "homepage": "https://github.com/Arecsu", 12 | "avatar": "https://avatars.githubusercontent.com/u/12679098" 13 | } 14 | ], 15 | "css": { 16 | "frame": ["tabs.css"], 17 | "client": ["client.css"] 18 | }, 19 | "js": { 20 | "frame": ["tabs.mjs"], 21 | "client": ["client.mjs"] 22 | }, 23 | "options": [ 24 | { 25 | "type": "toggle", 26 | "key": "padded", 27 | "label": "even padding", 28 | "tooltip": "matches the empty space at the top and sides of the frame with **extra padding at the bottom when the sidebar is hidden**", 29 | "value": true 30 | }, 31 | { 32 | "type": "toggle", 33 | "key": "tabs", 34 | "label": "hide tabs", 35 | "tooltip": "makes the tab bar transparent unless hovered over", 36 | "value": false 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /dark+/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dark+", 3 | "id": "c86cfe98-e645-4822-aa6b-e2de1e08bafa", 4 | "version": "0.2.0", 5 | "description": "a vivid-colour near-black theme, with configurable accents.", 6 | "preview": "dark+.png", 7 | "tags": ["theme", "dark"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "css": { 17 | "frame": ["variables.css"], 18 | "client": ["variables.css"], 19 | "menu": ["variables.css"] 20 | }, 21 | "js": { 22 | "frame": ["theme.mjs"], 23 | "client": ["theme.mjs"], 24 | "menu": ["theme.mjs"] 25 | }, 26 | "options": [ 27 | { 28 | "type": "color", 29 | "key": "primary", 30 | "label": "primary accent color", 31 | "tooltip": "**replaces notion's blue accent**", 32 | "value": "rgba(46,170,220,1)" 33 | }, 34 | { 35 | "type": "color", 36 | "key": "secondary", 37 | "label": "secondary accent color", 38 | "tooltip": "**replaces notion's red accent**", 39 | "value": "rgba(235,87,87,1)" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /quick-note/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: quick note 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web, components, notion }, db) { 10 | const targetDbId = await db.get(['target_db']); 11 | if (!targetDbId) return; 12 | 13 | const newQuickNote = async () => { 14 | try { 15 | const { collection_id } = await notion.get(targetDbId), 16 | noteID = await notion.create( 17 | { 18 | recordValue: { 19 | properties: { title: [[`quick note: ${new Date().toLocaleString()}`]] }, 20 | }, 21 | recordType: 'page', 22 | }, 23 | { parentID: collection_id, parentTable: 'collection' } 24 | ); 25 | location.assign(`https://www.notion.so/${noteID.replace(/-/g, '')}`); 26 | } catch { 27 | alert('quick note failed: target database id did not match any known databases'); 28 | } 29 | }; 30 | 31 | await components.addCornerAction(await components.feather('feather'), newQuickNote); 32 | web.addHotkeyListener(await db.get(['hotkey']), newQuickNote); 33 | } 34 | -------------------------------------------------------------------------------- /emoji-sets/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emoji sets", 3 | "id": "a2401ee1-93ba-4b8c-9781-7f570bf5d71e", 4 | "version": "0.4.0", 5 | "description": "pick from a variety of emoji styles to use.", 6 | "preview": "emoji-sets.jpg", 7 | "tags": ["extension", "customisation"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "select", 25 | "key": "style", 26 | "label": "emoji style", 27 | "tooltip": "**initial use may involve some lag and load-time for emojis until they have all been cached**", 28 | "values": [ 29 | "twitter", 30 | "apple", 31 | "google", 32 | "microsoft", 33 | "samsung", 34 | "whatsapp", 35 | "facebook", 36 | "messenger", 37 | "joypixels", 38 | "openmoji", 39 | "emojidex", 40 | "lg", 41 | "htc", 42 | "mozilla" 43 | ] 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /always-on-top/button.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .always_on_top--button { 8 | display: flex; 9 | align-items: center; 10 | } 11 | .sidebar > .always_on_top--button { 12 | margin: 0 0 0.75rem auto; 13 | } 14 | 15 | .always_on_top--button button { 16 | user-select: none; 17 | transition: background 20ms ease-in 0s; 18 | cursor: pointer; 19 | display: inline-flex; 20 | align-items: center; 21 | justify-content: center; 22 | flex-shrink: 0; 23 | border-radius: 3px; 24 | height: 28px; 25 | width: 33px; 26 | padding: 0 0.25px 0 0; 27 | 28 | margin-left: 2px; 29 | border: none; 30 | background: transparent; 31 | font-size: 18px; 32 | } 33 | .always_on_top--button button svg { 34 | display: block; 35 | width: 20px; 36 | height: 20px; 37 | fill: var(--theme--icon); 38 | color: var(--theme--icon); 39 | } 40 | 41 | .always_on_top--button button:focus, 42 | .always_on_top--button button:hover { 43 | background: var(--theme--ui_interactive-hover); 44 | } 45 | .always_on_top--button button:active { 46 | background: var(--theme--ui_interactive-active); 47 | } 48 | -------------------------------------------------------------------------------- /font-chooser/fonts.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: font chooser 3 | * (c) 2021 TorchAtlas (https://github.com/torchatlas/) 4 | * (c) 2021 admiraldus (https://github.com/admiraldus 5 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 6 | * (https://notion-enhancer.github.io/) under the MIT license 7 | */ 8 | 9 | 'use strict'; 10 | 11 | export default async function ({}, db) { 12 | const defaults = { 13 | sans: " -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, 'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'", 14 | serif: 15 | "Lyon-Text, Georgia, YuMincho, 'Yu Mincho', 'Hiragino Mincho ProN', 'Hiragino Mincho Pro', 'Songti TC', 'Songti SC', SimSun, 'Nanum Myeongjo', NanumMyeongjo, Batang, serif", 16 | mono: 'iawriter-mono, Nitti, Menlo, Courier, monospace', 17 | code: "SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace", 18 | quotes: 'inherit', 19 | headings: 'inherit', 20 | }; 21 | for (let style of ['sans', 'serif', 'mono', 'code', 'quotes', 'headings']) { 22 | const font = await db.get([style]); 23 | document.documentElement.style.setProperty( 24 | `--font_chooser--${style}`, 25 | font || defaults[style] 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /outliner/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: outliner 3 | * (c) 2020 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | #outliner--notice { 9 | color: var(--theme--text_secondary); 10 | font-size: 14px; 11 | margin-top: 0; 12 | margin-bottom: 1rem; 13 | } 14 | 15 | .outliner--header { 16 | position: relative; 17 | margin: 0 -1rem; 18 | padding: 0 1rem; 19 | display: block; 20 | font-size: 14px; 21 | line-height: 2.2; 22 | white-space: nowrap; 23 | overflow: hidden; 24 | user-select: none; 25 | text-overflow: ellipsis; 26 | text-decoration: none; 27 | text-indent: var(--outliner--indent); 28 | color: inherit; 29 | cursor: pointer !important; 30 | transition: background 20ms ease-in; 31 | } 32 | .outliner--header:hover { 33 | background: var(--theme--ui_interactive-hover); 34 | } 35 | 36 | .outliner--header:empty::after { 37 | color: var(--theme--text_secondary); 38 | content: attr(placeholder); 39 | } 40 | 41 | /* indentation lines */ 42 | .outliner--header:not([style='--outliner--indent:0px;'])::before { 43 | content: ''; 44 | height: 100%; 45 | position: absolute; 46 | left: calc((1rem + var(--outliner--indent)) - 11px); 47 | } 48 | -------------------------------------------------------------------------------- /view-scale/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "view scale", 3 | "id": "e71ce1e0-024c-435e-a25e-7dd50448d1df", 4 | "environments": ["linux", "win32", "darwin"], 5 | "version": "0.1.0", 6 | "description": "zoom in/out of the notion window with the mousewheel or a visual slider (`ctrl/cmd +/-` are available in-app by default).", 7 | "preview": "view-scale.jpg", 8 | "tags": ["extension", "app"], 9 | "authors": [ 10 | { 11 | "name": "SP12893678", 12 | "homepage": "https://sp12893678.tk/", 13 | "avatar": "https://sp12893678.tk/img/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "number", 25 | "key": "offset", 26 | "label": "scale +/- offset (%)", 27 | "value": 10 28 | }, 29 | { 30 | "type": "number", 31 | "key": "default_zoom", 32 | "label": "default scale (%)", 33 | "value": 100 34 | }, 35 | { 36 | "type": "toggle", 37 | "key": "ui", 38 | "label": "visual slider", 39 | "value": true 40 | }, 41 | { 42 | "type": "select", 43 | "key": "mousewheel", 44 | "label": "mousewheel scaling keyboard modifier", 45 | "values": ["Control", "Alt", "Command", "Shift", "-- none --"] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /scroll-to-top/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroll to top", 3 | "id": "0a958f5a-17c5-48b5-8713-16190cae1959", 4 | "version": "0.3.0", 5 | "description": "adds an arrow in the bottom right corner to scroll back to the top of a page.", 6 | "preview": "scroll-to-top.png", 7 | "tags": ["extension", "shortcut"], 8 | "authors": [ 9 | { 10 | "name": "CloudHill", 11 | "email": "rh.cloudhill@gmail.com", 12 | "homepage": "https://github.com/CloudHill", 13 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 14 | } 15 | ], 16 | "css": {}, 17 | "js": { 18 | "client": ["client.mjs"] 19 | }, 20 | "options": [ 21 | { 22 | "type": "toggle", 23 | "key": "smooth", 24 | "label": "smooth scrolling", 25 | "value": true 26 | }, 27 | { 28 | "type": "number", 29 | "key": "top_distance_px", 30 | "label": "distance scrolled until button shown (px)", 31 | "tooltip": "**the distance in pixels that must be scrolled down before the button appears**", 32 | "value": 5000 33 | }, 34 | { 35 | "type": "number", 36 | "key": "top_distance_percent", 37 | "label": "distance scrolled until button shown (%)", 38 | "tooltip": "**the percentage of the page that must be scrolled down before the button appears**", 39 | "value": 80 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /always-on-top/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "always on top", 3 | "id": "be2d75f5-48f5-4ece-98bd-772244e559f3", 4 | "environments": ["linux", "win32", "darwin"], 5 | "version": "0.2.0", 6 | "description": "adds a button that can be used to pin the notion window on top of all other windows at all times.", 7 | "preview": "always-on-top.jpg", 8 | "tags": ["extension", "app"], 9 | "authors": [ 10 | { 11 | "name": "dragonwocky", 12 | "email": "thedragonring.bod@gmail.com", 13 | "homepage": "https://dragonwocky.me/", 14 | "avatar": "https://dragonwocky.me/avatar.jpg" 15 | } 16 | ], 17 | "css": { 18 | "client": ["button.css"], 19 | "menu": ["button.css"] 20 | }, 21 | "js": { 22 | "client": ["client.mjs"], 23 | "menu": ["menu.mjs"] 24 | }, 25 | "options": [ 26 | { 27 | "type": "text", 28 | "key": "pin_icon", 29 | "label": "pin window icon", 30 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 31 | "value": "" 32 | }, 33 | { 34 | "type": "text", 35 | "key": "unpin_icon", 36 | "label": "unpin window icon", 37 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 38 | "value": "" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /collapsible-properties/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: collapse properties 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default function ({ web, notion }, db) { 10 | const propertyListSelector = 11 | '.notion-scroller.vertical [style*="env(safe-area-inset-left)"] > [style="width: 100%; font-size: 14px;"]', 12 | $collapseButton = web.html``; 16 | $collapseButton.addEventListener('click', async (event) => { 17 | if ($collapseButton.dataset.collapsed === 'true') { 18 | await db.set([notion.getPageID()], false); 19 | $collapseButton.dataset.collapsed = false; 20 | } else { 21 | await db.set([notion.getPageID()], true); 22 | $collapseButton.dataset.collapsed = true; 23 | } 24 | }); 25 | const insertButton = async () => { 26 | if (document.contains($collapseButton)) return; 27 | const $propertyList = document.querySelector(propertyListSelector); 28 | if ($propertyList) { 29 | $collapseButton.dataset.collapsed = await db.get([notion.getPageID()], true); 30 | $propertyList.before($collapseButton); 31 | } 32 | }; 33 | web.addDocumentObserver(insertButton, [propertyListSelector]); 34 | insertButton(); 35 | } 36 | -------------------------------------------------------------------------------- /icon-sets/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icon sets", 3 | "id": "2d1f4809-9581-40dd-9bf3-4239db406483", 4 | "version": "0.4.0", 5 | "description": "upload, save and reuse custom icons directly from the icon picker. check out the [icons page](https://notion-enhancer.github.io/advanced/icons) for instructions on loading entire sets.", 6 | "preview": "icon-sets.jpg", 7 | "tags": ["integration", "customisation"], 8 | "authors": [ 9 | { 10 | "name": "dragonwocky", 11 | "email": "thedragonring.bod@gmail.com", 12 | "homepage": "https://dragonwocky.me/", 13 | "avatar": "https://dragonwocky.me/avatar.jpg" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": { 20 | "client": ["client.css"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "toggle", 25 | "key": "default_sets", 26 | "label": "load default icon sets from github", 27 | "value": true 28 | }, 29 | { 30 | "type": "toggle", 31 | "key": "prevent_quality_reduction", 32 | "label": "prevent icon quality reduction", 33 | "tooltip": "**this may break icons in widgets or be rejected by notion if images are too high quality** - encodes and submits images as data urls to prevent notion from optimising them (reduces image quality by ~20%)", 34 | "value": true 35 | }, 36 | { 37 | "type": "file", 38 | "key": "json", 39 | "label": "custom icon sets (.json)", 40 | "extensions": [".json"] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /light+/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "light+", 3 | "id": "336cbc54-67b9-4b00-b4a2-9cc86eef763b", 4 | "version": "0.2.0", 5 | "description": "a simple white theme that brightens coloured text and blocks, with configurable accents.", 6 | "preview": "light+.png", 7 | "tags": ["theme", "light"], 8 | "authors": [ 9 | { 10 | "name": "Lizishan", 11 | "homepage": "https://www.reddit.com/user/Lizishan", 12 | "avatar": "https://styles.redditmedia.com/t5_110nz4/styles/profileIcon_h1m3b16exoi51.jpg" 13 | } 14 | ], 15 | "css": { 16 | "frame": ["variables.css"], 17 | "client": ["variables.css"], 18 | "menu": ["variables.css"] 19 | }, 20 | "js": { 21 | "frame": ["theme.mjs"], 22 | "client": ["theme.mjs"], 23 | "menu": ["theme.mjs"] 24 | }, 25 | "options": [ 26 | { 27 | "type": "color", 28 | "key": "primary", 29 | "label": "primary accent color", 30 | "tooltip": "**replaces notion's blue accent**", 31 | "value": "rgba(46,170,220,1)" 32 | }, 33 | { 34 | "type": "color", 35 | "key": "secondary", 36 | "label": "secondary accent color", 37 | "tooltip": "**replaces notion's red accent**", 38 | "value": "rgba(235,87,87,1)" 39 | }, 40 | { 41 | "type": "color", 42 | "key": "highlight", 43 | "label": "highlight accent color", 44 | "tooltip": "**affects dividers, text, icons and hovered scrollbars. set this to rgba(0,0,0,0) to disable it**", 45 | "value": "rgba(0,0,0,0)" 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /tabs/main.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tabs 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function ({}, db, __exports, __eval) { 10 | const electron = require('electron'); 11 | electron.ipcMain.on('notion-enhancer:close-tab', (event, { window, id }) => { 12 | electron.webContents.fromId(window).send('notion-enhancer:close-tab', id); 13 | }); 14 | 15 | __eval(` 16 | const notionHandleActivate = handleActivate; 17 | handleActivate = (relativeUrl) => { 18 | const api = require('notion-enhancer/api/index.cjs'), 19 | { BrowserWindow } = require('electron'), 20 | windows = api.electron.getNotionWindows(), 21 | electronWindows = BrowserWindow.getAllWindows(); 22 | if (relativeUrl && windows.length) { 23 | const win = api.electron.getFocusedNotionWindow() || windows[0]; 24 | win.webContents.send('notion-enhancer:open-tab', { 25 | notionUrl: \`notion://www.notion.so\$\{relativeUrl\}\`, 26 | }); 27 | win.show(); 28 | win.focus(); 29 | } else if (relativeUrl && electronWindows.length && !windows.length) { 30 | // enhancer menu is open: prevent override 31 | const { createWindow } = api.electron.notionRequire('main/createWindow'), 32 | win = createWindow(relativeUrl); 33 | win.focus(); 34 | } else notionHandleActivate(relativeUrl); 35 | }; 36 | `); 37 | }; 38 | -------------------------------------------------------------------------------- /topbar-icons/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "topbar icons", 3 | "id": "e0700ce3-a9ae-45f5-92e5-610ded0e348d", 4 | "version": "0.3.0", 5 | "description": "choose between text or icons for the topbar buttons.", 6 | "preview": "topbar-icons.jpg", 7 | "tags": ["extension", "customisation"], 8 | "authors": [ 9 | { 10 | "name": "CloudHill", 11 | "email": "rh.cloudhill@gmail.com", 12 | "homepage": "https://github.com/CloudHill", 13 | "avatar": "https://avatars.githubusercontent.com/u/54142180" 14 | } 15 | ], 16 | "js": { 17 | "client": ["client.mjs"] 18 | }, 19 | "css": {}, 20 | "options": [ 21 | { 22 | "type": "toggle", 23 | "key": "share", 24 | "label": "share", 25 | "tooltip": "**on = icon, off = text**", 26 | "value": false 27 | }, 28 | { 29 | "type": "toggle", 30 | "key": "comments", 31 | "label": "comments", 32 | "tooltip": "**on = icon, off = text**", 33 | "value": true 34 | }, 35 | { 36 | "type": "toggle", 37 | "key": "updates", 38 | "label": "updates", 39 | "tooltip": "**on = icon, off = text**", 40 | "value": true 41 | }, 42 | { 43 | "type": "toggle", 44 | "key": "favorite", 45 | "label": "favorite", 46 | "tooltip": "**on = icon, off = text**", 47 | "value": true 48 | }, 49 | { 50 | "type": "toggle", 51 | "key": "more", 52 | "label": "more (3 dots)", 53 | "tooltip": "**on = icon, off = text**", 54 | "value": true 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /collapsible-properties/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: collapse properties 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | #enhancer--collapse-properties { 8 | display: flex; 9 | background: var(--theme--ui_interactive-hover); 10 | border: 1px solid transparent; 11 | font-size: 14px; 12 | border-radius: 3px; 13 | line-height: 1.2; 14 | font-weight: 600; 15 | padding: 0.3em 0.5em; 16 | margin: 1em 0; 17 | width: 100%; 18 | } 19 | #enhancer--collapse-properties:focus, 20 | #enhancer--collapse-properties:hover { 21 | background: transparent; 22 | border: 1px solid var(--theme--ui_interactive-hover); 23 | } 24 | #enhancer--collapse-properties:active { 25 | background: var(--theme--ui_interactive-active); 26 | } 27 | 28 | #enhancer--collapse-properties > span { 29 | text-align: left; 30 | color: var(--theme--text); 31 | } 32 | #enhancer--collapse-properties > span:before { 33 | content: 'Properties'; 34 | } 35 | #enhancer--collapse-properties > svg { 36 | fill: var(--theme--icon); 37 | height: 0.8em; 38 | width: 0.8em; 39 | margin: auto 0.5em auto 0; 40 | transition: transform 200ms ease-out 0s; 41 | } 42 | 43 | #enhancer--collapse-properties[data-collapsed='false'] > svg { 44 | transform: rotateZ(180deg); 45 | } 46 | #enhancer--collapse-properties[data-collapsed='true'] > svg { 47 | transform: rotateZ(90deg); 48 | } 49 | 50 | #enhancer--collapse-properties + div { 51 | overflow: hidden; 52 | } 53 | #enhancer--collapse-properties[data-collapsed='true'] + div { 54 | max-height: 0 !important; 55 | opacity: 0; 56 | } 57 | -------------------------------------------------------------------------------- /integrated-titlebar/frame.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import { createWindowButtons } from './buttons.mjs'; 10 | 11 | export default async function (api, db) { 12 | const { web, registry } = api, 13 | tilingMode = await db.get(['tiling']), 14 | dragareaHeight = await db.get(['dragarea_height']), 15 | tabsEnabled = await registry.enabled('e1692c29-475e-437b-b7ff-3eee872e1a42'); 16 | 17 | if (tabsEnabled && !tilingMode) { 18 | const windowActionsSelector = '#window-actions'; 19 | await web.whenReady([windowActionsSelector]); 20 | 21 | const $topbarActions = document.querySelector(windowActionsSelector), 22 | $windowButtons = await createWindowButtons(api, db); 23 | web.render($topbarActions, $windowButtons); 24 | } else { 25 | const dragareaSelector = '[style*="-webkit-app-region: drag;"]'; 26 | await web.whenReady([dragareaSelector]); 27 | 28 | const dragarea = document.querySelector(dragareaSelector); 29 | dragarea.style.top = '2px'; 30 | dragarea.style.height = tilingMode ? '0' : `${dragareaHeight}px`; 31 | 32 | document.getElementById('notion').addEventListener('ipc-message', (event) => { 33 | switch (event.channel) { 34 | case 'notion-enhancer:sidebar-width': 35 | dragarea.style.left = event.args[0]; 36 | break; 37 | case 'notion-enhancer:panel-width': 38 | dragarea.style.right = event.args[0]; 39 | break; 40 | } 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /neutral/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: neutral 3 | * (c) 2020 Arecsu 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-dark-theme [placeholder='Heading 1'], 9 | .notion-dark-theme [placeholder='Heading 2'], 10 | .notion-dark-theme [placeholder='Heading 3'] { 11 | padding: 3px 1px !important; 12 | } 13 | 14 | /* hide sidebar "new page" button */ 15 | .notion-dark-theme .notion-sidebar > [style*='flex: 0 0 auto; margin-top: auto;'] { 16 | display: none !important; 17 | } 18 | 19 | /* 1.3 supreme ratio. https://www.modularscale.com/ */ 20 | .notion-collection_view_page-block > div[placeholder='Untitled'], 21 | .notion-frame .notion-page-block > div[placeholder='Untitled'], 22 | .notion-overlay-container .notion-page-block > div[placeholder='Untitled'] { 23 | font-size: 33px !important; 24 | } 25 | [placeholder='Heading 1'] { 26 | font-size: 2.2rem !important; 27 | } 28 | [placeholder='Heading 2'] { 29 | font-size: 1.687rem !important; 30 | } 31 | [placeholder='Heading 3'] { 32 | font-size: 1.3rem !important; 33 | } 34 | .notion-frame .notion-scroller.vertical.horizontal .notion-page-content, 35 | .notion-overlay-container .notion-scroller.vertical .notion-page-content { 36 | font-size: 15px !important; 37 | } 38 | .notion-frame 39 | .notion-scroller.vertical.horizontal 40 | .notion-page-content[style*='font-size: 14px'], 41 | .notion-overlay-container 42 | .notion-scroller.vertical 43 | .notion-page-content[style*='font-size: 14px'] { 44 | font-size: 13.5px !important; 45 | } 46 | .notion-code-block [placeholder=' '] { 47 | font-size: 0.9em !important; 48 | } 49 | -------------------------------------------------------------------------------- /code-line-numbers/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: code line numbers 3 | * (c) 2020 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-code-block.line-numbers { 9 | position: relative; 10 | } 11 | .code_line_numbers--plain:not(:empty) + div, 12 | .code_line_numbers--background:not(:empty) + div, 13 | .code_line_numbers--border:not(:empty) + div { 14 | padding-left: 64px !important; 15 | } 16 | 17 | .code_line_numbers--plain, 18 | .code_line_numbers--background, 19 | .code_line_numbers--border { 20 | position: absolute; 21 | left: 0; 22 | right: calc(100% - 64px); 23 | top: 34px; 24 | bottom: 32px; 25 | padding-right: 27px; 26 | 27 | font-size: 85%; 28 | font-family: var(--theme--font_code); 29 | text-align: right; 30 | line-height: 1.5; 31 | opacity: 0.8; 32 | color: var(--theme--text_secondary); 33 | 34 | overflow: hidden; 35 | pointer-events: none; 36 | } 37 | .code_line_numbers--plain:empty, 38 | .code_line_numbers--background:empty, 39 | .code_line_numbers--border:empty { 40 | display: none; 41 | } 42 | .code_line_numbers--background::before { 43 | content: ''; 44 | position: absolute; 45 | top: 0; 46 | left: 7.25px; 47 | width: calc(100% - 27px); 48 | height: 100%; 49 | display: block; 50 | background-color: var(--theme--bg); 51 | border-radius: 4px; 52 | z-index: -1; 53 | } 54 | .code_line_numbers--border::before { 55 | content: ''; 56 | position: absolute; 57 | top: 0; 58 | right: calc(100% - 52px); 59 | width: 2px; 60 | height: 100%; 61 | display: block; 62 | background-color: var(--theme--ui_divider); 63 | } 64 | -------------------------------------------------------------------------------- /tray/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tray", 3 | "id": "f96f4a73-21af-4e3f-a68f-ab4976b020da", 4 | "environments": ["linux", "win32", "darwin"], 5 | "version": "0.11.0", 6 | "description": "adds an icon to the system tray/menubar for extra app/window management features (e.g. open on startup, a global hotkey).", 7 | "preview": "tray.jpg", 8 | "tags": ["extension", "app"], 9 | "authors": [ 10 | { 11 | "name": "dragonwocky", 12 | "email": "thedragonring.bod@gmail.com", 13 | "homepage": "https://dragonwocky.me/", 14 | "avatar": "https://dragonwocky.me/avatar.jpg" 15 | } 16 | ], 17 | "css": {}, 18 | "js": { 19 | "client": ["client.mjs"], 20 | "electron": [ 21 | { "source": "main.cjs", "target": "main/main.js" }, 22 | { "source": "createWindow.cjs", "target": "main/createWindow.js" } 23 | ] 24 | }, 25 | "options": [ 26 | { 27 | "type": "toggle", 28 | "key": "startup", 29 | "label": "open notion on startup", 30 | "tooltip": "**if the 'run notion in the background' option is also enabled, the app will open in the background on startup** (this option may require relaunching the app BEFORE restarting your system to properly take effect)", 31 | "value": false 32 | }, 33 | { 34 | "type": "toggle", 35 | "key": "run_in_background", 36 | "label": "run notion in the background", 37 | "tooltip": "**pressing the close button or toggling window visibility will hide the app, running notion in the background** (instead of quitting or minimizing it)", 38 | "value": true 39 | }, 40 | { 41 | "type": "hotkey", 42 | "key": "hotkey", 43 | "label": "toggle window visibility hotkey", 44 | "value": "Ctrl+Shift+A" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /scroll-to-top/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: scroll to top 3 | * (c) 2021 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | 'use strict'; 9 | 10 | export default async function ({ web, components }, db) { 11 | const scrollStyle = (await db.get(['smooth'])) ? 'smooth' : 'auto', 12 | $scrollButton = await components.addCornerAction( 13 | await components.feather('chevron-up'), 14 | () => { 15 | document.querySelector('.notion-frame > .notion-scroller').scroll({ 16 | top: 0, 17 | left: 0, 18 | behavior: scrollStyle, 19 | }); 20 | } 21 | ); 22 | 23 | let $scroller; 24 | const topDistancePx = +(await db.get(['top_distance_px'])), 25 | topDistancePercent = 0.01 * (await db.get(['top_distance_percent'])), 26 | adjustButtonVisibility = async () => { 27 | if (!$scroller) return; 28 | $scrollButton.classList.add('hidden'); 29 | const scrolledDistance = 30 | $scroller.scrollTop >= topDistancePx || 31 | $scroller.scrollTop >= 32 | ($scroller.scrollHeight - $scroller.clientHeight) * topDistancePercent; 33 | if (scrolledDistance) $scrollButton.classList.remove('hidden'); 34 | }; 35 | web.addDocumentObserver(() => { 36 | $scroller = document.querySelector('.notion-frame > .notion-scroller'); 37 | $scroller.removeEventListener('scroll', adjustButtonVisibility); 38 | $scroller.addEventListener('scroll', adjustButtonVisibility); 39 | }, ['.notion-frame > .notion-scroller']); 40 | adjustButtonVisibility(); 41 | 42 | if (topDistancePx && topDistancePercent) $scrollButton.classList.add('hidden'); 43 | } 44 | -------------------------------------------------------------------------------- /calendar-scroll/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: calendar scroll 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const pageSelector = '.notion-page-content', 10 | calendarSelector = '.notion-calendar-view:not([data-calendar-scroll])', 11 | scrollerSelector = '.notion-frame > .notion-scroller', 12 | toolbarSelector = '.notion-calendar-view > :first-child > :first-child > :first-child', 13 | todaySelector = '.notion-calendar-view-day[style*="background:"]'; 14 | 15 | export default function ({ web }, db) { 16 | const insertButton = () => { 17 | document.querySelectorAll(calendarSelector).forEach(($calendar) => { 18 | $calendar.dataset.calendarScroll = true; 19 | const $page = document.querySelector(pageSelector); 20 | if ($page) return; 21 | const $toolbar = $calendar.querySelector(toolbarSelector), 22 | $pageScroller = document.querySelector(scrollerSelector), 23 | $scrollButton = web.html``; 24 | $scrollButton.addEventListener('click', async (event) => { 25 | let $today = $calendar.querySelector(todaySelector); 26 | if (!$today) { 27 | $toolbar.children[4].click(); 28 | await new Promise((res, rej) => setTimeout(res, 500)); 29 | $today = $calendar.querySelector(todaySelector); 30 | } 31 | $pageScroller.scroll({ 32 | top: $today.offsetParent.offsetParent.offsetTop + 70, 33 | behavior: 'auto', 34 | }); 35 | }); 36 | $toolbar.insertBefore($scrollButton, $toolbar.children[2]); 37 | }); 38 | }; 39 | web.addDocumentObserver(insertButton, [calendarSelector]); 40 | insertButton(); 41 | } 42 | -------------------------------------------------------------------------------- /indentation-lines/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "indentation lines", 3 | "id": "35815b3b-3916-4dc6-8769-c9c2448f8b57", 4 | "version": "0.2.0", 5 | "description": "adds vertical relationship lines to make list trees easier to follow.", 6 | "preview": "indentation-lines.jpg", 7 | "tags": ["extension", "usability"], 8 | "authors": [ 9 | { 10 | "name": "runargs", 11 | "email": "alnbaldon@gmail.com", 12 | "homepage": "http://github.com/runargs", 13 | "avatar": "https://avatars.githubusercontent.com/u/39810066" 14 | } 15 | ], 16 | "css": { 17 | "client": ["client.css"] 18 | }, 19 | "js": { 20 | "client": ["client.mjs"] 21 | }, 22 | "options": [ 23 | { 24 | "type": "select", 25 | "key": "style", 26 | "label": "style", 27 | "values": ["solid", "dashed", "dotted", "soft", "rainbow"] 28 | }, 29 | { 30 | "type": "toggle", 31 | "key": "bulleted_list", 32 | "label": "bulleted lists", 33 | "value": true 34 | }, 35 | { 36 | "type": "toggle", 37 | "key": "numbered_list", 38 | "label": "numbered lists", 39 | "value": true 40 | }, 41 | { 42 | "type": "toggle", 43 | "key": "to_do", 44 | "label": "to-do lists", 45 | "value": true 46 | }, 47 | { 48 | "type": "toggle", 49 | "key": "toggle", 50 | "label": "toggle lists", 51 | "value": true 52 | }, 53 | { 54 | "type": "toggle", 55 | "key": "toggle_header", 56 | "label": "toggle headers", 57 | "value": true 58 | }, 59 | { 60 | "type": "toggle", 61 | "key": "table_of_contents", 62 | "label": "tables of contents", 63 | "value": true 64 | }, 65 | { 66 | "type": "toggle", 67 | "key": "outliner", 68 | "label": "outliner (panel extension)", 69 | "value": true 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /dark+/theme.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: dark+ 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ fmt }, db) { 10 | { 11 | const primary = await db.get(['primary']), 12 | [r, g, b] = primary 13 | .slice(5, -1) 14 | .split(',') 15 | .map((i) => parseInt(i)); 16 | if (!(r === 46 && g === 170 && b === 220)) { 17 | document.documentElement.style.setProperty('--dark_plus--accent_blue', primary); 18 | document.documentElement.style.setProperty( 19 | '--dark_plus--accent_blue-selection', 20 | `rgba(${r},${g},${b},0.2)` 21 | ); 22 | document.documentElement.style.setProperty( 23 | '--dark_plus--accent_blue-hover', 24 | fmt.rgbLogShade(0.05, primary) 25 | ); 26 | document.documentElement.style.setProperty( 27 | '--dark_plus--accent_blue-active', 28 | fmt.rgbLogShade(0.025, primary) 29 | ); 30 | document.documentElement.style.setProperty( 31 | '--dark_plus--accent_blue-text', 32 | fmt.rgbContrast(r, g, b) 33 | ); 34 | } 35 | } 36 | 37 | { 38 | const secondary = await db.get(['secondary']), 39 | [r, g, b] = secondary 40 | .slice(5, -1) 41 | .split(',') 42 | .map((i) => parseInt(i)); 43 | if (!(r === 235 && g === 87 && b === 87)) { 44 | document.documentElement.style.setProperty('--dark_plus--accent_red', secondary); 45 | document.documentElement.style.setProperty( 46 | '--dark_plus--accent_red-button', 47 | `rgba(${r},${g},${b},0.2)` 48 | ); 49 | document.documentElement.style.setProperty( 50 | '--dark_plus--accent_red-text', 51 | fmt.rgbContrast(r, g, b) 52 | ); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /menu/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: menu 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const notificationsURL = 'https://notion-enhancer.github.io/notifications.json'; 10 | 11 | export default async function ({ env, fs, storage, registry, web }, db) { 12 | web.addHotkeyListener(await db.get(['hotkey']), env.focusMenu); 13 | 14 | const sidebarSelector = '.notion-sidebar-container .notion-sidebar > div:nth-child(3) > div > div:nth-child(2)'; 15 | await web.whenReady([sidebarSelector]); 16 | 17 | const $sidebarLink = web.html``; 23 | $sidebarLink.addEventListener('click', env.focusMenu); 24 | 25 | const notifications = { 26 | cache: await storage.get(['notifications'], []), 27 | provider: await fs.getJSON(notificationsURL), 28 | count: (await registry.errors()).length, 29 | }; 30 | for (const notification of notifications.provider) { 31 | if ( 32 | !notifications.cache.includes(notification.id) && 33 | notification.version === env.version && 34 | (!notification.environments || notification.environments.includes(env.name)) 35 | ) { 36 | notifications.count++; 37 | } 38 | } 39 | if ((await storage.get(['last_read_changelog'])) !== env.version) notifications.count++; 40 | if (notifications.count) { 41 | web.render( 42 | $sidebarLink.children[0], 43 | web.html`
${notifications.count}
` 44 | ); 45 | } 46 | 47 | web.render(document.querySelector(sidebarSelector), $sidebarLink); 48 | } 49 | -------------------------------------------------------------------------------- /theming/electronSearch.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | #container { 8 | background: var(--theme--bg) !important; 9 | box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.05)) 0px 0px 0px 1px, 10 | var(--theme--ui_shadow, rgba(15, 15, 15, 0.1)) 0px 3px 6px, 11 | var(--theme--ui_shadow, rgba(15, 15, 15, 0.2)) 0px 9px 24px !important; 12 | } 13 | 14 | #container #results { 15 | color: var(--theme--text) !important; 16 | } 17 | 18 | #container #prev, 19 | #container #next { 20 | border-color: var(--theme--ui_divider) !important; 21 | } 22 | #container .enabled:focus, 23 | #container .enabled:hover { 24 | background: var(--theme--ui_interactive-hover) !important; 25 | } 26 | #container .enabled:active { 27 | background: var(--theme--ui_interactive-active) !important; 28 | } 29 | #container #prev svg, 30 | #container #next svg { 31 | fill: var(--theme--icon_secondary) !important; 32 | } 33 | #container #button-separator { 34 | background: var(--theme--ui_divider) !important; 35 | } 36 | 37 | #container #search-icon svg, 38 | #container #clear-icon svg { 39 | fill: var(--theme--icon) !important; 40 | } 41 | #container #input-wrap #search { 42 | background: var(--theme--ui_input) !important; 43 | color: var(--theme--text) !important; 44 | } 45 | 46 | #container #done { 47 | background: var(--theme--accent_blue) !important; 48 | color: var(--theme--accent_blue-text) !important; 49 | } 50 | #container #done:focus, 51 | #container #done:hover { 52 | background: var(--theme--accent_blue-hover) !important; 53 | } 54 | #container #done:active { 55 | background: var(--theme--accent_blue-active) !important; 56 | } 57 | 58 | #container .enabled::after, 59 | #container #done::after { 60 | background: transparent !important; 61 | } 62 | -------------------------------------------------------------------------------- /tweaks/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tweaks 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web }, db) { 10 | const cssInsert = await db.get(['insert.css']); 11 | if (cssInsert?.filename) { 12 | document.head.append( 13 | web.html`` 14 | ); 15 | } 16 | 17 | const responsiveBreakpointPx = +(await db.get(['tweak.responsive_breakpoint_px'])), 18 | responsiveBreakpointPercent = 19 | screen.width * 0.01 * (await db.get(['tweak.responsive_breakpoint_percent'])), 20 | addResponsiveBreakpoint = () => { 21 | document.body.classList.remove('enhancer--tweak-responsive_breakpoint'); 22 | if ( 23 | window.innerWidth <= responsiveBreakpointPx || 24 | window.innerWidth <= responsiveBreakpointPercent 25 | ) { 26 | document.body.classList.add('enhancer--tweak-responsive_breakpoint'); 27 | } 28 | }; 29 | window.addEventListener('resize', addResponsiveBreakpoint); 30 | addResponsiveBreakpoint(); 31 | 32 | const tweaks = [ 33 | 'full_width_pages', 34 | 'normalise_table_scroll', 35 | 'hide_help', 36 | 'hide_slash_for_commands', 37 | 'snappy_transitions', 38 | 'thicker_bold', 39 | 'spaced_lines', 40 | 'condensed_bullets', 41 | 'bracketed_links', 42 | 'accented_links', 43 | 'quotation_marks', 44 | ]; 45 | for (const tweak of tweaks) { 46 | if (await db.get([`tweak.${tweak}`])) { 47 | document.body.classList.add(`enhancer--tweak-${tweak}`); 48 | } 49 | } 50 | 51 | const imgAlignment = await db.get(['tweak.img_alignment']); 52 | if (imgAlignment !== 'center') { 53 | document.body.classList.add(`enhancer--tweak-img_alignment-${imgAlignment}`); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /menu/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: menu 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .enhancer--sidebarMenuLink { 8 | user-select: none; 9 | -webkit-user-select: none; 10 | transition: background 20ms ease-in 0s; 11 | cursor: pointer; 12 | color: var(--theme--text_secondary); 13 | } 14 | .enhancer--sidebarMenuLink:hover { 15 | background: var(--theme--ui_interactive-hover); 16 | } 17 | .enhancer--sidebarMenuLink svg { 18 | width: 16px; 19 | height: 16px; 20 | margin-left: 2px; 21 | } 22 | .enhancer--sidebarMenuLink > div { 23 | display: flex; 24 | align-items: center; 25 | min-height: 27px; 26 | font-size: 14px; 27 | padding: 2px 14px; 28 | width: 100%; 29 | } 30 | .enhancer--sidebarMenuLink > div > :first-child { 31 | flex-shrink: 0; 32 | flex-grow: 0; 33 | border-radius: 3px; 34 | width: 22px; 35 | height: 22px; 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | margin-right: 8px; 40 | } 41 | .enhancer--sidebarMenuLink > div > :nth-child(2) { 42 | flex: 1 1 auto; 43 | white-space: nowrap; 44 | overflow: hidden; 45 | text-overflow: ellipsis; 46 | } 47 | 48 | .enhancer--sidebarMenuLink:active { 49 | color: var(--theme--text); 50 | } 51 | .enhancer--sidebarMenuLink > div > .enhancer--notificationBubble { 52 | display: flex; 53 | } 54 | .enhancer--sidebarMenuLink > div > .enhancer--notificationBubble > div { 55 | display: inline-flex; 56 | align-items: center; 57 | justify-content: center; 58 | width: 16px; 59 | height: 16px; 60 | font-size: 10px; 61 | font-weight: 600; 62 | border-radius: 3px; 63 | color: var(--theme--accent_red-text); 64 | background: var(--theme--accent_red); 65 | } 66 | .enhancer--sidebarMenuLink > div > .enhancer--notificationBubble > div > span { 67 | margin-bottom: 1px; 68 | margin-left: -0.5px; 69 | } 70 | -------------------------------------------------------------------------------- /global-block-links/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: global block links 3 | * (c) 2021 admiraldus (https://github.com/admiraldus) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .global_block_links--topbar_copy { 9 | display: inline-flex; 10 | align-items: center; 11 | border-radius: 3px; 12 | height: 28px; 13 | min-width: 0px; 14 | padding-right: 8px; 15 | padding-left: 6px; 16 | font-size: 14px; 17 | line-height: 1.2; 18 | color: var(--theme--text); 19 | cursor: pointer; 20 | transition: background 20ms ease-in 0s; 21 | user-select: none; 22 | } 23 | .global_block_links--topbar_copy > svg { 24 | display: block; 25 | height: 16px; 26 | width: 16px; 27 | fill: var(--theme--icon); 28 | } 29 | .global_block_links--topbar_copy > span { 30 | margin-left: 2px; 31 | opacity: 1; 32 | transition: opacity 0.4s ease; 33 | } 34 | .global_block_links--topbar_copy > svg + span { 35 | margin-left: 6px; 36 | } 37 | 38 | .global_block_links--block_copy { 39 | display: flex; 40 | align-items: center; 41 | height: 28px; 42 | width: 100%; 43 | font-size: 14px; 44 | line-height: 1.2; 45 | cursor: pointer; 46 | transition: background 20ms ease-in 0s; 47 | user-select: none; 48 | } 49 | .global_block_links--block_copy > svg { 50 | display: block; 51 | margin-left: 14px; 52 | height: 17px; 53 | width: 17px; 54 | color: var(--theme--icon); 55 | } 56 | .global_block_links--block_copy > span { 57 | margin-right: 14px; 58 | margin-left: 8px; 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | white-space: nowrap; 62 | } 63 | 64 | .global_block_links--topbar_copy:hover, 65 | .global_block_links--block_copy:hover { 66 | background: var(--theme--ui_interactive-hover); 67 | } 68 | 69 | .global_block_links--hidden { 70 | position: absolute; 71 | top: -9999px; 72 | opacity: 0 !important; 73 | } 74 | -------------------------------------------------------------------------------- /right-to-left/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: right to left 3 | * (c) 2021 obahareth (https://omar.engineer) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | 'use strict'; 9 | 10 | export default async function ({ web }, db) { 11 | const pageContentSelector = ` 12 | .notion-page-content > 13 | div[data-block-id]:not([dir]):not(.notion-column_list-block):not(.notion-collection_view_page-block), 14 | [placeholder="Untitled"]:not([dir]), 15 | .notion-column-block > div[data-block-id]:not([dir]), 16 | .notion-collection_view-block:not([dir]), 17 | .notion-table-view:not([dir]), 18 | .notion-board-view:not([dir]), 19 | .notion-gallery-view:not([dir])`, 20 | listItemSelector = ` 21 | div[placeholder="List"]:not([style*="text-align: start"]), 22 | div[placeholder="To-do"]:not([style*="text-align: start"]), 23 | div[placeholder="Toggle"]:not([style*="text-align: start"])`, 24 | inlineEquationSelector = 25 | '.notion-text-equation-token .katex-html:not([style*="direction: rtl;"])'; 26 | 27 | const autoAlignText = () => { 28 | document 29 | .querySelectorAll(pageContentSelector) 30 | .forEach(($block) => $block.setAttribute('dir', 'auto')); 31 | document.querySelectorAll(listItemSelector).forEach(($item) => { 32 | $item.style['text-align'] = 'start'; 33 | }); 34 | document.querySelectorAll(inlineEquationSelector).forEach(($equation) => { 35 | $equation.style.direction = 'rtl'; 36 | $equation.style.display = 'inline-flex'; 37 | $equation.style.flexDirection = 'row-reverse'; 38 | for (const $symbol of $equation.children) { 39 | $symbol.style.direction = 'ltr'; 40 | } 41 | }); 42 | }; 43 | web.addDocumentObserver(autoAlignText, [ 44 | pageContentSelector, 45 | listItemSelector, 46 | inlineEquationSelector, 47 | ]); 48 | await web.whenReady(); 49 | autoAlignText(); 50 | } 51 | -------------------------------------------------------------------------------- /topbar-icons/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: topbar icons 3 | * (c) 2020 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | 'use strict'; 9 | 10 | export default async function ({ web, components }, db) { 11 | await web.whenReady(['.notion-topbar-action-buttons']); 12 | 13 | const observeButton = (selector, label = '') => { 14 | const updateButton = () => { 15 | const $btns = document.querySelectorAll(selector); 16 | $btns.forEach(($btn) => { 17 | $btn.style.width = 'auto'; 18 | $btn.style.fontSize = '14px'; 19 | $btn.style.lineHeight = '1.2'; 20 | $btn.style.paddingLeft = '8px'; 21 | $btn.style.paddingRight = '8px'; 22 | const innerHTML = label || $btn.ariaLabel; 23 | if ($btn.innerHTML !== innerHTML) $btn.innerHTML = innerHTML; 24 | }); 25 | }; 26 | web.addDocumentObserver(updateButton, [selector]); 27 | updateButton(); 28 | }; 29 | 30 | if ((await db.get(['share'])) === true) { 31 | const selector = '.notion-topbar-share-menu', 32 | label = await components.feather('share-2', { 33 | style: 'width:16px;height:16px;color:var(--theme--icon);', 34 | }); 35 | observeButton(selector, label); 36 | } 37 | 38 | if ((await db.get(['comments'])) === false) { 39 | const selector = '.notion-topbar-comments-button'; 40 | observeButton(selector); 41 | } 42 | 43 | if ((await db.get(['updates'])) === false) { 44 | const selector = 45 | '.notion-topbar-updates-button, .notion-topbar-share-menu ~ [aria-label="Updates"]'; 46 | observeButton(selector); 47 | } 48 | 49 | if ((await db.get(['favorite'])) === false) { 50 | const selector = '.notion-topbar-share-menu ~ [aria-label^="Fav"]'; 51 | observeButton(selector); 52 | } 53 | 54 | if ((await db.get(['more'])) === false) { 55 | const selector = '.notion-topbar-more-button', 56 | label = 'More'; 57 | observeButton(selector, label); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tray/createWindow.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tray 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function (api, db, __exports, __eval) { 10 | const electron = require('electron'), 11 | urlHelpers = api.electron.notionRequire('helpers/urlHelpers'), 12 | runInBackground = await db.get(['run_in_background']); 13 | if (!runInBackground) return; 14 | 15 | let appQuit = false; 16 | electron.app.once('before-quit', () => { 17 | appQuit = true; 18 | }); 19 | 20 | const notionCreateWindow = __exports.createWindow; 21 | __exports.createWindow = (relativeUrl = '', args) => { 22 | const windows = api.electron.getNotionWindows(); 23 | if (windows.length) windows.forEach((win) => win.show()); 24 | 25 | if (relativeUrl || !windows.length) { 26 | // hijack close event to hide instead 27 | const window = notionCreateWindow(relativeUrl, args); 28 | window.prependListener('close', (e) => { 29 | const isLastWindow = electron.BrowserWindow.getAllWindows().length === 1; 30 | if (!appQuit && isLastWindow) { 31 | window.hide(); 32 | e.preventDefault(); 33 | throw new Error(': prevent window close'); 34 | } 35 | }); 36 | 37 | // no other windows yet + opened at startup = hide 38 | const wasOpenedAtStartup = 39 | process.argv.includes('--startup') || 40 | app.getLoginItemSettings({ args: ['--startup'] }).wasOpenedAtLogin; 41 | if (!windows.length && wasOpenedAtStartup) { 42 | window.once('ready-to-show', () => window.hide()); 43 | } 44 | 45 | return window; 46 | } else { 47 | const window = api.electron.getFocusedNotionWindow() || windows[0]; 48 | // prevents duplicate windows on dock/taskbar click 49 | window.focus(); 50 | if (relativeUrl) { 51 | // handle requests passed via the notion:// protocol 52 | // or ctrl+click 53 | window.loadURL(urlHelpers.getIndexUrl(relativeUrl)); 54 | } 55 | return window; 56 | } 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /font-chooser/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "font chooser", 3 | "id": "e0d8d148-45e7-4d79-8313-e7b2ad8abe16", 4 | "version": "0.4.0", 5 | "description": "set custom fonts.", 6 | "tags": ["extension", "customisation"], 7 | "authors": [ 8 | { 9 | "name": "TorchAtlas", 10 | "homepage": "https://github.com/torchatlas/", 11 | "avatar": "https://avatars.githubusercontent.com/u/12666855" 12 | } 13 | ], 14 | "js": { 15 | "client": ["fonts.mjs"], 16 | "menu": ["fonts.mjs"], 17 | "frame": ["fonts.mjs"] 18 | }, 19 | "css": { 20 | "client": ["fonts.css"], 21 | "menu": ["fonts.css"], 22 | "frame": ["fonts.css"] 23 | }, 24 | "options": [ 25 | { 26 | "type": "text", 27 | "key": "sans", 28 | "label": "sans-serif (inc. ui)", 29 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 30 | "value": "" 31 | }, 32 | { 33 | "type": "text", 34 | "key": "serif", 35 | "label": "serif", 36 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 37 | "value": "" 38 | }, 39 | { 40 | "type": "text", 41 | "key": "mono", 42 | "label": "monospace", 43 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 44 | "value": "" 45 | }, 46 | { 47 | "type": "text", 48 | "key": "code", 49 | "label": "code", 50 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 51 | "value": "" 52 | }, 53 | { 54 | "type": "text", 55 | "key": "quotes", 56 | "label": "quotes", 57 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 58 | "value": "" 59 | }, 60 | { 61 | "type": "text", 62 | "key": "headings", 63 | "label": "headings", 64 | "tooltip": "**this font needs to be installed on your device** - leave the option blank to use notion's default font", 65 | "value": "" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /menu/markdown.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: menu 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .markdown table { 8 | border-spacing: 0; 9 | border: 1px solid var(--theme--ui_divider); 10 | } 11 | .markdown table th { 12 | text-align: left; 13 | } 14 | .markdown table th, 15 | .markdown table td { 16 | padding: 5px 8px 6px; 17 | border: 1px solid var(--theme--ui_divider); 18 | } 19 | .markdown h1 { 20 | font-size: 1.875rem; 21 | margin: 1rem 0 0.5rem 0; 22 | } 23 | .markdown h2 { 24 | font-size: 1.5rem; 25 | margin: 1rem 0 0.5rem 0; 26 | } 27 | .markdown h3 { 28 | font-size: 1.25rem; 29 | margin: 1rem 0 0.5rem 0; 30 | } 31 | .markdown h4 { 32 | font-weight: bold; 33 | margin: 0.5rem 0; 34 | } 35 | .markdown ul, 36 | .markdown ol { 37 | padding-left: 1.25rem; 38 | } 39 | .markdown ul { 40 | list-style: disc; 41 | } 42 | .markdown ol { 43 | list-style: decimal; 44 | } 45 | .markdown li { 46 | margin: 1px 0; 47 | } 48 | .markdown ol li { 49 | padding-left: 0.25rem; 50 | } 51 | .markdown blockquote { 52 | border-left: 2px solid currentColor; 53 | padding-left: 0.75rem; 54 | margin: 0.5rem 0; 55 | } 56 | .markdown hr { 57 | border: 0.5px solid var(--theme--ui_divider); 58 | } 59 | .markdown-inline a, 60 | .markdown a { 61 | opacity: 0.7; 62 | text-decoration: none; 63 | border-bottom: 0.05em solid var(--theme--text_secondary); 64 | } 65 | .markdown-inline a:hover, 66 | .markdown a:hover { 67 | opacity: 0.9; 68 | } 69 | 70 | .markdown :not(pre) > code, 71 | .markdown-inline code { 72 | padding: 0.2em 0.4em; 73 | border-radius: 3px; 74 | background: var(--theme--code_inline); 75 | color: var(--theme--code_inline-text); 76 | } 77 | .markdown pre { 78 | padding: 2em 1.25em; 79 | border-radius: 3px; 80 | tab-size: 2; 81 | white-space: pre; 82 | overflow-x: auto; 83 | background: var(--theme--code); 84 | color: var(--theme--code_plain); 85 | } 86 | .markdown pre, 87 | .markdown code, 88 | .markdown-inline code { 89 | font-family: var(--theme--font_code); 90 | font-size: 0.796875rem; 91 | text-align: left; 92 | word-spacing: normal; 93 | word-break: normal; 94 | word-wrap: normal; 95 | hyphens: none; 96 | line-height: 1.5; 97 | } 98 | -------------------------------------------------------------------------------- /theming/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web, registry, storage, electron }, db) { 10 | const enabledThemes = await registry.list( 11 | async (m) => (await registry.enabled(m.id)) && m.tags.includes('theme') 12 | ); 13 | if (enabledThemes.length || (await db.get(['force_load']))) { 14 | // only override colors if theme is enable for perf 15 | web.loadStylesheet('repo/theming/theme.css'); 16 | web.loadStylesheet('repo/theming/colors.css'); 17 | } 18 | 19 | const updateTheme = async () => { 20 | if (document.visibilityState !== 'visible' && !document.hasFocus()) return; 21 | const isDark = 22 | document.querySelector('.notion-dark-theme') || 23 | document.querySelector('.notion-body.dark'), 24 | isLight = document.querySelector('.notion-light-theme'), 25 | mode = isDark ? 'dark' : isLight ? 'light' : ''; 26 | if (!mode) return; 27 | await storage.set(['theme'], mode); 28 | document.documentElement.classList.add(mode); 29 | document.documentElement.classList.remove(mode === 'light' ? 'dark' : 'light'); 30 | electron.sendMessage('update-theme'); 31 | const searchThemeVars = [ 32 | 'bg', 33 | 'text', 34 | 'icon', 35 | 'icon_secondary', 36 | 'accent_blue', 37 | 'accent_blue-text', 38 | 'accent_blue-hover', 39 | 'accent_blue-active', 40 | 'ui_shadow', 41 | 'ui_divider', 42 | 'ui_input', 43 | 'ui_interactive-hover', 44 | 'ui_interactive-active', 45 | ].map((key) => [ 46 | key, 47 | window.getComputedStyle(document.documentElement).getPropertyValue(`--theme--${key}`), 48 | ]); 49 | electron.sendMessage('set-search-theme', searchThemeVars); 50 | }; 51 | web.addDocumentObserver((mutation) => { 52 | const potentialThemeChange = mutation.target.matches?.('html, body, .notion-app-inner'); 53 | if (potentialThemeChange && document.hasFocus()) updateTheme(); 54 | }); 55 | updateTheme(); 56 | document.addEventListener('visibilitychange', updateTheme); 57 | document.addEventListener('focus', updateTheme); 58 | } 59 | -------------------------------------------------------------------------------- /integrated-titlebar/buttons.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .integrated_titlebar--dragarea { 8 | width: 100%; 9 | display: block; 10 | -webkit-app-region: drag; 11 | background: var(--theme--bg_secondary); 12 | height: var(--integrated_titlebar--dragarea-height); 13 | } 14 | 15 | .integrated_titlebar--buttons { 16 | display: flex; 17 | align-items: center; 18 | } 19 | .sidebar > .integrated_titlebar--buttons { 20 | margin: -0.5rem 0 0.75rem auto; 21 | } 22 | 23 | .integrated_titlebar--buttons button { 24 | user-select: none; 25 | transition: background 20ms ease-in 0s; 26 | cursor: pointer; 27 | display: inline-flex; 28 | align-items: center; 29 | justify-content: center; 30 | flex-shrink: 0; 31 | border-radius: 3px; 32 | height: 28px; 33 | width: 33px; 34 | padding: 0 0.25px 0 0; 35 | 36 | margin-left: 2px; 37 | border: none; 38 | background: transparent; 39 | font-size: 18px; 40 | -webkit-app-region: no-drag; 41 | } 42 | .integrated_titlebar--buttons button svg { 43 | display: block; 44 | width: 20px; 45 | height: 20px; 46 | fill: var(--theme--icon); 47 | color: var(--theme--icon); 48 | } 49 | 50 | .integrated_titlebar--buttons button:focus, 51 | .integrated_titlebar--buttons button:hover { 52 | background: var(--theme--ui_interactive-hover); 53 | } 54 | .integrated_titlebar--buttons button:active { 55 | background: var(--theme--ui_interactive-active); 56 | } 57 | #integrated_titlebar--close:focus, 58 | #integrated_titlebar--close:hover, 59 | #integrated_titlebar--close:active { 60 | background: var(--theme--accent_red); 61 | color: var(--theme--accent_red-text); 62 | } 63 | 64 | .notion-update-sidebar [style*='margin-top: 45px;'] { 65 | margin-top: calc(45px + var(--integrated_titlebar--dragarea-height, 0px)) !important; 66 | } 67 | .notion-topbar { 68 | height: calc(45px + var(--integrated_titlebar--dragarea-height, 0px)) !important; 69 | } 70 | .notion-frame { 71 | height: calc(100vh - 45px - var(--integrated_titlebar--dragarea-height, 0px)) !important; 72 | } 73 | 74 | body > .body-container { 75 | height: calc(100% - var(--integrated_titlebar--dragarea-height, 0px)) !important; 76 | } 77 | -------------------------------------------------------------------------------- /light+/theme.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: light+ 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ fmt }, db) { 10 | { 11 | const primary = await db.get(['primary']), 12 | [r, g, b] = primary 13 | .slice(5, -1) 14 | .split(',') 15 | .map((i) => parseInt(i)); 16 | if (!(r === 46 && g === 170 && b === 220)) { 17 | document.documentElement.style.setProperty('--light_plus--accent_blue', primary); 18 | document.documentElement.style.setProperty( 19 | '--light_plus--accent_blue-selection', 20 | `rgba(${r},${g},${b},0.2)` 21 | ); 22 | document.documentElement.style.setProperty( 23 | '--light_plus--accent_blue-hover', 24 | fmt.rgbLogShade(0.05, primary) 25 | ); 26 | document.documentElement.style.setProperty( 27 | '--light_plus--accent_blue-active', 28 | fmt.rgbLogShade(0.025, primary) 29 | ); 30 | document.documentElement.style.setProperty( 31 | '--light_plus--accent_blue-text', 32 | fmt.rgbContrast(r, g, b) 33 | ); 34 | } 35 | } 36 | 37 | { 38 | const secondary = await db.get(['secondary']), 39 | [r, g, b] = secondary 40 | .slice(5, -1) 41 | .split(',') 42 | .map((i) => parseInt(i)); 43 | if (!(r === 235 && g === 87 && b === 87)) { 44 | document.documentElement.style.setProperty('--light_plus--accent_red', secondary); 45 | document.documentElement.style.setProperty( 46 | '--light_plus--accent_red-button', 47 | `rgba(${r},${g},${b},0.2)` 48 | ); 49 | document.documentElement.style.setProperty( 50 | '--light_plus--accent_red-text', 51 | fmt.rgbContrast(r, g, b) 52 | ); 53 | } 54 | } 55 | 56 | { 57 | const highlight = await db.get(['highlight']), 58 | [r, g, b, a] = highlight 59 | .slice(5, -1) 60 | .split(',') 61 | .map((i) => parseFloat(i)); 62 | if (!(r === 0 && g === 0 && b === 0 && a === 0)) { 63 | document.documentElement.style.setProperty('--light_plus--accent_highlight', highlight); 64 | document.documentElement.style.setProperty( 65 | '--light_plus--accent_highlight-shaded', 66 | fmt.rgbLogShade(0.1, highlight) 67 | ); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /integrated-titlebar/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import { createWindowButtons } from './buttons.mjs'; 10 | 11 | export default async function (api, db) { 12 | const { web, registry, electron } = api, 13 | tilingMode = await db.get(['tiling']), 14 | dragareaHeight = await db.get(['dragarea_height']), 15 | tabsEnabled = await registry.enabled('e1692c29-475e-437b-b7ff-3eee872e1a42'), 16 | sidebarSelector = '.notion-sidebar', 17 | panelSelector = '#enhancer--panel', 18 | topbarSelector = '.notion-topbar', 19 | topbarActionsSelector = '.notion-topbar-action-buttons'; 20 | if (tilingMode || tabsEnabled) return; 21 | 22 | let sidebarWidth = '0px', 23 | panelWidth = '0px'; 24 | const updateDragareaOffsets = () => { 25 | const $sidebar = document.querySelector(sidebarSelector), 26 | newSidebarWidth = 27 | !$sidebar || $sidebar.style.height === 'auto' ? '0px' : $sidebar.style.width, 28 | $panel = document.querySelector(panelSelector), 29 | newPanelWidth = 30 | $panel && $panel.dataset.enhancerPanelPinned === 'true' 31 | ? window 32 | .getComputedStyle(document.documentElement) 33 | .getPropertyValue('--component--panel-width') 34 | : '0px'; 35 | if (newSidebarWidth !== sidebarWidth) { 36 | sidebarWidth = newSidebarWidth; 37 | electron.sendMessageToHost('sidebar-width', sidebarWidth); 38 | } 39 | if (newPanelWidth !== panelWidth) { 40 | panelWidth = newPanelWidth; 41 | electron.sendMessageToHost('panel-width', panelWidth); 42 | } 43 | }; 44 | web.addDocumentObserver(updateDragareaOffsets); 45 | 46 | await web.whenReady([topbarSelector, topbarActionsSelector]); 47 | const $topbar = document.querySelector(topbarSelector), 48 | $dragarea = web.html`
`; 49 | $topbar.prepend($dragarea); 50 | document.documentElement.style.setProperty( 51 | '--integrated_titlebar--dragarea-height', 52 | dragareaHeight + 'px' 53 | ); 54 | 55 | const $topbarActions = document.querySelector(topbarActionsSelector), 56 | $windowButtons = await createWindowButtons(api, db); 57 | web.render($topbarActions.parentElement, $windowButtons); 58 | } 59 | -------------------------------------------------------------------------------- /view-scale/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: view scale 3 | * (c) 2021 SP12893678 (https://sp12893678.tk/) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .view_scale--container { 9 | border: 1px solid var(--theme--ui_divider); 10 | border-radius: 9999px !important; 11 | margin: 0 4px; 12 | padding: 0 5px 0 4px; 13 | align-items: center; 14 | justify-content: center; 15 | display: flex; 16 | } 17 | 18 | .view_scale--slider { 19 | appearance: none; 20 | outline: 0; 21 | border: 0px; 22 | overflow: hidden; 23 | width: 150px; 24 | height: 20px; 25 | margin: auto 4px auto 0; 26 | cursor: ew-resize; 27 | border-radius: 9999px; 28 | } 29 | .view_scale--slider::-webkit-slider-runnable-track { 30 | appearance: none; 31 | height: 20px; 32 | background-color: var(--theme--ui_toggle-off); 33 | } 34 | .view_scale--slider::-webkit-slider-thumb { 35 | appearance: none; 36 | position: relative; 37 | width: 20px; 38 | height: 20px; 39 | border-radius: 9999px; 40 | border: 0px; 41 | background: var(--theme--ui_toggle-on); 42 | box-shadow: -100px 0 0 90px var(--theme--ui_toggle-on), 43 | inset 0 0 0 20px var(--theme--ui_toggle-on); 44 | transition: 0.2s; 45 | } 46 | .view_scale--slider:active::-webkit-slider-thumb { 47 | background: var(--theme--ui_toggle-feature); 48 | box-shadow: -100px 0 0 90px var(--theme--ui_toggle-on), 49 | inset 0 0 0 2px var(--theme--ui_toggle-on); 50 | } 51 | 52 | .view_scale--counter { 53 | font-size: 14px; 54 | margin: auto 4px auto 0; 55 | width: 5ch; 56 | text-align: right; 57 | } 58 | 59 | .view_scale--button { 60 | user-select: none; 61 | transition: background 20ms ease-in 0s; 62 | cursor: pointer; 63 | display: inline-flex; 64 | align-items: center; 65 | justify-content: center; 66 | flex-shrink: 0; 67 | border-radius: 9999px; 68 | height: 20px; 69 | width: 20px; 70 | padding: 0 0.25px 0 0; 71 | 72 | margin-left: 2px; 73 | border: none; 74 | background: transparent; 75 | font-size: 18px; 76 | } 77 | .view_scale--button:focus, 78 | .view_scale--button:hover { 79 | background: var(--theme--ui_interactive-hover); 80 | } 81 | .view_scale--button:active { 82 | background: var(--theme--ui_interactive-active); 83 | } 84 | .view_scale--button svg { 85 | display: block; 86 | width: 14px; 87 | height: 14px; 88 | fill: var(--theme--icon); 89 | color: var(--theme--icon); 90 | } 91 | -------------------------------------------------------------------------------- /truncated-titles/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: truncated titles 3 | * (c) 2021 admiraldus (https://github.com/admiraldus) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | export default async function ({ web, components }, db) { 9 | const enhanceTableTitles = await db.get(['tables']), 10 | enhanceTimelineItems = await db.get(['timelines']), 11 | tableCellSelector = '.notion-table-view-header-cell', 12 | tableTitleSelector = `${tableCellSelector} div[style*="text-overflow"]`, 13 | timelineItemSelector = '.notion-timeline-item', 14 | $elements = []; 15 | 16 | const addTooltips = () => { 17 | if (enhanceTableTitles) { 18 | document.querySelectorAll(tableTitleSelector).forEach(($tableTitle) => { 19 | if ($elements.includes($tableTitle)) return; 20 | 21 | if ($tableTitle.scrollWidth > $tableTitle.clientWidth) { 22 | components.addTooltip( 23 | $tableTitle.parentElement.parentElement.parentElement, 24 | web.html`${web.escape($tableTitle.innerText)}`, 25 | 750 26 | ); 27 | $elements.push($tableTitle); 28 | } 29 | }); 30 | } 31 | 32 | if (enhanceTimelineItems) { 33 | document.querySelectorAll(timelineItemSelector).forEach(($timelineItem) => { 34 | const $title = $timelineItem.nextElementSibling.firstElementChild; 35 | $title.style.position = 'absolute'; 36 | $title.style.left = $timelineItem.style.left; 37 | 38 | if ($elements.includes($timelineItem)) return; 39 | $elements.push($timelineItem); 40 | 41 | $title.style.width = $timelineItem.clientWidth + 'px'; 42 | $title.firstElementChild.firstElementChild.style.maxWidth = 43 | $timelineItem.clientWidth + 'px'; 44 | $timelineItem.addEventListener('mouseover', (event) => { 45 | $title.style.width = '100%'; 46 | $title.firstElementChild.firstElementChild.style.maxWidth = '400px'; 47 | }); 48 | $timelineItem.addEventListener('mouseout', async (event) => { 49 | if (!$timelineItem.matches(':hover')) { 50 | $title.style.width = $timelineItem.clientWidth + 'px'; 51 | $title.firstElementChild.firstElementChild.style.maxWidth = 52 | $timelineItem.clientWidth + 'px'; 53 | } 54 | }); 55 | }); 56 | } 57 | }; 58 | 59 | await web.whenReady(); 60 | addTooltips(); 61 | web.addDocumentObserver(addTooltips, [ 62 | tableCellSelector, 63 | timelineItemSelector, 64 | `${timelineItemSelector} + div > :first-child`, 65 | ]); 66 | } 67 | -------------------------------------------------------------------------------- /integrated-titlebar/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "integrated titlebar", 3 | "id": "a5658d03-21c6-4088-bade-fa4780459133", 4 | "environments": ["linux", "win32"], 5 | "version": "0.11.0", 6 | "description": "replaces the native window titlebar with buttons inset into the app.", 7 | "preview": "integrated-titlebar.jpg", 8 | "tags": ["extension", "layout"], 9 | "authors": [ 10 | { 11 | "name": "dragonwocky", 12 | "email": "thedragonring.bod@gmail.com", 13 | "homepage": "https://dragonwocky.me/", 14 | "avatar": "https://dragonwocky.me/avatar.jpg" 15 | } 16 | ], 17 | "css": { 18 | "frame": ["buttons.css"], 19 | "client": ["buttons.css"], 20 | "menu": ["buttons.css"] 21 | }, 22 | "js": { 23 | "frame": ["frame.mjs"], 24 | "client": ["client.mjs"], 25 | "menu": ["menu.mjs"], 26 | "electron": [{ "source": "createWindow.cjs", "target": "main/createWindow.js" }] 27 | }, 28 | "options": [ 29 | { 30 | "type": "toggle", 31 | "key": "tiling", 32 | "label": "tiling window manager mode", 33 | "tooltip": "**completely remove the close/minimise/maximise buttons** (only for advanced use, not recommended)", 34 | "value": false 35 | }, 36 | { 37 | "type": "number", 38 | "key": "dragarea_height", 39 | "label": "dragarea height (px)", 40 | "tooltip": "**the height of the rectangle added to the top of the window, used to drag/move the window around** (dragging is not possible in a frameless window without this bar)", 41 | "value": 12 42 | }, 43 | { 44 | "type": "text", 45 | "key": "minimize_icon", 46 | "label": "minimize window icon", 47 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 48 | "value": "" 49 | }, 50 | { 51 | "type": "text", 52 | "key": "maximize_icon", 53 | "label": "maximize window icon", 54 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 55 | "value": "" 56 | }, 57 | { 58 | "type": "text", 59 | "key": "unmaximize_icon", 60 | "label": "unmaximize window icon", 61 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 62 | "value": "" 63 | }, 64 | { 65 | "type": "text", 66 | "key": "close_icon", 67 | "label": "close window icon", 68 | "tooltip": "**may be an svg string or any unicode symbol e.g. an emoji** (the default icon will be used if this field is left empty)", 69 | "value": "" 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /code-line-numbers/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: code line numbers 3 | * (c) 2020 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | export default async function ({ web }, db) { 9 | const singleLined = await db.get(['single_lined']), 10 | codeBlockSelector = '.notion-code-block.line-numbers', 11 | numbersClass = `code_line_numbers--${await db.get(['style'])}`, 12 | $temp = web.html``; 13 | 14 | const numberCodeBlock = ($codeBlock) => { 15 | const $numbers = 16 | $codeBlock.querySelector(`.${numbersClass}`) || 17 | web.html`1`; 18 | if (!$codeBlock.contains($numbers)) $codeBlock.prepend($numbers); 19 | 20 | const lines = $codeBlock.lastElementChild.innerText.split(/\r\n|\r|\n/), 21 | wordWrap = $codeBlock.lastElementChild.style.wordBreak === 'break-all'; 22 | if (lines.reverse()[0] === '') lines.pop(); 23 | 24 | let lineNumbers = ''; 25 | for (let i = 1; i <= lines.length + 1; i++) { 26 | lineNumbers += `${i}\n`; 27 | if (wordWrap && lines[i - 1]) { 28 | $temp.innerText = lines[i - 1]; 29 | web.render($codeBlock.lastElementChild, $temp); 30 | const height = parseFloat($temp.getBoundingClientRect().height); 31 | $temp.remove(); 32 | for (let j = 1; j < height / 20.4; j++) lineNumbers += '\n'; 33 | } 34 | } 35 | 36 | if (!singleLined && lines.length < 2) lineNumbers = ''; 37 | if ($numbers.innerText !== lineNumbers) $numbers.innerText = lineNumbers; 38 | }, 39 | numberAllCodeBlocks = () => { 40 | for (const $codeBlock of document.querySelectorAll(codeBlockSelector)) { 41 | numberCodeBlock($codeBlock); 42 | } 43 | }, 44 | observeCodeBlocks = (event) => { 45 | const tempEvent = [...event.addedNodes, ...event.removedNodes].includes($temp), 46 | numbersEvent = 47 | event.target.classList.contains(numbersClass) || 48 | [...event.addedNodes, ...event.removedNodes].some(($node) => 49 | $node?.classList?.contains(numbersClass) 50 | ), 51 | codeEvent = event.target.matches(`${codeBlockSelector}, ${codeBlockSelector} *`); 52 | if (tempEvent || numbersEvent || !codeEvent) return; 53 | 54 | let $codeBlock = event.target; 55 | while (!$codeBlock.matches(codeBlockSelector)) $codeBlock = $codeBlock.parentElement; 56 | numberCodeBlock($codeBlock); 57 | }; 58 | 59 | await web.whenReady(); 60 | numberAllCodeBlocks(); 61 | web.addDocumentObserver(observeCodeBlocks, [codeBlockSelector]); 62 | } 63 | -------------------------------------------------------------------------------- /tabs/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabs", 3 | "id": "e1692c29-475e-437b-b7ff-3eee872e1a42", 4 | "environments": ["linux", "win32", "darwin"], 5 | "version": "0.3.0", 6 | "description": "open multiple notion pages in a single window.", 7 | "preview": "tabs.jpg", 8 | "tags": ["extension", "app"], 9 | "authors": [ 10 | { 11 | "name": "dragonwocky", 12 | "email": "thedragonring.bod@gmail.com", 13 | "homepage": "https://dragonwocky.me/", 14 | "avatar": "https://dragonwocky.me/avatar.jpg" 15 | } 16 | ], 17 | "css": { 18 | "frame": ["tabs.css"] 19 | }, 20 | "js": { 21 | "client": ["client.mjs"], 22 | "electron": [ 23 | { "source": "main.cjs", "target": "main/main.js" }, 24 | { "source": "systemMenu.cjs", "target": "main/systemMenu.js" }, 25 | { "source": "createWindow.cjs", "target": "main/createWindow.js" }, 26 | { "source": "rendererIndex.cjs", "target": "renderer/index.js" } 27 | ] 28 | }, 29 | "options": [ 30 | { 31 | "type": "toggle", 32 | "key": "remember_last_open", 33 | "label": "remember last open tabs", 34 | "tooltip": "**a continue-where-you-left-off experience** (reopens recently active tabs after an app relaunch)", 35 | "value": true 36 | }, 37 | { 38 | "type": "select", 39 | "key": "label_type", 40 | "label": "tab labels", 41 | "values": ["page icon & title", "page icon only", "page title only"] 42 | }, 43 | { 44 | "type": "select", 45 | "key": "layout_style", 46 | "label": "tab layout", 47 | "values": ["traditional tabbed", "rectangular", "bubble", "compact"] 48 | }, 49 | { 50 | "type": "select", 51 | "key": "select_modifier", 52 | "label": "tab select modifier", 53 | "tooltip": "**usage: Modifier+1 to Modifier+9, Modifier+ArrowLeft and Modifier+ArrowRight**", 54 | "values": [ 55 | "Alt", 56 | "Command", 57 | "Control", 58 | "Super", 59 | "Alt+Shift", 60 | "Command+Shift", 61 | "Control+Shift", 62 | "Super+Shift" 63 | ] 64 | }, 65 | { 66 | "type": "hotkey", 67 | "key": "prev_tab", 68 | "label": "previous tab hotkey", 69 | "value": "Control+Shift+Tab" 70 | }, 71 | { 72 | "type": "hotkey", 73 | "key": "next_tab", 74 | "label": "next tab hotkey", 75 | "value": "Control+Tab" 76 | }, 77 | { 78 | "type": "hotkey", 79 | "key": "new_tab", 80 | "label": "new tab hotkey", 81 | "value": "Control+T" 82 | }, 83 | { 84 | "type": "hotkey", 85 | "key": "close_tab", 86 | "label": "close tab hotkey", 87 | "value": "Control+W" 88 | }, 89 | { 90 | "type": "hotkey", 91 | "key": "restore_tab", 92 | "label": "restore previously opened tab hotkey", 93 | "value": "Control+Shift+T" 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /menu/router.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: menu 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import { web } from '../../api/index.mjs'; 10 | 11 | const _queryListeners = new Set(); 12 | 13 | export function addView(name, loadFunc) { 14 | const handlerFunc = (newView) => { 15 | if (newView === name) return loadFunc(); 16 | return false; 17 | }; 18 | _queryListeners.add({ param: 'view', viewName: name, handlerFunc }); 19 | handlerFunc(web.queryParams().get('view'), null); 20 | } 21 | export function removeView(name) { 22 | const view = [..._queryListeners].find((view) => view.viewName === name); 23 | if (view) _queryListeners.delete(view); 24 | } 25 | export async function setDefaultView(viewName) { 26 | const viewList = [..._queryListeners].filter((q) => q.viewName).map((v) => v.viewName); 27 | if (!viewList.includes(web.queryParams().get('view'))) { 28 | updateQuery(`?view=${viewName}`, true); 29 | } 30 | } 31 | 32 | export function addQueryListener(param, handlerFunc) { 33 | _queryListeners.add({ param: param, handlerFunc }); 34 | handlerFunc(web.queryParams().get(param), null); 35 | } 36 | export function removeQueryListener(handlerFunc) { 37 | const listener = [..._queryListeners].find((view) => view.handlerFunc === handlerFunc); 38 | if (listener) _queryListeners.delete(listener); 39 | } 40 | 41 | export const updateQuery = (search, replace = false) => { 42 | let query = web.queryParams(); 43 | for (const [key, val] of new URLSearchParams(search)) { 44 | query.set(key, val); 45 | } 46 | query = `?${query.toString()}`; 47 | if (location.search !== query) { 48 | if (replace) { 49 | window.history.replaceState(null, null, query); 50 | } else { 51 | window.history.pushState(null, null, query); 52 | } 53 | triggerQueryListeners(); 54 | } 55 | }; 56 | 57 | function router(event) { 58 | event.preventDefault(); 59 | const anchor = event.path 60 | ? event.path.find((anchor) => anchor.nodeName === 'A') 61 | : event.target; 62 | updateQuery(anchor.getAttribute('href')); 63 | } 64 | 65 | let queryCache = ''; 66 | async function triggerQueryListeners() { 67 | if (location.search === queryCache) return; 68 | const newQuery = web.queryParams(), 69 | oldQuery = new URLSearchParams(queryCache); 70 | queryCache = location.search; 71 | for (const listener of _queryListeners) { 72 | const newParam = newQuery.get(listener.param), 73 | oldParam = oldQuery.get(listener.param); 74 | if (newParam !== oldParam) listener.handlerFunc(newParam, oldParam); 75 | } 76 | } 77 | 78 | window.addEventListener('popstate', triggerQueryListeners); 79 | 80 | web.addDocumentObserver( 81 | (mutation) => { 82 | mutation.target.querySelectorAll('a[href^="?"]').forEach((a) => { 83 | a.removeEventListener('click', router); 84 | a.addEventListener('click', router); 85 | }); 86 | }, 87 | ['a[href^="?"]'] 88 | ); 89 | -------------------------------------------------------------------------------- /emoji-sets/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: emoji sets 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | export default async function ({ web, env }, db) { 8 | const style = await db.get(['style']), 9 | // real emojis are used on macos instead of the twitter set 10 | nativeEmojiSelector = `[aria-label][role="image"][style*="Apple Color Emoji"]:not([data-emoji-sets-unsupported])`, 11 | imgEmojiSelector = '.notion-emoji:not([data-emoji-sets-unsupported])', 12 | imgEmojiOverlaySelector = `${imgEmojiSelector} + [src*="notion-emojis"]`; 13 | 14 | await Promise.any([web.whenReady([nativeEmojiSelector]), web.whenReady([imgEmojiSelector])]); 15 | 16 | const nativeEmojis = document.querySelectorAll(nativeEmojiSelector).length, 17 | imgEmojis = document.querySelectorAll(imgEmojiSelector).length; 18 | 19 | const unsupportedEmojis = [], 20 | emojiReqs = new Map(), 21 | getEmoji = async (emoji) => { 22 | emoji = encodeURIComponent(emoji); 23 | if (unsupportedEmojis.includes(emoji)) return undefined; 24 | try { 25 | if (!emojiReqs.get(emoji)) { 26 | emojiReqs.set(emoji, fetch(`https://emojicdn.elk.sh/${emoji}?style=${style}`)); 27 | } 28 | const res = await emojiReqs.get(emoji); 29 | if (!res.ok) throw new Error(); 30 | return `url("https://emojicdn.elk.sh/${emoji}?style=${style}") 100% 100% / 100%`; 31 | } catch { 32 | unsupportedEmojis.push(emoji); 33 | return undefined; 34 | } 35 | }; 36 | 37 | if (nativeEmojis) { 38 | const updateEmojis = async () => { 39 | const $emojis = document.querySelectorAll(nativeEmojiSelector); 40 | for (const $emoji of $emojis) { 41 | const emojiSrc = await getEmoji($emoji.ariaLabel); 42 | if (emojiSrc) { 43 | $emoji.style.background = emojiSrc; 44 | $emoji.style.width = '1em'; 45 | $emoji.style.height = '1em'; 46 | $emoji.style.display = 'inline-block'; 47 | $emoji.innerText = ''; 48 | } else $emoji.dataset.emojiSetsUnsupported = true; 49 | } 50 | }; 51 | web.addDocumentObserver(updateEmojis, [nativeEmojiSelector]); 52 | } 53 | 54 | if (style !== 'twitter' && imgEmojis) { 55 | const updateEmojis = async () => { 56 | const $emojis = document.querySelectorAll(imgEmojiSelector); 57 | for (const $emoji of $emojis) { 58 | const emojiSrc = await getEmoji($emoji.ariaLabel); 59 | if (emojiSrc) { 60 | $emoji.style.background = emojiSrc; 61 | $emoji.style.opacity = 1; 62 | if ($emoji.nextElementSibling?.matches?.(imgEmojiOverlaySelector)) { 63 | $emoji.nextElementSibling.style.opacity = 0; 64 | } 65 | } else $emoji.dataset.emojiSetsUnsupported = true; 66 | } 67 | }; 68 | updateEmojis(); 69 | web.addDocumentObserver(updateEmojis, [imgEmojiSelector, imgEmojiOverlaySelector]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /integrated-titlebar/buttons.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: integrated titlebar 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export const createWindowButtons = async ({ electron, web, components }, db) => { 10 | let minimizeIcon = (await db.get(['minimize_icon'])) || (await components.feather('minus')), 11 | maximizeIcon = (await db.get(['maximize_icon'])) || (await components.feather('maximize')), 12 | unmaximizeIcon = 13 | (await db.get(['unmaximize_icon'])) || (await components.feather('minimize')), 14 | closeIcon = (await db.get(['close_icon'])) || (await components.feather('x')); 15 | minimizeIcon = minimizeIcon.trim(); 16 | maximizeIcon = maximizeIcon.trim(); 17 | unmaximizeIcon = unmaximizeIcon.trim(); 18 | closeIcon = closeIcon.trim(); 19 | 20 | minimizeIcon = 21 | minimizeIcon.startsWith('') 22 | ? minimizeIcon 23 | : web.escape(minimizeIcon); 24 | maximizeIcon = 25 | maximizeIcon.startsWith('') 26 | ? maximizeIcon 27 | : web.escape(maximizeIcon); 28 | unmaximizeIcon = 29 | unmaximizeIcon.startsWith('') 30 | ? unmaximizeIcon 31 | : web.escape(unmaximizeIcon); 32 | closeIcon = 33 | closeIcon.startsWith('') 34 | ? closeIcon 35 | : web.escape(closeIcon); 36 | 37 | const $windowButtons = web.html`
`, 38 | $minimize = web.html``, 41 | $maximize = web.html``, 44 | $unmaximize = web.html``, 47 | $close = web.html``; 50 | components.addTooltip($minimize, '**Minimize window**'); 51 | components.addTooltip($maximize, '**Maximize window**'); 52 | components.addTooltip($unmaximize, '**Unmaximize window**'); 53 | components.addTooltip($close, '**Close window**'); 54 | 55 | $minimize.addEventListener('click', () => electron.browser.minimize()); 56 | $maximize.addEventListener('click', () => electron.browser.maximize()); 57 | $unmaximize.addEventListener('click', () => electron.browser.unmaximize()); 58 | $close.addEventListener('click', () => electron.browser.close()); 59 | electron.browser.on('maximize', () => { 60 | $maximize.replaceWith($unmaximize); 61 | }); 62 | electron.browser.on('unmaximize', () => { 63 | $unmaximize.replaceWith($maximize); 64 | }); 65 | 66 | web.render( 67 | $windowButtons, 68 | $minimize, 69 | electron.browser.isMaximized() ? $unmaximize : $maximize, 70 | $close 71 | ); 72 | return $windowButtons; 73 | }; 74 | -------------------------------------------------------------------------------- /weekly-view/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: weekly view 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web }, db) { 10 | const pageSelector = '.notion-page-content', 11 | calendarSelector = '.notion-calendar-view', 12 | viewSelector = 13 | '.notion-page-content > .notion-selectable.notion-collection_view-block', 14 | viewControlSelector = ':scope>div>div>div>div>div', 15 | todaySelector = '.notion-calendar-view-day[style*="background"]', 16 | weekSelector = '[style^="position: relative; display: flex; height: "]', 17 | toolbarBtnSelector = 18 | '.notion-calendar-view > :first-child > :first-child > :first-child > :nth-last-child(2)'; 19 | 20 | const transformCalendarView = () => { 21 | const $page = document.querySelector(pageSelector); 22 | document.querySelectorAll(viewSelector).forEach(async ($view) => { 23 | let currentText; 24 | // Get view controls children nodes, convert to array, filter out non-text 25 | const $viewNodes = [] 26 | .slice.call($view.querySelector(viewControlSelector).children) 27 | .filter(node => node.tagName.toLowerCase().match(/(div|span)/g)); 28 | 29 | // Find current view by analyzing children (which changes on viewport) 30 | if ($viewNodes.length === 1) 31 | { 32 | // Mobile: Simple dropdown button (like legacy), text is current view 33 | currentText = $viewNodes[0].innerText.toLowerCase(); 34 | } else { 35 | // Wide/Desktop: Tabs listed, current view indicated by border style 36 | currentText = $viewNodes 37 | // Find selected view by border style (possibly fragile) 38 | .filter(($e) => $e.children[0].style.borderBottomWidth.toString() === '2px')[0] 39 | .innerText.toLowerCase(); 40 | } 41 | 42 | if (currentText !== 'weekly') return; 43 | 44 | const $calendar = $view.parentElement.parentElement.parentElement.parentElement; 45 | if (!$calendar.querySelector(todaySelector)) { 46 | $calendar.querySelector(toolbarBtnSelector).click(); 47 | } 48 | await new Promise((res, rej) => requestAnimationFrame(res)); 49 | if ($page) { 50 | for (const $week of $calendar.querySelectorAll(weekSelector)) { 51 | if (!$week.querySelector(todaySelector)) { 52 | $week.style.height = 0; 53 | $week.style.visibility = 'hidden'; 54 | } 55 | } 56 | } else { 57 | const $weekContainer = $calendar.querySelector(weekSelector).parentElement; 58 | for (const $week of $calendar.querySelectorAll(weekSelector)) { 59 | if ($week.querySelector(todaySelector)) { 60 | $weekContainer.style.maxHeight = $week.style.height; 61 | break; 62 | } else { 63 | $week.style.height = '0'; 64 | $week.style.visibility = 'hidden'; 65 | } 66 | } 67 | } 68 | }); 69 | }; 70 | web.addDocumentObserver(transformCalendarView, [calendarSelector]); 71 | } 72 | -------------------------------------------------------------------------------- /tabs/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web, electron }, db) { 10 | const newTabHotkey = await db.get(['new_tab']), 11 | closeTabHotkey = await db.get(['close_tab']), 12 | restoreTabHotkey = await db.get(['restore_tab']), 13 | selectTabModifier = await db.get(['select_modifier']), 14 | prevTabHotkey = await db.get(['prev_tab']), 15 | nextTabHotkey = await db.get(['next_tab']); 16 | web.addHotkeyListener(newTabHotkey, () => { 17 | electron.sendMessageToHost('new-tab'); 18 | }); 19 | web.addHotkeyListener(restoreTabHotkey, () => { 20 | electron.sendMessageToHost('restore-tab'); 21 | }); 22 | web.addHotkeyListener(closeTabHotkey, () => electron.sendMessageToHost('close-tab')); 23 | for (let i = 1; i < 10; i++) { 24 | web.addHotkeyListener([selectTabModifier, i.toString()], () => { 25 | electron.sendMessageToHost('select-tab', i); 26 | }); 27 | } 28 | web.addHotkeyListener(prevTabHotkey, () => { 29 | electron.sendMessageToHost('select-prev-tab') 30 | }); 31 | web.addHotkeyListener(nextTabHotkey, () => { 32 | electron.sendMessageToHost('select-next-tab') 33 | }); 34 | 35 | const breadcrumbSelector = 36 | '.notion-topbar > div > [class="notranslate"] > .notion-focusable:last-child', 37 | imgIconSelector = `${breadcrumbSelector} .notion-record-icon img:not(.notion-emoji)`, 38 | emojiIconSelector = `${breadcrumbSelector} .notion-record-icon img.notion-emoji`, 39 | nativeIconSelector = `${breadcrumbSelector} .notion-record-icon [role="image"]`, 40 | titleSelector = `${breadcrumbSelector} > :not(.notion-record-icon)`, 41 | viewSelector = '.notion-collection-view-select'; 42 | 43 | let title = '', 44 | icon = ''; 45 | const notionSetWindowTitle = __electronApi.setWindowTitle, 46 | getIcon = () => { 47 | const $imgIcon = document.querySelector(imgIconSelector), 48 | $emojiIcon = document.querySelector(emojiIconSelector), 49 | $nativeIcon = document.querySelector(nativeIconSelector); 50 | if ($imgIcon) { 51 | return `url("${$imgIcon.src}") 0 / 100%`; 52 | } 53 | if ($emojiIcon) { 54 | return $emojiIcon.style.background.replace( 55 | /url\("\/images/, 56 | 'url("notion://www.notion.so/images' 57 | ); 58 | } 59 | if ($nativeIcon) return $nativeIcon.ariaLabel; 60 | return ''; 61 | }, 62 | updateTitle = (newTitle = title) => { 63 | if (!newTitle) return; 64 | title = newTitle; 65 | icon = getIcon(); 66 | electron.sendMessageToHost('set-tab-title', title); 67 | electron.sendMessageToHost('set-tab-icon', icon); 68 | notionSetWindowTitle(title); 69 | }; 70 | __electronApi.setWindowTitle = (newTitle) => updateTitle(newTitle); 71 | document.addEventListener('focus', updateTitle); 72 | electron.onMessage('trigger-title-update', () => updateTitle()); 73 | 74 | await web.whenReady([titleSelector]); 75 | const $title = document.querySelector(titleSelector), 76 | $view = document.querySelector(viewSelector); 77 | if (!title && $title) { 78 | if ($view) { 79 | updateTitle(`${$title.innerText} | ${$view.innerText}`); 80 | } else updateTitle($title.innerText); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pastel-dark/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: pastel dark 3 | * (c) 2020 u/zenith_illinois 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-dark-theme img[src*='/images/onboarding/use-case-note.png'], 9 | .notion-dark-theme img[src*='/images/onboarding/team-features-illustration.png'] { 10 | filter: invert(1) !important; 11 | } 12 | .notion-dark-theme img[src*='/images/onboarding/checked.svg'] { 13 | filter: hue-rotate(45deg) !important; 14 | } 15 | .notion-dark-theme 16 | img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;'], 17 | .notion-dark-theme 18 | img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;'] { 19 | transition: filter 0.4s ease !important; 20 | } 21 | .notion-dark-theme 22 | img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;']:hover, 23 | .notion-dark-theme 24 | img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;']:hover { 25 | filter: brightness(1.2); 26 | } 27 | 28 | .notion-dark-theme .notion-token-remove-button[role*='button'][tabindex*='0']:hover, 29 | .notion-dark-theme .notion-record-icon { 30 | background: transparent !important; 31 | } 32 | 33 | .notion-dark-theme .notion-focusable:focus-within, 34 | .notion-dark-theme .notion-to_do-block > div > div > div[style*='background:'], 35 | .notion-dark-theme div[role='button'], 36 | [style*='height: 4px;'] > .notion-selectable.notion-collection_view_page-block > *, 37 | .notion-dark-theme .notion-calendar-view-day[style*='background: rgb(235, 87, 87);'], 38 | .DayPicker-Day--today, 39 | .notion-dark-theme 40 | .DayPicker:not(.DayPicker--interactionDisabled) 41 | .DayPicker-Day--outside:hover, 42 | .notion-dark-theme 43 | .DayPicker:not(.DayPicker--interactionDisabled) 44 | .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--value) 45 | .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected, 46 | .notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--start, 47 | .notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--end { 48 | transition: color 0.4s ease, background 0.4s ease, box-shadow 0.4s ease !important; 49 | } 50 | 51 | .notion-dark-theme :not(.notion-code-block) > * > [style*='background: rgb(63, 68, 71);'], 52 | .notion-dark-theme 53 | .notion-code-block 54 | [style*='background: rgb(63, 68, 71);'] 55 | .notion-code-block, 56 | .notion-dark-theme 57 | [style*='background: rgb(80, 85, 88);'][style*='color: rgba(255, 255, 255, 0.7)'], 58 | .notion-dark-theme 59 | [style*='background: rgb(80, 85, 88);'][style*='width: 18px;'][style*='height: 18px;'], 60 | .notion-dark-theme 61 | [style*='box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 5px 10px, rgba(15, 15, 15, 0.4) 0px 15px 40px;'], 62 | .notion-dark-theme [style*='background: rgba(151, 154, 155, 0.5);'], 63 | .notion-dark-theme [style*='background: rgba(147, 114, 100, 0.5)'], 64 | .notion-dark-theme [style*='background: rgba(255, 163, 68, 0.5)'], 65 | .notion-dark-theme [style*='background: rgba(255, 220, 73, 0.5)'], 66 | .notion-dark-theme [style*='background: rgba(77, 171, 154, 0.5)'], 67 | .notion-dark-theme [style*='background: rgba(82, 156, 202, 0.5)'], 68 | .notion-dark-theme [style*='background: rgba(154, 109, 215, 0.5)'], 69 | .notion-dark-theme [style*='background: rgba(226, 85, 161, 0.5)'], 70 | .notion-dark-theme [style*='background: rgba(255, 115, 105, 0.5)'] { 71 | box-shadow: 0 2px 4px rgb(0 0 0 / 66%) !important; 72 | } 73 | -------------------------------------------------------------------------------- /tray/main.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tray 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | let tray; 10 | 11 | module.exports = async function (api, db, __exports, __eval) { 12 | const { env, registry } = api, 13 | electron = require('electron'), 14 | path = require('path'), 15 | enhancerIcon = path.resolve(`${__dirname}/../../media/colour-x16.png`), 16 | hotkey = await db.get(['hotkey']), 17 | openAtStartup = await db.get(['startup']), 18 | runInBackground = await db.get(['run_in_background']), 19 | menuHotkey = await ( 20 | await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e') 21 | ).get(['hotkey']); 22 | 23 | const toggleWindows = (checkFocus = true) => { 24 | const windows = electron.BrowserWindow.getAllWindows(); 25 | if (runInBackground) { 26 | // hide 27 | if (windows.some((win) => (checkFocus ? win.isFocused() : true) && win.isVisible())) { 28 | windows.forEach((win) => [win.isFocused() && win.blur(), win.hide()]); 29 | } else windows.forEach((win) => win.show()); 30 | } else { 31 | // minimize 32 | if (windows.some((win) => (checkFocus ? win.isFocused() : true) && !win.isMinimized())) { 33 | windows.forEach((win) => win.minimize()); 34 | } else windows.forEach((win) => win.restore()); 35 | } 36 | }; 37 | 38 | await electron.app.whenReady(); 39 | electron.app.setLoginItemSettings({ openAtLogin: openAtStartup, args: ['--startup'] }); 40 | 41 | tray = new electron.Tray(enhancerIcon); 42 | tray.setToolTip('notion-enhancer'); 43 | tray.on('click', () => toggleWindows(false)); 44 | electron.globalShortcut.register(hotkey, toggleWindows); 45 | 46 | // connects to client hotkey listener 47 | // manually forces new window creation 48 | // since notion's default is broken by 49 | // duplicate window prevention 50 | const createWindow = () => { 51 | const { createWindow } = api.electron.notionRequire('main/createWindow.js'); 52 | createWindow('/'); 53 | }; 54 | electron.ipcMain.on('notion-enhancer:create-new-window', createWindow); 55 | 56 | const contextMenu = electron.Menu.buildFromTemplate([ 57 | { 58 | type: 'normal', 59 | label: 'notion-enhancer', 60 | icon: enhancerIcon, 61 | enabled: false, 62 | }, 63 | { type: 'separator' }, 64 | { 65 | type: 'normal', 66 | label: 'docs', 67 | click: () => electron.shell.openExternal('https://notion-enhancer.github.io/'), 68 | }, 69 | { 70 | type: 'normal', 71 | label: 'source code', 72 | click: () => electron.shell.openExternal('https://github.com/notion-enhancer/'), 73 | }, 74 | { 75 | type: 'normal', 76 | label: 'community', 77 | click: () => electron.shell.openExternal('https://discord.gg/sFWPXtA'), 78 | }, 79 | { 80 | type: 'normal', 81 | label: 'enhancements menu', 82 | accelerator: menuHotkey, 83 | click: env.focusMenu, 84 | }, 85 | { type: 'separator' }, 86 | { 87 | type: 'normal', 88 | label: 'toggle visibility', 89 | accelerator: hotkey, 90 | click: toggleWindows, 91 | }, 92 | { 93 | type: 'normal', 94 | label: 'new window', 95 | click: createWindow, 96 | accelerator: 'CmdOrCtrl+Shift+N', 97 | }, 98 | { 99 | label: 'relaunch', 100 | click: env.reload, 101 | }, 102 | { 103 | label: 'quit', 104 | role: 'quit', 105 | }, 106 | ]); 107 | tray.setContextMenu(contextMenu); 108 | }; 109 | -------------------------------------------------------------------------------- /outliner/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: outliner 3 | * (c) 2020 CloudHill (https://github.com/CloudHill) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | export default async function ({ web, components }, db) { 9 | const dbNoticeText = 'Open a page to see its table of contents.', 10 | pageNoticeText = 'Click on a heading to jump to it.', 11 | $notice = web.html`

${dbNoticeText}

`; 12 | 13 | const $headingList = web.html`
`; 14 | 15 | let viewFocused = false, 16 | $page; 17 | await components.addPanelView({ 18 | id: '87e077cc-5402-451c-ac70-27cc4ae65546', 19 | icon: web.html` 20 | 21 | 22 | 23 | 24 | 25 | 26 | `, 27 | title: 'Outliner', 28 | $content: web.render(web.html`
`, $notice, $headingList), 29 | onFocus: () => { 30 | viewFocused = true; 31 | $page = document.getElementsByClassName('notion-page-content')[0]; 32 | updateHeadings(); 33 | }, 34 | onBlur: () => { 35 | viewFocused = false; 36 | }, 37 | }); 38 | await web.whenReady(); 39 | 40 | function updateHeadings() { 41 | if (!$page) return; 42 | $notice.innerText = pageNoticeText; 43 | $headingList.style.display = ''; 44 | const $headerBlocks = $page.querySelectorAll('[class^="notion-"][class*="header-block"]'), 45 | $fragment = web.html`
`; 46 | let depth = 0, 47 | indent = 0; 48 | for (const $header of $headerBlocks) { 49 | const id = $header.dataset.blockId.replace(/-/g, ''), 50 | placeholder = $header.querySelector('[placeholder]').getAttribute('placeholder'), 51 | headerDepth = +[...placeholder].reverse()[0]; 52 | if (depth && depth < headerDepth) { 53 | indent += 18; 54 | } else if (depth > headerDepth) { 55 | indent = Math.max(indent - 18, 0); 56 | } 57 | depth = headerDepth; 58 | const $outlineHeader = web.render( 59 | web.html``, 62 | $header.innerText 63 | ); 64 | $outlineHeader.addEventListener('click', (event) => { 65 | location.hash = ''; 66 | }); 67 | web.render($fragment, $outlineHeader); 68 | } 69 | if ($fragment.innerHTML !== $headingList.innerHTML) { 70 | web.render(web.empty($headingList), ...$fragment.children); 71 | } 72 | } 73 | const pageObserver = () => { 74 | if (!viewFocused) return; 75 | if (document.contains($page)) { 76 | updateHeadings(); 77 | } else { 78 | $page = document.querySelector('.notion-page-content'); 79 | if (!$page) { 80 | $notice.innerText = dbNoticeText; 81 | $headingList.style.display = 'none'; 82 | } else updateHeadings(); 83 | } 84 | }; 85 | web.addDocumentObserver(pageObserver, [ 86 | '.notion-page-content', 87 | '.notion-collection_view_page-block', 88 | ]); 89 | pageObserver(); 90 | } 91 | -------------------------------------------------------------------------------- /view-scale/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: view scale 3 | * (c) 2021 SP12893678 (https://sp12893678.tk/) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | export default async function ({ electron, web, components }, db) { 9 | let zoomFactor = (await db.get(['default_zoom'])) / 100, 10 | updateScale = () => {}; 11 | electron.webFrame.setZoomFactor(zoomFactor); 12 | 13 | const zoomOffset = (await db.get(['offset'])) / 100, 14 | zoomMin = 0.5, 15 | zoomMax = 2, 16 | getZoomFactor = () => electron.webFrame.getZoomFactor(), 17 | setZoomFactor = (zoomFactor) => electron.webFrame.setZoomFactor(zoomFactor), 18 | zoomPlus = (multiplier = 1) => { 19 | zoomFactor = Math.min(getZoomFactor() + zoomOffset * multiplier, zoomMax); 20 | setZoomFactor(zoomFactor); 21 | updateScale(); 22 | }, 23 | zoomMinus = (multiplier = 1) => { 24 | zoomFactor = Math.max(getZoomFactor() - zoomOffset * multiplier, zoomMin); 25 | setZoomFactor(zoomFactor); 26 | updateScale(); 27 | }; 28 | 29 | const mousewheelModifier = await db.get(['mousewheel']); 30 | if (mousewheelModifier !== '-- none --') { 31 | const mousewheelModifierKey = { 32 | Control: 'ctrlKey', 33 | Alt: 'altKey', 34 | Command: 'metaKey', 35 | Shift: 'shiftKey', 36 | }[mousewheelModifier]; 37 | document.addEventListener('wheel', (event) => { 38 | if (event[mousewheelModifierKey] && event.deltaY < 0) zoomPlus(); 39 | if (event[mousewheelModifierKey] && event.deltaY > 0) zoomMinus(); 40 | }); 41 | } 42 | 43 | const showVisualSlider = await db.get(['ui']); 44 | if (showVisualSlider) { 45 | const topbarActionsSelector = 46 | '.notion-topbar-action-buttons > div[style="display: flex;"]'; 47 | await web.whenReady([topbarActionsSelector]); 48 | 49 | const $topbarActions = document.querySelector(topbarActionsSelector), 50 | $scaleContainer = web.html`
`, 51 | $scaleSlider = web.html``, 52 | $scaleCounter = web.html`100%`, 53 | $scalePlus = web.html``, 56 | $scaleMinus = web.html``; 59 | components.addTooltip($scalePlus, '**Zoom into the window**'); 60 | components.addTooltip($scaleMinus, '**Zoom out of the window**'); 61 | updateScale = () => { 62 | if (getZoomFactor() !== zoomFactor) zoomFactor = getZoomFactor(); 63 | $scaleSlider.value = Math.round(zoomFactor * 100); 64 | $scaleCounter.innerHTML = Math.round(zoomFactor * 100) + '%'; 65 | }; 66 | updateScale(); 67 | 68 | $scaleSlider.addEventListener('input', () => { 69 | zoomFactor = $scaleSlider.value / 100; 70 | $scaleCounter.innerHTML = Math.round(zoomFactor * 100) + '%'; 71 | }); 72 | $scaleSlider.addEventListener('change', () => setZoomFactor(zoomFactor)); 73 | $scalePlus.addEventListener('click', () => zoomPlus()); 74 | $scaleMinus.addEventListener('click', () => zoomMinus()); 75 | 76 | $topbarActions.prepend( 77 | web.render($scaleContainer, $scaleSlider, $scaleCounter, $scalePlus, $scaleMinus) 78 | ); 79 | 80 | web.addHotkeyListener(['Ctrl', '+'], updateScale); 81 | web.addHotkeyListener(['Ctrl', '-'], updateScale); 82 | web.addHotkeyListener(['Ctrl', '0'], updateScale); 83 | web.addHotkeyListener(['Command', '+'], updateScale); 84 | web.addHotkeyListener(['Command', '-'], updateScale); 85 | web.addHotkeyListener(['Command', '0'], updateScale); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /indentation-lines/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: indentation lines 3 | * (c) 2020 Alexa Baldon (https://github.com/runargs) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | export default async function ({ web }, db) { 10 | let style = 'solid', 11 | opacity = 1, 12 | rainbow = false; 13 | switch (await db.get(['style'])) { 14 | case 'dashed': 15 | style = 'dashed'; 16 | break; 17 | case 'dotted': 18 | style = 'dotted'; 19 | break; 20 | case 'soft': 21 | opacity = 0.25; 22 | break; 23 | case 'rainbow': 24 | opacity = 0.7; 25 | rainbow = true; 26 | break; 27 | } 28 | 29 | let css = ''; 30 | const colors = ['red', 'pink', 'purple', 'blue', 'green', 'yellow']; 31 | colors.push(...colors, ...colors, ...colors, 'gray'); 32 | 33 | for (const listType of ['bulleted_list', 'numbered_list', 'to_do', 'toggle']) { 34 | if (!(await db.get([listType]))) continue; 35 | css += ` 36 | .notion-page-content .notion-${listType}-block > div > div:last-child::before { 37 | border-left: 1px ${style} var(--indentation_lines--color, currentColor); 38 | opacity: ${opacity}; 39 | }`; 40 | 41 | if (rainbow) { 42 | for (let i = 0; i < colors.length; i++) { 43 | css += ` 44 | .notion-page-content ${`.notion-${listType}-block `.repeat(i + 1)} 45 | > div > div:last-child::before { 46 | --indentation_lines--color: var(--theme--text_${colors[i]}); 47 | }`; 48 | } 49 | } 50 | } 51 | 52 | if (await db.get(['toggle_header'])) { 53 | css += ` 54 | .notion-page-content [class$=header-block] > div > div > div:last-child::before { 55 | border-left: 1px ${style} var(--indentation_lines--color, currentColor); 56 | opacity: ${opacity}; 57 | }`; 58 | 59 | if (rainbow) { 60 | for (let i = 0; i < colors.length; i++) { 61 | css += ` 62 | .notion-page-content ${`[class$=header-block] `.repeat(i + 1)} 63 | > div > div > div:last-child::before{ 64 | --indentation_lines--color: var(--theme--text_${colors[i]}); 65 | }`; 66 | } 67 | } 68 | } 69 | 70 | if (await db.get(['table_of_contents'])) { 71 | css += ` 72 | .notion-page-content .notion-table_of_contents-block > div > div > a > div 73 | > div:not([style*='margin-left: 0px']) > div::before { 74 | border-left: 1px ${style} var(--indentation_lines--color, currentColor); 75 | opacity: ${opacity}; 76 | }`; 77 | 78 | if (rainbow) { 79 | css += ` 80 | .notion-page-content .notion-table_of_contents-block > div > div > a > div 81 | > div[style*='margin-left: 24px'] > div::before { 82 | --indentation_lines--color: var(--theme--text_${colors[0]}); 83 | } 84 | .notion-page-content .notion-table_of_contents-block > div > div > a > div 85 | > div[style*='margin-left: 48px'] > div::before { 86 | --indentation_lines--color: var(--theme--text_${colors[1]}); 87 | }`; 88 | } 89 | } 90 | 91 | if (await db.get(['outliner'])) { 92 | css += ` 93 | .outliner--header:not([style='--outliner--indent:0px;'])::before { 94 | border-left: 1px ${style} var(--indentation_lines--color, currentColor); 95 | opacity: ${opacity}; 96 | }`; 97 | if (rainbow) { 98 | css += ` 99 | .outliner--header[style='--outliner--indent:18px;']::before { 100 | --indentation_lines--color: var(--theme--text_${colors[0]}); 101 | } 102 | .outliner--header[style='--outliner--indent:36px;']::before { 103 | --indentation_lines--color: var(--theme--text_${colors[1]}); 104 | }`; 105 | } 106 | } 107 | 108 | web.render(document.head, web.html``); 109 | } 110 | -------------------------------------------------------------------------------- /dracula/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: dracula 3 | * (c) 2016 Dracula Theme 4 | * (c) 2020 CloudHill 5 | * (c) 2020 mimishahzad 6 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 7 | * (https://notion-enhancer.github.io/) under the MIT license 8 | */ 9 | 10 | .notion-dark-theme img[src*='/images/onboarding/use-case-note.png'], 11 | .notion-dark-theme img[src*='/images/onboarding/team-features-illustration.png'] { 12 | filter: invert(1) !important; 13 | } 14 | .notion-dark-theme img[src*='/images/onboarding/checked.svg'] { 15 | filter: hue-rotate(45deg) !important; 16 | } 17 | .notion-dark-theme 18 | img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;'], 19 | .notion-dark-theme 20 | img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;'] { 21 | transition: filter 0.4s ease !important; 22 | } 23 | .notion-dark-theme 24 | img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;']:hover, 25 | .notion-dark-theme 26 | img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;']:hover { 27 | filter: brightness(1.2); 28 | } 29 | 30 | .notion-dark-theme .notion-token-remove-button[role*='button'][tabindex*='0']:hover, 31 | .notion-dark-theme .notion-record-icon { 32 | background: transparent !important; 33 | } 34 | 35 | .notion-dark-theme .notion-focusable:focus-within, 36 | .notion-dark-theme .notion-to_do-block > div > div > div[style*='background:'], 37 | .notion-dark-theme div[role='button'], 38 | [style*='height: 4px;'] > .notion-selectable.notion-collection_view_page-block > *, 39 | .notion-dark-theme .notion-calendar-view-day[style*='background: #282a36;'], 40 | .DayPicker-Day--today, 41 | .notion-dark-theme 42 | .DayPicker:not(.DayPicker--interactionDisabled) 43 | .DayPicker-Day--outside:hover, 44 | .notion-dark-theme 45 | .DayPicker:not(.DayPicker--interactionDisabled) 46 | .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--value) 47 | .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected, 48 | .notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--start, 49 | .notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--end { 50 | transition: color 0.4s ease, background 0.4s ease, box-shadow 0.4s ease !important; 51 | } 52 | 53 | .notion-dark-theme [style*='background: #282a36;'], 54 | .notion-dark-theme 55 | [style*='background: rgb(80, 85, 88);'][style*='color: rgba(255, 255, 255, 0.7)'], 56 | .notion-dark-theme 57 | [style*='background: rgb(80, 85, 88);'][style*='width: 18px;'][style*='height: 18px;'], 58 | .notion-dark-theme 59 | [style*='box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 5px 10px, rgba(15, 15, 15, 0.4) 0px 15px 40px;'], 60 | .notion-dark-theme [style*='background: rgba(151, 154, 155, 0.5);'], 61 | .notion-dark-theme [style*='background: rgba(147, 114, 100, 0.5)'], 62 | .notion-dark-theme [style*='background: rgba(255, 163, 68, 0.5)'], 63 | .notion-dark-theme [style*='background: rgba(255, 220, 73, 0.5)'], 64 | .notion-dark-theme [style*='background: rgba(77, 171, 154, 0.5)'], 65 | .notion-dark-theme [style*='background: rgba(82, 156, 202, 0.5)'], 66 | .notion-dark-theme [style*='background: rgba(154, 109, 215, 0.5)'], 67 | .notion-dark-theme [style*='background: rgba(226, 85, 161, 0.5)'], 68 | .notion-dark-theme [style*='background: rgba(255, 115, 105, 0.5)'] { 69 | box-shadow: 0 2px 4px rgb(0 0 0 / 66%) !important; 70 | } 71 | 72 | .notion-dark-theme .notion-code-block .token.parameter, 73 | .notion-dark-theme .notion-code-block .token.decorator, 74 | .notion-dark-theme .notion-code-block .token.id, 75 | .notion-dark-theme .notion-code-block .token.class, 76 | .notion-dark-theme .notion-code-block .token.pseudo-element, 77 | .notion-dark-theme .notion-code-block .token.pseudo-class, 78 | .notion-dark-theme .notion-code-block .token.attribute { 79 | font-style: italic; 80 | } 81 | 82 | .notion-dark-theme .notion-code-block .token.punctuation { 83 | opacity: 1; 84 | } 85 | -------------------------------------------------------------------------------- /theming/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: theming 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | .token.property { 8 | color: var(--theme--code_property) !important; 9 | } 10 | .token.tag { 11 | color: var(--theme--code_tag) !important; 12 | } 13 | .token.boolean { 14 | color: var(--theme--code_boolean) !important; 15 | } 16 | .token.number { 17 | color: var(--theme--code_number) !important; 18 | } 19 | .token.constant { 20 | color: var(--theme--code_constant) !important; 21 | } 22 | .token.symbol { 23 | color: var(--theme--code_symbol) !important; 24 | } 25 | .token.deleted { 26 | color: var(--theme--code_deleted) !important; 27 | } 28 | .token.selector { 29 | color: var(--theme--code_selector) !important; 30 | } 31 | .token.attr-name { 32 | color: var(--theme--code_attr-name) !important; 33 | } 34 | .token.string { 35 | color: var(--theme--code_string) !important; 36 | } 37 | .token.char { 38 | color: var(--theme--code_char) !important; 39 | } 40 | .token.builtin { 41 | color: var(--theme--code_builtin) !important; 42 | } 43 | .token.inserted { 44 | color: var(--theme--code_inserted) !important; 45 | } 46 | .token.operator { 47 | color: var(--theme--code_operator) !important; 48 | } 49 | .token.entity { 50 | color: var(--theme--code_entity) !important; 51 | } 52 | .token.url { 53 | color: var(--theme--code_url) !important; 54 | } 55 | .token.variable { 56 | color: var(--theme--code_variable) !important; 57 | } 58 | .token.comment { 59 | color: var(--theme--code_comment) !important; 60 | } 61 | .token.cdata { 62 | color: var(--theme--code_cdata) !important; 63 | } 64 | .token.prolog { 65 | color: var(--theme--code_prolog) !important; 66 | } 67 | .token.doctype { 68 | color: var(--theme--code_doctype) !important; 69 | } 70 | .token.atrule { 71 | color: var(--theme--code_atrule) !important; 72 | } 73 | .token.attr-value { 74 | color: var(--theme--code_attr-value) !important; 75 | } 76 | .token.keyword { 77 | color: var(--theme--code_keyword) !important; 78 | } 79 | .token.regex { 80 | color: var(--theme--code_regex) !important; 81 | } 82 | .token.important { 83 | color: var(--theme--code_important) !important; 84 | } 85 | .token.function { 86 | color: var(--theme--code_function) !important; 87 | } 88 | .token.class-name { 89 | color: var(--theme--code_class-name) !important; 90 | } 91 | .token.parameter { 92 | color: var(--theme--code_parameter) !important; 93 | } 94 | .token.decorator { 95 | color: var(--theme--code_decorator) !important; 96 | } 97 | .token.id { 98 | color: var(--theme--code_id) !important; 99 | } 100 | .token.class { 101 | color: var(--theme--code_class) !important; 102 | } 103 | .token.pseudo-element { 104 | color: var(--theme--code_pseudo-element) !important; 105 | } 106 | .token.pseudo-class { 107 | color: var(--theme--code_pseudo-class) !important; 108 | } 109 | .token.attribute { 110 | color: var(--theme--code_attribute) !important; 111 | } 112 | .token.value { 113 | color: var(--theme--code_value) !important; 114 | } 115 | .token.unit { 116 | color: var(--theme--code_unit) !important; 117 | } 118 | .token.punctuation { 119 | color: var(--theme--code_punctuation) !important; 120 | opacity: 0.7 !important; 121 | } 122 | .token.annotation { 123 | color: var(--theme--code_annotation) !important; 124 | } 125 | 126 | .token.operator { 127 | background: transparent !important; 128 | } 129 | .token.namespace { 130 | opacity: 0.7 !important; 131 | } 132 | .token.important, 133 | .token.bold { 134 | font-weight: bold !important; 135 | } 136 | .token.italic { 137 | font-style: italic !important; 138 | } 139 | .token.entity { 140 | cursor: help !important; 141 | } 142 | .token a { 143 | color: inherit !important; 144 | } 145 | .token.punctuation.brace-hover, 146 | .token.punctuation.brace-selected { 147 | outline: solid 1px !important; 148 | } 149 | 150 | .token.operator, 151 | .token.entity, 152 | .token.url, 153 | .language-css .token.string, 154 | .style .token.string { 155 | background: none !important; 156 | } 157 | -------------------------------------------------------------------------------- /word-counter/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: word counter 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | const humanTime = (mins) => { 8 | let readable = ''; 9 | if (1 <= mins) { 10 | readable += `${Math.floor(mins)} min`; 11 | if (2 <= mins) readable += 's'; 12 | } 13 | const secs = Math.round((mins % 1) * 60); 14 | if (1 <= secs) { 15 | if (1 <= mins) readable += ' '; 16 | readable += `${secs} sec`; 17 | if (2 <= secs) readable += 's'; 18 | } 19 | return readable; 20 | }; 21 | 22 | export default async function ({ web, components }, db) { 23 | const dbNoticeText = 'Open a page to see its word count.', 24 | pageNoticeText = 'Click a stat to copy it.', 25 | $notice = web.html`

${dbNoticeText}

`; 26 | 27 | const $wordCount = web.html`12`, 28 | $characterCount = web.html`12`, 29 | $sentenceCount = web.html`12`, 30 | $blockCount = web.html`12`, 31 | $readingTime = web.html`10 mins`, 32 | $readingTooltip = web.html`${await components.feather('info')}`, 33 | $speakingTime = web.html`18 secs`, 34 | $speakingTooltip = web.html`${await components.feather('info')}`, 35 | $statList = web.render( 36 | web.html`
`, 37 | web.render(web.html`

`, $wordCount, ' words'), 38 | web.render(web.html`

`, $characterCount, ' characters'), 39 | web.render(web.html`

`, $sentenceCount, ' sentences'), 40 | web.render(web.html`

`, $blockCount, ' blocks'), 41 | web.render( 42 | web.html`

`, 43 | $readingTooltip, 44 | $readingTime, 45 | ' reading time' 46 | ), 47 | web.render( 48 | web.html`

`, 49 | $speakingTooltip, 50 | $speakingTime, 51 | ' speaking time' 52 | ) 53 | ); 54 | $statList.querySelectorAll('.word-counter--stat').forEach(($stat) => { 55 | $stat.addEventListener('click', () => web.copyToClipboard($stat.innerText)); 56 | }); 57 | components.addTooltip($readingTooltip, '**~ 275 wpm**', { offsetDirection: 'left' }); 58 | components.addTooltip($speakingTooltip, '**~ 180 wpm**', { offsetDirection: 'left' }); 59 | 60 | let viewFocused = false, 61 | $page; 62 | await components.addPanelView({ 63 | id: 'b99deb52-6955-43d2-a53b-a31540cd19a5', 64 | icon: await components.feather('type'), 65 | title: 'Word Counter', 66 | $content: web.render(web.html`
`, $notice, $statList), 67 | onFocus: () => { 68 | viewFocused = true; 69 | $page = document.getElementsByClassName('notion-page-content')[0]; 70 | updateStats(); 71 | }, 72 | onBlur: () => { 73 | viewFocused = false; 74 | }, 75 | }); 76 | 77 | function updateStats() { 78 | if (!$page) return; 79 | const words = $page.innerText.split(/[^\w]+/).length; 80 | $wordCount.innerText = words; 81 | $characterCount.innerText = $page.innerText.length; 82 | $sentenceCount.innerText = $page.innerText.split('.').length; 83 | $blockCount.innerText = $page.querySelectorAll('[data-block-id]').length; 84 | $readingTime.innerText = humanTime(words / 275); 85 | $speakingTime.innerText = humanTime(words / 180); 86 | } 87 | const pageObserver = () => { 88 | if (!viewFocused) return; 89 | if (document.contains($page)) { 90 | updateStats(); 91 | } else { 92 | $page = document.getElementsByClassName('notion-page-content')[0]; 93 | if ($page) { 94 | $notice.innerText = pageNoticeText; 95 | $statList.style.display = ''; 96 | updateStats(); 97 | } else { 98 | $notice.innerText = dbNoticeText; 99 | $statList.style.display = 'none'; 100 | } 101 | } 102 | }; 103 | web.addDocumentObserver(pageObserver, [ 104 | '.notion-page-content', 105 | '.notion-collection_view_page-block', 106 | ]); 107 | pageObserver(); 108 | } 109 | -------------------------------------------------------------------------------- /indentation-lines/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: indentation lines 3 | * (c) 2020 Alexa Baldon (https://github.com/runargs) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .notion-page-content .notion-bulleted_list-block > div > div:last-child, 9 | .notion-page-content .notion-numbered_list-block > div > div:last-child, 10 | .notion-page-content .notion-to_do-block > div > div:last-child, 11 | .notion-page-content .notion-toggle-block > div > div:last-child, 12 | .notion-page-content [class$='header-block'] > div > div > div:last-child, 13 | .notion-page-content .notion-table_of_contents-block > div > div > a > div > div { 14 | position: relative; 15 | } 16 | 17 | .notion-page-content .notion-bulleted_list-block > div > div:last-child::before, 18 | .notion-page-content .notion-numbered_list-block > div > div:last-child::before, 19 | .notion-page-content .notion-to_do-block > div > div:last-child::before, 20 | .notion-page-content .notion-toggle-block > div > div:last-child::before, 21 | .notion-page-content [class$='header-block'] > div > div > div:last-child::before, 22 | .notion-page-content .pseudoSelection > div > div:last-child::before { 23 | content: ''; 24 | position: absolute; 25 | height: calc(100% - 2em); 26 | top: 2em; 27 | margin-inline-start: -14.48px; 28 | } 29 | .notion-page-content [class$='header-block'] > div > div > div:last-child::before { 30 | margin-inline-start: -15.48px; 31 | } 32 | 33 | .notion-page-content 34 | .notion-table_of_contents-block 35 | > div 36 | > div 37 | > a 38 | > div 39 | > div:not([style*='margin-left: 0px']) 40 | > div::before { 41 | content: ''; 42 | position: absolute; 43 | height: 100%; 44 | top: 0; 45 | margin-inline-start: -14.48px; 46 | } 47 | 48 | /* add background to block dragger */ 49 | .notion-frame 50 | > [style*='position: absolute; top: 0px; left: 0px;'] 51 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 52 | [data-block-id], 53 | .notion-peek-renderer 54 | > div 55 | > [style*='position: absolute; top: 0px; left: 0px;'] 56 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 57 | [data-block-id] { 58 | background: var(--theme--bg) !important; 59 | border-radius: 3px; 60 | } 61 | .notion-frame 62 | > [style*='position: absolute; top: 0px; left: 0px;'] 63 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 64 | + .notion-focusable, 65 | .notion-peek-renderer 66 | > div 67 | > [style*='position: absolute; top: 0px; left: 0px;'] 68 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 69 | + .notion-focusable { 70 | left: -42px !important; 71 | background: transparent !important; 72 | } 73 | .notion-frame 74 | > [style*='position: absolute; top: 0px; left: 0px;'] 75 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 76 | + .notion-focusable 77 | > .plus, 78 | .notion-peek-renderer 79 | > div 80 | > [style*='position: absolute; top: 0px; left: 0px;'] 81 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 82 | + .notion-focusable 83 | > .plus { 84 | border-radius: 3px; 85 | width: 18px !important; 86 | padding: 0 1px !important; 87 | background: var(--theme--bg) !important; 88 | } 89 | .notion-frame 90 | > [style*='position: absolute; top: 0px; left: 0px;'] 91 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 92 | + .notion-focusable 93 | > .plus:hover, 94 | .notion-peek-renderer 95 | > div 96 | > [style*='position: absolute; top: 0px; left: 0px;'] 97 | [style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;'] 98 | + .notion-focusable 99 | > .plus:hover { 100 | background: var(--theme--ui_interactive-hover) !important; 101 | } 102 | -------------------------------------------------------------------------------- /tweaks/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tweaks", 3 | "id": "5174a483-c88d-4bf8-a95f-35cd330b76e2", 4 | "version": "0.2.0", 5 | "description": "common style/layout changes and custom code insertion. check out the [tweaks page](https://notion-enhancer.github.io/advanced/tweaks) for more.", 6 | "tags": ["extension", "customisation"], 7 | "authors": [ 8 | { 9 | "name": "dragonwocky", 10 | "email": "thedragonring.bod@gmail.com", 11 | "homepage": "https://dragonwocky.me/", 12 | "avatar": "https://dragonwocky.me/avatar.jpg" 13 | } 14 | ], 15 | "css": { 16 | "client": ["client.css"] 17 | }, 18 | "js": { 19 | "client": ["client.mjs"] 20 | }, 21 | "options": [ 22 | { 23 | "type": "file", 24 | "key": "insert.css", 25 | "label": "css insert", 26 | "tooltip": "**upload a css file that will be applied to the notion client**", 27 | "extensions": [".css"] 28 | }, 29 | { 30 | "type": "number", 31 | "key": "tweak.responsive_breakpoint_px", 32 | "label": "responsive columns breakpoint (px)", 33 | "tooltip": "the **width in pixels below which in-page columns are resized** to appear full-width to reduce content squishing", 34 | "value": 600 35 | }, 36 | { 37 | "type": "number", 38 | "key": "tweak.responsive_breakpoint_percent", 39 | "label": "responsive columns breakpoint (%)", 40 | "tooltip": "the **percentage of the screen below which in-page columns are resized to appear full-width** to reduce content squishing", 41 | "value": 30 42 | }, 43 | { 44 | "type": "toggle", 45 | "key": "tweak.full_width_pages", 46 | "label": "full width pages", 47 | "tooltip": "**decreases padding so every page appears full width**", 48 | "value": false 49 | }, 50 | { 51 | "type": "toggle", 52 | "key": "tweak.normalise_table_scroll", 53 | "label": "wrap tables to page width", 54 | "tooltip": "**force horizontally scrollable tables to respect the width and padding of a page when they overflow**", 55 | "value": true 56 | }, 57 | { 58 | "type": "toggle", 59 | "key": "tweak.snappy_transitions", 60 | "label": "snappy transitions", 61 | "tooltip": "enabling this **eliminates css animation time**, but will not prevent motion e.g. the sidebar popping out", 62 | "value": false 63 | }, 64 | { 65 | "type": "toggle", 66 | "key": "tweak.hide_help", 67 | "label": "hide help button", 68 | "value": false 69 | }, 70 | { 71 | "type": "toggle", 72 | "key": "tweak.hide_slash_for_commands", 73 | "label": "hide \"Type '/' for commands\"", 74 | "value": false 75 | }, 76 | { 77 | "type": "toggle", 78 | "key": "tweak.thicker_bold", 79 | "label": "thicker bold text", 80 | "value": true 81 | }, 82 | { 83 | "type": "toggle", 84 | "key": "tweak.spaced_lines", 85 | "label": "readable line spacing", 86 | "tooltip": "**greater line spacing between text blocks**", 87 | "value": false 88 | }, 89 | { 90 | "type": "toggle", 91 | "key": "tweak.condensed_bullets", 92 | "label": "condense bullet points", 93 | "tooltip": "**tighter line spacing between bullet point blocks**", 94 | "value": false 95 | }, 96 | { 97 | "type": "toggle", 98 | "key": "tweak.bracketed_links", 99 | "label": "bracketed links", 100 | "tooltip": "**render links surrounded with [[brackets]] instead of __underlined__**", 101 | "value": false 102 | }, 103 | { 104 | "type": "toggle", 105 | "key": "tweak.accented_links", 106 | "label": "accented links", 107 | "tooltip": "**links are shown by default with notion's blue colour or a theme's equivalent**", 108 | "value": false 109 | }, 110 | { 111 | "type": "toggle", 112 | "key": "tweak.quotation_marks", 113 | "label": "quote block quotation marks", 114 | "tooltip": "**wrap quote block content in large, decorative serif quotation marks**", 115 | "value": false 116 | }, 117 | { 118 | "type": "select", 119 | "key": "tweak.img_alignment", 120 | "label": "image alignment", 121 | "values": ["center", "left", "right"] 122 | } 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /always-on-top/button.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: always on top 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | export const createButton = async ({ electron, web, components }, db) => { 8 | let pinIcon = 9 | (await db.get(['pin_icon'])) || 10 | ` 11 | 12 | `, 13 | unpinIcon = 14 | (await db.get(['unpin_icon'])) || 15 | ` 16 | 17 | `; 18 | pinIcon = pinIcon.trim(); 19 | unpinIcon = unpinIcon.trim(); 20 | 21 | pinIcon = 22 | pinIcon.startsWith('') ? pinIcon : web.escape(pinIcon); 23 | unpinIcon = 24 | unpinIcon.startsWith('') 25 | ? unpinIcon 26 | : web.escape(unpinIcon); 27 | 28 | const $button = web.html`
`, 29 | $pin = web.html``, 30 | $unpin = web.html``; 31 | components.addTooltip($pin, '**Pin window to top**'); 32 | components.addTooltip($unpin, '**Unpin window from top**'); 33 | web.render($button, $pin); 34 | 35 | $pin.addEventListener('click', () => { 36 | $pin.replaceWith($unpin); 37 | electron.browser.setAlwaysOnTop(true); 38 | }); 39 | $unpin.addEventListener('click', () => { 40 | $unpin.replaceWith($pin); 41 | electron.browser.setAlwaysOnTop(false); 42 | }); 43 | 44 | return $button; 45 | }; 46 | -------------------------------------------------------------------------------- /tweaks/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tweaks 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (c) 2020 arecsu 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | .enhancer--tweak-responsive_breakpoint 9 | .notion-column_list-block 10 | [style='display: flex;'] 11 | > div { 12 | width: 100% !important; 13 | } 14 | .enhancer--tweak-responsive_breakpoint .notion-column_list-block [style='display: flex;'] { 15 | flex-direction: column !important; 16 | } 17 | .enhancer--tweak-responsive_breakpoint .notion-app-inner, 18 | .enhancer--tweak-full_width_pages .notion-app-inner { 19 | --theme--page-width: 100%; 20 | --theme--page-padding: calc(48px + env(safe-area-inset-left)); 21 | } 22 | 23 | .enhancer--tweak-normalise_table_scroll 24 | .notion-frame 25 | .notion-page-content 26 | .notion-collection_view-block, 27 | .enhancer--tweak-normalise_table_scroll .notion-peek-renderer .notion-collection_view-block, 28 | .enhancer--tweak-normalise_table_scroll 29 | .notion-page-template-modal 30 | .notion-collection_view-block, 31 | .enhancer--tweak-normalise_table_dscroll .notion-collection-view-body .notion-table-view { 32 | width: 100% !important; 33 | padding: 0 !important; 34 | } 35 | .enhancer--tweak-normalise_table_scroll 36 | .notion-collection_view-block 37 | > [contenteditable] 38 | > .notion-scroller 39 | > [class$='view'][style*='padding'], 40 | .enhancer--tweak-normalise_table_scroll 41 | .notion-collection_view-block 42 | > :first-child[style*='padding-right'] { 43 | padding: 1px !important; 44 | } 45 | 46 | .enhancer--tweak-snappy_transitions * { 47 | animation-duration: 0s !important; 48 | transition-duration: 0s !important; 49 | } 50 | .enhancer--tweak-snappy_transitions .notion-selectable-halo { 51 | opacity: 1 !important; 52 | } 53 | 54 | .enhancer--tweak-hide_help .notion-help-button { 55 | display: none !important; 56 | } 57 | 58 | .enhancer--tweak-hide_slash_for_commands [contenteditable]:empty:after { 59 | content: ' ' !important; 60 | } 61 | 62 | .enhancer--tweak-thicker_bold .notion-page-content span[style*='font-weight:600'] { 63 | font-weight: 700 !important; 64 | } 65 | 66 | .enhancer--tweak-spaced_lines .notion-page-content .notion-selectable.notion-text-block { 67 | line-height: 1.65 !important; 68 | margin-top: 0.75em !important; 69 | } 70 | 71 | .enhancer--tweak-condensed_bullets .notion-selectable.notion-bulleted_list-block { 72 | margin-top: -1.5px !important; 73 | margin-bottom: -1.5px !important; 74 | } 75 | 76 | .enhancer--tweak-bracketed_links .notion-link-token span { 77 | border-bottom: none !important; 78 | } 79 | .enhancer--tweak-bracketed_links .notion-link-token:before { 80 | content: '[['; 81 | opacity: 0.7; 82 | transition: opacity 100ms ease-in; 83 | } 84 | .enhancer--tweak-bracketed_links .notion-link-token:after { 85 | content: ']]'; 86 | opacity: 0.7; 87 | transition: opacity 100ms ease-in; 88 | } 89 | .enhancer--tweak-bracketed_links .notion-link-token:hover::before, 90 | .enhancer--tweak-bracketed_links .notion-link-token:hover::after { 91 | opacity: 1; 92 | } 93 | 94 | .enhancer--tweak-accented_links .notion-link-token { 95 | color: var(--theme--accent_blue) !important; 96 | } 97 | .enhancer--tweak-accented_links .notion-link-token span[style*='border-bottom:0.05em'] { 98 | opacity: 1 !important; 99 | border-color: var(--theme--accent_blue) !important; 100 | } 101 | 102 | .enhancer--tweak-quotation_marks 103 | .notion-quote-block 104 | [style*='border-left: 3px solid currentcolor;'] { 105 | position: relative; 106 | padding-left: 24px !important; 107 | padding-right: 18px !important; 108 | } 109 | .enhancer--tweak-quotation_marks .notion-quote-block [placeholder='Empty quote']::before, 110 | .enhancer--tweak-quotation_marks .notion-quote-block [placeholder='Empty quote']::after { 111 | font-family: Georgia, serif; 112 | font-size: 24px; 113 | font-weight: bold; 114 | position: absolute; 115 | } 116 | .enhancer--tweak-quotation_marks .notion-quote-block [placeholder='Empty quote']::before { 117 | content: '\201C'; 118 | left: 8px; 119 | top: -2px; 120 | } 121 | .enhancer--tweak-quotation_marks .notion-quote-block [placeholder='Empty quote']::after { 122 | content: '\201D'; 123 | right: 2px; 124 | bottom: -2px; 125 | } 126 | 127 | .enhancer--tweak-img_alignment-left .notion-image-block { 128 | align-self: start !important; 129 | } 130 | .enhancer--tweak-img_alignment-right .notion-image-block { 131 | align-self: end !important; 132 | } 133 | -------------------------------------------------------------------------------- /global-block-links/client.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: global block links 3 | * (c) 2021 admiraldus (https://github.com/admiraldus) 4 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 5 | * (https://notion-enhancer.github.io/) under the MIT license 6 | */ 7 | 8 | export default async function ({ web, components, notion }, db) { 9 | const topbarShareSelector = '.notion-topbar-share-menu', 10 | blockActionSelector = 11 | '.notion-overlay-container .notion-scroller.vertical .notion-focusable > div > div > [style*="text-overflow: ellipsis;"]', 12 | hoveredActionSelector = 13 | '.notion-overlay-container .notion-scroller.vertical .notion-focusable[style*="background:"]', 14 | topbarCopyClass = 'global_block_links--topbar_copy', 15 | blockCopyClass = 'global_block_links--block_copy', 16 | hiddenClass = 'global_block_links--hidden'; 17 | 18 | const topbarCopyIcon = await db.get(['topbar_copy_icon']), 19 | topbarCopyText = await db.get(['topbar_copy_text']); 20 | if (topbarCopyIcon || topbarCopyText) { 21 | const $topbarCopyTemplate = web.render( 22 | web.html`
`, 23 | topbarCopyIcon 24 | ? web.html` 25 | 32 | ` 33 | : '', 34 | topbarCopyText 35 | ? web.html` 36 | Copy link 37 | Link copied! 38 | ` 39 | : '' 40 | ); 41 | 42 | const insertTopbarCopy = () => { 43 | const $btns = document.querySelectorAll(topbarShareSelector); 44 | $btns.forEach(($btn) => { 45 | if (!$btn.previousElementSibling?.classList?.contains?.(topbarCopyClass)) { 46 | const $copy = $topbarCopyTemplate.cloneNode(true); 47 | components.addTooltip($copy, '**Copy page link**'); 48 | $btn.before($copy); 49 | 50 | let resetButtonDelay; 51 | $copy.addEventListener('click', () => { 52 | if (topbarCopyText) { 53 | const $copyText = $copy.querySelector('[data-copy]'), 54 | $copiedText = $copy.querySelector('[data-copied]'); 55 | $copyText.classList.add(hiddenClass); 56 | $copiedText.classList.remove(hiddenClass); 57 | clearTimeout(resetButtonDelay); 58 | resetButtonDelay = setTimeout(() => { 59 | $copyText.classList.remove(hiddenClass); 60 | $copiedText.classList.add(hiddenClass); 61 | }, 1250); 62 | } 63 | 64 | web.copyToClipboard(`https://notion.so/${notion.getPageID().replace(/-/g, '')}`); 65 | }); 66 | } 67 | }); 68 | }; 69 | insertTopbarCopy(); 70 | web.addDocumentObserver(insertTopbarCopy, [topbarShareSelector]); 71 | } 72 | 73 | const $blockCopyTemplate = web.html` 74 |
75 | ${await components.feather('globe')} 76 | Global link 77 |
`; 78 | 79 | const getLinkButtons = () => 80 | [...document.querySelectorAll(blockActionSelector)] 81 | .filter(($action) => 82 | ['Copy link', '링크 복사', 'リンクをコピー'].includes($action.textContent) 83 | ) 84 | .map(($action) => $action.closest('.notion-focusable')), 85 | insertBlockCopy = () => { 86 | const $btns = getLinkButtons(); 87 | $btns.forEach(($btn) => { 88 | if (!$btn.previousElementSibling?.classList?.contains?.(blockCopyClass)) { 89 | const $copy = $blockCopyTemplate.cloneNode(true); 90 | $btn.before($copy); 91 | 92 | $copy.addEventListener('mouseover', () => { 93 | document.querySelectorAll(hoveredActionSelector).forEach(($action) => { 94 | $action.style.background = ''; 95 | }); 96 | }); 97 | 98 | $copy.addEventListener('click', async () => { 99 | $btn.click(); 100 | const link = await web.readFromClipboard(), 101 | id = link.replace(/.+#(?=\w+)/, ''); 102 | web.copyToClipboard(id.length === 32 ? `https://notion.so/${id}` : link); 103 | }); 104 | } 105 | }); 106 | }; 107 | insertBlockCopy(); 108 | web.addDocumentObserver(insertBlockCopy, [blockActionSelector]); 109 | } 110 | -------------------------------------------------------------------------------- /tabs/rendererIndex.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tabs 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | 'use strict'; 8 | 9 | module.exports = async function (api, db, __exports, __eval) { 10 | const url = require('url'), 11 | electron = require('electron'), 12 | electronWindow = electron.remote.getCurrentWindow(), 13 | { components, web, env } = api; 14 | 15 | window['__start'] = async () => { 16 | // display:none; to prevent content flash while css loads 17 | document.body.style.display = 'none'; 18 | 19 | const tabCache = new Map(), 20 | Tab = await require('./tab.cjs')(api, db, tabCache); 21 | document.body.dataset.tabLabels = await db.get(['label_type']); 22 | document.body.dataset.tabStyle = await db.get(['layout_style']); 23 | 24 | const $header = web.html`
`, 25 | $tabs = web.html`
`, 26 | $newTab = web.html`
${await components.feather('plus')}
`, 27 | $root = document.querySelector('#root'), 28 | $windowActions = web.html`
`; 29 | document.body.prepend(web.render($header, $tabs, $newTab, $windowActions)); 30 | 31 | // make space for native window buttons on mac 32 | if (env.name === 'darwin') $tabs.style.paddingLeft = '72px'; 33 | 34 | $newTab.addEventListener('click', () => new Tab($tabs, $root)); 35 | electron.ipcRenderer.on('notion-enhancer:close-tab', (event, id) => { 36 | const tab = tabCache.get(id); 37 | if (tab) tab.close(); 38 | }); 39 | electron.ipcRenderer.on( 40 | 'notion-enhancer:open-tab', 41 | (event, opts) => new Tab($tabs, $root, opts) 42 | ); 43 | 44 | const rememberLastOpen = await db.get(['remember_last_open']), 45 | openTabs = await db.get(['last_open_tabs_cache']); 46 | if (rememberLastOpen && openTabs && Array.isArray(openTabs)) { 47 | for (const tab of openTabs) { 48 | new Tab($tabs, $root, { ...tab, cancelAnimation: true }); 49 | } 50 | } else { 51 | new Tab($tabs, $root, { 52 | notionUrl: url.parse(window.location.href, true).query.path, 53 | cancelAnimation: true, 54 | }); 55 | } 56 | window.addEventListener('beforeunload', () => { 57 | const openTabs = [...$tabs.children] 58 | .filter(($tab) => tabCache.get($tab.id)) 59 | .map(($tab) => { 60 | const tab = tabCache.get($tab.id); 61 | return { 62 | notionUrl: tab.$notion.src, 63 | icon: tab.icon, 64 | title: tab.title, 65 | }; 66 | }); 67 | db.set(['last_open_tabs_cache'], openTabs); 68 | }); 69 | 70 | let $draggedTab; 71 | const $dragIndicator = web.html``, 72 | getDragTarget = ($el) => { 73 | while (!$el.matches('.tab, header, body')) $el = $el.parentElement; 74 | if ($el.matches('header')) $el = $el.firstElementChild; 75 | return $el.matches('#tabs, .tab') ? $el : undefined; 76 | }, 77 | resetDraggedTabs = () => { 78 | if ($draggedTab) { 79 | $dragIndicator.remove(); 80 | $draggedTab.style.opacity = ''; 81 | $draggedTab = undefined; 82 | } 83 | }; 84 | $header.addEventListener('dragstart', (event) => { 85 | $draggedTab = getDragTarget(event.target); 86 | $draggedTab.style.opacity = 0.5; 87 | const tab = tabCache.get($draggedTab.id); 88 | event.dataTransfer.setData( 89 | 'text', 90 | JSON.stringify({ 91 | window: electronWindow.webContents.id, 92 | tab: $draggedTab.id, 93 | icon: tab.$tabIcon.innerText || tab.$tabIcon.style.background, 94 | title: tab.$tabTitle.innerText, 95 | url: tab.$notion.src, 96 | }) 97 | ); 98 | }); 99 | $header.addEventListener('dragover', (event) => { 100 | const $target = getDragTarget(event.target); 101 | if ($target) { 102 | if ($target.matches('#tabs')) { 103 | $target.after($dragIndicator); 104 | } else if ($target.matches('#tabs > :first-child')) { 105 | $tabs.before($dragIndicator); 106 | } else $target.before($dragIndicator); 107 | event.preventDefault(); 108 | } 109 | }); 110 | document.addEventListener('drop', (event) => { 111 | const eventData = JSON.parse(event.dataTransfer.getData('text')), 112 | $target = getDragTarget(event.target) || $tabs, 113 | sameWindow = eventData.window === electronWindow.webContents.id, 114 | tabMovement = 115 | !sameWindow || 116 | ($target && 117 | $target !== $draggedTab && 118 | $target !== $draggedTab.nextElementSibling && 119 | ($target.matches('#tabs') ? $target.lastElementChild !== $draggedTab : true)); 120 | if (!sameWindow) { 121 | electron.ipcRenderer.send('notion-enhancer:close-tab', { 122 | window: eventData.window, 123 | id: eventData.tab, 124 | }); 125 | const transferred = new Tab($tabs, $root, { 126 | notionUrl: eventData.url, 127 | cancelAnimation: true, 128 | icon: eventData.icon, 129 | title: eventData.title, 130 | }); 131 | $draggedTab = transferred.$tab; 132 | } 133 | if (tabMovement) { 134 | if ($target.matches('#tabs')) { 135 | $target.append($draggedTab); 136 | } else $target.before($draggedTab); 137 | } 138 | resetDraggedTabs(); 139 | }); 140 | $header.addEventListener('dragend', (event) => resetDraggedTabs()); 141 | }; 142 | }; 143 | -------------------------------------------------------------------------------- /tabs/tabs.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: tabs 3 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 4 | * (https://notion-enhancer.github.io/) under the MIT license 5 | */ 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | html, 12 | body { 13 | height: 100%; 14 | width: 100%; 15 | margin: 0; 16 | padding: 0; 17 | background: var(--theme--bg) !important; 18 | overflow: hidden; 19 | } 20 | 21 | body { 22 | display: flex !important; 23 | flex-direction: column; 24 | } 25 | 26 | header { 27 | display: flex; 28 | background: var(--theme--bg_secondary); 29 | border-bottom: 1px solid var(--theme--ui_divider); 30 | width: 100%; 31 | padding: 0.5em; 32 | user-select: none; 33 | -webkit-app-region: drag; 34 | z-index: 3; 35 | font-size: 16px; 36 | } 37 | 38 | #tabs { 39 | display: flex; 40 | overflow: hidden; 41 | } 42 | .tab { 43 | display: flex; 44 | flex-grow: 1; 45 | flex-shrink: 1; 46 | width: 14em; 47 | max-width: 14em; 48 | overflow: hidden; 49 | padding: 0.4em 0.6em; 50 | 51 | color: var(--theme--text_secondary); 52 | background: var(--theme--bg); 53 | font-family: var(--theme--font_sans); 54 | font-weight: 500; 55 | border: none; 56 | -webkit-app-region: no-drag; 57 | } 58 | .tab:hover { 59 | background: var(--theme--ui_interactive-hover); 60 | } 61 | .tab.current { 62 | background: var(--theme--ui_interactive-active); 63 | } 64 | 65 | .drag-indicator { 66 | z-index: 1; 67 | width: 0.125em; 68 | margin: 0 -0.0625em; 69 | background: var(--theme--accent_blue-selection); 70 | } 71 | 72 | .tab-title { 73 | white-space: nowrap; 74 | overflow: hidden; 75 | margin-right: 0.25em; 76 | } 77 | .tab-icon { 78 | margin-right: 0.375em; 79 | flex-shrink: 0; 80 | } 81 | .tab-icon[style*='background'] { 82 | width: 0.875em; 83 | height: 0.875em; 84 | align-self: center; 85 | margin-right: 0.5em; 86 | } 87 | .tab-icon:not([style*='background']):empty, 88 | .tab-icon + svg { 89 | display: none; 90 | } 91 | .tab-icon:not([style*='background']):empty + svg { 92 | /* placeholder icon */ 93 | flex-shrink: 0; 94 | display: inline-block; 95 | margin: 1px 0.5em 0 0; 96 | width: 1.125em; 97 | height: 1.125em; 98 | display: block; 99 | backface-visibility: hidden; 100 | fill: var(--theme--icon_secondary); 101 | } 102 | 103 | .new-tab, 104 | .tab-close { 105 | transition: background 20ms ease-in 0s; 106 | cursor: pointer; 107 | display: inline-flex; 108 | align-items: center; 109 | justify-content: center; 110 | flex-shrink: 0; 111 | border-radius: 0.1875em; 112 | height: 1.25em; 113 | width: 1.25em; 114 | padding: 0 0.25px 0 0; 115 | 116 | align-self: center; 117 | border: none; 118 | background: transparent; 119 | -webkit-app-region: no-drag; 120 | } 121 | .new-tab svg, 122 | .tab-close svg { 123 | width: 0.875em; 124 | height: 0.875em; 125 | fill: var(--theme--icon_secondary); 126 | color: var(--theme--icon_secondary); 127 | } 128 | .new-tab:focus, 129 | .new-tab:hover, 130 | .tab-close:focus, 131 | .tab-close:hover { 132 | background: var(--theme--ui_interactive-hover); 133 | } 134 | .new-tab:active, 135 | .tab-close:active { 136 | background: var(--theme--ui_interactive-active); 137 | } 138 | 139 | .new-tab { 140 | margin: 0 3em 0 0.375em; 141 | } 142 | .tab-close { 143 | margin-left: auto; 144 | } 145 | 146 | #window-actions { 147 | display: flex; 148 | align-items: center; 149 | margin-left: auto; 150 | } 151 | #window-actions > * { 152 | -webkit-app-region: no-drag; 153 | } 154 | 155 | [data-tab-labels='page title only'] .tab-icon { 156 | display: none; 157 | } 158 | [data-tab-labels='page icon only'] .tab { 159 | width: 4em; 160 | max-width: 4em; 161 | } 162 | [data-tab-labels='page icon only'] .tab-title { 163 | display: none; 164 | } 165 | 166 | [data-tab-style='rectangular'] .new-tab, 167 | [data-tab-style='traditional tabbed'] .new-tab { 168 | margin-bottom: -0.25em; 169 | } 170 | [data-tab-style='rectangular'] .tab-close, 171 | [data-tab-style='traditional tabbed'] .tab-close { 172 | align-self: auto; 173 | } 174 | [data-tab-style='rectangular'] .drag-indicator, 175 | [data-tab-style='traditional tabbed'] .drag-indicator, 176 | [data-tab-style='rectangular'] #tabs { 177 | margin-bottom: -0.5em; 178 | } 179 | [data-tab-style='rectangular'] .tab { 180 | padding: 0.6em 0.6em 0.8em 0.6em; 181 | } 182 | [data-tab-style='traditional tabbed'] header { 183 | padding-top: 0.6875em; 184 | } 185 | [data-tab-style='traditional tabbed'] #tabs { 186 | margin: -0.1875em 0 -0.5em 0; 187 | } 188 | [data-tab-style='traditional tabbed'] .tab { 189 | border-top-left-radius: 0.875em; 190 | border-top-right-radius: 0.875em; 191 | padding: 0.6em; 192 | } 193 | [data-tab-style='bubble'] .tab { 194 | border-radius: 0.375em; 195 | } 196 | [data-tab-style='bubble'] .tab:not(:first-child) { 197 | margin-left: 0.5em; 198 | } 199 | [data-tab-style='bubble'] .drag-indicator { 200 | margin: 0 -0.3125em 0 0.1875em; 201 | } 202 | [data-tab-style='bubble'] .drag-indicator:first-child { 203 | margin: 0 0.187em 0 -0.312em; 204 | } 205 | [data-tab-style='compact'] header { 206 | padding: 0; 207 | font-size: 14px; 208 | } 209 | [data-tab-style='compact'] #window-actions { 210 | transform: scale(0.8); 211 | margin-right: -0.35em; 212 | } 213 | 214 | #root { 215 | flex-grow: 1; 216 | } 217 | .notion-webview { 218 | width: 100%; 219 | height: 100%; 220 | display: none; 221 | } 222 | .search-webview { 223 | width: 100%; 224 | height: 60px; 225 | display: none; 226 | transition: transform 70ms ease-in; 227 | transform: translateY(-100%); 228 | pointer-events: none; 229 | position: absolute; 230 | z-index: 2; 231 | } 232 | .search-webview.search-active { 233 | transition: transform 70ms ease-out; 234 | transform: translateY(0%); 235 | pointer-events: auto; 236 | } 237 | -------------------------------------------------------------------------------- /icon-sets/client.css: -------------------------------------------------------------------------------- 1 | /** 2 | * notion-enhancer: icon sets 3 | * (c) 2019 jayhxmo (https://jaymo.io/) 4 | * (c) 2020 CloudHill (https://github.com/CloudHill) 5 | * (c) 2021 dragonwocky (https://dragonwocky.me/) 6 | * (https://notion-enhancer.github.io/) under the MIT license 7 | */ 8 | 9 | .icon_sets--tab_button { 10 | position: relative; 11 | padding-top: 6px; 12 | padding-bottom: 6px; 13 | flex-shrink: 0; 14 | } 15 | .icon_sets--tab_button > .notion-focusable { 16 | user-select: none; 17 | transition: background 20ms ease-in 0s; 18 | cursor: pointer; 19 | display: inline-flex; 20 | align-items: center; 21 | height: 28px; 22 | border-radius: 3px; 23 | font-size: 14px; 24 | line-height: 1.2; 25 | padding-left: 8px; 26 | padding-right: 8px; 27 | color: var(--theme--text); 28 | } 29 | .icon_sets--tab_button:hover > .notion-focusable { 30 | background: var(--theme--ui_interactive-hover); 31 | } 32 | .icon_sets--tab_button:active > .notion-focusable { 33 | background: var(--theme--ui_interactive-active); 34 | } 35 | 36 | .icon_sets--view { 37 | padding: 0; 38 | overflow: hidden; 39 | display: flex; 40 | flex-direction: column; 41 | flex-grow: 1; 42 | z-index: 1; 43 | } 44 | .icon_sets--actions { 45 | display: flex; 46 | padding: 10px 14px; 47 | } 48 | 49 | .icon_sets--actions > .notion-focusable-within { 50 | flex-grow: 1; 51 | } 52 | .icon_sets--link_input { 53 | flex-grow: 1; 54 | font-size: 14px; 55 | line-height: 20px; 56 | padding: 4px 6px; 57 | border-top-left-radius: 3px; 58 | border-bottom-left-radius: 3px; 59 | box-shadow: var(--theme--ui_shadow) 0px 0px 0px 1px inset; 60 | background: var(--theme--ui_input); 61 | cursor: text; 62 | height: 28px; 63 | } 64 | .icon_sets--link_input > input { 65 | font-size: inherit; 66 | line-height: inherit; 67 | border: none; 68 | background: none; 69 | width: 100%; 70 | display: block; 71 | resize: none; 72 | padding: 0px; 73 | } 74 | 75 | .icon_sets--link_submit { 76 | border-top-left-radius: 0; 77 | border-bottom-left-radius: 0; 78 | border-top-right-radius: 3px; 79 | border-bottom-right-radius: 3px; 80 | } 81 | 82 | .icon_sets--upload, 83 | .icon_sets--link_submit { 84 | user-select: none; 85 | transition: background 20ms ease-in 0s; 86 | cursor: pointer; 87 | border: none; 88 | background: var(--theme--accent_blue); 89 | color: var(--theme--accent_blue-text); 90 | line-height: 1.2; 91 | padding: 6px 8px; 92 | height: 28px; 93 | font-size: 14px; 94 | font-weight: 500; 95 | } 96 | .icon_sets--upload:hover, 97 | .icon_sets--link_submit:hover { 98 | background: var(--theme--accent_blue-hover); 99 | } 100 | .icon_sets--upload:active, 101 | .icon_sets--link_submit:active { 102 | background: var(--theme--accent_blue-active); 103 | } 104 | 105 | .icon_sets--upload { 106 | margin-left: 0.5em; 107 | border-radius: 3px; 108 | } 109 | 110 | .icon_sets--list { 111 | /* scroller */ 112 | height: 100%; 113 | word-break: break-all; 114 | overflow: hidden auto; 115 | padding: 0 14px 10px 14px; 116 | } 117 | 118 | .icon_sets--error { 119 | color: var(--theme--accent_red); 120 | } 121 | .icon_sets--title, 122 | .icon_sets--error { 123 | margin: 6px 0 8px 0; 124 | font-size: 11px; 125 | font-weight: 500; 126 | line-height: 1.2; 127 | user-select: none; 128 | text-transform: uppercase; 129 | border-radius: 2px; 130 | padding: 0.25em; 131 | display: flex; 132 | align-items: center; 133 | } 134 | 135 | .icon_sets--title { 136 | cursor: pointer; 137 | color: var(--theme--text_secondary); 138 | } 139 | .icon_sets--title:hover { 140 | background: var(--theme--ui_interactive-hover); 141 | } 142 | .icon_sets--title:active { 143 | background: var(--theme--ui_interactive-active); 144 | } 145 | 146 | .icon_sets--title .info { 147 | /* tooltips */ 148 | height: 1em; 149 | margin-left: 0.5em; 150 | } 151 | 152 | .icon_sets--spinner { 153 | margin-left: 0.5em; 154 | height: 1em; 155 | width: 1em; 156 | } 157 | .icon_sets--spinner img { 158 | width: 100%; 159 | height: 100%; 160 | animation: rotation 1.3s infinite linear; 161 | } 162 | @keyframes rotation { 163 | from { 164 | transform: rotate(0deg); 165 | } 166 | to { 167 | transform: rotate(359deg); 168 | } 169 | } 170 | 171 | .icon_sets--title a { 172 | color: currentColor; 173 | transition: color 100ms ease-in; 174 | } 175 | .icon_sets--title a:hover { 176 | color: var(--theme--accent_blue); 177 | } 178 | 179 | .icon_sets--title .triangle { 180 | height: 1em; 181 | width: 0.9em; 182 | margin: 0 0.5em 0 0.25em; 183 | transition: transform 200ms ease-out 0s; 184 | transform: rotateZ(180deg); 185 | } 186 | .icon_sets--title[data-collapsed='true'] .triangle { 187 | transform: rotateZ(90deg); 188 | } 189 | .icon_sets--title[data-collapsed='true'] + .icon_sets--set { 190 | height: 0 !important; 191 | } 192 | 193 | .icon_sets--set { 194 | display: flex; 195 | flex-wrap: wrap; 196 | overflow: hidden; 197 | transition: height 200ms ease-out 0s; 198 | } 199 | .icon_sets--icon { 200 | user-select: none; 201 | transition: background 20ms ease-in 0s; 202 | cursor: pointer; 203 | display: flex; 204 | align-items: center; 205 | justify-content: center; 206 | border-radius: 3px; 207 | width: 32px; 208 | height: 32px; 209 | font-size: 24px; 210 | } 211 | .icon_sets--icon:hover { 212 | background: var(--theme--ui_interactive-hover); 213 | } 214 | .icon_sets--icon:active { 215 | background: var(--theme--ui_interactive-active); 216 | } 217 | .icon_sets--icon > img { 218 | max-width: 24px; 219 | max-height: 24px; 220 | } 221 | .icon_sets--sprite { 222 | width: 24px; 223 | height: 24px; 224 | background-size: 24px; 225 | background-repeat: no-repeat; 226 | pointer-events: none; 227 | } 228 | 229 | .icon_sets--divider { 230 | height: 1px; 231 | margin: 1em 0; 232 | border-bottom: 1px solid var(--theme--ui_divider); 233 | } 234 | .icon_sets--title[data-collapsed='true'] + .icon_sets--set + .icon_sets--divider { 235 | margin-top: 0.5em; 236 | } 237 | --------------------------------------------------------------------------------