├── .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 | 
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 |
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 = `
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 = `
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 |
168 |
169 |
192 |
193 |
194 | The theme has been applied 🥳 Don't forget to refresh Notion to see the changes
197 |
198 |
199 |
🎨 Select a theme
200 |
201 |
☀️ Light
202 |
203 |
204 |
205 |
209 |
⚙️ Configuration
210 |
211 |
212 |
🔮 Font
213 |
214 |
215 | Default
216 |
217 |
218 |
219 |
220 |
221 |
🔄 Reset to default theme
222 | Reset
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 |
28 |
29 |
30 |
31 | Choose a theme
32 |
33 |
34 |
35 | NotionThemes • Made By
36 | Yudax •
37 | Donate
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------