Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

migrate to Bootstrap v4 #773

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bukuserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The following are os env config variables available for bukuserver.
| DISABLE_FAVICON | disable bookmark [favicons](https://wikipedia.org/wiki/Favicon) | boolean [default: `true`] ([here's why](#why-favicons-are-disabled-by-default))|
| OPEN_IN_NEW_TAB | url link open in new tab | boolean [default: `false`] |
| REVERSE_PROXY_PATH | reverse proxy path | string |
| THEME | [GUI theme](https://bootswatch.com/3) | string [default: `default`] (`slate` is a good pick for dark mode) |
| THEME | [GUI theme](https://bootswatch.com/4) | string [default: `default`] (`slate` is a good pick for dark mode) |
| LOCALE | GUI language (partial support) | string [default: `en`] |
| DEBUG | debug mode (verbose logging etc.) | boolean [default: `false`] |

Expand Down
2 changes: 1 addition & 1 deletion bukuserver/bookmarklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ url = "{{url}}" +
"?url=" + encodeURIComponent(url) +
"&title=" + encodeURIComponent(title) +
"&description=" + encodeURIComponent(desc);
window.open(url, '_blank', 'menubar=no, height=600, width=800, toolbar=no, scrollbars=yes, status=no, dialog=1');
window.open(url, '_blank', 'menubar=no, height=700, width=800, toolbar=no, scrollbars=yes, status=no, dialog=1');
2 changes: 1 addition & 1 deletion bukuserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def shell_context():
app.jinja_env.globals.update(_p=_p)

admin = Admin(
app, name='buku server', template_mode='bootstrap3',
app, name='buku server', template_mode='bootstrap4',
index_view=views.CustomAdminIndexView(
template='bukuserver/home.html', url='/'
)
Expand Down
3 changes: 3 additions & 0 deletions bukuserver/static/bukuserver/css/bookmark.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ table tr td:not(:first-child) {
}
}

/* fixing details filter width */
#fa_filter {flex-grow: 1}

.link {
display: block;
}
Expand Down
29 changes: 21 additions & 8 deletions bukuserver/static/bukuserver/css/list.css
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
/* overriding icon-button text color with theme color */
form.icon button {
color: inherit;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the Delete button in bookmarks/tags lists not matching theme color


/* fixing table layout */
.list-buttons-column {width: 0}
.list-buttons-column .icon:last-child {margin-right: 10px}

.filters .filter-op {width: var(--filter-op) !important}
.filters .filter-val {width: calc(var(--filters) - var(--filter-op) - var(--filter-buttons) - var(--filter-type)) !important}
#filter_form[action^='/tag/'] {--filter-type: var(--filter-type-tags)}

:root {--filters: 645px; --filter-op: 9em; --filter-buttons: 12.5em; --filter-type: 5.5em; --filter-type-tags: 9em}
:root {--filters: 510px; --filter-op: 9rem; --filter-buttons: 12.5rem; --filter-type: 6rem; --filter-type-tags: 9.5rem}
/* due to how flask-admin filters are set up, each language requires manual adjustments for full-width sizes */
html[lang=de] {--filter-buttons: 19em}
html[lang=fr] {--filter-buttons: 17em}
html[lang=ru] {--filter-buttons: 16.5em; --filter-type: 8.5em; --filter-type-tags: 11em}
html[lang=de] {--filter-buttons: 18rem}
html[lang=fr] {--filter-buttons: 16rem}
html[lang=ru] {--filter-buttons: 16.5rem; --filter-type: 8.5rem; --filter-type-tags: 11.5rem}

@media (max-width: 767px) {
.filters .filter-val {width: calc(var(--filters) - var(--filter-op) - var(--filter-type)) !important}
#filter_form .pull-right:first-child {margin: .5ex 0}
}
@media (min-width: 768px) {
:root {--filters: 710px; --filter-op: 11.5em}
:root {--filters: 690px; --filter-op: 11.5rem}
}
@media (min-width: 992px) {
:root {--filters: 930px; --filter-op: 20em}
:root {--filters: 930px; --filter-op: 20rem}
}
@media (min-width: 1200px) {
:root {--filters: 1130px; --filter-op: 20em}
html[lang=ru] #filter_form[action^='/bookmark/'] {--filter-op: 25em} /* the last 'buku' filter has a rather long name */
:root {--filters: 1110px; --filter-op: 20rem}
html[lang=ru] #filter_form[action^='/bookmark/'] {--filter-op: 25rem} /* the last 'buku' filter has a rather long name */
}
2 changes: 1 addition & 1 deletion bukuserver/static/bukuserver/css/modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ body.modal-open {

/* limit dialog height with a scrollbox */
.modal-content {
max-height: calc(100vh - 60px);
max-height: calc(100vh - 3.5rem);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bootstrap4 uses rem (root element font size) as the primary size unit

display: flex;
flex-direction: column;
}
Expand Down
1 change: 1 addition & 0 deletions bukuserver/templates/bukuserver/bookmark_create.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{{ buku.limit_navigation_if_popup() }}
{{ buku.script('bookmark.js') }}
{{ buku.fetch_checkbox(form.fetch.data) }}
{{ buku.horizontal_form(excluding_popups=True) }}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned, all forms were originally vertical in Bootstrap4 templates

{{ buku.focus() }}
{{ buku.link_saved() }}
{% endblock %}
3 changes: 2 additions & 1 deletion bukuserver/templates/bukuserver/bookmark_create_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{% block tail %}
{{ super() }}
{{ buku.script('bookmark.js') }}
{{ buku.fetch_checkbox(form.fetch.data) }}
{{ buku.fetch_checkbox(form.fetch.data, modal=True) }}
{{ buku.horizontal_form() }}
{{ buku.focus('.modal-body') }}
{% endblock %}
4 changes: 2 additions & 2 deletions bukuserver/templates/bukuserver/bookmark_details_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
{% block header_text %}
<h3>
{% if request.args.get('id') == 'random' %}
<button id="modal-random" class="btn btn-default" title="{{ _('Pick another') }}">
<span class="fa fa-eye glyphicon glyphicon-repeat"></span>
<button id="modal-random" class="btn btn-secondary" title="{{ _('Pick another') }}">
<span class="fa fa-repeat glyphicon glyphicon-repeat"></span>
</button>
{% endif %}
<a href="{{ url_for('bookmark.details_view', id=model.id, url=request.args.url) }}">{{_gettext('View Record')}} #{{model.id}}</a>
Expand Down
5 changes: 3 additions & 2 deletions bukuserver/templates/bukuserver/bookmark_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

{% block edit_form %}
{{ super() }}
<form method="POST" action="{{ get_url('.delete_view') }}" class="delete-form" style="display: inline-block; float: right">
<form method="POST" action="{{ get_url('.delete_view') }}" class="delete-form d-inline-block float-right">
<input type="hidden" name="id" value="{{ request.args.get('id') }}"/>
<input type="hidden" name="url" value="{{ return_url }}"/>
{% if csrf_token %}
Expand All @@ -24,7 +24,8 @@
{{ buku.set_lang() }}
{{ buku.limit_navigation_if_popup() }}
{{ buku.script('bookmark.js') }}
<script>$('.submit-row').append($('.delete-form'))</script>
{{ buku.horizontal_form(excluding_popups=True) }}
{{ buku.focus() }}
{{ buku.link_saved() }}
<script>$('.submit-row').append($('.delete-form'))</script>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the Delete button was moved to before making the form horizontal (which in turn is placed before changing keyboard focus)

{% endblock %}
1 change: 1 addition & 0 deletions bukuserver/templates/bukuserver/bookmark_edit_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
{% block tail %}
{{ super() }}
{{ buku.script('bookmark.js') }}
{{ buku.horizontal_form() }}
{{ buku.focus('.modal-body') }}
{% endblock %}
2 changes: 1 addition & 1 deletion bukuserver/templates/bukuserver/bookmarklet.url
Original file line number Diff line number Diff line change
@@ -1 +1 @@
javascript:void%20function(){var%20e=location.href,t=document.title.trim()||%22%22,o=document.getSelection().toString().trim()||(document.querySelector(%22meta[name$=description%20i],%20meta[property$=description%20i]%22)||{}).content||%22%22;o.length%3E4e3%26%26(o=o.substr(0,4e3)+%22...%22,alert(%22The%20selected%20text%20is%20too%20long,%20it%20will%20be%20truncated.%22)),e=%22{{url}}%3Furl=%22+encodeURIComponent(e)+%22%26title=%22+encodeURIComponent(t)+%22%26description=%22+encodeURIComponent(o),window.open(e,%22_blank%22,%22menubar=no,%20height=600,%20width=800,%20toolbar=no,%20scrollbars=yes,%20status=no,%20dialog=1%22)}();
javascript:void%20function(){var%20e=location.href,t=document.title.trim()||%22%22,o=document.getSelection().toString().trim()||(document.querySelector(%22meta[name$=description%20i],%20meta[property$=description%20i]%22)||{}).content||%22%22;o.length%3E4e3%26%26(o=o.substr(0,4e3)+%22...%22,alert(%22The%20selected%20text%20is%20too%20long,%20it%20will%20be%20truncated.%22)),e=%22{{url}}%3Furl=%22+encodeURIComponent(e)+%22%26title=%22+encodeURIComponent(t)+%22%26description=%22+encodeURIComponent(o),window.open(e,%22_blank%22,%22menubar=no,%20height=700,%20width=800,%20toolbar=no,%20scrollbars=yes,%20status=no,%20dialog=1%22)}();
10 changes: 6 additions & 4 deletions bukuserver/templates/bukuserver/bookmarks_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
{{ super() }}
{% if data %}
{% set _random = url_for('.details_view', modal=True, id='random', url=return_url, **(request.args|flt)) %}
<li><a id="random" data-target="#fa_modal_window" data-toggle="modal" href="{{ _random }}">{{ _('Random') }}</a></li>
<li class="nav-item">
<a id="random" class="nav-link" data-target="#fa_modal_window" data-toggle="modal" href="{{ _random }}">{{ _('Random') }}</a>
</li>
{% endif %}
{% endblock %}

Expand All @@ -46,13 +48,13 @@
{{ action.render_ctx(get_pk_value(row), row) }}
{% endfor %}
{% if request.args|flt|length == 0 %} {# only shown when filters/ordering are disabled #}
<div class="swap-toolbar" style="margin-left: 10px">
<div class="swap-toolbar" style="margin-left: 9px">
{% if row.id < 2 %}
<div style="display: inline-block; width: 14px"><!-- placeholder for the 1st row button --></div>
<div class="d-inline-block" style="width: 14px"><!-- placeholder for the 1st row button --></div>
{% else %}
{{ swap_rows_action('arrow-up', row.id, -1) }}
{% endif %}
{{ swap_rows_action('transfer', row.id) }}
{{ swap_rows_action('exchange', row.id) }}
{% if row.id < count %}
{{ swap_rows_action('arrow-down', row.id, +1) }}
{% endif %}
Expand Down
30 changes: 14 additions & 16 deletions bukuserver/templates/bukuserver/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,50 @@

{% block menu_links %}
{{ super() }}
<form class="navbar-form navbar-right" action="{{ url_for('admin.search') }}" method="POST">
<div class="form-group">
<input class="form-control" id="inputKeywords" placeholder="{{ _('Search bookmark') }}" name="keyword">
<form class="form-inline navbar-right" style="gap:.5ex" action="{{ url_for('admin.search') }}" method="POST">
<div class="d-inline-block align-middle">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navbar-form was removed in Bootstrap4; and form-group doesn't have the desired effect here.

<input class="form-control" id="inputKeywords" placeholder="{{ _('Search bookmark') }}" name="keyword"/>
<input type="hidden" name="markers" value="true"/>
<input type="hidden" name="all_keywords" value="true"/>
</div>
<button type="submit" class="btn btn-default">{{ _gettext('Search') }}</button>
<button type="submit" class="btn btn-secondary">{{ _gettext('Search') }}</button>
</form>
{% endblock %}

{% block body %}
{{ super() }}
<main class="container text-center">
<div style="padding: 40px 15px">
<main class="container text-center p-4">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using native Bootstrap4 padding classes

<h1>BUKU</h1>
<p class="lead">{{ _('Bookmark manager like a text-based mini-web') }}</p>
<p>
<a class="btn btn-lg btn-success" href="{{ url_for('bookmark.index_view') }}" role="button">{{ _('Bookmarks') }}</a>
<a class="btn btn-lg btn-success" href="{{ url_for('tag.index_view') }}" role="button">{{ _('Tags') }}</a>
<a class="btn btn-lg btn-success" href="{{ url_for('statistic.index') }}" role="button">{{ _('Statistic') }}</a>
</p>
<div class="col-md-4 col-md-offset-4">
<div class="col-md-4 offset-md-4">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing classes got renamed as well

<form action="{{ url_for('admin.search') }}" method="POST">
<div class="form-group">
{{ form.keyword.label }}
{{ form.keyword(class_='form-control', style='display: inline; width: auto') }}
{{ form.keyword(class_='form-control d-inline', style='width: auto') }}
</div>
<div class="text-left col-sm-offset-3">
<div class="text-left">
{% for field in [form.all_keywords, form.markers, form.deep, form.regex] -%}
<div class="checkbox" title="{{ field.description }}" data-toggle="tooltip" data-placement="bottom"> {{field()}} {{field.label}} </div>
<div class="form-check" title="{{ field.description }}" data-toggle="tooltip" data-placement="bottom"> {{field()}} {{field.label}} </div>
{%- endfor %}
</div>
<button type="submit" class="btn btn-default">{{ _gettext('Search') }}</button>
<button type="submit" class="btn btn-secondary">{{ _gettext('Search') }}</button>
</form>
</div>
<div class="col-md-4 col-md-offset-4">
<p style="padding-top: 2em"> {{_('Bookmarklet')}}:
<div class="col-md-4 offset-md-4">
<p class="pt-4"> {{_('Bookmarklet')}}:
<a title="Drag this link to your bookmarks toolbar" href="{{ buku.bookmarklet() }}">
<b>✚ {{ _('Add to Buku') }}</b>
</a><br/>
<em style="font-size: smaller">{{ _("Note: if you select text on the page before activating the bookmarklet, it'll be used as description instead of page metadata.") }}</em>
</p>
</div>

<details class="col-md-6 col-md-offset-3">
<details class="col-md-6 offset-md-3">
<summary style="display: list-item; cursor: pointer"> <em><strong>{{ _('Location Bar (keyboard-only) shortcut') }}</strong></em> </summary>
<dl>
<dt>{{ _('in Firefox:') }}</dt>
Expand All @@ -76,15 +75,14 @@ <h1>BUKU</h1>
</dd>
</dl>
</details>
</div>
</main>
{% endblock %}

{% block tail %}
{{ buku.set_lang() }}
<script>
$(`[data-toggle="tooltip"]`).attr('data-html', 'true').each(function () {
this.title = this.title.replace(/'(.*?)'/g, `'<strong><tt>$1</tt></strong>'`)
this.title = this.title.replace(/'(.*?)'/g, `'<strong><code>$1</code></strong>'`)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<tt> gets removed by sanitizer (along with its contents)

.replace(/(?<=^|[^\p{L}]){{ _('FULL') }}(?=$|[^\p{L}])/g, `<strong><em>{{ _('FULL')|lower }}</em></strong>`);
}).attr('data-container', 'body').attr('data-trigger', 'hover').tooltip();
</script>
Expand Down
57 changes: 38 additions & 19 deletions bukuserver/templates/bukuserver/lib.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
{% macro limit_navigation_if_popup() %}
<style>
.popup .navbar-brand {pointer-events: none}
.popup :is(#admin-navbar-collapse, .navbar-toggle) {display: none !important}
.popup .nav-tabs :is(:first-child, :nth-child(2):not(.active)) > * {display: none}
.popup #admin-navbar-collapse {display: block}
.popup :is(.navbar-nav, .navbar-toggler) {display: none}
.popup .nav-tabs :is(:first-child, :nth-child(2)) > .nav-link:not(.active) {display: none}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#admin-navbar-collapse is now the style of the entire header (it simply changes display: with page width); and the overall design of navbar/tabs got changed as well

</style>
<script>opener && (opener !== window) && document.body.classList.add('popup')</script>
{% endmacro %}
Expand All @@ -31,17 +32,37 @@
{% endif %}
{% endmacro %}

{% macro fetch_checkbox(checked=True) %}
{% macro fetch_checkbox(checked=True, modal=False) %}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modal= is used for placing the Fetch checkbox within the buttons row in modals (as per the screenshots)

<script>
$('.admin-form [name=fetch]').remove();
$('.admin-form').append(
$(`<div class="form-group"><label style="display: block"><span class="col-md-2 text-right">{{ _('Fetch') }} &nbsp; </span>`
+`<span class="col-md-10"><input type="checkbox" name="fetch"{% if checked %} checked{% endif %}></span></label></div>`));
$('.admin-form fieldset{% if modal %} .modal-footer{% endif %}').{% if modal %}prepend{% else %}append{% endif %}(
$(`<div class="form-group {{ 'mb-0' if modal else '' }}"{% if modal %} style="flex-grow: 1"{% endif %}>`
+`<label class="control-label {{ 'mb-0' if modal else '' }}">{{ _('Fetch') }} &nbsp; </label>`
+`<input type="checkbox" name="fetch"{% if checked %} checked{% endif %}></div>`));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rearranging it similarly to how checkbox inputs are generated by flask-admin in Bootstrap4 templates

</script>
{% endmacro %}

{% macro horizontal_form(excluding_popups=False) %}
<script>
$('.admin-form .form-group').each(function () {
if ($('.submit-row', this).length > 0{% if excluding_popups %} || document.body.matches('.popup'){% endif %}) {
$('.submit-row', this).addClass(document.body.matches('.popup') ? 'col-md-12' : 'offset-md-2');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within popups, the buttons row is made full-width instead (flask-admin pads the row instead, but it uses wrong CSS class name 😅)

} else if ($('input[type=checkbox], input[type=radio]', this).length > 0) {
$('label', this).addClass('form-row').css({cursor: 'pointer'}).html(`<span>${ $('label', this).html() }</span>`);
$('label span', this).addClass('col-md-2 col-form-label text-right d-inline-block');
$('input', this).appendTo($('label', this));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkboxes/radiobuttons need custom rearrangement

} else {
$(this).addClass('form-row');
$('input, textarea, select', this).addClass('col-md-10');
$('label', this).addClass('col-md-2 col-form-label text-right');
}
});
</script>
{% endmacro %}

{% macro details_formatting(prefix='') %}
<script>
$(`.modal-header h3`).wrapInner('<h5 class="modal-title">').children(0).unwrap(); // flask-admin #2505
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed once the related bug is fixed in upstream (…and ends up in the released version, of course)

$(`body.popup {{prefix}} a`).attr('target', '_blank');
</script>
{% endmacro %}
Expand Down Expand Up @@ -94,20 +115,18 @@
$(document).ready(function() {
const SIZES = [20, 50, 100]; // hardcoded list; see page_size_form() in admin/model/layout.html
let pageSize = url => new URL(url || location.host).searchParams.get('page_size');
$(`.actions-nav .dropdown-menu`).each(function () {
let _sizes = $(`li a`, this).map(function () {return pageSize(this.href)}).get();
$(`.nav.nav-tabs .dropdown-menu`).each(function () {
let _sizes = $(`a.dropdown-item`, this).map(function () {return pageSize(this.href)}).get();
if (SIZES.every((x, i) => x == _sizes[i]))
$(`li`, this).last().clone().each(function () {
$('a', this).text({{ _('custom')|tojson }}).attr('href', `#`).on('click', () => {
let page = prompt({{ _('Set custom page size (empty for default)')|tojson }}, pageSize(location) || '');
if ((page == "") || (Number(page) >= 1)) {
let search = new URL(location).searchParams;
(page ? search.set('page_size', parseInt(page)) : search.delete('page_size'));
location.search = search;
} else if (page != null)
alert({{ _('Invalid page size')|tojson }} + `: "${page}"`);
return false;
});
$('a', this).last().clone().text({{ _('custom')|tojson }}).attr('href', `#`).on('click', () => {
let page = prompt({{ _('Set custom page size (empty for default)')|tojson }}, pageSize(location) || '');
if (Number(page) || (page == "")) {
let search = new URL(location).searchParams;
(page ? search.set('page_size', page) : search.delete('page_size'));
location.search = search;
} else if (page != null)
alert({{ _('Invalid page size')|tojson }} + `: "${page}"`);
return false;
}).appendTo(this);
})
});
Expand Down
Loading