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
28 changed files with 571 additions and 473 deletions

View File

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

View File

@@ -131,11 +131,7 @@
{#each suggestions as tag,i} {#each suggestions as tag,i}
<li class="menu-item" class:selected={selectedIndex === i}> <li class="menu-item" class:selected={selectedIndex === i}>
<a href="#" on:mousedown|preventDefault={() => complete(tag)}> <a href="#" on:mousedown|preventDefault={() => complete(tag)}>
<div class="tile tile-centered"> {tag.name}
<div class="tile-content">
{tag.name}
</div>
</div>
</a> </a>
</li> </li>
{/each} {/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 { body {
margin: 20px 10px; margin: 20px 10px;
@media (min-width: $size-sm) { @media (min-width: $size-sm) {
// High horizontal padding accounts for checkboxes that show up in bulk edit mode // Horizontal padding accounts for checkboxes that show up in bulk edit mode
margin: 20px 24px; margin: 20px 32px;
} }
} }
header { 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 { 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; display: flex;
align-items: center; flex-wrap: wrap;
padding-bottom: $unit-2;
margin-bottom: $unit-4;
.logo { h2 {
width: 28px; line-height: 1.8rem;
height: 28px; margin-right: auto;
margin-bottom: 0;
} }
h1 {
text-transform: uppercase;
display: inline-block;
margin: 0 0 0 8px;
}
}
.dropdown-toggle {
} }
} }
/* Overrides */ // Confirm button component
span.confirmation {
// Reduce heading sizes display: flex;
h1 { align-items: baseline;
font-size: inherit;
} }
h2 { span.confirmation .btn.btn-link {
font-size: .85rem; 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; color: $gray-color-dark;
} }
// Fix up visited styles .align-baseline {
a:visited { align-items: baseline;
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;
} }
code { .align-center {
color: $gray-color-dark; align-items: center;
background-color: $code-bg-color;
box-shadow: 1px 1px 0 $code-shadow-color;
} }
// Increase spacing between columns .justify-between {
.container > .columns > .column:not(:first-child) { justify-content: space-between;
padding-left: 2rem;
} }
// Remove left padding from first pagination link .mb-4 {
.pagination .page-item:first-child a { margin-bottom: $unit-4;
padding-left: 0;
} }
// Override border color for tab block .mx-auto {
.tab-block { margin-left: auto;
border-bottom: solid 1px $border-color; margin-right: auto;
} }
// Form auto-complete menu .btn.btn-wide {
.form-autocomplete .menu { padding-left: $unit-6;
.menu-item.selected > a, .menu-item > a:hover { padding-right: $unit-6;
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

@@ -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 */ /* Bookmark search box */
.bookmarks-page .search { .bookmarks-page .search {
$searchbox-width: 180px; $searchbox-width: 180px;
@@ -65,6 +69,7 @@ li[ld-bookmark-item] {
.title img { .title img {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin-right: $unit-h;
vertical-align: text-top; vertical-align: text-top;
} }
@@ -84,7 +89,7 @@ li[ld-bookmark-item] {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.4rem; gap: $unit-2;
} }
.actions { .actions {
@@ -113,19 +118,19 @@ li[ld-bookmark-item] {
align-self: center; align-self: center;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.1rem; gap: $unit-h;
} }
} }
} }
.bookmark-pagination { .bookmark-pagination {
margin-top: 1rem; margin-top: $unit-4;
} }
.tag-cloud { .tag-cloud {
.selected-tags { .selected-tags {
margin-bottom: 0.8rem; margin-bottom: $unit-4;
a, a:visited:hover { a, a:visited:hover {
color: $error-color; color: $error-color;
@@ -139,7 +144,7 @@ li[ld-bookmark-item] {
} }
.group { .group {
margin-bottom: 0.4rem; margin-bottom: $unit-2;
} }
.highlight-char { .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 */ /* Bookmark notes */
ul.bookmark-list { ul.bookmark-list {
.notes { .notes {
display: none; display: none;
max-height: 300px; max-height: 300px;
margin: 4px 0; margin: $unit-1 0;
overflow: auto; overflow: auto;
} }
@@ -218,11 +172,11 @@ ul.bookmark-list {
/* Bookmark notes markdown styles */ /* Bookmark notes markdown styles */
ul.bookmark-list .notes-content { ul.bookmark-list .notes-content {
& { & {
padding: 0.4rem 0.6rem; padding: $unit-2 $unit-3;
} }
p, ul, ol, pre, blockquote { p, ul, ol, pre, blockquote {
margin: 0 0 0.4rem 0; margin: 0 0 $unit-2 0;
} }
> *:first-child { > *:first-child {
@@ -234,17 +188,17 @@ ul.bookmark-list .notes-content {
} }
ul, ol { ul, ol {
margin-left: 0.8rem; margin-left: $unit-4;
} }
ul li, ol li { ul li, ol li {
margin-top: 0.2rem; margin-top: $unit-1;
} }
pre { pre {
padding: 0.2rem 0.4rem; padding: $unit-1 $unit-2;
background-color: $code-bg-color; background-color: $code-bg-color;
border-radius: 0.2rem; border-radius: $unit-1;
} }
pre code { pre code {
@@ -268,9 +222,9 @@ $bulk-edit-transition-duration: 400ms;
[ld-bulk-edit] { [ld-bulk-edit] {
.bulk-edit-bar { .bulk-edit-bar {
margin-top: -17px; margin-top: -1px;
margin-bottom: 16px;
margin-left: -$bulk-edit-bar-offset; margin-left: -$bulk-edit-bar-offset;
margin-bottom: $unit-4;
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
transition: max-height $bulk-edit-transition-duration; transition: max-height $bulk-edit-transition-duration;
@@ -309,7 +263,7 @@ $bulk-edit-transition-duration: 400ms;
transition: all $bulk-edit-transition-duration; transition: all $bulk-edit-transition-duration;
.form-icon { .form-icon {
top: 0.2rem; top: $unit-1;
} }
} }
@@ -322,9 +276,9 @@ $bulk-edit-transition-duration: 400ms;
.bulk-edit-actions { .bulk-edit-actions {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
padding: 4px 0; padding: $unit-1 0;
border-top: solid 1px $border-color; border-top: solid 1px $border-color;
gap: 8px; gap: $unit-2;
button { button {
padding: 0 !important; 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 { .settings-page {
section.content-area { section.content-area {
margin-bottom: 2rem; margin-bottom: $unit-12;
h2 { h2 {
font-size: 1.0rem; margin-bottom: $unit-4;
margin-bottom: 0.8rem;
} }
} }

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 "variables-dark";
// Import Spectre CSS lib // Import Spectre CSS lib
@import "../../node_modules/spectre.css/src/spectre"; @import "spectre";
@import "../../node_modules/spectre.css/src/autocomplete";
// Import style modules // Import style modules
@import "base"; @import "base";
@import "util"; @import "responsive";
@import "shared"; @import "bookmark-page";
@import "bookmarks"; @import "bookmark-form";
@import "settings"; @import "settings";
@import "auth";
// Dark theme overrides /* Dark theme overrides */
@import "dark";
// 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 "variables-light";
// Import Spectre CSS lib // Import Spectre CSS lib
@import "../../node_modules/spectre.css/src/spectre"; @import "spectre";
@import "../../node_modules/spectre.css/src/autocomplete";
// Import style modules // Import style modules
@import "base"; @import "base";
@import "util"; @import "responsive";
@import "shared"; @import "bookmark-page";
@import "bookmarks"; @import "bookmark-form";
@import "settings"; @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 %} {% load bookmarks %}
{% block content %} {% block content %}
<div class="bookmarks-page columns" <div class="bookmarks-page grid columns-md-1"
ld-bulk-edit ld-bulk-edit
ld-bookmark-page ld-bookmark-page
bookmarks-url="{% url 'bookmarks:partials.bookmark_list.archived' %}" bookmarks-url="{% url 'bookmarks:partials.bookmark_list.archived' %}"
tags-url="{% url 'bookmarks:partials.tag_cloud.archived' %}"> tags-url="{% url 'bookmarks:partials.tag_cloud.archived' %}">
{# Bookmark list #} {# Bookmark list #}
<section class="content-area column col-8 col-md-12"> <section class="content-area col-2">
<div class="content-area-header"> <div class="content-area-header mb-0">
<h2>Archived bookmarks</h2> <h2>Archived bookmarks</h2>
<div class="spacer"></div> <div class="d-flex">
{% bookmark_search bookmark_list.filters tag_cloud.tags mode='archived' %} {% bookmark_search bookmark_list.filters tag_cloud.tags mode='archived' %}
{% include 'bookmarks/bulk_edit/toggle.html' %} {% include 'bookmarks/bulk_edit/toggle.html' %}
</div>
</div> </div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ bookmark_list.return_url }}" <form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ bookmark_list.return_url }}"
@@ -31,7 +32,7 @@
</section> </section>
{# Tag cloud #} {# Tag cloud #}
<section class="content-area column col-4 hide-md"> <section class="content-area col-1 hide-md">
<div class="content-area-header"> <div class="content-area-header">
<h2>Tags</h2> <h2>Tags</h2>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,13 +4,5 @@
{% block title %}Registration complete{% endblock %} {% block title %}Registration complete{% endblock %}
{% block content %} {% block content %}
<p>Registration complete. You can now use the application.</p>
<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>
{% endblock %} {% endblock %}

View File

@@ -4,41 +4,35 @@
{% block title %}Registration{% endblock %} {% block title %}Registration{% endblock %}
{% block content %} {% block content %}
<section class="content-area mx-auto width-50 width-md-100">
<div class="auth-page"> <div class="content-area-header">
<div class="columns"> <h2>Register</h2>
<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>
</div> </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 %} {% endblock %}

View File

@@ -4,46 +4,35 @@
{% block title %}Login{% endblock %} {% block title %}Login{% endblock %}
{% block content %} {% block content %}
<section class="content-area mx-auto width-50 width-md-100">
<div class="auth-page"> <div class="content-area-header">
<div class="columns"> <h2>Login</h2>
<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>
</div> </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 %} {% endblock %}

View File

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

View File

@@ -4,52 +4,42 @@
{% block title %}Change Password{% endblock %} {% block title %}Change Password{% endblock %}
{% block content %} {% block content %}
<section class="content-area mx-auto width-50 width-md-100">
<div class="auth-page"> <div class="content-area-header">
<div class="columns"> <h2>Change Password</h2>
<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>
</div> </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 %} {% endblock %}

View File

@@ -16,14 +16,14 @@
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="{{ form.theme.id_for_label }}" class="form-label">Theme</label> <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"> <div class="form-input-hint">
Whether to use a light or dark theme, or automatically adjust the theme based on your system's settings. Whether to use a light or dark theme, or automatically adjust the theme based on your system's settings.
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.bookmark_date_display.id_for_label }}" class="form-label">Bookmark date format</label> <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"> <div class="form-input-hint">
Whether to show bookmark dates as relative (how long ago), or as absolute dates. Alternatively the date can Whether to show bookmark dates as relative (how long ago), or as absolute dates. Alternatively the date can
be hidden. be hidden.
@@ -50,14 +50,14 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.bookmark_link_target.id_for_label }}" class="form-label">Open bookmarks in</label> <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"> <div class="form-input-hint">
Whether to open bookmarks a new page or in the same page. Whether to open bookmarks a new page or in the same page.
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.tag_search.id_for_label }}" class="form-label">Tag search</label> <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"> <div class="form-input-hint">
In strict mode, tags must be prefixed with a hash character (#). In strict mode, tags must be prefixed with a hash character (#).
In lax mode, tags can also be searched without the hash character. In lax mode, tags can also be searched without the hash character.
@@ -92,7 +92,7 @@
<div class="form-group"> <div class="form-group">
<label for="{{ form.web_archive_integration.id_for_label }}" class="form-label">Internet Archive <label for="{{ form.web_archive_integration.id_for_label }}" class="form-label">Internet Archive
integration</label> 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"> <div class="form-input-hint">
Enabling this feature will automatically create snapshots of bookmarked websites on the <a 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 href="https://web.archive.org/" target="_blank" rel="noopener">Internet Archive Wayback
@@ -155,9 +155,9 @@
</div> </div>
</div> </div>
<div class="form-group"> <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 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> </div>
{% if import_success_message %} {% if import_success_message %}
<div class="has-success"> <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> <p>The following token can be used to authenticate 3rd-party applications against the REST API:</p>
<div class="form-group"> <div class="form-group">
<div class="columns"> <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> <input class="form-input" value="{{ api_token }}" readonly>
</div> </div>
</div> </div>

View File

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