Initial commit
13
LICENSE.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Original code:
|
||||
- Copyright (c) 2021 Sascha Ißbrücker
|
||||
|
||||
Adaptions and injector functionality:
|
||||
- Copyright (c) 2022 Jakob Essbüchl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
68
README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
![logo](/icons/logo_full.svg)
|
||||
|
||||
Community browser extension for the self-hosted [linkding](https://github.com/sissbruecker/linkding) bookmark service.
|
||||
|
||||
**Features**
|
||||
- When searching on a search engine the search term is also sent to your linkding instance and results are added in a new box in the sidebar right to the search engine results.
|
||||
- Supports [google](https://www.google.com/) and [duckduckgo](https://duckduckgo.com/) search engines
|
||||
- Automatic light or dark theme detection
|
||||
|
||||
Works with: Firefox, Chrome
|
||||
|
||||
**Usage**
|
||||
|
||||
After installation the extension needs to be configured and connected to your linkding instance. Either open the extension options in the browser extension manager or follow the link in the new linkding injector box on the search page of google or duckduckgo.
|
||||
|
||||
Once the extension is properly configured linkding search results will show in the right sidebar. If there are no search results nothing will appear.
|
||||
|
||||
**Screenshots**
|
||||
|
||||
![duckduckgo](/docs/duckduckgo.png "Duckduckgo")
|
||||
![google](/docs/google.png "google")
|
||||
|
||||
## Installation
|
||||
|
||||
Firefox: [Mozilla Addon Store](https://addons.mozilla.org/de/firefox/addon/linkding-extension/)
|
||||
|
||||
Chrome: [Chrome Web Store](https://chrome.google.com/webstore/detail/linkding-extension/beakmhbijpdhipnjhnclmhgjlddhidpe)
|
||||
|
||||
## Manual installation
|
||||
|
||||
### Firefox
|
||||
|
||||
Run the build as described below and then follow the instructions [here](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#installing) to load it into Firefox.
|
||||
|
||||
### Chrome
|
||||
|
||||
Run the build as described below and then follow the instructions [here](https://developer.chrome.com/docs/extensions/mv3/getstarted/#manifest) to load it into Chrome.
|
||||
|
||||
## Build
|
||||
|
||||
**Requirements**
|
||||
- Latest LTS Node version (v14)
|
||||
- Latest LTS NPM version (v6)
|
||||
- bash (on Linux) or powershell (on Windows)
|
||||
- npx (included with npm v5.2+)
|
||||
|
||||
Internally, we use `web-ext` to bundle a distribution package for the extension for Firefox. You do not need to install `web-ext`. Note that `web-ext` will generate a zip file which can also be used for the Chrome Web Store.
|
||||
|
||||
Then run the following script to generate a build (might need to make the file executable on Linux using `chmod +x build.sh`):
|
||||
```bash
|
||||
./build.sh # Linux
|
||||
```
|
||||
```powershell
|
||||
./build.ps1 # Windows
|
||||
```
|
||||
|
||||
The script does:
|
||||
- Install all dependencies using NPM
|
||||
- Runs rollup to transpile and minify source files, with output written to the `build` directory
|
||||
- Run web-ext to package the extension for uploading to the Mozilla addon store
|
||||
|
||||
After the build the root directory contains the complete, unpackaged extension. Use the `manifest.json` file to load it manually into the browser.
|
||||
|
||||
The packaged extension can be found in the `web-ext-artifacts` folder.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This extension resuses and adapts code from the [official linkding extension](https://github.com/sissbruecker/linkding-extension).
|
12
build.ps1
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Update dependencies
|
||||
npm install
|
||||
|
||||
# Run rollup build
|
||||
npm run build
|
||||
|
||||
# Lint extension, while excluding dev files
|
||||
npx web-ext lint --ignore-files .idea dist docs src web-ext-artifacts .gitignore *.sh *.iml *.js *.lock
|
||||
# Build extension, while excluding dev files
|
||||
npx web-ext build --overwrite-dest --ignore-files .idea dist docs src web-ext-artifacts .gitignore *.sh *.iml *.js *.lock
|
||||
|
||||
echo "✅ Done"
|
14
build.sh
Normal file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Update dependencies
|
||||
npm install
|
||||
|
||||
# Run rollup build
|
||||
npm run build
|
||||
|
||||
# Lint extension, while excluding dev files
|
||||
npx web-ext lint --ignore-files .idea dist docs src web-ext-artifacts .gitignore *.sh *.iml *.js *.lock
|
||||
# Build extension, while excluding dev files
|
||||
npx web-ext build --overwrite-dest --ignore-files .idea dist docs src web-ext-artifacts .gitignore *.sh *.iml *.js *.lock
|
||||
|
||||
echo "✅ Done"
|
BIN
docs/duckduckgo.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
docs/google.png
Normal file
After Width: | Height: | Size: 240 KiB |
1
icons/cog.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24" style="fill: #a8b1ff"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/ld_128.png
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
icons/ld_19.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/ld_32.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/ld_38.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/ld_48.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
icons/ld_96.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
25
icons/logo.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 57 57" style="enable-background:new 0 0 57 57;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#5856E0;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:2.3769;stroke-miterlimit:10;}
|
||||
.st3{fill:#5B5EA9;stroke:#5856E0;stroke-width:2;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_1">
|
||||
<circle class="st0" cx="28.5" cy="28.5" r="28.5"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<polygon class="st1" points="33.2,14.2 18,29.3 18,39 27.7,39 42.8,23.8 "/>
|
||||
<line class="st2" x1="25.7" y1="31.3" x2="11.3" y2="45.7"/>
|
||||
<line class="st2" x1="33.7" y1="10.1" x2="47.3" y2="23.7"/>
|
||||
<line class="st2" x1="40.5" y1="16.9" x2="45.3" y2="12"/>
|
||||
<line class="st2" x1="41.6" y1="8.3" x2="49" y2="15.7"/>
|
||||
<line class="st3" x1="20.6" y1="24.8" x2="25.2" y2="29.3"/>
|
||||
<line class="st3" x1="26.2" y1="19.1" x2="30.8" y2="23.7"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/logo_128.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/logo_19.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
icons/logo_32.png
Normal file
After Width: | Height: | Size: 850 B |
BIN
icons/logo_38.png
Normal file
After Width: | Height: | Size: 949 B |
BIN
icons/logo_48.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/logo_96.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
70
icons/logo_full.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 199 57" style="enable-background:new 0 0 199 57;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#5856E0;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:none;stroke:#FFFFFF;stroke-width:2.3769;stroke-miterlimit:10;}
|
||||
.st3{fill:none;stroke:#5856E0;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st4{fill:none;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="Layer_1_1_">
|
||||
<circle class="st0" cx="28.5" cy="28.5" r="28.5"/>
|
||||
</g>
|
||||
<g id="Layer_2_1_">
|
||||
<g>
|
||||
<polygon class="st1" points="33.2,14.2 18,29.3 18,39 27.7,39 42.8,23.8 "/>
|
||||
<line class="st2" x1="25.7" y1="31.3" x2="11.3" y2="45.7"/>
|
||||
<line class="st2" x1="33.7" y1="10.1" x2="47.3" y2="23.7"/>
|
||||
<line class="st2" x1="40.5" y1="16.9" x2="45.3" y2="12"/>
|
||||
<line class="st2" x1="41.6" y1="8.3" x2="49" y2="15.7"/>
|
||||
<line class="st3" x1="20.6" y1="24.8" x2="25.2" y2="29.3"/>
|
||||
<line class="st3" x1="26.2" y1="19.1" x2="30.8" y2="23.7"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="65.1" y="4.7" class="st4" width="172.5" height="47.6"/>
|
||||
<path class="st0" d="M67.4,25.2V5.3h3.1v17.1h10.7v2.8H67.4z"/>
|
||||
<path class="st0" d="M83.7,25.2V5.3h3.1v19.9H83.7z"/>
|
||||
<path class="st0" d="M94.5,11.2v14h-3.1V5.3h2.5l11.4,14.3V5.3h3.2v19.9h-2.6L94.5,11.2z"/>
|
||||
<path class="st0" d="M112.8,25.2V5.3h3.1v10.2l9.4-10.2h3.5l-7.8,8.8l8.3,11.1h-3.5L119,16l-3.1,3.2v6H112.8z"/>
|
||||
<path class="st0" d="M131.3,25.2V5.3h7.1c1.6,0,3,0.3,4.2,0.8c1.2,0.5,2.2,1.2,3,2.1c0.8,0.9,1.4,1.9,1.8,3.1
|
||||
c0.4,1.2,0.6,2.5,0.6,3.9c0,1.5-0.2,2.9-0.7,4.1c-0.5,1.2-1.1,2.3-1.9,3.1c-0.8,0.9-1.9,1.5-3.1,2s-2.5,0.7-4,0.7H131.3z
|
||||
M144.8,15.2c0-1-0.1-2-0.4-2.9c-0.3-0.9-0.7-1.6-1.3-2.3c-0.6-0.6-1.2-1.1-2-1.5c-0.8-0.4-1.7-0.5-2.7-0.5h-3.9v14.3h3.9
|
||||
c1,0,2-0.2,2.8-0.5c0.8-0.4,1.5-0.9,2-1.5s1-1.4,1.2-2.3S144.8,16.2,144.8,15.2z"/>
|
||||
<path class="st0" d="M151.3,25.2V5.3h3.1v19.9H151.3z"/>
|
||||
<path class="st0" d="M162.1,11.2v14H159V5.3h2.5l11.4,14.3V5.3h3.2v19.9h-2.6L162.1,11.2z"/>
|
||||
<path class="st0" d="M194.4,22.7c-1.7,1.7-3.6,2.6-5.8,2.6c-1.3,0-2.6-0.3-3.7-0.8c-1.1-0.6-2.1-1.3-3-2.2c-0.8-0.9-1.5-2-2-3.2
|
||||
c-0.5-1.2-0.7-2.5-0.7-3.9c0-1.3,0.2-2.6,0.7-3.8c0.5-1.2,1.1-2.3,2-3.2s1.8-1.6,3-2.2c1.2-0.5,2.4-0.8,3.8-0.8
|
||||
c1.9,0,3.5,0.4,4.8,1.2c1.3,0.8,2.3,1.8,2.9,3.1l-2.4,1.7c-0.5-1.1-1.3-1.9-2.2-2.4c-1-0.5-2-0.8-3.1-0.8c-0.9,0-1.8,0.2-2.5,0.6
|
||||
s-1.4,0.9-2,1.6c-0.5,0.7-1,1.4-1.2,2.3c-0.3,0.9-0.4,1.8-0.4,2.7c0,1,0.2,2,0.5,2.8c0.3,0.9,0.8,1.6,1.3,2.3s1.2,1.2,2,1.6
|
||||
c0.8,0.4,1.6,0.6,2.5,0.6c1,0,2-0.2,2.9-0.7c0.9-0.5,1.8-1.2,2.6-2.2v-2.1h-4.2v-2.3h6.8v10.1h-2.6V22.7z"/>
|
||||
<path class="st0" d="M67.4,51.2V31.3h3.1v19.9H67.4z"/>
|
||||
<path class="st0" d="M78.1,37.2v14H75V31.3h2.5l11.4,14.3V31.3H92v19.9h-2.6L78.1,37.2z"/>
|
||||
<path class="st0" d="M95.3,47.7c0.3,0.2,0.8,0.4,1.4,0.7c0.6,0.2,1.3,0.3,2.1,0.3c0.9,0,1.5-0.2,2.1-0.5c0.5-0.3,0.9-0.8,1.2-1.4
|
||||
s0.4-1.4,0.5-2.3s0.1-2,0.1-3.2V31.3h3.2v10.1c0,1.5-0.1,2.9-0.2,4.1s-0.5,2.3-1,3.2c-0.5,0.9-1.2,1.6-2.1,2s-2.2,0.7-3.8,0.7
|
||||
c-1.7,0-3.1-0.4-4.3-1.2L95.3,47.7z"/>
|
||||
<path class="st0" d="M123.8,48.4v2.8h-13.6V31.3h13.4v2.8h-10.2v5.7h8.9v2.6h-8.9v6.1H123.8z"/>
|
||||
<path class="st0" d="M125.3,41.1c0-1.2,0.2-2.4,0.6-3.6c0.4-1.2,1.1-2.2,1.9-3.2c0.8-0.9,1.8-1.7,3-2.3c1.2-0.6,2.5-0.9,4.1-0.9
|
||||
c1.8,0,3.4,0.4,4.6,1.2c1.3,0.8,2.3,1.8,2.9,3.1l-2.5,1.7c-0.3-0.6-0.6-1.1-1-1.5c-0.4-0.4-0.8-0.7-1.3-1c-0.5-0.2-1-0.4-1.5-0.5
|
||||
s-1-0.2-1.5-0.2c-1,0-2,0.2-2.7,0.6s-1.4,1-2,1.7c-0.5,0.7-0.9,1.5-1.2,2.3s-0.4,1.7-0.4,2.6c0,1,0.2,1.9,0.5,2.8
|
||||
c0.3,0.9,0.8,1.7,1.3,2.3s1.2,1.2,2,1.6s1.6,0.6,2.6,0.6c0.5,0,1-0.1,1.5-0.2c0.5-0.1,1-0.3,1.5-0.6c0.5-0.3,0.9-0.6,1.3-1
|
||||
c0.4-0.4,0.7-0.9,1-1.5l2.6,1.5c-0.3,0.7-0.8,1.4-1.3,2c-0.6,0.6-1.2,1-2,1.4c-0.7,0.4-1.5,0.7-2.3,0.9c-0.8,0.2-1.6,0.3-2.4,0.3
|
||||
c-1.4,0-2.7-0.3-3.8-0.9c-1.2-0.6-2.2-1.4-3-2.3c-0.8-1-1.5-2.1-2-3.3S125.3,42.4,125.3,41.1z"/>
|
||||
<path class="st0" d="M160.1,34.1h-6.6v17.1h-3.2V34.1h-6.6v-2.8h16.4V34.1z"/>
|
||||
<path class="st0" d="M170.1,51.3c-1.4,0-2.7-0.3-3.9-0.9c-1.2-0.6-2.2-1.3-3-2.3c-0.8-0.9-1.5-2-1.9-3.2c-0.5-1.2-0.7-2.5-0.7-3.7
|
||||
c0-1.3,0.2-2.6,0.7-3.8c0.5-1.2,1.2-2.3,2-3.2s1.9-1.7,3-2.2c1.2-0.5,2.4-0.8,3.8-0.8c1.4,0,2.7,0.3,3.9,0.9c1.2,0.6,2.2,1.4,3,2.3
|
||||
c0.8,1,1.5,2,1.9,3.2c0.5,1.2,0.7,2.4,0.7,3.7c0,1.3-0.2,2.6-0.7,3.8c-0.5,1.2-1.1,2.3-2,3.2c-0.9,0.9-1.9,1.7-3,2.2
|
||||
C172.8,51,171.5,51.3,170.1,51.3z M163.8,41.2c0,0.9,0.1,1.8,0.4,2.7c0.3,0.9,0.7,1.7,1.3,2.3c0.6,0.7,1.2,1.2,2,1.6
|
||||
c0.8,0.4,1.7,0.6,2.6,0.6c1,0,1.9-0.2,2.7-0.6c0.8-0.4,1.4-1,2-1.7c0.5-0.7,1-1.5,1.2-2.3s0.4-1.7,0.4-2.6c0-1-0.1-1.9-0.4-2.7
|
||||
s-0.7-1.6-1.3-2.3c-0.6-0.7-1.2-1.2-2-1.6c-0.8-0.4-1.6-0.6-2.6-0.6c-1,0-1.9,0.2-2.7,0.6s-1.4,1-2,1.6c-0.5,0.7-1,1.4-1.2,2.3
|
||||
C164,39.4,163.8,40.3,163.8,41.2z"/>
|
||||
<path class="st0" d="M182.9,51.2V31.3h8.7c0.9,0,1.7,0.2,2.5,0.6c0.8,0.4,1.4,0.9,1.9,1.5c0.5,0.6,1,1.3,1.3,2.1
|
||||
c0.3,0.8,0.5,1.6,0.5,2.4c0,0.7-0.1,1.3-0.3,1.9c-0.2,0.6-0.4,1.2-0.8,1.7c-0.3,0.5-0.7,1-1.2,1.4c-0.5,0.4-1,0.7-1.6,0.9l4.7,7.6
|
||||
H195l-4.3-6.9h-4.6v6.9H182.9z M186.1,41.5h5.5c0.4,0,0.9-0.1,1.2-0.3c0.4-0.2,0.7-0.5,0.9-0.8c0.3-0.3,0.5-0.7,0.6-1.2
|
||||
c0.1-0.4,0.2-0.9,0.2-1.4s-0.1-1-0.3-1.4s-0.4-0.8-0.7-1.2c-0.3-0.3-0.6-0.6-1-0.8c-0.4-0.2-0.8-0.3-1.2-0.3h-5.3V41.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
25
icons/logo_small.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 57 57" style="enable-background:new 0 0 57 57;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#5856E0;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st3{fill:#5B5EA9;stroke:#5856E0;stroke-width:3;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_1">
|
||||
<circle class="st0" cx="28.5" cy="28.5" r="28.5"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<polygon class="st1" points="31.9,15.5 18,29.3 18,39 27.7,39 41.5,25.1 "/>
|
||||
<line class="st2" x1="25.7" y1="31.3" x2="11.3" y2="45.7"/>
|
||||
<line class="st2" x1="33.7" y1="10.1" x2="47.3" y2="23.7"/>
|
||||
<line class="st2" x1="40.5" y1="16.9" x2="45.3" y2="12"/>
|
||||
<line class="st2" x1="41.6" y1="8.3" x2="49" y2="15.7"/>
|
||||
<line class="st3" x1="20.6" y1="24.8" x2="25.2" y2="29.3"/>
|
||||
<line class="st3" x1="26.2" y1="19.1" x2="30.8" y2="23.7"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
231
manifest.json
Normal file
|
@ -0,0 +1,231 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "linkding injector",
|
||||
"version": "1.0.0",
|
||||
"description": "Injects search results from the linkding bookmark service into search pages like google and duckduckgo",
|
||||
"homepage_url": "https://github.com/sissbruecker/linkding-extension/",
|
||||
|
||||
"icons": {
|
||||
"19": "icons/logo_19.png",
|
||||
"32": "icons/logo_32.png",
|
||||
"48": "icons/logo_48.png",
|
||||
"96": "icons/logo_96.png",
|
||||
"128": "icons/logo_128.png"
|
||||
},
|
||||
|
||||
"background": {
|
||||
"scripts": ["build/background.js"]
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://duckduckgo.com/*"],
|
||||
"css": ["styles/searchInjection.css"],
|
||||
"js": ["build/searchInjection.js"]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://*.google.com/*",
|
||||
"*://*.google.ad/*",
|
||||
"*://*.google.ae/*",
|
||||
"*://*.google.com.af/*",
|
||||
"*://*.google.com.ag/*",
|
||||
"*://*.google.com.ai/*",
|
||||
"*://*.google.al/*",
|
||||
"*://*.google.am/*",
|
||||
"*://*.google.co.ao/*",
|
||||
"*://*.google.com.ar/*",
|
||||
"*://*.google.as/*",
|
||||
"*://*.google.at/*",
|
||||
"*://*.google.com.au/*",
|
||||
"*://*.google.az/*",
|
||||
"*://*.google.ba/*",
|
||||
"*://*.google.com.bd/*",
|
||||
"*://*.google.be/*",
|
||||
"*://*.google.bf/*",
|
||||
"*://*.google.bg/*",
|
||||
"*://*.google.com.bh/*",
|
||||
"*://*.google.bi/*",
|
||||
"*://*.google.bj/*",
|
||||
"*://*.google.com.bn/*",
|
||||
"*://*.google.com.bo/*",
|
||||
"*://*.google.com.br/*",
|
||||
"*://*.google.bs/*",
|
||||
"*://*.google.bt/*",
|
||||
"*://*.google.co.bw/*",
|
||||
"*://*.google.by/*",
|
||||
"*://*.google.com.bz/*",
|
||||
"*://*.google.ca/*",
|
||||
"*://*.google.cd/*",
|
||||
"*://*.google.cf/*",
|
||||
"*://*.google.cg/*",
|
||||
"*://*.google.ch/*",
|
||||
"*://*.google.ci/*",
|
||||
"*://*.google.co.ck/*",
|
||||
"*://*.google.cl/*",
|
||||
"*://*.google.cm/*",
|
||||
"*://*.google.cn/*",
|
||||
"*://*.google.com.co/*",
|
||||
"*://*.google.co.cr/*",
|
||||
"*://*.google.com.cu/*",
|
||||
"*://*.google.cv/*",
|
||||
"*://*.google.com.cy/*",
|
||||
"*://*.google.cz/*",
|
||||
"*://*.google.de/*",
|
||||
"*://*.google.dj/*",
|
||||
"*://*.google.dk/*",
|
||||
"*://*.google.dm/*",
|
||||
"*://*.google.com.do/*",
|
||||
"*://*.google.dz/*",
|
||||
"*://*.google.com.ec/*",
|
||||
"*://*.google.ee/*",
|
||||
"*://*.google.com.eg/*",
|
||||
"*://*.google.es/*",
|
||||
"*://*.google.com.et/*",
|
||||
"*://*.google.fi/*",
|
||||
"*://*.google.com.fj/*",
|
||||
"*://*.google.fm/*",
|
||||
"*://*.google.fr/*",
|
||||
"*://*.google.ga/*",
|
||||
"*://*.google.ge/*",
|
||||
"*://*.google.gg/*",
|
||||
"*://*.google.com.gh/*",
|
||||
"*://*.google.com.gi/*",
|
||||
"*://*.google.gl/*",
|
||||
"*://*.google.gm/*",
|
||||
"*://*.google.gr/*",
|
||||
"*://*.google.com.gt/*",
|
||||
"*://*.google.gy/*",
|
||||
"*://*.google.com.hk/*",
|
||||
"*://*.google.hn/*",
|
||||
"*://*.google.hr/*",
|
||||
"*://*.google.ht/*",
|
||||
"*://*.google.hu/*",
|
||||
"*://*.google.co.id/*",
|
||||
"*://*.google.ie/*",
|
||||
"*://*.google.co.il/*",
|
||||
"*://*.google.im/*",
|
||||
"*://*.google.coIn/*",
|
||||
"*://*.google.iq/*",
|
||||
"*://*.google.is/*",
|
||||
"*://*.google.it/*",
|
||||
"*://*.google.je/*",
|
||||
"*://*.google.com.jm/*",
|
||||
"*://*.google.jo/*",
|
||||
"*://*.google.co.jp/*",
|
||||
"*://*.google.co.ke/*",
|
||||
"*://*.google.com.kh/*",
|
||||
"*://*.google.ki/*",
|
||||
"*://*.google.kg/*",
|
||||
"*://*.google.co.kr/*",
|
||||
"*://*.google.com.kw/*",
|
||||
"*://*.google.kz/*",
|
||||
"*://*.google.la/*",
|
||||
"*://*.google.com.lb/*",
|
||||
"*://*.google.li/*",
|
||||
"*://*.google.lk/*",
|
||||
"*://*.google.co.ls/*",
|
||||
"*://*.google.lt/*",
|
||||
"*://*.google.lu/*",
|
||||
"*://*.google.lv/*",
|
||||
"*://*.google.com.ly/*",
|
||||
"*://*.google.co.ma/*",
|
||||
"*://*.google.md/*",
|
||||
"*://*.google.me/*",
|
||||
"*://*.google.mg/*",
|
||||
"*://*.google.mk/*",
|
||||
"*://*.google.ml/*",
|
||||
"*://*.google.com.mm/*",
|
||||
"*://*.google.mn/*",
|
||||
"*://*.google.ms/*",
|
||||
"*://*.google.com.mt/*",
|
||||
"*://*.google.mu/*",
|
||||
"*://*.google.mv/*",
|
||||
"*://*.google.mw/*",
|
||||
"*://*.google.com.mx/*",
|
||||
"*://*.google.com.my/*",
|
||||
"*://*.google.co.mz/*",
|
||||
"*://*.google.com.na/*",
|
||||
"*://*.google.com.ng/*",
|
||||
"*://*.google.com.ni/*",
|
||||
"*://*.google.ne/*",
|
||||
"*://*.google.nl/*",
|
||||
"*://*.google.no/*",
|
||||
"*://*.google.com.np/*",
|
||||
"*://*.google.nr/*",
|
||||
"*://*.google.nu/*",
|
||||
"*://*.google.co.nz/*",
|
||||
"*://*.google.com.om/*",
|
||||
"*://*.google.com.pa/*",
|
||||
"*://*.google.com.pe/*",
|
||||
"*://*.google.com.pg/*",
|
||||
"*://*.google.com.ph/*",
|
||||
"*://*.google.com.pk/*",
|
||||
"*://*.google.pl/*",
|
||||
"*://*.google.pn/*",
|
||||
"*://*.google.com.pr/*",
|
||||
"*://*.google.ps/*",
|
||||
"*://*.google.pt/*",
|
||||
"*://*.google.com.py/*",
|
||||
"*://*.google.com.qa/*",
|
||||
"*://*.google.ro/*",
|
||||
"*://*.google.ru/*",
|
||||
"*://*.google.rw/*",
|
||||
"*://*.google.com.sa/*",
|
||||
"*://*.google.com.sb/*",
|
||||
"*://*.google.sc/*",
|
||||
"*://*.google.se/*",
|
||||
"*://*.google.com.sg/*",
|
||||
"*://*.google.sh/*",
|
||||
"*://*.google.si/*",
|
||||
"*://*.google.sk/*",
|
||||
"*://*.google.com.sl/*",
|
||||
"*://*.google.sn/*",
|
||||
"*://*.google.so/*",
|
||||
"*://*.google.sm/*",
|
||||
"*://*.google.sr/*",
|
||||
"*://*.google.st/*",
|
||||
"*://*.google.com.sv/*",
|
||||
"*://*.google.td/*",
|
||||
"*://*.google.tg/*",
|
||||
"*://*.google.co.th/*",
|
||||
"*://*.google.com.tj/*",
|
||||
"*://*.google.tl/*",
|
||||
"*://*.google.tm/*",
|
||||
"*://*.google.tn/*",
|
||||
"*://*.google.to/*",
|
||||
"*://*.google.com.tr/*",
|
||||
"*://*.google.tt/*",
|
||||
"*://*.google.com.tw/*",
|
||||
"*://*.google.co.tz/*",
|
||||
"*://*.google.com.ua/*",
|
||||
"*://*.google.co.ug/*",
|
||||
"*://*.google.co.uk/*",
|
||||
"*://*.google.com.uy/*",
|
||||
"*://*.google.co.uz/*",
|
||||
"*://*.google.com.vc/*",
|
||||
"*://*.google.co.ve/*",
|
||||
"*://*.google.vg/*",
|
||||
"*://*.google.co.vi/*",
|
||||
"*://*.google.com.vn/*",
|
||||
"*://*.google.vu/*",
|
||||
"*://*.google.ws/*",
|
||||
"*://*.google.rs/*",
|
||||
"*://*.google.co.za/*",
|
||||
"*://*.google.co.zm/*",
|
||||
"*://*.google.co.zw/*",
|
||||
"*://*.google.cat/*"
|
||||
],
|
||||
"css": ["styles/searchInjection.css"],
|
||||
"js": ["build/searchInjection.js"]
|
||||
}
|
||||
],
|
||||
|
||||
"options_ui": {
|
||||
"page": "options/index.html"
|
||||
},
|
||||
|
||||
"web_accessible_resources": ["icons/*.png", "icons/*.svg"],
|
||||
|
||||
"permissions": ["https://*/*", "http://*/*"]
|
||||
}
|
15
options/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Linkding Extension Options</title>
|
||||
<link rel="stylesheet" href="../styles/spectre.css">
|
||||
<link rel="stylesheet" href="../styles/spectre-icons.css">
|
||||
<link rel="stylesheet" href="../styles/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="../build/bundle.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
2
options/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
var appTarget = document.getElementById('app');
|
||||
new linkding.Options({target: appTarget});
|
4332
package-lock.json
generated
Normal file
32
package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "linkding-injector",
|
||||
"version": "1.0.0",
|
||||
"description": "Injects search results from the linkding bookmark service into search pages like google and duckduckgo",
|
||||
"license": "MIT",
|
||||
"author": "Jakob Essbüchl",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/fivefold/linkding-injector.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/fivefold/linkding-injector/issues"
|
||||
},
|
||||
"homepage": "https://github.com/fivefold/linkding-injector#readme",
|
||||
"scripts": {
|
||||
"build": "rollup -c && sass scss/theme.scss styles/searchInjection.css",
|
||||
"dev": "rollup -c -w && sass scss/theme.scss styles/searchInjection.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"rollup": "^2.35.1",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"spectre.css": "^0.5.9",
|
||||
"svelte": "^3.31.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.2.1",
|
||||
"web-ext": "^6.1.0"
|
||||
}
|
||||
}
|
16
popup/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Linkding Extension Popup</title>
|
||||
<link rel="stylesheet" href="../styles/spectre.css">
|
||||
<link rel="stylesheet" href="../styles/spectre-icons.css">
|
||||
<link rel="stylesheet" href="../styles/spectre-exp.css">
|
||||
<link rel="stylesheet" href="../styles/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="../build/bundle.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
2
popup/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
var appTarget = document.getElementById('app');
|
||||
new linkding.Popup({target: appTarget});
|
92
rollup.config.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default [
|
||||
// Main bundle (options page)
|
||||
{
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'linkding',
|
||||
file: 'build/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
emitCss: false
|
||||
}),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
|
||||
}),
|
||||
commonjs(),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
},
|
||||
// Background bundle
|
||||
{
|
||||
input: 'src/background.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
file: 'build/background.js'
|
||||
},
|
||||
plugins: [
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
resolve({ browser: true }),
|
||||
commonjs(),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
},
|
||||
// searchInjection bundle
|
||||
{
|
||||
input: 'src/searchInjection.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
file: 'build/searchInjection.js'
|
||||
},
|
||||
plugins: [
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
resolve({ browser: true }),
|
||||
commonjs(),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
}
|
||||
];
|
23
scss/bookmarks.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
ul#bookmark-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: $gray-color;
|
||||
|
||||
.dark-bg &,
|
||||
body[data-dt="1"] & {
|
||||
color: $gray-color-dark;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited:hover {
|
||||
color: $alternative-color;
|
||||
}
|
||||
}
|
||||
}
|
105
scss/injectionBox.scss
Normal file
|
@ -0,0 +1,105 @@
|
|||
div#bookmark-list-container {
|
||||
padding: 20px 22px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
color: $primary-color;
|
||||
|
||||
// duckduckgo-specific styles
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid $duckduckgo-border-color;
|
||||
|
||||
// .dark-bg is the class duckduckgo globally uses for the dark theme
|
||||
// data-dt=1 is the attribute of the body tag google uses for the dark theme
|
||||
.dark-bg &,
|
||||
body[data-dt="1"] & {
|
||||
background-color: $duckduckgo-bg;
|
||||
border: none;
|
||||
color: $primary-color-dark;
|
||||
|
||||
a {
|
||||
color: $primary-color-dark;
|
||||
}
|
||||
|
||||
#navbar #results_amount span {
|
||||
color: $alternative-color !important;
|
||||
}
|
||||
|
||||
.tags a {
|
||||
color: $alternative-color;
|
||||
}
|
||||
}
|
||||
body[data-dt="1"] & {
|
||||
background-color: $google-bg;
|
||||
border: 1px solid $google-border-color-dark;
|
||||
}
|
||||
|
||||
&.google {
|
||||
width: 369px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
|
||||
h1 {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#navbar #ld-logo {
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
:hover {
|
||||
color: $link-color-dark;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tags a:hover {
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#error-message a,
|
||||
#error-message a:visited {
|
||||
color: $alternative-color !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
#ld-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
color: inherit;
|
||||
|
||||
h1 {
|
||||
text-transform: uppercase;
|
||||
font-size: inherit;
|
||||
display: inline-block;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
#results_amount {
|
||||
span {
|
||||
color: $alternative-color-light-theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
scss/theme.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Import custom variables
|
||||
@import "variables";
|
||||
|
||||
// Import style modules
|
||||
@import "injectionBox";
|
||||
@import "bookmarks";
|
40
scss/variables.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
$html-font-size: 18px !default;
|
||||
|
||||
$body-bg: #161822 !default;
|
||||
$bg-color: lighten($body-bg, 5%) !default;
|
||||
$bg-color-light: lighten($body-bg, 5%) !default;
|
||||
|
||||
$border-color: #4c4e53 !default;
|
||||
$border-color-dark: $border-color !default;
|
||||
|
||||
$body-font-color: #b5bec8 !default;
|
||||
$light-color: #fafafa !default;
|
||||
|
||||
$gray-color: #7f879b !default;
|
||||
$gray-color-dark: lighten($gray-color, 20%) !default;
|
||||
|
||||
$primary-color: #a8b1ff !default;
|
||||
$primary-color-dark: saturate($primary-color, 5%) !default;
|
||||
$secondary-color: lighten($body-bg, 10%) !default;
|
||||
|
||||
$link-color: $primary-color !default;
|
||||
$link-color-dark: darken($link-color, 5%) !default;
|
||||
$link-color-light: $link-color !default;
|
||||
|
||||
$alternative-color: #59bdb9;
|
||||
$alternative-color-dark: #73f1eb;
|
||||
|
||||
/* Dark theme specific */
|
||||
$dt-primary-button-color: #5761cb !default;
|
||||
|
||||
/* Custom additions to linkding variables */
|
||||
$alternative-color-light-theme: #05a6a3;
|
||||
$alternative-color-dark-light-theme: darken($alternative-color, 5%);
|
||||
|
||||
$duckduckgo-bg: #282828;
|
||||
$duckduckgo-border-color: #dfe1e5;
|
||||
|
||||
$google-bg: #202124;
|
||||
$google-border-color-dark: #3c4043;
|
||||
|
||||
$primary-color: #5755d9;
|
48
src/background.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { getBrowser, openOptions } from "./browser";
|
||||
import { getConfiguration, isConfigurationComplete } from "./configuration";
|
||||
|
||||
import { search } from "./linkding";
|
||||
|
||||
const browser = getBrowser();
|
||||
|
||||
// Connection to search injection content script
|
||||
let portFromCS;
|
||||
|
||||
function connected(p) {
|
||||
portFromCS = p;
|
||||
|
||||
// When the content script sends the search term, search on linkding and
|
||||
// return results
|
||||
portFromCS.onMessage.addListener(function (m) {
|
||||
if (m.action == "openOptions") {
|
||||
// Open the add on options if the user clicks on the options link in the
|
||||
// injected box
|
||||
openOptions();
|
||||
} else if (isConfigurationComplete() == false) {
|
||||
portFromCS.postMessage({
|
||||
message:
|
||||
"Connection to your linkding instance is not configured yet! " +
|
||||
"Please configure the extension in the <a class='openOptions'>options</a>.",
|
||||
});
|
||||
} else {
|
||||
let config = getConfiguration();
|
||||
// Configuration is complete, execute a search on linkding
|
||||
search(m.searchTerm, { limit: config.resultNum })
|
||||
.then((results) => {
|
||||
const bookmarkSuggestions = results.map((bookmark) => ({
|
||||
url: bookmark.url,
|
||||
title: bookmark.title || bookmark.website_title || bookmark.url,
|
||||
description: bookmark.description || bookmark.website_description,
|
||||
tags: bookmark.tag_names,
|
||||
date: bookmark.date_modified,
|
||||
}));
|
||||
portFromCS.postMessage({ results: bookmarkSuggestions });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
browser.runtime.onConnect.addListener(connected);
|
35
src/browser.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
function isChrome() {
|
||||
return typeof chrome !== "undefined";
|
||||
}
|
||||
|
||||
export function getBrowser() {
|
||||
return isChrome() ? chrome : browser;
|
||||
}
|
||||
|
||||
export async function getCurrentTabInfo() {
|
||||
const tabsPromise = isChrome() ? new Promise(resolve => getBrowser().tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, resolve)) : getBrowser().tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
const tabs = await tabsPromise;
|
||||
const tab = tabs && tabs[0];
|
||||
|
||||
return {
|
||||
url: tab ? tab.url : "",
|
||||
title: tab ? tab.title : ""
|
||||
};
|
||||
}
|
||||
|
||||
export function openOptions() {
|
||||
getBrowser().runtime.openOptionsPage();
|
||||
//window.close();
|
||||
/*
|
||||
keeping window.close() introduces a bug in chrome if the options page is
|
||||
opened and closed without saving options. The background script port closes
|
||||
seemingly indefinitely. The extension needs to be reloaded to fix it.
|
||||
|
||||
Since linkding injector does not use the svelte popup from linkding extension
|
||||
window.close can be safely omitted because there's no window to close
|
||||
*/
|
||||
}
|
20
src/configuration.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const CONFIG_KEY = "ld_ext_config";
|
||||
|
||||
export function getConfiguration() {
|
||||
const configJson = localStorage.getItem(CONFIG_KEY);
|
||||
const config = configJson
|
||||
? JSON.parse(configJson)
|
||||
: { baseUrl: "", token: "", resultNum: 10 };
|
||||
return config;
|
||||
}
|
||||
|
||||
export function saveConfiguration(config) {
|
||||
const configJson = JSON.stringify(config);
|
||||
localStorage.setItem(CONFIG_KEY, configJson);
|
||||
}
|
||||
|
||||
export function isConfigurationComplete() {
|
||||
const config = getConfiguration();
|
||||
|
||||
return config.baseUrl && config.token;
|
||||
}
|
5
src/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Options from "./options.svelte";
|
||||
|
||||
export default {
|
||||
Options,
|
||||
};
|
49
src/linkding.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { getConfiguration } from "./configuration";
|
||||
|
||||
export async function getTags() {
|
||||
const configuration = getConfiguration();
|
||||
|
||||
return fetch(`${configuration.baseUrl}/api/tags/?limit=1000`, {
|
||||
headers: {
|
||||
Authorization: `Token ${configuration.token}`,
|
||||
},
|
||||
}).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json().then((body) => body.results);
|
||||
}
|
||||
return Promise.reject(`Error loading tags: ${response.statusText}`);
|
||||
});
|
||||
}
|
||||
|
||||
export async function search(text, options) {
|
||||
const configuration = getConfiguration();
|
||||
const q = encodeURIComponent(text);
|
||||
const limit = options.limit || 100;
|
||||
|
||||
return fetch(
|
||||
`${configuration.baseUrl}/api/bookmarks/?q=${q}&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${configuration.token}`,
|
||||
},
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return response.json().then((body) => body.results);
|
||||
}
|
||||
return Promise.reject(`Error searching bookmarks: ${response.statusText}`);
|
||||
});
|
||||
}
|
||||
|
||||
export async function testConnection(configuration) {
|
||||
return fetch(`${configuration.baseUrl}/api/bookmarks/?limit=1`, {
|
||||
headers: {
|
||||
Authorization: `Token ${configuration.token}`,
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.status === 200 ? response.json() : Promise.reject(response)
|
||||
)
|
||||
.then((body) => !!body.results)
|
||||
.catch(() => false);
|
||||
}
|
91
src/options.svelte
Normal file
|
@ -0,0 +1,91 @@
|
|||
<script>
|
||||
import { getConfiguration, saveConfiguration } from "./configuration";
|
||||
import { testConnection } from "./linkding";
|
||||
|
||||
let baseUrl = "";
|
||||
let token = "";
|
||||
let resultNum = 10;
|
||||
let isSuccess = false;
|
||||
let isError = false;
|
||||
|
||||
function init() {
|
||||
const config = getConfiguration();
|
||||
baseUrl = config.baseUrl;
|
||||
token = config.token;
|
||||
resultNum = config.resultNum;
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
async function handleSubmit() {
|
||||
const config = {
|
||||
baseUrl,
|
||||
token,
|
||||
resultNum
|
||||
};
|
||||
|
||||
const testResult = await testConnection(config);
|
||||
|
||||
if (testResult) {
|
||||
saveConfiguration(config);
|
||||
isError = false;
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess = false;
|
||||
isError = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<h6>Configuration</h6>
|
||||
<div class="divider"></div>
|
||||
<p>This is a companion extension for the <a href="https://github.com/sissbruecker/linkding">linkding</a> bookmark
|
||||
service. Before you can start using it you have to configure some basic settings, so that the extension can
|
||||
communicate with your linkding installation.</p>
|
||||
<form class="form" on:submit|preventDefault={handleSubmit}>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="input-base-url">Base URL</label>
|
||||
<input class="form-input" type="text" id="input-base-url" placeholder="https://linkding.mydomain.com"
|
||||
bind:value={baseUrl}>
|
||||
<div class="form-input-hint">The base URL of your linkding installation, <b>without</b> the <samp>/bookmark</samp> path or a trailing slash</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="input-token">API Authentication Token</label>
|
||||
<input class="form-input" type="password" id="input-token" placeholder="Token" bind:value={token}>
|
||||
<div class="form-input-hint">Used to authenticate against the linkding API. You can find this on your linkding
|
||||
settings page.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="input-token">Maximum number of search results</label>
|
||||
<input class="form-input" type="mi,ner" id="input-search-num" placeholder="10" bind:value={resultNum}>
|
||||
<div class="form-input-hint">The maximum number of search results. High numbers could lead to worse performance.
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="button-row">
|
||||
{#if isSuccess}
|
||||
<div class="form-group has-success mr-2">
|
||||
<span class="form-input-hint"><i class="icon icon-check"></i> Connection successful</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isError}
|
||||
<div class="form-group has-error mr-2">
|
||||
<span class="form-input-hint"><i class="icon icon-cross"></i> Connection failed</span>
|
||||
</div>
|
||||
{/if}
|
||||
<button type="submit" class="btn btn-primary ml-2" disabled={!(baseUrl && token)}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<style>
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: baseline;
|
||||
}
|
||||
.button-row button {
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
</style>
|
133
src/searchInjection.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
function isChrome() {
|
||||
return typeof chrome !== "undefined";
|
||||
}
|
||||
|
||||
function getBrowser() {
|
||||
return isChrome() ? chrome : browser;
|
||||
}
|
||||
|
||||
/* Sanitise input to prevent unwanted injection of html or even javascript
|
||||
through linkding search results, e.g. in the bookmark title or description
|
||||
*/
|
||||
function escapeHTML(str) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode(str));
|
||||
return p.innerHTML;
|
||||
}
|
||||
|
||||
const browser = getBrowser();
|
||||
|
||||
let port = browser.runtime.connect({ name: "port-from-cs" });
|
||||
let searchEngine;
|
||||
if (document.location.hostname.match(/duckduckgo/)) {
|
||||
searchEngine = "duckduckgo";
|
||||
} else if (document.location.hostname.match(/google/)) {
|
||||
searchEngine = "google";
|
||||
}
|
||||
|
||||
// When background script answers with results, construct html for the result box
|
||||
port.onMessage.addListener(function (m) {
|
||||
let sidebar, html;
|
||||
// In case we don't get results, but a message from the background script,
|
||||
// display it. This is the case before proper configuration
|
||||
if ("message" in m) {
|
||||
html = `
|
||||
<div id="bookmark-list-container" class="${searchEngine}">
|
||||
<div id="navbar">
|
||||
<a id="ld-logo">
|
||||
<img src=${browser.runtime.getURL("icons/logo.svg")} />
|
||||
<h1>linkding injector</h1>
|
||||
</a>
|
||||
<a id="ld-options" class="openOptions">
|
||||
<img class="ld-settings" src=${browser.runtime.getURL(
|
||||
"icons/cog.svg"
|
||||
)} />
|
||||
</a>
|
||||
</div>
|
||||
<div id="error-message">
|
||||
${m.message}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// If there is no message and there are actual results display them
|
||||
else if (m.results.length > 0) {
|
||||
html = `
|
||||
<div id="bookmark-list-container" class="${searchEngine}">
|
||||
<div id="navbar">
|
||||
<a id="ld-logo">
|
||||
<img src=${browser.runtime.getURL("icons/logo.svg")} />
|
||||
<h1>linkding injector</h1>
|
||||
</a>
|
||||
<div id="results_amount">
|
||||
Found <span>${m.results.length}</span> ${
|
||||
m.results.length == 1 ? "result" : "results"
|
||||
}.
|
||||
</div>
|
||||
<a id="ld-options" class="openOptions">
|
||||
<img class="ld-settings" src=${browser.runtime.getURL(
|
||||
"icons/cog.svg"
|
||||
)} />
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += `<ul id="bookmark-list">`;
|
||||
|
||||
m.results.forEach((bookmark) => {
|
||||
html += `
|
||||
<li>
|
||||
<div class="title">
|
||||
<a
|
||||
href="${bookmark.url}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>${escapeHTML(bookmark.title)}</a
|
||||
>
|
||||
</div>
|
||||
<div class="description">
|
||||
<span class="tags">
|
||||
${bookmark.tags.map((tag) => {
|
||||
return "<a>#" + escapeHTML(tag) + "</a>";
|
||||
}).join(" ")}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
${bookmark.tags.length > 0 ? "|" : ""}
|
||||
|
||||
<span>
|
||||
${escapeHTML(bookmark.description)}
|
||||
</span>
|
||||
</div>
|
||||
</li>`;
|
||||
});
|
||||
html += `</ul></div>`;
|
||||
} else {
|
||||
console.error("linkding injector: no message and no search results");
|
||||
}
|
||||
|
||||
// querySelectors for finding the sidebar in the search engine websites
|
||||
if (searchEngine == "duckduckgo") {
|
||||
sidebar = document.querySelector(".sidebar-modules");
|
||||
} else if (searchEngine == "google") {
|
||||
sidebar = document.querySelector("#rhs");
|
||||
}
|
||||
|
||||
// The actual injection
|
||||
sidebar.insertAdjacentHTML("afterbegin", html);
|
||||
|
||||
// Event listeners for opening the extension options. These can only be opened
|
||||
// by the background script, so we need to send a message to it
|
||||
document.querySelectorAll(".openOptions").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
port.postMessage({ action: "openOptions" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Start the search by sending a message to background.js with the search term
|
||||
let queryString = location.search;
|
||||
let urlParams = new URLSearchParams(queryString);
|
||||
let searchTerm = urlParams.get("q");
|
||||
|
||||
port.postMessage({ searchTerm: searchTerm });
|
4
styles/index.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
body {
|
||||
padding: 16px;
|
||||
min-width: 400px;
|
||||
}
|
1231
styles/spectre-exp.css
Normal file
597
styles/spectre-icons.css
Normal file
|
@ -0,0 +1,597 @@
|
|||
/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */
|
||||
.icon {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-style: normal;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-indent: -9999px;
|
||||
vertical-align: middle;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon::before,
|
||||
.icon::after {
|
||||
content: "";
|
||||
display: block;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.icon.icon-2x {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.icon.icon-3x {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
.icon.icon-4x {
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.accordion .icon,
|
||||
.btn .icon,
|
||||
.toast .icon,
|
||||
.menu .icon {
|
||||
vertical-align: -10%;
|
||||
}
|
||||
|
||||
.btn-lg .icon {
|
||||
vertical-align: -15%;
|
||||
}
|
||||
|
||||
.icon-arrow-down::before,
|
||||
.icon-arrow-left::before,
|
||||
.icon-arrow-right::before,
|
||||
.icon-arrow-up::before,
|
||||
.icon-downward::before,
|
||||
.icon-back::before,
|
||||
.icon-forward::before,
|
||||
.icon-upward::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .65em;
|
||||
width: .65em;
|
||||
}
|
||||
|
||||
.icon-arrow-down::before {
|
||||
transform: translate(-50%, -75%) rotate(225deg);
|
||||
}
|
||||
|
||||
.icon-arrow-left::before {
|
||||
transform: translate(-25%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-arrow-right::before {
|
||||
transform: translate(-75%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-arrow-up::before {
|
||||
transform: translate(-50%, -25%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-back::after,
|
||||
.icon-forward::after {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-downward::after,
|
||||
.icon-upward::after {
|
||||
background: currentColor;
|
||||
height: .8em;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-back::after {
|
||||
left: 55%;
|
||||
}
|
||||
|
||||
.icon-back::before {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-downward::after {
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
.icon-downward::before {
|
||||
transform: translate(-50%, -50%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.icon-forward::after {
|
||||
left: 45%;
|
||||
}
|
||||
|
||||
.icon-forward::before {
|
||||
transform: translate(-50%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-upward::after {
|
||||
top: 55%;
|
||||
}
|
||||
|
||||
.icon-upward::before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-caret::before {
|
||||
border-left: .3em solid transparent;
|
||||
border-right: .3em solid transparent;
|
||||
border-top: .3em solid currentColor;
|
||||
height: 0;
|
||||
transform: translate(-50%, -25%);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-menu::before {
|
||||
background: currentColor;
|
||||
box-shadow: 0 -.35em, 0 .35em;
|
||||
height: .1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-apps::before {
|
||||
background: currentColor;
|
||||
box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before,
|
||||
.icon-resize-horiz::after,
|
||||
.icon-resize-vert::before,
|
||||
.icon-resize-vert::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .45em;
|
||||
width: .45em;
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before,
|
||||
.icon-resize-vert::before {
|
||||
transform: translate(-50%, -90%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::after,
|
||||
.icon-resize-vert::after {
|
||||
transform: translate(-50%, -10%) rotate(225deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before {
|
||||
transform: translate(-90%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::after {
|
||||
transform: translate(-10%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-more-horiz::before,
|
||||
.icon-more-vert::before {
|
||||
background: currentColor;
|
||||
border-radius: 50%;
|
||||
box-shadow: -.4em 0, .4em 0;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.icon-more-vert::before {
|
||||
box-shadow: 0 -.4em, 0 .4em;
|
||||
}
|
||||
|
||||
.icon-plus::before,
|
||||
.icon-minus::before,
|
||||
.icon-cross::before {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-plus::after,
|
||||
.icon-cross::after {
|
||||
background: currentColor;
|
||||
height: 100%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-cross::before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-cross::after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon-cross::before,
|
||||
.icon-cross::after {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-check::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -75%) rotate(-45deg);
|
||||
width: .9em;
|
||||
}
|
||||
|
||||
.icon-stop {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-stop::before {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-shutdown {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.icon-shutdown::before {
|
||||
background: currentColor;
|
||||
content: "";
|
||||
height: .5em;
|
||||
top: .1em;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-refresh::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-refresh::after {
|
||||
border: .2em solid currentColor;
|
||||
border-left-color: transparent;
|
||||
border-top-color: transparent;
|
||||
height: 0;
|
||||
left: 80%;
|
||||
top: 20%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-search::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .75em;
|
||||
left: 5%;
|
||||
top: 5%;
|
||||
transform: translate(0, 0) rotate(45deg);
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-search::after {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
left: 80%;
|
||||
top: 80%;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
width: .4em;
|
||||
}
|
||||
|
||||
.icon-edit::before {
|
||||
border: .1rem solid currentColor;
|
||||
height: .4em;
|
||||
transform: translate(-40%, -60%) rotate(-45deg);
|
||||
width: .85em;
|
||||
}
|
||||
|
||||
.icon-edit::after {
|
||||
border: .15em solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
height: 0;
|
||||
left: 5%;
|
||||
top: 95%;
|
||||
transform: translate(0, -100%);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-delete::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-left-radius: .1rem;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-top: 0;
|
||||
height: .75em;
|
||||
top: 60%;
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-delete::after {
|
||||
background: currentColor;
|
||||
box-shadow: -.25em .2em, .25em .2em;
|
||||
height: .1rem;
|
||||
top: .05rem;
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-share {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.icon-share::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: .4em;
|
||||
left: 100%;
|
||||
top: .25em;
|
||||
transform: translate(-125%, -50%) rotate(-45deg);
|
||||
width: .4em;
|
||||
}
|
||||
|
||||
.icon-share::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: 75% 0;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
width: .6em;
|
||||
}
|
||||
|
||||
.icon-flag::before {
|
||||
background: currentColor;
|
||||
height: 1em;
|
||||
left: 15%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-flag::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-left: 0;
|
||||
border-top-right-radius: .1rem;
|
||||
height: .65em;
|
||||
left: 60%;
|
||||
top: 35%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-bookmark::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-top-left-radius: .1rem;
|
||||
border-top-right-radius: .1rem;
|
||||
height: .9em;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-bookmark::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
border-radius: .1rem;
|
||||
height: .5em;
|
||||
transform: translate(-50%, 35%) rotate(-45deg) skew(15deg, 15deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-download,
|
||||
.icon-upload {
|
||||
border-bottom: .1rem solid currentColor;
|
||||
}
|
||||
|
||||
.icon-download::before,
|
||||
.icon-upload::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -60%) rotate(-135deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-download::after,
|
||||
.icon-upload::after {
|
||||
background: currentColor;
|
||||
height: .6em;
|
||||
top: 40%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-upload::before {
|
||||
transform: translate(-50%, -60%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-upload::after {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.icon-copy::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
height: .8em;
|
||||
left: 40%;
|
||||
top: 35%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-copy::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .8em;
|
||||
left: 60%;
|
||||
top: 60%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-time {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-time::before {
|
||||
background: currentColor;
|
||||
height: .4em;
|
||||
transform: translate(-50%, -75%);
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-time::after {
|
||||
background: currentColor;
|
||||
height: .3em;
|
||||
transform: translate(-50%, -75%) rotate(90deg);
|
||||
transform-origin: 50% 90%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-mail::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .8em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-mail::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -90%) rotate(-45deg) skew(10deg, 10deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-people::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .45em;
|
||||
top: 25%;
|
||||
width: .45em;
|
||||
}
|
||||
|
||||
.icon-people::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50% 50% 0 0;
|
||||
height: .4em;
|
||||
top: 75%;
|
||||
width: .9em;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.icon-message::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: .8em;
|
||||
left: 65%;
|
||||
top: 40%;
|
||||
width: .7em;
|
||||
}
|
||||
|
||||
.icon-message::after {
|
||||
background: currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .3em;
|
||||
left: 10%;
|
||||
top: 100%;
|
||||
transform: translate(0, -90%) rotate(45deg);
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-photo {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.icon-photo::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .25em;
|
||||
left: 35%;
|
||||
top: 35%;
|
||||
width: .25em;
|
||||
}
|
||||
|
||||
.icon-photo::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
height: .5em;
|
||||
left: 60%;
|
||||
transform: translate(-50%, 25%) rotate(-45deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-link::before,
|
||||
.icon-link::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 5em 0 0 5em;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-link::before {
|
||||
transform: translate(-70%, -45%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-link::after {
|
||||
transform: translate(-30%, -55%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-location::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50% 50% 50% 0;
|
||||
height: .8em;
|
||||
transform: translate(-50%, -60%) rotate(-45deg);
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-location::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .2em;
|
||||
transform: translate(-50%, -80%);
|
||||
width: .2em;
|
||||
}
|
||||
|
||||
.icon-emoji {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-emoji::before {
|
||||
border-radius: 50%;
|
||||
box-shadow: -.17em -.1em, .17em -.1em;
|
||||
height: .15em;
|
||||
width: .15em;
|
||||
}
|
||||
|
||||
.icon-emoji::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -40%) rotate(-135deg);
|
||||
width: .5em;
|
||||
}
|