diff --git a/CHANGELOG.md b/CHANGELOG.md index 49a8d6d..72b963d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,61 @@ **potential future features (not confirmed)** -- [groupy-like tabbing](https://www.npmjs.com/package/electron-tabs) -- [improved responsiveness](https://chrome.google.com/webstore/detail/notion%20%20-responsiveness-f/leadcilhbmibbkgbnjgmmnfgnnhmeddk) - [highlight/mark viewer](https://chrome.google.com/webstore/detail/notion%2B-mark-manager/hipgmnlpnimedfepbfbfiaobohhffcfc) - [advanced math editor](https://github.com/Manueloccorso/NotionMathEditor_BrowserExtension) -- re-orderable extensions + +### v0.10.0 (wip) + +a flexibility update. + +- new: mods can be reordered in the menu to control what order styling/scripts are added/executed in. + higher up on the list = higher priority of application = loaded last in order to override others. + (excluding the core, which though pinned to the top of the list is always loaded first so theming + variables can be modified.) +- new: in-page columns are disabled/wrapped and pages are wider when + the window is narrower than 600px for improved responsiveness. +- new: relaunch button in tray menu. +- new: a core mod option to make transitions snappy/0s. +- new: a core mod option for a default page id/url (all new windows will load it instead of the + normal "most recent" page). +- new: css variables for increasing line spacing/paragraph margins. +- new: patch the notion:// url scheme/protocol to work on linux. +- improved: menu will now respect integrated titlebar setting. +- improved: use keyup listeners instead of a globalShortcut for the enhancements menu toggle. +- bugfix: removed messenger emoji set as the provider no longer supports it. +- bugfix: remove shadow around light mode board headers + \+ minor text colour fixes for night shift theming. +- bugfix: properly detect/respond to `EACCES`/`EBUSY` errors. +- bugfix: night shift checks every interaction, + will respond to system changes without any manual changes. +- bugfix: toc blocks can have text colours. +- bugfix: bypass preview extension works with the back/forward keyboard shortcuts. +- bugfix: (maybe) fix csp issues under proxy. +- bugfix: remove focus mode footer from neutral theme. +- bugfix: improvements to the colour theming, particularly to make real- and fake-light/dark + modes (as applied by the night shift extension) look consistent. + relevant variables (assuming all are prefixed by `--theme_[dark|light]--`): + `box-shadow`, `box-shadow_strong`, `select_input`, and `ui-border` +- bugfix: font sizing applied to overlays/previews. +- bugfix: removed typo in variable name for brown text. +- bugfix: primary-colour text (mainly in "add a \_" popups) is now properly themed. +- bugfix: right-to-left extension applies to text in columns. +- tweak: sticky table/list rows. +- theme: "material ocean" = an oceanic colour palette. +- theme: "dracula" = a theme based on the popular dracula color palette + originally by zeno rocha and friends. +- extension: "tabs" = have multiple notion pages open in a single window. + +a fork of notion-deb-builder that does generate an app.asar has been created and is once again supported. + +// todo + +- new: menu shows theme conflicts. +- improved: default option for showing/hiding page properties. +- bugfix: night shift working on macOS. +- bugfix: windows are properly hidden/shown on macOS. +- extension: "tweaks" = common layout changes. +- update themes to new variables. ### v0.9.1 (2020-09-26) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 95f0667..ed22a83 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -20,7 +20,7 @@ _for examples of the stuff described below in action._ _at the moment, for ease of development and use (and security assurance), there's no way for users_ _to install their own modules. this means that testing modules requires_ -_[running a dev build of the enhancer](DOCUMENTATION.md#testing). a better system is in the works._ +_[running a dev build of the enhancer](https://github.com/dragonwocky/notion-enhancer/blob/master/CONTRIBUTING.md#testing). a better system is in the works._ _once your mod is working, open a pull request to add it to the enhancer!_ diff --git a/README.md b/README.md index 8a749d2..1f1f702 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ for support, join the [discord server](https://discord.gg/sFWPXtA). - the [official windows/mac releases](https://notion.so/desktop). - the arch linux AUR [notion-app](https://aur.archlinux.org/packages/notion-app/) package. - the linux [notion-app](https://github.com/jaredallard/notion-app) installer. +- [@haydn-jones](https://github.com/haydn-jones/)'s fork of the + linux [notion-deb-builder](https://github.com/haydn-jones/notion-deb-builder). (it can also be run from the wsl to apply enhancements to the windows app.) @@ -31,10 +33,23 @@ a chrome extension may be coming soon for web client support. during installation/removal, make sure no notion processes are running! (check your task manager.) -**win10, macOS** +**win10** -1. [install node.js](https://nodejs.org/en/download/) (_a computer restart may be required here._) -2. execute `npm i -g notion-enhancer` in the terminal/command prompt. +[install node.js](https://nodejs.org/en/download/) (_a computer restart may be required here_), +then execute `npm i -g notion-enhancer` in the command prompt. + +**macOS** + +[install node.js](https://nodejs.org/en/download/) (_a computer restart may be required here_), +then execute the following lines in the terminal: + +``` +sudo chmod -R a+wr /usr/local/lib/node_modules +sudo chmod -R a+wr /usr/local/bin +sudo chmod -R a+wr /Applications/Notion/Contents/Resources +sudo chmod -R a+wr /Applications/Notion.app/Contents/Resources +npm i -g notion-enhancer +``` **debian/ubuntu, chromeOS, WSL (to modify the win10 app)** @@ -43,7 +58,7 @@ execute the following lines in the terminal: ``` bash curl -sL https://deb.nodesource.com setup_current.x | sudo -E bash - sudo apt-get install -y nodejs -sudo npm i -g notion-enhancer +npm i -g notion-enhancer ``` **arch linux, manjaro** @@ -81,6 +96,14 @@ Options: -v, --version : display version number ``` +### faq + +**the themes aren't working?** + +if you pick a dark theme it will only be applied if notion is in dark mode, +and if you pick a light theme it will only work if notion is in light mode. +do `CMD/CTRL+SHIFT+L` to toggle between them. + **is this against notion's terms of service? can i get in trouble for using it?** definitely not! i contacted their support team to check, and the response was awesome: @@ -91,8 +114,16 @@ team to take to heart for future improvements." ## features -once applied, modules can be configured via the graphical menu, which is opened from -the tray/menubar icon or with `ALT+E`. +most of the enhancer's functionality is split into configurable enhancement modules, +but some basic improvements are built in by default: + +- the notion:// url scheme/protocol is patched to work on linux. +- in-page columns are disabled/wrapped and pages are wider when + the window is narrower than 600px for improved responsiveness. +- a tray/menubar icon: links relevant to the enhancer + buttons to manage notion windows. + +once applied, modules can be configured via the graphical menu, +which is opened from the tray/menubar icon or with `OPTION/ALT+E`. ![](https://user-images.githubusercontent.com/16874139/93692603-954fd980-fb38-11ea-9d52-82ac53449d33.png) @@ -164,7 +195,8 @@ on top of other windows even if it's not focused. **tags:** #extension -**description:** link files for small client-side tweaks. +**description:** link files for small client-side tweaks. (not sure how to do something? check out the +[tweaks](https://github.com/dragonwocky/notion-enhancer/blob/master/TWEAKS.md) collection.) **author**: [dragonwocky](https://github.com/dragonwocky/) @@ -343,8 +375,9 @@ the font you would like to use, or leave it blank to not change anything. a couple months after I ([@dragonwocky](https://github.com/dragonwocky/)) picked the project up, at first extending upon the original base and later moving to the javascript module system. -since then, various community members have helped out heaps - some listed as +the enhancer wouldn't be anything near to what it is now though without +interested community members testing, coding and ideating features - some are listed as [contributors](https://github.com/dragonwocky/notion-enhancer/graphs/contributors) here on github, -but many helping with code, feedback and testing on discord and in emails. +but many more have been helping out on discord and in emails. individual modules have their original authors attributed. diff --git a/TWEAKS.md b/TWEAKS.md index a15d9ed..aba6147 100644 --- a/TWEAKS.md +++ b/TWEAKS.md @@ -70,10 +70,34 @@ then use the 3 dots up in the top-right corner to remove them. +### sticky table/list row + +note: this will make the first row stick to the top of the screen when scrolling down. +to stick a specific row replace `:nth-child(2)` with `[data-block-id="ROW_BLOCK_ID_HERE"]`. + +does not apply to inline databases. + +```css +.notion-collection_view_page-block + .notion-page-block.notion-collection-item:nth-child(2) { + background: var(--theme--main); + z-index: 10; + position: sticky; + top: 0; +} +.notion-table-view + .notion-collection_view_page-block + .notion-page-block.notion-collection-item:nth-child(2) { + top: 32px; +} +``` + +![image](https://user-images.githubusercontent.com/16874139/94878420-a1c12400-04a0-11eb-8e74-2f01e2e696cd.png) + ### table columns below 100px **not recommended!** this may cause buggy viewing. -as it is a per-table-column style, unlike all others here, it must be prepended with the block ID and repeated for each column. +as it is a per-table-column style, unlike most others here, it must be prepended with the block ID and repeated for each column. to see how to do this, watch [this video](https://www.youtube.com/watch?v=6V7eqShm_4w). diff --git a/bin.js b/bin.js old mode 100644 new mode 100755 diff --git a/mods/bypass-preview/mod.js b/mods/bypass-preview/mod.js index 0a12995..2fe9f02 100644 --- a/mods/bypass-preview/mod.js +++ b/mods/bypass-preview/mod.js @@ -11,57 +11,43 @@ module.exports = { tags: ['extension'], name: 'bypass preview', desc: 'go straight to the normal full view when opening a page.', - version: '0.1.0', + version: '0.1.2', author: 'dragonwocky', hacks: { 'renderer/preload.js'(store, __exports) { document.addEventListener('readystatechange', (event) => { if (document.readyState !== 'complete') return false; - const attempt_interval = setInterval(enhance, 500); - function enhance() { - const notion_elem = document.querySelector('.notion-app-inner'); - if (!notion_elem) return; - clearInterval(attempt_interval); - const observer = new MutationObserver(handle); - observer.observe(notion_elem, { - childList: true, - subtree: true, - }); + let queue = []; + const observer = new MutationObserver((list, observer) => { + if (!queue.length) requestIdleCallback(() => handle(queue)); + queue.push(...list); + }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + }); - let pageHistory = []; - handle(); - function handle(list, observer) { - const pageID = (location.search - .slice(1) - .split('&') - .map((opt) => opt.split('=')) - .find((opt) => opt[0] === 'p') || [ - '', - ...location.pathname.split(/(-|\/)/g).reverse(), - ])[1], - preview = document.querySelector( - '.notion-peek-renderer [style*="height: 45px;"] a' - ); - if ( - pageID && - (!pageHistory[0] || - pageHistory[0][0] !== pageID || - pageHistory[0][1] !== !!preview) - ) { - if (preview) { - if ( - pageHistory[1] && - pageHistory[0][0] === pageID && - pageHistory[1][0] === pageID && - pageHistory[1][1] - ) { - document.querySelector('.notion-history-back-button').click(); - } else preview.click(); - } - // most recent is at start for easier access - pageHistory.unshift([pageID, !!preview]); - } - } + let lastPageID; + function handle(list) { + queue = []; + const pageID = (location.search + .slice(1) + .split('&') + .map((opt) => opt.split('=')) + .find((opt) => opt[0] === 'p') || [ + '', + ...location.pathname.split(/(-|\/)/g).reverse(), + ])[1], + preview = document.querySelector( + '.notion-peek-renderer [style*="height: 45px;"] a' + ); + if (!pageID) return; + if (preview) { + if (pageID === lastPageID) { + history.back(); + } else preview.click(); + } else lastPageID = pageID; } }); }, diff --git a/mods/bypass-preview/styles.css b/mods/bypass-preview/styles.css index 95f2308..176a1b9 100644 --- a/mods/bypass-preview/styles.css +++ b/mods/bypass-preview/styles.css @@ -5,5 +5,5 @@ */ .notion-peek-renderer { - opacity: 0; + display: none; } diff --git a/mods/cherrycola/mod.js b/mods/cherrycola/mod.js new file mode 100644 index 0000000..33e4c34 --- /dev/null +++ b/mods/cherrycola/mod.js @@ -0,0 +1,16 @@ +/* + * cherry cola + * (c) 2020 Alexa Baldon (https://github.com/runargs) + * under the MIT license + */ + +'use strict'; + +module.exports = { + id: 'ec5c4640-68d4-4d25-aefd-62c7e9737cfb', + tags: ['theme', 'dark'], + name: 'cherry cola', + desc: 'a delightfully plummy, cherry cola flavored theme.', + version: '0.1.0', + author: 'runargs', +}; diff --git a/mods/cherrycola/styles.css b/mods/cherrycola/styles.css new file mode 100644 index 0000000..893e96a --- /dev/null +++ b/mods/cherrycola/styles.css @@ -0,0 +1,150 @@ +/* + * cherry cola + * (c) 2020 Alexa Baldon (https://github.com/runargs) + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +:root { + --cola-main: #180915; + --cola-sec: #1d0919; + --cola-tet: #492341; + --cola-info: #9b6890; + --cola-accent: #bf799b; + --cola-gray: #8a8a8a; + --cola-brown: #755241; + --cola-orange: #e6846b; + --cola-yellow: #d7b56e; + --cola-green: #8f9b4f; + --cola-blue: #6ebdb7; + --cola-purple: #813d63; + --cola-pink: #d86f71; + --cola-red: #a33232; + + /* Main */ + --theme_dark--main: var(--cola-main); + --theme_dark--sidebar: var(--cola-sec); + --theme_dark--overlay: var(--cola-sec); + --theme_dark--dragarea: #210a1c; + --theme_dark--box-shadow: rgba(20, 0, 16, 0.2) 0px 0px 0px 1px, + rgba(20, 0, 16, 0.2) 0px 2px 4px; + --theme_dark--box-shadow_strong: rgba(20, 0, 16, 0.1) 0px 0px 0px 1px, + rgba(20, 0, 16, 0.2) 0px 3px 6px, rgba(20, 0, 16, 0.4) 0px 9px 24px; + + /* Scrollbar */ + --theme_dark--scrollbar: var(--cola-sec); + --theme_dark--scrollbar_hover: var(--cola-accent); + + /* Database */ + --theme_dark--card: var(--cola-sec); + --theme_dark--gallery: var(--cola-sec); + --theme_dark--select_input: var(--cola-tet); + --theme_dark--table-border: var(--cola-tet); + --theme_dark--ui-border: rgba(73, 35, 65, 0.7); + --theme_dark--interactive_hover: var(--cola-tet); + --theme_dark--button_close: var(--cola-accent); + + /* Select/hover/click */ + --theme_dark--selected: rgba(78, 32, 69, 0.5); + --theme_dark--primary: var(--cola-accent); + --theme_dark--primary_hover: var(--cola-accent); + --theme_dark--primary_click: var(--cola-sec); + --theme_dark--primary_indicator: var(--cola-accent); + + --theme_dark--option_active-background: var(--theme_dark--primary); + --theme_dark--option_hover-background: var(--theme_dark--primary_hover); + + /* Danger */ + --theme_dark--danger_text: #eb5757; + --theme_dark--danger_border: rgba(235, 87, 87, 0.5); + + /* Default text colors */ + --theme_dark--text: #ffffff; + --theme_dark--text_ui: var(--cola-info); + --theme_dark--text_ui_info: var(--cola-info); + + /* Text color options */ + --theme_dark--text_gray: var(--cola-gray); + --theme_dark--text_brown: var(--cola-brown); + --theme_dark--text_orange: var(--cola-orange); + --theme_dark--text_yellow: var(--cola-yellow); + --theme_dark--text_green: var(--cola-green); + --theme_dark--text_blue: var(--cola-blue); + --theme_dark--text_purple: var(--cola-purple); + --theme_dark--text_pink: var(--cola-pink); + --theme_dark--text_red: var(--cola-red); + + --theme_dark--select-text: var(--cola-main); + --theme_dark--select_gray: var(--cola-gray); + --theme_dark--select_brown: var(--cola-brown); + --theme_dark--select_brown-text: #ffffff; + --theme_dark--select_orange: var(--cola-orange); + --theme_dark--select_yellow: var(--cola-yellow); + --theme_dark--select_green: var(--cola-green); + --theme_dark--select_blue: var(--cola-blue); + --theme_dark--select_purple: var(--cola-purple); + --theme_dark--select_purple-text: #ffffff; + --theme_dark--select_pink: var(--cola-pink); + --theme_dark--select_red: var(--cola-red); + --theme_dark--select_red-text: #ffffff; + + --theme_dark--line-text: var(--cola-main); + --theme_dark--line_gray: var(--cola-gray); + --theme_dark--line_brown: var(--cola-brown); + --theme_dark--line_orange: var(--cola-orange); + --theme_dark--line_yellow: var(--cola-yellow); + --theme_dark--line_green: var(--cola-green); + --theme_dark--line_blue: var(--cola-blue); + --theme_dark--line_purple: var(--cola-purple); + --theme_dark--line_pink: var(--cola-pink); + --theme_dark--line_red: var(--cola-red); + + --theme_dark--bg-text: var(--theme_dark--select-text); + --theme_dark--bg_gray: var(--theme_dark--select_gray); + --theme_dark--bg_brown: var(--theme_dark--select_brown); + --theme_dark--bg_orange: var(--theme_dark--select_orange); + --theme_dark--bg_yellow: var(--theme_dark--select_yellow); + --theme_dark--bg_green: var(--theme_dark--select_green); + --theme_dark--bg_blue: var(--theme_dark--select_blue); + --theme_dark--bg_purple: var(--theme_dark--select_purple); + --theme_dark--bg_pink: var(--theme_dark--select_pink); + --theme_dark--bg_red: var(--theme_dark--select_red); + + /* Callout blocks */ + --theme_dark--callout-text: var(--theme_dark--line-text); + --theme_dark--callout_gray: var(--theme_dark--line_gray); + --theme_dark--callout_brown: var(--theme_dark--line_brown); + --theme_dark--callout_orange: var(--theme_dark--line_orange); + --theme_dark--callout_yellow: var(--theme_dark--line_yellow); + --theme_dark--callout_green: var(--theme_dark--line_green); + --theme_dark--callout_blue: var(--theme_dark--line_blue); + --theme_dark--callout_purple: var(--theme_dark--line_purple); + --theme_dark--callout_pink: var(--theme_dark--line_pink); + --theme_dark--callout_red: var(--theme_dark--line_red); + + /* Incline/code text */ + --theme_dark--code_inline-text: var(--cola-accent); + --theme_dark--code_inline-background: var(--cola-main); + --theme_dark--code-text: var(--theme_dark--text); + --theme_dark--code-background: var(--cola-sec); + --theme_dark--code_function: var(--theme_dark--text_blue); + --theme_dark--code_keyword: var(--theme_dark--text_pink); + --theme_dark--code_tag: var(--theme_dark--text_pink); + --theme_dark--code_operator: var(--theme_dark--text_yellow); + --theme_dark--code_important: var(--theme_dark--text_yellow); + --theme_dark--code_property: var(--theme_dark--text_pink); + --theme_dark--code_builtin: var(--theme_dark--text_yellow); + --theme_dark--code_attr-name: var(--theme_dark--text_yellow); + --theme_dark--code_comment: var(--theme_dark--text_gray); + --theme_dark--code_punctuation: var(--theme_dark--text_gray); + --theme_dark--code_doctype: var(--theme_dark--text_gray); + --theme_dark--code_number: var(--theme_dark--text_purple); + --theme_dark--code_string: var(--theme_dark--text_orange); + --theme_dark--code_attr-value: var(--theme_dark--text_orange); +} + +/* Quotations as serif */ +.notion-dark-theme .notion-quote-block { + font-family: Georgia, 'Times New Roman', Times, serif; + background-color: var(--cola-sec); +} diff --git a/mods/core/buttons.js b/mods/core/buttons.js index e93dddd..d132722 100644 --- a/mods/core/buttons.js +++ b/mods/core/buttons.js @@ -15,7 +15,7 @@ module.exports = (store) => { buttons = { element: helpers.createElement('
'), insert: [ - ...((store('mods', {})['72886371-dada-49a7-9afc-9f275ecf29d3'] || {}) + ...((store('mods')['72886371-dada-49a7-9afc-9f275ecf29d3'] || {}) .enabled ? ['alwaysontop'] : []), @@ -83,19 +83,19 @@ module.exports = (store) => { (async () => { for (let btn of buttons.insert) { - buttons.element.innerHTML += ``; } for (let btn of buttons.insert) { - document.querySelector(`.window-button#btn-${btn}`).onclick = + buttons.element.querySelector(`.window-button.btn-${btn}`).onclick = buttons.actions[btn]; } if (store().frameless && !store().tiling_mode && !is_mac) { window.addEventListener('resize', (event) => { Promise.resolve(buttons.icons.maximize()).then((icon) => { icon = icon.toString(); - const el = buttons.element.querySelector('#btn-maximize'); + const el = buttons.element.querySelector('.btn-maximize'); if (el.innerHTML != icon) el.innerHTML = icon; }); }); diff --git a/mods/core/client.js b/mods/core/client.js index 0aca69c..2d8d458 100644 --- a/mods/core/client.js +++ b/mods/core/client.js @@ -13,11 +13,61 @@ module.exports = (store, __exports) => { notionIpc = require(`${helpers.__notion.replace( /\\/g, '/' - )}/app/helpers/notionIpc.js`); + )}/app/helpers/notionIpc.js`), + { toKeyEvent } = require('keyboardevent-from-electron-accelerator'), + tabsEnabled = (store('mods')['e1692c29-475e-437b-b7ff-3eee872e1a42'] || {}) + .enabled; - // additional hotkeys document.defaultView.addEventListener('keyup', (event) => { - if (event.code === 'F5') location.reload(); + // additional hotkeys + if (event.key === 'F5') location.reload(); + // open menu on hotkey toggle + const hotkey = toKeyEvent(store().menu_toggle); + let triggered = true; + for (let prop in hotkey) + if (hotkey[prop] !== event[prop]) triggered = false; + if (triggered) electron.ipcRenderer.send('enhancer:open-menu'); + if (tabsEnabled) { + // switch between tabs via key modifier + const select_tab_modifier = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').select_modifier + ); + let triggered = true; + for (let prop in select_tab_modifier) + if (select_tab_modifier[prop] !== event[prop]) triggered = false; + if ( + triggered && + [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'ArrowRight', + 'ArrowLeft', + ].includes(event.key) + ) + electron.ipcRenderer.sendToHost('enhancer:select-tab', event.key); + // create/close tab keybindings + const new_tab_keybinding = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').new_tab + ); + triggered = true; + for (let prop in new_tab_keybinding) + if (new_tab_keybinding[prop] !== event[prop]) triggered = false; + if (triggered) electron.ipcRenderer.sendToHost('enhancer:new-tab'); + const close_tab_keybinding = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').close_tab + ); + triggered = true; + for (let prop in close_tab_keybinding) + if (close_tab_keybinding[prop] !== event[prop]) triggered = false; + if (triggered) electron.ipcRenderer.sendToHost('enhancer:close-tab'); + } }); const attempt_interval = setInterval(enhance, 500); @@ -29,18 +79,19 @@ module.exports = (store, __exports) => { return; clearInterval(attempt_interval); - // scrollbars + // toggleable styles if (store().smooth_scrollbars) document.body.classList.add('smooth-scrollbars'); + if (store().snappy_transitions) + document.body.classList.add('snappy-transitions'); // frameless - if (store().frameless && !store().tiling_mode) { + if (store().frameless && !store().tiling_mode && !tabsEnabled) { document.body.classList.add('frameless'); // draggable area - const dragarea = helpers.createElement( - '
' - ); - document.querySelector('.notion-topbar').prepend(dragarea); + document + .querySelector('.notion-topbar') + .prepend(helpers.createElement('
')); document.documentElement.style.setProperty( '--configured--dragarea_height', `${store().dragarea_height + 2}px` @@ -48,10 +99,12 @@ module.exports = (store, __exports) => { } // window buttons - const buttons = require('./buttons.js')(store); - document - .querySelector('.notion-topbar > div[style*="display: flex"]') - .appendChild(buttons.element); + if (!tabsEnabled) { + const buttons = require('./buttons.js')(store); + document + .querySelector('.notion-topbar > div[style*="display: flex"]') + .appendChild(buttons.element); + } document .querySelector('.notion-history-back-button') .parentElement.nextElementSibling.classList.add( @@ -66,7 +119,7 @@ module.exports = (store, __exports) => { document.querySelector('.notion-app-inner') ).getPropertyValue(prop); - // ctrl+f theming + // external theming document.defaultView.addEventListener('keydown', (event) => { if ((event.ctrlKey || event.metaKey) && event.key === 'f') { notionIpc.sendNotionToIndex('search:set-theme', { @@ -95,10 +148,9 @@ module.exports = (store, __exports) => { } }); - // enhancer menu function setThemeVars() { electron.ipcRenderer.send( - 'enhancer:set-theme-vars', + 'enhancer:set-menu-theme', [ '--theme--main', '--theme--sidebar', @@ -115,6 +167,7 @@ module.exports = (store, __exports) => { '--theme--interactive_hover-border', '--theme--button_close', '--theme--button_close-fill', + '--theme--selected', '--theme--primary', '--theme--primary_click', '--theme--option-color', @@ -132,40 +185,77 @@ module.exports = (store, __exports) => { '--theme--select_red', '--theme--line_text', '--theme--line_yellow', + '--theme--line_yellow-text', '--theme--line_green', + '--theme--line_green-text', '--theme--line_blue', + '--theme--line_blue-text', '--theme--line_red', + '--theme--line_red-text', '--theme--code_inline-text', '--theme--code_inline-background', ].map((rule) => [rule, getStyle(rule)]) ); - } - setThemeVars(); - const theme_observer = new MutationObserver(setThemeVars); - theme_observer.observe(document.querySelector('.notion-app-inner'), { - attributes: true, - }); - electron.ipcRenderer.on('enhancer:get-theme-vars', setThemeVars); - - const sidebar_observer = new MutationObserver(setSidebarWidth); - sidebar_observer.observe(document.querySelector('.notion-sidebar'), { - attributes: true, - }); - let sidebar_width; - function setSidebarWidth(list) { - if (!store().frameless && store().tiling_mode) return; - const new_sidebar_width = - list[0].target.style.height === 'auto' - ? '0px' - : list[0].target.style.width; - if (new_sidebar_width !== sidebar_width) { - sidebar_width = new_sidebar_width; + if (tabsEnabled) { electron.ipcRenderer.sendToHost( - 'enhancer:sidebar-width', - sidebar_width + 'enhancer:set-tab-theme', + [ + '--theme--main', + '--theme--dragarea', + '--theme--font_sans', + '--theme--table-border', + '--theme--interactive_hover', + '--theme--interactive_hover-border', + '--theme--button_close', + '--theme--button_close-fill', + '--theme--option_active-background', + '--theme--selected', + '--theme--text', + ].map((rule) => [rule, getStyle(rule)]) ); } } - setSidebarWidth([{ target: document.querySelector('.notion-sidebar') }]); + setThemeVars(); + new MutationObserver(setThemeVars).observe( + document.querySelector('.notion-app-inner'), + { attributes: true } + ); + electron.ipcRenderer.on('enhancer:get-menu-theme', setThemeVars); + + if (tabsEnabled) { + let tab_title = ''; + __electronApi.setWindowTitle = (title) => { + if (tab_title !== title) { + tab_title = title; + electron.ipcRenderer.sendToHost('enhancer:set-tab-title', title); + } + }; + __electronApi.openInNewWindow = (urlPath) => { + electron.ipcRenderer.sendToHost( + 'enhancer:new-tab', + `notion://www.notion.so${urlPath}` + ); + }; + } else if (store().frameless && !store().tiling_mode) { + let sidebar_width; + function setSidebarWidth(list) { + const new_sidebar_width = + list[0].target.style.height === 'auto' + ? '0px' + : list[0].target.style.width; + if (new_sidebar_width !== sidebar_width) { + sidebar_width = new_sidebar_width; + electron.ipcRenderer.sendToHost( + 'enhancer:sidebar-width', + sidebar_width + ); + } + } + new MutationObserver(setSidebarWidth).observe( + document.querySelector('.notion-sidebar'), + { attributes: true } + ); + setSidebarWidth([{ target: document.querySelector('.notion-sidebar') }]); + } } }; diff --git a/mods/core/create.js b/mods/core/createWindow.js similarity index 100% rename from mods/core/create.js rename to mods/core/createWindow.js diff --git a/mods/core/css/buttons.css b/mods/core/css/buttons.css index ac3cacc..70b8465 100644 --- a/mods/core/css/buttons.css +++ b/mods/core/css/buttons.css @@ -37,9 +37,7 @@ background: var(--theme--interactive_hover); box-shadow: 0 0 0 0.5px var(--theme--interactive_hover-border); } -.window-button#btn-close:hover { +.window-button.btn-close:hover { background: var(--theme--button_close); -} -.window-button#btn-close:hover svg line { - stroke: var(--theme--button_close-fill); + color: var(--theme--button_close-fill); } diff --git a/mods/core/css/menu.css b/mods/core/css/menu.css index 9bb5c5d..1f36cf7 100644 --- a/mods/core/css/menu.css +++ b/mods/core/css/menu.css @@ -50,11 +50,11 @@ body:not([style]) > * { body:not([style])::after { content: ''; position: absolute; - left: 44vw; - top: calc(50% - 7.5vw); - width: 10vw; - height: 10vw; - border: 4px solid rgb(34, 34, 34); + left: calc(50% - 13px); + top: calc(50% + 10px); + width: 18px; + height: 18px; + border: 4px solid rgb(34, 34, 34, 0.5); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; @@ -93,7 +93,7 @@ s { /* titlebar */ -#menu-titlebar::before { +#titlebar::before { content: ''; position: absolute; width: 100%; @@ -103,20 +103,18 @@ s { height: 2px; } -#menu-titlebar { +#titlebar { display: flex; -webkit-app-region: drag; -} -#menu-titlebar button { - -webkit-app-region: no-drag; -} -#menu-titlebar { background: var(--theme--dragarea); } -#menu-titlebar > .window-buttons-area { +#titlebar button { + -webkit-app-region: no-drag; +} +#titlebar .window-buttons-area { margin: 0.4em 0.4em 0.4em auto; } -#menu-titlebar > .window-buttons-area:empty { +#titlebar .window-buttons-area:empty { display: none; } @@ -148,6 +146,7 @@ s { color: var(--theme--select_red); } #alerts .error { + color: var(--theme--line_red-text); background: var(--theme--line_red); border-color: var(--theme--select_red); } @@ -155,6 +154,7 @@ s { color: var(--theme--select_yellow); } #alerts .warning { + color: var(--theme--line_yellow-text); background: var(--theme--line_yellow); border-color: var(--theme--select_yellow); } @@ -162,6 +162,7 @@ s { color: var(--theme--select_blue); } #alerts .info { + color: var(--theme--line_blue-text); background: var(--theme--line_blue); border-color: var(--theme--select_blue); } @@ -170,6 +171,7 @@ s { color: var(--theme--select_green); } #alerts .success { + color: var(--theme--line_green-text); background: var(--theme--line_green); border-color: var(--theme--select_green); } @@ -231,7 +233,7 @@ s { background: var(--theme--option-background); color: var(--theme--option-color); border-radius: 2px; - transition: color 200ms, background 200ms; + transition: color 200ms, background 200ms, opacity 200ms; user-select: none; } #search #tags > span:not(:last-child) { @@ -254,6 +256,9 @@ s { /* module meta */ +#modules { + position: relative; +} #modules section { background: var(--theme--sidebar); border: 1px solid var(--theme--table-border); @@ -589,3 +594,65 @@ s { height: 132.5px; } } + +/* draggable re-ordering of mods */ + +#draggable-toggle { + background: none; + border: none; + margin-top: 0.8em; + padding-left: 0; + cursor: pointer; + color: var(--theme--text_ui); +} + +[data-bolded] { + display: inline-flex; + flex-direction: column; +} +[data-bolded]::after { + content: attr(data-bolded); + height: 0; + visibility: hidden; + overflow: hidden; + user-select: none; + pointer-events: none; + font-weight: bold; +} + +.reorder #search #tags > span, +.reorder #search #tags > span:hover { + opacity: 0.7; + background: var(--theme--option-background); +} +.reorder #search #tags > .selected, +.reorder #search #tags > .selected:hover { + background: var(--tag_color, var(--theme--option_active-background)); +} + +.reorder #modules .dragged-over::after { + content: ''; + height: 0.25em; + width: 99%; + position: absolute; + margin: 0.3em 0; + opacity: 0.7; + background: var(--theme--selected); +} + +.reorder #modules .switch, +.reorder #modules .tags, +.reorder #modules .desc, +.reorder #modules .options, +.reorder #modules .author, +.reorder #modules .version { + display: none; +} +.reorder #modules label { + cursor: pointer; +} +.reorder #modules label::before { + content: '::'; + margin-right: 0.4em; + color: var(--theme--text_ui); +} diff --git a/mods/core/css/responsive.css b/mods/core/css/responsive.css new file mode 100644 index 0000000..0b92c03 --- /dev/null +++ b/mods/core/css/responsive.css @@ -0,0 +1,26 @@ +/* + * notion-enhancer + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +@media (max-width: 600px) { + .notion-column_list-block [style='display: flex;'] > div { + width: 100% !important; + } + .notion-column_list-block [style='display: flex;'] { + flex-direction: column !important; + } + + .notion-app-inner { + --theme_dark--page_normal-width: 100%; + --theme_dark--page-padding: calc(48px + env(safe-area-inset-left)); + --theme_light--page_normal-width: 100%; + --theme_light--page-padding: calc(48px + env(safe-area-inset-left)); + } +} + +.snappy-transitions * { + animation-duration: 0s !important; + transition-duration: 0s !important; +} diff --git a/mods/core/css/scrollbars.css b/mods/core/css/scrollbars.css index 2525fb5..2286f9a 100644 --- a/mods/core/css/scrollbars.css +++ b/mods/core/css/scrollbars.css @@ -11,6 +11,7 @@ .smooth-scrollbars ::-webkit-scrollbar { width: 8px; /* vertical */ height: 8px; /* horizontal */ + -webkit-app-region: no-drag; } .smooth-scrollbars ::-webkit-scrollbar-corner { background-color: transparent; /* overlap */ diff --git a/mods/core/css/tabs.css b/mods/core/css/tabs.css new file mode 100644 index 0000000..1e38668 --- /dev/null +++ b/mods/core/css/tabs.css @@ -0,0 +1,189 @@ +/* + * notion-enhancer + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +@import './buttons.css'; + +* { + box-sizing: border-box; + word-break: break-word; + text-decoration: none; + text-size-adjust: 100%; + font-family: var(--theme--font_sans) !important; + outline-color: var(--theme--table-border); +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +@keyframes tabSlide { + from { + width: 0; + } + to { + width: 8.5em; + } +} + +body:not([style*='--theme']):not(.error) > * { + opacity: 0; +} +body:not([style*='--theme']):not(.error)::after { + content: ''; + position: absolute; + left: calc(50% - 15px); + top: calc(50% + 10px); + width: 18px; + height: 18px; + border: 4px solid rgb(34, 34, 34, 0.5); + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} +body[style*='--theme']::after { + z-index: 1; + content: ''; + position: absolute; + left: calc(50% - 15px); + top: calc(50% + 10px); + width: 18px; + height: 18px; + opacity: 0.5; + border: 4px solid var(--theme--text); + border-top-color: transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +html, +body, +#root { + background: var(--theme--main) !important; + position: relative; + height: 100%; + margin: 0; +} +#root { + display: flex; + flex-direction: column; +} + +#titlebar::before { + content: ''; + position: absolute; + width: 100%; + -webkit-app-region: no-drag; + top: 0; + left: 0; + height: 2px; +} +#titlebar { + display: flex; + flex-shrink: 1; + user-select: none; + -webkit-app-region: drag; + background: var(--theme--dragarea); +} +#titlebar button { + color: var(--theme--text); + -webkit-app-region: no-drag; + border: none; + background: transparent; +} +#titlebar .window-buttons-area { + margin: 0.5em 0.55em 0.5em auto; +} +#titlebar .window-buttons-area:empty { + display: none; +} + +#open-enhancer-menu::before { + content: ''; + height: 1.25em; + width: 1.25em; + display: inline-block; + margin: auto 1em -0.25em 1em; + background-size: contain; + background-image: url('enhancement://core/icons/mac+linux.png'); + background-repeat: no-repeat; +} +#tabs { + margin-top: auto; +} +#tabs .tab { + display: inline-flex; + background: var(--theme--main); + border: none; + font-size: 1.15em; + padding: 0.2em 0.4em; + text-align: left; + border-bottom: 4px solid var(--theme--table-border); + opacity: 0.8; +} +#tabs .tab:first-child { + margin-top: 0.5em; +} +#tabs .tab:not(.new) span:not(.close) { + width: 8.5em; + margin-right: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#tabs .tab.slideIn span:not(.close) { + animation: tabSlide 100ms ease-in-out; +} +#tabs .tab.slideOut { + width: 0; + animation: tabSlide 100ms ease-in-out reverse; +} +#tabs .tab .close { + padding: 0 0.35em 0.1em 0.3em; + margin-left: auto; + font-weight: bold; +} +#tabs .tab.current { + opacity: 1; + background: var(--theme--selected); + border-bottom: 4px solid var(--theme--option_active-background); +} +#tabs .tab.new { + background: none; + border: none; + margin-left: -0.1em; +} +#tabs .tab.new span { + padding: 0 0.35em 0.1em 0.3em; + font-weight: bold; +} +#tabs .tab:hover { + opacity: 1; +} +#tabs .tab .close:hover, +#tabs .tab.new span:hover, +#titlebar .window-button:hover { + border-radius: 4px; + background: var(--theme--table-border); + box-shadow: 0 0 0 0.5px var(--theme--interactive_hover-border); +} +#titlebar .window-button.btn-close:hover { + background: var(--theme--button_close); + color: var(--theme--button_close-fill); +} +#tabs .tab.dragged-over { + box-shadow: inset 4px 0 0 0 var(--theme--selected); +} + +.notion { + z-index: 2; + width: 100%; + height: 100%; + display: none; +} diff --git a/mods/core/css/theme.css b/mods/core/css/theme.css index 052c6c0..dc1bb30 100644 --- a/mods/core/css/theme.css +++ b/mods/core/css/theme.css @@ -9,20 +9,25 @@ /** app **/ +.notion-body, +.notion-body.dark [style*='background: rgb(47, 52, 55)'], +.notion-body.dark [style*='background-color: rgb(47, 52, 55)'], +.notion-body:not(.dark) + .notion-light-theme + [style*='background: white']:not(.notion-help-button), +.notion-body:not(.dark) + .notion-dark-theme + [style*='background: white']:not(.notion-help-button):not([style*='box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;']), +.notion-body:not(.dark) [style*='background-color: white'] { + background: var(--theme--main) !important; +} +.notion-sidebar > div, .notion-body.dark [style*='background: rgb(55, 60, 63)'], .notion-body.dark [style*='background: rgb(120, 123, 123)'], .notion-body:not(.dark) [style*='background: rgb(247, 246, 243)'], .notion-body:not(.dark) [style*='background: rgb(223, 223, 222)'] { background: var(--theme--sidebar) !important; } -.notion-body, -.notion-body.dark [style*='background: rgb(47, 52, 55)'], -.notion-body.dark [style*='background-color: rgb(47, 52, 55)'], -.notion-body:not(.dark) - [style*='background: white']:not(.notion-help-button):not([style*='box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;']), -.notion-body:not(.dark) [style*='background-color: white'] { - background: var(--theme--main) !important; -} [style*='background: rgba(15, 15, 15, 0.6)'] { background: var(--theme--overlay) !important; } @@ -40,6 +45,12 @@ width: var(--theme--page_normal-width) !important; } +.notion-frame + [style*='padding-left: calc(96px + env(safe-area-inset-left)); padding-right: calc(96px + env(safe-area-inset-right));'] { + padding-left: var(--theme--page-padding) !important; + padding-right: var(--theme--page-padding) !important; +} + .notion-page-content [data-block-id][style*='max-width'] { max-width: 100% !important; } @@ -141,7 +152,8 @@ [style*='SFMono-Regular'] { font-family: var(--theme--font_code) !important; } -.notion-frame .notion-page-block div[placeholder='Untitled'] { +.notion-frame .notion-page-block div[placeholder='Untitled'], +.notion-overlay-container .notion-page-block div[placeholder='Untitled'] { font-size: calc( var(--theme--font_body-size) * (var(--theme--font_heading1-size) / 1em) ) !important; @@ -161,14 +173,19 @@ var(--theme--font_body-size) * (var(--theme--font_heading3-size) / 1em) ) !important; } -.notion-frame .notion-scroller.vertical.horizontal [style*='font-size: 14px'] { +.notion-frame .notion-scroller.vertical.horizontal [style*='font-size: 14px'], +.notion-overlay-container .notion-scroller.vertical [style*='font-size: 14px'] { font-size: var(--theme--font_label-size) !important; } -.notion-frame .notion-scroller.vertical.horizontal .notion-page-content { +.notion-frame .notion-scroller.vertical.horizontal .notion-page-content, +.notion-overlay-container .notion-scroller.vertical .notion-page-content { font-size: var(--theme--font_body-size) !important; } .notion-frame .notion-scroller.vertical.horizontal + .notion-page-content[style*='font-size: 14px'], +.notion-overlay-container + .notion-scroller.vertical .notion-page-content[style*='font-size: 14px'] { font-size: var(--theme--font_body-size_small) !important; } @@ -179,6 +196,13 @@ font-size: var(--theme--font_sidebar-size) !important; } +/** text-block readability **/ + +.notion-selectable.notion-text-block { + line-height: var(--theme--text-block_line-height) !important; + margin-top: var(--theme--text-block_margin-top) !important; +} + /** databases **/ .notion-body.dark [style*='background: rgb(63, 68, 71)'], @@ -189,7 +213,8 @@ > a[style*='background: white'], .notion-body:not(.dark) [style*='background: rgb(247, 246, 243)'], .notion-body:not(.dark) - [style*='box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;'] { + .notion-dark-theme + [style*='background: white'][style*='box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;'] { background: var(--theme--card) !important; } .notion-body.dark @@ -200,17 +225,36 @@ [style*='background: rgba(55, 53, 47, 0.024)'] { background: var(--theme--gallery) !important; } +.notion-body.dark .notion-scroller > [style*='rgb(55, 60, 63)'], +.notion-body:not(.dark) [style*='background: rgba(242, 241, 238, 0.6)'] { + background: var(--theme--select_input) !important; +} .notion-body.dark - [style*='box-shadow: rgba(15, 15, 15, 0.2) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 2px 4px'], -.notion-body:not(.dark) [style*='box-shadow: white -3px 0px 0px'] { - box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, - rgba(15, 15, 15, 0.1) 0px 2px 4px !important; + [style*='box-shadow: rgba(255, 255, 255, 0.14) 0px -1px inset;'], +.notion-body:not(.dark) + [style*='box-shadow: rgba(55, 53, 47, 0.16) 0px -1px 0px inset'] { + box-shadow: rgba(55, 53, 47, 0.16) 0px -1px inset !important; +} + +.notion-body.dark [style*='box-shadow: rgba(255, 255, 255, 0.07) 0px 1px 0px'], +.notion-body:not(.dark) + [style*='box-shadow: rgba(55, 53, 47, 0.09) 0px 1px 0px'] { + box-shadow: var(--theme--ui-border) 0px 1px 0px !important; +} +.notion-body.dark + [style*='box-shadow: rgba(255, 255, 255, 0.14) 0px 1px 0px inset'], +.notion-body:not(.dark) + [style*='box-shadow: rgba(55, 53, 47, 0.16) 0px 1px 0px inset'] { + box-shadow: var(--theme--table-border) 0px 1px 0px inset !important; } .notion-body.dark [style*='box-shadow: rgb(47, 52, 55) -3px 0px 0px'] { box-shadow: var(--theme--main) -3px 0px 0px !important; } +.notion-body:not(.dark) [style*='box-shadow: white -3px 0px 0px;'] { + box-shadow: none !important; +} .notion-body.dark [style*='box-shadow: rgb(47, 52, 55) -3px 0px 0px, rgba(255, 255, 255, 0.14) 0px 1px 0px'], .notion-body:not(.dark) @@ -218,7 +262,7 @@ .notion-body:not(.dark) [style*='box-shadow: rgba(255, 255, 255, 0.07) 0px -1px 0px'] { box-shadow: var(--theme--main) -3px 0px 0px, - var(--theme--table-border) 0px 1px 0px !important; + var(--theme--ui-border) 0px 1px 0px !important; } .notion-body.dark [style*='border-top: 1px solid rgba(255, 255, 255,'], @@ -246,7 +290,7 @@ .notion-body.dark [style*='box-shadow: rgba(55, 53, 47, 0.09) 0px -1px 0px'], .notion-body:not(.dark) [style*='box-shadow: rgba(55, 53, 47, 0.09) 0px -1px 0px'] { - box-shadow: var(--theme--table-border) 0px -1px 0px !important; + box-shadow: var(--theme--ui-border) 0px -1px 0px !important; } .notion-body.dark [style*='border-left: 1px solid rgba(255, 255, 255,'], .notion-body.dark @@ -255,12 +299,6 @@ .notion-body:not(.dark) [style*='border-left: 1px solid rgba(55, 53, 47,'] { border-left: 1px solid var(--theme--table-border) !important; } -.notion-body.dark - [style*='box-shadow: rgba(255, 255, 255, 0.14) 0px 1px 0px inset'], -.notion-body:not(.dark) - [style*='box-shadow: rgba(55, 53, 47, 0.16) 0px 1px 0px inset'] { - box-shadow: var(--theme--table-border) 0px 1px 0px inset !important; -} .notion-body.dark [style*='box-shadow: rgba(255, 255, 255, 0.14) 1px 0px 0px inset'], .notion-body:not(.dark) @@ -288,6 +326,7 @@ .notion-body.dark [style*='background: rgb(71, 76, 80)'], .notion-body.dark [style*='background: rgb(80, 85, 88)'], .notion-body.dark [style*='background: rgb(98, 102, 104)'], +.notion-body.dark [style*='height: 1px; background: rgba(255, 255, 255, 0.07)'], .notion-body:not(.dark) [style*='background: rgba(55, 53, 47,'], .notion-body:not(.dark) [style*='background: rgb(239, 239, 238)'], .notion-body:not(.dark) [style*='background: rgba(206, 205, 202, 0.5)'] { @@ -296,7 +335,8 @@ } /* normalise inline-table size */ -.notion-page-content .notion-collection_view-block { +.notion-page-content .notion-collection_view-block[style*=' width'], +.notion-page-content .notion-collection_view-block[style^='width'] { width: 100% !important; } .notion-page-content @@ -363,7 +403,8 @@ background: var(--theme--selected) !important; } -[style*=' color: rgb(46, 170, 220)'] { +[style*=' color: rgb(46, 170, 220)'], +[style^='color: rgb(46, 170, 220)'] { color: var(--theme--primary) !important; } [style*='fill: rgb(46, 170, 220)'] { @@ -387,6 +428,19 @@ background: var(--theme--primary_indicator) !important; } +.notion-body.dark + [style*='box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 3px 6px, rgba(15, 15, 15, 0.4) 0px 9px 24px'], +.notion-body:not(.dark) + [style*='box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px'] { + box-shadow: var(--theme--box-shadow_strong) !important; +} +.notion-body.dark + [style*='box-shadow: rgba(15, 15, 15, 0.2) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 2px 4px'], +.notion-body:not(.dark) + [style*='box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 2px 4px'] { + box-shadow: var(--theme--box-shadow) !important; +} + .notion-to_do-block > div [role='button']:hover, .notion-to_do-block > div [role='button']:hover .checkboxSquare, .notion-to_do-block > div [role='button']:hover .check { @@ -422,11 +476,13 @@ /** content colours **/ .notion-body, -.notion-page-content [style*='color: inherit;'], .notion-frame .notion-page-block, .notion-body.dark [style*=' color: rgba(255, 255, 255, 0.9)'], -.notion-body.dark [style*='color: rgba(255, 255, 255, 0.7)'], -.notion-body:not(.dark) [style*=' color: rgb(55, 53, 47);'] { +.notion-body.dark [style^='color: rgba(255, 255, 255, 0.9)'], +.notion-body.dark [style*=' color: rgba(255, 255, 255, 0.7)'], +.notion-body.dark [style^='color: rgba(255, 255, 255, 0.7)'], +.notion-body:not(.dark) [style*=' color: rgb(55, 53, 47);'], +.notion-body:not(.dark) [style^='color: rgb(55, 53, 47);'] { color: var(--theme--text) !important; } .notion-body.dark [style*='color: rgba(255, 255, 255, 0.6)'], @@ -434,18 +490,38 @@ .notion-body:not(.dark) [style*='color: rgba(25, 23, 17, 0.6)'] { color: var(--theme--text_ui) !important; } +::placeholder { + opacity: 1 !important; +} +::placeholder, +[style*='-webkit-text-fill-color:'], .notion-body.dark [style*='color: rgba(255, 255, 255, 0.4)'], -.notion-body:not(.dark) [style*='color: rgba(55, 53, 47, 0.4)'] { +.notion-body.dark [style*='color: rgba(255, 255, 255, 0.4)']::before, +.notion-body:not(.dark) [style*='color: rgba(55, 53, 47, 0.4)'], +.notion-body:not(.dark) [style*='color: rgba(55, 53, 47, 0.4)']::before { color: var(--theme--text_ui_info) !important; + -webkit-text-fill-color: var(--theme--text_ui_info) !important; } .notion-body.dark [style*='fill: rgb(202, 204, 206)'] { fill: var(--theme--text) !important; } -.notion-body.dark [style*='fill: rgba(202, 204, 206,'], + +.notion-body.dark [style*='fill: rgba(255, 255, 255, 0.6)'], .notion-body:not(.dark) [style*='fill: rgba(55, 53, 47, 0.8)'], -.notion-body:not(.dark) [style*='fill: rgba(55, 53, 47,'] { +.notion-body:not(.dark) [style*='fill: rgba(55, 53, 47, 0.6)'], +.notion-body:not(.dark) [style*='fill: rgba(25, 23, 17, 0.6)'] { fill: var(--theme--text_ui) !important; } +.notion-body.dark [style*='fill: rgba(202, 204, 206, 0.6)'], +.notion-body.dark [style*='fill: rgba(202, 204, 206, 0.4)'], +.notion-body:not(.dark) [style*='fill: rgba(55, 53, 47, 0.4)'], +.notion-body:not(.dark) [style*='fill: rgba(55, 53, 47, 0.3)'] { + fill: var(--theme--text_ui_info) !important; +} +.notion-body.dark [style*='border-color:rgba(255,255,255,0.4);opacity:0.7'], +.notion-body:not(.dark) [style*='border-color:rgba(55,53,47,0.4);opacity:0.7'] { + border-color: var(--theme--text_ui_info) !important; +} .notion-body.dark [style*='caret-color: rgba(255, 255, 255, 0.9)'], .notion-body:not(.dark) [style*='caret-color: rgb(55, 53, 47)'] { caret-color: var(--theme--text) !important; @@ -487,7 +563,7 @@ .notion-body:not(.dark) [style*='color: rgb(100, 71, 58); fill: rgb(100, 71, 58);'] { fill: var(--theme--text_brown) !important; - color: var(--theme--text_brown-text) !important; + color: var(--theme--text_brown) !important; } .notion-body.dark [style*='background: rgba(147, 114, 100, 0.5)'], .notion-body:not(.dark) [style*='background: rgba(140, 46, 0, 0.2)'] { diff --git a/mods/core/css/titlebar.css b/mods/core/css/titlebar.css index 0fd160f..9ab225c 100644 --- a/mods/core/css/titlebar.css +++ b/mods/core/css/titlebar.css @@ -8,19 +8,23 @@ @import './buttons.css'; .frameless .notion-topbar { - height: calc(var(--configured--dragarea_height, 10px) + 45px) !important; + height: calc(var(--configured--dragarea_height, 15px) + 45px) !important; } .frameless .window-dragarea { - height: var(--configured--dragarea_height, 10px); + height: var(--configured--dragarea_height, 15px); width: 100%; } .frameless .window-dragarea { background: var(--theme--dragarea); } +.frameless [style*='top: 10.4972px'] { + top: calc(10.4972px + var(--configured--dragarea_height, 15px)) !important; +} + @media (max-width: 760px) { .frameless .notion-topbar { - height: calc(var(--configured--dragarea_height, 10px) + 80px) !important; + height: calc(var(--configured--dragarea_height, 15px) + 80px) !important; } .frameless .notion-topbar > :nth-child(2) { height: 80px !important; diff --git a/mods/core/css/variables.css b/mods/core/css/variables.css index 225fdd3..b23f538 100644 --- a/mods/core/css/variables.css +++ b/mods/core/css/variables.css @@ -14,12 +14,17 @@ --theme_dark--sidebar: rgb(55, 60, 63); --theme_dark--overlay: rgba(15, 15, 15, 0.6); --theme_dark--dragarea: #272d2f; + --theme_dark--box-shadow: rgba(15, 15, 15, 0.2) 0px 0px 0px 1px, + rgba(15, 15, 15, 0.2) 0px 2px 4px; + --theme_dark--box-shadow_strong: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, + rgba(15, 15, 15, 0.2) 0px 3px 6px, rgba(15, 15, 15, 0.4) 0px 9px 24px; --theme_dark--page_normal-width: 900px; --theme_dark--page_full-width: 100%; + --theme_dark--page-padding: calc(96px + env(safe-area-inset-left)); + --theme_dark--page_banner-height: 30vh; --theme_dark--preview-width: 977px; --theme_dark--preview-padding: 8rem; --theme_dark--preview_banner-height: 20vh; - --theme_dark--page_banner-height: 30vh; --theme_dark--font_sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, 'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', @@ -40,6 +45,9 @@ --theme_dark--font_body-size_small: 14px; --theme_dark--font_code-size: 0.796875em; --theme_dark--font_sidebar-size: 14px; + + --theme_dark--text-block_line-height: 1.5; + --theme_dark--text-block_margin-top: 1px; --theme_dark--scrollbar: #505457; --theme_dark--scrollbar-border: transparent; @@ -47,7 +55,9 @@ --theme_dark--card: rgb(63, 68, 71); --theme_dark--gallery: rgba(255, 255, 255, 0.05); + --theme_dark--select_input: rgb(55, 60, 63); --theme_dark--table-border: rgba(255, 255, 255, 0.1); + --theme_dark--ui-border: rgba(255, 255, 255, 0.07); --theme_dark--interactive_hover: rgb(71, 76, 80); --theme_dark--interactive_hover-border: transparent; --theme_dark--button_close: #e81123; @@ -188,12 +198,17 @@ --theme_light--sidebar: rgb(247, 246, 243); --theme_light--overlay: rgba(15, 15, 15, 0.6); --theme_light--dragarea: rgba(55, 53, 47, 0.04); + --theme_light--box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, + rgba(15, 15, 15, 0.1) 0px 2px 4px; + --theme_light--box-shadow_strong: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, + rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; --theme_light--page_normal-width: 900px; --theme_light--page_full-width: 100%; + --theme_light--page-padding: calc(96px + env(safe-area-inset-left)); + --theme_light--page_banner-height: 30vh; --theme_light--preview-width: 977px; --theme_light--preview-padding: 8rem; --theme_light--preview_banner-height: 20vh; - --theme_light--page_banner-height: 30vh; --theme_light--font_sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, 'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', @@ -214,6 +229,9 @@ --theme_light--font_body-size_small: 14px; --theme_light--font_code-size: 0.796875em; --theme_light--font_sidebar-size: 14px; + + --theme_light--text-block_line-height: 1.5; + --theme_light--text-block_margin-top: 1px; --theme_light--scrollbar: #d9d8d6; --theme_light--scrollbar-border: #cacac8; @@ -221,7 +239,9 @@ --theme_light--card: rgb(247, 247, 247); --theme_light--gallery: rgba(55, 53, 47, 0.024); + --theme_light--select_input: rgba(242, 241, 238, 0.6); --theme_light--table-border: rgba(55, 53, 47, 0.16); + --theme_light--ui-border: rgba(55, 53, 47, 0.09); --theme_light--interactive_hover: rgb(239, 239, 239); --theme_light--interactive_hover-border: transparent; --theme_light--button_close: #e81123; @@ -245,6 +265,7 @@ --theme_light--text: rgb(55, 53, 47); --theme_light--text_ui: rgba(55, 53, 47, 0.6); + --theme_light--text_ui: rgba(55, 53, 47, 0.6); --theme_light--text_ui_info: rgba(55, 53, 47, 0.4); --theme_light--text_gray: rgb(155, 154, 151); @@ -362,12 +383,15 @@ --theme--sidebar: var(--theme_dark--sidebar); --theme--overlay: var(--theme_dark--overlay); --theme--dragarea: var(--theme_dark--dragarea); + --theme--box-shadow: var(--theme_dark--box-shadow); + --theme--box-shadow_strong: var(--theme_dark--box-shadow_strong); --theme--page_normal-width: var(--theme_dark--page_normal-width); --theme--page_full-width: var(--theme_dark--page_full-width); + --theme--page-padding: var(--theme_dark--page-padding); + --theme--page_banner-height: var(--theme_dark--page_banner-height); --theme--preview-width: var(--theme_dark--preview-width); --theme--preview-padding: var(--theme_dark--preview-padding); --theme--preview_banner-height: var(--theme_dark--preview_banner-height); - --theme--page_banner-height: var(--theme_dark--page_banner-height); --theme--font_sans: var(--theme_dark--font_sans); --theme--font_serif: var(--theme_dark--font_serif); --theme--font_mono: var(--theme_dark--font_mono); @@ -381,12 +405,16 @@ --theme--font_body-size_small: var(--theme_dark--font_body-size_small); --theme--font_code-size: var(--theme_dark--font_code-size); --theme--font_sidebar-size: var(--theme_dark--font_sidebar-size); + --theme--text-block_line-height: var(--theme_dark--text-block_line-height); + --theme--text-block_margin-top: var(--theme_dark--text-block_margin-top); --theme--scrollbar: var(--theme_dark--scrollbar); --theme--scrollbar-border: var(--theme_dark--scrollbar-border); --theme--scrollbar_hover: var(--theme_dark--scrollbar_hover); --theme--card: var(--theme_dark--card); --theme--gallery: var(--theme_dark--gallery); + --theme--select_input: var(--theme_dark--select_input); --theme--table-border: var(--theme_dark--table-border); + --theme--ui-border: var(--theme_dark--ui-border); --theme--interactive_hover: var(--theme_dark--interactive_hover); --theme--interactive_hover-border: var( --theme_dark--interactive_hover-border @@ -521,12 +549,15 @@ --theme--sidebar: var(--theme_light--sidebar); --theme--overlay: var(--theme_light--overlay); --theme--dragarea: var(--theme_light--dragarea); - --theme--page_normal-width: var(--theme_dark--page_normal-width); - --theme--page_full-width: var(--theme_dark--page_full-width); + --theme--box-shadow: var(--theme_light--box-shadow); + --theme--box-shadow_strong: var(--theme_light--box-shadow_strong); + --theme--page_normal-width: var(--theme_light--page_normal-width); + --theme--page_full-width: var(--theme_light--page_full-width); + --theme--page-padding: var(--theme_light--page-padding); + --theme--page_banner-height: var(--theme_light--page_banner-height); --theme--preview-width: var(--theme_light--preview-width); --theme--preview-padding: var(--theme_light--preview-padding); --theme--preview_banner-height: var(--theme_light--preview_banner-height); - --theme--page_banner-height: var(--theme_light--page_banner-height); --theme--font_sans: var(--theme_light--font_sans); --theme--font_serif: var(--theme_light--font_serif); --theme--font_mono: var(--theme_light--font_mono); @@ -540,12 +571,16 @@ --theme--font_body-size_small: var(--theme_light--font_body-size_small); --theme--font_code-size: var(--theme_light--font_code-size); --theme--font_sidebar-size: var(--theme_light--font_sidebar-size); + --theme--text-block_line-height: var(--theme_light--text-block_line-height); + --theme--text-block_margin-top: var(--theme_light--text-block_margin-top); --theme--scrollbar: var(--theme_light--scrollbar); --theme--scrollbar-border: var(--theme_light--scrollbar-border); --theme--scrollbar_hover: var(--theme_light--scrollbar_hover); --theme--card: var(--theme_light--card); --theme--gallery: var(--theme_light--gallery); + --theme--select_input: var(--theme_light--select_input); --theme--table-border: var(--theme_light--table-border); + --theme--ui-border: var(--theme_light--ui-border); --theme--interactive_hover: var(--theme_light--interactive_hover); --theme--interactive_hover-border: var( --theme_light--interactive_hover-border diff --git a/mods/core/menu.js b/mods/core/enhancerMenu.js similarity index 67% rename from mods/core/menu.js rename to mods/core/enhancerMenu.js index d839720..cdea17f 100644 --- a/mods/core/menu.js +++ b/mods/core/enhancerMenu.js @@ -7,57 +7,52 @@ 'use strict'; const store = require('../../pkg/store.js'), - { id } = require('./mod.js'), helpers = require('../../pkg/helpers.js'), fs = require('fs-extra'), path = require('path'), electron = require('electron'), - browser = electron.remote.getCurrentWindow(); + { toKeyEvent } = require('keyboardevent-from-electron-accelerator'); window['__start'] = async () => { + // mod loader + const modules = helpers.getEnhancements(); + if (modules.loaded.length) + console.info( + ` enhancements loaded: ${modules.loaded + .map((mod) => mod.name) + .join(', ')}.` + ); + if (modules.invalid.length) { + createAlert( + 'error', + `invalid mods found: ${modules.invalid + .map((mod) => `${mod}`) + .join(', ')}.` + ).append(); + } + const coreStore = (...args) => { + const mod = modules.loaded.find( + (m) => m.id === '0f0bf8b6-eae6-4273-b307-8fc43f2ee082' + ); + return !args.length + ? store(mod.id, mod.defaults) + : args.length === 1 && typeof args[0] === 'object' + ? store(mod.id, { ...mod.defaults, ...args[0] }) + : store(args[0], { ...mod.defaults, ...args[1] }); + }; + const buttons = require('./buttons.js')(() => ({ '72886371-dada-49a7-9afc-9f275ecf29d3': { enabled: (store('mods')['72886371-dada-49a7-9afc-9f275ecf29d3'] || {}) .enabled, }, - tiling_mode: store('0f0bf8b6-eae6-4273-b307-8fc43f2ee082').tiling_mode, - frameless: true, + tiling_mode: coreStore().tiling_mode, + frameless: coreStore().frameless, })); - document.querySelector('#menu-titlebar').appendChild(buttons.element); + document.querySelector('#titlebar').appendChild(buttons.element); - document.defaultView.addEventListener('keyup', (event) => { - if (event.code === 'F5') location.reload(); - const meta = - !(event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey; - if ( - meta && - document.activeElement.parentElement.id === 'tags' && - event.key === 'Enter' - ) - document.activeElement.click(); - if (document.activeElement.tagName.toLowerCase() === 'input') { - if (document.activeElement.type === 'checkbox' && event.key === 'Enter') - document.activeElement.checked = !document.activeElement.checked; - if ( - ['Escape', 'Enter'].includes(event.key) && - document.activeElement.type !== 'checkbox' && - (document.activeElement.parentElement.id !== 'search' || - event.key === 'Escape') - ) - document.activeElement.blur(); - } else if (meta && event.key === '/') - document.querySelector('#search > input').focus(); - if ( - (event.ctrlKey || event.metaKey) && - event.key === 'f' && - !event.altKey && - !event.shiftKey - ) - document.querySelector('#search > input').focus(); - }); - - electron.ipcRenderer.send('enhancer:get-theme-vars'); - electron.ipcRenderer.on('enhancer:set-theme-vars', (event, theme) => { + electron.ipcRenderer.send('enhancer:get-menu-theme'); + electron.ipcRenderer.on('enhancer:set-menu-theme', (event, theme) => { for (const style of theme) document.body.style.setProperty(style[0], style[1]); }); @@ -121,32 +116,52 @@ window['__start'] = async () => { ).prepend(); }); - // mod loader - const modules = helpers.getEnhancements(); - if (modules.loaded.length) - console.info( - ` enhancements loaded: ${modules.loaded - .map((mod) => mod.name) - .join(', ')}.` - ); - if (modules.invalid.length) { - createAlert( - 'error', - `invalid mods found: ${modules.invalid - .map((mod) => `${mod}`) - .join(', ')}.` - ).append(); - } - - // further-configuration popup const $popup = document.querySelector('#popup'); document.addEventListener('keyup', (event) => { + if (event.key === 'F5') location.reload(); + // further-configuration popup if ( $popup.classList.contains('visible') && - [13, 27].includes(event.keyCode) + ['Enter', 'Escape'].includes(event.key) ) $popup.classList.remove('visible'); + // close window on hotkey toggle + const hotkey = toKeyEvent(coreStore().menu_toggle); + let triggered = true; + for (let prop in hotkey) + if (hotkey[prop] !== event[prop]) triggered = false; + if (triggered || ((event.ctrlKey || event.metaKey) && event.key === 'w')) + electron.remote.getCurrentWindow().close(); + // focus search + const meta = + !(event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey; + if ( + meta && + document.activeElement.parentElement.id === 'tags' && + event.key === 'Enter' + ) + document.activeElement.click(); + if (document.activeElement.tagName.toLowerCase() === 'input') { + if (document.activeElement.type === 'checkbox' && event.key === 'Enter') + document.activeElement.checked = !document.activeElement.checked; + if ( + ['Escape', 'Enter'].includes(event.key) && + document.activeElement.type !== 'checkbox' && + (document.activeElement.parentElement.id !== 'search' || + event.key === 'Escape') + ) + document.activeElement.blur(); + } else if (meta && event.key === '/') + document.querySelector('#search > input').focus(); + if ( + (event.ctrlKey || event.metaKey) && + event.key === 'f' && + !event.altKey && + !event.shiftKey + ) + document.querySelector('#search > input').focus(); }); + let colorpicker_target = null; const $colorpicker = colorjoe .rgb('colorpicker') @@ -159,7 +174,6 @@ window['__start'] = async () => { store(colorpicker_target.id)[colorpicker_target.key] = color.css(); }) .update(); - document .querySelector('#colorpicker') .appendChild( @@ -184,12 +198,12 @@ window['__start'] = async () => { }; function innerText(elem) { let text = ''; - for (let node of elem.childNodes) { - if (node.nodeType === 3) text += node.textContent; - if (node.nodeType === 1) - text += ['text', 'number'].includes(node.type) - ? node.value - : innerText(node); + for (let $node of elem.childNodes) { + if ($node.nodeType === 3) text += $node.textContent; + if ($node.nodeType === 1) + text += ['text', 'number'].includes($node.type) + ? $node.value + : innerText($node); } return text; } @@ -197,13 +211,15 @@ window['__start'] = async () => { modules.loaded.forEach((mod) => { const $search_input = document.querySelector('#search > input'); if ( - (mod.elem.classList.contains('enabled') && !search_filters.enabled) || - (mod.elem.classList.contains('disabled') && !search_filters.disabled) || - !mod.tags.some((tag) => search_filters.tags.has(tag)) || - ($search_input.value && - !innerText(mod.elem) - .toLowerCase() - .includes($search_input.value.toLowerCase().trim())) + !document.body.classList.contains('reorder') && + ((mod.elem.classList.contains('enabled') && !search_filters.enabled) || + (mod.elem.classList.contains('disabled') && + !search_filters.disabled) || + !mod.tags.some((tag) => search_filters.tags.has(tag)) || + ($search_input.value && + !innerText(mod.elem) + .toLowerCase() + .includes($search_input.value.toLowerCase().trim()))) ) return (mod.elem.style.display = 'none'); mod.elem.style.display = 'block'; @@ -223,21 +239,20 @@ window['__start'] = async () => { ); document.querySelector('#tags').append(el); el.addEventListener('click', (event) => { - el.className = el.className === 'selected' ? '' : 'selected'; - onclick(el.className === 'selected'); + if (!document.body.classList.contains('reorder')) { + el.className = el.className === 'selected' ? '' : 'selected'; + onclick(el.className === 'selected'); + } }); return el; } - createTag( - 'enabled', - (state) => [(search_filters.enabled = state), search()] - // 'var(--theme--bg_green)' - ); - createTag( - 'disabled', - (state) => [(search_filters.disabled = state), search()] - // 'var(--theme--bg_red)' - ); + createTag('enabled', (state) => [ + ((search_filters.enabled = state), search()), + ]); + createTag('disabled', (state) => [ + (search_filters.disabled = state), + search(), + ]); for (let tag of search_filters.tags) createTag(`#${tag}`, (state) => [ state ? search_filters.tags.add(tag) : search_filters.tags.delete(tag), @@ -284,20 +299,20 @@ window['__start'] = async () => { return parsed; } - let modified_notice; + let $modified_notice; function modified() { - if (modified_notice) return; - modified_notice = createAlert( + if ($modified_notice) return; + $modified_notice = createAlert( 'info', `changes may not fully apply until app relaunch.` ); - modified_notice.el + $modified_notice.el .querySelector('[data-relaunch]') .addEventListener('click', (event) => { electron.remote.app.relaunch(); electron.remote.app.quit(); }); - modified_notice.append(); + $modified_notice.append(); } const file_icon = await fs.readFile( @@ -389,15 +404,8 @@ window['__start'] = async () => { } const $modules = document.querySelector('#modules'); - for (let mod of modules.loaded.sort((a, b) => - a.tags.includes('core') || - store('mods', { [a.id]: { pinned: false } }).pinned - ? -1 - : b.tags.includes('core') || - store('mods', { [b.id]: { pinned: false } }).pinned - ? 1 - : a.name.localeCompare(b.name) - )) { + + for (let mod of modules.loaded) { for (let fonts of mod.fonts || []) { document .querySelector('head') @@ -417,37 +425,41 @@ window['__start'] = async () => { avatar: `https://github.com/${mod.author}.png`, }; mod.elem = helpers.createElement(` -
+
-

${mod.name}` - : `class="toggle"> - ${mod.name}` + : `class="toggle"> + ` - }

-

${mod.tags - .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`)) - .join(' ')}

-
${markdown(mod.desc)}
-

- + ` + } +

${mod.tags + .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`)) + .join(' ')}

+
${markdown(mod.desc)}
+

+ ${author.name} - + v${mod.version} -

-
- ${ - mod.options && mod.options.length ? '
' : '' - } -
+

+ + ${ + mod.options && mod.options.length + ? '
' + : '' + } +
`); const $enable = mod.elem.querySelector(`#enable_${mod.id}`); if ($enable) @@ -500,12 +512,102 @@ window['__start'] = async () => { } $options.appendChild($opt); } - $modules.append(mod.elem); + if (mod.tags.includes('core')) $modules.append(mod.elem); } - document .querySelectorAll('input[type="checkbox"]') .forEach((checkbox) => checkbox.addEventListener('click', (event) => event.target.blur()) ); + + // draggable re-ordering + const draggable = { + state: 0, + tags: ['b', 'span'], + $toggle: document.querySelector('#draggable-toggle'), + list: modules.loaded + .filter((m) => !m.tags.includes('core')) + .map((m) => m.elem), + target: null, + render() { + draggable.target = null; + for (let $node of draggable.list) { + $node.draggable = false; + $modules.append($node); + } + }, + mouseover(event) { + if (!draggable.target && event.target.innerText) { + for (let $node of draggable.list) $node.draggable = false; + const $node = draggable.list.find( + (node) => node.innerText === event.target.innerText + ); + if ($node) $node.draggable = draggable.state; + } + }, + }; + document.addEventListener('dragstart', (event) => { + draggable.target = event.target; + event.target.style.opacity = 0.5; + }); + document.addEventListener('dragend', (event) => { + event.target.style.opacity = ''; + }); + document.addEventListener('dragover', (event) => { + event.preventDefault(); + document + .querySelectorAll('.dragged-over') + .forEach((el) => el.classList.remove('dragged-over')); + const $node = [ + draggable.list[0].previousElementSibling, + ...draggable.list, + ].find((node) => node.innerText === event.target.innerText); + if ($node) $node.classList.add('dragged-over'); + }); + document.addEventListener('drop', (event) => { + event.preventDefault(); + document + .querySelectorAll('.dragged-over') + .forEach((el) => el.classList.remove('dragged-over')); + if ( + draggable.target && + draggable.target.innerText !== event.target.innerText + ) { + const from = draggable.list.findIndex( + (node) => node.innerText === draggable.target.innerText + ), + to = + event.target.innerText === + draggable.list[0].previousElementSibling.innerText + ? 0 + : draggable.list.findIndex( + (node) => node.innerText === event.target.innerText + ) + 1; + if (to >= 0) { + draggable.list.splice( + to > from ? to - 1 : to, + 0, + draggable.list.splice(from, 1)[0] + ); + store('mods').priority = draggable.list.map((m) => m.id); + } + } + draggable.render(); + modified(); + }); + document.addEventListener('mouseover', draggable.mouseover); + draggable.render(); + draggable.$toggle.addEventListener('click', (event) => { + draggable.state = !draggable.state; + draggable.tags = draggable.tags.reverse(); + draggable.$toggle.innerHTML = ` + <${draggable.tags[0]} data-bolded="configure">configure | + <${draggable.tags[1]} data-bolded="reorder">reorder + `; + document.body.classList[draggable.state ? 'add' : 'remove']('reorder'); + $modules + .querySelectorAll('input') + .forEach((input) => (input.disabled = draggable.state)); + search(); + }); }; diff --git a/mods/core/icons/alwaysontop_off.svg b/mods/core/icons/alwaysontop_off.svg index 9fa0fb5..96afcf0 100644 --- a/mods/core/icons/alwaysontop_off.svg +++ b/mods/core/icons/alwaysontop_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/mods/core/icons/alwaysontop_on.svg b/mods/core/icons/alwaysontop_on.svg index 1c344f8..3fec5d5 100644 --- a/mods/core/icons/alwaysontop_on.svg +++ b/mods/core/icons/alwaysontop_on.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/mods/core/icons/maximize_off.svg b/mods/core/icons/maximize_off.svg index 7241f63..0bf56b0 100644 --- a/mods/core/icons/maximize_off.svg +++ b/mods/core/icons/maximize_off.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/mods/core/icons/maximize_on.svg b/mods/core/icons/maximize_on.svg index 681fdc9..af77a3e 100644 --- a/mods/core/icons/maximize_on.svg +++ b/mods/core/icons/maximize_on.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/mods/core/icons/minimize.svg b/mods/core/icons/minimize.svg index abcebb7..d179e14 100644 --- a/mods/core/icons/minimize.svg +++ b/mods/core/icons/minimize.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/mods/core/menu.html b/mods/core/menu.html index 83e6d60..56da84e 100644 --- a/mods/core/menu.html +++ b/mods/core/menu.html @@ -9,7 +9,7 @@ - +
diff --git a/mods/core/mod.js b/mods/core/mod.js index 2294d0a..1d3bd3f 100644 --- a/mods/core/mod.js +++ b/mods/core/mod.js @@ -56,6 +56,12 @@ module.exports = { type: 'toggle', value: true, }, + { + key: 'snappy_transitions', + label: 'snappy transitions', + type: 'toggle', + value: false, + }, { key: 'hotkey', label: 'window display hotkey:', @@ -68,10 +74,17 @@ module.exports = { type: 'input', value: 'Alt+E', }, + { + key: 'default_page', + label: 'open to default page id/url:', + type: 'input', + value: '', + }, ], hacks: { 'main/main.js': require('./tray.js'), - 'main/createWindow.js': require('./create.js'), + 'main/systemMenu.js': require('./systemMenu.js'), + 'main/createWindow.js': require('./createWindow.js'), 'renderer/index.js': require('./render.js'), 'renderer/preload.js': require('./client.js'), }, diff --git a/mods/core/render.js b/mods/core/render.js index 9b17f03..c2e731e 100644 --- a/mods/core/render.js +++ b/mods/core/render.js @@ -6,30 +6,1067 @@ 'use strict'; +const url = require('url'), + path = require('path'), + electron = require('electron'), + { __notion } = require('../../pkg/helpers.js'), + config = require(`${__notion}/app/config.js`), + constants = require(`${__notion}/app/shared/constants.js`), + notion_intl = require(`${__notion}/app/shared/notion-intl/index.js`), + notionIpc = require(`${__notion}/app/helpers/notionIpc.js`), + localizationHelper = require(`${__notion}/app/helpers/localizationHelper.js`), + koMessages = require(`${__notion}/app/i18n/ko_KR/messages.json`), + schemeHelpers = require(`${__notion}/app/shared/schemeHelpers.js`), + React = require(`${__notion}/app/node_modules/react/index.js`), + ReactDOM = require(`${__notion}/app/node_modules/react-dom/index.js`), + { toKeyEvent } = require('keyboardevent-from-electron-accelerator'); + +const insertCSP = ` + const csp = document.createElement('meta'); + csp.httpEquiv = 'Content-Security-Policy'; + csp.content = "script-src 'self' 'unsafe-inline' 'unsafe-eval' enhancement: https://gist.github.com https://apis.google.com https://api.amplitude.com https://widget.intercom.io https://js.intercomcdn.com https://logs-01.loggly.com https://cdn.segment.com https://analytics.pgncs.notion.so https://checkout.stripe.com https://embed.typeform.com https://admin.typeform.com https://platform.twitter.com https://cdn.syndication.twimg.com; connect-src 'self' https://msgstore.www.notion.so wss://msgstore.www.notion.so https://notion-emojis.s3-us-west-2.amazonaws.com https://s3-us-west-2.amazonaws.com https://s3.us-west-2.amazonaws.com https://notion-production-snapshots-2.s3.us-west-2.amazonaws.com https: http: https://api.amplitude.com https://api.embed.ly https://js.intercomcdn.com https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://logs-01.loggly.com https://api.segment.io https://api.pgncs.notion.so https://checkout.stripe.com https://cdn.contentful.com https://preview.contentful.com https://images.ctfassets.net https://api.unsplash.com https://boards-api.greenhouse.io; font-src 'self' data: enhancement: https: http:; img-src 'self' data: blob: https: https://platform.twitter.com https://syndication.twitter.com https://pbs.twimg.com https://ton.twimg.com; style-src 'self' 'unsafe-inline' enhancement: https: http:; frame-src https: http:; media-src https: http:"; + document.head.appendChild(csp); +`, + idToNotionURL = (id) => + `notion://www.notion.so/${ + url.parse(id).pathname.split('/').reverse()[0] || '' + }/${url.parse(id).search || ''}`; + module.exports = (store, __exports) => { - const __start = window['__start']; + if ((store('mods')['e1692c29-475e-437b-b7ff-3eee872e1a42'] || {}).enabled) { + class Index extends React.PureComponent { + constructor() { + super(...arguments); + this.state = { + error: false, + searching: false, + searchingPeekView: false, + zoomFactor: 1, + tabs: new Map([[0, { title: 'notion.so', open: true }]]), + slideIn: new Set(), + slideOut: new Set(), + }; + this.$titlebar = null; + this.$dragging = null; + this.views = { + active: null, + current: { + $el: () => this.views.html[this.views.current.id], + id: 0, + }, + react: {}, + html: {}, + loaded: {}, + tabs: {}, + }; + this.$search = null; + this.handleReload = () => { + this.setState({ error: false }); + Object.values(this.views.html).forEach(($notion) => { + if ($notion.isWaitingForResponse()) $notion.reload(); + }); + }; + this.communicateWithView = this.communicateWithView.bind(this); + this.startSearch = this.startSearch.bind(this); + this.stopSearch = this.stopSearch.bind(this); + this.nextSearch = this.nextSearch.bind(this); + this.prevSearch = this.prevSearch.bind(this); + this.clearSearch = this.clearSearch.bind(this); + this.doneSearch = this.doneSearch.bind(this); - window['__start'] = function () { - __start(); - const dragarea = document.querySelector( - '#root [style*="-webkit-app-region: drag"]' - ), - default_styles = dragarea.getAttribute('style'); + // draggable re-ordering + const getTab = ($el) => { + if ($el.tagName !== 'BUTTON') $el = $el.parentElement; + if ($el.innerText === '+') + return [null, document.querySelector('.tab.new')]; + const tab = Object.entries(this.views.tabs).find( + ([id, $node]) => $node === $el + ); + return tab ? [+tab[0], tab[1]] : []; + }; + document.addEventListener('dragstart', (event) => { + if (!this.$titlebar) return; + const tab = getTab(event.target); + this.$dragging = tab[0]; + event.dataTransfer.setData( + 'text', + JSON.stringify({ + target: electron.remote.getCurrentWindow().webContents.id, + tab: tab[0], + title: tab[1].children[0].innerText, + url: document.getElementById(getTab(event.target)[0]).src, + }) + ); + event.target.style.opacity = 0.5; + }); + document.addEventListener('dragend', (event) => { + if (!this.$titlebar) return; + event.target.style.opacity = ''; + document + .querySelectorAll('.dragged-over') + .forEach((el) => el.classList.remove('dragged-over')); + }); + document.addEventListener('dragover', (event) => { + if (!this.$titlebar) return; + event.preventDefault(); + document + .querySelectorAll('.dragged-over') + .forEach((el) => el.classList.remove('dragged-over')); + const tab = getTab(event.target)[1]; + if (tab) tab.classList.add('dragged-over'); + }); + document.addEventListener('drop', async (event) => { + event.preventDefault(); + const eventData = JSON.parse(event.dataTransfer.getData('text')); + if ( + eventData.target !== + electron.remote.getCurrentWindow().webContents.id + ) { + electron.ipcRenderer.send( + 'enhancer:close-tab', + eventData.target, + eventData.tab + ); + this.$dragging = await this.newTab( + eventData.url, + eventData.title, + false + ); + } + if (this.$titlebar) { + const from = getTab(this.views.tabs[+this.$dragging]), + to = getTab(event.target); + if (from[0] !== to[0]) { + if (to[1].classList.contains('new')) { + const list = new Map(this.state.tabs); + list.delete(from[0]); + list.set(from[0], this.state.tabs.get(from[0])); + this.setState({ tabs: list }); + } else { + const list = [...this.state.tabs], + fromIndex = list.findIndex( + ([id, { title, open }]) => id === from[0] + ), + toIndex = list.findIndex( + ([id, { title, open }]) => id === to[0] + ); + list.splice( + toIndex > fromIndex ? toIndex - 1 : toIndex, + 0, + list.splice(fromIndex, 1)[0] + ); + this.setState({ tabs: new Map(list) }); + } + } + this.$dragging = null; + } + }); + document.addEventListener('keyup', (event) => { + if (!electron.remote.getCurrentWindow().isFocused()) return; + // switch between tabs via key modifier + const select_tab_modifier = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').select_modifier + ); + let triggered = true; + for (let prop in select_tab_modifier) + if (select_tab_modifier[prop] !== event[prop]) triggered = false; + if ( + triggered && + [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'ArrowRight', + 'ArrowLeft', + ].includes(event.key) + ) + this.selectTab(event.key); + // create/close tab keybindings + const new_tab_keybinding = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').new_tab + ); + triggered = true; + for (let prop in new_tab_keybinding) + if (new_tab_keybinding[prop] !== event[prop]) triggered = false; + if (triggered) this.newTab(); + const close_tab_keybinding = toKeyEvent( + store('e1692c29-475e-437b-b7ff-3eee872e1a42').close_tab + ); + triggered = true; + for (let prop in close_tab_keybinding) + if (close_tab_keybinding[prop] !== event[prop]) triggered = false; + if (triggered && document.querySelector('.tab.current .close')) + document.querySelector('.tab.current .close').click(); + }); + electron.ipcRenderer.on('enhancer:close-tab', (event, tab) => { + this.closeTab(tab); + }); + } - if (store().tiling_mode) { - dragarea.style.display = 'none'; - } else { - document - .getElementById('notion') - .addEventListener('ipc-message', (event) => { - if (event.channel !== 'enhancer:sidebar-width') return; - dragarea.setAttribute( - 'style', - `${default_styles} top: 2px; height: ${ - store().dragarea_height - }px; left: ${event.args[0]};` + componentDidMount() { + const buttons = require('./buttons.js')(store); + this.$titlebar.appendChild(buttons.element); + this.loadListeners(); + + let electronWindow; + try { + electronWindow = electron.remote.getCurrentWindow(); + } catch (error) { + notionIpc.sendToMain('notion:log-error', { + level: 'error', + from: 'index', + type: 'GetCurrentWindowError', + error: error.message, + }); + } + if (!electronWindow) { + this.setState({ error: true }); + this.handleReload(); + return; + } + electronWindow.addListener('app-command', (e, cmd) => { + const webContents = this.views.current.$el().getWebContents(); + if (cmd === 'browser-backward' && webContents.canGoBack()) { + webContents.goBack(); + } else if (cmd === 'browser-forward' && webContents.canGoForward()) { + webContents.goForward(); + } + }); + electronWindow.addListener('swipe', (e, dir) => { + const webContents = this.views.current.$el().getWebContents(); + if (dir === 'left' && webContents.canGoBack()) { + webContents.goBack(); + } else if (dir === 'right' && webContents.canGoForward()) { + webContents.goForward(); + } + }); + } + + newTab(url = '', title = 'notion.so', animate = true) { + let id = 0; + const list = new Map(this.state.tabs); + while (this.state.tabs.get(id) && this.state.tabs.get(id).open) id++; + list.delete(id); + return this.openTab(id, { + state: list, + load: url || true, + title, + animate, + }); + } + openTab( + id, + { + state = new Map(this.state.tabs), + slideOut = new Set(this.state.slideOut), + load, + animate, + title = 'notion.so', + } = { + state: new Map(this.state.tabs), + slideOut: new Set(this.state.slideOut), + load: false, + title: 'notion.so', + animate: false, + } + ) { + return new Promise((res, rej) => { + if (!id && id !== 0) { + if (state.get(this.views.current.id).open) return res(id); + const currentIndex = [...state].findIndex( + ([id, { title, open }]) => id === this.views.current.id + ); + id = ([...state].find( + ([id, { title, open }], tabIndex) => + open && tabIndex > currentIndex + ) || [...state].find(([id, { title, open }]) => open))[0]; + } + const current_src = this.views.current.$el().src; + this.views.current.id = id; + this.setState( + { + tabs: state.set(id, { + title: state.get(id) ? state.get(id).title : title, + open: true, + }), + slideIn: animate + ? this.state.slideIn.add(id) + : this.state.slideIn, + slideOut: slideOut, + }, + async () => { + this.focusTab(); + new Promise((resolve, reject) => { + let attempt, + clear = () => { + clearInterval(attempt); + return true; + }; + attempt = setInterval(() => { + if (this.views.current.id !== id) return clear() && reject(); + if (document.body.contains(this.views.html[id])) + return clear() && resolve(); + }, 50); + }) + .then(() => { + if (load) { + this.views.html[id].style.opacity = '0'; + let unhide; + unhide = () => { + this.views.html[id].style.opacity = ''; + this.views.html[id].removeEventListener( + 'did-stop-loading', + unhide + ); + }; + this.views.html[id].addEventListener( + 'did-stop-loading', + unhide + ); + this.views.html[id].loadURL( + typeof load === 'string' + ? load + : store().default_page + ? idToNotionURL(store().default_page) + : current_src + ); + } + }) + .catch(() => { + // nothing + }) + .finally(() => { + setTimeout(() => { + this.setState( + { slideIn: new Set(), slideOut: new Set() }, + () => res(id) + ); + }, 150); + }); + } ); }); + } + closeTab(id) { + if ((!id && id !== 0) || !this.state.tabs.get(id)) return; + const list = new Map(this.state.tabs); + list.set(id, { ...list.get(id), open: false }); + if (![...list].filter(([id, { title, open }]) => open).length) + return electron.remote.getCurrentWindow().close(); + return this.openTab( + this.views.current.id === id ? null : this.views.current.id, + { state: list, slideOut: this.state.slideOut.add(id) } + ); + } + focusTab() { + if (this.views.active === this.views.current.id) return; + this.loadListeners(); + this.blurListeners(); + this.focusListeners(); + for (const id in this.views.loaded) { + if (this.views.loaded.hasOwnProperty(id) && this.views.loaded[id]) { + const selected = + id == this.views.current.id && + this.state.tabs.get(+id) && + this.state.tabs.get(+id).open; + this.views.loaded[id].style.display = selected ? 'flex' : 'none'; + if (selected) { + this.views.active = +id; + this.views.loaded[id].focus(); + const electronWindow = electron.remote.getCurrentWindow(); + if ( + electronWindow && + electronWindow.getTitle() !== this.state.tabs.get(+id).title + ) { + electronWindow.setTitle(this.state.tabs.get(+id).title); + } + } + } + } + } + selectTab(num) { + if (num === 'ArrowLeft') { + const prev = document.querySelector('.tab.current') + .previousElementSibling; + if (prev) prev.click(); + } else if (num === 'ArrowRight') { + const next = document.querySelector('.tab.current') + .nextElementSibling; + if (next && !next.classList.contains('new')) next.click(); + } else { + num = +num; + if (num == 9) { + document + .querySelector('#tabs') + .children[ + document.querySelector('#tabs').children.length - 2 + ].click(); + } else if ( + document.querySelector('#tabs').children[num - 1] && + document.querySelector('#tabs').children.length > num + ) { + document.querySelector('#tabs').children[num - 1].click(); + } + } + } + + communicateWithView(event) { + switch (event.channel) { + case 'enhancer:set-tab-theme': + for (const style of event.args[0]) + document.body.style.setProperty(style[0], style[1]); + break; + case 'enhancer:set-tab-title': + if (this.state.tabs.get(+event.target.id)) { + this.setState({ + tabs: new Map( + this.state.tabs.set(+event.target.id, { + ...this.state.tabs.get(+event.target.id), + title: event.args[0], + }) + ), + }); + const electronWindow = electron.remote.getCurrentWindow(); + if ( + event.target.id == this.views.current.id && + electronWindow.getTitle() !== event.args[0] + ) + electronWindow.setTitle(event.args[0]); + } + break; + case 'enhancer:select-tab': + this.selectTab(event.args[0]); + break; + case 'enhancer:new-tab': + this.newTab(event.args[0]); + break; + case 'enhancer:close-tab': + this.closeTab( + event.args[0] || event.args[0] === 0 + ? event.args[0] + : this.views.current.id + ); + break; + } + } + startSearch(isPeekView) { + this.setState( + { + searching: true, + searchingPeekView: isPeekView, + }, + () => { + if (document.activeElement instanceof HTMLElement) + document.activeElement.blur(); + this.$search.focus(); + notionIpc.sendIndexToSearch(this.$search, 'search:start'); + notionIpc.sendIndexToNotion(this.$search, 'search:started'); + } + ); + } + stopSearch() { + notionIpc.sendIndexToSearch(this.$search, 'search:reset'); + this.setState({ + searching: false, + }); + this.lastSearchQuery = undefined; + this.views.current + .$el() + .getWebContents() + .stopFindInPage('clearSelection'); + notionIpc.sendIndexToNotion(this.views.current.$el(), 'search:stopped'); + } + nextSearch(query) { + this.views.current + .$el() + .getWebContents() + .findInPage(query, { + forward: true, + findNext: query === this.lastSearchQuery, + }); + this.lastSearchQuery = query; + } + prevSearch(query) { + this.views.current + .$el() + .getWebContents() + .findInPage(query, { + forward: false, + findNext: query === this.lastSearchQuery, + }); + this.lastSearchQuery = query; + } + clearSearch() { + this.lastSearchQuery = undefined; + this.views.current + .$el() + .getWebContents() + .stopFindInPage('clearSelection'); + } + doneSearch() { + this.lastSearchQuery = undefined; + this.views.current + .$el() + .getWebContents() + .stopFindInPage('clearSelection'); + this.setState({ searching: false }); + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + this.views.current.$el().focus(); + notionIpc.sendIndexToNotion(this.views.current.$el(), 'search:stopped'); + } + focusListeners() { + if (!this.views.current.$el() || !this.$search) return; + this.views.current + .$el() + .addEventListener('ipc-message', this.communicateWithView); + notionIpc.receiveIndexFromNotion.addListener( + this.views.current.$el(), + 'search:start', + this.startSearch + ); + notionIpc.receiveIndexFromNotion.addListener( + this.views.current.$el(), + 'search:stop', + this.stopSearch + ); + notionIpc.receiveIndexFromSearch.addListener( + this.$search, + 'search:next', + this.nextSearch + ); + notionIpc.receiveIndexFromSearch.addListener( + this.$search, + 'search:prev', + this.prevSearch + ); + notionIpc.receiveIndexFromSearch.addListener( + this.$search, + 'search:clear', + this.clearSearch + ); + notionIpc.receiveIndexFromSearch.addListener( + this.$search, + 'search:stop', + this.doneSearch + ); + } + blurListeners() { + if (!this.views.current.$el() || !this.$search) return; + if (this.state.searching) this.stopSearch(); + this.views.current + .$el() + .removeEventListener('ipc-message', this.communicateWithView); + notionIpc.receiveIndexFromNotion.removeListener( + this.views.current.$el(), + 'search:start', + this.startSearch + ); + notionIpc.receiveIndexFromNotion.removeListener( + this.views.current.$el(), + 'search:stop', + this.stopSearch + ); + notionIpc.receiveIndexFromSearch.removeListener( + this.$search, + 'search:next', + this.nextSearch + ); + notionIpc.receiveIndexFromSearch.removeListener( + this.$search, + 'search:prev', + this.prevSearch + ); + notionIpc.receiveIndexFromSearch.removeListener( + this.$search, + 'search:clear', + this.clearSearch + ); + notionIpc.receiveIndexFromSearch.removeListener( + this.$search, + 'search:stop', + this.doneSearch + ); + } + loadListeners() { + if (!this.$search) return; + Object.entries(this.views.html) + .filter(([id, $notion]) => !this.views.loaded[id] && $notion) + .forEach(([id, $notion]) => { + if (!$notion) return; + this.views.loaded[id] = $notion; + $notion.addEventListener('did-fail-load', (error) => { + // logger.info('Failed to load:', error); + if ( + error.errorCode === -3 || + !error.validatedURL.startsWith( + schemeHelpers.getSchemeUrl({ + httpUrl: config.default.baseURL, + protocol: config.default.protocol, + }) + ) + ) { + return; + } + this.setState({ error: true }); + }); + $notion.addEventListener('dom-ready', () => { + $notion.getWebContents().executeJavaScript(insertCSP); + $notion + .getWebContents() + .addListener('found-in-page', (event, result) => { + const matches = result + ? { + count: result.matches, + index: result.activeMatchOrdinal, + } + : { count: 0, index: 0 }; + notionIpc.sendIndexToSearch( + this.$search, + 'search:result', + matches + ); + }); + notionIpc.proxyAllMainToNotion($notion); + }); + notionIpc.receiveIndexFromNotion.addListener( + $notion, + 'search:set-theme', + (theme) => { + notionIpc.sendIndexToSearch( + this.$search, + 'search:set-theme', + theme + ); + } + ); + notionIpc.receiveIndexFromNotion.addListener( + $notion, + 'zoom', + (zoomFactor) => { + $notion.getWebContents().setZoomFactor(zoomFactor); + this.$search.getWebContents().setZoomFactor(zoomFactor); + this.setState({ zoomFactor }); + } + ); + let electronWindow; + try { + electronWindow = electron.remote.getCurrentWindow(); + } catch (error) { + notionIpc.sendToMain('notion:log-error', { + level: 'error', + from: 'index', + type: 'GetCurrentWindowError', + error: error.message, + }); + } + if (!electronWindow) { + this.setState({ error: true }); + this.handleReload(); + return; + } + const sendFullScreenChangeEvent = () => { + notionIpc.sendIndexToNotion( + $notion, + 'notion:full-screen-changed' + ); + }; + electronWindow.addListener( + 'enter-full-screen', + sendFullScreenChangeEvent + ); + electronWindow.addListener( + 'leave-full-screen', + sendFullScreenChangeEvent + ); + electronWindow.addListener( + 'enter-html-full-screen', + sendFullScreenChangeEvent + ); + electronWindow.addListener( + 'leave-html-full-screen', + sendFullScreenChangeEvent + ); + }); + } + + renderTitlebar() { + return React.createElement( + 'header', + { + id: 'titlebar', + ref: ($titlebar) => { + this.$titlebar = $titlebar; + }, + }, + React.createElement('button', { + id: 'open-enhancer-menu', + onClick: (e) => { + electron.ipcRenderer.send('enhancer:open-menu'); + }, + }), + React.createElement( + 'div', + { id: 'tabs' }, + ...[...this.state.tabs] + .filter( + ([id, { title, open }]) => open || this.state.slideOut.has(id) + ) + .map(([id, { title, open }]) => + React.createElement( + 'button', + { + className: + 'tab' + + (id === this.views.current.id ? ' current' : '') + + (this.state.slideIn.has(id) ? ' slideIn' : '') + + (this.state.slideOut.has(id) ? ' slideOut' : ''), + draggable: true, + onClick: (e) => { + if (!e.target.classList.contains('close')) + this.openTab(id); + }, + onMouseUp: (e) => { + if (window.event.which === 2) this.closeTab(id); + }, + ref: ($tab) => { + this.views.tabs[id] = $tab; + }, + }, + React.createElement('span', {}, title), + React.createElement( + 'span', + { + className: 'close', + onClick: () => { + this.closeTab(id); + }, + }, + '×' + ) + ) + ), + React.createElement( + 'button', + { + className: 'tab new', + onClick: () => { + this.newTab(); + }, + }, + React.createElement('span', {}, '+') + ) + ) + ); + } + renderNotionContainer() { + this.views.react = Object.fromEntries( + [...this.state.tabs].map(([id, { title, open }]) => { + return [ + id, + this.views.react[id] || + React.createElement('webview', { + className: 'notion', + id, + ref: ($notion) => { + this.views.html[id] = $notion; + this.focusTab(); + }, + partition: constants.electronSessionPartition, + preload: path.resolve(`${__notion}/app/renderer/preload.js`), + src: this.props.notionUrl, + }), + ]; + }) + ); + return React.createElement( + 'div', + { + style: { + flexGrow: 1, + display: this.state.error ? 'none' : 'flex', + }, + }, + ...Object.values(this.views.react) + ); + } + renderSearchContainer() { + return React.createElement( + 'div', + { + style: { + position: 'fixed', + overflow: 'hidden', + pointerEvents: 'none', + padding: '0 20px', + top: + (this.state.searchingPeekView + ? 0 + : process.platform === 'darwin' + ? 37 + : 45) * this.state.zoomFactor, + right: (48 - 24) * this.state.zoomFactor, + width: 460 * this.state.zoomFactor, + height: 72 * this.state.zoomFactor, + zIndex: 99, + }, + }, + React.createElement('webview', { + id: 'search', + style: { + width: '100%', + height: '100%', + transition: `transform 70ms ease-${ + this.state.searching ? 'out' : 'in' + }`, + transform: `translateY(${this.state.searching ? '0' : '-100'}%)`, + pointerEvents: this.state.searching ? 'auto' : 'none', + }, + ref: ($search) => { + this.$search = $search; + this.focusTab(); + }, + partition: constants.electronSessionPartition, + preload: `file://${path.resolve( + `${__notion}/app/renderer/search.js` + )}`, + src: `file://${path.resolve( + `${__notion}/app/renderer/search.html` + )}`, + }) + ); + } + renderErrorContainer() { + return React.createElement( + 'div', + { + style: { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + display: this.state.error ? 'flex' : 'none', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: 24, + paddingBottom: '8vh', + }, + }, + React.createElement('img', { + style: { + width: 300, + maxWidth: '100%', + }, + src: './onboarding-offline.png', + }), + React.createElement( + 'div', + { + style: { + paddingTop: 16, + paddingBottom: 16, + textAlign: 'center', + lineHeight: 1.4, + fontSize: 17, + letterSpacing: '-0.01em', + color: '#424241', + fontWeight: 500, + }, + }, + React.createElement( + 'div', + null, + React.createElement(notion_intl.FormattedMessage, { + id: 'desktopLogin.offline.title', + defaultMessage: 'Welcome to Notion!', + values: { + strong: (...chunks) => + React.createElement('strong', null, chunks), + }, + }) + ), + React.createElement( + 'div', + null, + React.createElement(notion_intl.FormattedMessage, { + id: 'desktopLogin.offline.message', + defaultMessage: 'Connect to the internet to get started.', + }) + ) + ), + React.createElement( + 'div', + null, + React.createElement( + 'button', + { + style: { + background: '#fefaf8', + border: '1px solid #f1cbca', + boxSizing: 'border-box', + boxShadow: '0px 1px 2px rgba(0, 0, 0, 0.1)', + borderRadius: 3, + lineHeight: 'normal', + fontSize: 14, + fontWeight: 600, + letterSpacing: '-0.03em', + color: '#d8615c', + paddingLeft: 12, + paddingRight: 12, + height: 36, + }, + onClick: this.handleReload, + }, + React.createElement(notion_intl.FormattedMessage, { + id: + 'desktopLogin.offline.retryConnectingToInternetButton.label', + defaultMessage: 'Try again', + }) + ) + ) + ); + } + render() { + const notionLocale = localizationHelper.getNotionLocaleFromElectronLocale( + window.navigator.language + ), + result = React.createElement( + notion_intl.IntlProvider, + { + locale: notionLocale, + messages: + notionLocale === 'ko-KR' + ? koMessages + : { + 'desktopLogin.offline.title': + 'Welcome to Notion!', + 'desktopLogin.offline.message': + 'Connect to the internet to get started.', + 'desktopLogin.offline.retryConnectingToInternetButton.label': + 'Try again', + }, + }, + this.renderTitlebar(), + this.renderNotionContainer(), + this.renderSearchContainer(), + this.renderErrorContainer() + ); + document.body.classList[this.state.error ? 'add' : 'remove']('error'); + this.loadListeners(); + return result; + } } - }; + + window['__start'] = () => { + document.head.innerHTML += ``; + + // open menu on hotkey toggle + document.addEventListener('keyup', (event) => { + const hotkey = toKeyEvent(store().menu_toggle); + let triggered = true; + for (let prop in hotkey) + if (hotkey[prop] !== event[prop]) triggered = false; + if (triggered) electron.ipcRenderer.send('enhancer:open-menu'); + }); + + const parsed = url.parse(window.location.href, true), + notionUrl = + parsed.query.path || + (store().default_page + ? idToNotionURL(store().default_page) + : schemeHelpers.getSchemeUrl({ + httpUrl: config.default.baseURL, + protocol: config.default.protocol, + })); + delete parsed.search; + delete parsed.query; + const plainUrl = url.format(parsed); + window.history.replaceState(undefined, undefined, plainUrl); + + document.title = localizationHelper + .createIntlShape( + localizationHelper.getNotionLocaleFromElectronLocale( + window.navigator.language + ) + ) + .formatMessage( + notion_intl.defineMessages({ + documentTitle: { + id: 'desktop.documentTitle', + defaultMessage: 'Notion Desktop', + }, + }).documentTitle + ); + const $root = document.getElementById('root'); + ReactDOM.render( + React.createElement(Index, { notionUrl: notionUrl }), + $root + ); + }; + } else { + const __start = window['__start']; + window['__start'] = () => { + __start(); + + if (store().default_page) { + new Promise((res, rej) => { + let attempt; + attempt = setInterval(() => { + if ( + !document.getElementById('notion') || + !document.getElementById('notion').loadURL + ) + return; + clearInterval(attempt); + res(); + }, 50); + }).then(() => { + if ( + document.getElementById('notion').getAttribute('src') === + 'notion://www.notion.so' + ) { + document + .getElementById('notion') + .loadURL(idToNotionURL(store().default_page)); + } + }); + } + + const dragarea = document.querySelector( + '#root [style*="-webkit-app-region: drag"]' + ), + default_styles = dragarea.getAttribute('style'); + if (store().tiling_mode) { + dragarea.style.display = 'none'; + } else { + document + .getElementById('notion') + .addEventListener('ipc-message', (event) => { + if (event.channel !== 'enhancer:sidebar-width') return; + dragarea.setAttribute( + 'style', + `${default_styles} top: 2px; height: ${ + store().dragarea_height + }px; left: ${event.args[0]};` + ); + }); + + document.getElementById('notion').addEventListener('dom-ready', () => { + document + .getElementById('notion') + .getWebContents() + .executeJavaScript(insertCSP); + }); + } + }; + } }; diff --git a/mods/core/styles.css b/mods/core/styles.css index da69ff5..fbfe5be 100644 --- a/mods/core/styles.css +++ b/mods/core/styles.css @@ -8,3 +8,4 @@ @import './css/variables.css'; @import './css/scrollbars.css'; @import './css/titlebar.css'; +@import './css/responsive.css'; diff --git a/mods/core/systemMenu.js b/mods/core/systemMenu.js new file mode 100644 index 0000000..d301476 --- /dev/null +++ b/mods/core/systemMenu.js @@ -0,0 +1,476 @@ +/* + * notion-enhancer + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +'use strict'; + +module.exports = (store, __exports) => { + const electron = require('electron'), + fs = require('fs-extra'), + { __notion } = require('../../pkg/helpers.js'), + createWindow = require(`${__notion}/app/main/createWindow.js`), + config = require(`${__notion}/app/config.js`), + notion_intl = require(`${__notion}/app/shared/notion-intl/index.js`), + localizationHelper = require(`${__notion}/app/helpers/localizationHelper.js`), + isMac = process.platform === 'darwin', + // why is it inversed? i have no idea, but for some reason this is what works + tabsEnabled = !(store('mods')['e1692c29-475e-437b-b7ff-3eee872e1a42'] || {}) + .enabled, + menuMessages = notion_intl.defineMessages({ + fileMenuTitle: { + id: 'desktopTopbar.fileMenu.title', + defaultMessage: 'File', + }, + editMenuTitle: { + id: 'desktopTopbar.editMenu.title', + defaultMessage: 'Edit', + }, + viewMenuTitle: { + id: 'desktopTopbar.viewMenu.title', + defaultMessage: 'View', + }, + windowMenuTitle: { + id: 'desktopTopbar.windowMenu.title', + defaultMessage: 'Window', + }, + helpTitle: { + id: 'desktopTopbar.helpMenu.title', + defaultMessage: 'Help', + }, + newWindow: { + id: 'desktopTopbar.fileMenu.newWindow', + defaultMessage: 'New Window', + }, + closeWindow: { + id: 'desktopTopbar.fileMenu.close', + defaultMessage: 'Close Window', + }, + quit: { + id: 'desktopTopbar.fileMenu.quit', + defaultMessage: 'Exit', + }, + undo: { + id: 'desktopTopbar.editMenu.undo', + defaultMessage: 'Undo', + }, + redo: { + id: 'desktopTopbar.editMenu.redo', + defaultMessage: 'Redo', + }, + cut: { + id: 'desktopTopbar.editMenu.cut', + defaultMessage: 'Cut', + }, + copy: { + id: 'desktopTopbar.editMenu.copy', + defaultMessage: 'Copy', + }, + paste: { + id: 'desktopTopbar.editMenu.paste', + defaultMessage: 'Paste', + }, + selectAll: { + id: 'desktopTopbar.editMenu.selectAll', + defaultMessage: 'Select All', + }, + startSpeaking: { + id: 'desktopTopbar.editMenu.speech.startSpeaking', + defaultMessage: 'Start Speaking', + }, + stopSpeaking: { + id: 'desktopTopbar.editMenu.speech.stopSpeaking', + defaultMessage: 'Stop Speaking', + }, + speech: { + id: 'desktopTopbar.editMenu.speech', + defaultMessage: 'Speech', + }, + reload: { + id: 'desktopTopbar.viewMenu.reload', + defaultMessage: 'Reload', + }, + togglefullscreen: { + id: 'desktopTopbar.viewMenu.togglefullscreen', + defaultMessage: 'Toggle Full Screen', + }, + toggleDevTools: { + id: 'desktopTopbar.toggleDevTools', + defaultMessage: 'Toggle Developer Tools', + }, + toggleWindowDevTools: { + id: 'desktopTopbar.toggleWindowDevTools', + defaultMessage: 'Toggle Window Developer Tools', + }, + maximize: { + id: 'desktopTopbar.windowMenu.maximize', + defaultMessage: 'Maximize', + }, + minimize: { + id: 'desktopTopbar.windowMenu.minimize', + defaultMessage: 'Minimize', + }, + zoom: { + id: 'desktopTopbar.windowMenu.zoom', + defaultMessage: 'Zoom', + }, + front: { + id: 'desktopTopbar.windowMenu.front', + defaultMessage: 'Front', + }, + close: { + id: 'desktopTopbar.windowMenu.close', + defaultMessage: 'Close', + }, + help: { + id: 'desktopTopbar.helpMenu.openHelpAndSupport', + defaultMessage: 'Open Help & Support', + }, + reset: { + id: 'desktopTopbar.appMenu.resetAppAndClearData', + defaultMessage: 'Reset App & Clear Local Data', + }, + about: { + id: 'desktopTopbar.appMenu.about', + defaultMessage: 'About Notion', + }, + services: { + id: 'desktopTopbar.appMenu.services', + defaultMessage: 'Services', + }, + hide: { id: 'desktopTopbar.appMenu.hide', defaultMessage: 'Hide Notion' }, + hideOthers: { + id: 'desktopTopbar.appMenu.hideOthers', + defaultMessage: 'Hide Others', + }, + unhide: { + id: 'desktopTopbar.appMenu.unhide', + defaultMessage: 'Show All', + }, + quitMac: { id: 'desktopTopbar.appMenu.quit', defaultMessage: 'Quit' }, + }), + escapeAmpersand = (message) => message.replace(/&/g, '&&'); + __exports.setupSystemMenu = (locale) => { + const intl = localizationHelper.createIntlShape(locale), + fileMenu = { + role: 'fileMenu', + label: escapeAmpersand(intl.formatMessage(menuMessages.fileMenuTitle)), + submenu: isMac + ? [ + { + label: escapeAmpersand( + intl.formatMessage(menuMessages.newWindow) + ), + accelerator: 'CmdOrCtrl+Shift+N', + click: () => createWindow.createWindow(), + }, + ...(tabsEnabled + ? [ + { + role: 'close', + label: escapeAmpersand( + intl.formatMessage(menuMessages.closeWindow) + ), + }, + ] + : []), + ] + : [ + { + label: escapeAmpersand( + intl.formatMessage(menuMessages.newWindow) + ), + accelerator: 'CmdOrCtrl+Shift+N', + click: () => createWindow.createWindow(), + }, + ...(tabsEnabled + ? [ + { + role: 'quit', + label: escapeAmpersand( + intl.formatMessage(menuMessages.quit) + ), + }, + ] + : []), + ], + }, + editMenu = { + role: 'editMenu', + label: escapeAmpersand(intl.formatMessage(menuMessages.editMenuTitle)), + submenu: isMac + ? [ + { + role: 'undo', + label: escapeAmpersand(intl.formatMessage(menuMessages.undo)), + }, + { + role: 'redo', + label: escapeAmpersand(intl.formatMessage(menuMessages.redo)), + }, + { type: 'separator' }, + { + role: 'cut', + label: escapeAmpersand(intl.formatMessage(menuMessages.cut)), + }, + { + role: 'copy', + label: escapeAmpersand(intl.formatMessage(menuMessages.copy)), + }, + { + role: 'paste', + label: escapeAmpersand(intl.formatMessage(menuMessages.paste)), + }, + { + role: 'selectAll', + label: escapeAmpersand( + intl.formatMessage(menuMessages.selectAll) + ), + }, + { type: 'separator' }, + { + label: escapeAmpersand(intl.formatMessage(menuMessages.speech)), + submenu: [ + { + role: 'startSpeaking', + label: escapeAmpersand( + intl.formatMessage(menuMessages.startSpeaking) + ), + }, + { + role: 'stopSpeaking', + label: escapeAmpersand( + intl.formatMessage(menuMessages.stopSpeaking) + ), + }, + ], + }, + ] + : [ + { + role: 'undo', + label: escapeAmpersand(intl.formatMessage(menuMessages.undo)), + }, + { + role: 'redo', + label: escapeAmpersand(intl.formatMessage(menuMessages.redo)), + }, + { type: 'separator' }, + { + role: 'cut', + label: escapeAmpersand(intl.formatMessage(menuMessages.cut)), + }, + { + role: 'copy', + label: escapeAmpersand(intl.formatMessage(menuMessages.copy)), + }, + { + role: 'paste', + label: escapeAmpersand(intl.formatMessage(menuMessages.paste)), + }, + { type: 'separator' }, + { + role: 'selectAll', + label: escapeAmpersand( + intl.formatMessage(menuMessages.selectAll) + ), + }, + ], + }, + viewMenu = { + role: 'viewMenu', + label: escapeAmpersand(intl.formatMessage(menuMessages.viewMenuTitle)), + submenu: [ + { + label: escapeAmpersand(intl.formatMessage(menuMessages.reload)), + accelerator: 'CmdOrCtrl+R', + click() { + const focusedWebContents = electron.webContents.getFocusedWebContents(); + if (focusedWebContents) { + if (focusedWebContents.hostWebContents) { + for (const webContentsInstance of electron.webContents.getAllWebContents()) { + if ( + webContentsInstance.hostWebContents === + focusedWebContents.hostWebContents + ) { + webContentsInstance.reload(); + } + } + } else { + focusedWebContents.reload(); + } + } + }, + }, + { + label: escapeAmpersand( + intl.formatMessage(menuMessages.toggleDevTools) + ), + accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I', + click() { + let focusedWebContents = electron.webContents.getFocusedWebContents(); + if (focusedWebContents) { + const focusedWebContentsUrl = focusedWebContents.getURL(); + if ( + focusedWebContentsUrl.startsWith('file://') && + focusedWebContentsUrl.endsWith('/search.html') + ) { + const notionWebviewWebContents = electron.webContents + .getAllWebContents() + .find( + (webContentsInstance) => + webContentsInstance.hostWebContents === + focusedWebContents.hostWebContents && + webContentsInstance !== focusedWebContents + ); + if (notionWebviewWebContents) { + focusedWebContents = notionWebviewWebContents; + } + } + focusedWebContents.toggleDevTools(); + } + }, + }, + { + label: escapeAmpersand( + intl.formatMessage(menuMessages.toggleWindowDevTools) + ), + accelerator: isMac ? 'Shift+Alt+Command+I' : 'Alt+Ctrl+Shift+I', + visible: false, + click(menuItem, focusedWindow) { + if (focusedWindow) { + focusedWindow.webContents.toggleDevTools(); + } + }, + }, + { type: 'separator' }, + { + role: 'togglefullscreen', + label: escapeAmpersand( + intl.formatMessage(menuMessages.togglefullscreen) + ), + }, + ], + }, + windowMenu = { + role: 'windowMenu', + label: escapeAmpersand( + intl.formatMessage(menuMessages.windowMenuTitle) + ), + submenu: isMac + ? [ + { + role: 'minimize', + label: escapeAmpersand( + intl.formatMessage(menuMessages.minimize) + ), + }, + { + role: 'zoom', + label: escapeAmpersand(intl.formatMessage(menuMessages.zoom)), + }, + { type: 'separator' }, + { + role: 'front', + label: escapeAmpersand(intl.formatMessage(menuMessages.front)), + }, + ] + : [ + { + role: 'minimize', + label: escapeAmpersand( + intl.formatMessage(menuMessages.minimize) + ), + }, + { + label: escapeAmpersand( + intl.formatMessage(menuMessages.maximize) + ), + click(item, focusedWindow) { + if (focusedWindow) { + if (focusedWindow.isMaximized()) { + focusedWindow.unmaximize(); + } else { + focusedWindow.maximize(); + } + } + }, + }, + ...(tabsEnabled + ? [ + { + role: 'close', + label: escapeAmpersand( + intl.formatMessage(menuMessages.close) + ), + }, + ] + : []), + ], + }, + helpMenu = { + role: 'help', + label: escapeAmpersand(intl.formatMessage(menuMessages.helpTitle)), + submenu: [ + { + label: escapeAmpersand(intl.formatMessage(menuMessages.help)), + click() { + electron.shell.openExternal(config.default.baseURL + '/help'); + }, + }, + ], + }, + appMenu = { + role: 'appMenu', + submenu: [ + { + role: 'about', + label: escapeAmpersand(intl.formatMessage(menuMessages.about)), + }, + { type: 'separator' }, + { + label: escapeAmpersand(intl.formatMessage(menuMessages.reset)), + async click(item, focusedWindow) { + await fs.remove(electron.app.getPath('userData')); + electron.app.relaunch(); + electron.app.exit(); + }, + }, + { type: 'separator' }, + { + role: 'services', + label: escapeAmpersand(intl.formatMessage(menuMessages.services)), + }, + { type: 'separator' }, + { + role: 'hide', + label: escapeAmpersand(intl.formatMessage(menuMessages.hide)), + }, + { + role: 'hideOthers', + label: escapeAmpersand(intl.formatMessage(menuMessages.hideOthers)), + }, + { + role: 'unhide', + label: escapeAmpersand(intl.formatMessage(menuMessages.unhide)), + }, + ...(tabsEnabled + ? [ + { type: 'separator' }, + { + role: 'quit', + label: escapeAmpersand( + intl.formatMessage(menuMessages.quitMac) + ), + }, + ] + : []), + ], + }, + template = [fileMenu, editMenu, viewMenu, windowMenu, helpMenu]; + if (isMac) template.unshift(appMenu); + const menu = electron.Menu.buildFromTemplate(template); + electron.Menu.setApplicationMenu(menu); + }; +}; diff --git a/mods/core/tray.js b/mods/core/tray.js index 871cbdb..59e292a 100644 --- a/mods/core/tray.js +++ b/mods/core/tray.js @@ -17,6 +17,8 @@ module.exports = (store, __exports) => { helpers = require('../../pkg/helpers.js'); electron.app.on('ready', () => { + // tray + tray = new electron.Tray( is_win ? path.resolve(`${__dirname}/icons/windows.ico`) @@ -28,17 +30,27 @@ module.exports = (store, __exports) => { }) ); - electron.ipcMain.on('enhancer:set-theme-vars', (event, arg) => { - if (!enhancer_menu) return; - enhancer_menu.webContents.send('enhancer:set-theme-vars', arg); + // menu + + electron.ipcMain.on('enhancer:open-menu', (event, arg) => { + openEnhancerMenu(); }); - electron.ipcMain.on('enhancer:get-theme-vars', (event, arg) => { + electron.ipcMain.on('enhancer:set-menu-theme', (event, arg) => { + if (!enhancer_menu) return; + enhancer_menu.webContents.send('enhancer:set-menu-theme', arg); + }); + electron.ipcMain.on('enhancer:get-menu-theme', (event, arg) => { electron.webContents .getAllWebContents() .forEach((webContents) => - webContents.send('enhancer:get-theme-vars', arg) + webContents.send('enhancer:get-menu-theme', arg) ); }); + electron.ipcMain.on('enhancer:close-tab', (event, target, tab) => { + electron.webContents + .fromId(target) + .webContents.send('enhancer:close-tab', tab); + }); function calculateWindowPos(width, height) { const screen = electron.screen.getDisplayNearestPoint({ @@ -74,13 +86,13 @@ module.exports = (store, __exports) => { }; } - function openExtensionMenu() { + function openEnhancerMenu() { if (enhancer_menu) return enhancer_menu.show(); const window_state = require(`${helpers.__notion.replace( /\\/g, '/' )}/app/node_modules/electron-window-state/index.js`)({ - file: 'menu-windowstate.json', + file: 'menu.windowstate.json', path: helpers.__data, defaultWidth: 275, defaultHeight: 600, @@ -88,7 +100,7 @@ module.exports = (store, __exports) => { electron.shell.openExternal(JSON.stringify(window_state)); enhancer_menu = new electron.BrowserWindow({ show: true, - frame: false, + frame: !store().frameless, titleBarStyle: 'hiddenInset', x: window_state.x || @@ -99,7 +111,7 @@ module.exports = (store, __exports) => { width: window_state.width, height: window_state.height, webPreferences: { - preload: path.resolve(`${__dirname}/menu.js`), + preload: path.resolve(`${__dirname}/enhancerMenu.js`), nodeIntegration: true, session: electron.session.fromPartition('persist:notion'), }, @@ -109,8 +121,11 @@ module.exports = (store, __exports) => { window_state.saveState(enhancer_menu); enhancer_menu = null; }); + // enhancer_menu.webContents.openDevTools(); } + // tray + const contextMenu = electron.Menu.buildFromTemplate([ { type: 'normal', @@ -156,13 +171,13 @@ module.exports = (store, __exports) => { type: 'normal', label: 'Enhancements', accelerator: store().menu_toggle, - click: openExtensionMenu, + click: openEnhancerMenu, }, { type: 'normal', label: 'New Window', click: () => { - require('./create.js')( + require('./createWindow.js')( store, require(path.resolve( `${helpers.__notion}/app/main/createWindow.js` @@ -185,6 +200,13 @@ module.exports = (store, __exports) => { { type: 'separator', }, + { + label: 'Relaunch', + click: () => { + electron.app.relaunch(); + electron.app.quit(); + }, + }, { label: 'Quit', role: 'quit', @@ -193,16 +215,7 @@ module.exports = (store, __exports) => { tray.setContextMenu(contextMenu); tray.setToolTip('Notion'); - electron.globalShortcut.register(store().menu_toggle, () => { - if ( - electron.BrowserWindow.getAllWindows() - .filter((win) => win.getTitle() !== 'notion-enhancer menu') - .some((win) => win.isFocused()) - ) { - openExtensionMenu(); - } else if (enhancer_menu && enhancer_menu.isFocused()) - enhancer_menu.close(); - }); + // hotkey function showWindows() { const windows = electron.BrowserWindow.getAllWindows(); diff --git a/mods/custom-inserts/mod.js b/mods/custom-inserts/mod.js index 59c0ba0..8ae3aa4 100644 --- a/mods/custom-inserts/mod.js +++ b/mods/custom-inserts/mod.js @@ -12,8 +12,9 @@ module.exports = { id: 'b4b0aced-2059-43bf-8d1d-ccd757ee5ebb', tags: ['extension'], name: 'custom inserts', - desc: 'link files for small client-side tweaks.', - version: '0.1.2', + desc: `link files for small client-side tweaks. (not sure how to do something? check out the + [tweaks](https://github.com/dragonwocky/notion-enhancer/blob/master/TWEAKS.md) collection.)`, + version: '0.1.3', author: 'dragonwocky', options: [ { diff --git a/mods/dracula-theme/mod.js b/mods/dracula-theme/mod.js new file mode 100644 index 0000000..cd3668a --- /dev/null +++ b/mods/dracula-theme/mod.js @@ -0,0 +1,18 @@ +/* + * dracula-theme + * (c) 2020 u/mimi-shahzad + * (c) 2020 Dracula Theme + * under the MIT license + */ + +'use strict'; + +module.exports = { + id: '033bff54-50ba-4cec-bdc0-b2ca7e307086', + tags: ['theme', 'dark'], + name: 'dracula', + desc: + 'a theme based on the popular dracula color palette originally by zeno rocha and friends. ', + version: '0.1.0', + author: 'mimi-shahzad', +}; diff --git a/mods/dracula-theme/styles.css b/mods/dracula-theme/styles.css new file mode 100644 index 0000000..67e9a74 --- /dev/null +++ b/mods/dracula-theme/styles.css @@ -0,0 +1,186 @@ +/* + * dracula-theme + * (c) 2020 u/mimi-shahzad + * (c) 2020 Dracula Theme + * under the MIT license + */ + +:root { + --theme_dark--main: #282a36; + --theme_dark--sidebar: #282a36; + --theme_dark--overlay: #282a36; + --theme_dark--dragarea: #282a36; + + --theme_dark--font_sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', + Helvetica, 'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', + 'Segoe UI Symbol'; + + --theme_dark--scrollbar: #282a36; + --theme_dark--scrollbar_hover: #6272a4; + + --theme_dark--card: #6272a4; + --theme_dark--gallery: #282a36; + --theme_dark--table-border: #6272a4; + --theme_dark--interactive_hover: #282a36; + --theme_dark--button_close: #ff5555; + + --theme_dark--selected: #454158; + --theme_dark--primary: #bd93f9; + --theme_dark--primary_hover: #8be9fd; + --theme_dark--primary_click: #bd93f9; + --theme_dark--primary_indicator: #8be9fd; + + --theme_dark--option_active-background: var(--theme_dark--primary); + --theme_dark--option_hover-background: var(--theme_dark--primary_hover); + + --theme_dark--danger_text: #ff5555; + --theme_dark--danger_border: #ffb86c; + + --theme_dark--text: #f8f8f2; + --theme_dark--text_ui: #f8f8f2; + --theme_dark--text_ui_info: #f8f8f2; + + --theme_dark--text_gray: #454158; + --theme_dark--text_brown: #6272a4; + --theme_dark--text_orange: #ffb86c; + --theme_dark--text_yellow: #f1fa8c; + --theme_dark--text_green: #50fa7b; + --theme_dark--text_blue: #8be9fd; + --theme_dark--text_purple: #bd93f9; + --theme_dark--text_pink: #ff79c6; + --theme_dark--text_red: #ff5555; + + --theme_dark--select-text: #000000; + --theme_dark--select_gray: #454158; + --theme_dark--select_brown: #6272a4; + --theme_dark--select_orange: #ffb86c; + --theme_dark--select_yellow: #f1fa8c; + --theme_dark--select_green: #50fa7b; + --theme_dark--select_blue: #8be9fd; + --theme_dark--select_purple: #bd93f9; + --theme_dark--select_pink: #ff79c6; + --theme_dark--select_red: #ff5555; + + --theme_dark--bg-text: var(--theme_dark--select-text); + --theme_dark--bg_gray: var(--theme_dark--select_gray); + --theme_dark--bg_brown: var(--theme_dark--select_brown); + --theme_dark--bg_orange: var(--theme_dark--select_orange); + --theme_dark--bg_yellow: var(--theme_dark--select_yellow); + --theme_dark--bg_green: var(--theme_dark--select_green); + --theme_dark--bg_blue: var(--theme_dark--select_blue); + --theme_dark--bg_purple: var(--theme_dark--select_purple); + --theme_dark--bg_pink: var(--theme_dark--select_pink); + --theme_dark--bg_red: var(--theme_dark--select_red); + + --theme_dark--line-text: #000000; + --theme_dark--line_gray: #454158; + --theme_dark--line_brown: #6272a4; + --theme_dark--line_orange: #ffb86c; + --theme_dark--line_yellow: #f1fa8c; + --theme_dark--line_green: #50fa7b; + --theme_dark--line_blue: #8be9fd; + --theme_dark--line_purple: #bd93f9; + --theme_dark--line_pink: #ff79c6; + --theme_dark--line_red: #ff5555; + + --theme_dark--callout-text: var(--theme_dark--line-text); + --theme_dark--callout_gray: var(--theme_dark--line_gray); + --theme_dark--callout_brown: var(--theme_dark--line_brown); + --theme_dark--callout_orange: var(--theme_dark--line_orange); + --theme_dark--callout_yellow: var(--theme_dark--line_yellow); + --theme_dark--callout_green: var(--theme_dark--line_green); + --theme_dark--callout_blue: var(--theme_dark--line_blue); + --theme_dark--callout_purple: var(--theme_dark--line_purple); + --theme_dark--callout_pink: var(--theme_dark--line_pink); + --theme_dark--callout_red: var(--theme_dark--line_red); + + --theme_dark--code_inline-text: #50fa7b; + --theme_dark--code_inline-background: #44475a; + --theme_dark--code-text: var(--theme_dark--text); + --theme_dark--code-background: #44475a; + --theme_dark--code_function: var(--theme_dark--text_blue); + --theme_dark--code_keyword: var(--theme_dark--text_pink); + --theme_dark--code_tag: var(--theme_dark--text_pink); + --theme_dark--code_operator: var(--theme_dark--text_yellow); + --theme_dark--code_important: var(--theme_dark--text_yellow); + --theme_dark--code_property: var(--theme_dark--text_pink); + --theme_dark--code_builtin: var(--theme_dark--text_yellow); + --theme_dark--code_attr-name: var(--theme_dark--text_yellow); + --theme_dark--code_comment: var(--theme_dark--text_ui); + --theme_dark--code_punctuation: var(--theme_dark--text_gray); + --theme_dark--code_doctype: var(--theme_dark--text_gray); + --theme_dark--code_number: var(--theme_dark--text_purple); + --theme_dark--code_string: var(--theme_dark--text_orange); + --theme_dark--code_attr-value: var(--theme_dark--text_orange); +} + +.notion-dark-theme img[src*='/images/onboarding/use-case-note.png'], +.notion-dark-theme + img[src*='/images/onboarding/team-features-illustration.png'] { + filter: invert(1) !important; +} +.notion-dark-theme img[src*='/images/onboarding/checked.svg'] { + filter: hue-rotate(45deg) !important; +} +.notion-dark-theme + img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;'], +.notion-dark-theme + img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;'] { + transition: filter 0.4s ease !important; +} +.notion-dark-theme + img[style*='display: block; object-fit: cover; border-radius: 100%; width: 90px; height: 90px;']:hover, +.notion-dark-theme + img[style*='display: block; object-fit: cover; border-radius: 3px; width: 56.832px; height: 56.832px; transition: opacity 100ms ease-out 0s;']:hover { + filter: brightness(1.2); +} + +.notion-dark-theme + [style*='font-family: Fira Code, Menlo, Courier, monospace;'] { + filter: hue-rotate(170deg) !important; +} + +.notion-dark-theme + .notion-token-remove-button[role*='button'][tabindex*='0']:hover, +.notion-dark-theme .notion-record-icon { + background: transparent !important; +} + +.notion-dark-theme .notion-focusable:focus-within, +.notion-dark-theme .notion-to_do-block > div > div > div[style*='background:'], +.notion-dark-theme div[role='button'], +[style*='height: 4px;'] + > .notion-selectable.notion-collection_view_page-block + > *, +.notion-dark-theme .notion-calendar-view-day[style*='background: #282a36;'], +.DayPicker-Day--today, +.notion-dark-theme + .DayPicker:not(.DayPicker--interactionDisabled) + .DayPicker-Day--outside:hover, +.notion-dark-theme + .DayPicker:not(.DayPicker--interactionDisabled) + .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--value) + .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected, +.notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--start, +.notion-dark-theme .DayPicker-Day.DayPicker-Day--range.DayPicker-Day--end { + transition: color 0.4s ease, background 0.4s ease, box-shadow 0.4s ease !important; +} + +.notion-dark-theme [style*='background: #282a36;'], +.notion-dark-theme + [style*='background: rgb(80, 85, 88);'][style*='color: rgba(255, 255, 255, 0.7)'], +.notion-dark-theme + [style*='background: rgb(80, 85, 88);'][style*='width: 18px;'][style*='height: 18px;'], +.notion-dark-theme + [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;'], +.notion-dark-theme [style*='background: rgba(151, 154, 155, 0.5);'], +.notion-dark-theme [style*='background: rgba(147, 114, 100, 0.5)'], +.notion-dark-theme [style*='background: rgba(255, 163, 68, 0.5)'], +.notion-dark-theme [style*='background: rgba(255, 220, 73, 0.5)'], +.notion-dark-theme [style*='background: rgba(77, 171, 154, 0.5)'], +.notion-dark-theme [style*='background: rgba(82, 156, 202, 0.5)'], +.notion-dark-theme [style*='background: rgba(154, 109, 215, 0.5)'], +.notion-dark-theme [style*='background: rgba(226, 85, 161, 0.5)'], +.notion-dark-theme [style*='background: rgba(255, 115, 105, 0.5)'] { + box-shadow: 0 2px 4px rgb(0 0 0 / 66%) !important; +} diff --git a/mods/emoji-sets/mod.js b/mods/emoji-sets/mod.js index 289da8b..8ceb5b5 100644 --- a/mods/emoji-sets/mod.js +++ b/mods/emoji-sets/mod.js @@ -29,7 +29,6 @@ module.exports = { 'joypixels', 'openmoji', 'emojidex', - 'messenger', 'lg', 'htc', 'mozilla', @@ -109,6 +108,15 @@ module.exports = { el.style.display = 'none'; if (el.parentElement.getAttribute('contenteditable')) el.remove(); + } else if ( + el.previousElementSibling.matches( + 'span[role="image"][aria-label]' + ) + ) { + el.previousElementSibling.innerText = el.getAttribute( + 'alt' + ); + el.setAttribute('aria-label', el.getAttribute('alt')); } }); } else { @@ -116,11 +124,11 @@ module.exports = { el.parentElement .querySelectorAll('span[role="image"][aria-label]') .forEach((text) => text.remove()); + el.style.display = 'inline-block'; if (!el.style.background.includes(store().style)) { el.style.background = `url(https://emojicdn.elk.sh/${el.getAttribute( 'aria-label' )}?style=${store().style})`; - el.style.display = 'inline-block'; el.style.backgroundSize = 'contain'; el.style.backgroundRepeat = 'no-repeat'; el.style.opacity = 1; @@ -135,12 +143,3 @@ module.exports = { }, }, }; -// span[role="image"][aria-label] -/* */ - -//
-// 😀 -// 😀 -//
- -// ✝ diff --git a/mods/emoji-sets/styles.css b/mods/emoji-sets/styles.css deleted file mode 100644 index 600e758..0000000 --- a/mods/emoji-sets/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -.notion-emoji::after { - content: attr(aria-label, ''); - width: 1em; - height: 1em; -} diff --git a/mods/focus-mode/mod.js b/mods/focus-mode/mod.js index 3f61241..10994ac 100644 --- a/mods/focus-mode/mod.js +++ b/mods/focus-mode/mod.js @@ -13,6 +13,6 @@ module.exports = { name: 'focus mode', desc: 'hide the titlebar/menubar if the sidebar is closed (will be shown on hover).', - version: '0.1.0', + version: '0.1.1', author: 'arecsu', }; diff --git a/mods/focus-mode/styles.css b/mods/focus-mode/styles.css index 77f14b5..c71829f 100644 --- a/mods/focus-mode/styles.css +++ b/mods/focus-mode/styles.css @@ -5,6 +5,11 @@ * under the MIT license */ +/* add space at the bottom of the main frame when sidebar is hidden + * -- matches space at top for titlebar */ +.notion-dark-theme .notion-frame { + transition: height 100ms ease 0s; +} .notion-sidebar-container[style*='width: 0px;'] + .notion-frame { height: calc( 100% - (var(--configured--dragarea_height, 10px) + 45px) diff --git a/mods/material-ocean/mod.js b/mods/material-ocean/mod.js new file mode 100644 index 0000000..dd3eab5 --- /dev/null +++ b/mods/material-ocean/mod.js @@ -0,0 +1,16 @@ +/* + * material ocean + * (c) 2020 Abubakar Yagoub (https://blacksuan19.tk) + * under the MIT license + */ + +'use strict'; + +module.exports = { + id: '69e7ccb2-4aef-484c-876d-3de1b433d2b9', + tags: ['theme', 'dark'], + name: 'material ocean', + desc: 'an oceanic colour palette.', + version: '0.1.0', + author: 'blacksuan19', +}; diff --git a/mods/material-ocean/styles.css b/mods/material-ocean/styles.css new file mode 100644 index 0000000..cb6bc4f --- /dev/null +++ b/mods/material-ocean/styles.css @@ -0,0 +1,123 @@ +/* + * material ocean + * (c) 2020 Abubakar Yagoub (https://blacksuan19.tk) + * under the MIT license + */ + +:root { + --ocean-main: #0f111a; + --ocean-sec: #00010a; + --ocean-accent: #ff4151; + --ocean-gray: #e0e0e0; + --ocean-brown: #d8b6a6; + --ocean-orange: #fde3c0; + --ocean-yellow: #ebcb8b; + --ocean-green: #a3be8c; + --ocean-blue: #81a1c1; + --ocean-purple: #b48ead; + --ocean-pink: #ffc0cb; + --ocean-red: #bf616a; + + --theme_dark--main: var(--ocean-main); + --theme_dark--sidebar: var(--ocean-sec); + --theme_dark--overlay: var(--ocean-sec); + --theme_dark--dragarea: var(--ocean-sec); + + --theme_dark--scrollbar: var(--ocean-sec); + --theme_dark--scrollbar_hover: var(--ocean-accent); + + --theme_dark--card: var(--ocean-sec); + --theme_dark--gallery: var(--ocean-sec); + --theme_dark--table-border: rgba(255, 255, 255, 0.1); + --theme_dark--interactive_hover: var(--ocean-main); + --theme_dark--button_close: var(--ocean-accent); + + --theme_dark--selected: rgba(255, 65, 81, 0.2); + --theme_dark--primary: var(--ocean-accent); + --theme_dark--primary_hover: var(--ocean-accent); + --theme_dark--primary_click: var(--ocean-sec); + --theme_dark--primary_indicator: var(--ocean-accent); + + --theme_dark--option_active-background: var(--theme_dark--primary); + --theme_dark--option_hover-background: var(--theme_dark--primary_hover); + + --theme_dark--danger_text: #eb5757; + --theme_dark--danger_border: rgba(235, 87, 87, 0.5); + + --theme_dark--text: #ffffff; + --theme_dark--text_ui: var(--ocean-gray); + --theme_dark--text_ui_info: var(--ocean-gray); + + --theme_dark--text_gray: var(--ocean-gray); + --theme_dark--text_brown: var(--ocean-brown); + --theme_dark--text_orange: var(--ocean-orange); + --theme_dark--text_yellow: var(--ocean-yellow); + --theme_dark--text_green: var(--ocean-green); + --theme_dark--text_blue: var(--ocean-blue); + --theme_dark--text_purple: var(--ocean-purple); + --theme_dark--text_pink: var(--ocean-pink); + --theme_dark--text_red: var(--ocean-red); + + --theme_dark--select-text: var(--ocean-main); + --theme_dark--select_gray: var(--ocean-gray); + --theme_dark--select_brown: var(--ocean-brown); + --theme_dark--select_orange: var(--ocean-orange); + --theme_dark--select_yellow: var(--ocean-yellow); + --theme_dark--select_green: var(--ocean-green); + --theme_dark--select_blue: var(--ocean-blue); + --theme_dark--select_purple: var(--ocean-purple); + --theme_dark--select_pink: var(--ocean-pink); + --theme_dark--select_red: var(--ocean-red); + + --theme_dark--line-text: var(--ocean-main); + --theme_dark--line_gray: #e0e0e089; + --theme_dark--line_brown: #d8b6a692; + --theme_dark--line_orange: #fde3c09f; + --theme_dark--line_yellow: #ffe6a6ad; + --theme_dark--line_green: #a3be8ca3; + --theme_dark--line_blue: #81a1c1a3; + --theme_dark--line_purple: #b48eada8; + --theme_dark--line_pink: #ffc0cbb1; + --theme_dark--line_red: #bf616a9e; + + --theme_dark--bg-text: var(--theme_dark--select-text); + --theme_dark--bg_gray: var(--theme_dark--select_gray); + --theme_dark--bg_brown: var(--theme_dark--select_brown); + --theme_dark--bg_orange: var(--theme_dark--select_orange); + --theme_dark--bg_yellow: var(--theme_dark--select_yellow); + --theme_dark--bg_green: var(--theme_dark--select_green); + --theme_dark--bg_blue: var(--theme_dark--select_blue); + --theme_dark--bg_purple: var(--theme_dark--select_purple); + --theme_dark--bg_pink: var(--theme_dark--select_pink); + --theme_dark--bg_red: var(--theme_dark--select_red); + + --theme_dark--callout-text: var(--theme_dark--line-text); + --theme_dark--callout_gray: var(--theme_dark--line_gray); + --theme_dark--callout_brown: var(--theme_dark--line_brown); + --theme_dark--callout_orange: var(--theme_dark--line_orange); + --theme_dark--callout_yellow: var(--theme_dark--line_yellow); + --theme_dark--callout_green: var(--theme_dark--line_green); + --theme_dark--callout_blue: var(--theme_dark--line_blue); + --theme_dark--callout_purple: var(--theme_dark--line_purple); + --theme_dark--callout_pink: var(--theme_dark--line_pink); + --theme_dark--callout_red: var(--theme_dark--line_red); + + --theme_dark--code_inline-text: #b3f5c8; + --theme_dark--code_inline-background: var(--ocean-sec); + --theme_dark--code-text: var(--theme_dark--text); + --theme_dark--code-background: var(--ocean-sec); + --theme_dark--code_function: var(--theme_dark--text_blue); + --theme_dark--code_keyword: var(--theme_dark--text_pink); + --theme_dark--code_tag: var(--theme_dark--text_pink); + --theme_dark--code_operator: var(--theme_dark--text_yellow); + --theme_dark--code_important: var(--theme_dark--text_yellow); + --theme_dark--code_property: var(--theme_dark--text_pink); + --theme_dark--code_builtin: var(--theme_dark--text_yellow); + --theme_dark--code_attr-name: var(--theme_dark--text_yellow); + --theme_dark--code_comment: var(--theme_dark--text_gray); + --theme_dark--code_punctuation: var(--theme_dark--text_gray); + --theme_dark--code_doctype: var(--theme_dark--text_gray); + --theme_dark--code_number: var(--theme_dark--text_purple); + --theme_dark--code_string: var(--theme_dark--text_orange); + --theme_dark--code_attr-value: var(--theme_dark--text_orange); +} diff --git a/mods/neutral/mod.js b/mods/neutral/mod.js index 8f1533a..a695dc9 100644 --- a/mods/neutral/mod.js +++ b/mods/neutral/mod.js @@ -12,7 +12,7 @@ module.exports = { tags: ['theme', 'dark'], name: 'neutral', desc: 'smoother colours and fonts, designed to be more pleasing to the eye.', - version: '0.1.3', + version: '0.1.4', author: 'arecsu', fonts: [ 'https://rsms.me/inter/inter.css', diff --git a/mods/neutral/styles.css b/mods/neutral/styles.css index 8b28df9..5f6c242 100644 --- a/mods/neutral/styles.css +++ b/mods/neutral/styles.css @@ -143,17 +143,6 @@ padding-top: 5px !important; } */ -/* add space at the bottom of the main frame when sidebar is hidden - * -- matches space at top for titlebar */ -.notion-dark-theme .notion-frame { - transition: height 300ms ease 0s; -} -.notion-dark-theme - .notion-sidebar-container[style*='width: 0px;'] - + .notion-frame { - height: calc(100vh - 40px) !important; -} - /* hide sidebar "new page" button */ .notion-dark-theme .notion-sidebar diff --git a/mods/night-shift/mod.js b/mods/night-shift/mod.js index 51d5702..0c29d7c 100644 --- a/mods/night-shift/mod.js +++ b/mods/night-shift/mod.js @@ -12,7 +12,7 @@ module.exports = { name: 'night shift', desc: 'sync dark/light theme with the system (overrides normal theme setting).', - version: '0.1.0', + version: '0.1.1', author: 'dragonwocky', hacks: { 'renderer/preload.js'(store, __exports) { @@ -27,6 +27,7 @@ module.exports = { const observer = new MutationObserver(process); observer.observe(notion_elem, { attributes: true, + subtree: true, }); function process(list, observer) { const mode = `notion-app-inner notion-${ @@ -34,8 +35,7 @@ module.exports = { ? 'dark' : 'light' }-theme`; - if (list[0].target.className !== mode) - list[0].target.className = mode; + if (notion_elem.className !== mode) notion_elem.className = mode; } } }); diff --git a/mods/right-to-left/mod.js b/mods/right-to-left/mod.js index 270669a..3f1fb80 100644 --- a/mods/right-to-left/mod.js +++ b/mods/right-to-left/mod.js @@ -12,7 +12,7 @@ module.exports = { tags: ['extension'], name: 'right-to-left', desc: 'enables auto rtl/ltr text direction detection.', - version: '1.4.0', + version: '1.4.1', author: 'obahareth', hacks: { 'renderer/preload.js'(store, __exports) { @@ -48,7 +48,9 @@ module.exports = { function autoAlignPageContent() { document .querySelectorAll( - '.notion-page-content > div[data-block-id]:not([dir]), [placeholder="Untitled"]:not([dir])' + `.notion-page-content > div[data-block-id]:not([dir]):not(.notion-column_list-block), + [placeholder="Untitled"]:not([dir]), + .notion-column-block > div[data-block-id]:not([dir])` ) .forEach((block) => block.setAttribute('dir', 'auto')); document diff --git a/mods/tabs/mod.js b/mods/tabs/mod.js new file mode 100644 index 0000000..7e9d7dc --- /dev/null +++ b/mods/tabs/mod.js @@ -0,0 +1,49 @@ +/* + * tabs + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +'use strict'; + +// this is just a pseudo mod to "separate" the options +// from the core module - the core still handles actually +// making it work. +module.exports = { + id: 'e1692c29-475e-437b-b7ff-3eee872e1a42', + tags: ['core', 'extension'], + name: 'tabs', + desc: 'have multiple notion pages open in a single window.', + version: '0.1.0', + author: 'dragonwocky', + options: [ + { + key: 'select_modifier', + label: + 'tab select modifier (key+1, +2, +3, ... +9 and key+left/right arrows):', + type: 'select', + value: [ + 'Alt', + 'Command', + 'Control', + 'Super', + 'Alt+Shift', + 'Command+Shift', + 'Control+Shift', + 'Super+Shift', + ], + }, + { + key: 'new_tab', + label: 'new tab keybinding:', + type: 'input', + value: 'CommandOrControl+T', + }, + { + key: 'close_tab', + label: 'close tab keybinding:', + type: 'input', + value: 'CommandOrControl+W', + }, + ], +}; diff --git a/mods/word-counter/mod.js b/mods/word-counter/mod.js index 19edd55..ea183c1 100644 --- a/mods/word-counter/mod.js +++ b/mods/word-counter/mod.js @@ -54,7 +54,7 @@ module.exports = { let queue = [], $page = document.getElementsByClassName('notion-page-content')[0]; const DOCUMENT_OBSERVER = new MutationObserver((list, observer) => { - if (!queue.length) requestIdleCallback(() => process(queue)); + if (!queue.length) requestIdleCallback(() => handle(queue)); queue.push(...list); }), PAGE_OBSERVER = new MutationObserver(showPageWordDetails); @@ -62,7 +62,7 @@ module.exports = { childList: true, subtree: true, }); - function process(list) { + function handle(list) { queue = []; for (let { addedNodes } of list) { if ( diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a9ea556 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,198 @@ +{ + "name": "notion-enhancer", + "version": "0.9.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@jsdevtools/file-path-filter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@jsdevtools/file-path-filter/-/file-path-filter-3.0.2.tgz", + "integrity": "sha512-+SbZG6stIE/nRF2PpRnubtuzhh4pouDsk/hEWwM5mKsSKlFfr4ziAE5VMogGG/K++i9NHbUTxxW0y4vdM678ew==", + "requires": { + "glob-to-regexp": "^0.4.1" + } + }, + "@jsdevtools/readdir-enhanced": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@jsdevtools/readdir-enhanced/-/readdir-enhanced-6.0.4.tgz", + "integrity": "sha512-I6D6Omu6C7XWHzvlVbXeCS0FSxYYQ13XzdrFuo1K30unnRSpdt9AxY2KyJZbYJyfI2uNNidqDkG9/K/y699AjA==", + "requires": { + "@jsdevtools/file-path-filter": "^3.0.2" + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "optional": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "optional": true + }, + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==", + "optional": true + }, + "asar": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/asar/-/asar-3.0.3.tgz", + "integrity": "sha512-k7zd+KoR+n8pl71PvgElcoKHrVNiSXtw7odKbyNpmgKe7EGRF9Pnu3uLOukD37EvavKwVFxOUpqXTIZC5B5Pmw==", + "requires": { + "@types/glob": "^7.1.1", + "chromium-pickle-js": "^0.2.0", + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + } + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "cac": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.6.1.tgz", + "integrity": "sha512-uhki4T3Ax68hw7Dufi0bATVAF8ayBSwOKUEJHjObPrUN4tlQ8Lf7oljpTje/mArLxYN0D743c2zJt4C1bVTCqg==" + }, + "chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=" + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "readdir-enhanced": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/readdir-enhanced/-/readdir-enhanced-6.0.4.tgz", + "integrity": "sha512-MWY048D/nEpHwqdnsBiUxpqjJPkEw2i2RmY5gM2Gadn0rkHS/DhUBqrYTkOqKHF4RoUlYZZ8GnP4ymlRGuo30A==", + "requires": { + "@jsdevtools/readdir-enhanced": "6.0.4" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json index d0cc94f..a71d9c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-enhancer", - "version": "0.9.1", + "version": "0.10.0-wip", "description": "an enhancer/customiser for the all-in-one productivity workspace notion.so", "main": "index.js", "bin": { @@ -36,6 +36,7 @@ "asar": "^3.0.3", "cac": "^6.5.12", "fs-extra": "^9.0.1", + "keyboardevent-from-electron-accelerator": "^2.0.0", "readdir-enhanced": "^6.0.3" } } diff --git a/pkg/apply.js b/pkg/apply.js index 36f95ab..9956c07 100644 --- a/pkg/apply.js +++ b/pkg/apply.js @@ -55,7 +55,7 @@ module.exports = async function ({ overwrite_version, friendly_errors } = {}) { console.info(' ...unpacking app.asar.'); const asar_app = path.resolve(`${helpers.__notion}/app.asar`); extractAll(asar_app, `${path.resolve(`${helpers.__notion}/app`)}`); - fs.move(asar_app, path.resolve(`${helpers.__notion}/app.asar.bak`)); + await fs.move(asar_app, path.resolve(`${helpers.__notion}/app.asar.bak`)); // patching launch script target of custom wrappers if ( @@ -76,8 +76,8 @@ module.exports = async function ({ overwrite_version, friendly_errors } = {}) { await fs.outputFile( bin_path, bin_script - .replace('electron app.asar\n', 'electron app\n') - .replace('electron6 app.asar\n', 'electron6 app\n') + .replace('electron app.asar', 'electron app') + .replace('electron6 app.asar', 'electron6 app') ); } } @@ -90,18 +90,40 @@ module.exports = async function ({ overwrite_version, friendly_errors } = {}) { filter: (stats) => stats.isFile() && stats.path.endsWith('.js'), } )) { - insertion_target = path.resolve( + const insertion_file = path.resolve( `${helpers.__notion}/app/${insertion_target}` ); - fs.appendFile( - insertion_target, - `\n\n//notion-enhancer\nrequire('${helpers.realpath( - __dirname - )}/loader.js')(__filename, exports);` - ); + if (insertion_target === 'main/main.js') { + // https://github.com/dragonwocky/notion-enhancer/issues/160 + // patch the notion:// url scheme/protocol to work on linux + fs.readFile(insertion_file, 'utf8', (err, data) => { + if (err) throw err; + fs.writeFile( + insertion_file, + `${data.replace( + /process.platform === "win32"/g, + 'process.platform === "win32" || process.platform === "linux"' + )}\n\n//notion-enhancer\nrequire('${helpers.realpath( + __dirname + )}/loader.js')(__filename, exports);`, + 'utf8', + (err) => { + if (err) throw err; + } + ); + }); + } else { + fs.appendFile( + insertion_file, + `\n\n//notion-enhancer\nrequire('${helpers.realpath( + __dirname + )}/loader.js')(__filename, exports);` + ); + } } - // not resolved, nothing depends on it so it's just a "let it do its thing" + // not resolved, nothing else in application depends on it + // so it's just a "let it do its thing" console.info(' ...recording enhancement version.'); fs.outputFile( path.resolve(`${helpers.__notion}/app/ENHANCER_VERSION.txt`), @@ -112,11 +134,25 @@ module.exports = async function ({ overwrite_version, friendly_errors } = {}) { return true; } catch (err) { console.error('### ERROR ###'); - if (err.toString().includes('EACCESS') && friendly_errors) { + if (err.code === 'EACCES' && friendly_errors) { console.error( - 'file access forbidden: try again with sudo or in an elevated/admin prompt.' + `file access forbidden - ${ + process.platform === 'win32' + ? 'make sure your user has elevated permissions.' + : `try running "sudo chmod -R a+wr ${err.path.replace( + 'Notion.app', + 'Notion' + )}" ${ + err.dest + ? `and/or "sudo chmod -R a+wr ${err.dest.replace( + 'Notion.app', + 'Notion' + )}"` + : '' + }` + }` ); - } else if (err.toString().includes('EIO') && friendly_errors) { + } else if (['EIO', 'EBUSY'].includes(err.code) && friendly_errors) { console.error('file access failed: is notion running?'); } else console.error(err); return false; diff --git a/pkg/helpers.js b/pkg/helpers.js index c905331..d64c628 100644 --- a/pkg/helpers.js +++ b/pkg/helpers.js @@ -130,6 +130,11 @@ function getEnhancements() { )) ) throw Error; + mod.defaults = {}; + for (let opt of mod.options || []) + mod.defaults[opt.key] = Array.isArray(opt.value) + ? opt.value[0] + : opt.value; modules.IDs.push(mod.id); modules.loaded.push({ ...mod, @@ -140,6 +145,17 @@ function getEnhancements() { modules.invalid.push(dir); } } + modules.loaded = modules.loaded.sort((a, b) => a.name.localeCompare(b.name)); + const priority = require('./store.js')('mods', { priority: [] }).priority; + modules.loaded = [ + ...modules.loaded.filter((m) => m.tags.includes('core')), + ...modules.loaded.filter( + (m) => !m.tags.includes('core') && !priority.includes(m.id) + ), + ...priority + .map((id) => modules.loaded.find((m) => m.id === id)) + .filter((m) => m), + ]; return modules; } diff --git a/pkg/loader.js b/pkg/loader.js index 9466004..93a92ef 100644 --- a/pkg/loader.js +++ b/pkg/loader.js @@ -55,9 +55,12 @@ module.exports = function (__file, __exports) { } const modules = helpers.getEnhancements(); - for (let mod of modules.loaded) { + for (let mod of [ + ...modules.loaded.filter((m) => m.tags.includes('core')), + ...modules.loaded.filter((m) => !m.tags.includes('core')).reverse(), + ]) { if ( - (mod.tags || []).includes('core') || + mod.id === '0f0bf8b6-eae6-4273-b307-8fc43f2ee082' || store('mods', { [mod.id]: { enabled: false } })[mod.id].enabled ) { if ( @@ -81,20 +84,16 @@ module.exports = function (__file, __exports) { }); } if (mod.hacks && mod.hacks[__file]) { - mod.defaults = {}; - for (let opt of mod.options || []) - mod.defaults[opt.key] = Array.isArray(opt.value) - ? opt.value[0] - : opt.value; - mod.hacks[__file]( - (...args) => - !args.length - ? store(mod.id, mod.defaults) - : args.length === 1 - ? store(mod.id, { ...mod.defaults, ...args[0] }) - : store(args[0], { ...mod.defaults, ...args[1] }), - __exports - ); + mod.hacks[__file]((...args) => { + if (!args.length) return store(mod.id, mod.defaults); + if (args.length === 1 && typeof args[0] === 'object') + return store(mod.id, { ...mod.defaults, ...args[0] }); + const other_mod = modules.loaded.find((m) => m.id === args[0]); + return store(args[0], { + ...(other_mod ? other_mod.defaults : {}), + ...args[1], + }); + }, __exports); } } } diff --git a/pkg/remove.js b/pkg/remove.js index bf28f00..5528c3a 100644 --- a/pkg/remove.js +++ b/pkg/remove.js @@ -103,8 +103,9 @@ module.exports = async function ({ await fs.outputFile( bin_path, bin_script - .replace('electron app\n', 'electron app.asar\n') - .replace('electron6 app\n', 'electron6 app.asar\n') + .replace('electron app', 'electron app.asar') + .replace('electron6 app', 'electron6 app.asar') + .replace(/(.asar)+/g, '.asar') ); } } @@ -114,11 +115,25 @@ module.exports = async function ({ return true; } catch (err) { console.error('### ERROR ###'); - if (err.toString().includes('EACCESS') && friendly_errors) { + if (err.code === 'EACCES' && friendly_errors) { console.error( - 'file access forbidden: try again with sudo or in an elevated/admin prompt.' + `file access forbidden - ${ + process.platform === 'win32' + ? 'make sure your user has elevated permissions.' + : `try running "sudo chmod -R a+wr ${err.path.replace( + 'Notion.app', + 'Notion' + )}" ${ + err.dest + ? `and/or "sudo chmod -R a+wr ${err.dest.replace( + 'Notion.app', + 'Notion' + )}"` + : '' + }` + }` ); - } else if (err.toString().includes('EIO') && friendly_errors) { + } else if (['EIO', 'EBUSY'].includes(err.code) && friendly_errors) { console.error('file access failed: is notion running?'); } else console.error(err); return false; diff --git a/yarn.lock b/yarn.lock index 42d4db4..0b9ca31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -143,6 +143,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +keyboardevent-from-electron-accelerator@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-2.0.0.tgz#ace21b1aa4e47148815d160057f9edb66567c50c" + integrity sha512-iQcmNA0M4ETMNi0kG/q0h/43wZk7rMeKYrXP7sqKIJbHkTU8Koowgzv+ieR/vWJbOwxx5nDC3UnudZ0aLSu4VA== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"