├── .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 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 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 |
13 | ${fs.readFileSync(path.resolve(iconsFolder, 'minimize.svg'))} 14 |
15 |
16 | ${fs.readFileSync(path.resolve(iconsFolder, 'square.svg'))} 17 |
18 |
19 | ${fs.readFileSync(path.resolve(iconsFolder, 'close.svg'))} 20 |
` 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 | ![Preview](.github/preview.png) 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 | ![Imgur](https://i.imgur.com/NkJz7cn.gif) 51 | 52 | --- 53 | 54 | ## Slide Presentation 55 | 56 | You can use this app as a slide presentation. 57 | 58 | ![Slides preview](https://i.imgur.com/9QL2WTy.gif) 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 ", 30 | "license": "MIT", 31 | "dependencies": { 32 | "electron-squirrel-startup": "^1.0.0", 33 | "electron-window-state": "^5.0.3" 34 | }, 35 | "config": { 36 | "forge": { 37 | "packagerConfig": { 38 | "name": "notion-omni", 39 | "executableName": "notion-omni", 40 | "icon": "assets/icon", 41 | "extraResource": [ 42 | "assets", 43 | "src" 44 | ] 45 | }, 46 | "plugins": [ 47 | [ 48 | "@electron-forge/plugin-webpack", 49 | { 50 | "mainConfig": "./webpack/main.webpack.js", 51 | "renderer": { 52 | "config": "./webpack/renderer.webpack.js", 53 | "entryPoints": [ 54 | { 55 | "js": "./src/renderer/preload.js", 56 | "name": "main_window", 57 | "preload": { 58 | "js": "./src/renderer/preload.js" 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | ] 65 | ], 66 | "makers": [ 67 | { 68 | "name": "@electron-forge/maker-squirrel", 69 | "config": { 70 | "name": "notion-omni", 71 | "setupExe": "${name}-${version}-setup.exe", 72 | "setupIcon": "./assets/icon.ico", 73 | "iconUrl": "https://raw.githubusercontent.com/maykbrito/electron-desktop-custom-notion-omni/feat/slides/assets/icon.ico" 74 | } 75 | }, 76 | { 77 | "name": "@electron-forge/maker-zip", 78 | "platforms": [ 79 | "darwin" 80 | ] 81 | }, 82 | { 83 | "name": "@electron-forge/maker-deb", 84 | "config": {} 85 | }, 86 | { 87 | "name": "@electron-forge/maker-rpm", 88 | "config": {} 89 | } 90 | ], 91 | "publishers": [ 92 | { 93 | "name": "@electron-forge/publisher-github", 94 | "config": { 95 | "repository": { 96 | "owner": "maykbrito", 97 | "name": "notion-omni" 98 | }, 99 | "draft": true 100 | } 101 | } 102 | ] 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/renderer/modules/slides/configurator/highlight.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Theme: Default 3 | Description: Original highlight.js style 4 | Author: (c) Ivan Sagalaev 5 | Maintainer: @highlightjs/core-team 6 | Website: https://highlightjs.org/ 7 | License: see project LICENSE 8 | Touched: 2021 9 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} 10 | 11 | 12 | /*! 13 | Theme: GitHub Dark 14 | Description: Dark theme as seen on github.com 15 | Author: github.com 16 | Maintainer: @Hirse 17 | Updated: 2021-05-15 18 | 19 | Outdated base version: https://github.com/primer/github-syntax-dark 20 | Current colors taken from GitHub's CSS 21 | */ 22 | 23 | .hljs { 24 | color: #c9d1d9; 25 | background: var(--dark-0); 26 | } 27 | 28 | .hljs-doctag, 29 | .hljs-keyword, 30 | .hljs-meta .hljs-keyword, 31 | .hljs-template-tag, 32 | .hljs-template-variable, 33 | .hljs-type, 34 | .hljs-variable.language_ { 35 | /* prettylights-syntax-keyword */ 36 | color: #ff7b72; 37 | } 38 | 39 | .hljs-title, 40 | .hljs-title.class_, 41 | .hljs-title.class_.inherited__, 42 | .hljs-title.function_ { 43 | /* prettylights-syntax-entity */ 44 | color: #d2a8ff; 45 | } 46 | 47 | .hljs-attr, 48 | .hljs-attribute, 49 | .hljs-literal, 50 | .hljs-meta, 51 | .hljs-number, 52 | .hljs-operator, 53 | .hljs-variable, 54 | .hljs-selector-attr, 55 | .hljs-selector-class, 56 | .hljs-selector-id { 57 | /* prettylights-syntax-constant */ 58 | color: #79c0ff; 59 | } 60 | 61 | .hljs-regexp, 62 | .hljs-string, 63 | .hljs-meta .hljs-string { 64 | /* prettylights-syntax-string */ 65 | color: #a5d6ff; 66 | } 67 | 68 | .hljs-built_in, 69 | .hljs-symbol { 70 | /* prettylights-syntax-variable */ 71 | color: #ffa657; 72 | } 73 | 74 | .hljs-comment, 75 | .hljs-code, 76 | .hljs-formula { 77 | /* prettylights-syntax-comment */ 78 | color: #8b949e; 79 | } 80 | 81 | .hljs-name, 82 | .hljs-quote, 83 | .hljs-selector-tag, 84 | .hljs-selector-pseudo { 85 | /* prettylights-syntax-entity-tag */ 86 | color: #7ee787; 87 | } 88 | 89 | .hljs-subst { 90 | /* prettylights-syntax-storage-modifier-import */ 91 | color: #c9d1d9; 92 | } 93 | 94 | .hljs-section { 95 | /* prettylights-syntax-markup-heading */ 96 | color: #1f6feb; 97 | font-weight: bold; 98 | } 99 | 100 | .hljs-bullet { 101 | /* prettylights-syntax-markup-list */ 102 | color: #f2cc60; 103 | } 104 | 105 | .hljs-emphasis { 106 | /* prettylights-syntax-markup-italic */ 107 | color: #c9d1d9; 108 | font-style: italic; 109 | } 110 | 111 | .hljs-strong { 112 | /* prettylights-syntax-markup-bold */ 113 | color: #c9d1d9; 114 | font-weight: bold; 115 | } 116 | 117 | .hljs-addition { 118 | /* prettylights-syntax-markup-inserted */ 119 | color: #aff5b4; 120 | background-color: #033a16; 121 | } 122 | 123 | .hljs-deletion { 124 | /* prettylights-syntax-markup-deleted */ 125 | color: #ffdcd7; 126 | background-color: #67060c; 127 | } 128 | 129 | /* 130 | .hljs-char.escape_, 131 | .hljs-link, 132 | .hljs-params, 133 | .hljs-property, 134 | .hljs-punctuation, 135 | .hljs-tag { 136 | purposely ignored 137 | }*/ -------------------------------------------------------------------------------- /src/renderer/modules/slides/configurator/modal.js: -------------------------------------------------------------------------------- 1 | const Modal = { 2 | content: `
3 |

Inserir Script

4 |
5 | 11 | 17 | 20 | 26 |
27 |
28 |
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 | } --------------------------------------------------------------------------------