Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notes to bookmarks #472

Merged
merged 15 commits into from
May 20, 2023
5 changes: 4 additions & 1 deletion bookmarks/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Meta:
'url',
'title',
'description',
'notes',
'website_title',
'website_description',
'is_archived',
Expand All @@ -47,6 +48,7 @@ class Meta:
# Override optional char fields to provide default value
title = serializers.CharField(required=False, allow_blank=True, default='')
description = serializers.CharField(required=False, allow_blank=True, default='')
notes = serializers.CharField(required=False, allow_blank=True, default='')
is_archived = serializers.BooleanField(required=False, default=False)
unread = serializers.BooleanField(required=False, default=False)
shared = serializers.BooleanField(required=False, default=False)
Expand All @@ -58,6 +60,7 @@ def create(self, validated_data):
bookmark.url = validated_data['url']
bookmark.title = validated_data['title']
bookmark.description = validated_data['description']
bookmark.notes = validated_data['notes']
bookmark.is_archived = validated_data['is_archived']
bookmark.unread = validated_data['unread']
bookmark.shared = validated_data['shared']
Expand All @@ -66,7 +69,7 @@ def create(self, validated_data):

def update(self, instance: Bookmark, validated_data):
# Update fields if they were provided in the payload
for key in ['url', 'title', 'description', 'unread', 'shared']:
for key in ['url', 'title', 'description', 'notes', 'unread', 'shared']:
if key in validated_data:
setattr(instance, key, validated_data[key])

Expand Down
18 changes: 17 additions & 1 deletion bookmarks/e2e/test_bookmark_form.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.urls import reverse
from playwright.sync_api import sync_playwright
from playwright.sync_api import sync_playwright, expect

from bookmarks.e2e.helpers import LinkdingE2ETestCase

Expand All @@ -8,6 +8,7 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
def test_create_should_check_for_existing_bookmark(self):
existing_bookmark = self.setup_bookmark(title='Existing title',
description='Existing description',
notes='Existing notes',
tags=[self.setup_tag(name='tag1'), self.setup_tag(name='tag2')],
website_title='Existing website title',
website_description='Existing website description',
Expand All @@ -26,6 +27,7 @@ def test_create_should_check_for_existing_bookmark(self):
# Form should be pre-filled with data from existing bookmark
self.assertEqual(existing_bookmark.title, page.get_by_label('Title').input_value())
self.assertEqual(existing_bookmark.description, page.get_by_label('Description').input_value())
self.assertEqual(existing_bookmark.notes, page.get_by_label('Notes').input_value())
self.assertEqual(existing_bookmark.website_title, page.get_by_label('Title').get_attribute('placeholder'))
self.assertEqual(existing_bookmark.website_description,
page.get_by_label('Description').get_attribute('placeholder'))
Expand All @@ -49,3 +51,17 @@ def test_edit_should_not_check_for_existing_bookmark(self):

page.wait_for_timeout(timeout=1000)
page.get_by_text('This URL is already bookmarked.').wait_for(state='hidden')

def test_enter_url_of_existing_bookmark_should_show_notes(self):
bookmark = self.setup_bookmark(notes='Existing notes', description='Existing description')

with sync_playwright() as p:
browser = self.setup_browser(p)
page = browser.new_page()
page.goto(self.live_server_url + reverse('bookmarks:new'))

details = page.locator('details.notes')
expect(details).not_to_have_attribute('open', value='')

page.get_by_label('URL').fill(bookmark.url)
expect(details).to_have_attribute('open', value='')
27 changes: 27 additions & 0 deletions bookmarks/e2e/test_bookmark_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from unittest import skip

from django.urls import reverse
from playwright.sync_api import sync_playwright, expect

from bookmarks.e2e.helpers import LinkdingE2ETestCase


@skip("Fails in CI, needs investigation")
class BookmarkListE2ETestCase(LinkdingE2ETestCase):
def test_toggle_notes_should_show_hide_notes(self):
self.setup_bookmark(notes='Test notes')

with sync_playwright() as p:
browser = self.setup_browser(p)
page = browser.new_page()
page.goto(self.live_server_url + reverse('bookmarks:index'))

notes = page.locator('li .notes')
expect(notes).to_be_hidden()

toggle_notes = page.locator('li button.toggle-notes')
toggle_notes.click()
expect(notes).to_be_visible()

toggle_notes.click()
expect(notes).to_be_hidden()
18 changes: 18 additions & 0 deletions bookmarks/migrations/0022_bookmark_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-05-19 10:52

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bookmarks', '0021_userprofile_display_url'),
]

operations = [
migrations.AddField(
model_name='bookmark',
name='notes',
field=models.TextField(blank=True),
),
]
18 changes: 18 additions & 0 deletions bookmarks/migrations/0023_userprofile_permanent_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.9 on 2023-05-20 08:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bookmarks', '0022_bookmark_notes'),
]

operations = [
migrations.AddField(
model_name='userprofile',
name='permanent_notes',
field=models.BooleanField(default=False),
),
]
9 changes: 8 additions & 1 deletion bookmarks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Bookmark(models.Model):
url = models.CharField(max_length=2048, validators=[BookmarkURLValidator()])
title = models.CharField(max_length=512, blank=True)
description = models.TextField(blank=True)
notes = models.TextField(blank=True)
website_title = models.CharField(max_length=512, blank=True, null=True)
website_description = models.TextField(blank=True, null=True)
web_archive_snapshot_url = models.CharField(max_length=2048, blank=True)
Expand Down Expand Up @@ -110,13 +111,18 @@ class Meta:
'tag_string',
'title',
'description',
'notes',
'website_title',
'website_description',
'unread',
'shared',
'auto_close',
]

@property
def has_notes(self):
return self.instance and self.instance.notes


class BookmarkFilters:
def __init__(self, request: WSGIRequest):
Expand Down Expand Up @@ -172,13 +178,14 @@ class UserProfile(models.Model):
enable_sharing = models.BooleanField(default=False, null=False)
enable_favicons = models.BooleanField(default=False, null=False)
display_url = models.BooleanField(default=False, null=False)
permanent_notes = models.BooleanField(default=False, null=False)


class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['theme', 'bookmark_date_display', 'bookmark_link_target', 'web_archive_integration', 'tag_search',
'enable_sharing', 'enable_favicons', 'display_url']
'enable_sharing', 'enable_favicons', 'display_url', 'permanent_notes']


@receiver(post_save, sender=get_user_model())
Expand Down
1 change: 1 addition & 0 deletions bookmarks/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, query_stri
for term in query['search_terms']:
conditions = Q(title__icontains=term) \
| Q(description__icontains=term) \
| Q(notes__icontains=term) \
| Q(website_title__icontains=term) \
| Q(website_description__icontains=term) \
| Q(url__icontains=term)
Expand Down
1 change: 1 addition & 0 deletions bookmarks/services/bookmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def untag_bookmarks(bookmark_ids: [Union[int, str]], tag_string: str, current_us
def _merge_bookmark_data(from_bookmark: Bookmark, to_bookmark: Bookmark):
to_bookmark.title = from_bookmark.title
to_bookmark.description = from_bookmark.description
to_bookmark.notes = from_bookmark.notes
to_bookmark.unread = from_bookmark.unread
to_bookmark.shared = from_bookmark.shared

Expand Down
44 changes: 44 additions & 0 deletions bookmarks/static/bookmark_list.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
(function () {
function allowBulkEdit() {
return !!document.getElementById('bulk-edit-mode');
}

function setupBulkEdit() {
if (!allowBulkEdit()) {
return;
}

const bulkEditToggle = document.getElementById('bulk-edit-mode')
const bulkEditBar = document.querySelector('.bulk-edit-bar')
const singleToggles = document.querySelectorAll('.bulk-edit-toggle input')
Expand Down Expand Up @@ -64,6 +72,10 @@
}

function setupBulkEditTagAutoComplete() {
if (!allowBulkEdit()) {
return;
}

const wrapper = document.createElement('div');
const tagInput = document.getElementById('bulk-edit-tags-input');
const apiBaseUrl = document.documentElement.dataset.apiBaseUrl || '';
Expand Down Expand Up @@ -121,7 +133,39 @@
});
}

function setupNotes() {
// Shortcut for toggling all notes
document.addEventListener('keydown', function(event) {
// Filter for shortcut key
if (event.key !== 'e') return;
// Skip if event occurred within an input element
const targetNodeName = event.target.nodeName;
const isInputTarget = targetNodeName === 'INPUT'
|| targetNodeName === 'SELECT'
|| targetNodeName === 'TEXTAREA';

if (isInputTarget) return;

const list = document.querySelector('.bookmark-list');
list.classList.toggle('show-notes');
});

// Toggle notes for single bookmark
const bookmarks = document.querySelectorAll('.bookmark-list li');
bookmarks.forEach(bookmark => {
const toggleButton = bookmark.querySelector('.toggle-notes');
if (toggleButton) {
toggleButton.addEventListener('click', event => {
event.preventDefault();
event.stopPropagation();
bookmark.classList.toggle('show-notes');
});
}
});
}

setupBulkEdit();
setupBulkEditTagAutoComplete();
setupListNavigation();
setupNotes();
})()
6 changes: 6 additions & 0 deletions bookmarks/styles/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ a:visited:hover {
color: $link-color-dark;
}

code {
color: $gray-color-dark;
background-color: $code-bg-color;
box-shadow: 1px 1px 0 $code-shadow-color;
}

// Increase spacing between columns
.container > .columns > .column:not(:first-child) {
padding-left: 2rem;
Expand Down
Loading