├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── assets ├── chrome.png ├── firefox.png ├── notionthemes.gif └── notionthemes.png ├── background.js ├── build.sh ├── icons ├── 128.png ├── 16.png └── 48.png ├── js ├── content.js ├── options.js └── popup.js ├── manifest.json ├── options.html ├── package-lock.json └── popup.html /.gitignore: -------------------------------------------------------------------------------- 1 | themes 2 | build -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "themes"] 2 | path = themes 3 | url = https://github.com/notionblog/themes.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 NotionStuff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![NotionThemes](./assets/notionthemes.png) 2 | 3 |

Notion Themes

4 |

Make your Notion pretty with custom themes

5 |
6 | 7 | 8 |
9 |
10 | 11 | ## ⚙️ Installation Instructions 12 | 13 | ### 1. Download and install the extension 14 | 15 | **Google Chrome / Microsoft Edge / Opera** 16 | 17 | 1. Download this repo as a [ZIP file](https://github.com/notionblog/NotionThemes/releases/download/0.0.2/chrome.zip). 18 | 1. Unzip the file and you should have a folder named `chrome`. 19 | 1. In Chrome/Edge go to the extensions page (`chrome://extensions` or `edge://extensions` or `opera:extensions`). 20 | 1. Enable Developer Mode. 21 | 1. Drag the `chrome` folder anywhere on the page to import it (do not delete the folder afterwards). 22 | 23 | **Mozilla Firefox** 24 | 25 | 1. Download this file [firefox.xpi](https://github.com/notionblog/NotionThemes/releases/download/0.0.2/firefox.xpi) 26 | 2. In Firefox go to the addons page `about:addons` or click `CMD+SHIFT+A` 27 | 3. drag and drop the file on the page or click `install Add-on From file ...` 28 | 29 | ### 2. Guide 30 | 31 | 1. Pin the extension in your browser 32 | 2. Open it and click `Choose a theme` blue button 33 | 3. You'll be redirected to options page where you can select a theme 34 | 4. Refresh your Notion page 35 | 5. Enjoy!! 36 | 37 | ## 📝 Note 38 | 39 | - Dark themes will work only if you switched to the dark Mode in Notion 40 | 41 | ## ✨ Feedback 42 | 43 | All bugs, feature requests, pull requests, feedback, etc., are welcome. [Create an issue](https://github.com/notionblog/NotionThemes/issues). 44 | 45 | ## 💖 Donate 46 | 47 | If you’d like to support my work and efforts, your donations mean a lot <3 48 | 49 | Buy Me a Coffee at ko-fi.com 50 | -------------------------------------------------------------------------------- /assets/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/assets/chrome.png -------------------------------------------------------------------------------- /assets/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/assets/firefox.png -------------------------------------------------------------------------------- /assets/notionthemes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/assets/notionthemes.gif -------------------------------------------------------------------------------- /assets/notionthemes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/assets/notionthemes.png -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { 2 | if (request.query == "getTheme") { 3 | const url = request.url; 4 | fetch(url) 5 | .then((response) => response.text()) 6 | .then((response) => sendResponse(response)) 7 | .catch(); 8 | return true; 9 | } else if (request.query == "setTheme") { 10 | const theme = request.theme; 11 | chrome.storage.sync.set({ selectedTheme: theme }, function () { 12 | sendResponse(true); 13 | }); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copy neccesary files 4 | mkdir ext 5 | cp -r ./icons ./js ./background.js ./options.html ./popup.html ./manifest.json ./ext 6 | 7 | mkdir build 8 | cd ext 9 | 10 | # Generate Firefox .xpi 11 | 7z a -r ../build/firefox.xpi * 12 | 13 | # Generate Chrome .crx 14 | 7z a -r ../build/chrome.zip * 15 | 16 | cd .. 17 | # delete uneccesary files 18 | rm -rf ./ext -------------------------------------------------------------------------------- /icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/icons/128.png -------------------------------------------------------------------------------- /icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/icons/16.png -------------------------------------------------------------------------------- /icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionblog/NotionThemes/54fb50a46caf25491f8ff1ff68d3c14aed13e39a/icons/48.png -------------------------------------------------------------------------------- /js/content.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = "https://notionthemes.netlify.app"; 2 | 3 | const sendMessage = async (params) => { 4 | return new Promise((resolve, reject) => { 5 | try { 6 | chrome.runtime.sendMessage(params, (response) => { 7 | resolve(response); 8 | }); 9 | } catch (err) { 10 | reject(err); 11 | } 12 | }); 13 | }; 14 | 15 | const getStorageData = async (params) => { 16 | return new Promise((resolve, reject) => { 17 | try { 18 | chrome.storage.sync.get(params, async (result) => { 19 | if (result) { 20 | resolve(result); 21 | } else { 22 | resolve({ name: "default", path: "default", style: "default" }); 23 | } 24 | }); 25 | } catch (err) { 26 | reject(err); 27 | } 28 | }); 29 | }; 30 | 31 | const getTheme = async () => { 32 | let { path, name, style, font } = await getStorageData([ 33 | "path", 34 | "name", 35 | "style", 36 | "font", 37 | ]); 38 | 39 | if (path && name && style) { 40 | const global = await sendMessage({ 41 | query: "getTheme", 42 | url: `${BASE_URL}/${style}/global.css`, 43 | }); 44 | const theme = await sendMessage({ 45 | query: "getTheme", 46 | url: `${BASE_URL}/${path}`, 47 | }); 48 | let customFont = ``; 49 | 50 | if (global !== undefined && theme !== undefined) { 51 | const head = document.head || document.getElementsByTagName("head")[0], 52 | style = document.createElement("style"); 53 | head.appendChild(style); 54 | style.type = "text/css"; 55 | 56 | if (font && font !== "Default") { 57 | customFont = ` 58 | @import url('https://fonts.googleapis.com/css2?family=${font.replace( 59 | " ", 60 | "+" 61 | )}:wght@300;400;500;700&display=swap'); 62 | #notion-app * { 63 | font-family:${font}, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol" !important; 64 | } 65 | `; 66 | } 67 | 68 | if (style.styleSheet) { 69 | style.styleSheet.cssText = `${customFont} \n ${theme} \n ${global}`; 70 | } else { 71 | style.appendChild( 72 | document.createTextNode(`${customFont} \n ${theme} \n ${global}`) 73 | ); 74 | } 75 | } else { 76 | console.log("error fetching theme"); 77 | } 78 | } 79 | }; 80 | 81 | getTheme(); 82 | -------------------------------------------------------------------------------- /js/options.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = "https://notionthemes.netlify.app"; 2 | 3 | // Fetch themes from the json config file 4 | const getThemes = async () => { 5 | try { 6 | const data = await fetch(`${BASE_URL}/themes.json`); 7 | return await data.json(); 8 | } catch (err) { 9 | console.error(err); 10 | return null; 11 | } 12 | }; 13 | 14 | // Fetch fonts 15 | const getFonts = async () => { 16 | try { 17 | const data = await fetch(`${BASE_URL}/fonts.json`); 18 | return await data.json(); 19 | } catch (err) { 20 | console.error(err); 21 | return null; 22 | } 23 | }; 24 | 25 | // get Storage Data 26 | const getStorageData = async (params) => { 27 | return new Promise((resolve, reject) => { 28 | try { 29 | chrome.storage.sync.get(params, async (result) => { 30 | if (result) { 31 | resolve(result); 32 | } else { 33 | resolve({ name: "default" }); 34 | } 35 | }); 36 | } catch (err) { 37 | reject(err); 38 | } 39 | }); 40 | }; 41 | 42 | // Set storage data 43 | const setStorageData = async (params) => { 44 | return new Promise((resolve, reject) => { 45 | try { 46 | chrome.storage.sync.set(params, async (result) => { 47 | if (result) { 48 | resolve(true); 49 | } else { 50 | resolve(false); 51 | } 52 | }); 53 | } catch (err) { 54 | reject(err); 55 | } 56 | }); 57 | }; 58 | 59 | // Set theme 60 | const setTheme = async (theme, target) => { 61 | chrome.storage.sync.set( 62 | { 63 | path: theme.path, 64 | name: theme.name, 65 | img: theme.img, 66 | style: theme.style, 67 | }, 68 | function () { 69 | const toast = document.querySelector("#mainToast"); 70 | toast.classList.remove("hidden"); 71 | 72 | const selected = document.querySelector(".selected"); 73 | if (selected) { 74 | selected.classList.remove("selected"); 75 | } 76 | if (target) { 77 | target.classList.add("selected"); 78 | } 79 | 80 | setTimeout(() => { 81 | toast.classList.add("hidden"); 82 | }, 3000); 83 | } 84 | ); 85 | }; 86 | 87 | // Preview fonts 88 | const previewFonts = (data) => { 89 | const select = document.getElementById("selectFont"); 90 | data.forEach((el) => { 91 | let opt = document.createElement("option"); 92 | opt.value = el; 93 | opt.innerHTML = el; 94 | select.appendChild(opt); 95 | }); 96 | }; 97 | 98 | // Preview Themes 99 | const previewThemes = async (data) => { 100 | const themes = Object.entries(data).map((theme) => { 101 | return { 102 | name: theme[0], 103 | ...theme[1], 104 | }; 105 | }); 106 | 107 | let { name } = await getStorageData(["name"]); 108 | 109 | // Filter themes with style type 110 | const dark = themes.filter((theme) => theme.style.toLowerCase() === "dark"); 111 | const ligth = themes.filter((theme) => theme.style.toLowerCase() === "light"); 112 | 113 | // Load themes to DOM 114 | const darkThemesContainer = document.querySelector("#darkThemes"); 115 | const ligthThemesContainer = document.querySelector("#lightThemes"); 116 | 117 | dark.forEach((theme) => { 118 | const li = document.createElement("li"); 119 | li.setAttribute("id", theme.name); 120 | li.setAttribute("class", `theme ${name === theme.name ? "selected" : ""}`); 121 | darkThemesContainer.appendChild(li); 122 | li.innerHTML = `${theme.name} 123 |
124 | 125 | ${theme.name} 126 | 127 |
`; 128 | li.addEventListener("click", (event) => { 129 | setTheme(theme, event.currentTarget); 130 | }); 131 | }); 132 | ligth.forEach((theme) => { 133 | const li = document.createElement("li"); 134 | li.setAttribute("id", theme.name); 135 | li.setAttribute("class", `theme ${name === theme.name ? "selected" : ""}`); 136 | ligthThemesContainer.appendChild(li); 137 | li.innerHTML = `${theme.name} 138 |
139 | 140 | ${theme.name} 141 | 142 |
`; 143 | li.addEventListener("click", (event) => { 144 | setTheme(theme, event.currentTarget); 145 | }); 146 | }); 147 | }; 148 | 149 | // Reset to default theme 150 | const resetTheme = async () => { 151 | await setTheme({ 152 | path: null, 153 | name: "default", 154 | img: null, 155 | style: null, 156 | }); 157 | }; 158 | 159 | // Config selected theme 160 | 161 | const fontSelectConfig = async () => { 162 | let { font } = await getStorageData(["font"]); 163 | const selectFont = document.querySelector("#selectFont"); 164 | selectFont.value = font; 165 | }; 166 | 167 | // Load Themes 168 | window.onload = async () => { 169 | /* 170 | State 171 | */ 172 | // Themes 173 | const data = await getThemes(); 174 | if (data !== null) previewThemes(data); 175 | 176 | // Fonts 177 | const { fonts } = await getFonts(); 178 | if (fonts !== null) previewFonts(fonts); 179 | 180 | // Selected font 181 | fontSelectConfig(); 182 | 183 | /* 184 | Actions 185 | */ 186 | 187 | // Reset button 188 | const resetBtn = document.querySelector("#resetTheme"); 189 | resetBtn.addEventListener("click", () => { 190 | resetTheme(); 191 | }); 192 | // selectFont 193 | const selectFont = document.querySelector("#selectFont"); 194 | selectFont.addEventListener("change", async (event) => { 195 | console.log(event.target.value); 196 | await setStorageData({ font: event.target.value }); 197 | }); 198 | }; 199 | -------------------------------------------------------------------------------- /js/popup.js: -------------------------------------------------------------------------------- 1 | document.querySelector("#go-to-options").addEventListener("click", function () { 2 | if (chrome.runtime.openOptionsPage) { 3 | chrome.runtime.openOptionsPage(); 4 | } else { 5 | window.open(chrome.runtime.getURL("options.html")); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Notion Themes", 4 | "version": "0.0.2", 5 | "description": "Make your Notion pretty with custom themes.", 6 | "permissions": ["storage"], 7 | "host_permissions": ["https://notionthemes.netlify.app/"], 8 | "icons": { 9 | "16": "icons/16.png", 10 | "48": "icons/48.png", 11 | "128": "icons/128.png" 12 | }, 13 | "background": { 14 | "service_worker": "background.js" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": ["https://www.notion.so/*"], 19 | "js": ["js/content.js"] 20 | } 21 | ], 22 | "action": { 23 | "default_title": "Choose a theme", 24 | "default_popup": "popup.html", 25 | "default_icon": { 26 | "19": "icons/16.png", 27 | "38": "icons/48.png" 28 | } 29 | }, 30 | "options_ui": { 31 | "page": "options.html", 32 | "open_in_tab": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Choose a Notion theme 4 | 5 | 6 | 163 | 164 | 165 | 166 |
167 | 193 | 198 | 199 |

🎨 Select a theme

200 |
201 |

☀️ Light

202 | 203 |
204 | 205 |
206 |

🌙 Dark

207 | 208 |
209 |

⚙️ Configuration

210 | 211 |
212 |

🔮 Font

213 | 218 |
219 |
220 |
221 |

🔄 Reset to default theme

222 | 223 |
224 |
225 | 226 | 234 |
235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NotionThemes", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notion Build 4 | 22 | 23 | 24 | 25 |
26 |
27 | Notion Web Themes 28 |
29 | 30 |
31 | 32 |
33 |
34 | 39 |
40 | 41 | 42 | 43 | --------------------------------------------------------------------------------