diff --git a/Extension/.gitignore b/Extension/.gitignore new file mode 100644 index 0000000..ea5c73c --- /dev/null +++ b/Extension/.gitignore @@ -0,0 +1 @@ +packaged-extension.zip diff --git a/Extension/_locales/en/messages.json b/Extension/_locales/en/messages.json index 6c8d733..6e5390d 100644 --- a/Extension/_locales/en/messages.json +++ b/Extension/_locales/en/messages.json @@ -1,10 +1,10 @@ { "extension_name": { - "message": "REPLACEME", + "message": "IndieSearch", "description": "The display name for the extension." }, "extension_description": { - "message": "REPLACEME", + "message": "Easily search across all the pagefind powered sites you visit.", "description": "Description of what the extension does." } } diff --git a/Extension/images/.gitignore b/Extension/images/.gitignore new file mode 100644 index 0000000..aab52d9 --- /dev/null +++ b/Extension/images/.gitignore @@ -0,0 +1 @@ +*.png \ No newline at end of file diff --git a/Extension/images/.gitkeep b/Extension/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Extension/images/app_icon.svg b/Extension/images/app_icon.svg new file mode 100644 index 0000000..b4101e2 --- /dev/null +++ b/Extension/images/app_icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Extension/images/toolbar.svg b/Extension/images/toolbar.svg new file mode 120000 index 0000000..254a99c --- /dev/null +++ b/Extension/images/toolbar.svg @@ -0,0 +1 @@ +app_icon.svg \ No newline at end of file diff --git a/Extension/manifest.json b/Extension/manifest.json index 1a5dcd6..f9f4a88 100644 --- a/Extension/manifest.json +++ b/Extension/manifest.json @@ -19,15 +19,19 @@ }, "host_permissions": [], - + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';", + "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'; child-src 'self';" + }, "content_scripts": [ { "js": ["dist/content.js"], - "matches": ["*://example.com/*"] + "matches": [""] } ], "action": { + "default": "Start a search", "default_popup": "public/popup.html", "default_icon": { "16": "images/toolbar_16px.png", @@ -39,7 +43,7 @@ } }, - "permissions": ["storage"], + "permissions": ["storage", "tabs", "activeTab", "scripting", "http://*/", "https://*/"], "options_ui": { "page": "public/settings.html", diff --git a/Extension/package-lock.json b/Extension/package-lock.json index 1f16bf9..940bb1a 100644 --- a/Extension/package-lock.json +++ b/Extension/package-lock.json @@ -1,15 +1,16 @@ { - "name": "REPLACEME", - "version": "1.0.0", + "name": "indiesearch", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "REPLACEME", - "version": "1.0.0", + "name": "indiesearch", + "version": "0.1.0", "license": "MIT", "dependencies": { "detect-browser": "^5.3.0", + "pagefind": "^1.0.4", "svelte": "^4.0.5", "webextension-polyfill": "^0.10.0" }, @@ -438,6 +439,66 @@ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.0.4.tgz", + "integrity": "sha512-2OcthvceX2xhm5XbgOmW+lT45oLuHqCmvFeFtxh1gsuP5cO8vcD8ZH8Laj4pXQFCcK6eAdSShx+Ztx/LsQWZFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.0.4.tgz", + "integrity": "sha512-xkdvp0D9Ld/ZKsjo/y1bgfhTEU72ITimd2PMMQtts7jf6JPIOJbsiErCvm37m/qMFuPGEq/8d+fZ4pydOj08HQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.0.4.tgz", + "integrity": "sha512-jGBrcCzIrMnNxLKVtogaQyajVfTAXM59KlBEwg6vTn8NW4fQ6nuFbbhlG4dTIsaamjEM5e8ZBEAKZfTB/qd9xw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.0.4.tgz", + "integrity": "sha512-LIn/QcvcEtLEBqKe5vpSbSC2O3fvqbRCWOTIklslqSORisCsvzsWbP6j+LYxE9q0oWIfkdMoWV1vrE/oCKRxHg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.0.4.tgz", + "integrity": "sha512-QlBCVeZfj9fc9sbUgdOz76ZDbeK4xZihOBAFqGuRJeChfM8pnVeH9iqSnXgO3+m9oITugTf7PicyRUFAG76xeQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@tsconfig/svelte": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.0.tgz", @@ -790,6 +851,21 @@ "wrappy": "1" } }, + "node_modules/pagefind": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.0.4.tgz", + "integrity": "sha512-oRIizYe+zSI2Jw4zcMU0ebDZm27751hRFiSOBLwc1OIYMrsZKk+3m8p9EVaOmc6zZdtqwwdilNUNxXvBeHcP9w==", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.0.4", + "@pagefind/darwin-x64": "1.0.4", + "@pagefind/linux-arm64": "1.0.4", + "@pagefind/linux-x64": "1.0.4", + "@pagefind/windows-x64": "1.0.4" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", diff --git a/Extension/package.json b/Extension/package.json index 8ff1586..1cbc3c1 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -1,17 +1,19 @@ { - "name": "REPLACEME", - "version": "1.0.0", + "name": "indiesearch", + "version": "0.1.0", "description": "", "main": "background.js", + "type": "module", "scripts": { "format": "prettier --write .", "build": "node scripts/build.js", "build:prod": "node scripts/prod-build.js", "build:chrome": "node scripts/package-extension.js", - "build:firefox": "node scripts/firefox-build.js" + "build:firefox": "node scripts/firefox-build.js", + "generate:icons": "node scripts/generate-icons.js" }, "keywords": [], - "author": "REPLACEME", + "author": "hello@byjp.me", "license": "MIT", "devDependencies": { "@tsconfig/svelte": "^5.0.0", @@ -34,6 +36,7 @@ }, "dependencies": { "detect-browser": "^5.3.0", + "pagefind": "^1.0.4", "svelte": "^4.0.5", "webextension-polyfill": "^0.10.0" } diff --git a/Extension/public/.gitignore b/Extension/public/.gitignore new file mode 100644 index 0000000..0935e32 --- /dev/null +++ b/Extension/public/.gitignore @@ -0,0 +1 @@ +pagefind/ \ No newline at end of file diff --git a/Extension/public/css/water.css b/Extension/public/css/water.css new file mode 100644 index 0000000..faa2dc1 --- /dev/null +++ b/Extension/public/css/water.css @@ -0,0 +1,2 @@ +/* https://watercss.kognise.dev/ */ +button,input,select{margin-right:6px}b,h1,h2,h3,h4,h5,h6,legend,strong,th{font-weight:600}input,pre>code,select,textarea{display:block}table,textarea:not([cols]){width:100%}table caption,td,th{text-align:left}input,td,th{vertical-align:top}body,td,th{word-wrap:break-word}input,select,tbody tr:nth-child(2n),textarea{background-color:#efefef}code,kbd,samp,time{background:#efefef}details[open] summary,table{margin-bottom:10px}button,input[type=button],input[type=checkbox],input[type=radio],input[type=range],input[type=reset],input[type=submit],select,summary{cursor:pointer}details,dialog{background-color:#f7f7f7}body,dialog,kbd{color:#363636}:root{--background-body:#fff;--background:#efefef;--background-alt:#f7f7f7;--selection:#9e9e9e;--text-main:#363636;--text-bright:#000;--text-muted:#70777f;--links:#0076d1;--focus:#0096bfab;--border:#dbdbdb;--code:#000;--animation-duration:0.1s;--button-base:#d0cfcf;--button-hover:#9b9b9b;--scrollbar-thumb:rgb(170, 170, 170);--scrollbar-thumb-hover:var(--button-hover);--form-placeholder:#949494;--form-text:#1d1d1d;--variable:#39a33c;--highlight:#ff0;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E")}html{scrollbar-color:rgb(170,170,170) #fff;scrollbar-color:var(--scrollbar-thumb) var(--background-body);scrollbar-width:thin}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue','Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji',sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;color:var(--text-main);background:#fff;background:var(--background-body);text-rendering:optimizeLegibility}button,input,textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}h1{font-size:2.2em;color:#000;color:var(--text-bright)}h1,h2,h3,h4,h5,h6{margin-bottom:12px;margin-top:24px}h2,h3,h4,h5,h6,strong{color:#000;color:var(--text-bright)}q::after,q::before{content:none}blockquote,q{border-left:4px solid #0096bfab;border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}address,blockquote cite,blockquote>footer,var{font-style:normal}blockquote>footer{border:0}a[href^='mailto\:']::before{content:'📧 '}a[href^='tel\:']::before{content:'📞 '}a[href^='sms\:']::before{content:'💬 '}mark{background-color:#ff0;background-color:var(--highlight);border-radius:2px;padding:0 2px;color:#000}a>code,a>strong{color:inherit}[type=checkbox],[type=radio]{display:initial}input{color:#1d1d1d;color:var(--form-text);background-color:var(--background);font-family:inherit;font-size:inherit;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:0}button,textarea{border:none;color:#1d1d1d;font-family:inherit;font-size:inherit;margin-bottom:6px;padding:10px;outline:0}button{color:var(--form-text);background-color:var(--background);border-radius:6px}textarea{color:var(--form-text);background-color:var(--background);border-radius:6px;margin-right:0;box-sizing:border-box;resize:vertical}select{color:#1d1d1d;color:var(--form-text);background-color:var(--background);font-family:inherit;font-size:inherit;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:0}@media (prefers-color-scheme:dark){:root{--background-body:#202b38;--background:#161f27;--background-alt:#1a242f;--selection:#1c76c5;--text-main:#dbdbdb;--text-bright:#fff;--text-muted:#a9b1ba;--links:#41adff;--focus:#0096bfab;--border:#526980;--code:#ffbe85;--animation-duration:0.1s;--button-base:#0c151c;--button-hover:#040a0f;--scrollbar-thumb:var(--button-hover);--scrollbar-thumb-hover:rgb(0, 0, 0);--form-placeholder:#a9a9a9;--form-text:#fff;--variable:#d941e2;--highlight:#efdb43;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E")}html{scrollbar-color:#040a0f #202b38;scrollbar-color:var(--scrollbar-thumb) var(--background-body)}body{background:#202b38;background:var(--background-body);color:#dbdbdb;color:var(--text-main)}button,input,textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}h1,h2,h3,h4,h5,h6,strong{color:#fff;color:var(--text-bright)}blockquote,q{border-left:4px solid #0096bfab;border-left:4px solid var(--focus)}mark{background-color:#efdb43;background-color:var(--highlight)}button,input,select,textarea{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button,input[type=button],input[type=reset],input[type=submit]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}button:hover,input[type=button]:hover,input[type=reset]:hover,input[type=submit]:hover{background:#9b9b9b;background:var(--button-hover)}input[type=color]{min-height:2rem;padding:8px;cursor:pointer}input[type=checkbox],input[type=radio]{height:1em;width:1em}input[type=radio]{border-radius:100%}label{vertical-align:middle;margin-bottom:4px;display:inline-block}button,input:not([type=checkbox]):not([type=radio]),input[type=range],select,textarea{-webkit-appearance:none}textarea:not([rows]){min-height:40px;height:140px}select{background:url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat #efefef;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat;padding-right:35px}select::-ms-expand{display:none}select[multiple]{padding-right:10px;background-image:none;overflow-y:auto}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px #0096bfab;box-shadow:0 0 0 2px var(--focus)}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=range]:active,input[type=reset]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-moz-placeholder{color:#949494;color:var(--form-placeholder)}:-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::placeholder{color:#949494;color:var(--form-placeholder)}fieldset{border:1px solid #0096bfab;border:1px var(--focus) solid;border-radius:6px;margin:0 0 12px;padding:10px}legend{font-size:.9em}input[type=range]{margin:10px 0;padding:10px 0;background:0 0}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{width:100%;height:9.5px;-webkit-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}input[type=range]::-webkit-slider-thumb{box-shadow:0 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border);-webkit-appearance:none;margin-top:-7px}input[type=range]:focus::-webkit-slider-runnable-track{background:#efefef;background:var(--background)}input[type=range]::-moz-range-track{width:100%;height:9.5px;-moz-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}input[type=range]::-moz-range-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}input[type=range]::-ms-track{width:100%;height:9.5px;background:0 0;border-color:transparent;border-width:16px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}input[type=range]::-ms-fill-upper{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}input[type=range]::-ms-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;border:1px solid #000;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}input[type=range]:focus::-ms-fill-lower{background:#efefef;background:var(--background)}input[type=range]:focus::-ms-fill-upper{background:#efefef;background:var(--background)}a{text-decoration:none;color:#0076d1;color:var(--links)}a:hover,summary:focus,summary:hover{text-decoration:underline}code,samp,time{background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}pre>code{padding:10px;overflow-x:auto}var{color:#39a33c;color:var(--variable);font-family:monospace}kbd{background:var(--background);border:1px solid #dbdbdb;border:1px solid var(--border);border-radius:2px;color:var(--text-main);padding:2px 4px}img,video{max-width:100%;height:auto}hr{border:none;border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}table{border-collapse:collapse;table-layout:fixed}td,th{padding:6px}thead{border-bottom:1px solid #dbdbdb;border-bottom:1px solid var(--border)}tfoot{border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}tbody tr:nth-child(2n){background-color:var(--background)}tbody tr:nth-child(2n) button{background-color:#f7f7f7;background-color:var(--background-alt)}tbody tr:nth-child(2n) button:hover{background-color:#fff;background-color:var(--background-body)}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;background:var(--background);border-radius:6px}::-webkit-scrollbar-thumb{background:#aaa;background:var(--scrollbar-thumb);border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#9b9b9b;background:var(--scrollbar-thumb-hover)}::-moz-selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}::selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}details{display:flex;flex-direction:column;align-items:flex-start;background-color:var(--background-alt);padding:10px 10px 0;margin:1em 0;border-radius:6px;overflow:hidden}dialog>header:first-child,summary{background-color:#efefef;padding:10px}details[open]{padding:10px}details>:last-child{margin-bottom:0}summary{display:list-item;background-color:var(--background);margin:-10px -10px 0;outline:0}details>:not(summary){margin-top:0}summary::-webkit-details-marker{color:#363636;color:var(--text-main)}dialog{background-color:var(--background-alt);color:var(--text-main);border:#dbdbdb;border-radius:6px;border-color:var(--border);padding:10px 30px}dialog>header:first-child{background-color:var(--background);border-radius:6px 6px 0 0;margin:-10px -30px 10px;text-align:center}dialog::-webkit-backdrop{background:#0000009c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}dialog::backdrop{background:#0000009c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}footer{border-top:1px solid #dbdbdb;border-top:1px solid var(--border);padding-top:10px;color:#70777f;color:var(--text-muted)}@media (prefers-color-scheme:dark){button,input[type=button],input[type=reset],input[type=submit]{background-color:#0c151c;background-color:var(--button-base)}button:hover,input[type=button]:hover,input[type=reset]:hover,input[type=submit]:hover{background:#040a0f;background:var(--button-hover)}select{background:url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat #161f27;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px #0096bfab;box-shadow:0 0 0 2px var(--focus)}::-moz-placeholder{color:#a9a9a9;color:var(--form-placeholder)}:-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::placeholder{color:#a9a9a9;color:var(--form-placeholder)}fieldset{border:1px solid #0096bfab;border:1px var(--focus) solid}input[type=range]::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}input[type=range]::-webkit-slider-thumb{background:#526980;background:var(--border)}input[type=range]:focus::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}input[type=range]::-moz-range-track{background:#161f27;background:var(--background)}input[type=range]::-moz-range-thumb{background:#526980;background:var(--border)}input[type=range]::-ms-fill-lower{background:#161f27;background:var(--background)}input[type=range]::-ms-fill-upper{background:#161f27;background:var(--background)}input[type=range]::-ms-thumb{background:#526980;background:var(--border)}input[type=range]:focus::-ms-fill-lower{background:#161f27;background:var(--background)}input[type=range]:focus::-ms-fill-upper{background:#161f27;background:var(--background)}a{color:#41adff;color:var(--links)}code,samp,time{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}var{color:#d941e2;color:var(--variable)}kbd{color:#dbdbdb;color:var(--text-main);border:1px solid #526980;border:1px solid var(--border);background:#161f27;background:var(--background)}hr,tfoot{border-top:1px solid #526980;border-top:1px solid var(--border)}thead{border-bottom:1px solid #526980;border-bottom:1px solid var(--border)}dialog>header:first-child,summary,tbody tr:nth-child(2n){background-color:#161f27;background-color:var(--background)}details,tbody tr:nth-child(2n) button{background-color:#1a242f;background-color:var(--background-alt)}tbody tr:nth-child(2n) button:hover{background-color:#202b38;background-color:var(--background-body)}::-webkit-scrollbar-track{background:#161f27;background:var(--background)}::-webkit-scrollbar-thumb{background:#040a0f;background:var(--scrollbar-thumb)}::-webkit-scrollbar-thumb:hover{background:#000;background:var(--scrollbar-thumb-hover)}::-moz-selection{color:#fff;color:var(--text-bright);background-color:#1c76c5;background-color:var(--selection)}::selection{color:#fff;color:var(--text-bright);background-color:#1c76c5;background-color:var(--selection)}summary::-webkit-details-marker{color:#dbdbdb;color:var(--text-main)}dialog{border-color:#526980;border-color:var(--border);color:#dbdbdb;color:var(--text-main);background-color:#1a242f;background-color:var(--background-alt)}footer{color:#a9b1ba;color:var(--text-muted);border-top:1px solid #526980;border-top:1px solid var(--border)}}body>footer{margin-top:40px}@media print{body,button,code,details,input,pre,summary,textarea{background-color:#fff}button,input,textarea{border:1px solid #000}body,button,code,footer,h1,h2,h3,h4,h5,h6,input,pre,strong,summary,textarea{color:#000}summary::marker{color:#000}summary::-webkit-details-marker{color:#000}tbody tr:nth-child(2n){background-color:#f2f2f2}a{color:#00f;text-decoration:underline}} \ No newline at end of file diff --git a/Extension/public/help.html b/Extension/public/help.html new file mode 100644 index 0000000..02657dd --- /dev/null +++ b/Extension/public/help.html @@ -0,0 +1,17 @@ + + + + + + IndieSearch: Help + + + + + +
+

Help

+

This page is here to help you use IndieSearch.

+
+ + diff --git a/Extension/public/popup.html b/Extension/public/popup.html index cf47d5f..2a43335 100644 --- a/Extension/public/popup.html +++ b/Extension/public/popup.html @@ -3,11 +3,9 @@ + IndieSearch + - diff --git a/Extension/public/search.html b/Extension/public/search.html new file mode 100644 index 0000000..7ca1df8 --- /dev/null +++ b/Extension/public/search.html @@ -0,0 +1,22 @@ + + + + + + IndieSearch + + + + + + + + + +
+ + diff --git a/Extension/public/settings.html b/Extension/public/settings.html index 1a818ca..26d917e 100644 --- a/Extension/public/settings.html +++ b/Extension/public/settings.html @@ -3,15 +3,12 @@ - REPLACEME + IndieSearch: Settings + - -
+
diff --git a/Extension/scripts/build.js b/Extension/scripts/build.js index 94ebaf6..6604fd3 100644 --- a/Extension/scripts/build.js +++ b/Extension/scripts/build.js @@ -1,6 +1,7 @@ -const { build } = require('esbuild'); -const sveltePlugin = require('esbuild-svelte'); -const sveltePreprocess = require('svelte-preprocess'); +import { build } from 'esbuild'; +import sveltePlugin from 'esbuild-svelte'; +import sveltePreprocess from 'svelte-preprocess'; +import * as pagefind from "pagefind"; const isProdBuild = process.argv.includes('--prod'); @@ -55,7 +56,35 @@ async function main() { ] }); - return Promise.all([contentJob, backgroundJob, popupJob, settingsJob]).then( + const searchJob = build({ + ...commonConfig, + entryPoints: ['./src/search/search.ts'], + outbase: './src/search', + outdir: './dist', + mainFields: ['svelte', 'module', 'main', 'browser'], + plugins: [ + sveltePlugin({ + preprocess: sveltePreprocess() + }) + ] + }); + + const createLocalSearchIndex = async () => { + const { index } = await pagefind.createIndex(); + // TODO: Do I want to include the extension's help in the index? + await index.addDirectory({ + path: "./public/", + }); + await index.writeFiles({ + outputPath: "./public/pagefind" + }); + } + + return Promise.all([ + contentJob, backgroundJob, + popupJob, settingsJob, + searchJob, createLocalSearchIndex(), + ]).then( () => console.log('⚡ Compiled') ); } diff --git a/Extension/scripts/generate-icons.js b/Extension/scripts/generate-icons.js new file mode 100644 index 0000000..141239c --- /dev/null +++ b/Extension/scripts/generate-icons.js @@ -0,0 +1,16 @@ +const util = require('util'); +const exec = util.promisify(require('child_process').exec); + +const toolbarSizes = [16, 19, 32, 38, 48, 64, 72]; +const appIconSizes = [48, 96, 128, 256, 512]; + +main(); + +async function main() { + await Promise.all(toolbarSizes.map(size => generatePng("toolbar", size))); + await Promise.all(appIconSizes.map(size => generatePng("app_icon", size))); +} + +async function generatePng(name, size) { + return exec(`rsvg-convert -h ${size} images/${name}.svg > images/${name}_${size}px.png`) +} diff --git a/Extension/src/background.ts b/Extension/src/background.ts index abe1aed..963031b 100644 --- a/Extension/src/background.ts +++ b/Extension/src/background.ts @@ -3,7 +3,6 @@ import { detect } from 'detect-browser'; import { type BrowserMessage, type BrowserMessageType, - type ColorScheme } from './models'; import settingsConnector from './settings-connector'; @@ -11,15 +10,11 @@ console.log('background script running...'); browser.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log('got message', message); + switch (message.type as BrowserMessageType) { - case 'gotColorScheme': { - updateIcon(message.value as ColorScheme).then(sendResponse); + case 'foundSite': { + settingsConnector.newSite(message.searchSpec); return true; } } }); - -async function updateIcon(colorScheme: ColorScheme) { - console.log('updating icon', colorScheme); - // do work here -} diff --git a/Extension/src/content.ts b/Extension/src/content.ts index b79316b..7ee93fa 100644 --- a/Extension/src/content.ts +++ b/Extension/src/content.ts @@ -1,20 +1,37 @@ -import browser from 'webextension-polyfill'; -import { type BrowserMessageType, type ColorScheme } from './models'; +import browser, { find } from 'webextension-polyfill'; +import { type BrowserMessage } from './models'; -browser.runtime.onMessage.addListener(message => { - console.log('got message', message); - switch (message.type as BrowserMessageType) { - case 'getColorScheme': { - return Promise.resolve(getColorScheme()); - } +const pageFindType = 'application/pagefind'; + +function findSearchLink(): void { + const searchLinkEl = document.querySelector(`link[rel="search"][type="${pageFindType}"]`); + if (!searchLinkEl) { + return; + } + const searchHref = searchLinkEl.getAttribute('href'); + if (!searchHref) { + return; } -}); -function getColorScheme() { - let scheme: ColorScheme = 'light'; - const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - if (darkModeMediaQuery.matches) { - scheme = 'dark'; + const baseUrl = getBaseUrl(); + + browser.runtime.sendMessage({ + type: "foundSite", + searchSpec: { + title: searchLinkEl.getAttribute('title'), + bundlePath: new URL(searchHref, baseUrl).toString(), + baseUrl, + } + } as BrowserMessage); +} + +function getBaseUrl(): string { + const baseHref = document.querySelector('base[href]')?.getAttribute('href'); + if (baseHref) { + return new URL(baseHref, document.location.href).toString(); } - return scheme; + + return document.location.origin; } + +findSearchLink(); diff --git a/Extension/src/models.ts b/Extension/src/models.ts index 47ae1f7..e75d918 100644 --- a/Extension/src/models.ts +++ b/Extension/src/models.ts @@ -1,16 +1,23 @@ -export type BrowserMessageType = 'getColorScheme' | 'gotColorScheme'; +export type BrowserMessageType = 'foundSite'; export type BrowserMessage = { - type: BrowserMessageType; - value?: any; + type: 'foundSite'; + searchSpec: SiteSpec; }; export type AppSettings = { - displayHelpMessage: boolean; + sites: { [key: string]: SiteSpec }; }; export const DEFAULT_SETTINGS: AppSettings = { - displayHelpMessage: true + sites: {}, }; -export type ColorScheme = 'light' | 'dark'; +export type SiteSpec = { + title: string; + bundlePath: string; + baseUrl: string; + category?: SiteCategory; +}; + +export type SiteCategory = undefined | 'included' | 'excluded'; diff --git a/Extension/src/popup/SiteList.svelte b/Extension/src/popup/SiteList.svelte new file mode 100644 index 0000000..2fc1ee6 --- /dev/null +++ b/Extension/src/popup/SiteList.svelte @@ -0,0 +1,30 @@ + + +{#if sites.length === 0} + No sites here yet +{:else} +
    + {#each sites as site} +
  1. + {site.title} + {#if showInclude} + + {/if} + {#if showExclude} + + {/if} + {#if showDestroy} + + {/if} +
  2. + {/each} +
+{/if} \ No newline at end of file diff --git a/Extension/src/popup/popup.svelte b/Extension/src/popup/popup.svelte index 0ed6541..6e636b9 100644 --- a/Extension/src/popup/popup.svelte +++ b/Extension/src/popup/popup.svelte @@ -1,14 +1,71 @@ + import SiteList from './SiteList.svelte'; + import settingsConnector from '../settings-connector'; + import type { SiteCategory, SiteSpec } from '../models'; + + type SitesInCategories = { new: SiteSpec[], included: SiteSpec[], excluded: SiteSpec[] } + + const getSites = () => { + sitesProm = settingsConnector.getAppSettings() + .then(settings => Object.values(settings.sites).reduce((acc, site) => { + acc[site.category || 'new'].push(site); + return acc; + }, ( + { + new: [], + included: [], + excluded: [], + } as SitesInCategories + ))) + sitesProm.then(console.log) + }; -
-

My New Web Extension

-
+ let sitesProm: Promise; + getSites(); - + + + const cheatAddSite = (title: string, bundlePath: string, baseUrl: string): Promise => + settingsConnector.newSite({ title, bundlePath, baseUrl }).then(getSites); + + +
+

IndieSearch: Sites

+

Choose which sites are included in your IndieSearches.

+ +

✨ New

+

These are pagefind sites you've stumbled across since the last time you were here. They're included by default.

+ {#await sitesProm} +

…

+ {:then sites} + + {/await} + +

✅ Included

+

You've marked these sites to be included in your IndieSearches.

+ {#await sitesProm} +

…

+ {:then sites} + + {/await} + +

🚫 Excluded

+

These sites will never feature in your IndieSearches.

+ {#await sitesProm} +

…

+ {:then sites} + + {/await} + +

🔗 Cheatcodes

+

Here are a few sites you can add to get started:

+ + + +
diff --git a/Extension/src/search/search.svelte b/Extension/src/search/search.svelte new file mode 100644 index 0000000..a837664 --- /dev/null +++ b/Extension/src/search/search.svelte @@ -0,0 +1,44 @@ + + +

IndieSearch

+{#await siteProm} +

…

+{:then sites} +
+ + +{/await} + + diff --git a/Extension/src/search/search.ts b/Extension/src/search/search.ts new file mode 100644 index 0000000..9a03f7c --- /dev/null +++ b/Extension/src/search/search.ts @@ -0,0 +1,2 @@ +import App from './search.svelte'; +new App({ target: document.getElementById('root') as HTMLDivElement }); diff --git a/Extension/src/settings-connector.ts b/Extension/src/settings-connector.ts index c356524..46f8d07 100644 --- a/Extension/src/settings-connector.ts +++ b/Extension/src/settings-connector.ts @@ -1,32 +1,55 @@ import browser from 'webextension-polyfill'; -import { type AppSettings, DEFAULT_SETTINGS } from './models'; +import { type AppSettings, DEFAULT_SETTINGS, type SiteSpec, type SiteCategory } from './models'; class SettingsConnector { private static readonly settingsKey = 'settings'; + async getAppSettings() { let settings = ( await browser.storage.sync.get(SettingsConnector.settingsKey) )[SettingsConnector.settingsKey] as Partial | undefined; - if ( - !settings || - Object.keys(settings).length !== Object.keys(DEFAULT_SETTINGS).length - ) { + + if (!settings) { console.log('no settings found, using default settings'); await browser.storage.sync.set({ [SettingsConnector.settingsKey]: DEFAULT_SETTINGS }); settings = DEFAULT_SETTINGS; } + return settings as AppSettings; } - async updateSettings(newSettings: Partial) { + async newSite(siteSpec: SiteSpec): Promise { + const settings = await this.getAppSettings(); + if (settings.sites[siteSpec.bundlePath]) { + return; + } + settings.sites[siteSpec.bundlePath] = siteSpec; + + await browser.storage.sync.set({ + [SettingsConnector.settingsKey]: settings + }); + } + + async moveSite(bundlePath: string, category: SiteCategory): Promise { + const settings = await this.getAppSettings(); + if (!settings.sites[bundlePath]) { + return; + } + settings.sites[bundlePath].category = category; + + await browser.storage.sync.set({ + [SettingsConnector.settingsKey]: settings + }); + } + + async removeSite(bundlePath: string): Promise { const settings = await this.getAppSettings(); - const updatedSettings = { ...settings, ...newSettings }; + delete settings.sites[bundlePath]; await browser.storage.sync.set({ - [SettingsConnector.settingsKey]: updatedSettings + [SettingsConnector.settingsKey]: settings }); - return updatedSettings; } } diff --git a/Extension/src/settings/settings.svelte b/Extension/src/settings/settings.svelte index 8a226eb..bec32da 100644 --- a/Extension/src/settings/settings.svelte +++ b/Extension/src/settings/settings.svelte @@ -10,33 +10,12 @@ settingsConnector .getAppSettings() .then(settingsFromStorage => (appSettings = settingsFromStorage)); - - const updateDisplayHelpMessage = (e: InputChangeEvent) => { - const target = e.target as HTMLInputElement; - const displayHelpMessage = target.checked; - settingsConnector.updateSettings({ displayHelpMessage }); - }; - - $: { - if (appSettings?.displayHelpMessage) console.log('Get help on GitHub!'); - } {#if appSettings}

Settings

-
-
- - -
-
+ Go to search
{/if}