Initial prototype of the V3 manifest extension (#4357)

* Initial prototype of the V3 manifest extension

* Make sure the content script is only injected once

* Implement addNote button

* More separation work for tasks, implement archive and update note

* Add back missing functionality, add guide to install Extensions

* Revert v2 changes

---------

Co-authored-by: Thomas Rogers <Podginator@gmail.com>
This commit is contained in:
Jackson Harper
2025-02-23 23:35:41 +08:00
committed by GitHub
parent cc6b1f3b6e
commit 2ec1d6f58c
78 changed files with 4830 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

26
pkg/extension-v3/Makefile Normal file
View File

@ -0,0 +1,26 @@
all: firefox chrome edge
build: *
rm -rf extension
yarn build
firefox:
echo "building firefox package"
rm -rf extension
yarn build
FIREFOX_PKG_NAME="firefox-$(shell cat extension/manifest.json| jq -j .version).zip" ; \
FIREFOX_SRC_NAME="firefox-$(shell cat extension/manifest.json| jq -j .version)-src.zip" ; \
cd extension; zip -r ../$$FIREFOX_PKG_NAME *; cd ..;\
zip -r $$FIREFOX_SRC_NAME src/* Makefile yarn.lock package.json; \
echo "done"
chrome: build
echo "building chrome package"
zip -r chrome-$(shell cat dist/manifest.json| jq -j .version).zip ./dist/*
edge: build
echo "building edge package"
EDGE_PKG_NAME="omnivore-extension-edge-$(shell cat dist/manifest.json| jq -j .version).zip" ; \
pushd dist; zip -r $${EDGE_PKG_NAME} ./*; popd;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,14 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4278)">
<path d="M8 20.8342H11.3333L20.0833 12.0842C20.5254 11.6422 20.7737 11.0427 20.7737 10.4176C20.7737 9.79245 20.5254 9.19293 20.0833 8.7509C19.6413 8.30888 19.0418 8.06055 18.4167 8.06055C17.7915 8.06055 17.192 8.30888 16.75 8.7509L8 17.5009V20.8342Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.9166 9.58398L19.25 12.9173" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.25 16.25L12.5833 19.5833" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23 17.5V20.8333H16.3334L19.6667 17.5H23Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_11897_4278">
<rect width="20" height="20" fill="white" transform="translate(5.5 5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,12 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4271)">
<path d="M10.9166 12.2493C10.9166 12.4704 11.0044 12.6823 11.1607 12.8386C11.317 12.9949 11.5289 13.0827 11.75 13.0827C11.971 13.0827 12.1829 12.9949 12.3392 12.8386C12.4955 12.6823 12.5833 12.4704 12.5833 12.2493C12.5833 12.0283 12.4955 11.8164 12.3392 11.6601C12.1829 11.5038 11.971 11.416 11.75 11.416C11.5289 11.416 11.317 11.5038 11.1607 11.6601C11.0044 11.8164 10.9166 12.0283 10.9166 12.2493Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 11V15.31C8.00009 15.752 8.17575 16.1758 8.48833 16.4883L14.9133 22.9133C15.29 23.2899 15.8007 23.5015 16.3333 23.5015C16.8659 23.5015 17.3767 23.2899 17.7533 22.9133L22.4133 18.2533C22.7899 17.8767 23.0015 17.3659 23.0015 16.8333C23.0015 16.3007 22.7899 15.79 22.4133 15.4133L15.9883 8.98833C15.6758 8.67575 15.252 8.50009 14.81 8.5H10.5C9.83696 8.5 9.20107 8.76339 8.73223 9.23223C8.26339 9.70107 8 10.337 8 11Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_11897_4271">
<rect width="20" height="20" fill="white" transform="translate(5.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle class="circle-bg" cx="15.5" cy="15" r="15" fill="#6A6968"/>
<g clip-path="url(#clip0_11897_4264)">
<path d="M11 9.00021V21.0002C11 21.1337 11.0355 21.2647 11.103 21.3798C11.1705 21.4949 11.2675 21.5899 11.384 21.6551C11.5005 21.7202 11.6322 21.753 11.7657 21.7503C11.8991 21.7475 12.0293 21.7091 12.143 21.6392L21.893 15.6392C22.0022 15.5721 22.0924 15.4781 22.1549 15.3663C22.2175 15.2544 22.2503 15.1284 22.2503 15.0002C22.2503 14.872 22.2175 14.746 22.1549 14.6341C22.0924 14.5223 22.0022 14.4283 21.893 14.3612L12.143 8.36121C12.0293 8.29128 11.8991 8.25295 11.7657 8.25016C11.6322 8.24738 11.5005 8.28024 11.384 8.34536C11.2675 8.41048 11.1705 8.50549 11.103 8.62061C11.0355 8.73572 11 8.86676 11 9.00021Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4264">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle class="circle-bg" cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4264)">
<path d="M11 9.00021V21.0002C11 21.1337 11.0355 21.2647 11.103 21.3798C11.1705 21.4949 11.2675 21.5899 11.384 21.6551C11.5005 21.7202 11.6322 21.753 11.7657 21.7503C11.8991 21.7475 12.0293 21.7091 12.143 21.6392L21.893 15.6392C22.0022 15.5721 22.0924 15.4781 22.1549 15.3663C22.2175 15.2544 22.2503 15.1284 22.2503 15.0002C22.2503 14.872 22.2175 14.746 22.1549 14.6341C22.0924 14.5223 22.0022 14.4283 21.893 14.3612L12.143 8.36121C12.0293 8.29128 11.8991 8.25295 11.7657 8.25016C11.6322 8.24738 11.5005 8.28024 11.384 8.34536C11.2675 8.41048 11.1705 8.50549 11.103 8.62061C11.0355 8.73572 11 8.86676 11 9.00021Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4264">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@ -0,0 +1,3 @@
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.5076 30.1582C23.7381 30.1582 30.5 23.3963 30.5 15.1659C30.5 6.92006 23.7381 0.158203 15.4924 0.158203C7.26186 0.158203 0.5 6.92006 0.5 15.1659C0.5 23.3963 7.26186 30.1582 15.5076 30.1582ZM15.5076 28.9955C7.85849 28.9955 1.66267 22.7997 1.66267 15.1659C1.66267 7.53199 7.85849 1.32088 15.4924 1.32088C23.1415 1.32088 29.3373 7.53199 29.3373 15.1659C29.3373 22.7997 23.1415 28.9955 15.5076 28.9955ZM11.3465 20.918H12.9069C13.5342 20.918 13.8248 20.5662 13.8248 20.0613V10.2398C13.8248 9.73495 13.5342 9.38309 12.9069 9.38309H11.3465C10.7346 9.38309 10.4286 9.73495 10.4286 10.2398V20.0613C10.4286 20.5662 10.7346 20.918 11.3465 20.918ZM18.0931 20.918H19.6382C20.2501 20.918 20.5561 20.5662 20.5561 20.0613V10.2398C20.5561 9.73495 20.2501 9.38309 19.6382 9.38309H18.0931C17.4658 9.38309 17.1752 9.73495 17.1752 10.2398V20.0613C17.1752 20.5662 17.4658 20.918 18.0931 20.918Z" fill="#D9D9D9"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -0,0 +1,5 @@
<svg width="31" height="32" viewBox="0 0 31 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="16.1582" r="15" fill="#3D3D3D"/>
<path d="M15.5 31.1582C13.5302 31.1582 11.5796 30.7702 9.75975 30.0164C7.93986 29.2626 6.28628 28.1577 4.8934 26.7648C3.50052 25.3719 2.39563 23.7183 1.64181 21.8985C0.887985 20.0786 0.499999 18.128 0.499999 16.1582C0.5 14.1884 0.887986 12.2378 1.64181 10.4179C2.39563 8.59807 3.50052 6.94448 4.8934 5.5516C6.28628 4.15872 7.93987 3.05383 9.75975 2.30001C11.5796 1.54619 13.5302 1.1582 15.5 1.1582C17.4698 1.1582 19.4204 1.54619 21.2403 2.30001C23.0601 3.05383 24.7137 4.15872 26.1066 5.5516C27.4995 6.94448 28.6044 8.59807 29.3582 10.418C30.112 12.2378 30.5 14.1884 30.5 16.1582C30.5 18.128 30.112 20.0786 29.3582 21.8985C28.6044 23.7183 27.4995 25.3719 26.1066 26.7648C24.7137 28.1577 23.0601 29.2626 21.2402 30.0164C19.4204 30.7702 17.4698 31.1582 15.5 31.1582L15.5 31.1582Z" stroke="#898989"/>
<path d="M8.26074 20.1582V14.4648H8.23145L6.43457 15.7051V14.377L8.25586 13.1123H9.7207V20.1582H8.26074ZM12.0693 20.2168C11.6299 20.2168 11.2832 19.8652 11.2832 19.4307C11.2832 18.9912 11.6299 18.6445 12.0693 18.6445C12.5039 18.6445 12.8555 18.9912 12.8555 19.4307C12.8555 19.8652 12.5039 20.2168 12.0693 20.2168ZM16.6299 20.3145C15.1162 20.3145 14.0273 19.416 13.9834 18.1416H15.3457C15.4287 18.7373 15.9561 19.1475 16.6396 19.1475C17.4111 19.1475 17.9434 18.6104 17.9434 17.8438C17.9434 17.0625 17.4111 16.5156 16.6494 16.5156C16.1172 16.5156 15.6582 16.7598 15.4141 17.165H14.0957L14.4424 13.1123H18.9248V14.2842H15.5996L15.4385 16.2129H15.4678C15.7559 15.7393 16.3174 15.4414 17.0352 15.4414C18.3828 15.4414 19.3496 16.4326 19.3496 17.8047C19.3496 19.2988 18.2461 20.3145 16.6299 20.3145ZM22.2402 18.3418H22.2109L21.2295 20.1582H19.7305L21.3711 17.5508L19.7402 14.9238H21.3467L22.2891 16.6914H22.3184L23.2461 14.9238H24.7988L23.1631 17.5117L24.7842 20.1582H23.2363L22.2402 18.3418Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,15 @@
<svg width="31" height="32" viewBox="0 0 31 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="16.1582" r="15" fill="#3D3D3D"/>
<path d="M15.5 31.1582C13.5302 31.1582 11.5796 30.7702 9.75975 30.0164C7.93986 29.2626 6.28628 28.1577 4.8934 26.7648C3.50052 25.3719 2.39563 23.7183 1.64181 21.8985C0.887985 20.0786 0.499999 18.128 0.499999 16.1582C0.5 14.1884 0.887986 12.2378 1.64181 10.4179C2.39563 8.59807 3.50052 6.94448 4.8934 5.5516C6.28628 4.15872 7.93987 3.05383 9.75975 2.30001C11.5796 1.54619 13.5302 1.1582 15.5 1.1582C17.4698 1.1582 19.4204 1.54619 21.2403 2.30001C23.0601 3.05383 24.7137 4.15872 26.1066 5.5516C27.4995 6.94448 28.6044 8.59807 29.3582 10.418C30.112 12.2378 30.5 14.1884 30.5 16.1582C30.5 18.128 30.112 20.0786 29.3582 21.8985C28.6044 23.7183 27.4995 25.3719 26.1066 26.7648C24.7137 28.1577 23.0601 29.2626 21.2402 30.0164C19.4204 30.7702 17.4698 31.1582 15.5 31.1582L15.5 31.1582Z" stroke="#898989"/>
<g clip-path="url(#clip0_11902_5015)">
<path d="M18.1266 11.6096C17.9021 11.8338 17.902 12.1975 18.1263 12.4219C19.6353 13.932 19.6344 16.39 18.1241 17.9011C17.8999 18.1256 17.9 18.4892 18.1243 18.7135C18.2366 18.8256 18.3835 18.8817 18.5305 18.8817C18.6776 18.8817 18.8247 18.8255 18.9369 18.7133C20.8945 16.7543 20.8955 13.5678 18.939 11.6098C18.7148 11.3854 18.351 11.3852 18.1266 11.6096Z" fill="white"/>
<path d="M15.979 13.2581C15.7545 13.4822 15.7544 13.846 15.9787 14.0704C16.5803 14.6725 16.5802 15.6521 15.9786 16.2541C15.7543 16.4785 15.7544 16.8422 15.9789 17.0665C16.091 17.1786 16.238 17.2346 16.3849 17.2346C16.532 17.2346 16.6791 17.1785 16.7913 17.0662C17.8404 16.0164 17.8404 14.3081 16.7914 13.2583C16.5672 13.0338 16.2034 13.0337 15.979 13.2581Z" fill="white"/>
<path d="M21.4821 9.06494C21.2578 8.84045 20.8941 8.84033 20.6697 9.0646C20.4452 9.28886 20.4451 9.65261 20.6694 9.87699C23.5803 12.7897 23.579 17.5303 20.6665 20.4444C20.4421 20.6687 20.4423 21.0325 20.6667 21.2567C20.7789 21.3688 20.9258 21.4249 21.0728 21.4249C21.2198 21.4249 21.367 21.3687 21.4791 21.2565C24.8392 17.8946 24.8405 12.4254 21.4821 9.06494Z" fill="white"/>
<path d="M11.1793 18.8664C12.6562 18.7646 13.8271 17.5299 13.8271 16.0258C13.8271 14.4553 12.5508 13.1777 10.9821 13.1777C9.4133 13.1777 8.13699 14.4553 8.13699 16.0258C8.13699 17.5298 9.30775 18.7645 10.7848 18.8664C9.67895 18.9089 8.70503 19.3084 8.02629 20.0031C7.32894 20.7169 6.97457 21.705 7.00142 22.861C7.00869 23.1729 7.26363 23.4221 7.5757 23.4221H14.3878C14.6999 23.4221 14.9549 23.1729 14.9621 22.8609C14.989 21.7049 14.6345 20.7166 13.9371 20.0029C13.2585 19.3083 12.2849 18.9089 11.1793 18.8664ZM9.28589 16.0258C9.28589 15.0889 10.0468 14.3266 10.9821 14.3266C11.9173 14.3266 12.6782 15.0889 12.6782 16.0258C12.6782 16.9628 11.9173 17.7251 10.9821 17.7251C10.0468 17.7251 9.28589 16.9628 9.28589 16.0258ZM8.17677 22.2731C8.25158 21.6784 8.4763 21.1866 8.84812 20.8061C9.34897 20.2935 10.1068 20.0112 10.9819 20.0112C11.857 20.0112 12.6146 20.2934 13.1154 20.8059C13.4871 21.1863 13.712 21.6783 13.7867 22.2731H8.17677Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11902_5015">
<rect width="17" height="17" fill="white" transform="translate(7 7.6582)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4287)">
<path d="M17 7.5C17.9946 7.5 18.9484 7.89509 19.6517 8.59835C20.3549 9.30161 20.75 10.2554 20.75 11.25V21.75C20.75 21.8858 20.7132 22.019 20.6434 22.1355C20.5736 22.252 20.4735 22.3473 20.3538 22.4114C20.2341 22.4755 20.0992 22.5058 19.9636 22.4992C19.828 22.4926 19.6967 22.4494 19.5837 22.374L15.5 19.6515L11.417 22.374C11.31 22.4456 11.1863 22.4885 11.0579 22.4985C10.9295 22.5084 10.8007 22.4852 10.6839 22.4309C10.5671 22.3766 10.4662 22.2932 10.391 22.1887C10.3158 22.0841 10.2688 21.962 10.2545 21.834L10.25 21.75V11.25C10.25 10.2554 10.6451 9.30161 11.3483 8.59835C12.0516 7.89509 13.0054 7.5 14 7.5H17Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4287">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@ -0,0 +1,42 @@
{
"manifest_version": 3,
"name": "Omnivore - Self Hosted",
"version": "3.0",
"description": "Save content to your Omnivore library.",
"permissions": [
"activeTab",
"scripting",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js",
"scripts": ["background.js"]
},
"web_accessible_resources": [
{
"resources": [
"images/*.svg",
"views/toast.html"
],
"matches": [
"<all_urls>"
]
}
],
"options_page": "views/options.html",
"action": {
"default_icon": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"icons": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}

View File

@ -0,0 +1 @@
(()=>{"use strict";var r={322:(r,e,t)=>{t(454),t(782)},782:(r,e,t)=>{t(454),t(322)},454:(r,e,t)=>{}},e={};function t(o){var n=e[o];if(void 0!==n)return n.exports;var p=e[o]={exports:{}};return r[o](p,p.exports,t),p.exports}t.d=(r,e)=>{for(var o in e)t.o(e,o)&&!t.o(r,o)&&Object.defineProperty(r,o,{enumerable:!0,get:e[o]})},t.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),t(782)})();

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>Omnivore Extension Settings</title>
</head>
<body>
<div class='wrapper'>
<h1>API Key</h1>
<label for="api-key">API Key:</label>
<input type="text" id="api-key" style="width: 95%">
<br><br>
<button id="save-api-key-btn">Save API Key</button>
<h1>API URL</h1>
<label for="api-url">API URL:</label>
<input type="text" id="api-url" style="width: 95%">
<br><br>
<button id="save-api-url-btn">Save API URL</button>
<h1>OMNIVORE URL</h1>
<label for="omnivore-url">API URL:</label>
<input type="text" id="omnivore-url" style="width: 95%">
<br><br>
<button id="save-omnivore-url-btn">Save Omnivore URL</button>
<script src="options.js"></script>
</body>
</html>

View File

@ -0,0 +1 @@
function addStorage(e){return chrome.storage.local.set(e)}document.addEventListener("DOMContentLoaded",(()=>{const e=document.getElementById("save-api-key-btn"),t=document.getElementById("api-key");chrome.storage.local.get("omnivoreApiKey").then((e=>{t.value=e.omnivoreApiKey??""})),e.addEventListener("click",(e=>{addStorage({omnivoreApiKey:t.value})}));const o=document.getElementById("save-api-url-btn"),n=document.getElementById("api-url");chrome.storage.local.get("omnivoreApiUrl").then((e=>{n.value=e.omnivoreApiUrl??""})),o.addEventListener("click",(e=>{addStorage({omnivoreApiUrl:n.value})}));const r=document.getElementById("save-omnivore-url-btn"),a=document.getElementById("omnivore-url");chrome.storage.local.get("omnivoreUrl").then((e=>{a.value=e.omnivoreUrl??""})),r.addEventListener("click",(e=>{addStorage({omnivoreUrl:a.value})}))}));

View File

@ -0,0 +1,555 @@
<style>
#omnivore-toast-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-end;
overflow: hidden;
border-radius: 4px;
color: #3D3D3D;
background: #fff;
font: 400 12px sans-serif;
line-height: 20px;
box-shadow: 0px 5px 20px rgba(32, 31, 29, 0.12);
transition: all 300ms ease;
z-index: 9999999;
width: 480px;
}
#omnivore-toast-container input, select, textarea{
color: #3D3D3D;
}
#omnivore-toast-container .omnivore-toast-func-row {
display: flex;
width: 100%;
padding-top: 0px;
padding-left: 15px;
padding-right: 15px;
padding-bottom: 10px;
}
#omnivore-toast-container #omnivore-extra-buttons-row {
padding-left: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button {
padding-left: 10px;
width: 80%;
}
#omnivore-toast-container #omnivore-logged-out-row {
flex-direction: column;
align-items: center;
justify-content: center;
}
#omnivore-toast-container .omnivore-toast-func-row[data-state="open"] {
display: flex;
}
#omnivore-toast-container .omnivore-toast-func-row[data-state="closed"] {
display: none;
}
#omnivore-toast-container .omnivore-toast-func-status {
display: flex;
align-items: flex-start;
justify-content: center;
width: 100%;
color:#898989;
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 20px;
text-align: center;
}
#omnivore-toast-container #omnivore-edit-labels-status {
height: 22px;
padding-top: 10px;
}
#omnivore-toast-button-row {
gap: 5px;
align-items: center;
padding-top: 7px;
padding-bottom: 7px;
display: flex;
width: 100%;
padding-left: 15px;
padding-right: 10px;
}
#omnivore-toast-container button {
display: flex;
align-items: center;
gap: 8px;
color: #898989;
border: none;
background-color: transparent;
cursor: pointer;
border-radius: 5px;
height: 30px;
margin-left: auto;
}
#omnivore-toast-container button:hover {
color: #3B3A38;
background-color: #EBEBEB;
}
#omnivore-toast-container .omnivore-save-button button {
background-color: rgb(255, 210, 52)
}
#omnivore-toast-container #omnivore-toast-close-btn:hover {
background-color: unset;
}
#omnivore-toast-container #omnivore-toast-close-btn svg {
fill: #3D3D3D;
}
#omnivore-toast-container #omnivore-toast-close-btn:hover > svg *,
#omnivore-toast-container #omnivore-toast-close-btn svg:hover {
fill: #D9D9D9;
stroke: white;
}
#omnivore-toast-container svg {
fill: #898989;
}
#omnivore-toast-container > svg *,
#omnivore-toast-container svg:hover {
fill: #3B3A38;
}
#omnivore-toast-container #omnivore-toast-edit-title-btn > svg * {
fill: none;
}
#omnivore-toast-container #omnivore-toast-delete-btn > svg * {
fill: none;
}
#omnivore-toast-container form {
display: block;
margin: 0px;
}
.omnivore-toast-divider {
height:20px;
border-right: 1px solid #D9D9D9;
}
.omnivore-toast-statusBox {
display: flex;
width: 20px;
margin-left: 0px;
margin-right: 7px;
}
#omnivore-edit-title-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-edit-title-row textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-add-note-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-add-note-row textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-toast-container .omnivore-toast-func-row button {
height: 30px;
padding: 15px;
margin-left: auto;
background-color: #FFEA9F;
}
#omnivore-edit-labels-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-edit-labels-row input {
width: 100%;
height: 34px;
padding: 10px;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-extra-buttons-row {
flex-direction: column;
padding-top: 5px;
padding-bottom: 10px;
gap: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button {
align-self: flex-start;
padding: 10px;
margin: 0px;
background-color: transparent;
border-radius: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button:hover {
background-color: #EBEBEB;
}
#omnivore-toast-container #omnivore-edit-labels-list button:hover {
background-color: #EBEBEB;
}
#omnivore-edit-labels-list {
overflow-y: scroll;
max-height: 200px;
gap: 5px;
color: #3B3A38;
margin-top: 0px;
margin-bottom: 10px;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
height: 35px;
display: flex;
align-items: center;
padding-left: 10px;
padding-right: 10px;
border-bottom: 1px solid #EEEEEE;
border-radius: 0px;
width: 100%;
background: unset;
}
#omnivore-toast-container #omnivore-edit-labels-list button[data-label-selected="on"] .checkbox {
visibility: visible;
}
#omnivore-toast-container #omnivore-edit-labels-list button[data-label-selected="off"] .checkbox {
visibility: hidden;
}
#omnivore-toast-container #omnivore-edit-labels-list button:focus-visible {
outline: unset;
border-radius: 5px;
background-color: #EBEBEB;
}
#omnivore-edit-labels-list div:hover {
background-color: #EBEBEB;
}
#omnivore-edit-labels-row form {
margin-bottom: unset;
}
#omnivore-add-note-row textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
#omnivore-edit-labels-row input, select, textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
#omnivore-edit-title-row textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
.omnivore-toast-menu-divider {
border-bottom: 1px solid #D9D9D9;
width: 80%;
}
#omnivore-toast-login-btn {
color: #898989;
text-decoration: underline;
}
@media (prefers-color-scheme: dark) {
#omnivore-toast-container {
background: #333333;
}
#omnivore-toast-container input, select, textarea{
color: #D9D9D9;
}
.omnivore-toast-divider {
border-right: 1px solid #898989;
}
#omnivore-toast-container svg {
fill: #898989;
}
.omnivore-save-button button {
margin-top: 10px;
color: #333333;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
color: #898989;
}
#omnivore-toast-container button:hover > svg *,
#omnivore-toast-container button:hover {
color: #D9D9D9;
fill: #D9D9D9;
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-edit-labels-list button:hover {
color: #D9D9D9;
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-extra-buttons-row button:hover {
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
border-bottom: 1px solid #898989;
}
#omnivore-toast-container #omnivore-toast-edit-title-btn > svg * {
fill: none;
}
#omnivore-toast-container #omnivore-toast-delete-btn > svg * {
fill: none;
}
.omnivore-toast-menu-divider {
border-bottom: 1px solid #898989;
}
#omnivore-toast-login-btn {
color: #898989;
}
}
</style>
<style>
.label-editor {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: inline-block;
background-color: var(--colors-thBackground2);
border: 1px solid transparent;
border-radius: 6px;
padding: 5px;
line-height: 2;
cursor: text;
font-size: 12px;
width: calc(100% - 10px);
min-height: 28px;
}
.label-list {
flex-wrap: wrap;
justify-content: start;
display: flex;
width: 100%;
align-items: center;
padding: 0px;
box-sizing: border-box;
height: auto;
flex: 0 0 auto;
overflow-y: auto;
margin: 0;
list-style: none;
gap: 5px;
}
#omnivore-toast-container .omnivore-toast-func-row .label-remove-button {
display: flex;
align-items: center;
justify-content: center;
color: transparent;
border: none;
background-color: transparent;
cursor: pointer;
padding: 0px;
}
#omnivore-edit-labels-row .label-input {
box-sizing: content-box;
font-size: 16px;
min-width: 2px;
border: none;
outline: none;
padding: 0px;
height: 28px;
}
.label {
display: inline-table;
padding: 1px;
padding-left: 7px;
padding-right: 7px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 5px;
border: 1px solid rgb(222, 222, 222);
background-color: rgb(249, 249, 249);
}
#omnivore-toast-container .omnivore-toast-func-row .label button {
height: unset;
}
.label[data-label-backspaced="on"] {
border: 1px solid rgb(255, 234, 159);
}
.label[data-item-highlighted="on"] {
border: 1px solid black;
transition: border-color 0.25s linear;
}
.loading-spinner {
/* control spinner size with setting font-size */
font-size: 3rem;
border: 2px solid #FFD234;
border-top-color: transparent;
border-radius: 50%;
width: 18px;
height: 18px;
animation: loading-spinner 1.5s linear infinite;
margin: 0 auto;
box-sizing: border-box;
}
@keyframes loading-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<div id="omnivore-toast-container">
<div id="omnivore-toast-button-row">
<span class="omnivore-toast-statusBox">
<div class="loading-spinner"></div>
</span>
<span class="omnivore-toast-divider"></span>
<button class="omnivore-top-button" id="omnivore-toast-add-note-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 256 256">
<path d="M88,96a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,96Zm8,40h64a8,8,0,0,0,0-16H96a8,8,0,0,0,0,16Zm32,16H96a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16ZM224,48V156.69A15.86,15.86,0,0,1,219.31,168L168,219.31A15.86,15.86,0,0,1,156.69,224H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H152V160a8,8,0,0,1,8-8h48V48H48Zm120-40v28.7L196.69,168Z"></path>
</svg>
<span class="omnivore-top-button-label">Add Note</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-edit-labels-btn">
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8.99031 14.9786L15.3061 8.67029C15.3757 8.6002 15.4307 8.51707 15.468 8.42568C15.5053 8.33429 15.5242 8.23643 15.5237 8.13772L15.5237 1.38683C15.5237 1.18789 15.4446 0.997101 15.304 0.85643C15.1633 0.715759 14.9725 0.636731 14.7736 0.636731L8.02269 0.636731C7.92397 0.63616 7.82611 0.655082 7.73472 0.69241C7.64333 0.729738 7.5602 0.784739 7.49012 0.85426L1.18179 7.17009C0.76038 7.59202 0.523681 8.16397 0.523681 8.7603C0.523681 9.35663 0.76038 9.92857 1.18179 10.3505L5.77239 14.9786C6.19432 15.4 6.76627 15.6367 7.3626 15.6367C7.95893 15.6367 8.53087 15.4 8.95281 14.9786L8.99031 14.9786ZM6.87503 13.921L2.24693 9.28536C2.10722 9.14482 2.0288 8.95471 2.0288 8.75655C2.0288 8.55838 2.10722 8.36827 2.24693 8.22773L8.33022 2.13693L14.0235 2.13693L14.0235 7.83018L7.93267 13.921C7.86258 13.9905 7.77946 14.0455 7.68807 14.0828C7.59668 14.1202 7.49882 14.1391 7.4001 14.1385C7.20332 14.1377 7.01475 14.0595 6.87503 13.921Z" />
<circle cx="10.8818" cy="5.48069" r="1.24925" />
</svg>
<span class="omnivore-top-button-label">Set Labels</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-read-now-btn">
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.1272 0.939454H12.6584C11.6995 0.939454 10.762 1.21484 9.95532 1.73438L9.0022 2.3457L8.04907 1.73438C7.24323 1.21494 6.30469 0.938941 5.34595 0.939454H0.877197C0.531494 0.939454 0.252197 1.21875 0.252197 1.56445V12.6582C0.252197 13.0039 0.531494 13.2832 0.877197 13.2832H5.34595C6.30493 13.2832 7.24243 13.5586 8.04907 14.0781L8.91626 14.6367C8.94165 14.6523 8.97095 14.6621 9.00024 14.6621C9.02954 14.6621 9.05884 14.6543 9.08423 14.6367L9.95142 14.0781C10.76 13.5586 11.6995 13.2832 12.6584 13.2832H17.1272C17.4729 13.2832 17.7522 13.0039 17.7522 12.6582V1.56445C17.7522 1.21875 17.4729 0.939454 17.1272 0.939454ZM5.34595 11.877H1.65845V2.3457H5.34595C6.03735 2.3457 6.70923 2.54297 7.28931 2.91602L8.24243 3.52734L8.3772 3.61523V12.6387C7.44751 12.1387 6.40845 11.877 5.34595 11.877ZM16.3459 11.877H12.6584C11.5959 11.877 10.5569 12.1387 9.6272 12.6387V3.61523L9.76196 3.52734L10.7151 2.91602C11.2952 2.54297 11.967 2.3457 12.6584 2.3457H16.3459V11.877ZM6.75415 4.8457H3.12524C3.04907 4.8457 2.98657 4.91211 2.98657 4.99219V5.87109C2.98657 5.95117 3.04907 6.01758 3.12524 6.01758H6.7522C6.82837 6.01758 6.89087 5.95117 6.89087 5.87109V4.99219C6.89282 4.91211 6.83032 4.8457 6.75415 4.8457ZM11.1116 4.99219V5.87109C11.1116 5.95117 11.1741 6.01758 11.2502 6.01758H14.8772C14.9534 6.01758 15.0159 5.95117 15.0159 5.87109V4.99219C15.0159 4.91211 14.9534 4.8457 14.8772 4.8457H11.2502C11.1741 4.8457 11.1116 4.91211 11.1116 4.99219ZM6.75415 7.58008H3.12524C3.04907 7.58008 2.98657 7.64648 2.98657 7.72656V8.60547C2.98657 8.68555 3.04907 8.75195 3.12524 8.75195H6.7522C6.82837 8.75195 6.89087 8.68555 6.89087 8.60547V7.72656C6.89282 7.64648 6.83032 7.58008 6.75415 7.58008ZM14.8792 7.58008H11.2502C11.1741 7.58008 11.1116 7.64648 11.1116 7.72656V8.60547C11.1116 8.68555 11.1741 8.75195 11.2502 8.75195H14.8772C14.9534 8.75195 15.0159 8.68555 15.0159 8.60547V7.72656C15.0178 7.64648 14.9553 7.58008 14.8792 7.58008Z" />
</svg>
<span class="omnivore-top-button-label">Read Now</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-open-menu-btn">
<svg width="15" height="4" viewBox="0 0 15 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="1.48679" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="7.00217" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="7.00217" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="12.5176" cy="1.79492" rx="1.4846" ry="1.5" />
</svg>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-close-btn">
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="9.50049" cy="9.52783" r="9" />
<path d="M12.554 11.874L12.554 11.874L10.2075 9.52783L12.554 7.18165L12.554 7.18164C12.6478 7.08785 12.7005 6.96063 12.7005 6.82798C12.7005 6.69533 12.6478 6.56812 12.554 6.47432C12.4602 6.38053 12.333 6.32783 12.2003 6.32783C12.0677 6.32783 11.9405 6.38053 11.8467 6.47432L11.8467 6.47433L9.50049 8.82087L7.15431 6.47433L7.1543 6.47432C7.0605 6.38053 6.93329 6.32783 6.80064 6.32783C6.66799 6.32783 6.54078 6.38053 6.44698 6.47432C6.35318 6.56812 6.30049 6.69533 6.30049 6.82798C6.30049 6.96063 6.35318 7.08785 6.44698 7.18164L6.44699 7.18165L8.79352 9.52783L6.44699 11.874L6.44698 11.874C6.35318 11.9678 6.30049 12.095 6.30049 12.2277C6.30049 12.3603 6.35318 12.4875 6.44698 12.5813C6.54078 12.6751 6.66799 12.7278 6.80064 12.7278C6.93329 12.7278 7.0605 12.6751 7.1543 12.5813L7.15431 12.5813L9.50049 10.2348L11.8467 12.5813L11.8467 12.5813C11.8931 12.6278 11.9483 12.6646 12.0089 12.6898C12.0696 12.7149 12.1347 12.7278 12.2003 12.7278C12.266 12.7278 12.3311 12.7149 12.3917 12.6898C12.4524 12.6646 12.5076 12.6278 12.554 12.5813C12.6004 12.5349 12.6373 12.4798 12.6624 12.4191C12.6876 12.3584 12.7005 12.2934 12.7005 12.2277C12.7005 12.162 12.6876 12.097 12.6624 12.0363C12.6373 11.9756 12.6004 11.9205 12.554 11.874Z" fill="#EBEBEB" stroke="#EBEBEB" stroke-width="0.4"/>
</svg>
</button>
</div>
<div id="omnivore-add-note-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-add-note-status" class="omnivore-toast-func-status"></span>
<form id="omnivore-add-note-form">
<textarea id="omnivore-add-note-textarea" name="title"></textarea>
<button class="omnivore-save-button">Save</button>
</form>
</div>
<div id="omnivore-edit-title-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-edit-title-status" class="omnivore-toast-func-status"></span>
<form id="omnivore-edit-title-form">
<textarea id="omnivore-edit-title-textarea" name="title"></textarea>
<button class="omnivore-save-button">Save</button>
</form>
</div>
<div id="omnivore-edit-labels-row" class="omnivore-toast-func-row" data-state="closed">
<form id="omnivore-edit-labels-form">
<span id="omnivore-edit-labels-status" class="omnivore-toast-func-status"></span>
<div id="omnivore-edit-labels-list"></div>
</form>
</div>
<div id="omnivore-extra-buttons-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-extra-status" class="omnivore-toast-func-status"></span>
<button id="omnivore-toast-edit-title-btn">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5625 4.50004L13.5 8.43754M6.71406 15.1516L2.84828 11.2858M6.517 15.1875H3.375C3.22582 15.1875 3.08274 15.1283 2.97725 15.0228C2.87176 14.9173 2.8125 14.7742 2.8125 14.625V11.483C2.8125 11.4092 2.82705 11.336 2.85532 11.2678C2.88359 11.1995 2.92502 11.1375 2.97725 11.0853L11.4148 2.64778C11.5202 2.5423 11.6633 2.48303 11.8125 2.48303C11.9617 2.48303 12.1048 2.5423 12.2102 2.64778L15.3523 5.78979C15.4577 5.89528 15.517 6.03835 15.517 6.18754C15.517 6.33672 15.4577 6.4798 15.3523 6.58528L6.91475 15.0228C6.86252 15.075 6.80051 15.1165 6.73226 15.1447C6.66402 15.173 6.59087 15.1875 6.517 15.1875Z" stroke="#6A6968" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Edit Title
</button>
<span class="omnivore-toast-menu-divider"></span>
<button id="omnivore-toast-archive-btn">
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.0143 0.166748H1.93155C1.157 0.166748 0.523193 0.800512 0.523193 1.5751V3.92222C0.523193 4.6264 1.03956 5.18958 1.69675 5.30698V14.0148C1.69675 14.7893 2.33052 15.4231 3.10511 15.4231H14.8407C15.6152 15.4231 16.249 14.7894 16.249 14.0148V5.30698C16.9062 5.18959 17.4226 4.6264 17.4226 3.92222V1.5751C17.4226 0.800554 16.7888 0.166748 16.0143 0.166748ZM1.93155 1.5751H16.0143V3.92222H1.93155V1.5751ZM14.8407 14.0148H3.10511V5.33049H14.8407V14.0148Z" fill="#6A6968"/>
<path d="M7.82307 8.26431H10.1702C10.5692 8.26431 10.8744 7.95914 10.8744 7.56013C10.8744 7.16114 10.5692 6.85596 10.1702 6.85596H7.82307C7.42408 6.85596 7.1189 7.16113 7.1189 7.56013C7.1189 7.95912 7.44748 8.26431 7.82307 8.26431Z" fill="#6A6968"/>
</svg>
Archive
</button>
<span class="omnivore-toast-menu-divider"></span>
<button id="omnivore-toast-delete-btn">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6602 5.16992L3.51147 5.16993" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29272 8.91992V13.9199" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.8787 8.91992V13.9199" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.09741 2.66992H13.0741" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.4648 5.16993V17.0449C15.4648 17.2107 15.4018 17.3697 15.2897 17.4869C15.1777 17.6041 15.0256 17.6699 14.8671 17.6699H5.30445C5.14594 17.6699 4.99392 17.6041 4.88184 17.4869C4.76976 17.3697 4.70679 17.2107 4.70679 17.0449V5.16992" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Delete
</button>
</div>
<div id="omnivore-logged-out-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-logged-out-status" class="omnivore-toast-func-status"></span>
<a href="" id="omnivore-toast-login-btn">Login to Omnivore</a>
</div>
</div>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,25 @@
{
"name": "omnivore-extension",
"version": "1.0.0",
"description": "browser extension for omnivore",
"scripts": {
"build": "env webpack --mode production",
"watch": "webpack --mode development --watch"
},
"devDependencies": {
"@types/chrome": "^0.0.268",
"@types/dompurify": "^3.0.5",
"@types/firefox-webext-browser": "^120.0.4",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^12.0.2",
"dotenv-webpack": "^8.1.0",
"ts-loader": "^8.0.0",
"typescript": "^4.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0"
},
"dependencies": {
"nanoid": "^4.0.2",
"uuid": "^8.3.2"
}
}

View File

@ -0,0 +1,90 @@
import { v4 as uuidv4 } from 'uuid'
import { addNoteToLibraryItem, savePageRequest } from './scripts/omnivore-api'
import {
isAddNoteInput,
isEnqueueTaskMessage,
isSavePageInput,
} from './scripts/types'
import { TaskQueue } from './task-queue'
const browserAPI = typeof browser !== 'undefined' ? browser : chrome
const taskQueues: Record<string, TaskQueue> = {}
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
console.log('message: ', message, 'sender', sender.tab?.id)
const tabId = sender.tab?.id
if (message.action === 'savePage' && isSavePageInput(message)) {
const { result, libraryItemId } = await savePageRequest({
url: message.url,
title: message.title,
clientRequestId: message.clientRequestId,
originalContent: message.originalContent,
})
console.log('result: ', result, 'libraryItemId', libraryItemId)
if (tabId) {
switch (result) {
case 'success':
chrome.tabs.sendMessage(tabId, {
message: 'startToolbarDismiss',
status: 'success',
})
if (libraryItemId) {
const taskQueue = taskQueues[message.clientRequestId]
taskQueue.setReady(tabId, libraryItemId)
}
break
case 'failure':
chrome.tabs.sendMessage(tabId, {
message: 'startToolbarDismiss',
status: 'failure',
})
break
case 'unauthorized':
chrome.tabs.sendMessage(tabId, {
message: 'showLoggedOutToolbar',
})
break
}
}
} else if (message.action == 'enqueueTask' && isEnqueueTaskMessage(message)) {
const taskQueue = taskQueues[message.clientRequestId]
console.log('enqueing task message: ', message)
taskQueue.enqueue({ ...message, libraryItemId: message.clientRequestId })
}
return true
})
const scriptsAlreadyLoaded = async (tabId: number) => {
try {
const pingCheck = await chrome.tabs.sendMessage(tabId, {
message: 'ping',
})
console.log('pingCheck: ', pingCheck)
return true
} catch {
return false
}
}
browserAPI.action.onClicked.addListener(async (tab) => {
const tabId = tab.id
if (tabId) {
try {
const scriptsLoaded = await scriptsAlreadyLoaded(tabId)
if (!scriptsLoaded) {
await chrome.scripting.executeScript({
target: { tabId },
files: ['content.js'],
})
}
} catch (err) {
console.log('[omnivore] error injecting content script: ', err)
}
const clientRequestId = uuidv4()
taskQueues[clientRequestId] = new TaskQueue()
chrome.tabs.sendMessage(tabId, { message: 'savePage', clientRequestId })
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,14 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4278)">
<path d="M8 20.8342H11.3333L20.0833 12.0842C20.5254 11.6422 20.7737 11.0427 20.7737 10.4176C20.7737 9.79245 20.5254 9.19293 20.0833 8.7509C19.6413 8.30888 19.0418 8.06055 18.4167 8.06055C17.7915 8.06055 17.192 8.30888 16.75 8.7509L8 17.5009V20.8342Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.9166 9.58398L19.25 12.9173" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.25 16.25L12.5833 19.5833" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23 17.5V20.8333H16.3334L19.6667 17.5H23Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_11897_4278">
<rect width="20" height="20" fill="white" transform="translate(5.5 5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,12 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4271)">
<path d="M10.9166 12.2493C10.9166 12.4704 11.0044 12.6823 11.1607 12.8386C11.317 12.9949 11.5289 13.0827 11.75 13.0827C11.971 13.0827 12.1829 12.9949 12.3392 12.8386C12.4955 12.6823 12.5833 12.4704 12.5833 12.2493C12.5833 12.0283 12.4955 11.8164 12.3392 11.6601C12.1829 11.5038 11.971 11.416 11.75 11.416C11.5289 11.416 11.317 11.5038 11.1607 11.6601C11.0044 11.8164 10.9166 12.0283 10.9166 12.2493Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 11V15.31C8.00009 15.752 8.17575 16.1758 8.48833 16.4883L14.9133 22.9133C15.29 23.2899 15.8007 23.5015 16.3333 23.5015C16.8659 23.5015 17.3767 23.2899 17.7533 22.9133L22.4133 18.2533C22.7899 17.8767 23.0015 17.3659 23.0015 16.8333C23.0015 16.3007 22.7899 15.79 22.4133 15.4133L15.9883 8.98833C15.6758 8.67575 15.252 8.50009 14.81 8.5H10.5C9.83696 8.5 9.20107 8.76339 8.73223 9.23223C8.26339 9.70107 8 10.337 8 11Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_11897_4271">
<rect width="20" height="20" fill="white" transform="translate(5.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle class="circle-bg" cx="15.5" cy="15" r="15" fill="#6A6968"/>
<g clip-path="url(#clip0_11897_4264)">
<path d="M11 9.00021V21.0002C11 21.1337 11.0355 21.2647 11.103 21.3798C11.1705 21.4949 11.2675 21.5899 11.384 21.6551C11.5005 21.7202 11.6322 21.753 11.7657 21.7503C11.8991 21.7475 12.0293 21.7091 12.143 21.6392L21.893 15.6392C22.0022 15.5721 22.0924 15.4781 22.1549 15.3663C22.2175 15.2544 22.2503 15.1284 22.2503 15.0002C22.2503 14.872 22.2175 14.746 22.1549 14.6341C22.0924 14.5223 22.0022 14.4283 21.893 14.3612L12.143 8.36121C12.0293 8.29128 11.8991 8.25295 11.7657 8.25016C11.6322 8.24738 11.5005 8.28024 11.384 8.34536C11.2675 8.41048 11.1705 8.50549 11.103 8.62061C11.0355 8.73572 11 8.86676 11 9.00021Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4264">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle class="circle-bg" cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4264)">
<path d="M11 9.00021V21.0002C11 21.1337 11.0355 21.2647 11.103 21.3798C11.1705 21.4949 11.2675 21.5899 11.384 21.6551C11.5005 21.7202 11.6322 21.753 11.7657 21.7503C11.8991 21.7475 12.0293 21.7091 12.143 21.6392L21.893 15.6392C22.0022 15.5721 22.0924 15.4781 22.1549 15.3663C22.2175 15.2544 22.2503 15.1284 22.2503 15.0002C22.2503 14.872 22.2175 14.746 22.1549 14.6341C22.0924 14.5223 22.0022 14.4283 21.893 14.3612L12.143 8.36121C12.0293 8.29128 11.8991 8.25295 11.7657 8.25016C11.6322 8.24738 11.5005 8.28024 11.384 8.34536C11.2675 8.41048 11.1705 8.50549 11.103 8.62061C11.0355 8.73572 11 8.86676 11 9.00021Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4264">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 976 B

View File

@ -0,0 +1,3 @@
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.5076 30.1582C23.7381 30.1582 30.5 23.3963 30.5 15.1659C30.5 6.92006 23.7381 0.158203 15.4924 0.158203C7.26186 0.158203 0.5 6.92006 0.5 15.1659C0.5 23.3963 7.26186 30.1582 15.5076 30.1582ZM15.5076 28.9955C7.85849 28.9955 1.66267 22.7997 1.66267 15.1659C1.66267 7.53199 7.85849 1.32088 15.4924 1.32088C23.1415 1.32088 29.3373 7.53199 29.3373 15.1659C29.3373 22.7997 23.1415 28.9955 15.5076 28.9955ZM11.3465 20.918H12.9069C13.5342 20.918 13.8248 20.5662 13.8248 20.0613V10.2398C13.8248 9.73495 13.5342 9.38309 12.9069 9.38309H11.3465C10.7346 9.38309 10.4286 9.73495 10.4286 10.2398V20.0613C10.4286 20.5662 10.7346 20.918 11.3465 20.918ZM18.0931 20.918H19.6382C20.2501 20.918 20.5561 20.5662 20.5561 20.0613V10.2398C20.5561 9.73495 20.2501 9.38309 19.6382 9.38309H18.0931C17.4658 9.38309 17.1752 9.73495 17.1752 10.2398V20.0613C17.1752 20.5662 17.4658 20.918 18.0931 20.918Z" fill="#D9D9D9"/>
</svg>

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -0,0 +1,5 @@
<svg width="31" height="32" viewBox="0 0 31 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="16.1582" r="15" fill="#3D3D3D"/>
<path d="M15.5 31.1582C13.5302 31.1582 11.5796 30.7702 9.75975 30.0164C7.93986 29.2626 6.28628 28.1577 4.8934 26.7648C3.50052 25.3719 2.39563 23.7183 1.64181 21.8985C0.887985 20.0786 0.499999 18.128 0.499999 16.1582C0.5 14.1884 0.887986 12.2378 1.64181 10.4179C2.39563 8.59807 3.50052 6.94448 4.8934 5.5516C6.28628 4.15872 7.93987 3.05383 9.75975 2.30001C11.5796 1.54619 13.5302 1.1582 15.5 1.1582C17.4698 1.1582 19.4204 1.54619 21.2403 2.30001C23.0601 3.05383 24.7137 4.15872 26.1066 5.5516C27.4995 6.94448 28.6044 8.59807 29.3582 10.418C30.112 12.2378 30.5 14.1884 30.5 16.1582C30.5 18.128 30.112 20.0786 29.3582 21.8985C28.6044 23.7183 27.4995 25.3719 26.1066 26.7648C24.7137 28.1577 23.0601 29.2626 21.2402 30.0164C19.4204 30.7702 17.4698 31.1582 15.5 31.1582L15.5 31.1582Z" stroke="#898989"/>
<path d="M8.26074 20.1582V14.4648H8.23145L6.43457 15.7051V14.377L8.25586 13.1123H9.7207V20.1582H8.26074ZM12.0693 20.2168C11.6299 20.2168 11.2832 19.8652 11.2832 19.4307C11.2832 18.9912 11.6299 18.6445 12.0693 18.6445C12.5039 18.6445 12.8555 18.9912 12.8555 19.4307C12.8555 19.8652 12.5039 20.2168 12.0693 20.2168ZM16.6299 20.3145C15.1162 20.3145 14.0273 19.416 13.9834 18.1416H15.3457C15.4287 18.7373 15.9561 19.1475 16.6396 19.1475C17.4111 19.1475 17.9434 18.6104 17.9434 17.8438C17.9434 17.0625 17.4111 16.5156 16.6494 16.5156C16.1172 16.5156 15.6582 16.7598 15.4141 17.165H14.0957L14.4424 13.1123H18.9248V14.2842H15.5996L15.4385 16.2129H15.4678C15.7559 15.7393 16.3174 15.4414 17.0352 15.4414C18.3828 15.4414 19.3496 16.4326 19.3496 17.8047C19.3496 19.2988 18.2461 20.3145 16.6299 20.3145ZM22.2402 18.3418H22.2109L21.2295 20.1582H19.7305L21.3711 17.5508L19.7402 14.9238H21.3467L22.2891 16.6914H22.3184L23.2461 14.9238H24.7988L23.1631 17.5117L24.7842 20.1582H23.2363L22.2402 18.3418Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,15 @@
<svg width="31" height="32" viewBox="0 0 31 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="16.1582" r="15" fill="#3D3D3D"/>
<path d="M15.5 31.1582C13.5302 31.1582 11.5796 30.7702 9.75975 30.0164C7.93986 29.2626 6.28628 28.1577 4.8934 26.7648C3.50052 25.3719 2.39563 23.7183 1.64181 21.8985C0.887985 20.0786 0.499999 18.128 0.499999 16.1582C0.5 14.1884 0.887986 12.2378 1.64181 10.4179C2.39563 8.59807 3.50052 6.94448 4.8934 5.5516C6.28628 4.15872 7.93987 3.05383 9.75975 2.30001C11.5796 1.54619 13.5302 1.1582 15.5 1.1582C17.4698 1.1582 19.4204 1.54619 21.2403 2.30001C23.0601 3.05383 24.7137 4.15872 26.1066 5.5516C27.4995 6.94448 28.6044 8.59807 29.3582 10.418C30.112 12.2378 30.5 14.1884 30.5 16.1582C30.5 18.128 30.112 20.0786 29.3582 21.8985C28.6044 23.7183 27.4995 25.3719 26.1066 26.7648C24.7137 28.1577 23.0601 29.2626 21.2402 30.0164C19.4204 30.7702 17.4698 31.1582 15.5 31.1582L15.5 31.1582Z" stroke="#898989"/>
<g clip-path="url(#clip0_11902_5015)">
<path d="M18.1266 11.6096C17.9021 11.8338 17.902 12.1975 18.1263 12.4219C19.6353 13.932 19.6344 16.39 18.1241 17.9011C17.8999 18.1256 17.9 18.4892 18.1243 18.7135C18.2366 18.8256 18.3835 18.8817 18.5305 18.8817C18.6776 18.8817 18.8247 18.8255 18.9369 18.7133C20.8945 16.7543 20.8955 13.5678 18.939 11.6098C18.7148 11.3854 18.351 11.3852 18.1266 11.6096Z" fill="white"/>
<path d="M15.979 13.2581C15.7545 13.4822 15.7544 13.846 15.9787 14.0704C16.5803 14.6725 16.5802 15.6521 15.9786 16.2541C15.7543 16.4785 15.7544 16.8422 15.9789 17.0665C16.091 17.1786 16.238 17.2346 16.3849 17.2346C16.532 17.2346 16.6791 17.1785 16.7913 17.0662C17.8404 16.0164 17.8404 14.3081 16.7914 13.2583C16.5672 13.0338 16.2034 13.0337 15.979 13.2581Z" fill="white"/>
<path d="M21.4821 9.06494C21.2578 8.84045 20.8941 8.84033 20.6697 9.0646C20.4452 9.28886 20.4451 9.65261 20.6694 9.87699C23.5803 12.7897 23.579 17.5303 20.6665 20.4444C20.4421 20.6687 20.4423 21.0325 20.6667 21.2567C20.7789 21.3688 20.9258 21.4249 21.0728 21.4249C21.2198 21.4249 21.367 21.3687 21.4791 21.2565C24.8392 17.8946 24.8405 12.4254 21.4821 9.06494Z" fill="white"/>
<path d="M11.1793 18.8664C12.6562 18.7646 13.8271 17.5299 13.8271 16.0258C13.8271 14.4553 12.5508 13.1777 10.9821 13.1777C9.4133 13.1777 8.13699 14.4553 8.13699 16.0258C8.13699 17.5298 9.30775 18.7645 10.7848 18.8664C9.67895 18.9089 8.70503 19.3084 8.02629 20.0031C7.32894 20.7169 6.97457 21.705 7.00142 22.861C7.00869 23.1729 7.26363 23.4221 7.5757 23.4221H14.3878C14.6999 23.4221 14.9549 23.1729 14.9621 22.8609C14.989 21.7049 14.6345 20.7166 13.9371 20.0029C13.2585 19.3083 12.2849 18.9089 11.1793 18.8664ZM9.28589 16.0258C9.28589 15.0889 10.0468 14.3266 10.9821 14.3266C11.9173 14.3266 12.6782 15.0889 12.6782 16.0258C12.6782 16.9628 11.9173 17.7251 10.9821 17.7251C10.0468 17.7251 9.28589 16.9628 9.28589 16.0258ZM8.17677 22.2731C8.25158 21.6784 8.4763 21.1866 8.84812 20.8061C9.34897 20.2935 10.1068 20.0112 10.9819 20.0112C11.857 20.0112 12.6146 20.2934 13.1154 20.8059C13.4871 21.1863 13.712 21.6783 13.7867 22.2731H8.17677Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11902_5015">
<rect width="17" height="17" fill="white" transform="translate(7 7.6582)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,11 @@
<svg width="31" height="30" viewBox="0 0 31 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15.5" cy="15" r="15" fill="#3D3D3D"/>
<g clip-path="url(#clip0_11897_4287)">
<path d="M17 7.5C17.9946 7.5 18.9484 7.89509 19.6517 8.59835C20.3549 9.30161 20.75 10.2554 20.75 11.25V21.75C20.75 21.8858 20.7132 22.019 20.6434 22.1355C20.5736 22.252 20.4735 22.3473 20.3538 22.4114C20.2341 22.4755 20.0992 22.5058 19.9636 22.4992C19.828 22.4926 19.6967 22.4494 19.5837 22.374L15.5 19.6515L11.417 22.374C11.31 22.4456 11.1863 22.4885 11.0579 22.4985C10.9295 22.5084 10.8007 22.4852 10.6839 22.4309C10.5671 22.3766 10.4662 22.2932 10.391 22.1887C10.3158 22.0841 10.2688 21.962 10.2545 21.834L10.25 21.75V11.25C10.25 10.2554 10.6451 9.30161 11.3483 8.59835C12.0516 7.89509 13.0054 7.5 14 7.5H17Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11897_4287">
<rect width="18" height="18" fill="white" transform="translate(6.5 6)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@ -0,0 +1,42 @@
{
"manifest_version": 3,
"name": "Omnivore - Self Hosted",
"version": "3.0",
"description": "Save content to your Omnivore library.",
"permissions": [
"activeTab",
"scripting",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js",
"scripts": ["background.js"]
},
"web_accessible_resources": [
{
"resources": [
"images/*.svg",
"views/toast.html"
],
"matches": [
"<all_urls>"
]
}
],
"options_page": "views/options.html",
"action": {
"default_icon": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"icons": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}

View File

@ -0,0 +1,87 @@
import { SavePageInput, ToolbarMessage } from '../types'
import {
showLoggedOutToolbar,
showToolbar,
startToolbarDismiss,
updateToolbarStatus
} from './toolbar'
import { editLabels } from './labels'
const collectPageContent = async (): Promise<string> => {
const mainContent = document.documentElement.outerHTML
console.log('[omnivore] captured mainContent')
return mainContent
}
const handleToolbarMessage = async (
request: any,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void
) => {
console.log('[omnivore] content script message:', request)
switch (request.message) {
case 'showLoggedOutToolbar':
showLoggedOutToolbar()
sendResponse({ success: true })
break
case 'updateToolbar':
updateToolbarStatus(request.status, request.task)
sendResponse({ success: true })
break
case 'startToolbarDismiss':
startToolbarDismiss(request as ToolbarMessage)
sendResponse({ success: true })
return
case 'updateLabelCache':
editLabels()
break
default:
sendResponse({ success: false })
return
}
}
const setupToolbar = async (clientRequestId: string) => {
// toolbar message listener
if (!chrome.runtime.onMessage.hasListener(handleToolbarMessage)) {
chrome.runtime.onMessage.addListener(handleToolbarMessage)
}
await showToolbar(clientRequestId)
}
const savePage = async (clientRequestId: string) => {
console.log('[omnivore] v3 extension triggered: ', clientRequestId)
await setupToolbar(clientRequestId)
const content = await collectPageContent()
console.log('[omnivore] collected page content: ', content)
try {
const page: SavePageInput = {
clientRequestId,
title: document.title,
url: document.location.href,
originalContent: content,
}
await chrome.runtime.sendMessage({
action: 'savePage',
...page,
})
} catch (err) {
console.log('error sending content: ', err)
}
}
// toolbar message listener
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
switch (request.message) {
case 'savePage':
await savePage(request.clientRequestId)
sendResponse({ success: true })
return
}
})

View File

@ -0,0 +1,177 @@
import { getStorageItem } from '../utils'
import { Label } from '../types'
import { cancelAutoDismiss, getClientRequestId, toggleRow, updateStatusBox, updateToolbarStatus } from './toolbar'
export async function editLabels() {
cancelAutoDismiss()
const currentToastEl = document.querySelector('#omnivore-extension-root')
if (!currentToastEl || !currentToastEl.shadowRoot) {
console.log('no statusBox to update')
return
}
let labels = await getStorageItem('labels').then((value: any) => {
if (value && value.length > 0) {
return value as Label[]
}
return undefined
})
toggleRow('#omnivore-edit-labels-row')
currentToastEl.shadowRoot
.querySelector<HTMLInputElement>('#omnivore-edit-label-input')
?.focus()
const list = currentToastEl.shadowRoot.querySelector(
'#omnivore-edit-labels-list'
)
// Add a box for waiting for the labels.
if (!labels) {
console.error('No labels found, trying to update the cache.')
chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'updateLabelCache',
clientRequestId: getClientRequestId(),
})
return;
}
// currentToastEl.shadowRoot.querySelector(
// '#omnivore-edit-label-input'
// )<HTMLInputElement>.onkeydown = labelEditorKeyDownHandler
//
// currentToastEl.shadowRoot.querySelector(
// '#omnivore-edit-label-editor'
// )<HTMLInputElement>.onclick = labelEditorClickHandler
//
// currentToastEl.shadowRoot
// .querySelector<HTMLInputElement>('#omnivore-edit-label-input')
// .addEventListener('input', (event) => {
// updateLabels(event.target.value)
// })
if (list) {
list.innerHTML = ''
labels
.sort((a, b) =>
a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
)
.forEach(function (label, idx) {
const rowHtml = createLabelRow(label)
list.appendChild(rowHtml)
})
}
}
function createLabelRow(label: Label) {
const element = document.createElement('button')
const dot = document.createElement('span')
// @ts-ignore
dot.style = 'width:10px;height:10px;border-radius:1000px;'
dot.style.setProperty('background-color', label.color)
const title = document.createElement('span')
// @ts-ignore
title.style = 'margin-left: 10px;pointer-events: none;'
title.innerText = label.name
const check = document.createElement('span')
// @ts-ignore
check.style = 'margin-left: auto;pointer-events: none;'
check.className = 'checkbox'
check.innerHTML = `
<svg width="14" height="11" viewBox="0 0 14 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7411 1.75864L4.79692 10.7028L0.69751 6.60341L1.74845 5.55246L4.79692 8.59348L12.6902 0.707703L13.7411 1.75864Z" fill="#888888"/>
</svg>
`
element.appendChild(dot)
element.appendChild(title)
element.appendChild(check)
element.onclick = labelClick
// element.onkeydown = labelEditorKeyDownHandler
element.setAttribute('data-label-id', label.id)
element.setAttribute(
'data-label-selected',
label['selected'] ? 'on' : 'off'
)
element.setAttribute('tabIndex', '-1')
return element
}
function labelClick(event: any) {
event.preventDefault()
const labelId = event.target?.getAttribute('data-label-id')
if (labelId) {
toggleLabel(event, labelId)
}
}
async function toggleLabel(event: any, labelId: string) {
const labelSelected = event.target?.getAttribute('data-label-selected')
if (!labelId || !labelSelected) {
return
}
const toggledValue = labelSelected == 'on' ? false : true
event.target?.setAttribute(
'data-label-selected',
toggledValue ? 'on' : 'off'
)
let labels = await getStorageItem('labels').then((value: any) => {
if (value && value.length > 0) {
return value as Label[]
}
return undefined
})
if (!labels) {
throw Error("No labels selected")
}
const label = labels.find((l) => l.id === labelId)
if (label) {
syncLabelChanges()
}
}
function syncLabelChanges() {
updateStatusBox(
'#omnivore-edit-labels-status',
'waiting',
'Updating Labels...',
undefined
)
const currentToastEl = document.querySelector('#omnivore-extension-root')
const labels = currentToastEl?.shadowRoot?.querySelector("#omnivore-edit-labels-list")
if (labels) {
const setLabels = [...labels.children]
.filter((l) => l.getAttribute('data-label-selected') === 'on')
.map((l) => l.getAttribute('data-label-id')!)
chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'setLabels',
clientRequestId: getClientRequestId(),
labels: setLabels,
})
}
}

View File

@ -0,0 +1,593 @@
import { ToolbarMessage, ToolbarStatus } from '../types'
import { getStorageItem, setStorage } from '../utils'
import { editLabels } from './labels'
const systemIcons: { [key: string]: string } = {
waiting: '<div class="loading-spinner"></div>',
success: `
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.91626 18.6047C14.8868 18.6047 18.9163 14.5752 18.9163 9.60468C18.9163 4.63411 14.8868 0.604675 9.91626 0.604675C4.9457 0.604675 0.91626 4.63411 0.91626 9.60468C0.91626 14.5752 4.9457 18.6047 9.91626 18.6047ZM9.91626 17.1046C14.0584 17.1046 17.4163 13.7468 17.4163 9.60463C17.4163 5.4625 14.0584 2.10463 9.91626 2.10463C5.77412 2.10463 2.41626 5.4625 2.41626 9.60463C2.41626 13.7468 5.77412 17.1046 9.91626 17.1046Z" fill="#32D74B"/>
<path d="M13.3538 7.28851L8.7704 11.9209L6.47876 9.60469" stroke="#32D74B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`,
failure: `
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.74048 18.5508C14.711 18.5508 18.7405 14.5213 18.7405 9.55078C18.7405 4.58022 14.711 0.550781 9.74048 0.550781C4.76992 0.550781 0.740479 4.58022 0.740479 9.55078C0.740479 14.5213 4.76992 18.5508 9.74048 18.5508ZM9.74048 17.0507C13.8826 17.0507 17.2405 13.6929 17.2405 9.55074C17.2405 5.4086 13.8826 2.05074 9.74048 2.05074C5.59834 2.05074 2.24048 5.4086 2.24048 9.55074C2.24048 13.6929 5.59834 17.0507 9.74048 17.0507Z" fill="#C7372F"/>
<path d="M12.794 11.897L12.794 11.897L10.4474 9.55078L12.794 7.2046L12.794 7.20459C12.8878 7.11079 12.9405 6.98358 12.9405 6.85093C12.9405 6.71828 12.8878 6.59107 12.794 6.49727C12.7002 6.40348 12.573 6.35078 12.4403 6.35078C12.3077 6.35078 12.1805 6.40348 12.0867 6.49727L12.0867 6.49728L9.74048 8.84382L7.3943 6.49728L7.39429 6.49727C7.30049 6.40348 7.17328 6.35078 7.04063 6.35078C6.90798 6.35078 6.78077 6.40348 6.68697 6.49727C6.59317 6.59107 6.54048 6.71828 6.54048 6.85093C6.54048 6.98358 6.59317 7.11079 6.68697 7.20459L6.68698 7.2046L9.03351 9.55078L6.68698 11.897L6.68697 11.897C6.59317 11.9908 6.54048 12.118 6.54048 12.2506C6.54048 12.3833 6.59317 12.5105 6.68697 12.6043C6.78077 12.6981 6.90798 12.7508 7.04063 12.7508C7.17328 12.7508 7.30049 12.6981 7.39429 12.6043L7.3943 12.6043L9.74048 10.2577L12.0867 12.6043L12.0867 12.6043C12.1331 12.6507 12.1882 12.6876 12.2489 12.7127C12.3096 12.7378 12.3746 12.7508 12.4403 12.7508C12.506 12.7508 12.571 12.7378 12.6317 12.7127C12.6924 12.6876 12.7474 12.6507 12.7938 12.6043C12.8878 12.5105 12.9405 12.3833 12.9405 12.2506C12.9405 12.118 12.8878 11.9908 12.7938 11.897L12.7939 11.897H12.794Z" fill="#C7372F" stroke="#C7372F" stroke-width="0.3"/>
</svg>
`,
}
const createToastContainer = async (clientRequestId: string) => {
console.log('===== CREATING TOAST CONTAINER ===== ')
const file = await fetch(chrome.runtime.getURL('views/toast.html'))
const html = await file.text()
const root = document.createElement('div')
root.tabIndex = 0
root.id = 'omnivore-extension-root'
root.attachShadow({ mode: 'open' })
root.style.opacity = '1.0'
if (root.shadowRoot) {
root.shadowRoot.innerHTML = `<style>:host {all initial;}</style>`
} else {
alert('Error opening Omnivore user interface.')
return
}
const toastEl = document.createElement('div')
toastEl.id = '#omnivore-toast'
toastEl.innerHTML = html
toastEl.tabIndex = 0
root.shadowRoot.appendChild(toastEl)
document.body.appendChild(root)
connectButtons(root)
// connectKeyboard(root)
updateToolbarStatus('waiting')
return root
}
export const showToolbar = async (clientRequestId: string) => {
let currentToastEl =
document.querySelector<HTMLElement>('#omnivore-extension-root') ?? undefined
const bodyEl = document.body
if (!bodyEl) return
console.log('existing currentToastEl: ', currentToastEl)
if (!currentToastEl) {
currentToastEl = await createToastContainer(clientRequestId)
}
const disableAutoDismiss = await getStorageItem('disableAutoDismiss')
if (disableAutoDismiss) {
currentToastEl?.setAttribute('data-disable-auto-dismiss', 'true')
}
currentToastEl?.setAttribute(
'data-omnivore-client-request-id',
clientRequestId
)
;(currentToastEl as HTMLDivElement)?.focus({
preventScroll: true,
})
updateToolbarStatus('waiting')
}
const autoDismissTime = async () => {
const strVal = (await getStorageItem('autoDismissTime')) ?? '2500'
return !Number.isNaN(Number(strVal)) ? Number(strVal) : 2500
}
export const updateToolbarStatus = async (
status: ToolbarStatus,
task: string | undefined = undefined
) => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
const statusBox = currentToastEl?.shadowRoot?.querySelector(
'.omnivore-toast-statusBox'
)
console.log('updating', status, statusBox)
if (statusBox) {
switch (status) {
case 'success':
statusBox.innerHTML = systemIcons.success
break
case 'failure':
statusBox.innerHTML = systemIcons.failure
break
case 'waiting':
statusBox.innerHTML = systemIcons.waiting
break
}
}
// Set a task specific message
if (task) {
if (task == 'addNote' && status == 'failure') {
updateStatusBox(
'#omnivore-add-note-status',
'failure',
'Error adding note...',
undefined
)
}
if (task == 'addNote' && status == 'success') {
updateStatusBox(
'#omnivore-add-note-status',
'success',
'Note updated.',
2500
)
setTimeout(() => {
toggleRow('#omnivore-add-note-status')
}, 3000)
}
if (task == 'setLabels' && status == 'success') {
updateStatusBox(
'#omnivore-edit-labels-status',
'success',
'Labels Updated',
2500
)
}
if (task == 'setLabels' && status == 'failure') {
updateStatusBox(
'#omnivore-edit-labels-status',
'failure',
'Error Updating Labels...',
2500
)
}
if (task == 'editTitle' && status == 'failure') {
updateStatusBox(
'#omnivore-add-note-status',
'failure',
'Error updating title...',
undefined
)
}
if (task == 'editTitle' && status == 'success') {
updateStatusBox(
'omnivore-edit-title-status',
'success',
'Title updated.',
2500
)
setTimeout(() => {
toggleRow('#omnivore-edit-title-status')
}, 3000)
}
if (task == 'archive') {
updateStatusBox(
'#omnivore-extra-status',
status,
status == 'success' ? 'Success' : 'Error',
status == 'success' ? 2500 : undefined
)
if (status == 'success') {
closeToolbarLater()
}
}
}
}
export const cancelAutoDismiss = () => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
if (currentToastEl) {
currentToastEl.setAttribute('data-disable-auto-dismiss', 'true')
}
}
// If the user has not disabled auto dismiss on the toolbar this
// will remove it. If the user interacts with the toolbar, this
// dismiss will also be ignored.
export const startToolbarDismiss = async (message: ToolbarMessage) => {
if (message.status) {
updateToolbarStatus(message.status)
}
const dimissTime = await autoDismissTime()
setTimeout(() => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
if (
currentToastEl &&
!currentToastEl.getAttribute('data-disable-auto-dismiss')
) {
;(currentToastEl as HTMLElement).style.transition = 'opacity 3.5s ease;'
;(currentToastEl as HTMLElement).style.opacity = '0'
setTimeout(() => {
const currentToastEl = document.querySelector(
'#omnivore-extension-root'
)
if (
currentToastEl &&
!currentToastEl.getAttribute('data-disable-auto-dismiss')
) {
currentToastEl.remove()
}
}, 500)
}
}, dimissTime)
}
const connectButtons = (root: HTMLElement) => {
const btns = [
{ id: '#omnivore-toast-add-note-btn', func: addNote },
{ id: '#omnivore-toast-edit-title-btn', func: editTitle },
{ id: '#omnivore-toast-edit-labels-btn', func: editLabels },
{ id: '#omnivore-toast-read-now-btn', func: readNow },
{ id: '#omnivore-open-menu-btn', func: openMenu },
{ id: '#omnivore-toast-close-btn', func: closeToolbar },
{ id: '#omnivore-toast-login-btn', func: login },
{ id: '#omnivore-toast-archive-btn', func: archive },
{ id: '#omnivore-toast-delete-btn', func: deleteItem },
]
for (const btnInfo of btns) {
const btn = root.shadowRoot?.querySelector(btnInfo.id)
if (btn) {
console.log(btnInfo.id)
btn.addEventListener('click', btnInfo.func)
}
}
var x = window.matchMedia('(max-width: 500px)')
if (x.matches) {
const labels = root.shadowRoot?.querySelectorAll<HTMLElement>(
'.omnivore-top-button-label'
)
labels?.forEach((label) => {
label.style.display = 'none'
})
const container = root.shadowRoot?.querySelector<HTMLElement>(
'#omnivore-toast-container'
)
if (container) {
container.style.width = '280px'
container.style.top = 'unset'
container.style.bottom = '20px'
}
}
}
function editTitle() {
cancelAutoDismiss()
toggleRow('#omnivore-edit-title-row')
let currentToastEl =
document.querySelector<HTMLElement>('#omnivore-extension-root') ?? undefined
if (!currentToastEl) {
console.log('no statusBox to update')
return
}
const titleArea = currentToastEl?.shadowRoot?.querySelector<HTMLTextAreaElement>(
'#omnivore-edit-title-textarea'
)
if (titleArea) {
titleArea.focus()
titleArea.onkeydown = (e) => {
e.cancelBubble = true
}
}
const formElement = currentToastEl?.shadowRoot?.querySelector<HTMLFormElement>(
'#omnivore-edit-title-form'
);
if (!formElement) {
console.log('no form to update')
return
}
formElement.onsubmit = (event) => {
updateStatusBox(
'#omnivore-edit-title-status',
'waiting',
'Updating title...',
undefined
)
const title = titleArea?.value ?? ''
chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'editTitle',
clientRequestId: getClientRequestId(),
title
})
event.preventDefault()
}
}
export const showLoggedOutToolbar = () => {
cancelAutoDismiss()
updateToolbarStatus('failure')
toggleRow('#omnivore-logged-out-row')
disableAllButtons()
updateStatusBox(
'#omnivore-logged-out-status',
undefined,
`You are not logged in.`,
undefined
)
}
export const updateStatusBox = (
boxId: string,
state: ToolbarStatus | undefined,
message: string,
dismissAfter: number | undefined
) => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
const statusBox = currentToastEl?.shadowRoot?.querySelector(boxId)
const image = (() => {
switch (state) {
case 'waiting':
return systemIcons.animatedLoader
case 'success':
return systemIcons.success
case 'failure':
return systemIcons.failure
default:
return undefined
}
})()
if (image && statusBox) {
const color = state == 'failure' ? 'red' : 'unset'
statusBox.innerHTML = `<span style='padding-right: 10px'>${image}</span><span style='line-height: 20px;color: ${color};text-decoration: none;'>${message}</span>`
} else if (statusBox) {
statusBox.innerHTML = message
}
if (dismissAfter && statusBox) {
setTimeout(() => {
statusBox.innerHTML = ''
}, dismissAfter)
}
}
const disableAllButtons = () => {
const actionButtons = [
'#omnivore-toast-edit-title-btn',
'#omnivore-toast-edit-labels-btn',
'#omnivore-toast-read-now-btn',
'#omnivore-toast-add-note-btn',
'#omnivore-open-menu-btn',
]
let currentToastEl = document.querySelector('#omnivore-extension-root')
actionButtons.forEach((btnId) => {
const btn =
currentToastEl?.shadowRoot?.querySelector<HTMLButtonElement>(btnId)
if (btn) {
btn.disabled = true
}
})
}
export const toggleRow = (rowId: string) => {
let currentToastEl = document.querySelector('#omnivore-extension-root')
if (!currentToastEl) {
console.log('toggleRow: no row to toggle')
// its possible this was called after closing the extension
// so just return
return
}
const container = currentToastEl?.shadowRoot?.querySelector(rowId)
const initialState = container?.getAttribute('data-state')
const rows = currentToastEl?.shadowRoot?.querySelectorAll(
'.omnivore-toast-func-row'
)
rows?.forEach((r) => {
r.setAttribute('data-state', 'closed')
})
if (container && initialState) {
const newState = initialState === 'open' ? 'closed' : 'open'
container.setAttribute('data-state', newState)
}
}
const noteCacheKey = () => {
return `cached-note-${document.location.href}`
}
export const getClientRequestId = () => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
const clientRequestId = currentToastEl?.getAttribute(
'data-omnivore-client-request-id'
)
return clientRequestId
}
//
// Button functions
//
const login = () => {
window.open(new URL(`/login`, process.env.OMNIVORE_URL), '_blank')
closeToolbarLater()
}
const openMenu = () => {
cancelAutoDismiss()
toggleRow('#omnivore-extra-buttons-row')
}
const addNote = async () => {
console.log('[omnivore] adding note')
cancelAutoDismiss()
const currentToastEl = document.querySelector('#omnivore-extension-root')
const clientRequestId = currentToastEl?.getAttribute(
'data-omnivore-client-request-id'
)
console.log('client request id: ', clientRequestId)
if (!clientRequestId) {
// TODO: move into an error state
updateStatusBox(
'#omnivore-add-note-status',
'failure',
'Error adding note...',
undefined
)
return
}
const cachedNoteKey = noteCacheKey()
cancelAutoDismiss()
toggleRow('#omnivore-add-note-row')
const noteArea =
currentToastEl?.shadowRoot?.querySelector<HTMLTextAreaElement>(
'#omnivore-add-note-textarea'
)
if (noteArea) {
if (cachedNoteKey) {
const existingNote =
((await getStorageItem(cachedNoteKey)) as string) ?? ''
noteArea.value = existingNote
}
if (noteArea.value) {
noteArea.select()
} else {
noteArea.focus()
}
noteArea.addEventListener('input', async (event) => {
const note: Record<string, string> = {}
note[cachedNoteKey] = (event.target as HTMLTextAreaElement).value
await setStorage(note)
})
noteArea.onkeydown = async (e: KeyboardEvent) => {
// e.preventDefault()
e.stopPropagation()
// Handle the enter key
console.log('handling the enter key: ', e.keyCode)
if (e.keyCode == 13 && (e.metaKey || e.ctrlKey)) {
updateStatusBox(
'#omnivore-add-note-status',
'waiting',
'Adding note...',
undefined
)
await saveNote(clientRequestId, noteArea.value)
}
}
}
const form = currentToastEl?.shadowRoot?.querySelector<HTMLElement>(
'#omnivore-add-note-form'
)
if (form) {
form.onsubmit = async (event) => {
console.log('handling form submit')
updateStatusBox(
'#omnivore-add-note-status',
'waiting',
'Adding note...',
undefined
)
if (noteArea) {
await saveNote(clientRequestId, noteArea.value)
}
event.preventDefault()
event.stopPropagation()
}
}
}
const archive = async (event: Event) => {
const clientRequestId = getClientRequestId()
try {
await chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'archive',
clientRequestId,
})
} catch (err) {
console.log('error archiving item')
}
event.preventDefault()
}
const deleteItem = async (event: Event) => {
const clientRequestId = getClientRequestId()
try {
await chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'delete',
clientRequestId,
})
} catch (err) {
console.log('error archiving item')
}
event.preventDefault()
}
const readNow = async () => {
cancelAutoDismiss()
let currentToastEl = document.querySelector('#omnivore-extension-root')
const container = currentToastEl?.shadowRoot?.querySelector(
'#omnivore-toast-container'
)
container?.setAttribute('data-state', 'open')
window.open(
new URL(
`/article?url=${encodeURI(document.location.href)}`,
(await getStorageItem("omnivoreUrl")) as string,
),
'_blank'
)
closeToolbarLater()
}
const closeToolbarLater = () => {
setTimeout(() => {
closeToolbar()
}, 1000)
}
const closeToolbar = () => {
const currentToastEl = document.querySelector('#omnivore-extension-root')
if (currentToastEl) {
currentToastEl.remove()
}
}
//
// API interactions
//
const saveNote = async (clientRequestId: string, note: string) => {
try {
await chrome.runtime.sendMessage({
action: 'enqueueTask',
task: 'addNote',
note,
clientRequestId,
})
} catch (err) {
console.log('error adding note: ', err)
}
}

View File

@ -0,0 +1,528 @@
import { ArticleData, Label, SavePageData, SetLinkArchivedData } from './types'
import { getStorageItem, setStorage } from './utils'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
export type ApiResult = 'success' | 'failure' | 'unauthorized'
const gqlRequest = async (query: string) => {
const apiKey = (await getStorageItem('omnivoreApiKey')) as string | undefined
let headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
} as Record<string, string>
if (apiKey) {
headers['Authorization'] = apiKey
}
try {
const apiUrl= (await getStorageItem('omnivoreApiUrl')) as string | undefined
?? process.env.OMNIVORE_GRAPHQL_URL
?? ''
console.log(apiUrl)
const response = await fetch(`${apiUrl}/api/graphql`, {
method: 'POST',
redirect: 'follow',
credentials: 'include',
mode: 'cors',
headers,
body: query,
})
const json = await response.json()
if (!('data' in json) || !json.data) {
throw new Error('No response data')
}
return json.data
} catch (err) {
console.log('[omnivore] error making api request: ', query)
}
}
export async function savePageRequest(input: {
url: string
title: string
clientRequestId: string
originalContent: string
}) {
const mutation = JSON.stringify({
query: `mutation SavePage ($input: SavePageInput!) {
savePage(input:$input){
... on SaveSuccess {
url
clientRequestId
}
... on SaveError {
errorCodes
}
}
}`,
variables: {
input: {
source: 'extension',
...input,
},
},
})
const data = (await gqlRequest(mutation)) as SavePageData
if (data.savePage?.errorCodes?.length) {
console.log('[omnivore] api: error saving page:', data)
if (data.savePage.errorCodes.indexOf('UNAUTHORIZED') > -1) {
console.log('[omnivore] api is not authorized')
return { result: 'unauthorized' }
}
return { result: 'failure' }
}
return { result: 'success', libraryItemId: data.savePage?.clientRequestId }
}
export async function addNoteToLibraryItem(input: {
libraryItemId: string
note: string
}) {
const query = JSON.stringify({
query: `query GetArticle(
$username: String!
$slug: String!
$includeFriendsHighlights: Boolean
) {
article(username: $username, slug: $slug) {
... on ArticleSuccess {
article {
highlights(input: { includeFriends: $includeFriendsHighlights }) {
...HighlightFields
}
}
}
... on ArticleError {
errorCodes
}
}
}
fragment HighlightFields on Highlight {
id
type
annotation
}
`,
variables: {
username: 'me',
slug: input.libraryItemId,
includeFriendsHighlights: false,
},
})
const data = (await gqlRequest(query)) as ArticleData
if (data.article?.errorCodes?.length) {
console.log('[omnivore] api: error getting article:', data)
if (data.article.errorCodes.indexOf('UNAUTHORIZED') > -1) {
console.log('[omnivore] api is not authorized')
return 'unauthorized'
}
return 'failure'
}
console.log('DATA.ARTICLE: ', data.article)
const existingNote = data.article?.highlights?.find((h) => h.type == 'NOTE')
if (existingNote) {
const mutation = JSON.stringify({
query: `
mutation UpdateHighlight($input: UpdateHighlightInput!) {
updateHighlight(input: $input) {
... on UpdateHighlightSuccess {
highlight {
id
}
}
... on UpdateHighlightError {
errorCodes
}
}
}
`,
variables: {
input: {
highlightId: existingNote.id,
annotation: existingNote.annotation
? existingNote.annotation + '\n\n' + input.note
: input.note,
},
},
})
const result = await gqlRequest(mutation)
if (
!result.updateHighlight ||
result.updateHighlight['errorCodes'] ||
!result.updateHighlight.highlight
) {
console.log('GQL Error updating note:', result)
return
}
return result.updateHighlight.highlight.id
} else {
const noteId = uuidv4()
const shortId = nanoid(8)
const mutation = JSON.stringify({
query: `
mutation CreateHighlight($input: CreateHighlightInput!) {
createHighlight(input: $input) {
... on CreateHighlightSuccess {
highlight {
id
}
}
... on CreateHighlightError {
errorCodes
}
}
}
`,
variables: {
input: {
id: noteId,
shortId: shortId,
type: 'NOTE',
articleId: input.libraryItemId,
annotation: input.note,
},
},
})
const result = await gqlRequest(mutation)
if (
!result.createHighlight ||
result.createHighlight['errorCodes'] ||
!result.createHighlight.highlight
) {
console.log('GQL Error setting note:', result)
return 'failure'
}
return 'success'
}
}
export async function updateLabelsCache(): Promise<Label[]> {
const query = JSON.stringify({
query: `query GetLabels {
labels {
... on LabelsSuccess {
labels {
...LabelFields
}
}
... on LabelsError {
errorCodes
}
}
}
fragment LabelFields on Label {
id
name
color
description
createdAt
}
`,
})
const data = await gqlRequest(query)
if (!data.labels || data.labels['errorCodes'] || !data.labels['labels']) {
console.log('GQL Error updating label cache response:', data, data)
console.log(!data.labels, data.labels['errorCodes'], !data.labels['labels'])
return []
}
await setStorage({
labels: data.labels.labels,
labelsLastUpdated: new Date().toISOString(),
})
return data.labels.labels
}
export async function updatePageTitle(pageId: string, title: string) {
const mutation = JSON.stringify({
query: `mutation UpdatePage($input: UpdatePageInput!) {
updatePage(input: $input) {
... on UpdatePageSuccess {
updatedPage {
id
}
}
... on UpdatePageError {
errorCodes
}
}
}
`,
variables: {
input: {
pageId,
title,
},
},
})
const data = await gqlRequest(mutation)
console.log(data);
if (
!data.updatePage ||
data.updatePage['errorCodes'] ||
!data.updatePage['updatedPage']
) {
console.log('GQL Error updating page:', data)
throw new Error('Error updating title.')
}
return 'success'
}
export async function setLabels(pageId: string, labels: string[]) {
const mutation = JSON.stringify({
query: `mutation SetLabels($input: SetLabelsInput!) {
setLabels(input: $input) {
... on SetLabelsSuccess {
labels {
id
name
color
}
}
... on SetLabelsError {
errorCodes
}
}
}
`,
variables: {
input: {
pageId,
labelIds: labels,
},
},
})
const data = await gqlRequest(mutation)
if (
!data.setLabels ||
data.setLabels['errorCodes'] ||
!data.setLabels['labels']
) {
console.log('GQL Error setting labels:', data)
throw new Error('Error setting labels.')
}
return data.setLabels.labels
}
// async function appendLabelsToCache(labels) {
// const cachedLabels = await getStorageItem('labels')
// if (cachedLabels) {
// labels.forEach((l) => {
// const existing = cachedLabels.find((cached) => cached.name === l.name)
// if (!existing) {
// cachedLabels.unshift(l)
// }
// })
// await setStorage({
// labels: cachedLabels,
// labelsLastUpdated: new Date().toISOString(),
// })
// } else {
// await setStorage({
// labels: labels,
// labelsLastUpdated: new Date().toISOString(),
// })
// }
// }
// async function addNote(apiUrl, pageId, noteId, shortId, note) {
// const query = JSON.stringify({
// query: `query GetArticle(
// $username: String!
// $slug: String!
// $includeFriendsHighlights: Boolean
// ) {
// article(username: $username, slug: $slug) {
// ... on ArticleSuccess {
// article {
// highlights(input: { includeFriends: $includeFriendsHighlights }) {
// ...HighlightFields
// }
// }
// }
// ... on ArticleError {
// errorCodes
// }
// }
// }
// fragment HighlightFields on Highlight {
// id
// type
// annotation
// }
// `,
// variables: {
// username: 'me',
// slug: pageId,
// includeFriendsHighlights: false,
// },
// })
// const data = await gqlRequest(apiUrl, query)
// if (!data.article || data.article['errorCodes'] || !data.article['article']) {
// console.log('GQL Error getting existing highlights:', data)
// return
// }
// const existingNote = data.article.article.highlights.find(
// (h) => h.type == 'NOTE'
// )
// if (existingNote) {
// const mutation = JSON.stringify({
// query: `
// mutation UpdateHighlight($input: UpdateHighlightInput!) {
// updateHighlight(input: $input) {
// ... on UpdateHighlightSuccess {
// highlight {
// id
// }
// }
// ... on UpdateHighlightError {
// errorCodes
// }
// }
// }
// `,
// variables: {
// input: {
// highlightId: existingNote.id,
// annotation: existingNote.annotation
// ? existingNote.annotation + '\n\n' + note
// : note,
// },
// },
// })
// const result = await gqlRequest(apiUrl, mutation)
// if (
// !result.updateHighlight ||
// result.updateHighlight['errorCodes'] ||
// !result.updateHighlight.highlight
// ) {
// console.log('GQL Error updating note:', result)
// return
// }
// return result.updateHighlight.highlight.id
// } else {
// const mutation = JSON.stringify({
// query: `
// mutation CreateHighlight($input: CreateHighlightInput!) {
// createHighlight(input: $input) {
// ... on CreateHighlightSuccess {
// highlight {
// id
// }
// }
// ... on CreateHighlightError {
// errorCodes
// }
// }
// }
// `,
// variables: {
// input: {
// id: noteId,
// shortId: shortId,
// type: 'NOTE',
// articleId: pageId,
// annotation: note,
// },
// },
// })
// const result = await gqlRequest(apiUrl, mutation)
// if (
// !result.createHighlight ||
// result.createHighlight['errorCodes'] ||
// !result.createHighlight.highlight
// ) {
// console.log('GQL Error setting note:', result)
// return
// }
// return result.createHighlight.highlight.id
// }
// }
export const archiveLibraryItem = async (
libraryItemId: string
): Promise<ApiResult> => {
const mutation = JSON.stringify({
query: `mutation SetLinkArchived($input: ArchiveLinkInput!) {
setLinkArchived(input: $input) {
... on ArchiveLinkSuccess {
linkId
message
}
... on ArchiveLinkError {
message
errorCodes
}
}
}
`,
variables: {
input: {
linkId: libraryItemId,
archived: true,
},
},
})
const data = (await gqlRequest(mutation)) as SetLinkArchivedData
if (data.setLinkArchived?.errorCodes?.length) {
console.log('[omnivore] api: error getting article:', data)
if (data.setLinkArchived.errorCodes.indexOf('UNAUTHORIZED') > -1) {
console.log('[omnivore] api is not authorized')
return 'unauthorized'
}
return 'failure'
}
return 'success'
}
export async function deleteItem(pageId: string) {
const mutation = JSON.stringify({
query: `mutation SetBookmarkArticle($input: SetBookmarkArticleInput!) {
setBookmarkArticle(input: $input) {
... on SetBookmarkArticleSuccess {
bookmarkedArticle {
id
}
}
... on SetBookmarkArticleError {
errorCodes
}
}
}
`,
variables: {
input: {
articleID: pageId,
bookmark: false,
},
},
})
const data = await gqlRequest(mutation)
if (
!data.setBookmarkArticle ||
data.setBookmarkArticle['errorCodes'] ||
!data.setBookmarkArticle.bookmarkedArticle
) {
console.log('GQL Error deleting:', data)
throw new Error('Error deleting.')
}
return 'success'
}

View File

@ -0,0 +1,112 @@
export type ToolbarStatus = 'waiting' | 'success' | 'failure'
export interface ToolbarMessage {
status?: ToolbarStatus
}
export interface SavePageData {
savePage?: SavePageResult
}
export interface SavePageResult {
url?: string
clientRequestId?: string
errorCodes?: string[]
}
export interface Highlight {
id: string
type: string
annotation: string
}
export interface ArticleResult {
highlights?: Highlight[]
errorCodes?: string[]
}
export interface ArticleData {
article?: ArticleResult
}
export interface SetLinkArchivedResult {
linkId?: string
message?: string
errorCodes?: string[]
}
export interface SetLinkArchivedData {
setLinkArchived?: SetLinkArchivedResult
}
export interface SavePageInput {
url: string
title: string
clientRequestId: string
originalContent: string
}
export interface AddNoteInput {
clientRequestId: string
note: string
}
export interface TaskInput {
clientRequestId: string
libraryItemId?: string | undefined
task: 'addNote' | 'archive' | 'editTitle' | 'delete' | 'updateLabelCache' | 'setLabels'
title?: string | undefined
note?: string | undefined
labels?: string[] | undefined
}
export interface Label {
id: string
name: string
color: string
selected: 'on' | 'off'
}
export function isSavePageResult(obj: any): obj is SavePageResult {
return (
typeof obj === 'object' &&
(obj.url === undefined || typeof obj.url === 'string') &&
(obj.clientRequestId === undefined ||
typeof obj.clientRequestId === 'string') &&
(obj.errorCodes === undefined ||
(Array.isArray(obj.errorCodes) &&
obj.errorCodes.every((code: any) => typeof code === 'string')))
)
}
export function isSavePageData(obj: any): obj is SavePageData {
return (
typeof obj === 'object' &&
(obj.savePage === undefined || isSavePageResult(obj.savePage))
)
}
export function isSavePageInput(obj: any): obj is SavePageInput {
return (
typeof obj === 'object' &&
typeof obj.url === 'string' &&
typeof obj.title === 'string' &&
typeof obj.clientRequestId === 'string' &&
typeof obj.originalContent === 'string'
)
}
export function isAddNoteInput(obj: any): obj is AddNoteInput {
return (
typeof obj === 'object' &&
typeof obj.note === 'string' &&
typeof obj.clientRequestId === 'string'
)
}
export function isEnqueueTaskMessage(obj: any): obj is TaskInput {
return (
typeof obj === 'object' &&
typeof obj.task === 'string' &&
typeof obj.clientRequestId === 'string'
)
}

View File

@ -0,0 +1,18 @@
export const getStorageItem = async (singleKey: string) => {
return new Promise((resolve) => {
chrome.storage.local.get(singleKey, (result) => {
const finalResult = (result && result[singleKey]) || null
resolve(finalResult)
})
})
}
export const setStorage = (itemsToSet: Record<string, string>) => {
return chrome.storage.local.set(itemsToSet)
}
// function removeStorage(itemsToRemove) {
// return new Promise((resolve) => {
// browserApi.storage.local.remove(itemsToRemove, resolve)
// })
// }

View File

@ -0,0 +1,126 @@
import {
addNoteToLibraryItem,
archiveLibraryItem,
deleteItem, setLabels, updateLabelsCache,
updatePageTitle
} from './scripts/omnivore-api'
import { TaskInput } from './scripts/types'
export class TaskQueue {
private queue: Array<TaskInput> = []
private isRunning: boolean = false
private isReady: boolean = false
private tabId: number | undefined
private libraryItemId: string | undefined
constructor() {}
enqueue(task: TaskInput): void {
this.queue.push(task)
// Only run the next task if the queue is ready
if (this.isReady) {
this.runNext()
}
}
private async runNext(): Promise<void> {
if (this.isRunning || this.queue.length === 0 || !this.isReady) return
this.isRunning = true
const task = this.queue.shift()
if (task) {
try {
await this.executeTask(task)
} catch (err) {
console.error('Task failed:', err)
} finally {
this.isRunning = false
if (this.isReady) {
this.runNext()
}
}
}
}
private executeTask = async (task: TaskInput) => {
console.log('executing task: ', task)
if (!this.libraryItemId) {
throw Error('Attempting to execute queue that is not ready.')
}
try {
let success = false
switch (task.task) {
case 'archive': {
await archiveLibraryItem(this.libraryItemId)
success = true
break
}
case 'delete': {
await deleteItem(this.libraryItemId)
success = true
break
}
case 'addNote': {
await addNoteToLibraryItem({
note: task.note || '',
libraryItemId: this.libraryItemId,
})
success = true
break
}
case 'setLabels':
await setLabels(this.libraryItemId, task.labels ?? [])
success = true;
break
case 'editTitle': {
if (!task.title || !this.libraryItemId) {
throw new Error("Title not set, or library item not yet saved.")
}
await updatePageTitle(this.libraryItemId, task.title)
success = true
break;
}
case 'updateLabelCache': {
await updateLabelsCache()
if (this.tabId) {
chrome.tabs.sendMessage(this.tabId, {
message: 'updateLabelCache',
status: 'success',
task: task.task,
})
}
}
}
if (success && this.tabId) {
chrome.tabs.sendMessage(this.tabId, {
message: 'updateToolbar',
status: 'success',
task: task.task,
})
}
} catch (err) {
console.log('[omnivore] task queue error: ', err)
if (this.tabId) {
console.log('sending error message')
chrome.tabs.sendMessage(this.tabId, {
message: 'updateToolbar',
status: 'failure',
task: task.task,
})
}
}
}
setReady(tabId: number, libraryItemId: string): void {
console.log('setting ready')
this.tabId = tabId
this.libraryItemId = libraryItemId
this.isReady = true
this.runNext()
}
}

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>Omnivore Extension Settings</title>
</head>
<body>
<div class='wrapper'>
<h1>API Key</h1>
<label for="api-key">API Key:</label>
<input type="text" id="api-key" style="width: 95%">
<br><br>
<button id="save-api-key-btn">Save API Key</button>
<h1>API URL</h1>
<label for="api-url">API URL:</label>
<input type="text" id="api-url" style="width: 95%">
<br><br>
<button id="save-api-url-btn">Save API URL</button>
<h1>OMNIVORE URL</h1>
<label for="omnivore-url">API URL:</label>
<input type="text" id="omnivore-url" style="width: 95%">
<br><br>
<button id="save-omnivore-url-btn">Save Omnivore URL</button>
<script src="options.js"></script>
</body>
</html>

View File

@ -0,0 +1,46 @@
function addStorage(itemsToAdd) {
return chrome.storage.local.set(itemsToAdd)
}
document.addEventListener('DOMContentLoaded', () => {
const saveApiButton = document.getElementById('save-api-key-btn')
const apiInput = document.getElementById('api-key')
chrome.storage.local.get('omnivoreApiKey').then(
apiKey => {
apiInput.value = apiKey.omnivoreApiKey ?? ''
}
)
saveApiButton.addEventListener('click', (e) => {
addStorage({ "omnivoreApiKey": apiInput.value })
})
const saveUrlButton = document.getElementById('save-api-url-btn')
const apiUrlInput = document.getElementById('api-url')
chrome.storage.local.get('omnivoreApiUrl').then(
url => {
apiUrlInput.value = url.omnivoreApiUrl ?? ''
}
)
saveUrlButton.addEventListener('click', (e) => {
addStorage({ "omnivoreApiUrl": apiUrlInput.value })
})
const urlButton = document.getElementById('save-omnivore-url-btn')
const urlInput = document.getElementById('omnivore-url')
chrome.storage.local.get('omnivoreUrl').then(
url => {
urlInput.value = url.omnivoreUrl ?? ''
}
)
urlButton.addEventListener('click', (e) => {
addStorage({ "omnivoreUrl": urlInput.value })
})
});

View File

@ -0,0 +1,555 @@
<style>
#omnivore-toast-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-end;
overflow: hidden;
border-radius: 4px;
color: #3D3D3D;
background: #fff;
font: 400 12px sans-serif;
line-height: 20px;
box-shadow: 0px 5px 20px rgba(32, 31, 29, 0.12);
transition: all 300ms ease;
z-index: 9999999;
width: 480px;
}
#omnivore-toast-container input, select, textarea{
color: #3D3D3D;
}
#omnivore-toast-container .omnivore-toast-func-row {
display: flex;
width: 100%;
padding-top: 0px;
padding-left: 15px;
padding-right: 15px;
padding-bottom: 10px;
}
#omnivore-toast-container #omnivore-extra-buttons-row {
padding-left: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button {
padding-left: 10px;
width: 80%;
}
#omnivore-toast-container #omnivore-logged-out-row {
flex-direction: column;
align-items: center;
justify-content: center;
}
#omnivore-toast-container .omnivore-toast-func-row[data-state="open"] {
display: flex;
}
#omnivore-toast-container .omnivore-toast-func-row[data-state="closed"] {
display: none;
}
#omnivore-toast-container .omnivore-toast-func-status {
display: flex;
align-items: flex-start;
justify-content: center;
width: 100%;
color:#898989;
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 20px;
text-align: center;
}
#omnivore-toast-container #omnivore-edit-labels-status {
height: 22px;
padding-top: 10px;
}
#omnivore-toast-button-row {
gap: 5px;
align-items: center;
padding-top: 7px;
padding-bottom: 7px;
display: flex;
width: 100%;
padding-left: 15px;
padding-right: 10px;
}
#omnivore-toast-container button {
display: flex;
align-items: center;
gap: 8px;
color: #898989;
border: none;
background-color: transparent;
cursor: pointer;
border-radius: 5px;
height: 30px;
margin-left: auto;
}
#omnivore-toast-container button:hover {
color: #3B3A38;
background-color: #EBEBEB;
}
#omnivore-toast-container .omnivore-save-button button {
background-color: rgb(255, 210, 52)
}
#omnivore-toast-container #omnivore-toast-close-btn:hover {
background-color: unset;
}
#omnivore-toast-container #omnivore-toast-close-btn svg {
fill: #3D3D3D;
}
#omnivore-toast-container #omnivore-toast-close-btn:hover > svg *,
#omnivore-toast-container #omnivore-toast-close-btn svg:hover {
fill: #D9D9D9;
stroke: white;
}
#omnivore-toast-container svg {
fill: #898989;
}
#omnivore-toast-container > svg *,
#omnivore-toast-container svg:hover {
fill: #3B3A38;
}
#omnivore-toast-container #omnivore-toast-edit-title-btn > svg * {
fill: none;
}
#omnivore-toast-container #omnivore-toast-delete-btn > svg * {
fill: none;
}
#omnivore-toast-container form {
display: block;
margin: 0px;
}
.omnivore-toast-divider {
height:20px;
border-right: 1px solid #D9D9D9;
}
.omnivore-toast-statusBox {
display: flex;
width: 20px;
margin-left: 0px;
margin-right: 7px;
}
#omnivore-edit-title-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-edit-title-row textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-add-note-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-add-note-row textarea {
width: 100%;
height: 100px;
padding: 5px;
resize: none;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-toast-container .omnivore-toast-func-row button {
height: 30px;
padding: 15px;
margin-left: auto;
background-color: #FFEA9F;
}
#omnivore-edit-labels-row {
flex-direction: column;
visibility: unset;
padding-top: 20px;
height: 100%;
gap: 10px;
}
#omnivore-edit-labels-row input {
width: 100%;
height: 34px;
padding: 10px;
border: 1px solid #8E8E93;
border-radius: 4px;
box-sizing: border-box;
background-color: transparent;
}
#omnivore-extra-buttons-row {
flex-direction: column;
padding-top: 5px;
padding-bottom: 10px;
gap: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button {
align-self: flex-start;
padding: 10px;
margin: 0px;
background-color: transparent;
border-radius: 5px;
}
#omnivore-toast-container #omnivore-extra-buttons-row button:hover {
background-color: #EBEBEB;
}
#omnivore-toast-container #omnivore-edit-labels-list button:hover {
background-color: #EBEBEB;
}
#omnivore-edit-labels-list {
overflow-y: scroll;
max-height: 200px;
gap: 5px;
color: #3B3A38;
margin-top: 0px;
margin-bottom: 10px;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
height: 35px;
display: flex;
align-items: center;
padding-left: 10px;
padding-right: 10px;
border-bottom: 1px solid #EEEEEE;
border-radius: 0px;
width: 100%;
background: unset;
}
#omnivore-toast-container #omnivore-edit-labels-list button[data-label-selected="on"] .checkbox {
visibility: visible;
}
#omnivore-toast-container #omnivore-edit-labels-list button[data-label-selected="off"] .checkbox {
visibility: hidden;
}
#omnivore-toast-container #omnivore-edit-labels-list button:focus-visible {
outline: unset;
border-radius: 5px;
background-color: #EBEBEB;
}
#omnivore-edit-labels-list div:hover {
background-color: #EBEBEB;
}
#omnivore-edit-labels-row form {
margin-bottom: unset;
}
#omnivore-add-note-row textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
#omnivore-edit-labels-row input, select, textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
#omnivore-edit-title-row textarea:focus-visible {
border-color: #2f81f7;
outline: none;
}
.omnivore-toast-menu-divider {
border-bottom: 1px solid #D9D9D9;
width: 80%;
}
#omnivore-toast-login-btn {
color: #898989;
text-decoration: underline;
}
@media (prefers-color-scheme: dark) {
#omnivore-toast-container {
background: #333333;
}
#omnivore-toast-container input, select, textarea{
color: #D9D9D9;
}
.omnivore-toast-divider {
border-right: 1px solid #898989;
}
#omnivore-toast-container svg {
fill: #898989;
}
.omnivore-save-button button {
margin-top: 10px;
color: #333333;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
color: #898989;
}
#omnivore-toast-container button:hover > svg *,
#omnivore-toast-container button:hover {
color: #D9D9D9;
fill: #D9D9D9;
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-edit-labels-list button:hover {
color: #D9D9D9;
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-extra-buttons-row button:hover {
background-color: #2A2A2A;
}
#omnivore-toast-container #omnivore-edit-labels-list button {
border-bottom: 1px solid #898989;
}
#omnivore-toast-container #omnivore-toast-edit-title-btn > svg * {
fill: none;
}
#omnivore-toast-container #omnivore-toast-delete-btn > svg * {
fill: none;
}
.omnivore-toast-menu-divider {
border-bottom: 1px solid #898989;
}
#omnivore-toast-login-btn {
color: #898989;
}
}
</style>
<style>
.label-editor {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: inline-block;
background-color: var(--colors-thBackground2);
border: 1px solid transparent;
border-radius: 6px;
padding: 5px;
line-height: 2;
cursor: text;
font-size: 12px;
width: calc(100% - 10px);
min-height: 28px;
}
.label-list {
flex-wrap: wrap;
justify-content: start;
display: flex;
width: 100%;
align-items: center;
padding: 0px;
box-sizing: border-box;
height: auto;
flex: 0 0 auto;
overflow-y: auto;
margin: 0;
list-style: none;
gap: 5px;
}
#omnivore-toast-container .omnivore-toast-func-row .label-remove-button {
display: flex;
align-items: center;
justify-content: center;
color: transparent;
border: none;
background-color: transparent;
cursor: pointer;
padding: 0px;
}
#omnivore-edit-labels-row .label-input {
box-sizing: content-box;
font-size: 16px;
min-width: 2px;
border: none;
outline: none;
padding: 0px;
height: 28px;
}
.label {
display: inline-table;
padding: 1px;
padding-left: 7px;
padding-right: 7px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 5px;
border: 1px solid rgb(222, 222, 222);
background-color: rgb(249, 249, 249);
}
#omnivore-toast-container .omnivore-toast-func-row .label button {
height: unset;
}
.label[data-label-backspaced="on"] {
border: 1px solid rgb(255, 234, 159);
}
.label[data-item-highlighted="on"] {
border: 1px solid black;
transition: border-color 0.25s linear;
}
.loading-spinner {
/* control spinner size with setting font-size */
font-size: 3rem;
border: 2px solid #FFD234;
border-top-color: transparent;
border-radius: 50%;
width: 18px;
height: 18px;
animation: loading-spinner 1.5s linear infinite;
margin: 0 auto;
box-sizing: border-box;
}
@keyframes loading-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<div id="omnivore-toast-container">
<div id="omnivore-toast-button-row">
<span class="omnivore-toast-statusBox">
<div class="loading-spinner"></div>
</span>
<span class="omnivore-toast-divider"></span>
<button class="omnivore-top-button" id="omnivore-toast-add-note-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 256 256">
<path d="M88,96a8,8,0,0,1,8-8h64a8,8,0,0,1,0,16H96A8,8,0,0,1,88,96Zm8,40h64a8,8,0,0,0,0-16H96a8,8,0,0,0,0,16Zm32,16H96a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16ZM224,48V156.69A15.86,15.86,0,0,1,219.31,168L168,219.31A15.86,15.86,0,0,1,156.69,224H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H152V160a8,8,0,0,1,8-8h48V48H48Zm120-40v28.7L196.69,168Z"></path>
</svg>
<span class="omnivore-top-button-label">Add Note</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-edit-labels-btn">
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8.99031 14.9786L15.3061 8.67029C15.3757 8.6002 15.4307 8.51707 15.468 8.42568C15.5053 8.33429 15.5242 8.23643 15.5237 8.13772L15.5237 1.38683C15.5237 1.18789 15.4446 0.997101 15.304 0.85643C15.1633 0.715759 14.9725 0.636731 14.7736 0.636731L8.02269 0.636731C7.92397 0.63616 7.82611 0.655082 7.73472 0.69241C7.64333 0.729738 7.5602 0.784739 7.49012 0.85426L1.18179 7.17009C0.76038 7.59202 0.523681 8.16397 0.523681 8.7603C0.523681 9.35663 0.76038 9.92857 1.18179 10.3505L5.77239 14.9786C6.19432 15.4 6.76627 15.6367 7.3626 15.6367C7.95893 15.6367 8.53087 15.4 8.95281 14.9786L8.99031 14.9786ZM6.87503 13.921L2.24693 9.28536C2.10722 9.14482 2.0288 8.95471 2.0288 8.75655C2.0288 8.55838 2.10722 8.36827 2.24693 8.22773L8.33022 2.13693L14.0235 2.13693L14.0235 7.83018L7.93267 13.921C7.86258 13.9905 7.77946 14.0455 7.68807 14.0828C7.59668 14.1202 7.49882 14.1391 7.4001 14.1385C7.20332 14.1377 7.01475 14.0595 6.87503 13.921Z" />
<circle cx="10.8818" cy="5.48069" r="1.24925" />
</svg>
<span class="omnivore-top-button-label">Set Labels</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-read-now-btn">
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.1272 0.939454H12.6584C11.6995 0.939454 10.762 1.21484 9.95532 1.73438L9.0022 2.3457L8.04907 1.73438C7.24323 1.21494 6.30469 0.938941 5.34595 0.939454H0.877197C0.531494 0.939454 0.252197 1.21875 0.252197 1.56445V12.6582C0.252197 13.0039 0.531494 13.2832 0.877197 13.2832H5.34595C6.30493 13.2832 7.24243 13.5586 8.04907 14.0781L8.91626 14.6367C8.94165 14.6523 8.97095 14.6621 9.00024 14.6621C9.02954 14.6621 9.05884 14.6543 9.08423 14.6367L9.95142 14.0781C10.76 13.5586 11.6995 13.2832 12.6584 13.2832H17.1272C17.4729 13.2832 17.7522 13.0039 17.7522 12.6582V1.56445C17.7522 1.21875 17.4729 0.939454 17.1272 0.939454ZM5.34595 11.877H1.65845V2.3457H5.34595C6.03735 2.3457 6.70923 2.54297 7.28931 2.91602L8.24243 3.52734L8.3772 3.61523V12.6387C7.44751 12.1387 6.40845 11.877 5.34595 11.877ZM16.3459 11.877H12.6584C11.5959 11.877 10.5569 12.1387 9.6272 12.6387V3.61523L9.76196 3.52734L10.7151 2.91602C11.2952 2.54297 11.967 2.3457 12.6584 2.3457H16.3459V11.877ZM6.75415 4.8457H3.12524C3.04907 4.8457 2.98657 4.91211 2.98657 4.99219V5.87109C2.98657 5.95117 3.04907 6.01758 3.12524 6.01758H6.7522C6.82837 6.01758 6.89087 5.95117 6.89087 5.87109V4.99219C6.89282 4.91211 6.83032 4.8457 6.75415 4.8457ZM11.1116 4.99219V5.87109C11.1116 5.95117 11.1741 6.01758 11.2502 6.01758H14.8772C14.9534 6.01758 15.0159 5.95117 15.0159 5.87109V4.99219C15.0159 4.91211 14.9534 4.8457 14.8772 4.8457H11.2502C11.1741 4.8457 11.1116 4.91211 11.1116 4.99219ZM6.75415 7.58008H3.12524C3.04907 7.58008 2.98657 7.64648 2.98657 7.72656V8.60547C2.98657 8.68555 3.04907 8.75195 3.12524 8.75195H6.7522C6.82837 8.75195 6.89087 8.68555 6.89087 8.60547V7.72656C6.89282 7.64648 6.83032 7.58008 6.75415 7.58008ZM14.8792 7.58008H11.2502C11.1741 7.58008 11.1116 7.64648 11.1116 7.72656V8.60547C11.1116 8.68555 11.1741 8.75195 11.2502 8.75195H14.8772C14.9534 8.75195 15.0159 8.68555 15.0159 8.60547V7.72656C15.0178 7.64648 14.9553 7.58008 14.8792 7.58008Z" />
</svg>
<span class="omnivore-top-button-label">Read Now</span>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-open-menu-btn">
<svg width="15" height="4" viewBox="0 0 15 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="1.48679" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="7.00217" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="7.00217" cy="1.79492" rx="1.4846" ry="1.5" />
<ellipse cx="12.5176" cy="1.79492" rx="1.4846" ry="1.5" />
</svg>
</button>
<span class="omnivore-toast-divider"></span>
<button id="omnivore-toast-close-btn">
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="9.50049" cy="9.52783" r="9" />
<path d="M12.554 11.874L12.554 11.874L10.2075 9.52783L12.554 7.18165L12.554 7.18164C12.6478 7.08785 12.7005 6.96063 12.7005 6.82798C12.7005 6.69533 12.6478 6.56812 12.554 6.47432C12.4602 6.38053 12.333 6.32783 12.2003 6.32783C12.0677 6.32783 11.9405 6.38053 11.8467 6.47432L11.8467 6.47433L9.50049 8.82087L7.15431 6.47433L7.1543 6.47432C7.0605 6.38053 6.93329 6.32783 6.80064 6.32783C6.66799 6.32783 6.54078 6.38053 6.44698 6.47432C6.35318 6.56812 6.30049 6.69533 6.30049 6.82798C6.30049 6.96063 6.35318 7.08785 6.44698 7.18164L6.44699 7.18165L8.79352 9.52783L6.44699 11.874L6.44698 11.874C6.35318 11.9678 6.30049 12.095 6.30049 12.2277C6.30049 12.3603 6.35318 12.4875 6.44698 12.5813C6.54078 12.6751 6.66799 12.7278 6.80064 12.7278C6.93329 12.7278 7.0605 12.6751 7.1543 12.5813L7.15431 12.5813L9.50049 10.2348L11.8467 12.5813L11.8467 12.5813C11.8931 12.6278 11.9483 12.6646 12.0089 12.6898C12.0696 12.7149 12.1347 12.7278 12.2003 12.7278C12.266 12.7278 12.3311 12.7149 12.3917 12.6898C12.4524 12.6646 12.5076 12.6278 12.554 12.5813C12.6004 12.5349 12.6373 12.4798 12.6624 12.4191C12.6876 12.3584 12.7005 12.2934 12.7005 12.2277C12.7005 12.162 12.6876 12.097 12.6624 12.0363C12.6373 11.9756 12.6004 11.9205 12.554 11.874Z" fill="#EBEBEB" stroke="#EBEBEB" stroke-width="0.4"/>
</svg>
</button>
</div>
<div id="omnivore-add-note-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-add-note-status" class="omnivore-toast-func-status"></span>
<form id="omnivore-add-note-form">
<textarea id="omnivore-add-note-textarea" name="title"></textarea>
<button class="omnivore-save-button">Save</button>
</form>
</div>
<div id="omnivore-edit-title-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-edit-title-status" class="omnivore-toast-func-status"></span>
<form id="omnivore-edit-title-form">
<textarea id="omnivore-edit-title-textarea" name="title"></textarea>
<button class="omnivore-save-button">Save</button>
</form>
</div>
<div id="omnivore-edit-labels-row" class="omnivore-toast-func-row" data-state="closed">
<form id="omnivore-edit-labels-form">
<span id="omnivore-edit-labels-status" class="omnivore-toast-func-status"></span>
<div id="omnivore-edit-labels-list"></div>
</form>
</div>
<div id="omnivore-extra-buttons-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-extra-status" class="omnivore-toast-func-status"></span>
<button id="omnivore-toast-edit-title-btn">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5625 4.50004L13.5 8.43754M6.71406 15.1516L2.84828 11.2858M6.517 15.1875H3.375C3.22582 15.1875 3.08274 15.1283 2.97725 15.0228C2.87176 14.9173 2.8125 14.7742 2.8125 14.625V11.483C2.8125 11.4092 2.82705 11.336 2.85532 11.2678C2.88359 11.1995 2.92502 11.1375 2.97725 11.0853L11.4148 2.64778C11.5202 2.5423 11.6633 2.48303 11.8125 2.48303C11.9617 2.48303 12.1048 2.5423 12.2102 2.64778L15.3523 5.78979C15.4577 5.89528 15.517 6.03835 15.517 6.18754C15.517 6.33672 15.4577 6.4798 15.3523 6.58528L6.91475 15.0228C6.86252 15.075 6.80051 15.1165 6.73226 15.1447C6.66402 15.173 6.59087 15.1875 6.517 15.1875Z" stroke="#6A6968" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Edit Title
</button>
<span class="omnivore-toast-menu-divider"></span>
<button id="omnivore-toast-archive-btn">
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.0143 0.166748H1.93155C1.157 0.166748 0.523193 0.800512 0.523193 1.5751V3.92222C0.523193 4.6264 1.03956 5.18958 1.69675 5.30698V14.0148C1.69675 14.7893 2.33052 15.4231 3.10511 15.4231H14.8407C15.6152 15.4231 16.249 14.7894 16.249 14.0148V5.30698C16.9062 5.18959 17.4226 4.6264 17.4226 3.92222V1.5751C17.4226 0.800554 16.7888 0.166748 16.0143 0.166748ZM1.93155 1.5751H16.0143V3.92222H1.93155V1.5751ZM14.8407 14.0148H3.10511V5.33049H14.8407V14.0148Z" fill="#6A6968"/>
<path d="M7.82307 8.26431H10.1702C10.5692 8.26431 10.8744 7.95914 10.8744 7.56013C10.8744 7.16114 10.5692 6.85596 10.1702 6.85596H7.82307C7.42408 6.85596 7.1189 7.16113 7.1189 7.56013C7.1189 7.95912 7.44748 8.26431 7.82307 8.26431Z" fill="#6A6968"/>
</svg>
Archive
</button>
<span class="omnivore-toast-menu-divider"></span>
<button id="omnivore-toast-delete-btn">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6602 5.16992L3.51147 5.16993" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.29272 8.91992V13.9199" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.8787 8.91992V13.9199" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.09741 2.66992H13.0741" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.4648 5.16993V17.0449C15.4648 17.2107 15.4018 17.3697 15.2897 17.4869C15.1777 17.6041 15.0256 17.6699 14.8671 17.6699H5.30445C5.14594 17.6699 4.99392 17.6041 4.88184 17.4869C4.76976 17.3697 4.70679 17.2107 4.70679 17.0449V5.16992" stroke="#6A6968" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Delete
</button>
</div>
<div id="omnivore-logged-out-row" class="omnivore-toast-func-row" data-state="closed">
<span id="omnivore-logged-out-status" class="omnivore-toast-func-status"></span>
<a href="" id="omnivore-toast-login-btn">Login to Omnivore</a>
</div>
</div>

12
pkg/extension-v3/src/window.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
export {}
declare type AndroidWebKitMessenger = {
// 1st argument is an actionID value, 2nd is jsonString
handleIdentifiableMessage: (string, string) => void
}
declare global {
interface Window {
showToolbar?: (payload: { type: string }) => void
}
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"outDir": "./dist",
"module": "esnext",
"target": "es6",
"lib": ["dom", "es6"],
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}

View File

@ -0,0 +1,39 @@
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
entry: {
background: './src/background.ts',
toolbar: './src/scripts/content/toolbar.ts',
content: './src/scripts/content/content.ts',
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'extension'),
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{ from: 'src/manifest.json', to: 'manifest.json' },
{ from: 'src/icons', to: 'icons' },
{ from: 'src/images', to: 'images' },
{ from: 'src/views', to: 'views' },
],
}),
],
}

1467
pkg/extension-v3/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -95,4 +95,4 @@
"web_accessible_resources": [ "web_accessible_resources": [
"views/toast.html" "views/toast.html"
] ]
} }

View File

@ -3,6 +3,7 @@
- [Docker Compose](#docker-compose) - [Docker Compose](#docker-compose)
- [Nginx Reverse Proxy](#nginx-reverse-proxy) - [Nginx Reverse Proxy](#nginx-reverse-proxy)
- [Cloudflare Tunnel](#cloudflare-tunnel) - [Cloudflare Tunnel](#cloudflare-tunnel)
- [Web Extensions](#web-extensions)
- [Email](#email) - [Email](#email)
- - [IMap Watcher](#imap-watcher) - - [IMap Watcher](#imap-watcher)
- - [Self Hosted Mail Server](#docker-mailserver-and-mail-watcher) - - [Self Hosted Mail Server](#docker-mailserver-and-mail-watcher)
@ -136,6 +137,40 @@ Omnivore is no way affiliated with Cloudflare, it is just the method to which th
[Read More](https://www.cloudflare.com/products/tunnel/) [Read More](https://www.cloudflare.com/products/tunnel/)
## Web Extensions
The web extensions have been updated to support self-hosting - The manifest version 2 of these could be enabled to work with Self-hosting, but required some manual code changes.
The extension has been updated for Manifest v3, and to hopefully ease the difficulty of making it work with Self-Hosted versions.
These extensions have been sent for submission to the Chrome and Firefox webstore, but are awaiting approval. In the mean-time, a guide about how to install these manually is provided.
### Chrome
1. Navigate to the Chrome extension page at Chrome://extensions
![Extensions PAge](../docs/guides/images/1-extension-page.png)
2. Enable Developer mode on the Extensions page using the toggle.
![Extensions Developer](../docs/guides/images/2-developer-mode.png)
3. Use the Load Unpacked Option to load the extension from source.
![Extension Unpacked](../docs/guides/images/3-load-unpacked.png)
4. Navigate to the source folder, found at pkg/extension-v3/extension
![Source](../docs/guides/images/4-folder.png)
![Source](../docs/guides/images/5-folders.png)
5. The extension should have been installed. Go to the details page on the newly installed extension
![Installed](../docs/guides/images/6-installed.png)
6. Navigate to the options page, using the "Extensions Options" button. You should see the following page.
![Extension Options](../docs/guides/images/7-options.png)
![Extension Options](../docs/guides/images/8-options-page.png)
7. Generate an API Key using Omnivore.
![API Options](../docs/guides/images/9-omnivore-settings.png)
![API Options](../docs/guides/images/10-omnivore-api-keys.png)
![API Options](../docs/guides/images/11-generate-key.png)
![API Options](../docs/guides/images/12-copy-key.png)
8. Update the settings with the hosted options
![API Options](../docs/guides/images/13-update-settings.png)
## Emails and Newsletters ## Emails and Newsletters