Improve PWA capabilities
@@ -13,7 +13,7 @@
|
|||||||
!/package-lock.json
|
!/package-lock.json
|
||||||
!/requirements.dev.txt
|
!/requirements.dev.txt
|
||||||
!/requirements.txt
|
!/requirements.txt
|
||||||
!/rollup.config.js
|
!/rollup.config.mjs
|
||||||
!/supervisord.conf
|
!/supervisord.conf
|
||||||
!/uwsgi.ini
|
!/uwsgi.ini
|
||||||
!/version.txt
|
!/version.txt
|
||||||
|
22
.github/workflows/main.yaml
vendored
@@ -7,17 +7,18 @@ jobs:
|
|||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
run: pip install -r requirements.txt -r requirements.dev.txt
|
run: pip install -r requirements.txt -r requirements.dev.txt
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
@@ -26,17 +27,18 @@ jobs:
|
|||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
cache: 'npm'
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
- name: Setup Python environment
|
- name: Setup Python environment
|
||||||
run: |
|
run: |
|
||||||
pip install -r requirements.txt -r requirements.dev.txt
|
pip install -r requirements.txt -r requirements.dev.txt
|
||||||
|
@@ -40,6 +40,7 @@ The name comes from:
|
|||||||
- Automatically provides titles, descriptions and icons of bookmarked websites
|
- Automatically provides titles, descriptions and icons of bookmarked websites
|
||||||
- Automatically creates snapshots of bookmarked websites on [the Internet Archive Wayback Machine](https://archive.org/web/)
|
- Automatically creates snapshots of bookmarked websites on [the Internet Archive Wayback Machine](https://archive.org/web/)
|
||||||
- Import and export bookmarks in Netscape HTML format
|
- Import and export bookmarks in Netscape HTML format
|
||||||
|
- Installable as a Progressive Web App (PWA)
|
||||||
- Extensions for [Firefox](https://addons.mozilla.org/firefox/addon/linkding-extension/) and [Chrome](https://chrome.google.com/webstore/detail/linkding-extension/beakmhbijpdhipnjhnclmhgjlddhidpe), as well as a bookmarklet
|
- Extensions for [Firefox](https://addons.mozilla.org/firefox/addon/linkding-extension/) and [Chrome](https://chrome.google.com/webstore/detail/linkding-extension/beakmhbijpdhipnjhnclmhgjlddhidpe), as well as a bookmarklet
|
||||||
- Light and dark themes
|
- Light and dark themes
|
||||||
- REST API for developing 3rd party apps
|
- REST API for developing 3rd party apps
|
||||||
|
@@ -209,9 +209,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each suggestions.tags as suggestion}
|
{#each suggestions.tags as suggestion}
|
||||||
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
||||||
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
<button on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
||||||
{suggestion.label}
|
{suggestion.label}
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
@@ -220,9 +220,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each suggestions.recentSearches as suggestion}
|
{#each suggestions.recentSearches as suggestion}
|
||||||
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
||||||
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
<button on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
||||||
{suggestion.label}
|
{suggestion.label}
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
@@ -231,9 +231,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each suggestions.bookmarks as suggestion}
|
{#each suggestions.bookmarks as suggestion}
|
||||||
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
|
||||||
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
<button on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
|
||||||
{suggestion.label}
|
{suggestion.label}
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -258,4 +258,4 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -131,9 +131,9 @@
|
|||||||
<!-- menu list items -->
|
<!-- menu list items -->
|
||||||
{#each suggestions as tag,i}
|
{#each suggestions as tag,i}
|
||||||
<li class="menu-item" class:selected={selectedIndex === i}>
|
<li class="menu-item" class:selected={selectedIndex === i}>
|
||||||
<a href="#" on:mousedown|preventDefault={() => complete(tag)}>
|
<button on:mousedown|preventDefault={() => complete(tag)}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
import TagAutoComplete from "./components/TagAutocomplete.svelte";
|
import './behaviors/bookmark-page';
|
||||||
import SearchAutoComplete from "./components/SearchAutoComplete.svelte";
|
import './behaviors/bulk-edit';
|
||||||
import { ApiClient } from "./api";
|
import './behaviors/confirm-button';
|
||||||
import "./behaviors/bookmark-page";
|
import './behaviors/dropdown';
|
||||||
import "./behaviors/bulk-edit";
|
import './behaviors/modal';
|
||||||
import "./behaviors/confirm-button";
|
import './behaviors/global-shortcuts';
|
||||||
import "./behaviors/dropdown";
|
import './behaviors/tag-autocomplete';
|
||||||
import "./behaviors/modal";
|
export { default as TagAutoComplete } from './components/TagAutocomplete.svelte';
|
||||||
import "./behaviors/global-shortcuts";
|
export { default as SearchAutoComplete } from './components/SearchAutoComplete.svelte';
|
||||||
import "./behaviors/tag-autocomplete";
|
export { ApiClient } from './api';
|
||||||
|
|
||||||
export default {
|
|
||||||
ApiClient,
|
|
||||||
TagAutoComplete,
|
|
||||||
SearchAutoComplete,
|
|
||||||
};
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
bookmarks/static/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 12 KiB |
1
bookmarks/static/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><circle cx="255.0164" cy="254.9236" fill="#5856e0" r="224.78528" stroke-width="1.18"/><g fill="none" stroke="#fff" stroke-width="31.25"><path d="m1244.39 1293.95v199.64s-.81 67.89 74.9 68.88c75.98.99 74.88-68.88 74.88-68.88v-199.64" transform="matrix(.70710678 .70710678 -.70710678 .70710678 284.139117 -1684.198509)"/><path d="m1244.39 1293.95v199.64s-.81 67.89 74.9 68.88c75.98.99 74.88-68.88 74.88-68.88v-199.64" transform="matrix(-.70957074 -.70463421 .70463421 -.70957074 235.113139 2195.434643)"/></g></svg>
|
After Width: | Height: | Size: 663 B |
BIN
bookmarks/static/linkding-screenshot.png
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
bookmarks/static/logo-192.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
bookmarks/static/logo-512.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.6 KiB |
1
bookmarks/static/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg clip-rule="evenodd" fill-rule="evenodd" height="512" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><circle cx="255.0164" cy="254.9236" fill="#5856e0" r="224.78528" stroke-width="1.18"/><g fill="none" stroke="#fff" stroke-width="31.25"><path d="m1244.39 1293.95v199.64s-.81 67.89 74.9 68.88c75.98.99 74.88-68.88 74.88-68.88v-199.64" transform="matrix(.70710678 .70710678 -.70710678 .70710678 284.139117 -1684.198509)"/><path d="m1244.39 1293.95v199.64s-.81 67.89 74.9 68.88c75.98.99 74.88-68.88 74.88-68.88v-199.64" transform="matrix(-.70957074 -.70463421 .70463421 -.70957074 235.113139 2195.434643)"/></g></svg>
|
After Width: | Height: | Size: 688 B |
BIN
bookmarks/static/maskable-logo-192.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
bookmarks/static/maskable-logo-512.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
1
bookmarks/static/maskable-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg clip-rule="evenodd" fill-rule="evenodd" height="512" stroke-linejoin="round" stroke-miterlimit="1.5" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m512 512h-512v-512h512" fill="#5856e0" fill-rule="nonzero" stroke-width=".293"/><g fill="none" stroke="#fff" stroke-width="31.25"><path d="m249.095 110.679-141.167 141.167s-48.578 47.432 4.257 101.668c53.026 54.426 101.654 4.242 101.654 4.242l141.166-141.166"/><path d="m263.892 400.446 140.673-141.659s48.412-47.602-4.612-101.652c-53.215-54.24-101.667-3.888-101.667-3.888l-140.674 141.659"/></g></svg>
|
After Width: | Height: | Size: 564 B |
1
bookmarks/static/safari-pinned-tab.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="700pt" preserveAspectRatio="xMidYMid meet" viewBox="0 0 700 700" width="700pt" xmlns="http://www.w3.org/2000/svg"><path d="m3210 6573c-780-79-1463-417-1985-983-444-481-716-1082-791-1750-18-160-18-490 0-650 80-713 380-1341 880-1842 492-494 1125-801 1822-883 150-18 512-21 654-5 407 44 737 142 1094 323 775 394 1350 1108 1571 1952 71 271 98 487 98 785-1 311-35 562-117 847-54 188-99 302-201 508-216 439-510 795-900 1090-441 335-992 550-1544 605-95 9-499 12-581 3zm-639-2228c-543-544-1003-1011-1020-1038-91-134-135-274-134-422 1-167 61-314 200-485 135-165 308-291 467-338 110-32 264-27 376 12 185 65 130 15 1233 1115l1007 1006 150-150c83-82 150-154 150-160s-442-452-982-992c-658-656-1010-1000-1064-1040-304-223-643-298-965-214-271 72-548 272-751 543-115 153-179 283-226 457-22 85-26 115-25 256 0 140 3 172 26 257 34 130 95 266 169 380 54 83 172 205 1067 1101l1006 1007 152-152 153-153zm2359 1051c242-44 461-167 675-381 282-281 415-567 415-890 0-119-17-230-51-336-32-101-123-277-188-363-54-71-2003-2046-2020-2046-11 0-301 281-301 292 0 6 435 449 967 985 533 536 986 998 1007 1027 53 70 121 216 140 303 30 136 14 277-48 416-65 147-245 351-396 449-211 137-403 161-617 79-162-63-173-73-1009-913-428-431-871-876-984-991-112-114-207-207-211-207s-75 67-160 148l-153 149 785 789c431 434 865 872 964 973 266 271 397 369 612 455 176 70 396 94 573 62z" transform="matrix(.1 0 0 -.1 0 700)"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@@ -6,8 +6,10 @@
|
|||||||
<html lang="en" data-api-base-url="{% url 'bookmarks:api-root' %}">
|
<html lang="en" data-api-base-url="{% url 'bookmarks:api-root' %}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="{% static 'favicon.png' %}"/>
|
<link rel="icon" href="{% static 'favicon.ico' %}" sizes="48x48">
|
||||||
<link rel="apple-touch-icon" href="{% static 'apple-touch-icon.png' %}">
|
<link rel="icon" href="{% static 'favicon.svg' %}" sizes="any" type="image/svg+xml">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'apple-touch-icon.png' %}">
|
||||||
|
<link rel="mask-icon" href="{% static 'safari-pinned-tab.svg' %}" color="#5856e0">
|
||||||
<link rel="manifest" href="{% url 'bookmarks:manifest' %}">
|
<link rel="manifest" href="{% url 'bookmarks:manifest' %}">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||||
@@ -19,14 +21,18 @@
|
|||||||
{# Include specific theme variant based on user profile setting #}
|
{# Include specific theme variant based on user profile setting #}
|
||||||
{% if request.user_profile.theme == 'light' %}
|
{% if request.user_profile.theme == 'light' %}
|
||||||
<link href="{% sass_src 'theme-light.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"/>
|
<link href="{% sass_src 'theme-light.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"/>
|
||||||
|
<meta name="theme-color" content="#5856e0">
|
||||||
{% elif request.user_profile.theme == 'dark' %}
|
{% elif request.user_profile.theme == 'dark' %}
|
||||||
<link href="{% sass_src 'theme-dark.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"/>
|
<link href="{% sass_src 'theme-dark.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"/>
|
||||||
|
<meta name="theme-color" content="#161822">
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Use auto theme as fallback #}
|
{# Use auto theme as fallback #}
|
||||||
<link href="{% sass_src 'theme-dark.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"
|
<link href="{% sass_src 'theme-dark.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"
|
||||||
media="(prefers-color-scheme: dark)"/>
|
media="(prefers-color-scheme: dark)"/>
|
||||||
<link href="{% sass_src 'theme-light.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"
|
<link href="{% sass_src 'theme-light.scss' %}?v={{ app_version }}" rel="stylesheet" type="text/css"
|
||||||
media="(prefers-color-scheme: light)"/>
|
media="(prefers-color-scheme: light)"/>
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#161822">
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#5856e0">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
<body ld-global-shortcuts>
|
<body ld-global-shortcuts>
|
||||||
|
@@ -11,9 +11,91 @@ class MetadataViewTestCase(TestCase):
|
|||||||
response_body = response.json()
|
response_body = response.json()
|
||||||
expected_body = {
|
expected_body = {
|
||||||
"short_name": "linkding",
|
"short_name": "linkding",
|
||||||
|
"name": "linkding",
|
||||||
|
"description": "Self-hosted bookmark service",
|
||||||
"start_url": "bookmarks",
|
"start_url": "bookmarks",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
|
"theme_color": "#5856e0",
|
||||||
|
"background_color": "#161822",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/maskable-logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/maskable-logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/maskable-logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Add bookmark",
|
||||||
|
"url": "/bookmarks/new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Archived",
|
||||||
|
"url": "/bookmarks/archived",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unread",
|
||||||
|
"url": "/bookmarks?unread=yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Untagged",
|
||||||
|
"url": "/bookmarks?q=!untagged",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shared",
|
||||||
|
"url": "/bookmarks/shared",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/static/linkding-screenshot.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "2158x1160",
|
||||||
|
"form_factor": "wide"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/bookmarks/new",
|
||||||
|
"method": "GET",
|
||||||
|
"enctype": "application/x-www-form-urlencoded",
|
||||||
|
"params": {
|
||||||
|
"url": "url",
|
||||||
|
"text": "url",
|
||||||
|
"title": "title",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
|
||||||
@@ -26,8 +108,90 @@ class MetadataViewTestCase(TestCase):
|
|||||||
response_body = response.json()
|
response_body = response.json()
|
||||||
expected_body = {
|
expected_body = {
|
||||||
"short_name": "linkding",
|
"short_name": "linkding",
|
||||||
|
"name": "linkding",
|
||||||
|
"description": "Self-hosted bookmark service",
|
||||||
"start_url": "bookmarks",
|
"start_url": "bookmarks",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/linkding/",
|
"scope": "/linkding/",
|
||||||
|
"theme_color": "#5856e0",
|
||||||
|
"background_color": "#161822",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/maskable-logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/maskable-logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/maskable-logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Add bookmark",
|
||||||
|
"url": "/linkding/bookmarks/new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Archived",
|
||||||
|
"url": "/linkding/bookmarks/archived",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unread",
|
||||||
|
"url": "/linkding/bookmarks?unread=yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Untagged",
|
||||||
|
"url": "/linkding/bookmarks?q=!untagged",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shared",
|
||||||
|
"url": "/linkding/bookmarks/shared",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/linkding/static/linkding-screenshot.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "2158x1160",
|
||||||
|
"form_factor": "wide"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/linkding/bookmarks/new",
|
||||||
|
"method": "GET",
|
||||||
|
"enctype": "application/x-www-form-urlencoded",
|
||||||
|
"params": {
|
||||||
|
"url": "url",
|
||||||
|
"text": "url",
|
||||||
|
"title": "title",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.assertDictEqual(response_body, expected_body)
|
self.assertDictEqual(response_body, expected_body)
|
||||||
|
@@ -5,9 +5,91 @@ from django.conf import settings
|
|||||||
def manifest(request):
|
def manifest(request):
|
||||||
response = {
|
response = {
|
||||||
"short_name": "linkding",
|
"short_name": "linkding",
|
||||||
|
"name": "linkding",
|
||||||
|
"description": "Self-hosted bookmark service",
|
||||||
"start_url": "bookmarks",
|
"start_url": "bookmarks",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": "/" + settings.LD_CONTEXT_PATH,
|
"scope": "/" + settings.LD_CONTEXT_PATH,
|
||||||
|
"theme_color": "#5856e0",
|
||||||
|
"background_color": "#ffffff" if request.user_profile.theme == "light" else "#161822",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/maskable-logo.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/maskable-logo-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/maskable-logo-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Add bookmark",
|
||||||
|
"url": "/" + settings.LD_CONTEXT_PATH + "bookmarks/new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Archived",
|
||||||
|
"url": "/" + settings.LD_CONTEXT_PATH + "bookmarks/archived",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unread",
|
||||||
|
"url": "/" + settings.LD_CONTEXT_PATH + "bookmarks?unread=yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Untagged",
|
||||||
|
"url": "/" + settings.LD_CONTEXT_PATH + "bookmarks?q=!untagged",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shared",
|
||||||
|
"url": "/" + settings.LD_CONTEXT_PATH + "bookmarks/shared",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/" + settings.LD_CONTEXT_PATH + "static/linkding-screenshot.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "2158x1160",
|
||||||
|
"form_factor": "wide"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/" + settings.LD_CONTEXT_PATH + "bookmarks/new",
|
||||||
|
"method": "GET",
|
||||||
|
"enctype": "application/x-www-form-urlencoded",
|
||||||
|
"params": {
|
||||||
|
"url": "url",
|
||||||
|
"text": "url",
|
||||||
|
"title": "title",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponse(response, status=200)
|
return JsonResponse(response, status=200)
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
FROM node:18.18.0-alpine AS node-build
|
FROM node:20-alpine AS node-build
|
||||||
WORKDIR /etc/linkding
|
WORKDIR /etc/linkding
|
||||||
# install build dependencies
|
# install build dependencies
|
||||||
COPY rollup.config.js package.json package-lock.json ./
|
COPY rollup.config.mjs package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
# copy files needed for JS build
|
# copy files needed for JS build
|
||||||
COPY bookmarks/frontend ./bookmarks/frontend
|
COPY bookmarks/frontend ./bookmarks/frontend
|
||||||
# run build
|
# run build
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
FROM node:18.18.0-alpine AS node-build
|
FROM node:20-alpine AS node-build
|
||||||
WORKDIR /etc/linkding
|
WORKDIR /etc/linkding
|
||||||
# install build dependencies
|
# install build dependencies
|
||||||
COPY rollup.config.js package.json package-lock.json ./
|
COPY rollup.config.mjs package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
# copy files needed for JS build
|
# copy files needed for JS build
|
||||||
COPY bookmarks/frontend ./bookmarks/frontend
|
COPY bookmarks/frontend ./bookmarks/frontend
|
||||||
# run build
|
# run build
|
||||||
|
1376
package-lock.json
generated
13
package.json
@@ -13,19 +13,18 @@
|
|||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sissbruecker/linkding/issues"
|
"url": "https://github.com/sissbruecker/linkding/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/sissbruecker/linkding#readme",
|
"homepage": "https://github.com/sissbruecker/linkding#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-commonjs": "^21.0.2",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"rollup": "^2.70.1",
|
"rollup": "^4.13.0",
|
||||||
"rollup-plugin-svelte": "^7.1.0",
|
"rollup-plugin-svelte": "^7.2.0",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
|
||||||
"spectre.css": "^0.5.8",
|
"spectre.css": "^0.5.8",
|
||||||
"svelte": "^3.49.0"
|
"svelte": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^3.0.2"
|
"prettier": "^3.0.2"
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
import svelte from 'rollup-plugin-svelte';
|
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
|
||||||
import { terser } from 'rollup-plugin-terser';
|
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
input: 'bookmarks/frontend/index.js',
|
|
||||||
output: {
|
|
||||||
sourcemap: true,
|
|
||||||
format: 'iife',
|
|
||||||
name: 'linkding',
|
|
||||||
// Generate bundle in static folder to that it is picked up by Django static files finder
|
|
||||||
file: 'bookmarks/static/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
|
|
||||||
}
|
|
||||||
};
|
|
37
rollup.config.mjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import svelte from 'rollup-plugin-svelte';
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import terser from '@rollup/plugin-terser';
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'bookmarks/frontend/index.js',
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: 'iife',
|
||||||
|
name: 'linkding',
|
||||||
|
// Generate bundle in static folder to that it is picked up by Django static files finder
|
||||||
|
file: 'bookmarks/static/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,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
};
|