Compare commits

..

10 Commits

Author SHA1 Message Date
Sascha Ißbrücker
492de5618c Bump version 2025-12-13 10:33:32 +01:00
Sascha Ißbrücker
c349ad7670 Use sandbox CSP for viewing assets (#1245) 2025-12-13 10:32:06 +01:00
Simon
1c17e16655 Bump supervisor to 4.3.0 to fix warning (#1216)
Co-authored-by: simonhammes <simonhammes@users.noreply.github.com>
2025-12-13 10:07:32 +01:00
Devinside
9b70bc3b55 Add Komrade project to community resources (#1236) 2025-12-13 09:54:00 +01:00
vbsampath
beba4f8b93 Add Javascript API to community resources (#1195)
* Added Javascript client and library for Linkding REST API

* Cleanup

---------

Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@googlemail.com>
2025-12-13 09:53:16 +01:00
Sascha Ißbrücker
bb7af56dc1 Bump docs dependencies 2025-12-13 09:37:51 +01:00
dependabot[bot]
e89fecbd10 Bump astro from 5.13.2 to 5.14.4 in /docs (#1201)
Bumps [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) from 5.13.2 to 5.14.4.
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@5.14.4/packages/astro)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 5.14.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 07:08:12 +02:00
Sascha Ißbrücker
70734ed273 Fix tag cloud highlighting first char when tags are not grouped (#1209)
* Fix tag cloud highlighting first char when tags are not grouped

* update test
2025-10-18 07:05:15 +02:00
m3e
dcb15f1942 Fix devcontainer (#1208)
* Update Python version to 3.13 in devcontainer

* Update `postCreateCommand` to install and use uv

* Update DevContainers paragraph in README with uv commands

* Update commands

---------

Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@googlemail.com>
2025-10-18 06:24:31 +02:00
Sascha Ißbrücker
3b6cdbdd84 Update CHANGELOG.md 2025-10-11 13:37:40 +02:00
14 changed files with 1398 additions and 2706 deletions

View File

@@ -2,7 +2,7 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"image": "mcr.microsoft.com/devcontainers/python:3.13",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
},
@@ -14,7 +14,7 @@
"forwardPorts": [8000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pip3 install --user -r requirements.txt -r requirements.dev.txt && npm install && mkdir -p data && python3 manage.py migrate",
"postCreateCommand": "pip install uv && uv sync --group dev && npm install && mkdir -p data && uv run manage.py migrate",
// Configure tool-specific properties.
"customizations": {

View File

@@ -1,5 +1,18 @@
# Changelog
## v1.44.1 (11/10/2025)
### What's Changed
* Fix normalized URL not being generated in bookmark import by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1202
* Fix missing tags causing errors in import with Postgres by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1203
* Check for dupes by exact URL if normalized URL is missing by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1204
* Attempt to fix botched normalized URL migration from 1.43.0 by @sissbruecker in https://github.com/sissbruecker/linkding/pull/1205
**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.44.0...v1.44.1
---
## v1.44.0 (05/10/2025)
### What's Changed

View File

@@ -105,25 +105,20 @@ make format
### DevContainers
> [!WARNING]
> The dev container setup is currently broken after switching to uv.
> Feel free to contribute a PR if you want to fix it.
> The instructions below are outdated until then.
This repository also supports DevContainers: [![Open in Remote - Containers](https://img.shields.io/static/v1?label=Remote%20-%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/sissbruecker/linkding.git)
Once checked out, only the following commands are required to get started:
Create a user for the frontend:
```
python3 manage.py createsuperuser --username=joe --email=joe@example.com
uv run manage.py createsuperuser --username=joe --email=joe@example.com
```
Start the Node.js development server (used for compiling JavaScript components like tag auto-completion) with:
```
npm run dev
make frontend
```
Start the Django development server with:
```
python3 manage.py runserver
make serve
```
The frontend is now available under http://localhost:8000

View File

@@ -15,15 +15,15 @@
{% for group in tag_cloud.groups %}
<p class="group">
{% for tag in group.tags %}
{# Highlight first char of first tag in group #}
{% if forloop.counter == 1 %}
{# Highlight first char of first tag in group if grouping is enabled #}
{% if group.highlight_first_char and forloop.counter == 1 %}
<a href="?{{ tag.query_string }}"
class="mr-2" data-is-tag-item>
<span
class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span>
</a>
{% else %}
{# Render remaining tags normally #}
{# Render tags normally #}
<a href="?{{ tag.query_string }}"
class="mr-2" data-is-tag-item>
<span>{{ tag.name }}</span>

View File

@@ -141,7 +141,7 @@ class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin):
def test_reader_view_access_guest_user(self):
self.view_access_guest_user_test("linkding:assets.read")
def test_snapshot_download_name(self):
def test_snapshot_download_headers(self):
bookmark = self.setup_bookmark()
asset = self.setup_asset_with_file(bookmark)
response = self.client.get(reverse("linkding:assets.view", args=[asset.id]))
@@ -151,8 +151,9 @@ class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin):
response["Content-Disposition"],
f'inline; filename="{asset.display_name}.html"',
)
self.assertEqual(response["Content-Security-Policy"], "sandbox")
def test_uploaded_file_download_name(self):
def test_uploaded_file_download_headers(self):
bookmark = self.setup_bookmark()
asset = self.setup_asset_with_uploaded_file(bookmark)
response = self.client.get(reverse("linkding:assets.view", args=[asset.id]))
@@ -162,3 +163,4 @@ class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin):
response["Content-Disposition"],
f'inline; filename="{asset.display_name}"',
)
self.assertEqual(response["Content-Security-Policy"], "sandbox")

View File

@@ -32,7 +32,12 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
template_to_render = Template("{% include 'bookmarks/tag_cloud.html' %}")
return template_to_render.render(context)
def assertTagGroups(self, rendered_template: str, groups: List[List[str]]):
def assertTagGroups(
self,
rendered_template: str,
groups: List[List[str]],
highlight_first_char: bool = True,
):
soup = self.make_soup(rendered_template)
group_elements = soup.select("p.group")
@@ -48,6 +53,18 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
link_element = link_elements[tag_index]
self.assertEqual(link_element.text.strip(), tag)
if tag_index == 0:
if highlight_first_char:
self.assertIn(
f'<span class="highlight-char">{tag[0]}</span>',
str(link_element),
)
else:
self.assertNotIn(
f'<span class="highlight-char">{tag[0]}</span>',
str(link_element),
)
def assertNumSelectedTags(self, rendered_template: str, count: int):
soup = self.make_soup(rendered_template)
link_elements = soup.select("p.selected-tags a")
@@ -178,6 +195,7 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
"Coyote",
],
],
False,
)
def test_no_duplicate_tag_names(self):

View File

@@ -33,6 +33,7 @@ def view(request, asset_id: int):
response = HttpResponse(content, content_type=asset.content_type)
response["Content-Disposition"] = f'inline; filename="{asset.download_name}"'
response["Content-Security-Policy"] = "sandbox"
return response

View File

@@ -383,10 +383,13 @@ class RemoveTagItem:
class TagGroup:
def __init__(self, context: RequestContext, char: str):
def __init__(
self, context: RequestContext, char: str, highlight_first_char: bool = True
):
self.context = context
self.tags = []
self.char = char
self.highlight_first_char = highlight_first_char
def __repr__(self):
return f"<{self.char} TagGroup>"
@@ -436,7 +439,7 @@ class TagGroup:
return []
sorted_tags = sorted(tags, key=lambda x: str.lower(x.name))
group = TagGroup(context, "Ungrouped")
group = TagGroup(context, "Ungrouped", highlight_first_char=False)
for tag in sorted_tags:
group.add_tag(tag)

3994
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,8 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/starlight": "^0.34.3",
"astro": "^5.13.2",
"sharp": "^0.32.5",
"typescript": "^5.6.2"
"@astrojs/starlight": "^0.37.1",
"astro": "^5.6.1",
"sharp": "^0.34.2"
}
}

View File

@@ -15,8 +15,10 @@ This section lists community projects around using linkding, in alphabetical ord
- [iOS Shortcut using API and Tagging](https://gist.github.com/andrewdolphin/a7dff49505e588d940bec55132fab8ad) An iOS shortcut using the Linkding API (no extra logins required) that pulls previously used tags and allows tagging at the time of link creation.
- [iOS Shortcut and workflow](https://joshdick.net/2025/01/23/how_i_use_linkding_on_ios.html) iOS shortcut that accepts URLs in various ways, and shows a corresponding Linkding add/edit webview in a modal popup
- [k8s + s3](https://github.com/jzck/linkding-k8s-s3) - Setup for hosting stateless linkding on k8s with sqlite replicated to s3. By [jzck](https://github.com/jzck)
- [Komrade](https://codeberg.org/kodemonaut/komrade) - A simple docker based startpage/dashboard which syncs with Linkding, Linkwarden or Nextcloud Bookmarks.
- [Linka!](https://github.com/cmsax/linka) Web app (also a PWA) for quickly searching & opening bookmarks in linkding, support multi keywords, exclude mode and other advance options. By [cmsax](https://github.com/cmsax)
- [LinkBuddy](https://github.com/peterto/LinkBuddy) An open-source Android and iOS client for linkding, written in React Native. Android apk available on [github](https://github.com/peterto/LinkBuddy/releases) and iOS version on [Apple AppStore](https://apps.apple.com/us/app/linkbuddy-for-linkding/id6740408952). By [peterto](https://github.com/peterto).
- [linkding-api](https://github.com/vbsampath/linkding-api) A Javascript library implementing linkding REST API by [vbsampath](https://github.com/vbsampath)
- [linkding-archiver](https://github.com/sebw/linkding-archiver) A Python application that integrates with SingleFile and Tube Archivist to archive your links and videos. By [sebw](https://github.com/sebw)
- [linkding-cli](https://github.com/bachya/linkding-cli) A command-line interface (CLI) to interact with the linkding REST API. Powered by [aiolinkding](https://github.com/bachya/aiolinkding). By [bachya](https://github.com/bachya)
- [linkding-extension](https://github.com/jeroenpardon/linkding-extension) Chromium compatible extension that wraps the linkding bookmarklet. Tested with Chrome, Edge, Brave. By [jeroenpardon](https://github.com/jeroenpardon)

View File

@@ -1,6 +1,6 @@
[project]
name = "linkding"
version = "1.44.1"
version = "1.44.2"
description = "Self-hosted bookmark manager that is designed be to be minimal, fast, and easy to set up using Docker."
readme = "README.md"
requires-python = ">=3.13"
@@ -17,7 +17,7 @@ dependencies = [
"mozilla-django-oidc>=4.0.1",
"python-dateutil>=2.9.0.post0",
"requests>=2.32.4",
"supervisor>=4.2.5",
"supervisor>=4.3.0",
"uwsgi>=2.0.28",
"waybackpy>=3.0.6",
]

26
uv.lock generated
View File

@@ -329,6 +329,8 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
{ url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" },
{ url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" },
{ url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
@@ -336,6 +338,8 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" },
{ url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" },
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
]
@@ -377,7 +381,7 @@ wheels = [
[[package]]
name = "linkding"
version = "1.44.1"
version = "1.44.2"
source = { virtual = "." }
dependencies = [
{ name = "beautifulsoup4" },
@@ -426,7 +430,7 @@ requires-dist = [
{ name = "mozilla-django-oidc", specifier = ">=4.0.1" },
{ name = "python-dateutil", specifier = ">=2.9.0.post0" },
{ name = "requests", specifier = ">=2.32.4" },
{ name = "supervisor", specifier = ">=4.2.5" },
{ name = "supervisor", specifier = ">=4.3.0" },
{ name = "uwsgi", specifier = ">=2.0.28" },
{ name = "waybackpy", specifier = ">=3.0.6" },
]
@@ -674,15 +678,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]]
name = "six"
version = "1.17.0"
@@ -712,14 +707,11 @@ wheels = [
[[package]]
name = "supervisor"
version = "4.2.5"
version = "4.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/37/517989b05849dd6eaa76c148f24517544704895830a50289cbbf53c7efb9/supervisor-4.2.5.tar.gz", hash = "sha256:34761bae1a23c58192281a5115fb07fbf22c9b0133c08166beffc70fed3ebc12", size = 466073, upload-time = "2022-12-24T01:02:43.705Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a9/b5/37e7a3706de436a8a2d75334711dad1afb4ddffab09f25e31d89e467542f/supervisor-4.3.0.tar.gz", hash = "sha256:4a2bf149adf42997e1bb44b70c43b613275ec9852c3edacca86a9166b27e945e", size = 468912, upload-time = "2025-08-23T18:25:02.418Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/7a/0ad3973941590c040475046fef37a2b08a76691e61aa59540828ee235a6e/supervisor-4.2.5-py2.py3-none-any.whl", hash = "sha256:2ecaede32fc25af814696374b79e42644ecaba5c09494c51016ffda9602d0f08", size = 319561, upload-time = "2022-12-24T01:02:40.814Z" },
{ url = "https://files.pythonhosted.org/packages/0e/65/5e726c372da8a5e35022a94388b12252710aad0c2351699c3d76ae8dba78/supervisor-4.3.0-py2.py3-none-any.whl", hash = "sha256:0bcb763fddafba410f35cbde226aa7f8514b9fb82eb05a0c85f6588d1c13f8db", size = 320736, upload-time = "2025-08-23T18:25:00.767Z" },
]
[[package]]

View File

@@ -1 +1 @@
1.44.1
1.44.2