Various CSS improvements (#514)

* Replace flexbox grid with CSS grid

* Update new and edit forms

* Update settings views

* Update auth views

* Fix margin in menu

* Remove unused Spectre modules

* Simplify navbar

* Reuse CSS variables

* Fix grid gap on small screen sizes

* Simplify grid system

* Improve section headers

* Restructure SASS files

* Cleanup base styles

* Update test
This commit is contained in:
Sascha Ißbrücker 2023-08-24 14:46:47 +02:00 committed by GitHub
parent 768f1346a3
commit bca9bf9b11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 571 additions and 473 deletions

View file

@ -210,11 +210,7 @@
{#each suggestions.tags as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
{suggestion.label}
</a>
</li>
{/each}
@ -225,11 +221,7 @@
{#each suggestions.search as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
{suggestion.label}
</a>
</li>
{/each}
@ -240,11 +232,7 @@
{#each suggestions.bookmarks as suggestion}
<li class="menu-item" class:selected={selectedIndex === suggestion.index}>
<a href="#" on:mousedown|preventDefault={() => completeSuggestion(suggestion)}>
<div class="tile tile-centered">
<div class="tile-content">
{suggestion.label}
</div>
</div>
{suggestion.label}
</a>
</li>
{/each}

View file

@ -131,11 +131,7 @@
{#each suggestions as tag,i}
<li class="menu-item" class:selected={selectedIndex === i}>
<a href="#" on:mousedown|preventDefault={() => complete(tag)}>
<div class="tile tile-centered">
<div class="tile-content">
{tag.name}
</div>
</div>
{tag.name}
</a>
</li>
{/each}

View file

@ -1,6 +0,0 @@
.auth-page {
> .columns {
align-items: center;
justify-content: center;
}
}

View file

@ -1,14 +1,29 @@
/* Main layout */
body {
margin: 20px 10px;
@media (min-width: $size-sm) {
// High horizontal padding accounts for checkboxes that show up in bulk edit mode
margin: 20px 24px;
// Horizontal padding accounts for checkboxes that show up in bulk edit mode
margin: 20px 32px;
}
}
header {
margin-bottom: 40px;
margin-bottom: $unit-10;
.logo {
width: 28px;
height: 28px;
}
a:hover {
text-decoration: none;
}
h1 {
margin: 0 0 0 $unit-3;
font-size: $font-size-lg;
}
}
header .toasts {
@ -23,97 +38,81 @@ header .toasts {
}
}
.navbar {
/* Shared components */
.navbar-brand {
// Content area component
section.content-area {
h2 {
font-size: $font-size-lg;
}
.content-area-header {
border-bottom: solid 1px $border-color;
display: flex;
align-items: center;
flex-wrap: wrap;
padding-bottom: $unit-2;
margin-bottom: $unit-4;
.logo {
width: 28px;
height: 28px;
h2 {
line-height: 1.8rem;
margin-right: auto;
margin-bottom: 0;
}
h1 {
text-transform: uppercase;
display: inline-block;
margin: 0 0 0 8px;
}
}
.dropdown-toggle {
}
}
/* Overrides */
// Reduce heading sizes
h1 {
font-size: inherit;
// Confirm button component
span.confirmation {
display: flex;
align-items: baseline;
}
h2 {
font-size: .85rem;
span.confirmation .btn.btn-link {
color: $error-color !important;
&:hover {
text-decoration: underline;
}
}
/* Additional utilities */
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text-sm {
font-size: 0.7rem;
}
.text-gray-dark {
color: $gray-color-dark;
}
// Fix up visited styles
a:visited {
color: $link-color;
}
a:visited:hover {
color: $link-color-dark;
}
.btn-link:visited:not(.btn-primary) {
color: $link-color;
}
.btn-link:visited:not(.btn-primary):hover {
color: $link-color-dark;
.align-baseline {
align-items: baseline;
}
code {
color: $gray-color-dark;
background-color: $code-bg-color;
box-shadow: 1px 1px 0 $code-shadow-color;
.align-center {
align-items: center;
}
// Increase spacing between columns
.container > .columns > .column:not(:first-child) {
padding-left: 2rem;
.justify-between {
justify-content: space-between;
}
// Remove left padding from first pagination link
.pagination .page-item:first-child a {
padding-left: 0;
.mb-4 {
margin-bottom: $unit-4;
}
// Override border color for tab block
.tab-block {
border-bottom: solid 1px $border-color;
.mx-auto {
margin-left: auto;
margin-right: auto;
}
// Form auto-complete menu
.form-autocomplete .menu {
.menu-item.selected > a, .menu-item > a:hover {
background: $secondary-color;
color: $primary-color;
}
.group-item, .group-item:hover {
color: $gray-color;
text-transform: uppercase;
background: none;
font-size: 0.6rem;
font-weight: bold;
}
}
// Increase input font size on small viewports to prevent zooming on focus the input
// on mobile devices. 430px relates to the "normalized" iPhone 14 Pro Max
// viewport size
@media screen and (max-width: 430px) {
.form-input {
font-size: 16px;
}
.btn.btn-wide {
padding-left: $unit-6;
padding-right: $unit-6;
}

View file

@ -0,0 +1,50 @@
.bookmarks-form {
.btn.form-icon {
padding: 0;
width: 20px;
height: 20px;
visibility: hidden;
color: $gray-color;
&:focus,
&:hover,
&:active,
&.active {
color: $gray-color-dark;
}
> svg {
width: 20px;
height: 20px;
}
}
.has-icon-right > input, .has-icon-right > textarea {
padding-right: 30px;
}
.has-icon-right > input:placeholder-shown ~ .btn.form-icon,
.has-icon-right > textarea:placeholder-shown ~ .btn.form-icon {
visibility: visible;
}
.form-icon.loading {
visibility: hidden;
}
.form-input-hint.bookmark-exists {
display: none;
color: $warning-color;
a {
color: $warning-color;
text-decoration: underline;
font-weight: bold;
}
}
details.notes textarea {
box-sizing: border-box;
}
}

View file

@ -1,3 +1,7 @@
.bookmarks-page.grid {
grid-gap: $unit-10;
}
/* Bookmark search box */
.bookmarks-page .search {
$searchbox-width: 180px;
@ -65,6 +69,7 @@ li[ld-bookmark-item] {
.title img {
width: 16px;
height: 16px;
margin-right: $unit-h;
vertical-align: text-top;
}
@ -84,7 +89,7 @@ li[ld-bookmark-item] {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 0.4rem;
gap: $unit-2;
}
.actions {
@ -113,19 +118,19 @@ li[ld-bookmark-item] {
align-self: center;
display: flex;
align-items: center;
gap: 0.1rem;
gap: $unit-h;
}
}
}
.bookmark-pagination {
margin-top: 1rem;
margin-top: $unit-4;
}
.tag-cloud {
.selected-tags {
margin-bottom: 0.8rem;
margin-bottom: $unit-4;
a, a:visited:hover {
color: $error-color;
@ -139,7 +144,7 @@ li[ld-bookmark-item] {
}
.group {
margin-bottom: 0.4rem;
margin-bottom: $unit-2;
}
.highlight-char {
@ -149,63 +154,12 @@ li[ld-bookmark-item] {
}
}
.bookmarks-form {
.btn.form-icon {
padding: 0;
width: 20px;
height: 20px;
visibility: hidden;
color: $gray-color;
&:focus,
&:hover,
&:active,
&.active {
color: $gray-color-dark;
}
> svg {
width: 20px;
height: 20px;
}
}
.has-icon-right > input, .has-icon-right > textarea {
padding-right: 30px;
}
.has-icon-right > input:placeholder-shown ~ .btn.form-icon,
.has-icon-right > textarea:placeholder-shown ~ .btn.form-icon {
visibility: visible;
}
.form-icon.loading {
visibility: hidden;
}
.form-input-hint.bookmark-exists {
display: none;
color: $warning-color;
a {
color: $warning-color;
text-decoration: underline;
font-weight: bold;
}
}
details.notes textarea {
box-sizing: border-box;
}
}
/* Bookmark notes */
ul.bookmark-list {
.notes {
display: none;
max-height: 300px;
margin: 4px 0;
margin: $unit-1 0;
overflow: auto;
}
@ -218,11 +172,11 @@ ul.bookmark-list {
/* Bookmark notes markdown styles */
ul.bookmark-list .notes-content {
& {
padding: 0.4rem 0.6rem;
padding: $unit-2 $unit-3;
}
p, ul, ol, pre, blockquote {
margin: 0 0 0.4rem 0;
margin: 0 0 $unit-2 0;
}
> *:first-child {
@ -234,17 +188,17 @@ ul.bookmark-list .notes-content {
}
ul, ol {
margin-left: 0.8rem;
margin-left: $unit-4;
}
ul li, ol li {
margin-top: 0.2rem;
margin-top: $unit-1;
}
pre {
padding: 0.2rem 0.4rem;
padding: $unit-1 $unit-2;
background-color: $code-bg-color;
border-radius: 0.2rem;
border-radius: $unit-1;
}
pre code {
@ -268,9 +222,9 @@ $bulk-edit-transition-duration: 400ms;
[ld-bulk-edit] {
.bulk-edit-bar {
margin-top: -17px;
margin-bottom: 16px;
margin-top: -1px;
margin-left: -$bulk-edit-bar-offset;
margin-bottom: $unit-4;
max-height: 0;
overflow: hidden;
transition: max-height $bulk-edit-transition-duration;
@ -309,7 +263,7 @@ $bulk-edit-transition-duration: 400ms;
transition: all $bulk-edit-transition-duration;
.form-icon {
top: 0.2rem;
top: $unit-1;
}
}
@ -322,9 +276,9 @@ $bulk-edit-transition-duration: 400ms;
.bulk-edit-actions {
display: flex;
align-items: baseline;
padding: 4px 0;
padding: $unit-1 0;
border-top: solid 1px $border-color;
gap: 8px;
gap: $unit-2;
button {
padding: 0 !important;

View file

@ -1,32 +0,0 @@
/* Dark theme overrides */
/* Buttons */
.btn.btn-primary {
background: $dt-primary-button-color;
border-color: darken($dt-primary-button-color, 5%);
&:hover, &:active, &:focus {
background: darken($dt-primary-button-color, 5%);
border-color: darken($dt-primary-button-color, 10%);
}
}
/* Focus ring*/
a:focus, .btn:focus {
box-shadow: 0 0 0 .1rem rgba($primary-color, .5);
}
/* Forms */
.has-error .form-input, .form-input.is-error, .has-error .form-select, .form-select.is-error {
background: darken($error-color, 40%);
}
.form-checkbox input:checked + .form-icon, .form-radio input:checked + .form-icon, .form-switch input:checked + .form-icon {
background: $dt-primary-button-color;
border-color: $dt-primary-button-color;
}
/* Pagination */
.pagination .page-item.active a {
background: $dt-primary-button-color;
}

View file

@ -0,0 +1,108 @@
.container {
margin-left: auto;
margin-right: auto;
width: 100%;
max-width: $size-lg;
}
.show-sm,
.show-md {
display: none !important;
}
.width-25 {
width: 25%;
}
.width-50 {
width: 50%;
}
.width-75 {
width: 75%;
}
.width-100 {
width: 100%;
}
.grid {
--grid-columns: 3;
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
grid-gap: $unit-4;
}
.grid > * {
min-width: 0;
}
.col-1 {
grid-column: unquote("span min(1, var(--grid-columns))");
}
.col-2 {
grid-column: unquote("span min(2, var(--grid-columns))");
}
.col-3 {
grid-column: unquote("span min(3, var(--grid-columns))");
}
@media (max-width: $size-md) {
.hide-md {
display: none !important;
}
.show-md {
display: block !important;
}
.width-md-25 {
width: 25%;
}
.width-md-50 {
width: 50%;
}
.width-md-75 {
width: 75%;
}
.width-md-100 {
width: 100%;
}
.columns-md-1 {
--grid-columns: 1;
}
.columns-md-2 {
--grid-columns: 2;
}
}
@media (max-width: $size-sm) {
.hide-sm {
display: none !important;
}
.show-sm {
display: block !important;
}
.width-sm-25 {
width: 25%;
}
.width-sm-50 {
width: 50%;
}
.width-sm-75 {
width: 75%;
}
.width-sm-100 {
width: 100%;
}
.columns-sm-1 {
--grid-columns: 1;
}
.columns-sm-2 {
--grid-columns: 2;
}
}

View file

@ -1,10 +1,9 @@
.settings-page {
section.content-area {
margin-bottom: 2rem;
margin-bottom: $unit-12;
h2 {
font-size: 1.0rem;
margin-bottom: 0.8rem;
margin-bottom: $unit-4;
}
}

View file

@ -1,28 +0,0 @@
// Content area component
section.content-area {
.content-area-header {
border-bottom: solid 1px $border-color;
display: flex;
flex-direction: row;
margin-bottom: 16px;
h2 {
line-height: 1.8rem;
}
}
}
// Confirm button component
span.confirmation {
display: flex;
align-items: baseline;
}
span.confirmation .btn.btn-link {
color: $error-color !important;
&:hover {
text-decoration: underline;
}
}

View file

@ -0,0 +1,110 @@
// Customized Spectre CSS imports, removing modules that are not used
// See node_modules/spectre.css/src/spectre.scss for the original version
// Variables and mixins
@import "../../node_modules/spectre.css/src/variables";
@import "../../node_modules/spectre.css/src/mixins";
/*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */
// Reset and dependencies
@import "../../node_modules/spectre.css/src/normalize";
@import "../../node_modules/spectre.css/src/base";
// Elements
@import "../../node_modules/spectre.css/src/typography";
@import "../../node_modules/spectre.css/src/asian";
@import "../../node_modules/spectre.css/src/tables";
@import "../../node_modules/spectre.css/src/buttons";
@import "../../node_modules/spectre.css/src/forms";
@import "../../node_modules/spectre.css/src/labels";
@import "../../node_modules/spectre.css/src/codes";
@import "../../node_modules/spectre.css/src/media";
// Components
@import "../../node_modules/spectre.css/src/dropdowns";
@import "../../node_modules/spectre.css/src/empty";
@import "../../node_modules/spectre.css/src/menus";
@import "../../node_modules/spectre.css/src/pagination";
@import "../../node_modules/spectre.css/src/tabs";
@import "../../node_modules/spectre.css/src/toasts";
@import "../../node_modules/spectre.css/src/tooltips";
// Utility classes
@import "../../node_modules/spectre.css/src/animations";
@import "../../node_modules/spectre.css/src/utilities";
// Auto-complete component
@import "../../node_modules/spectre.css/src/autocomplete";
/* Spectre overrides / fixes */
// Fix up visited styles
a:visited {
color: $link-color;
}
a:visited:hover {
color: $link-color-dark;
}
.btn-link:visited:not(.btn-primary) {
color: $link-color;
}
.btn-link:visited:not(.btn-primary):hover {
color: $link-color-dark;
}
// Disable transitions on buttons, which can otherwise flicker while loading CSS file
// something to do with .btn applying a transition for background, and then .btn-link setting a different background
.btn {
transition: none !important;
}
// Make code work with light and dark theme
code {
color: $gray-color-dark;
background-color: $code-bg-color;
box-shadow: 1px 1px 0 $code-shadow-color;
}
// Remove left padding from first pagination link
.pagination .page-item:first-child a {
padding-left: 0;
}
// Override border color for tab block
.tab-block {
border-bottom: solid 1px $border-color;
}
// Fix padding for first menu item
ul.menu li:first-child {
margin-top: 0;
}
// Form auto-complete menu
.form-autocomplete .menu {
.menu-item.selected > a, .menu-item > a:hover {
background: $secondary-color;
color: $primary-color;
}
.group-item, .group-item:hover {
color: $gray-color;
text-transform: uppercase;
background: none;
font-size: 0.6rem;
font-weight: bold;
}
}
// Increase input font size on small viewports to prevent zooming on focus the input
// on mobile devices. 430px relates to the "normalized" iPhone 14 Pro Max
// viewport size
@media screen and (max-width: 430px) {
.form-input {
font-size: 16px;
}
}

View file

@ -2,16 +2,49 @@
@import "variables-dark";
// Import Spectre CSS lib
@import "../../node_modules/spectre.css/src/spectre";
@import "../../node_modules/spectre.css/src/autocomplete";
@import "spectre";
// Import style modules
@import "base";
@import "util";
@import "shared";
@import "bookmarks";
@import "responsive";
@import "bookmark-page";
@import "bookmark-form";
@import "settings";
@import "auth";
// Dark theme overrides
@import "dark";
/* Dark theme overrides */
// Buttons
.btn.btn-primary {
background: $dt-primary-button-color;
border-color: darken($dt-primary-button-color, 5%);
&:hover, &:active, &:focus {
background: darken($dt-primary-button-color, 5%);
border-color: darken($dt-primary-button-color, 10%);
}
}
// Focus ring
a:focus, .btn:focus {
box-shadow: 0 0 0 .1rem rgba($primary-color, .5);
}
// Forms
.form-input:not(:placeholder-shown):invalid,
.form-input:not(:placeholder-shown):invalid:focus,
.has-error .form-input,
.form-input.is-error,
.has-error .form-select,
.form-select.is-error {
background: darken($error-color, 40%);
}
.form-checkbox input:checked + .form-icon, .form-radio input:checked + .form-icon, .form-switch input:checked + .form-icon {
background: $dt-primary-button-color;
border-color: $dt-primary-button-color;
}
// Pagination
.pagination .page-item.active a {
background: $dt-primary-button-color;
}

View file

@ -2,13 +2,11 @@
@import "variables-light";
// Import Spectre CSS lib
@import "../../node_modules/spectre.css/src/spectre";
@import "../../node_modules/spectre.css/src/autocomplete";
@import "spectre";
// Import style modules
@import "base";
@import "util";
@import "shared";
@import "bookmarks";
@import "responsive";
@import "bookmark-page";
@import "bookmark-form";
@import "settings";
@import "auth";

View file

@ -1,21 +0,0 @@
.spacer {
flex: 1 1 0;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text-sm {
font-size: 0.7rem;
}
.text-gray-dark {
color: $gray-color-dark;
}
.align-baseline {
align-items: baseline;
}

View file

@ -4,19 +4,20 @@
{% load bookmarks %}
{% block content %}
<div class="bookmarks-page columns"
<div class="bookmarks-page grid columns-md-1"
ld-bulk-edit
ld-bookmark-page
bookmarks-url="{% url 'bookmarks:partials.bookmark_list.archived' %}"
tags-url="{% url 'bookmarks:partials.tag_cloud.archived' %}">
{# Bookmark list #}
<section class="content-area column col-8 col-md-12">
<div class="content-area-header">
<section class="content-area col-2">
<div class="content-area-header mb-0">
<h2>Archived bookmarks</h2>
<div class="spacer"></div>
{% bookmark_search bookmark_list.filters tag_cloud.tags mode='archived' %}
{% include 'bookmarks/bulk_edit/toggle.html' %}
<div class="d-flex">
{% bookmark_search bookmark_list.filters tag_cloud.tags mode='archived' %}
{% include 'bookmarks/bulk_edit/toggle.html' %}
</div>
</div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ bookmark_list.return_url }}"
@ -31,7 +32,7 @@
</section>
{# Tag cloud #}
<section class="content-area column col-4 hide-md">
<section class="content-area col-1 hide-md">
<div class="content-area-header">
<h2>Tags</h2>
</div>

View file

@ -2,15 +2,13 @@
{% load bookmarks %}
{% block content %}
<div class="columns">
<section class="content-area column col-12">
<div class="content-area-header">
<h2>Edit bookmark</h2>
</div>
<form action="{% url 'bookmarks:edit' bookmark_id %}?return_url={{ return_url|urlencode }}" method="post"
class="col-6 col-md-12" novalidate>
{% bookmark_form form return_url bookmark_id %}
</form>
</section>
</div>
<section class="content-area">
<div class="content-area-header">
<h2>Edit bookmark</h2>
</div>
<form action="{% url 'bookmarks:edit' bookmark_id %}?return_url={{ return_url|urlencode }}" method="post"
class="width-50 width-md-100" novalidate>
{% bookmark_form form return_url bookmark_id %}
</form>
</section>
{% endblock %}

View file

@ -4,19 +4,20 @@
{% load bookmarks %}
{% block content %}
<div class="bookmarks-page columns"
<div class="bookmarks-page grid columns-md-1"
ld-bulk-edit
ld-bookmark-page
bookmarks-url="{% url 'bookmarks:partials.bookmark_list.active' %}"
tags-url="{% url 'bookmarks:partials.tag_cloud.active' %}">
{# Bookmark list #}
<section class="content-area column col-8 col-md-12">
<div class="content-area-header">
<section class="content-area col-2">
<div class="content-area-header mb-0">
<h2>Bookmarks</h2>
<div class="spacer"></div>
{% bookmark_search bookmark_list.filters tag_cloud.tags %}
{% include 'bookmarks/bulk_edit/toggle.html' %}
<div class="d-flex">
{% bookmark_search bookmark_list.filters tag_cloud.tags %}
{% include 'bookmarks/bulk_edit/toggle.html' %}
</div>
</div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ bookmark_list.return_url }}"
@ -31,8 +32,8 @@
</section>
{# Tag cloud #}
<section class="content-area column col-4 hide-md">
<div class="content-area-header">
<section class="content-area col-1 hide-md">
<div class="content-area-header mb-4">
<h2>Tags</h2>
</div>
<div class="tag-cloud-container">

View file

@ -30,9 +30,9 @@
{% endif %}
</head>
<body ld-global-shortcuts>
<header>
<header class="container">
{% if has_toasts %}
<div class="toasts container grid-lg">
<div class="toasts">
<form action="{% url 'bookmarks:toasts.acknowledge' %}?return_url={{ request.path | urlencode }}" method="post">
{% csrf_token %}
{% for toast in toast_messages %}
@ -44,27 +44,21 @@
</form>
</div>
{% endif %}
<div class="navbar container grid-lg">
<section class="navbar-section">
<a href="{% url 'bookmarks:index' %}" class="navbar-brand text-bold">
<img class="logo" src="{% static 'logo.png' %}" alt="Application logo">
<h1>linkding</h1>
</a>
</section>
<div class="d-flex justify-between">
<a href="{% url 'bookmarks:index' %}" class="d-flex align-center">
<img class="logo" src="{% static 'logo.png' %}" alt="Application logo">
<h1>LINKDING</h1>
</a>
{% if request.user.is_authenticated %}
{# Only show nav items menu when logged in #}
<section class="navbar-section">
{% include 'bookmarks/nav_menu.html' %}
</section>
{% include 'bookmarks/nav_menu.html' %}
{% elif has_public_shares %}
{# Otherwise show link to shared bookmarks if there are publicly shared bookmarks #}
<section class="navbar-section">
<a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared bookmarks</a>
</section>
<a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared bookmarks</a>
{% endif %}
</div>
</header>
<div class="content container grid-lg">
<div class="content container">
{% block content %}
{% endblock %}
</div>

View file

@ -2,14 +2,12 @@
{% load bookmarks %}
{% block content %}
<div class="columns">
<section class="content-area column col-12">
<div class="content-area-header">
<h2>New bookmark</h2>
</div>
<form action="{% url 'bookmarks:new' %}" method="post" class="col-6 col-md-12" novalidate>
{% bookmark_form form return_url auto_close=auto_close %}
</form>
</section>
</div>
<section class="content-area">
<div class="content-area-header">
<h2>New bookmark</h2>
</div>
<form action="{% url 'bookmarks:new' %}" method="post" class="width-50 width-md-100" novalidate>
{% bookmark_form form return_url auto_close=auto_close %}
</form>
</section>
{% endblock %}

View file

@ -4,16 +4,15 @@
{% load bookmarks %}
{% block content %}
<div class="bookmarks-page columns"
<div class="bookmarks-page grid columns-md-1"
ld-bookmark-page
bookmarks-url="{% url 'bookmarks:partials.bookmark_list.shared' %}"
tags-url="{% url 'bookmarks:partials.tag_cloud.shared' %}">
{# Bookmark list #}
<section class="content-area column col-8 col-md-12">
<section class="content-area col-2">
<div class="content-area-header">
<h2>Shared bookmarks</h2>
<div class="spacer"></div>
{% bookmark_search bookmark_list.filters tag_cloud.tags mode='shared' %}
</div>
@ -28,7 +27,7 @@
</section>
{# Filters #}
<section class="content-area column col-4 hide-md">
<section class="content-area col-1 hide-md">
<div class="content-area-header">
<h2>User</h2>
</div>

View file

@ -4,13 +4,5 @@
{% block title %}Registration complete{% endblock %}
{% block content %}
<div class="auth-page">
<div class="columns">
<section class="content-area column col-12">
<p>Registration complete. You can now use the application.</p>
</section>
</div>
</div>
<p>Registration complete. You can now use the application.</p>
{% endblock %}

View file

@ -4,41 +4,35 @@
{% block title %}Registration{% endblock %}
{% block content %}
<div class="auth-page">
<div class="columns">
<section class="content-area column col-5 col-md-12">
<div class="content-area-header">
<h2>Register</h2>
</div>
<form method="post" action="{% url 'django_registration_register' %}">
{% csrf_token %}
<div class="form-group {% if form.errors.username %}has-error{% endif %}">
<label class="form-label" for="{{ form.username.id_for_label }}">Username</label>
{{ form.username|add_class:'form-input' }}
<div class="form-input-hint">{{ form.errors.username }}</div>
</div>
<div class="form-group {% if form.errors.email %}has-error{% endif %}">
<label class="form-label" for="{{ form.email.id_for_label }}">Email</label>
{{ form.email|add_class:'form-input' }}
<div class="form-input-hint">{{ form.errors.email }}</div>
</div>
<div class="form-group {% if form.errors.password1 %}has-error{% endif %}">
<label class="form-label" for="{{ form.password1.id_for_label }}">Password</label>
{{ form.password1|add_class:'form-input' }}
<div class="form-input-hint">{{ form.errors.password1 }}</div>
</div>
<div class="form-group {% if form.errors.password2 %}has-error{% endif %}">
<label class="form-label" for="{{ form.password2.id_for_label }}">Confirm Password</label>
{{ form.password2|add_class:'form-input' }}
<div class="form-input-hint">{{ form.errors.password2 }}</div>
</div>
<br/>
<input type="submit" value="Register" class="btn btn-primary col-md-12">
<input type="hidden" name="next" value="{{ next }}">
</form>
</section>
</div>
<section class="content-area mx-auto width-50 width-md-100">
<div class="content-area-header">
<h2>Register</h2>
</div>
<form method="post" action="{% url 'django_registration_register' %}" novalidate>
{% csrf_token %}
<div class="form-group {% if form.errors.username %}has-error{% endif %}">
<label class="form-label" for="{{ form.username.id_for_label }}">Username</label>
{{ form.username|add_class:'form-input'|attr:"placeholder: " }}
<div class="form-input-hint">{{ form.errors.username }}</div>
</div>
<div class="form-group {% if form.errors.email %}has-error{% endif %}">
<label class="form-label" for="{{ form.email.id_for_label }}">Email</label>
{{ form.email|add_class:'form-input'|attr:"placeholder: " }}
<div class="form-input-hint">{{ form.errors.email }}</div>
</div>
<div class="form-group {% if form.errors.password1 %}has-error{% endif %}">
<label class="form-label" for="{{ form.password1.id_for_label }}">Password</label>
{{ form.password1|add_class:'form-input'|attr:"placeholder: " }}
<div class="form-input-hint">{{ form.errors.password1 }}</div>
</div>
<div class="form-group {% if form.errors.password2 %}has-error{% endif %}">
<label class="form-label" for="{{ form.password2.id_for_label }}">Confirm Password</label>
{{ form.password2|add_class:'form-input'|attr:"placeholder: " }}
<div class="form-input-hint">{{ form.errors.password2 }}</div>
</div>
<br/>
<input type="submit" value="Register" class="btn btn-primary btn-wide">
<input type="hidden" name="next" value="{{ next }}">
</form>
</section>
{% endblock %}

View file

@ -4,46 +4,35 @@
{% block title %}Login{% endblock %}
{% block content %}
<div class="auth-page">
<div class="columns">
<section class="content-area column col-5 col-md-12">
<div class="content-area-header">
<h2>Login</h2>
</div>
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
{% if form.errors %}
<div class="form-group has-error">
<p class="form-input-hint">Your username and password didn't match. Please try again.</p>
</div>
{% endif %}
<div class="form-group">
<label class="form-label" for="{{ form.username.id_for_label }}">Username</label>
{{ form.username|add_class:'form-input'|attr:"placeholder: " }}
</div>
<div class="form-group">
<label class="form-label" for="{{ form.password.id_for_label }}">Password</label>
{{ form.password|add_class:'form-input'|attr:"placeholder: " }}
</div>
<br/>
<div class="columns">
<div class="column col-3">
<input type="submit" value="Login" class="btn btn-primary">
<input type="hidden" name="next" value="{{ next }}">
</div>
{% if allow_registration %}
<div class="column col-auto col-ml-auto">
<a href="{% url 'django_registration_register' %}" class="btn btn-link">Register</a>
</div>
{% endif %}
</div>
</form>
</section>
</div>
<section class="content-area mx-auto width-50 width-md-100">
<div class="content-area-header">
<h2>Login</h2>
</div>
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
{% if form.errors %}
<div class="form-group has-error">
<p class="form-input-hint">Your username and password didn't match. Please try again.</p>
</div>
{% endif %}
<div class="form-group">
<label class="form-label" for="{{ form.username.id_for_label }}">Username</label>
{{ form.username|add_class:'form-input'|attr:"placeholder: " }}
</div>
<div class="form-group">
<label class="form-label" for="{{ form.password.id_for_label }}">Password</label>
{{ form.password|add_class:'form-input'|attr:"placeholder: " }}
</div>
<br/>
<div class="d-flex justify-between">
<input type="submit" value="Login" class="btn btn-primary btn-wide">
<input type="hidden" name="next" value="{{ next }}">
{% if allow_registration %}
<a href="{% url 'django_registration_register' %}" class="btn btn-link">Register</a>
{% endif %}
</div>
</form>
</section>
{% endblock %}

View file

@ -4,18 +4,12 @@
{% block title %}Password changed{% endblock %}
{% block content %}
<div class="auth-page">
<div class="columns">
<section class="content-area column col-5 col-md-12">
<div class="content-area-header">
<h2>Password Changed</h2>
</div>
<p class="text-success">
Your password was changed successfully.
</p>
</section>
</div>
<section class="content-area mx-auto width-50 width-md-100">
<div class="content-area-header">
<h2>Password Changed</h2>
</div>
<p class="text-success">
Your password was changed successfully.
</p>
</section>
{% endblock %}

View file

@ -4,52 +4,42 @@
{% block title %}Change Password{% endblock %}
{% block content %}
<div class="auth-page">
<div class="columns">
<section class="content-area column col-5 col-md-12">
<div class="content-area-header">
<h2>Change Password</h2>
</div>
<form method="post" action="{% url 'change_password' %}">
{% csrf_token %}
<div class="form-group {% if form.old_password.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.old_password.id_for_label }}">Old password</label>
{{ form.old_password|add_class:'form-input'|attr:"placeholder: " }}
{% if form.old_password.errors %}
<div class="form-input-hint">
{{ form.old_password.errors }}
</div>
{% endif %}
</div>
<div class="form-group {% if form.new_password1.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.new_password1.id_for_label }}">New password</label>
{{ form.new_password1|add_class:'form-input'|attr:"placeholder: " }}
{% if form.new_password1.errors %}
<div class="form-input-hint">
{{ form.new_password1.errors }}
</div>
{% endif %}
</div>
<div class="form-group {% if form.new_password2.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.new_password2.id_for_label }}">Confirm new password</label>
{{ form.new_password2|add_class:'form-input'|attr:"placeholder: " }}
{% if form.new_password2.errors %}
<div class="form-input-hint">
{{ form.new_password2.errors }}
</div>
{% endif %}
</div>
<br/>
<div class="columns">
<div class="column col-3">
<input type="submit" value="Change Password" class="btn btn-primary">
</div>
</div>
</form>
</section>
</div>
<section class="content-area mx-auto width-50 width-md-100">
<div class="content-area-header">
<h2>Change Password</h2>
</div>
<form method="post" action="{% url 'change_password' %}">
{% csrf_token %}
<div class="form-group {% if form.old_password.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.old_password.id_for_label }}">Old password</label>
{{ form.old_password|add_class:'form-input'|attr:"placeholder: " }}
{% if form.old_password.errors %}
<div class="form-input-hint">
{{ form.old_password.errors }}
</div>
{% endif %}
</div>
<div class="form-group {% if form.new_password1.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.new_password1.id_for_label }}">New password</label>
{{ form.new_password1|add_class:'form-input'|attr:"placeholder: " }}
{% if form.new_password1.errors %}
<div class="form-input-hint">
{{ form.new_password1.errors }}
</div>
{% endif %}
</div>
<div class="form-group {% if form.new_password2.errors %}has-error{% endif %}">
<label class="form-label" for="{{ form.new_password2.id_for_label }}">Confirm new password</label>
{{ form.new_password2|add_class:'form-input'|attr:"placeholder: " }}
{% if form.new_password2.errors %}
<div class="form-input-hint">
{{ form.new_password2.errors }}
</div>
{% endif %}
</div>
<br/>
<input type="submit" value="Change Password" class="btn btn-primary btn-wide">
</form>
</section>
{% endblock %}

View file

@ -16,14 +16,14 @@
{% csrf_token %}
<div class="form-group">
<label for="{{ form.theme.id_for_label }}" class="form-label">Theme</label>
{{ form.theme|add_class:"form-select col-2 col-sm-12" }}
{{ form.theme|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
Whether to use a light or dark theme, or automatically adjust the theme based on your system's settings.
</div>
</div>
<div class="form-group">
<label for="{{ form.bookmark_date_display.id_for_label }}" class="form-label">Bookmark date format</label>
{{ form.bookmark_date_display|add_class:"form-select col-2 col-sm-12" }}
{{ form.bookmark_date_display|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
Whether to show bookmark dates as relative (how long ago), or as absolute dates. Alternatively the date can
be hidden.
@ -50,14 +50,14 @@
</div>
<div class="form-group">
<label for="{{ form.bookmark_link_target.id_for_label }}" class="form-label">Open bookmarks in</label>
{{ form.bookmark_link_target|add_class:"form-select col-2 col-sm-12" }}
{{ form.bookmark_link_target|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
Whether to open bookmarks a new page or in the same page.
</div>
</div>
<div class="form-group">
<label for="{{ form.tag_search.id_for_label }}" class="form-label">Tag search</label>
{{ form.tag_search|add_class:"form-select col-2 col-sm-12" }}
{{ form.tag_search|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
In strict mode, tags must be prefixed with a hash character (#).
In lax mode, tags can also be searched without the hash character.
@ -92,7 +92,7 @@
<div class="form-group">
<label for="{{ form.web_archive_integration.id_for_label }}" class="form-label">Internet Archive
integration</label>
{{ form.web_archive_integration|add_class:"form-select col-2 col-sm-12" }}
{{ form.web_archive_integration|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
Enabling this feature will automatically create snapshots of bookmarked websites on the <a
href="https://web.archive.org/" target="_blank" rel="noopener">Internet Archive Wayback
@ -155,9 +155,9 @@
</div>
</div>
<div class="form-group">
<div class="input-group col-8 col-md-12">
<div class="input-group width-75 width-md-100">
<input class="form-input" type="file" name="import_file">
<input type="submit" class="input-group-btn col-2 btn btn-primary" value="Upload">
<input type="submit" class="input-group-btn btn btn-primary" value="Upload">
</div>
{% if import_success_message %}
<div class="has-success">

View file

@ -33,7 +33,7 @@
<p>The following token can be used to authenticate 3rd-party applications against the REST API:</p>
<div class="form-group">
<div class="columns">
<div class="column col-6 col-md-12">
<div class="column width-50 width-md-100">
<input class="form-input" value="{{ api_token }}" readonly>
</div>
</div>

View file

@ -30,7 +30,7 @@ class ToastsViewTestCase(TestCase, BookmarkFactoryMixin):
response = self.client.get(reverse('bookmarks:index'))
# Should render toasts container
self.assertContains(response, '<div class="toasts container grid-lg">')
self.assertContains(response, '<div class="toasts">')
# Should render two toasts
self.assertContains(response, '<div class="toast">', count=2)