-
-
Notifications
You must be signed in to change notification settings - Fork 298
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
[jarun#753] pre-migration WebUI fixes #761
Conversation
@@ -16,4 +16,4 @@ url = "{{url}}" + | |||
"?url=" + encodeURIComponent(url) + | |||
"&title=" + encodeURIComponent(title) + | |||
"&description=" + encodeURIComponent(desc); | |||
window.open(url, '_blank', 'menubar=no, height=600, width=600, toolbar=no, scrollbars=yes, status=no, dialog=1'); | |||
window.open(url, '_blank', 'menubar=no, height=600, width=800, toolbar=no, scrollbars=yes, status=no, dialog=1'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -120,7 +119,6 @@ def shell_context(): | |||
|
|||
app.jinja_env.filters['netloc'] = lambda x: urlparse(x).netloc # pylint: disable=no-member | |||
|
|||
Bootstrap(app) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using Bootstrap bundled with flask-admin
instead
|
||
.description { | ||
white-space: pre-wrap; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* overriding icon-button text color with theme color */ | ||
.modal-header :is(button, button:hover) { | ||
color: inherit; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
window._tagsQueried = Date.now(); | ||
$.getJSON('/api/tags', ({tags}) => resolve(tags)); | ||
})); | ||
_tags.then(tags => $('input#tags').select2({tags, tokenSeparators: [',']})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This avoids sending more than one /api/tags
request per second
<div class="form-group"> {{form.regex()}} {{form.regex.label}} </div> | ||
<div class="text-left col-sm-offset-3"> | ||
<div class="checkbox"> {{form.deep()}} {{form.deep.label}} </div> | ||
<div class="checkbox"> {{form.regex()}} {{form.regex.label}} </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$(`<label class="form-group" style="display: block"><span class="col-md-2 text-right">{{ _gettext('Fetch') }}</span>` | ||
+`<span class="col-md-10"><input type="checkbox" name="fetch"{% if checked %} checked{% endif %}></span></label>`)) | ||
$(`<div class="form-group"><label style="display: block"><span class="col-md-2 text-right">{{ _gettext('Fetch') }} </span>` | ||
+`<span class="col-md-10"><input type="checkbox" name="fetch"{% if checked %} checked{% endif %}></span></label></div>`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is closer to how other inputs in the form are arranged
created.innerText = (!unit ? "just now" : `${parseInt(diff/UNITS[unit])} ${unit}s ago`); | ||
setTimeout(recalcReltime, 1000 * {second: 1, minute: 15, hour: 60, day: 15*60}[unit||'second']); | ||
}); | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Live recalculation of displayed relative time (with appropriate units)
I'd say this is easier to wrap the mind around than "very many seconds" you'd see otherwise 😅
(Refresh rate is: every second when showing time in seconds, four times a minute when showing minutes, once a minute when showing hours, and every quarter of hour when showing days. The reason to have steps shorter than the unit is to reduce discrepancy between displayed and actual time, since the timer is not reset when reopening/refreshing the page.)
{% else %} | ||
<span class="btn btn-default" disabled="disabled">(No Netloc)</span> | ||
{% endif %} | ||
<a href="{{ buku.filter('url_netloc_match', name) }}">{{ name or '(no netloc)' }}</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no reason to disallow searching for entries with no netloc 😅
{% else %} | ||
<span class="btn btn-default" disabled="disabled">(No Title)</span> | ||
{% endif %} | ||
<a href="{{ buku.filter('title_equals', name) }}">{{ name or '(no title)' }}</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no reason to disallow searching for entries with no title 😅
$('.modal-body').each(function () { | ||
$('thead', this).css({top: '-' + $.css(this, 'padding-top')}); | ||
}); | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sticky header top:
must equal the (negative) distance to the top of the scrollbox
{{ buku.script('Chart.js') }} | ||
{% set NO_NETLOC = '\u200B(no netloc)\u200B' %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling the blank name in the chart (it should be displayed, same as in table, but searching should be done against an empty string)
var netlocCtx = document.getElementById("mostCommonChart").getContext('2d'); | ||
var netlocChart = new Chart(netlocCtx, { | ||
var netlocCtx = document.getElementById("mostCommonChart")?.getContext('2d'); | ||
var netlocChart = netlocCtx && new Chart(netlocCtx, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoiding errors when the chart is not rendered
@@ -333,8 +363,6 @@ <h4 class="modal-title" id="myModalLabel">Title ranking</h4> | |||
} | |||
} | |||
}); | |||
|
|||
titleChart.canvas.parentNode.style.height = '128px'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't doing anything other than causing rendering issues in some cases 😅
@@ -430,8 +418,8 @@ def _apply_filters(self, models, filters): | |||
def _name_formatter(self, _, model, name): | |||
data = getattr(model, name) | |||
query, title = (({'flt0_tags_contain': data}, data) if data else | |||
({'flt0_tags_number_equal': 0}, '<EMPTY TAG>')) | |||
return Markup(f'<a href="{escape(url_for("bookmark.index_view", **query))}">{escape(title)}</a>') | |||
({'flt0_tags_number_equal': 0}, '<UNTAGGED>')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming for clarity
{% block tail %} | ||
{{ super() }} | ||
<script>$('tr:has(a[href$="/bookmark/?flt0_tags_number_equal=0"]) .list-buttons-column').html('')</script> | ||
{% endblock %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tags=CountedData(data['tags']), | ||
titles=CountedData(data['titles']), | ||
datetime=datetime, | ||
datetime_text=datetime.humanize(arrow.now(), granularity='second'), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactoring stats code to simplify it and remove logic duplication.
Also moved amount filtering here from the UI, and modified it slightly (there's no need to exclude netlocs with 1 URL, but the only good reason to have a "ranking" for bookmark titles is looking for duplicates)
|
||
def sorted_counter(keys, *, min_count=0): | ||
data = Counter(keys) | ||
return Counter({k: v for k, v in sorted(data.items()) if v > min_count}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
</table> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
{% endif %} | ||
|
||
<h3 class="col-md-12">Title</h3> | ||
<h3 class="col-md-12">Title (common)</h3> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reflecting exclusion of unique titles in displayed captions
super().__init__(*args, **kwargs) | ||
self.bukudb = bukudb | ||
custom_model = types.SimpleNamespace(bukudb=bukudb, __name__='bookmark') | ||
super().__init__(custom_model, *args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A simpler way of doing this
@@ -715,4 +624,30 @@ def filter_key(flt, idx=''): | |||
|
|||
def format_value(field, bookmark, spacing=''): | |||
s = bookmark[field.value] | |||
return s if field != BookmarkField.TAGS else s.strip(',').replace(',', ','+spacing) | |||
return s if field != BookmarkField.TAGS else (s or '').strip(',').replace(',', ','+spacing) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was causing occasional errors during form parsing
def link(text, url, new_tab=False, badge=''): | ||
target = ('' if not new_tab else ' target="_blank"') | ||
cls = ('' if not badge else f' class="btn label label-{badge}"') | ||
return f'<a{cls} href="{escape(url)}"{target}>{escape(text)}</a>' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rendering hyperlinks with an utility for simpler and more consistent handling
assert cell == (prefix + f'<br/><a href="{url}"{target}>{url}</a><br/>' + | ||
f'<a class="btn btn-default" href="/bookmark/?flt0_url_netloc_match={netloc}">netloc:{netloc}</a>' + | ||
suffix) | ||
assert cell == f'{prefix}<span class="link"><a href="{url}"{target}>{url}</a></span>{suffix}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that there's no <br/>
s now; tags and description are in <div>
s instead, and .link
has display: block
.
Also, every component has its own DOM node (tagged with an appropriate CSS class), allowing for further CSS-only formatting.
@@ -109,6 +109,7 @@ def test_bmv_create_form(bmv_instance, url, backlink, app): | |||
# | |||
|
|||
xpath_alert = lambda kind, message: f'//div[@class="alert alert-{kind} alert-dismissable"][contains(., "{message}")]' | |||
xpath_cls = lambda s: ''.join(f'[contains(concat(" ", @class, " "), " {s} ")]' for s in s.split(' ') if s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@class=
literally just checks for string equality; this matches separate class-names instead (like a selector query would)
assert dom.xpath('//head/link[@rel="stylesheet"][@href=$href]', | ||
href=f'/static/admin/bootstrap/bootstrap3/swatch/{theme or "default"}/bootstrap.min.css?v=3.3.5') | ||
assert dom.xpath('//head/link[@rel="stylesheet"][starts-with(@href, $href)]', | ||
href=f'/static/admin/bootstrap/bootstrap3/swatch/{theme or "default"}/bootstrap.min.css?') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't actually care about precise version numbers here; we're only checking the theme name
Thank you! Please let me know if you have any more immediate fixes/improvements in mind. Otherwise, I'll plan for a release. |
I actually have the Bootstrap v4 changes more or less ready (sans screenshots), but if we merge those in we'll need to wait for the I'm actually not sure why they're stalling the release (they've been quiet for 2 weeks now), but I'm pretty sure it's going to happen sooner rather than later… and we'll probably need to make a release after that regardless of whether we release Bootstrap v3 changes now. (If you don't see a problem with that, then making a release before migration seems like a good idea.) Other than that, I only had a thought about adjusting the title/name filter (in Bookmarks/Tags pages, respectively) so that it actually allows for partial match – the filter is nearly useless without it (for manual usecase). P.S. Have you seen my question about migrating dev team posts? I don't seem to have the access to do it (which makes sense but then why do I have the button? 😅) |
We can wait for a while. No issues. How frequently do they release? I tried to migrate the team posts, but it didn't work out. It is not listing the new Buku project discussion thread. |
I don't think they have a schedule, but the only remaining item they have on their pre-release TODO list is "Remove any existing As for team posts migration, I believe it's supposed to be migrated to either organization or a repo within it. And yes, we can use that but the point for migrating them is to at the very least have access to the posts archive in GitHub UI (as opposed to trying to navigate a single JSON file)… and I guess that would allow to discuss development plans before going public with them, as the team posts allowed (since I don't believe there's a way to make a public repo discussion non-public). |
OK. Please ping me once they are through their release. |
As I mentioned, I've been working on Bootstrap v4 migration; while doing so, I made a number of fixes and adjustments that aren't strictly related to Bootstrap v4, so I made a separate pull-request for them to be merged before migrating to the newer Bootstrap version.
This includes the following changes:
/api/tags
query if possible (see also [jarun#753] implement thread safety #760)views.py
and functional tests intest_views.py