├── .gitignore ├── assets ├── icon.icns ├── icon.ico ├── icon.png └── windowsIcons │ ├── minimize.svg │ ├── square.svg │ └── close.svg ├── .github └── preview.png ├── src ├── renderer │ ├── modules │ │ ├── slides │ │ │ ├── slide-element.js │ │ │ ├── bg-cover.css │ │ │ ├── bg-cover.js │ │ │ ├── copy.js │ │ │ ├── get-slides.js │ │ │ ├── main-frame.js │ │ │ ├── render.js │ │ │ ├── slides.css │ │ │ ├── configurator │ │ │ │ ├── modal.css │ │ │ │ ├── index.js │ │ │ │ ├── button-action.js │ │ │ │ ├── highlight.css │ │ │ │ └── modal.js │ │ │ ├── index.js │ │ │ └── texting.css │ │ ├── window-manager │ │ │ ├── windowControls.css │ │ │ ├── create-window.controls.js │ │ │ └── create-windows-menu.js │ │ └── content-only │ │ │ └── index.js │ ├── utils │ │ └── inject-css.js │ ├── preload.js │ └── styles │ │ └── style.css └── main │ ├── utils │ ├── assets-path.js │ └── check-url.js │ ├── modules │ └── window-manager.js │ └── app.js ├── webpack ├── main.webpack.js ├── rules.webpack.js └── renderer.webpack.js ├── .vscode ├── launch.json └── settings.json ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/ 3 | .DS_Store 4 | out/ 5 | dist 6 | .webpack 7 | .idea -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maykbrito/electron-desktop-custom-notion-omni/HEAD/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maykbrito/electron-desktop-custom-notion-omni/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maykbrito/electron-desktop-custom-notion-omni/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.github/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maykbrito/electron-desktop-custom-notion-omni/HEAD/.github/preview.png -------------------------------------------------------------------------------- /src/renderer/modules/slides/slide-element.js: -------------------------------------------------------------------------------- 1 | module.exports.createSlideElement = function () { 2 | let $slide = document.createElement('div') 3 | return $slide 4 | } 5 | -------------------------------------------------------------------------------- /webpack/main.webpack.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | extensions: ['.ts', '.js'] 4 | }, 5 | entry: './src/main/app.js', 6 | module: { 7 | rules: require('./rules.webpack') 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/utils/assets-path.js: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | 3 | const assetsPath = 4 | process.env.NODE_ENV === 'production' 5 | ? process.resourcesPath 6 | : app.getAppPath() 7 | 8 | export { assetsPath } 9 | -------------------------------------------------------------------------------- /assets/windowsIcons/minimize.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /webpack/rules.webpack.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | test: /\.node$/, 4 | use: 'node-loader' 5 | }, 6 | { 7 | test: /\.(m?js|node)$/, 8 | parser: { amd: false }, 9 | use: { 10 | loader: '@marshallofsound/webpack-asset-relocator-loader', 11 | options: { 12 | outputAssetBase: 'native_modules' 13 | } 14 | } 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /webpack/renderer.webpack.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | extensions: ['.ts', '.js', '.scss', '.sass', '.css'], 4 | fallback: { 5 | path: false, 6 | fs: false, 7 | util: false, 8 | assert: false, 9 | stream: false, 10 | constants: false 11 | } 12 | }, 13 | target: 'electron-renderer', 14 | module: { 15 | rules: require('./rules.webpack') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/windowsIcons/square.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/bg-cover.css: -------------------------------------------------------------------------------- 1 | .slides__cover-image { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | object-fit: cover; 8 | opacity: 0.6; 9 | } 10 | 11 | .slides.dark .slides__cover-image { 12 | opacity: 1; 13 | filter: brightness(0.5); 14 | } 15 | 16 | /* image appear in heading 1 only */ 17 | .slides.dark:not(:has([placeholder="Heading 1"])) .slides__cover-image { 18 | display: none; 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/bg-cover.js: -------------------------------------------------------------------------------- 1 | module.exports.backgroundCover = function () { 2 | let bgImage = document.querySelector( 3 | '#notion-app div.notion-scroller [contenteditable="false"] img[style*="height: 30vh"]' 4 | ) 5 | 6 | if (bgImage === "undefined" || bgImage === null) return 7 | 8 | let imgElement = document.createElement("img") 9 | imgElement.src = bgImage.src 10 | imgElement.classList.add("slides__cover-image") 11 | 12 | return imgElement 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 12 | }, 13 | "args": [ 14 | "." 15 | ], 16 | "outputCapture": "std" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/renderer/utils/inject-css.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require("electron") 2 | const path = require("path") 3 | const appPath = ipcRenderer.sendSync("request-app-path") 4 | const fs = require("fs") 5 | 6 | function injectCSS(...cssPathSegments) { 7 | const cssPath = path.resolve(appPath, ...cssPathSegments) 8 | const cssContent = fs.readFileSync(cssPath) 9 | const styleEl = document.createElement("style") 10 | styleEl.innerHTML = cssContent 11 | document.head.append(styleEl) 12 | } 13 | 14 | module.exports = injectCSS 15 | -------------------------------------------------------------------------------- /src/main/utils/check-url.js: -------------------------------------------------------------------------------- 1 | import { shell } from 'electron' 2 | /** 3 | * This function is used electron's new-window event 4 | * It allows non-electron links to be opened with the computer's default browser 5 | * Keep opening pop-ups for google login for example 6 | * @param {NewWindowEvent} e 7 | * @param {String} url 8 | */ 9 | function checkerURL(e, url) { 10 | const isNotUrlOfTheNotion = !url.match('/www.notion.so/') 11 | 12 | if (isNotUrlOfTheNotion) { 13 | e.preventDefault() 14 | shell.openExternal(url) 15 | } 16 | } 17 | 18 | export { checkerURL } 19 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/copy.js: -------------------------------------------------------------------------------- 1 | async function copyCode(text) { 2 | await navigator.clipboard.writeText(text) 3 | } 4 | 5 | module.exports.createAndAttachCopyButtonToElement = function (attachToElement) { 6 | const button = document.createElement("button") 7 | button.innerText = "copy" 8 | button.classList.add("copyButton") 9 | button.onclick = async (e) => { 10 | let finalText = "" 11 | attachToElement.firstChild.querySelectorAll("span").forEach((span) => { 12 | finalText += span.innerText 13 | }) 14 | 15 | await copyCode(finalText) 16 | } 17 | 18 | attachToElement.appendChild(button) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/modules/window-manager.js: -------------------------------------------------------------------------------- 1 | import { ipcMain, BrowserWindow } from 'electron' 2 | import { assetsPath } from '../utils/assets-path' 3 | 4 | ipcMain.on('request-app-path', (event, arg) => { 5 | event.returnValue = assetsPath 6 | }) 7 | 8 | ipcMain.on('minimize', (event, arg) => { 9 | const win = BrowserWindow.getFocusedWindow() 10 | win.minimize() 11 | }) 12 | 13 | ipcMain.on('expand', (event, arg) => { 14 | const win = BrowserWindow.getFocusedWindow() 15 | if (win.isMaximized()) { 16 | win.restore() 17 | } else { 18 | win.maximize() 19 | } 20 | }) 21 | 22 | ipcMain.on('close', (event, arg) => { 23 | const win = BrowserWindow.getFocusedWindow() 24 | win.close() 25 | }) 26 | -------------------------------------------------------------------------------- /assets/windowsIcons/close.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/get-slides.js: -------------------------------------------------------------------------------- 1 | module.exports.getSlides = function () { 2 | const pageContent = [ 3 | ...document.querySelectorAll(".notion-page-content > div"), 4 | ] 5 | const newSlides = pageContent.filter((item) => 6 | item.classList.contains("notion-divider-block") 7 | ) 8 | 9 | const newSlide = (block) => ({ 10 | title: block, 11 | blocks: [], 12 | }) 13 | 14 | let currentSlide 15 | 16 | const slides = pageContent 17 | .map((block) => { 18 | const isNewSlide = newSlides.find((slide) => slide === block) 19 | 20 | if (isNewSlide) { 21 | currentSlide = newSlide(block) 22 | currentBlock = undefined 23 | return currentSlide 24 | } 25 | 26 | currentSlide && currentSlide.blocks.push(block) 27 | }) 28 | .filter(Boolean) 29 | 30 | return slides 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/modules/window-manager/windowControls.css: -------------------------------------------------------------------------------- 1 | #window-controls-wrapper { 2 | position: fixed; 3 | background-color: #000; 4 | height: 35px; 5 | top: 0; 6 | right: 0; 7 | display: flex; 8 | z-index: 100; 9 | margin-top: -35px; 10 | transition: 300ms; 11 | } 12 | 13 | #window-controls-wrapper.active { 14 | margin-top: 0 !important; 15 | } 16 | 17 | #window-controls-wrapper > .button { 18 | width: 45px; 19 | height: 100%; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | #window-controls-wrapper > .button:hover { 26 | background-color: #505050; 27 | } 28 | 29 | #window-controls-wrapper > .button:hover:last-child { 30 | background-color: rgba(255, 0, 0, 0.712); 31 | } 32 | 33 | #window-controls-wrapper > .button > svg { 34 | width: 20px; 35 | height: 20px; 36 | margin: auto; 37 | } 38 | -------------------------------------------------------------------------------- /src/renderer/modules/window-manager/create-window.controls.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const appPath = ipcRenderer.sendSync('request-app-path') 5 | 6 | function createWindowControls() { 7 | const iconsFolder = path.resolve(appPath, 'assets', 'windowsIcons') 8 | const wrapper = document.createElement('div') 9 | wrapper.id = 'window-controls-wrapper' 10 | 11 | wrapper.innerHTML = ` 12 |
15 | 18 | ` 21 | return wrapper 22 | } 23 | 24 | module.exports = createWindowControls 25 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/main-frame.js: -------------------------------------------------------------------------------- 1 | const { backgroundCover } = require('./bg-cover') 2 | 3 | module.exports.createMainFrame = function () { 4 | let mainFrame = document.createElement('div') 5 | mainFrame.classList.add('notion-frame') 6 | mainFrame.classList.add('slides') 7 | 8 | const isDark = document.body.classList.contains('dark') 9 | if (isDark) { 10 | mainFrame.style.color = 'rgba(255, 255, 255, 0.9)' 11 | mainFrame.style.fill = 'rgba(255, 255, 255, 0.9)' 12 | mainFrame.classList.add('dark') 13 | } 14 | 15 | mainFrame.style.position = 'absolute' 16 | mainFrame.style.top = '0' 17 | mainFrame.style.bottom = '0' 18 | mainFrame.style.left = '0' 19 | mainFrame.style.right = '0' 20 | mainFrame.style.zIndex = '1000' 21 | mainFrame.style.overflow = 'overlay' 22 | 23 | let bgCover = backgroundCover() 24 | bgCover && mainFrame.append(bgCover) 25 | 26 | return mainFrame 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/preload.js: -------------------------------------------------------------------------------- 1 | const injectCSS = require("./utils/inject-css") 2 | 3 | window.addEventListener("DOMContentLoaded", () => { 4 | injectCSS("src", "renderer", "styles", "style.css") 5 | 6 | if (process.platform !== "darwin") { 7 | require("./modules/window-manager/create-windows-menu")() 8 | } 9 | 10 | /** slides feat */ 11 | require("./modules/slides/index.js") 12 | 13 | /** content-only feat */ 14 | require("./modules/content-only") 15 | 16 | // drag and move window 17 | var windowTopBar = document.createElement("div") 18 | windowTopBar.id = "maykbrito-eh-o-brabo" 19 | windowTopBar.style.width = "100%" 20 | windowTopBar.style.height = "2.75rem" 21 | windowTopBar.style.position = "absolute" 22 | windowTopBar.style.top = windowTopBar.style.left = 0 23 | windowTopBar.style.webkitAppRegion = "drag" 24 | windowTopBar.style.pointerEvents = "none" 25 | document.body.appendChild(windowTopBar) 26 | }) 27 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/render.js: -------------------------------------------------------------------------------- 1 | const { createMainFrame } = require("./main-frame.js") 2 | const { createSlideElement } = require("./slide-element.js") 3 | const { createAndAttachCopyButtonToElement } = require("./copy.js") 4 | 5 | const $slide = createSlideElement() 6 | 7 | module.exports.$slide = $slide 8 | 9 | module.exports.getMainFrame = () => { 10 | const mainFrame = createMainFrame() 11 | mainFrame.appendChild($slide) 12 | document.body.appendChild(mainFrame) 13 | return mainFrame 14 | } 15 | 16 | module.exports.render = ($slide, currentSlide) => { 17 | $slide.innerHTML = "" 18 | if (!currentSlide) return 19 | 20 | for (let block of currentSlide.blocks) { 21 | const buildBlock = block.cloneNode(true) 22 | buildBlock.classList.add("appear") 23 | $slide.appendChild(buildBlock) 24 | } 25 | 26 | let notionCodeBlock = document.querySelectorAll(".slides .notion-code-block") 27 | 28 | if (!notionCodeBlock) return 29 | 30 | notionCodeBlock.forEach(createAndAttachCopyButtonToElement) 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/slides.css: -------------------------------------------------------------------------------- 1 | .slides { 2 | --base-bg-color: #070715; 3 | --base-text-color: #f5f5fa; 4 | } 5 | 6 | .slides.notion-frame { 7 | width: 100%; 8 | box-sizing: border-box; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | } 13 | 14 | .slides.dark.notion-frame { 15 | background: var(--base-bg-color) !important; 16 | } 17 | 18 | .slides.notion-frame { 19 | background-color: var(--base-text-color) !important; 20 | } 21 | 22 | .slides #slide-inner { 23 | width: 90% !important; 24 | max-width: 800px !important; 25 | margin: 0 auto !important; 26 | padding-bottom: 10vh !important; 27 | padding-left: calc(4rem + env(safe-area-inset-left)) !important; 28 | padding-right: calc(4rem + env(safe-area-inset-right)) !important; 29 | } 30 | 31 | .slides > div > div { 32 | max-width: 100% !important; 33 | } 34 | 35 | /* the copy button is repeated an not needed */ 36 | .slides .copyButton { 37 | display: none !important; 38 | } 39 | 40 | /* animate slides */ 41 | .slides.notion-frame .appear { 42 | opacity: 0; 43 | transform: translateX(-10px); 44 | animation: appear 0.3s ease-in-out forwards; 45 | } 46 | 47 | @keyframes appear { 48 | to { 49 | opacity: 1; 50 | transform: translateX(0); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/configurator/modal.css: -------------------------------------------------------------------------------- 1 | #scriptModal { 2 | width: 90vw; 3 | padding: 20px; 4 | border: none; 5 | border-radius: 8px; 6 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 7 | background: var(--dark); 8 | } 9 | 10 | #scriptModal h2 { 11 | margin-top: 0; 12 | margin-bottom: 0.5rem; 13 | font-size: 1.2rem; 14 | color: var(--white); 15 | } 16 | 17 | #scriptModal::backdrop { 18 | background-color: rgba(0, 0, 0, 0.5); 19 | } 20 | 21 | #scriptTextarea { 22 | width: 100%; 23 | height: 350px; 24 | margin-bottom: 10px; 25 | padding: 10px; 26 | border-radius: 4px; 27 | font-family: monospace; 28 | } 29 | 30 | #scriptModal header { 31 | display: flex; 32 | justify-content: space-between; 33 | align-items: center; 34 | } 35 | 36 | /* Estilo dos botões */ 37 | #buttonWrapper { 38 | display: flex; 39 | justify-content: flex-end; 40 | align-items: center; 41 | gap: 0.5rem; 42 | } 43 | 44 | #scriptModal button { 45 | padding: 0 0.5rem; 46 | border: none; 47 | border-radius: 4px; 48 | cursor: pointer; 49 | font-size: 14px; 50 | height: 1.5rem; 51 | display: flex; 52 | align-items: center; 53 | justify-content: center; 54 | color: var(--white); 55 | background-color: var(--dark-2); 56 | } 57 | 58 | #scriptModal button:hover { 59 | background-color: var(--dark-4); 60 | } 61 | 62 | #scriptModal button svg { 63 | width: 14px; 64 | } -------------------------------------------------------------------------------- /src/renderer/modules/content-only/index.js: -------------------------------------------------------------------------------- 1 | let showingOnlyContent = false 2 | const elementsToHide = [ 3 | '#notion-app > div > div:nth-child(1) > div > div:nth-child(2) > header', 4 | '.notion-ai-button' 5 | ] 6 | let notionFrameInitialHeight 7 | let elementsInitialDisplay = {} 8 | 9 | window.addEventListener('keydown', ev => { 10 | const shortcutKeys = ev.key === 'Escape' && ev.ctrlKey 11 | if (shortcutKeys) { 12 | showingOnlyContent = !showingOnlyContent 13 | showingOnlyContent ? hide() : show() 14 | } 15 | }) 16 | 17 | const elementDisplay = (element, display) => { 18 | const currentElement = document.querySelector(element) 19 | console.log({ currentElement }) 20 | if (!currentElement) return 21 | 22 | if (display === 'initial') { 23 | currentElement.style.display = elementsInitialDisplay[element] 24 | return 25 | } 26 | 27 | elementsInitialDisplay[element] = currentElement.style.display 28 | currentElement.style.display = display; 29 | } 30 | 31 | const hide = () => { 32 | elementsToHide.forEach(element => elementDisplay(element, 'none')) 33 | 34 | notionFrameInitialHeight = document.querySelector('.notion-frame').style.height 35 | document.querySelector('.notion-frame').style.height = '100vh' 36 | } 37 | const show = () => { 38 | elementsToHide.forEach(element => elementDisplay(element, 'initial')) 39 | document.querySelector('.notion-frame').style.height = notionFrameInitialHeight 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // editor 3 | "editor.wordWrap": "on", 4 | "editor.fontSize": 18, 5 | "editor.lineHeight": 30, 6 | "editor.tabSize": 2, 7 | "editor.bracketPairColorization.enabled": true, 8 | "editor.guides.bracketPairs": true, 9 | "editor.minimap.enabled": false, 10 | "editor.formatOnSave": true, 11 | "editor.formatOnPaste": true, 12 | "files.autoSave": "afterDelay", 13 | 14 | // explorer 15 | "explorer.compactFolders": false, 16 | 17 | // workbench 18 | "workbench.editor.enablePreview": false, 19 | "workbench.iconTheme": "material-icon-theme", 20 | "workbench.colorTheme": "Default Dark Modern", 21 | 22 | // prettier 23 | "editor.defaultFormatter": "esbenp.prettier-vscode", 24 | "prettier.enable": true, 25 | "prettier.singleQuote": false, 26 | "prettier.tabWidth": 2, 27 | "prettier.semi": false, 28 | 29 | // terminal 30 | "terminal.integrated.fontSize": 16, 31 | "terminal.integrated.profiles.windows": { 32 | "Git Bash": { 33 | "source": "Git Bash" 34 | } 35 | }, 36 | "terminal.integrated.defaultProfile.windows": "Git Bash", 37 | 38 | // zen mode 39 | "zenMode.hideActivityBar": true, 40 | "zenMode.silentNotifications": true, 41 | "zenMode.fullScreen": false, 42 | "zenMode.centerLayout": false, 43 | "zenMode.hideLineNumbers": false, 44 | 45 | // 46 | "window.zoomLevel": 0, 47 | "zenMode.showTabs": "single", 48 | "cSpell.words": [ 49 | "Omni", 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/renderer/modules/window-manager/create-windows-menu.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron') 2 | const injectCSS = require('../../utils/inject-css') 3 | 4 | const createWindowControls = require('./create-window.controls') 5 | 6 | function createWindowsMenu() { 7 | injectCSS( 8 | 'src', 9 | 'renderer', 10 | 'modules', 11 | 'window-manager', 12 | 'windowControls.css' 13 | ) 14 | 15 | const windowControlsMenu = createWindowControls() 16 | document.body.appendChild(windowControlsMenu) 17 | 18 | const hideMenu = () => windowControlsMenu.classList.remove('active') 19 | 20 | window.addEventListener('mousemove', event => { 21 | const { pageX, pageY } = event 22 | const { x, height, y } = windowControlsMenu.getBoundingClientRect() 23 | if (pageY <= 10 && pageX > x) { 24 | if (!windowControlsMenu.classList.contains('active')) { 25 | windowControlsMenu.classList.add('active') 26 | } 27 | } 28 | 29 | if (pageY > y + height + 10 || pageX < x) { 30 | if (windowControlsMenu.classList.contains('active')) { 31 | hideMenu() 32 | } 33 | } 34 | }) 35 | 36 | const minimize_btn = document.getElementById('minimize') 37 | minimize_btn.onclick = () => { 38 | ipcRenderer.send('minimize') 39 | hideMenu() 40 | } 41 | 42 | const maxime_btn = document.getElementById('expand') 43 | maxime_btn.onclick = () => { 44 | ipcRenderer.send('expand') 45 | hideMenu() 46 | } 47 | 48 | const close_btn = document.getElementById('close') 49 | close_btn.onclick = () => { 50 | ipcRenderer.send('close') 51 | hideMenu() 52 | } 53 | } 54 | 55 | module.exports = createWindowsMenu 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notion Omni 2 | 3 | Inject some JS/CSS to customize Notion ;) 4 | 5 | --- 6 | 7 |8 | How to build 9 | Features 10 | 11 | Contact 12 | 13 |
14 | 15 |  16 | 17 | --- 18 | 19 | ## How to build 20 | 21 | 1. Install 22 | 23 | ```sh 24 | yarn 25 | ``` 26 | 27 | 2. Build 28 | 29 | ```sh 30 | yarn package 31 | ``` 32 | 33 | 3. Run your new app. 34 | _It will be at dir ./out_ 35 | 36 | 4. Put your Notion in Dark Mode ;) 37 | 38 | # Features 39 | 40 | ## Content Only 41 | 42 | Because we love a clean layout, we can toggle (show/hide) view of 43 | 44 | - header bar 45 | - page title & attributes block 46 | - help button 47 | 48 | by pressing `ctrl+esc` 49 | 50 |  51 | 52 | --- 53 | 54 | ## Slide Presentation 55 | 56 | You can use this app as a slide presentation. 57 | 58 |  59 | 60 | ### 🤔 How? 61 | 62 | 1. Prepare your presentation 63 | 64 | - Each divider will be a new slide block 65 | 66 | 2. Toggle presentation with `SHIFT+ESC` 67 | 68 | Try this. 69 | 70 | Create a new Notion page and add code bellow, then hit `SHIFT+ESC` to see the magic. 71 | 72 | ```md 73 | --- 74 | # Slide one 75 | 76 | Some content 77 | --- 78 | 79 | ## Slide two 80 | 81 | Second slide 82 | 83 | --- 84 | ## With h2 header 85 | 86 | Try it! 87 | ``` 88 | 89 | # Contact 90 | 91 | Mayk Brito 92 | 93 | - [maykbrito.dev](https://maykbrito.dev) 94 | 95 | --- 96 | 97 | If this project helps you, please leave your star 🌟 Thank you 💛 98 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/configurator/index.js: -------------------------------------------------------------------------------- 1 | const ButtonAction = require('./button-action.js') 2 | require('./highlight.js') 3 | const injectCSS = require("../../../utils/inject-css.js") 4 | injectCSS('src', 'renderer', 'modules', 'slides', 'configurator', 'highlight.css') 5 | injectCSS('src', 'renderer', 'modules', 'slides', 'configurator', 'modal.css') 6 | 7 | const observer = new MutationObserver((mutationsList) => { 8 | /* need to observe to show or hide the script button */ 9 | const notionControls = showOrHideButton.targetElement(); 10 | if (notionControls) { 11 | showOrHideButton.observer().observe(notionControls, { attributeFilter: ['style'], attributes: true }) 12 | } 13 | 14 | for (const mutation of mutationsList) { 15 | if (mutation.type === 'childList') { 16 | const configurator = [...document.querySelectorAll('div[contenteditable="true"]')].find( 17 | div => div.textContent.includes('[enable-slide-configurator=true]') 18 | ) 19 | 20 | if (configurator) { 21 | ButtonAction.addToTargetElement(); 22 | } else { 23 | ButtonAction.removeFromTargetElement(); 24 | } 25 | } 26 | } 27 | }); 28 | 29 | observer.observe(document.body, { childList: true, subtree: true }); 30 | 31 | 32 | /* need to observe to show or hide custom button */ 33 | const showOrHideButton = { 34 | targetElement: () => document.querySelector('.notion-page-controls > div:first-child'), 35 | elementoParaAplicarEstilo: () => document.querySelector('.add-script-button'), 36 | observer: () => new MutationObserver((mutationsList) => { 37 | const targetElement = showOrHideButton.targetElement(); 38 | const elementoParaAplicarEstilo = showOrHideButton.elementoParaAplicarEstilo(); 39 | 40 | if (!targetElement || !elementoParaAplicarEstilo) return; 41 | 42 | for (let mutation of mutationsList) { 43 | if (mutation.type === 'attributes' && mutation.attributeName === 'style') { 44 | const novoEstilo = targetElement.getAttribute('style'); 45 | elementoParaAplicarEstilo.setAttribute('style', novoEstilo); 46 | } 47 | } 48 | }) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/index.js: -------------------------------------------------------------------------------- 1 | require('./configurator/index.js') 2 | const { getSlides } = require("./get-slides.js") 3 | const { render, $slide, getMainFrame } = require("./render.js") 4 | 5 | const injectCSS = require("../../utils/inject-css") 6 | 7 | injectCSS("src", "renderer", "modules", "slides", "slides.css") 8 | injectCSS("src", "renderer", "modules", "slides", "texting.css") 9 | injectCSS("src", "renderer", "modules", "slides", "bg-cover.css") 10 | 11 | let data 12 | let slideIndex = 0 13 | let isRunning = false 14 | let mainFrame 15 | 16 | window.addEventListener("keydown", function (ev) { 17 | if (ev.key === "Escape" && ev.shiftKey) { 18 | isRunning ? hide() : show() 19 | isRunning = !isRunning 20 | } 21 | }) 22 | 23 | function show() { 24 | mainFrame && mainFrame.parentNode.removeChild(mainFrame) 25 | start(slideIndex) 26 | } 27 | 28 | function hide() { 29 | mainFrame.style.display = "none" 30 | window.removeEventListener("keydown", control) 31 | } 32 | 33 | function control(ev) { 34 | switch (ev.key) { 35 | case "ArrowLeft": 36 | move("backward") 37 | break 38 | case "ArrowRight": 39 | move("forward") 40 | break 41 | } 42 | } 43 | 44 | function move(dir) { 45 | var dx = dir === "backward" ? -1 : 1 46 | 47 | const isLeft = dx === -1 48 | 49 | if (isLeft) { 50 | if (slideIndex - 1 < 0) { 51 | return 52 | } else { 53 | slideIndex-- 54 | } 55 | } else { 56 | if (slideIndex + 1 >= data.length) { 57 | return 58 | } else { 59 | slideIndex++ 60 | } 61 | } 62 | 63 | render($slide, data[slideIndex]) 64 | } 65 | 66 | function start() { 67 | data = getSlides() 68 | mainFrame = getMainFrame() 69 | 70 | window.addEventListener("keydown", control) 71 | 72 | prepareSlideWrapper() 73 | render($slide, data[slideIndex]) 74 | } 75 | 76 | function prepareSlideWrapper() { 77 | const notionPageContentStyles = document 78 | .querySelector("#notion-app main .notion-page-content") 79 | .getAttribute("style") 80 | $slide.classList.add("notion-page-content") 81 | $slide.setAttribute("style", notionPageContentStyles) 82 | $slide.setAttribute("id", "slide-inner") 83 | } 84 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/configurator/button-action.js: -------------------------------------------------------------------------------- 1 | const Modal = require("./modal.js") 2 | 3 | const ButtonAction = { 4 | targetElement: () => document.querySelector("#notion-app div.notion-page-controls"), 5 | createButton() { 6 | const content = ` 7 | 8 | Add script 9 | ` 10 | 11 | const button = document.createElement('div'); 12 | button.classList.add('add-script-button'); 13 | button.setAttribute('role', 'button'); 14 | button.setAttribute('tabindex', '0'); 15 | button.setAttribute('style', `user-select: none; transition: opacity 100ms ease 0s; cursor: pointer; opacity: 0; display: inline-flex; align-items: center; flex-shrink: 0; white-space: nowrap; height: 28px; border-radius: 6px; font-size: 14px; line-height: 1.2; min-width: 0px; padding-left: 6px; padding-right: 8px; color: rgba(255, 255, 255, 0.282); pointer-events: none;`); 16 | button.onmouseover = () => button.style.background = 'rgba(255, 255, 255, 0.055)'; 17 | button.onmouseout = () => button.style.background = 'transparent'; 18 | // button.innerText = 'ಠ'; 19 | button.innerHTML = content; 20 | button.onclick = Modal.open; 21 | button.onkeydown = ({ key }) => key === 'Space' && Modal.open(); 22 | return button; 23 | }, 24 | addToTargetElement() { 25 | const targetElement = this.targetElement(); 26 | 27 | if (targetElement && !targetElement.querySelector('.add-script-button')) { 28 | const button = this.createButton(); 29 | targetElement.appendChild(button); 30 | } 31 | }, 32 | removeFromTargetElement() { 33 | const targetElement = this.targetElement(); 34 | if (targetElement) { 35 | const button = targetElement.querySelector('.add-script-button'); 36 | if (button) { 37 | targetElement.removeChild(button); 38 | } 39 | } 40 | }, 41 | } 42 | 43 | module.exports = ButtonAction; -------------------------------------------------------------------------------- /src/main/app.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, screen, nativeImage } from "electron" 2 | import windowStateKeeper from "electron-window-state" 3 | import path from "path" 4 | 5 | import { assetsPath } from "./utils/assets-path" 6 | import { checkerURL } from "./utils/check-url" 7 | 8 | import "./modules/window-manager" 9 | 10 | let win = null 11 | app.allowRendererProcessReuse = true 12 | 13 | async function createWindow() { 14 | win = new BrowserWindow({ 15 | icon: nativeImage.createFromPath( 16 | path.join(assetsPath, "assets", "icon.png") 17 | ), 18 | frame: false, 19 | titleBarStyle: "customButtonsOnHover", 20 | draggable: true, 21 | webPreferences: { 22 | nodeIntegration: false, 23 | contextIsolation: true, 24 | sandbox: false, 25 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, 26 | }, 27 | }) 28 | 29 | adjustWindow(win) 30 | 31 | win.loadURL("https://notion.so") 32 | 33 | win.webContents.on("new-window", checkerURL) // add event listener for URL check 34 | } 35 | 36 | function adjustWindow(win) { 37 | const mainScreen = screen.getPrimaryDisplay() 38 | const dimensions = mainScreen.size 39 | 40 | const mainWindowState = windowStateKeeper({ 41 | defaultWidth: dimensions.width, 42 | defaultHeight: dimensions.height, 43 | }) 44 | 45 | let { width, height, x, y } = mainWindowState 46 | x = x || 0 47 | y = y || 0 48 | 49 | win.setSize(width, height) 50 | win.setPosition(x, y) 51 | mainWindowState.manage(win) 52 | } 53 | 54 | const isUnicInstance = app.requestSingleInstanceLock() //Verifica se o app já foi iniciado 55 | 56 | if (!isUnicInstance) { 57 | app.quit() // Caso o app já tiver sido aberto ele é fechado 58 | } else { 59 | // This method will be called when Electron has finished 60 | // initialization and is ready to create browser windows. 61 | // Some APIs can only be used after this event occurs. 62 | app 63 | .whenReady() 64 | .then(createWindow) 65 | .catch((e) => console.error(e)) 66 | } 67 | 68 | // Faz com que o programa não inicie várias vezes durante a instalação no windows 69 | if (require("electron-squirrel-startup")) { 70 | app.quit() 71 | } 72 | 73 | app.on("second-instance", () => { 74 | const win = BrowserWindow.getAllWindows()[0] 75 | if (win.isMinimized()) { 76 | win.restore() 77 | } 78 | win.focus() 79 | }) 80 | 81 | // Quit when all windows are closed. 82 | app.on("window-all-closed", () => { 83 | // On macOS it is common for applications and their menu bar 84 | // to stay active until the user quits explicitly with Cmd + Q 85 | if (process.platform !== "darwin") { 86 | app.quit() 87 | } 88 | }) 89 | 90 | app.on("activate", recreateWindow) 91 | 92 | function recreateWindow() { 93 | // On macOS it's common to re-create a window in the app when the 94 | // dock icon is clicked and there are no other windows open. 95 | if (BrowserWindow.getAllWindows().length === 0) { 96 | setTimeout(createWindow, 200) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-omni", 3 | "version": "1.0.0", 4 | "description": "Inject some CSS to custom Notion colors ;)", 5 | "main": "./.webpack/main/index.js", 6 | "scripts": { 7 | "start": "electron-forge start", 8 | "package": "electron-forge package", 9 | "make": "electron-forge make" 10 | }, 11 | "devDependencies": { 12 | "@electron-forge/cli": "^6.0.0-beta.61", 13 | "@electron-forge/maker-deb": "^6.0.0-beta.61", 14 | "@electron-forge/maker-rpm": "^6.0.0-beta.61", 15 | "@electron-forge/maker-squirrel": "^6.0.0-beta.61", 16 | "@electron-forge/maker-zip": "^6.0.0-beta.61", 17 | "@electron-forge/plugin-webpack": "^6.0.0-beta.61", 18 | "@marshallofsound/webpack-asset-relocator-loader": "^0.5.0", 19 | "electron": "^21", 20 | "node-loader": "^2.0.0", 21 | "webpack": "^5.61.0", 22 | "webpack-cli": "^4.9.1" 23 | }, 24 | "keywords": [ 25 | "Notion", 26 | "Styling", 27 | "Custom" 28 | ], 29 | "author": "Mayk Brito
29 | `,
30 | createDialog() {
31 | const dialog = document.createElement('dialog');
32 | dialog.id = 'scriptModal'
33 | dialog.innerHTML = this.content;
34 | return dialog;
35 | },
36 | open() {
37 | const scriptModal = document.getElementById('scriptModal');
38 | const scriptTextarea = document.getElementById('scriptTextarea');
39 | const scriptTextareaExecute = document.getElementById('scriptTextareaExecute');
40 | const saveButton = document.getElementById('saveButton');
41 | const executeButton = document.getElementById('executeButton');
42 | const pasteButton = document.getElementById('pasteButton');
43 | const closeButton = document.getElementById('closeButton');
44 |
45 | scriptModal.showModal();
46 |
47 | scriptTextareaExecute.value = localStorage.getItem('savedScript') || '';
48 | scriptTextarea.innerHTML = scriptTextareaExecute.value;
49 | const highlightedValue = hljs.highlightAuto(scriptTextarea.textContent).value
50 | scriptTextarea.innerHTML = highlightedValue
51 |
52 | saveButton.addEventListener('click', () => {
53 | localStorage.setItem('savedScript', scriptTextareaExecute.value);
54 | alert('Script salvo com sucesso!');
55 | });
56 |
57 | executeButton.addEventListener('click', () => {
58 | try {
59 | eval(scriptTextareaExecute.value);
60 | alert('Script executado com sucesso!');
61 | } catch (error) {
62 | alert(`Erro ao executar o script: ${error.message}`);
63 | }
64 | });
65 |
66 | pasteButton.addEventListener('click', () => {
67 | navigator.clipboard.readText().then((text) => {
68 | scriptTextareaExecute.value = text;
69 | const highlightedValue = hljs.highlightAuto(text).value
70 | scriptTextarea.innerHTML = highlightedValue
71 | });
72 | });
73 |
74 | closeButton.addEventListener('click', () => {
75 | scriptModal.close();
76 | });
77 | }
78 | }
79 |
80 | document.body.appendChild(Modal.createDialog())
81 |
82 | module.exports = Modal;
--------------------------------------------------------------------------------
/src/renderer/modules/slides/texting.css:
--------------------------------------------------------------------------------
1 | .slides {
2 | --base-primary-color: var(--green);
3 | --font-family: "Plus Jakarta Sans", "Ubuntu", "PT Sans", "Helvetica",
4 | sans-serif;
5 | letter-spacing: 0.03em;
6 | }
7 |
8 | .slides.dark {
9 | color: #f5f5fa;
10 | }
11 |
12 | /* h1 */
13 | .slides:has([placeholder="Heading 1"]) {
14 | text-align: center !important;
15 | }
16 |
17 | .slides [placeholder="Heading 1"] {
18 | font-size: 3em !important;
19 | line-height: 1 !important;
20 | font-family: var(--font-family) !important;
21 | }
22 |
23 | .slides [placeholder="Heading 1"]::after {
24 | display: block !important;
25 | content: "";
26 | width: 1.25em !important;
27 | height: 0.125em !important;
28 | background: var(--base-primary-color);
29 | border-radius: 4em !important;
30 | margin: 0.225em auto 0.3875em !important;
31 | }
32 |
33 | .slides #slide-inner:has(div[data-block-id]:only-child) [placeholder="Heading 1"]::after {
34 | display: none !important;
35 | }
36 |
37 | /* h2 */
38 | .slides [placeholder="Heading 2"] {
39 | font-size: 1em !important;
40 | margin-bottom: 0em !important;
41 | line-height: 1.6 !important;
42 | font-family: var(--font-family) !important;
43 | }
44 |
45 | .slides [placeholder="Heading 2"]::after {
46 | content: "";
47 | display: block;
48 | background: var(--base-primary-color);
49 | height: 0.2em;
50 | width: 1.875em;
51 | border-radius: 10px;
52 | margin-top: 0.3125em;
53 | margin-bottom: 0.875em;
54 | }
55 |
56 | .slides #slide-inner:has(div[data-block-id]:only-child) [placeholder="Heading 2"] {
57 | font-size: 1.5em !important;
58 | }
59 | .slides #slide-inner:has(div[data-block-id]:only-child) [placeholder="Heading 2"]::after {
60 | display: none;
61 | }
62 |
63 | /* p */
64 | .slides .notion-text-block > div > div > div {
65 | font-size: 1.125em !important;
66 | line-height: 1.6 !important;
67 | margin-inline: 0 !important;
68 | font-family: var(--font-family) !important ;
69 | }
70 |
71 | /* sub h1 p */
72 | .slides .notion-header-block + .notion-text-block > div > div > div {
73 | font-family: "IBM Plex Mono", "American Typewriter", monospace !important;
74 | }
75 |
76 | /* list */
77 | .slides
78 | .notion-bulleted_list-block.appear
79 | + .notion-bulleted_list-block.appear {
80 | margin-top: 1.5em !important;
81 | }
82 |
83 | .slides .notion-bulleted_list-block.appear [placeholder="List"] {
84 | font-size: 1.375em !important;
85 | line-height: 1.2 !important;
86 | font-family: var(--font-family) !important;
87 | }
88 |
89 | .slides .notion-bulleted_list-block .notion-text-block {
90 | font-size: 1em !important;
91 | line-height: 1.4 !important;
92 | opacity: 0.7;
93 | }
94 |
95 | .slides .notion-bulleted_list-block > div > div:nth-child(1) div::before {
96 | content: "";
97 | display: inline-block;
98 | width: 0.3375em !important;
99 | height: 0.3375em !important;
100 | border-radius: 50%;
101 | background-color: var(--base-primary-color);
102 | border: 2px solid black;
103 | margin-right: 0.625em;
104 | display: block !important;
105 | }
106 |
107 | /* sub list item */
108 | .slides .notion-bulleted_list-block .notion-bulleted_list-block {
109 | font-size: 0.8em !important;
110 | opacity: 0.8;
111 | }
112 |
113 | .slides
114 | .notion-bulleted_list-block
115 | .notion-bulleted_list-block
116 | > div
117 | > div:nth-child(1)
118 | div::before {
119 | background-color: transparent;
120 | border: 2px solid var(--base-primary-color);
121 | }
122 |
123 | /* code-block */
124 | .slides .notion-code-block {
125 | position: relative;
126 | }
127 |
128 | .copyButton {
129 | border: 0;
130 | background-color: rgb(54 53 54 / 47%);
131 | color: white;
132 | border-radius: 4px;
133 | font-size: 10px;
134 | position: absolute;
135 | right: 8px;
136 | top: 8px;
137 | line-height: 0;
138 | display: flex;
139 | align-items: center;
140 | justify-content: center;
141 | height: 20px;
142 | padding: 0 10px;
143 | cursor: pointer;
144 |
145 | display: none;
146 | }
147 |
148 | .copyButton:hover {
149 | background-color: rgb(54 53 54 / 77%);
150 | }
151 |
152 | .slides .notion-code-block:hover .copyButton {
153 | display: block;
154 | }
155 |
156 | /* img */
157 | .slides:has(img) #slide-inner:has(> div.notion-image-block:only-child) {
158 | padding: 0 !important;
159 | /* width: 90% !important; */
160 | max-width: initial !important;
161 | }
162 | .slides:has(img) #slide-inner:has(> div.notion-image-block:only-child) img {
163 | width: 90% !important;
164 | height: auto !important;
165 | object-fit: contain !important;
166 | margin: auto !important;
167 | max-height: initial !important;
168 | aspect-ratio: 16/9 !important;
169 | }
--------------------------------------------------------------------------------
/src/renderer/styles/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* Dark */
3 | --dark-hue: 253;
4 | --dark-0: hsl(var(--dark-hue), 24%, 5%);
5 | --dark: hsl(var(--dark-hue), 24%, 10%);
6 | --dark-1: hsl(var(--dark-hue), 24%, 14%);
7 | --dark-2: hsl(var(--dark-hue), 24%, 18%);
8 | --dark-3: hsl(var(--dark-hue), 24%, 22%);
9 | --dark-4: hsl(var(--dark-hue), 24%, 26%);
10 | --dark-5: hsl(var(--dark-hue), 24%, 30%);
11 | /* Primary */
12 | --primary-hue: 132;
13 | --primary: hsl(var(--primary-hue), 70%, 65%);
14 | --primary-1: hsl(var(--primary-hue), 70%, 55%);
15 | --primary-2: hsl(var(--primary-hue), 70%, 45%);
16 | --primary-3: hsl(var(--primary-hue), 70%, 35%);
17 | --primary-4: hsl(var(--primary-hue), 70%, 25%);
18 | --primary-5: hsl(var(--primary-hue), 70%, 15%);
19 |
20 | /* colors */
21 | --pink: hsl(326, 100%, 74%);
22 | --green: #29e0a9;
23 | --purple: hsl(253, 35%, 66%);
24 | --purple-dark: hsl(257, 26%, 40%);
25 | --yellow: hsl(55, 70%, 69%);
26 | --blue: hsl(189, 64%, 68%);
27 | --white: hsl(240, 9%, 89%);
28 | --orange: hsl(26, 74%, 65%);
29 | --red: #ff5c39;
30 | }
31 |
32 | body.dark #notion-app .notion-sidebar {
33 | background-color: var(--dark-1) !important;
34 | color: #fff !important;
35 | }
36 |
37 | body.dark .notion-scroller > div:nth-child(1) {
38 | color: #eee !important;
39 | }
40 |
41 | body.dark
42 | #notion-app
43 | > div
44 | > div
45 | > div.notion-sidebar-container
46 | > div:nth-child(1)
47 | > div:nth-child(2)
48 | > div
49 | > div:nth-child(1),
50 | body.dark
51 | #notion-app
52 | > div
53 | > div
54 | > div.notion-sidebar-container
55 | > div:nth-child(1)
56 | > div:nth-child(2)
57 | > div
58 | > div:nth-child(5)
59 | > div:nth-child(1)
60 | > span
61 | > div
62 | > div
63 | > div:nth-child(2),
64 | body.dark .notion-topbar {
65 | color: #fff !important;
66 | background-color: var(--dark-1) !important;
67 | border-bottom: 1px solid var(--primary-4);
68 | }
69 |
70 | body.dark .notion-frame .notion-selectable a {
71 | background: rgb(43, 43, 43) !important;
72 | }
73 |
74 | body.dark
75 | div.notion-overlay-container.notion-default-overlay-container
76 | > div:nth-child(2)
77 | > div
78 | > div:nth-child(2)
79 | > div:nth-child(1)
80 | > div:nth-child(1)
81 | > div:nth-child(3) {
82 | background: transparent !important;
83 | }
84 |
85 | /* scrollbar */
86 |
87 | body.dark .notion-scroller::-webkit-scrollbar {
88 | width: 0.4rem;
89 | height: 0.4rem;
90 | background: var(--dark-1);
91 | }
92 |
93 | body.dark .notion-scroller::-webkit-scrollbar * {
94 | background: transparent;
95 | }
96 |
97 | body.dark .notion-scroller::-webkit-scrollbar-thumb {
98 | background: var(--primary-2) !important;
99 | cursor: pointer;
100 | border-radius: 1.6rem;
101 | }
102 |
103 | body.dark .notion-scroller::-webkit-scrollbar-track {
104 | background: var(--dark-1);
105 | }
106 |
107 | body.dark #notion-app .notion-topbar > div > div:nth-child(2) {
108 | display: none !important;
109 | }
110 |
111 | /* links clickable */
112 |
113 | body.dark .notion-frame .notion-selectable a {
114 | background: var(--dark-1) !important;
115 | }
116 |
117 | /* help button */
118 |
119 | body.dark
120 | #notion-app
121 | > div
122 | > div.notion-cursor-listener
123 | > div.notion-help-button {
124 | background: var(--dark-1) !important;
125 | }
126 |
127 | body.dark .notion-frame,
128 | .notion-cursor-listener,
129 | /* float page */
130 | body.dark .notion-peek-renderer>div>div,
131 | /* every style that has rgb(47, 52, 55) as bg */
132 | [style*='rgb(47, 52, 55)'],
133 | [style*='background: rgb(32, 32, 32)'],
134 | [style*='background: rgb(25, 25, 25)'] {
135 | background: var(--dark-1) !important;
136 | }
137 |
138 | /* when hovering some elements */
139 | [style*="rgb(71, 76, 80)"] {
140 | background: var(--dark-3) !important;
141 | }
142 |
143 | /* floating menu * floating options */
144 | body.dark div[style*="rgb(55, 60, 63)"],
145 | body.dark div[style*="rgb(63, 68, 71)"] {
146 | background-color: var(--dark-1) !important;
147 | }
148 |
149 | /* code block */
150 | body.dark .notion-code-block, body.dark .notion-code-block [style*='background: rgb(32, 32, 32)'] {
151 | background: var(--dark) !important;
152 | }
153 | body.dark [style*="rgba(255, 255, 255, 0.03)"] {
154 | background: rgba(255, 255, 255, 0) !important;
155 | }
156 |
157 | body.dark .notion-code-block .token.keyword,
158 | body.dark .notion-code-block .token.operator,
159 | body.dark .notion-code-block .token.unit,
160 | body.dark .notion-code-block .token.tag,
161 | body.dark .notion-code-block .token.selector,
162 | body.dark .notion-code-block .token.important {
163 | color: var(--pink);
164 | }
165 |
166 | body.dark .notion-code-block .token.property {
167 | color: var(--purple);
168 | }
169 |
170 | body.dark .notion-code-block .token.comment {
171 | color: var(--purple-dark);
172 | }
173 |
174 | body.dark .notion-code-block .token.variable {
175 | color: var(--white);
176 | }
177 |
178 | body.dark .notion-code-block .token.parameter {
179 | color: var(--orange);
180 | }
181 |
182 | body.dark .notion-code-block .token.function,
183 | body.dark .notion-code-block .token.attr-name,
184 | body.dark .notion-code-block .token.punctuation.attr-equals {
185 | color: var(--green);
186 | }
187 |
188 | body.dark .notion-code-block .token.number {
189 | color: var(--blue);
190 | }
191 |
192 | body.dark .notion-code-block .token.string,
193 | body.dark .notion-code-block .token.attr-value {
194 | color: var(--yellow);
195 | }
196 |
197 | /* colors */
198 | body [style*="209, 87, 150"],
199 | body [style*="193, 76, 138"],
200 | body [style*="223, 132, 209"] {
201 | color: var(--pink) !important;
202 | }
203 |
204 | body [style*="223, 84, 82"],
205 | body [style*="212, 76, 71"] {
206 | color: var(--red) !important;
207 | }
208 |
209 | body [style*="68, 131, 97"],
210 | body [style*="82, 158, 114"],
211 | body [style*="113, 178, 131"] {
212 | color: var(--green) !important;
213 | }
214 |
215 | body [style*="217, 115, 13"],
216 | body [style*="199, 125, 72"],
217 | body [style*="217, 133, 56"] {
218 | color: var(--orange) !important;
219 | }
220 |
221 | body [style*="201, 145, 38"],
222 | body [style*="203, 145, 47"],
223 | body [style*="202, 152, 73"] {
224 | color: var(--yellow) !important;
225 | }
226 |
227 | body [style*="51, 126, 169"],
228 | body [style*="94, 135, 201"],
229 | body [style*="102, 170, 218"] {
230 | color: var(--blue) !important;
231 | }
232 |
233 | body [style*="144, 101, 176"],
234 | body [style*="157, 104, 211"],
235 | body [style*="176, 152, 217"] {
236 | color: var(--purple) !important;
237 | }
238 |
239 | .notion-topbar > div:first-child {
240 | -webkit-app-region: drag;
241 | }
242 |
243 | .notion-topbar > div:first-child * {
244 | -webkit-app-region: no-drag;
245 | }
246 |
247 | [style*="background: rgb(37, 37, 37)"] {
248 | background: var(--dark) !important;
249 | }
--------------------------------------------------------------------------------