1
0
mirror of https://github.com/aquatix/digimarks.git synced 2025-12-06 23:05:10 +01:00

6 Commits

11 changed files with 208 additions and 149 deletions

View File

@@ -0,0 +1 @@
"""Digimarks project."""

View File

@@ -1,3 +1,5 @@
"""Alembic environment file for SQLAlchemy."""
import asyncio import asyncio
from logging.config import fileConfig from logging.config import fileConfig
@@ -7,7 +9,7 @@ from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config from sqlalchemy.ext.asyncio import async_engine_from_config
from sqlmodel import SQLModel from sqlmodel import SQLModel
from src.digimarks.models import Bookmark, PublicTag, User from src.digimarks.models import Bookmark, PublicTag, User # noqa
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@@ -56,6 +58,7 @@ def run_migrations_offline() -> None:
def do_run_migrations(connection: Connection) -> None: def do_run_migrations(connection: Connection) -> None:
"""Run the migrations."""
context.configure( context.configure(
connection=connection, connection=connection,
target_metadata=target_metadata, target_metadata=target_metadata,

View File

@@ -1,15 +1,14 @@
"""Initial migration """Initial migration.
Revision ID: 115bcd2e1a38 Revision ID: 115bcd2e1a38
Revises: Revises:
Create Date: 2025-09-12 16:06:16.479075 Create Date: 2025-09-12 16:06:16.479075
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '115bcd2e1a38' revision: str = '115bcd2e1a38'
@@ -21,38 +20,41 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('bookmark', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), 'bookmark',
sa.Column('userkey', sa.String(length=255), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False), sa.Column('userkey', sa.String(length=255), nullable=False),
sa.Column('url', sa.String(length=255), nullable=False), sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('created_date', sa.DateTime(), nullable=False), sa.Column('url', sa.String(length=255), nullable=False),
sa.Column('url_hash', sa.String(length=255), nullable=False), sa.Column('created_date', sa.DateTime(), nullable=False),
sa.Column('tags', sa.String(length=255), nullable=False), sa.Column('url_hash', sa.String(length=255), nullable=False),
sa.Column('http_status', sa.Integer(), nullable=False), sa.Column('tags', sa.String(length=255), nullable=False),
sa.Column('modified_date', sa.DateTime(), nullable=True), sa.Column('http_status', sa.Integer(), nullable=False),
sa.Column('favicon', sa.String(length=255), nullable=True), sa.Column('modified_date', sa.DateTime(), nullable=True),
sa.Column('starred', sa.Boolean(), server_default=sa.text('0'), nullable=True), sa.Column('favicon', sa.String(length=255), nullable=True),
sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True), sa.Column('starred', sa.Boolean(), server_default=sa.text('0'), nullable=True),
sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True), sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True), sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True),
sa.PrimaryKeyConstraint('id') sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True),
sa.PrimaryKeyConstraint('id'),
) )
op.create_table('publictag', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), 'publictag',
sa.Column('tagkey', sa.String(length=255), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('userkey', sa.String(length=255), nullable=False), sa.Column('tagkey', sa.String(length=255), nullable=False),
sa.Column('tag', sa.String(length=255), nullable=False), sa.Column('userkey', sa.String(length=255), nullable=False),
sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True), sa.Column('tag', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('id') sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
sa.PrimaryKeyConstraint('id'),
) )
op.create_table('user', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), 'user',
sa.Column('username', sa.String(length=255), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(length=255), nullable=False), sa.Column('username', sa.String(length=255), nullable=False),
sa.Column('created_date', sa.DateTime(), nullable=False), sa.Column('key', sa.String(length=255), nullable=False),
sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True), sa.Column('created_date', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id') sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True),
sa.PrimaryKeyConstraint('id'),
) )
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -3,15 +3,14 @@
Revision ID: a8d8e45f60a1 Revision ID: a8d8e45f60a1
Revises: 115bcd2e1a38 Revises: 115bcd2e1a38
Create Date: 2025-09-12 16:10:41.378716 Create Date: 2025-09-12 16:10:41.378716
""" """
from datetime import UTC, datetime
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
from datetime import UTC, datetime
import sqlalchemy as sa import sqlalchemy as sa
import sqlmodel import sqlmodel
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'a8d8e45f60a1' revision: str = 'a8d8e45f60a1'
@@ -24,72 +23,74 @@ def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bookmark', schema=None) as batch_op: with op.batch_alter_table('bookmark', schema=None) as batch_op:
batch_op.alter_column('note', batch_op.alter_column(
existing_type=sa.TEXT(), 'note',
type_=sqlmodel.sql.sqltypes.AutoString(), existing_type=sa.TEXT(),
nullable=True, type_=sqlmodel.sql.sqltypes.AutoString(),
existing_server_default=sa.text('(null)')) nullable=True,
batch_op.alter_column('starred', existing_server_default=sa.text('(null)'),
existing_type=sa.BOOLEAN(), )
nullable=False, batch_op.alter_column(
existing_server_default=sa.text('0')) 'starred', existing_type=sa.BOOLEAN(), nullable=False, existing_server_default=sa.text('0')
batch_op.alter_column('modified_date', )
existing_type=sa.DATETIME(), batch_op.alter_column('modified_date', existing_type=sa.DATETIME(), nullable=True)
nullable=True) batch_op.alter_column(
batch_op.alter_column('deleted_date', 'deleted_date', existing_type=sa.DATETIME(), nullable=True, existing_server_default=sa.text('(null)')
existing_type=sa.DATETIME(), )
nullable=True, batch_op.alter_column(
existing_server_default=sa.text('(null)')) 'status', existing_type=sa.INTEGER(), nullable=False, existing_server_default=sa.text('0')
batch_op.alter_column('status', )
existing_type=sa.INTEGER(),
nullable=False,
existing_server_default=sa.text('0'))
batch_op.create_foreign_key('bookmark_user', 'user', ['userkey'], ['key']) batch_op.create_foreign_key('bookmark_user', 'user', ['userkey'], ['key'])
with op.batch_alter_table('publictag', schema=None) as batch_op: with op.batch_alter_table('publictag', schema=None) as batch_op:
batch_op.alter_column('created_date', batch_op.alter_column(
existing_type=sa.DATETIME(), 'created_date',
nullable=True, existing_type=sa.DATETIME(),
existing_server_default=sa.text(str(datetime.now(UTC)))) nullable=True,
existing_server_default=sa.text(str(datetime.now(UTC))),
)
batch_op.create_foreign_key('publictag_user', 'user', ['userkey'], ['key']) batch_op.create_foreign_key('publictag_user', 'user', ['userkey'], ['key'])
with op.batch_alter_table('user', schema=None) as batch_op: with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.alter_column('theme', batch_op.alter_column(
existing_type=sa.VARCHAR(length=20), 'theme', existing_type=sa.VARCHAR(length=20), nullable=False, existing_server_default=sa.text("'green'")
nullable=False, )
existing_server_default=sa.text("'green'"))
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema.""" """Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user', 'theme', op.alter_column(
existing_type=sa.VARCHAR(length=20), 'user', 'theme', existing_type=sa.VARCHAR(length=20), nullable=True, existing_server_default=sa.text("'green'")
nullable=True, )
existing_server_default=sa.text("'green'"))
op.drop_constraint(None, 'publictag', type_='foreignkey') op.drop_constraint(None, 'publictag', type_='foreignkey')
op.alter_column('publictag', 'created_date', op.alter_column(
existing_type=sa.DATETIME(), 'publictag',
nullable=True, 'created_date',
existing_server_default=sa.text('(null)')) existing_type=sa.DATETIME(),
nullable=True,
existing_server_default=sa.text('(null)'),
)
op.drop_constraint(None, 'bookmark', type_='foreignkey') op.drop_constraint(None, 'bookmark', type_='foreignkey')
op.alter_column('bookmark', 'status', op.alter_column(
existing_type=sa.INTEGER(), 'bookmark', 'status', existing_type=sa.INTEGER(), nullable=True, existing_server_default=sa.text('0')
nullable=True, )
existing_server_default=sa.text('0')) op.alter_column(
op.alter_column('bookmark', 'deleted_date', 'bookmark',
existing_type=sa.DATETIME(), 'deleted_date',
nullable=True, existing_type=sa.DATETIME(),
existing_server_default=sa.text('(null)')) nullable=True,
op.alter_column('bookmark', 'modified_date', existing_server_default=sa.text('(null)'),
existing_type=sa.DATETIME(), )
nullable=True) op.alter_column('bookmark', 'modified_date', existing_type=sa.DATETIME(), nullable=True)
op.alter_column('bookmark', 'starred', op.alter_column(
existing_type=sa.BOOLEAN(), 'bookmark', 'starred', existing_type=sa.BOOLEAN(), nullable=True, existing_server_default=sa.text('0')
nullable=True, )
existing_server_default=sa.text('0')) op.alter_column(
op.alter_column('bookmark', 'note', 'bookmark',
existing_type=sqlmodel.sql.sqltypes.AutoString(), 'note',
type_=sa.TEXT(), existing_type=sqlmodel.sql.sqltypes.AutoString(),
nullable=True, type_=sa.TEXT(),
existing_server_default=sa.text('(null)')) nullable=True,
existing_server_default=sa.text('(null)'),
)
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -1,16 +1,13 @@
"""Renamed keys """Renamed keys.
Revision ID: b8cbc6957df5 Revision ID: b8cbc6957df5
Revises: a8d8e45f60a1 Revises: a8d8e45f60a1
Create Date: 2025-09-12 22:26:38.684120 Create Date: 2025-09-12 22:26:38.684120
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'b8cbc6957df5' revision: str = 'b8cbc6957df5'

View File

@@ -0,0 +1 @@
"""digimarks main module."""

View File

@@ -0,0 +1,3 @@
"""Top-level package for Digimarks."""
__author__ = """Michiel Scholten"""

View File

@@ -34,8 +34,10 @@ class Settings(BaseSettings):
favicons_dir: DirectoryPath favicons_dir: DirectoryPath
# inside the codebase # inside the codebase
static_dir: DirectoryPath = 'static' # static_dir: DirectoryPath = Path('digimarks/static')
template_dir: DirectoryPath = 'templates' # template_dir: DirectoryPath = Path('digimarks/templates')
static_dir: DirectoryPath = 'digimarks/static'
template_dir: DirectoryPath = 'digimarks/templates'
media_url: str = '/static/' media_url: str = '/static/'

View File

@@ -30,6 +30,7 @@
--border-color: #d5d9d9; --border-color: #d5d9d9;
--border-width: 1px; --border-width: 1px;
--border-radius: 8px; --border-radius: 8px;
--chip-border-radius: 2rem;
--shadow-color: rgba(213, 217, 217, .5); --shadow-color: rgba(213, 217, 217, .5);
--global-theme-toggle-content: ' 🌞'; --global-theme-toggle-content: ' 🌞';
@@ -77,6 +78,7 @@ html[data-theme='nebula-dark'] {
--border-color: #333; --border-color: #333;
--border-width: 1px; --border-width: 1px;
--border-radius: 8px; --border-radius: 8px;
--chip-border-radius: 2rem;
--shadow-color: rgba(3, 3, 3, .5); --shadow-color: rgba(3, 3, 3, .5);
--global-theme-toggle-content: ' 🌝'; --global-theme-toggle-content: ' 🌝';
} }
@@ -99,6 +101,7 @@ html[data-theme='bbs'] {
--border-color: #333; --border-color: #333;
--border-width: 2px; --border-width: 2px;
--border-radius: 0; --border-radius: 0;
--chip-border-radius: 0;
--global-theme-toggle-content: ' 🖥️'; --global-theme-toggle-content: ' 🖥️';
} }
@@ -124,6 +127,7 @@ html[data-theme='silo'] {
/*--border-color: #003eaa;*/ /*--border-color: #003eaa;*/
--border-width: 2px; --border-width: 2px;
--border-radius: 0; --border-radius: 0;
--chip-border-radius: 0;
--global-theme-toggle-content: ' ⌨️'; --global-theme-toggle-content: ' ⌨️';
} }
@@ -229,6 +233,7 @@ ol li::marker, ul li::marker {
.active { .active {
background-color: var(--color-highlight); background-color: var(--color-highlight);
color: var(--text-color); color: var(--text-color);
transition-duration: 0.2s;
} }
/* Special button */ /* Special button */
@@ -254,6 +259,7 @@ button, .button, input, select, textarea {
-webkit-user-select: none; -webkit-user-select: none;
touch-action: manipulation; touch-action: manipulation;
vertical-align: middle; vertical-align: middle;
transition-duration: 0.2s;
} }
button, .button, input, select, textarea, table { button, .button, input, select, textarea, table {
@@ -267,6 +273,7 @@ button:hover, .button:hover {
/*background-color: #d57803;*/ /*background-color: #d57803;*/
background-color: var(--color-highlight); background-color: var(--color-highlight);
filter: brightness(80%); filter: brightness(80%);
transition-duration: 0.2s;
} }
button:focus, .button:focus { button:focus, .button:focus {
@@ -303,6 +310,22 @@ button:focus, .button:focus {
filter: brightness(80%); filter: brightness(80%);
} }
/* Toggle buttons */
.button-group {
display: inline-flex;
overflow: hidden;
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
}
.button-group button {
/* Reset borders because the buttons are mashed together and the group has its own border */
border: none;
border-radius: 0;
box-shadow: none !important;
}
/* Table */ /* Table */
th { th {
@@ -334,6 +357,7 @@ th, td {
[data-theme='nebula'] .card, [data-theme='nebula'] .card,
[data-theme='nebula'] button, [data-theme='nebula'] button,
[data-theme='nebula'] .button, [data-theme='nebula'] .button,
[data-theme='nebula'] .button-group,
[data-theme='nebula'] input, [data-theme='nebula'] input,
[data-theme='nebula'] select, [data-theme='nebula'] select,
[data-theme='nebula'] textarea, [data-theme='nebula'] textarea,
@@ -342,6 +366,7 @@ th, td {
[data-theme='nebula-dark'] .card, [data-theme='nebula-dark'] .card,
[data-theme='nebula-dark'] button, [data-theme='nebula-dark'] button,
[data-theme='nebula-dark'] .button, [data-theme='nebula-dark'] .button,
[data-theme='nebula-dark'] .button-group,
[data-theme='nebula-dark'] input, [data-theme='nebula-dark'] input,
[data-theme='nebula-dark'] select, [data-theme='nebula-dark'] select,
[data-theme='nebula-dark'] textarea, [data-theme='nebula-dark'] textarea,
@@ -419,7 +444,7 @@ th, td {
.chip { .chip {
font-size: .8rem; font-size: .8rem;
border-radius: var(--border-radius); border-radius: var(--chip-border-radius);
background-color: var(--background-color-secondary); background-color: var(--background-color-secondary);
color: var(--text-color-secondary); color: var(--text-color-secondary);
/*color: var(--text-color);*/ /*color: var(--text-color);*/
@@ -428,7 +453,7 @@ th, td {
} }
.chip .button { .chip .button {
border-radius: var(--border-radius); border-radius: var(--chip-border-radius);
} }
/* Status */ /* Status */

View File

@@ -18,6 +18,7 @@ document.addEventListener('alpine:init', () => {
/* Bookmark that is being edited, used to fill the form, etc. */ /* Bookmark that is being edited, used to fill the form, etc. */
bookmarkToEdit: Alpine.$persist({}).as('bookmarkToEdit'), bookmarkToEdit: Alpine.$persist({}).as('bookmarkToEdit'),
bookmarkToEditError: null, bookmarkToEditError: null,
bookmarkToEditVisible: false,
/* Loading indicator */ /* Loading indicator */
loading: false, loading: false,
@@ -147,6 +148,10 @@ document.addEventListener('alpine:init', () => {
) )
}, },
get filteredTags() { get filteredTags() {
if (this.cache[this.userKey].tags === undefined) {
console.log('Tags not yet cached');
return [];
}
/* Search in the list of all tags */ /* Search in the list of all tags */
return this.cache[this.userKey].tags.filter( return this.cache[this.userKey].tags.filter(
i => i.match(new RegExp(this.search, "i")) i => i.match(new RegExp(this.search, "i"))
@@ -214,7 +219,6 @@ document.addEventListener('alpine:init', () => {
'note': '', 'note': '',
'tags': '' 'tags': ''
} }
}, },
async startAddingBookmark() { async startAddingBookmark() {
/* Open 'add bookmark' page */ /* Open 'add bookmark' page */
@@ -222,6 +226,7 @@ document.addEventListener('alpine:init', () => {
this.resetEditBookmark(); this.resetEditBookmark();
// this.show_bookmark_details = true; // this.show_bookmark_details = true;
const editFormDialog = document.getElementById("editFormDialog"); const editFormDialog = document.getElementById("editFormDialog");
this.bookmarkToEditVisible = true;
editFormDialog.showModal(); editFormDialog.showModal();
}, },
async bookmarkURLChanged() { async bookmarkURLChanged() {
@@ -253,6 +258,7 @@ document.addEventListener('alpine:init', () => {
}, },
async saveBookmark() { async saveBookmark() {
console.log('Saving bookmark'); console.log('Saving bookmark');
// this.bookmarkToEditVisible = false;
// this.show_bookmark_details = false; // this.show_bookmark_details = false;
}, },
async addBookmark() { async addBookmark() {

View File

@@ -11,9 +11,14 @@
<ul> <ul>
<li><h1>digimarks</h1></li> <li><h1>digimarks</h1></li>
<li> <li>
<button x-data @click="$store.digimarks.toggleTagPage()" <div class="button-group">
:class="$store.digimarks.showTags && 'active'">tags <button x-data @click="$store.digimarks.toggleTagPage()"
</button> :class="!$store.digimarks.showTags && 'active'">bookmarks
</button>
<button x-data @click="$store.digimarks.toggleTagPage()"
:class="$store.digimarks.showTags && 'active'">tags
</button>
</div>
</li> </li>
<li> <li>
<button @click="$store.digimarks.startAddingBookmark()">add bookmark</button> <button @click="$store.digimarks.startAddingBookmark()">add bookmark</button>
@@ -32,6 +37,7 @@
<h1 x-bind:title="$store.digimarks.userKey">Bookmarks</h1> <h1 x-bind:title="$store.digimarks.userKey">Bookmarks</h1>
<p> <p>
<div class="button-group">
<button @click="$store.digimarks.sortAlphabetically()" <button @click="$store.digimarks.sortAlphabetically()"
:class="$store.digimarks.sortTitleAsc && 'active'">a-z &darr; :class="$store.digimarks.sortTitleAsc && 'active'">a-z &darr;
</button> </button>
@@ -44,9 +50,15 @@
<button @click="$store.digimarks.sortCreated('desc')" <button @click="$store.digimarks.sortCreated('desc')"
:class="$store.digimarks.sortCreatedDesc && 'active'">date &uarr; :class="$store.digimarks.sortCreatedDesc && 'active'">date &uarr;
</button> </button>
</div>
<div class="button-group">
<button @click="$store.digimarks.toggleListOrGrid()" <button @click="$store.digimarks.toggleListOrGrid()"
:class="$store.digimarks.showBookmarksCards && 'active'">list or grid :class="$store.digimarks.showBookmarksCards && 'active'">grid
</button> </button>
<button @click="$store.digimarks.toggleListOrGrid()"
:class="!$store.digimarks.showBookmarksCards && 'active'">list
</button>
</div>
</p> </p>
<table x-cloak x-show="$store.digimarks.showBookmarksList"> <table x-cloak x-show="$store.digimarks.showBookmarksList">
@@ -98,15 +110,18 @@
<div class="card-thumb" x-show="bookmark.favicon"><img <div class="card-thumb" x-show="bookmark.favicon"><img
x-bind:src="'/content/favicons/' + bookmark.favicon"></div> x-bind:src="'/content/favicons/' + bookmark.favicon"></div>
<div class="statuses"> <div class="statuses">
<div x-show="bookmark.starred" class="star"><i class="fa-fw fa-solid fa-star"></i> <div x-show="bookmark.starred" class="star"><i
class="fa-fw fa-solid fa-star"></i>
</div> </div>
<div x-show="bookmark.http_status !== 200 && bookmark.http_status !== 304" <div x-show="bookmark.http_status !== 200 && bookmark.http_status !== 304"
class="error"><i class="error"><i
class="fa-fw fa-solid fa-triangle-exclamation"></i> class="fa-fw fa-solid fa-triangle-exclamation"></i>
</div> </div>
<div x-show="bookmark.note"><i class="fa-fw fa-regular fa-note-sticky"></i></div> <div x-show="bookmark.note"><i class="fa-fw fa-regular fa-note-sticky"></i>
</div>
</div>
<div><a x-text="bookmark.title" x-bind:href="bookmark.url" target="_blank"></a>
</div> </div>
<div><a x-text="bookmark.title" x-bind:href="bookmark.url" target="_blank"></a></div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<button title="show actions"><i class="fa-solid fa-square-caret-down"></i></button> <button title="show actions"><i class="fa-solid fa-square-caret-down"></i></button>
@@ -170,43 +185,46 @@
</span> </span>
</div> </div>
#} #}
<form method="dialog" id="bookmarkEditForm" x-if="$store.digimarks.bookmarkToEdit"> <template x-if="$store.digimarks.bookmarkToEditVisible">
<fieldset class="form-group"> <form method="dialog" id="bookmarkEditForm">
<label for="bookmark_url">URL</label> <fieldset class="form-group">
<input id="bookmark_url" type="text" name="bookmark_url" placeholder="url" <label for="bookmark_url">URL</label>
x-on:change.debounce="$store.digimarks.bookmarkURLChanged()" <input id="bookmark_url" type="text" name="bookmark_url" placeholder="url"
x-model="$store.digimarks.bookmarkToEdit.url"> x-on:change.debounce="$store.digimarks.bookmarkURLChanged()"
</fieldset> x-model="$store.digimarks.bookmarkToEdit.url">
<fieldset class="form-group"> </fieldset>
<label for="bookmark_title">Title</label> <fieldset class="form-group">
<input id="bookmark_title" type="text" name="bookmark_title" <label for="bookmark_title">Title</label>
placeholder="title (leave empty for autofetch)" <input id="bookmark_title" type="text" name="bookmark_title"
x-model="$store.digimarks.bookmarkToEdit.title"> placeholder="title (leave empty for autofetch)"
</fieldset> x-model="$store.digimarks.bookmarkToEdit.title">
<fieldset class="form-group"> </fieldset>
<label for="bookmark_note">Note</label> <fieldset class="form-group">
<textarea id="bookmark_note" type="text" name="bookmark_note" <label for="bookmark_note">Note</label>
x-model="$store.digimarks.bookmarkToEdit.note"> <textarea id="bookmark_note" type="text" name="bookmark_note"
x-model="$store.digimarks.bookmarkToEdit.note">
</textarea> </textarea>
</fieldset> </fieldset>
<fieldset class="form-group"> <fieldset class="form-group">
<label for="bookmark_tags">Tags</label> <label for="bookmark_tags">Tags</label>
<input id="bookmark_tags" type="text" name="bookmark_tags" <input id="bookmark_tags" type="text" name="bookmark_tags"
placeholder="tags, divided bij comma's" placeholder="tags, divided bij comma's"
x-model="$store.digimarks.bookmarkToEdit.tags"> x-model="$store.digimarks.bookmarkToEdit.tags">
</fieldset> </fieldset>
<p x-show="$store.digimarks.bookmarkToEditError" x-data="$store.digimarks.bookmarkToEditError"></p> <p x-show="$store.digimarks.bookmarkToEditError"
<p> x-data="$store.digimarks.bookmarkToEditError"></p>
<label> <p>
<input type="checkbox" name="strip" id="strip"/> <label>
<span>Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</span> <input type="checkbox" name="strip" id="strip"/>
</label> <span>Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</span>
</p> </label>
<div> </p>
<button value="cancel">Cancel</button> <div>
<button @click="$store.digimarks.saveBookmark()">Save</button> <button value="cancel">Cancel</button>
</div> <button @click="$store.digimarks.saveBookmark()">Save</button>
</form> </div>
</form>
</template>
</dialog> </dialog>
</main> </main>