mirror of
https://github.com/aquatix/digimarks.git
synced 2025-12-07 03:45:10 +01:00
Compare commits
11 Commits
bf6cd081f9
...
fastapi
| Author | SHA1 | Date | |
|---|---|---|---|
| 79be98abea | |||
| a7498a2fba | |||
| 8810a47faa | |||
| cae9ebf3ef | |||
| 5eb9c606f0 | |||
| 894f97a25e | |||
| 8ccb18839f | |||
| dda209fa96 | |||
| dcac963fa6 | |||
| da28f2f781 | |||
| 987a030c4f |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -7,13 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Sorting of bookmarks
|
|
||||||
- Sort by title
|
|
||||||
- Sort by date
|
|
||||||
- Logging of actions
|
|
||||||
- Add new way of authentication and editing bookmark collections:
|
- Add new way of authentication and editing bookmark collections:
|
||||||
https://github.com/aquatix/digimarks/issues/8 and https://github.com/aquatix/digimarks/issues/9
|
https://github.com/aquatix/digimarks/issues/8 and https://github.com/aquatix/digimarks/issues/9
|
||||||
- Change adding tags to use the MaterializeCSS tags: https://materializecss.com/chips.html
|
- Change adding tags to use ~~the MaterializeCSS tags: https://materializecss.com/chips.html~~ a nice tags lib/styling
|
||||||
- Do calls to the API endpoint of an existing bookmark when editing properties
|
- Do calls to the API endpoint of an existing bookmark when editing properties
|
||||||
(for example to update tags, title and such, also to already suggest title)
|
(for example to update tags, title and such, also to already suggest title)
|
||||||
- Look into compatibility with del.icio.us, so we can make use of existing browser integration
|
- Look into compatibility with del.icio.us, so we can make use of existing browser integration
|
||||||
@@ -23,7 +19,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Settings through Pydantic Settings
|
- Settings now work through Pydantic Settings
|
||||||
|
- New UI theme(s) through digui
|
||||||
|
- Caching of the bookmarks, tags and more in the browser, for fast filtering and lookups
|
||||||
|
- Sorting of bookmarks
|
||||||
|
- Sort by title
|
||||||
|
- Sort by date
|
||||||
|
- Logging of actions
|
||||||
|
- Recognise when a url already is in the list of known bookmarks and fill in the form with already-known data
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Moved from Flask to FastAPI
|
- Moved from Flask to FastAPI
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ necessary packages:
|
|||||||
mkvirtualenv digimarks # or whatever project you are working on
|
mkvirtualenv digimarks # or whatever project you are working on
|
||||||
# If you just want to run it, no need for development dependencies
|
# If you just want to run it, no need for development dependencies
|
||||||
uv sync --active --no-dev
|
uv sync --active --no-dev
|
||||||
# Otherwise, install everything
|
# Otherwise, install everything in the active virtualenv
|
||||||
uv sync --active
|
uv sync --active
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Digimarks project."""
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,7 +20,8 @@ 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(
|
||||||
|
'bookmark',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('userkey', sa.String(length=255), nullable=False),
|
sa.Column('userkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('title', sa.String(length=255), nullable=False),
|
sa.Column('title', sa.String(length=255), nullable=False),
|
||||||
@@ -36,23 +36,25 @@ def upgrade() -> None:
|
|||||||
sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
||||||
sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True),
|
sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True),
|
||||||
sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
op.create_table('publictag',
|
op.create_table(
|
||||||
|
'publictag',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('tagkey', sa.String(length=255), nullable=False),
|
sa.Column('tagkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('userkey', sa.String(length=255), nullable=False),
|
sa.Column('userkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('tag', sa.String(length=255), nullable=False),
|
sa.Column('tag', sa.String(length=255), nullable=False),
|
||||||
sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
op.create_table('user',
|
op.create_table(
|
||||||
|
'user',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('username', sa.String(length=255), nullable=False),
|
sa.Column('username', sa.String(length=255), nullable=False),
|
||||||
sa.Column('key', sa.String(length=255), nullable=False),
|
sa.Column('key', sa.String(length=255), nullable=False),
|
||||||
sa.Column('created_date', sa.DateTime(), nullable=False),
|
sa.Column('created_date', sa.DateTime(), nullable=False),
|
||||||
sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True),
|
sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
'note',
|
||||||
existing_type=sa.TEXT(),
|
existing_type=sa.TEXT(),
|
||||||
type_=sqlmodel.sql.sqltypes.AutoString(),
|
type_=sqlmodel.sql.sqltypes.AutoString(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
existing_server_default=sa.text('(null)'))
|
existing_server_default=sa.text('(null)'),
|
||||||
batch_op.alter_column('starred',
|
)
|
||||||
existing_type=sa.BOOLEAN(),
|
batch_op.alter_column(
|
||||||
nullable=False,
|
'starred', existing_type=sa.BOOLEAN(), nullable=False, existing_server_default=sa.text('0')
|
||||||
existing_server_default=sa.text('0'))
|
)
|
||||||
batch_op.alter_column('modified_date',
|
batch_op.alter_column('modified_date', existing_type=sa.DATETIME(), nullable=True)
|
||||||
existing_type=sa.DATETIME(),
|
batch_op.alter_column(
|
||||||
nullable=True)
|
'deleted_date', existing_type=sa.DATETIME(), nullable=True, existing_server_default=sa.text('(null)')
|
||||||
batch_op.alter_column('deleted_date',
|
)
|
||||||
existing_type=sa.DATETIME(),
|
batch_op.alter_column(
|
||||||
nullable=True,
|
'status', existing_type=sa.INTEGER(), nullable=False, existing_server_default=sa.text('0')
|
||||||
existing_server_default=sa.text('(null)'))
|
)
|
||||||
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(
|
||||||
|
'created_date',
|
||||||
existing_type=sa.DATETIME(),
|
existing_type=sa.DATETIME(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
existing_server_default=sa.text(str(datetime.now(UTC))))
|
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(
|
||||||
|
'publictag',
|
||||||
|
'created_date',
|
||||||
existing_type=sa.DATETIME(),
|
existing_type=sa.DATETIME(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
existing_server_default=sa.text('(null)'))
|
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',
|
||||||
|
'deleted_date',
|
||||||
existing_type=sa.DATETIME(),
|
existing_type=sa.DATETIME(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
existing_server_default=sa.text('(null)'))
|
existing_server_default=sa.text('(null)'),
|
||||||
op.alter_column('bookmark', 'modified_date',
|
)
|
||||||
existing_type=sa.DATETIME(),
|
op.alter_column('bookmark', 'modified_date', existing_type=sa.DATETIME(), nullable=True)
|
||||||
nullable=True)
|
op.alter_column(
|
||||||
op.alter_column('bookmark', 'starred',
|
'bookmark', 'starred', existing_type=sa.BOOLEAN(), nullable=True, existing_server_default=sa.text('0')
|
||||||
existing_type=sa.BOOLEAN(),
|
)
|
||||||
nullable=True,
|
op.alter_column(
|
||||||
existing_server_default=sa.text('0'))
|
'bookmark',
|
||||||
op.alter_column('bookmark', 'note',
|
'note',
|
||||||
existing_type=sqlmodel.sql.sqltypes.AutoString(),
|
existing_type=sqlmodel.sql.sqltypes.AutoString(),
|
||||||
type_=sa.TEXT(),
|
type_=sa.TEXT(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
existing_server_default=sa.text('(null)'))
|
existing_server_default=sa.text('(null)'),
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
1
src/__init__.py
Normal file
1
src/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""digimarks main module."""
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""Top-level package for Digimarks."""
|
||||||
|
|
||||||
|
__author__ = """Michiel Scholten"""
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ from urllib.parse import urlparse, urlunparse
|
|||||||
|
|
||||||
import bs4
|
import bs4
|
||||||
import httpx
|
import httpx
|
||||||
import tags_service
|
|
||||||
import utils
|
|
||||||
from exceptions import BookmarkNotFound
|
|
||||||
from extract_favicon import from_html
|
from extract_favicon import from_html
|
||||||
from fastapi import Query, Request
|
from fastapi import Query, Request
|
||||||
from models import Bookmark, Visibility
|
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
|
from digimarks import tags_service, utils
|
||||||
|
from digimarks.exceptions import BookmarkNotFound
|
||||||
|
from digimarks.models import Bookmark, Visibility
|
||||||
|
|
||||||
DIGIMARKS_USER_AGENT = 'digimarks/2.0.0-dev'
|
DIGIMARKS_USER_AGENT = 'digimarks/2.0.0-dev'
|
||||||
|
|
||||||
logger = logging.getLogger('digimarks')
|
logger = logging.getLogger('digimarks')
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ from contextlib import asynccontextmanager
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
import bookmarks_service
|
|
||||||
import httpx
|
import httpx
|
||||||
import tags_service
|
|
||||||
from exceptions import BookmarkNotFound
|
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Query, Request
|
from fastapi import Depends, FastAPI, HTTPException, Query, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from models import DEFAULT_THEME, Bookmark, User, Visibility
|
|
||||||
from pydantic import DirectoryPath, FilePath
|
from pydantic import DirectoryPath, FilePath
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
@@ -23,6 +19,10 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from sqlmodel import desc, select
|
from sqlmodel import desc, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
from digimarks import bookmarks_service, tags_service
|
||||||
|
from digimarks.exceptions import BookmarkNotFound
|
||||||
|
from digimarks.models import DEFAULT_THEME, Bookmark, User, Visibility
|
||||||
|
|
||||||
DIGIMARKS_VERSION = '2.0.0a1'
|
DIGIMARKS_VERSION = '2.0.0a1'
|
||||||
|
|
||||||
|
|
||||||
@@ -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/'
|
||||||
|
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ document.addEventListener('alpine:init', () => {
|
|||||||
showBookmarksCards: Alpine.$persist(false).as('showBookmarksCards'),
|
showBookmarksCards: Alpine.$persist(false).as('showBookmarksCards'),
|
||||||
showTags: Alpine.$persist(false).as('showTags'),
|
showTags: Alpine.$persist(false).as('showTags'),
|
||||||
/* 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(null).as('bookmarkToEdit'),
|
bookmarkToEdit: Alpine.$persist({}).as('bookmarkToEdit'),
|
||||||
bookmarkToEditError: null,
|
bookmarkToEditError: null,
|
||||||
|
bookmarkToEditVisible: false,
|
||||||
|
|
||||||
/* Loading indicator */
|
/* Loading indicator */
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -39,6 +40,9 @@ document.addEventListener('alpine:init', () => {
|
|||||||
/** Initialise the application after loading */
|
/** Initialise the application after loading */
|
||||||
document.documentElement.setAttribute('data-theme', this.theme);
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
console.log('Set theme', this.theme);
|
console.log('Set theme', this.theme);
|
||||||
|
|
||||||
|
/* Make sure the edit/add bookmark form has a fresh empty object */
|
||||||
|
this.resetEditBookmark();
|
||||||
/* Bookmarks are refreshed through the getBookmarks() call in the HTML page */
|
/* Bookmarks are refreshed through the getBookmarks() call in the HTML page */
|
||||||
/* await this.getBookmarks(); */
|
/* await this.getBookmarks(); */
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -144,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"))
|
||||||
@@ -204,17 +212,21 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.showBookmarksCards = !this.showBookmarksList;
|
this.showBookmarksCards = !this.showBookmarksList;
|
||||||
},
|
},
|
||||||
|
|
||||||
async startAddingBookmark() {
|
resetEditBookmark() {
|
||||||
/* Open 'add bookmark' page */
|
|
||||||
console.log('Start adding bookmark');
|
|
||||||
this.bookmarkToEdit = {
|
this.bookmarkToEdit = {
|
||||||
'url': '',
|
'url': '',
|
||||||
'title': '',
|
'title': '',
|
||||||
'note': '',
|
'note': '',
|
||||||
'tags': ''
|
'tags': ''
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async startAddingBookmark() {
|
||||||
|
/* Open 'add bookmark' page */
|
||||||
|
console.log('Start adding bookmark');
|
||||||
|
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() {
|
||||||
@@ -246,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() {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"""Helper functions for tags used with Bookmark models."""
|
"""Helper functions for tags used with Bookmark models."""
|
||||||
|
|
||||||
from models import Bookmark, Visibility
|
|
||||||
from sqlalchemy import Sequence
|
from sqlalchemy import Sequence
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
|
|
||||||
|
from digimarks.models import Bookmark, Visibility
|
||||||
|
|
||||||
|
|
||||||
def i_filter_false(predicate, iterable):
|
def i_filter_false(predicate, iterable):
|
||||||
"""Filter an iterable if predicate returns True.
|
"""Filter an iterable if predicate returns True.
|
||||||
|
|||||||
@@ -11,9 +11,14 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><h1>digimarks</h1></li>
|
<li><h1>digimarks</h1></li>
|
||||||
<li>
|
<li>
|
||||||
|
<div class="button-group">
|
||||||
|
<button x-data @click="$store.digimarks.toggleTagPage()"
|
||||||
|
:class="!$store.digimarks.showTags && 'active'">bookmarks
|
||||||
|
</button>
|
||||||
<button x-data @click="$store.digimarks.toggleTagPage()"
|
<button x-data @click="$store.digimarks.toggleTagPage()"
|
||||||
:class="$store.digimarks.showTags && 'active'">tags
|
:class="$store.digimarks.showTags && 'active'">tags
|
||||||
</button>
|
</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 ↓
|
:class="$store.digimarks.sortTitleAsc && 'active'">a-z ↓
|
||||||
</button>
|
</button>
|
||||||
@@ -44,9 +50,15 @@
|
|||||||
<button @click="$store.digimarks.sortCreated('desc')"
|
<button @click="$store.digimarks.sortCreated('desc')"
|
||||||
:class="$store.digimarks.sortCreatedDesc && 'active'">date ↑
|
:class="$store.digimarks.sortCreatedDesc && 'active'">date ↑
|
||||||
</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,6 +185,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
#}
|
#}
|
||||||
|
<template x-if="$store.digimarks.bookmarkToEditVisible">
|
||||||
<form method="dialog" id="bookmarkEditForm">
|
<form method="dialog" id="bookmarkEditForm">
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<label for="bookmark_url">URL</label>
|
<label for="bookmark_url">URL</label>
|
||||||
@@ -195,7 +211,8 @@
|
|||||||
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"
|
||||||
|
x-data="$store.digimarks.bookmarkToEditError"></p>
|
||||||
<p>
|
<p>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="strip" id="strip"/>
|
<input type="checkbox" name="strip" id="strip"/>
|
||||||
@@ -207,6 +224,7 @@
|
|||||||
<button @click="$store.digimarks.saveBookmark()">Save</button>
|
<button @click="$store.digimarks.saveBookmark()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</template>
|
||||||
</dialog>
|
</dialog>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user