Skip to content

Commit

Permalink
Merge pull request #15 from krzysztofjeziorny/master
Browse files Browse the repository at this point in the history
1.36
  • Loading branch information
krzysztofjeziorny authored Oct 3, 2024
2 parents 0f16ae7 + 14c954c commit 50ed579
Show file tree
Hide file tree
Showing 35 changed files with 535 additions and 301 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ typings/
# Database file
/data
# ublock + chromium
/uBlock0.chromium
/uBOLite.chromium.mv3
/chromium-profile
/.direnv

Expand Down
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# Changelog

## (23/09/2024)
## v1.36.0 (02/10/2024)

### What's Changed
* Replace uBlock Origin with uBlock Origin Lite by @sissbruecker in https://github.com/sissbruecker/linkding/pull/866
* Add LAST_MODIFIED attribute when exporting by @ixzhao in https://github.com/sissbruecker/linkding/pull/860
* Return client error status code for invalid form submissions by @sissbruecker in https://github.com/sissbruecker/linkding/pull/849
* Fix header.svg text by @vladh in https://github.com/sissbruecker/linkding/pull/850
* Do not clear fields in POST requests (API behavior change) by @sissbruecker in https://github.com/sissbruecker/linkding/pull/852
* Prevent duplicates when editing by @sissbruecker in https://github.com/sissbruecker/linkding/pull/853
* Fix jumping details modal on back navigation by @sissbruecker in https://github.com/sissbruecker/linkding/pull/854
* Fix select dropdown menu background in dark theme by @sissbruecker in https://github.com/sissbruecker/linkding/pull/858
* Do not escape valid characters in custom CSS by @sissbruecker in https://github.com/sissbruecker/linkding/pull/863
* Simplify Docker build by @sissbruecker in https://github.com/sissbruecker/linkding/pull/865
* Improve error handling for auto tagging by @sissbruecker in https://github.com/sissbruecker/linkding/pull/855
* Bump rollup from 4.13.0 to 4.22.4 by @dependabot in https://github.com/sissbruecker/linkding/pull/851
* Bump rollup from 4.21.3 to 4.22.4 in /docs by @dependabot in https://github.com/sissbruecker/linkding/pull/856

### New Contributors
* @vladh made their first contribution in https://github.com/sissbruecker/linkding/pull/850
* @ixzhao made their first contribution in https://github.com/sissbruecker/linkding/pull/860

**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.35.0...v1.36.0

---

## v1.35.0 (23/09/2024)

### What's Changed
* Add configuration options for pagination by @sissbruecker in https://github.com/sissbruecker/linkding/pull/835
Expand Down
18 changes: 18 additions & 0 deletions bookmarks/migrations/0042_userprofile_custom_css_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-09-28 08:03

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("bookmarks", "0041_merge_metadata"),
]

operations = [
migrations.AddField(
model_name="userprofile",
name="custom_css_hash",
field=models.CharField(blank=True, max_length=32),
),
]
11 changes: 11 additions & 0 deletions bookmarks/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import binascii
import hashlib
import logging
import os
from typing import List
Expand Down Expand Up @@ -430,6 +431,7 @@ class UserProfile(models.Model):
display_remove_bookmark_action = models.BooleanField(default=True, null=False)
permanent_notes = models.BooleanField(default=False, null=False)
custom_css = models.TextField(blank=True, null=False)
custom_css_hash = models.CharField(blank=True, null=False, max_length=32)
auto_tagging_rules = models.TextField(blank=True, null=False)
search_preferences = models.JSONField(default=dict, null=False)
enable_automatic_html_snapshots = models.BooleanField(default=True, null=False)
Expand All @@ -439,6 +441,15 @@ class UserProfile(models.Model):
)
sticky_pagination = models.BooleanField(default=False, null=False)

def save(self, *args, **kwargs):
if self.custom_css:
self.custom_css_hash = hashlib.md5(
self.custom_css.encode("utf-8")
).hexdigest()
else:
self.custom_css_hash = ""
super().save(*args, **kwargs)


class UserProfileForm(forms.ModelForm):
class Meta:
Expand Down
3 changes: 3 additions & 0 deletions bookmarks/services/auto_tagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ def get_tags(script: str, url: str):
parsed_url = urlparse(url.lower())
result = set()

if not parsed_url.hostname:
return result

for line in script.lower().split("\n"):
if "#" in line:
i = line.index("#")
Expand Down
3 changes: 2 additions & 1 deletion bookmarks/services/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
toread = "1" if bookmark.unread else "0"
private = "0" if bookmark.shared else "1"
added = int(bookmark.date_added.timestamp())
modified = int(bookmark.date_modified.timestamp())

doc.append(
f'<DT><A HREF="{url}" ADD_DATE="{added}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>'
f'<DT><A HREF="{url}" ADD_DATE="{added}" LAST_MODIFIED="{modified}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>'
)

if desc:
Expand Down
5 changes: 4 additions & 1 deletion bookmarks/services/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ def _copy_bookmark_data(
bookmark.date_added = parse_timestamp(netscape_bookmark.date_added)
else:
bookmark.date_added = timezone.now()
bookmark.date_modified = bookmark.date_added
if netscape_bookmark.date_modified:
bookmark.date_modified = parse_timestamp(netscape_bookmark.date_modified)
else:
bookmark.date_modified = bookmark.date_added
bookmark.unread = netscape_bookmark.to_read
if netscape_bookmark.title:
bookmark.title = netscape_bookmark.title
Expand Down
4 changes: 4 additions & 0 deletions bookmarks/services/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class NetscapeBookmark:
description: str
notes: str
date_added: str
date_modified: str
tag_names: List[str]
to_read: bool
private: bool
Expand All @@ -27,6 +28,7 @@ def __init__(self):
self.bookmark = None
self.href = ""
self.add_date = ""
self.last_modified = ""
self.tags = ""
self.title = ""
self.description = ""
Expand Down Expand Up @@ -72,6 +74,7 @@ def handle_start_a(self, attrs: Dict[str, str]):
description="",
notes="",
date_added=self.add_date,
date_modified=self.last_modified,
tag_names=tag_names,
to_read=self.toread == "1",
# Mark as private by default, also when attribute is not specified
Expand All @@ -97,6 +100,7 @@ def add_bookmark(self):
self.bookmark = None
self.href = ""
self.add_date = ""
self.last_modified = ""
self.tags = ""
self.title = ""
self.description = ""
Expand Down
7 changes: 7 additions & 0 deletions bookmarks/styles/theme-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,10 @@
--bookmark-actions-weight: 400;
--bulk-actions-bg-color: var(--contrast-5);
}

/* Try to force dark color scheme for all native elements (e.g. upload button
in file inputs, native select dropdown). For the select dropdown some browsers
ignore this and use whatever users have configured in their system settings. */
:root {
color-scheme: dark;
}
10 changes: 10 additions & 0 deletions bookmarks/styles/theme/forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ textarea.form-input {
no-repeat right 0.35rem center / 0.4rem 0.5rem;
padding-right: calc(var(--control-icon-size) + var(--control-padding-x));
}

/* Options */
& option {
/* On Windows with Chrome / Edge, options seems to use the same
background color as the select. However for the dark theme the
background is a semi-transparent white, resulting in an opaque white
background for the dropdown. Use the modal background color to force
a dark background instead. */
background: var(--modal-container-bg-color);
}
}

/* Form element: Checkbox and Radio */
Expand Down
2 changes: 1 addition & 1 deletion bookmarks/styles/theme/modals.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
cursor: default;
display: block;
left: 0;
position: absolute;
position: fixed;
right: 0;
top: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion bookmarks/templates/bookmarks/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#5856e0">
{% endif %}
{% if request.user_profile.custom_css %}
<style>{{ request.user_profile.custom_css }}</style>
<link href="{% url 'bookmarks:custom_css' %}?hash={{ request.user_profile.custom_css_hash }}" rel="stylesheet" type="text/css"/>
{% endif %}
<meta name="turbo-cache-control" content="no-preview">
{% if not request.global_settings.enable_link_prefetch %}
Expand Down
8 changes: 7 additions & 1 deletion bookmarks/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def setup_bookmark(
favicon_file: str = "",
preview_image_file: str = "",
added: datetime = None,
modified: datetime = None,
):
if title is None:
title = get_random_string(length=32)
Expand All @@ -57,13 +58,15 @@ def setup_bookmark(
url = "https://example.com/" + unique_id
if added is None:
added = timezone.now()
if modified is None:
modified = timezone.now()
bookmark = Bookmark(
url=url,
title=title,
description=description,
notes=notes,
date_added=added,
date_modified=timezone.now(),
date_modified=modified,
owner=user,
is_archived=is_archived,
unread=unread,
Expand Down Expand Up @@ -320,6 +323,7 @@ def __init__(
title: str = "",
description: str = "",
add_date: str = "",
last_modified: str = "",
tags: str = "",
to_read: bool = False,
private: bool = True,
Expand All @@ -328,6 +332,7 @@ def __init__(
self.title = title
self.description = description
self.add_date = add_date
self.last_modified = last_modified
self.tags = tags
self.to_read = to_read
self.private = private
Expand All @@ -339,6 +344,7 @@ def render_tag(self, tag: BookmarkHtmlTag):
<DT>
<A {f'HREF="{tag.href}"' if tag.href else ''}
{f'ADD_DATE="{tag.add_date}"' if tag.add_date else ''}
{f'LAST_MODIFIED="{tag.last_modified}"' if tag.last_modified else ''}
{f'TAGS="{tag.tags}"' if tag.tags else ''}
TOREAD="{1 if tag.to_read else 0}"
PRIVATE="{1 if tag.private else 0}">
Expand Down
14 changes: 14 additions & 0 deletions bookmarks/tests/test_auto_tagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ def test_auto_tag_by_domain(self):

self.assertEqual(tags, {"example"})

def test_auto_tag_by_domain_handles_invalid_urls(self):
script = """
example.com example
test.com test
"""

url = "https://"
tags = auto_tagging.get_tags(script, url)
self.assertEqual(tags, set([]))

url = "example.com"
tags = auto_tagging.get_tags(script, url)
self.assertEqual(tags, set([]))

def test_auto_tag_by_domain_works_with_port(self):
script = """
example.com example
Expand Down
21 changes: 0 additions & 21 deletions bookmarks/tests/test_custom_css.py

This file was deleted.

28 changes: 28 additions & 0 deletions bookmarks/tests/test_custom_css_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.test import TestCase
from django.urls import reverse

from bookmarks.tests.helpers import BookmarkFactoryMixin


class CustomCssViewTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self) -> None:
user = self.get_or_create_test_user()
self.client.force_login(user)

def test_with_empty_css(self):
response = self.client.get(reverse("bookmarks:custom_css"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/css")
self.assertEqual(response.headers["Cache-Control"], "public, max-age=2592000")
self.assertEqual(response.content.decode(), "")

def test_with_custom_css(self):
css = "body { background-color: red; }"
self.user.profile.custom_css = css
self.user.profile.save()

response = self.client.get(reverse("bookmarks:custom_css"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/css")
self.assertEqual(response.headers["Cache-Control"], "public, max-age=2592000")
self.assertEqual(response.content.decode(), css)
Loading

0 comments on commit 50ed579

Please sign in to comment.