summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArun <engineerarun@gmail.com>2024-10-22 22:47:24 +0530
committerGitHub <noreply@github.com>2024-10-22 22:47:24 +0530
commit48bf29e0f03b754c5d2810a101911e02708b277f (patch)
treedf370cf051cc74fa9df75a46f423d3ff13311101
parent1f6c1a8fe168314d38651e500e5cf0584f3657a1 (diff)
parentde8bc94b4095c9ca18f4e26a6a58b3f7d7f28551 (diff)
Merge pull request #789 from LeXofLeviafan/swap-indices
Bookmark index swapping
-rw-r--r--README.md19
-rw-r--r--auto-completion/bash/buku-completion.bash3
-rw-r--r--auto-completion/fish/buku.fish2
-rw-r--r--auto-completion/zsh/_buku2
-rwxr-xr-xbuku57
-rw-r--r--buku.112
-rw-r--r--bukuserver/forms.py5
-rw-r--r--bukuserver/templates/bukuserver/bookmarks_list.html48
-rw-r--r--bukuserver/translations/de/LC_MESSAGES/messages.po62
-rw-r--r--bukuserver/translations/fr/LC_MESSAGES/messages.po62
-rw-r--r--bukuserver/translations/ru/LC_MESSAGES/messages.mobin11514 -> 12190 bytes
-rw-r--r--bukuserver/translations/ru/LC_MESSAGES/messages.po62
-rw-r--r--bukuserver/views.py6
-rw-r--r--tests/test_bukuDb.py9
14 files changed, 293 insertions, 56 deletions
diff --git a/README.md b/README.md
index 79e3227..e16ffd3 100644
--- a/README.md
+++ b/README.md
@@ -191,6 +191,7 @@ EDIT OPTIONS:
clears description, if no arguments
--immutable N disable web-fetch during auto-refresh
N=0: mutable (default), N=1: immutable
+ --swap N M swap two records at specified indices
SEARCH OPTIONS:
-s, --sany [...] find records with ANY matching keyword
@@ -290,12 +291,24 @@ SYMBOLS:
PROMPT KEYS:
1-N browse search result indices and/or ranges
+ R [N] print out N random search results
+ (or random bookmarks if negative or N/A)
+ ^ id1 id2 swap two records at specified indices
O [id|range [...]] open search results/indices in GUI browser
toggle try GUI browser if no arguments
a open all results in browser
s keyword [...] search for records with ANY keyword
S keyword [...] search for records with ALL keywords
d match substrings ('pen' matches 'opened')
+ m search with markers - search string is split
+ into keywords by prefix markers, which determine
+ what field the keywords is searched in:
+ '.', '>' or ':' - title, description or URL
+ '#'/'#,' - tags (comma-separated, partial/full match)
+ '*' - all fields (can be omitted in the 1st keyword)
+ note: tag marker is not affected by 'd' (deep search)
+ v fields change sorting order (default is '+index')
+ multiple comma/space separated fields can be specified
r expression run a regex search
t [tag, ...] search by tags; show taglist, if no args
g taglist id|range [...] [>>|>|<<] [record id|range ...]
@@ -548,7 +561,11 @@ PROMPT KEYS:
$ buku --random -S kernel debugging --export random.md
-46. More **help**:
+46. Swap positions of records #4 and #5:
+
+ $ buku --swap 4 5
+
+47. More **help**:
$ buku -h
$ man buku
diff --git a/auto-completion/bash/buku-completion.bash b/auto-completion/bash/buku-completion.bash
index 9bf9257..1b3f64d 100644
--- a/auto-completion/bash/buku-completion.bash
+++ b/auto-completion/bash/buku-completion.bash
@@ -39,11 +39,13 @@ _buku () {
--order
-p --print
-r --sreg
+ --random
--replace
-s --sany
-S --sall
--shorten
--suggest
+ --swap
-t --stag
--tacit
--tag
@@ -76,6 +78,7 @@ _buku () {
-s --sany
-S --sall
--shorten
+ --swap
--threads
--url
-x --exclude
diff --git a/auto-completion/fish/buku.fish b/auto-completion/fish/buku.fish
index b013c5c..529d19f 100644
--- a/auto-completion/fish/buku.fish
+++ b/auto-completion/fish/buku.fish
@@ -32,11 +32,13 @@ complete -c buku -l offline --description 'add a bookmark without connec
complete -c buku -l order -r --description 'order by fields (+/- prefix for direction)'
complete -c buku -s p -l print --description 'show bookmark details'
complete -c buku -s r -l sreg -r --description 'match a regular expression'
+complete -c buku -l random --description 'random subset (of 1 or given amount)'
complete -c buku -l replace -r --description 'replace a tag'
complete -c buku -s s -l sany -r --description 'match any keyword'
complete -c buku -s S -l sall -r --description 'match all keywords'
complete -c buku -l shorten -r --description 'shorten a URL using tny.im'
complete -c buku -l suggest --description 'show a list of similar tags'
+complete -c buku -l swap -r --description 'swap 2 given bookmark indices'
complete -c buku -s t -l stag --description 'search by tag or show tags'
complete -c buku -l tacit --description 'reduce verbosity'
complete -c buku -l tag --description 'set tags, use + to append, - to remove'
diff --git a/auto-completion/zsh/_buku b/auto-completion/zsh/_buku
index f99a486..d0a2423 100644
--- a/auto-completion/zsh/_buku
+++ b/auto-completion/zsh/_buku
@@ -37,11 +37,13 @@ args=(
'(--order)--order[order by fields (+/- prefix for direction)]:fields'
'(-p --print)'{-p,--print}'[show bookmark details]'
'(-r --sreg)'{-r,--sreg}'[match a regular expression]:regex'
+ '(--random)--random[random subset (of 1 or given amount)]::amount'
'(--replace)--replace[replace a tag]:tag to replace'
'(-s --sany)'{-s,--sany}'[match any keyword]:keyword(s)'
'(-S --sall)'{-S,--sall}'[match all keywords]:keyword(s)'
'(--shorten)--shorten[shorten a URL using tny.im]:index/url'
'(--suggest)--suggest[show a list of similar tags]'
+ '(--swap)--swap[swap 2 given bookmark indices]:index1 index2'
'(-t --stag)'{-t,--stag}'[search by tag or show tags]'
'(--tacit)--tacit[reduce verbosity]'
'(--tag)--tag[set tags, use + to append, - to remove]'
diff --git a/buku b/buku
index a38cc01..20c82c0 100755
--- a/buku
+++ b/buku
@@ -1791,6 +1791,40 @@ class BukuDb:
return search_results
return filter_from(search_results, self.searchdb(without, deep=deep, markers=markers), exclude=True)
+ def swap_recs(self, index1: int, index2: int, *, lock: bool = True, delay_commit: bool = False):
+ """Swaps two records with given indices
+
+ Parameters
+ ----------
+ index1 : int
+ Index of the 1st record to be exchanged.
+ index2 : int
+ Index of the 2nd record to be exchanged.
+ lock : bool
+ Whether to restrict concurrent access (True by default).
+ delay_commit : bool
+ True if record should not be committed to the DB,
+ leaving commit responsibility to caller. Default is False.
+
+ Returns
+ -------
+ bool
+ True on success, False on failure.
+ """
+ if lock:
+ with self.lock:
+ return self.swap_recs(index1, index2, lock=False, delay_commit=delay_commit)
+
+ max_id = self.get_max_id()
+ if not max_id or index1 == index2 or not all(0 < x <= max_id for x in [index1, index2]):
+ return False
+
+ self.cur.executemany('UPDATE bookmarks SET id = ? WHERE id = ?',
+ [(max_id+1, index1), (index1, index2), (index2, max_id+1)])
+ if not delay_commit:
+ self.conn.commit()
+ return True
+
def compactdb(self, index: int, delay_commit: bool = False):
"""When an entry at index is deleted, move the
last entry in DB to index, if index is lesser.
@@ -3362,6 +3396,7 @@ PROMPT KEYS:
1-N browse search result indices and/or ranges
R [N] print out N random search results
(or random bookmarks if negative or N/A)
+ ^ id1 id2 swap two records at specified indices
O [id|range [...]] open search results/indices in GUI browser
toggle try GUI browser if no arguments
a open all results in browser
@@ -4705,7 +4740,7 @@ def prompt(obj, results, noninteractive=False, deep=False, listtags=False, sugge
if nav == 'n':
continue
- if (m := re.match(r'^R(?: (-)?([0-9]+))?$', nav)) and (n := int(m[2] or 1)) > 0:
+ if (m := re.match(r'^R(?: (-)?([0-9]+))?$', nav.rstrip())) and (n := int(m[2] or 1)) > 0:
skip_print = True
if results and not m[1]: # from search results
picked = random.sample(results, min(n, len(results)))
@@ -4716,6 +4751,14 @@ def prompt(obj, results, noninteractive=False, deep=False, listtags=False, sugge
print_single_rec(row, columns=columns)
continue
+ if (m := re.match(r'^\^ ([1-9][0-9]*) ([1-9][0-9]*)$', nav.rstrip())):
+ index1, index2 = map(int, m.group(1, 2))
+ if bdb.swap_recs(index1, index2):
+ bdb.print_rec({index1, index2})
+ else:
+ print('Failed to swap records #%d and #%d' % (index1, index2))
+ continue
+
# search ANY match with new keywords
if nav.startswith('s '):
keywords = (nav[2:].split() if not markers else split_by_marker(nav[2:]))
@@ -5881,13 +5924,15 @@ POSITIONAL ARGUMENTS:
-c, --comment [...] notes or description of the bookmark
clears description, if no arguments
--immutable N disable web-fetch during auto-refresh
- N=0: mutable (default), N=1: immutable''')
+ N=0: mutable (default), N=1: immutable
+ --swap N M swap two records at specified indices''')
addarg = edit_grp.add_argument
addarg('--url', nargs=1, help=hide)
addarg('--tag', nargs='*', help=hide)
addarg('--title', nargs='*', help=hide)
addarg('-c', '--comment', nargs='*', help=hide)
addarg('--immutable', type=int, choices={0, 1}, help=hide)
+ addarg('--swap', nargs=2, type=int, help=hide)
_bool = lambda x: x if x is None else bool(x)
_immutable = lambda args: _bool(args.immutable)
@@ -6181,6 +6226,14 @@ POSITIONAL ARGUMENTS:
colorize=not args.nc
)
+ if args.swap:
+ index1, index2 = args.swap
+ if bdb.swap_recs(index1, index2):
+ bdb.print_rec({index1, index2})
+ else:
+ LOGERR('Failed to swap records #%d and #%d', index1, index2)
+ bdb.close_quit(0)
+
# Editor mode
if args.write is not None:
if not is_editor_valid(args.write):
diff --git a/buku.1 b/buku.1
index e974eec..13b9fa0 100644
--- a/buku.1
+++ b/buku.1
@@ -56,6 +56,7 @@ Bookmarks with immutable titles are listed with '(L)' after the title.
- If --url is passed (and --title is omitted), update the title from web using the URL. Description is updated (if --comment is omitted). Tags remain untouched.
- If indices are passed without any other options (--url, --title, --tag, --comment and --immutable), read the URLs from DB and update titles, description and append tags from web. Bookmarks marked immutable are skipped.
- Can update bookmarks matching a search, when combined with any of the search options and no arguments to update are passed.
+ - Additionally, --swap allows to modify records order (standalone operation).
.PP
.IP 7. 4
\fBDelete\fR operation:
@@ -143,6 +144,9 @@ Add notes or description of the bookmark, works with --add, --update. Clears the
.TP
.BI \--immutable " N"
Set the title, description and tags of a bookmark immutable during autorefresh. Works with --add, --update. N=1 sets the immutable flag, N=0 removes it. If omitted, bookmarks are added with N=0.
+.TP
+.BI \--swap " N M"
+Swap two records at specified indices. This is a standalone operation (cannot be invoked along with any other).
.SH SEARCH OPTIONS
.TP
.BI \-s " " \--sany " keyword [...]"
@@ -957,6 +961,14 @@ Print out a single \fBrandom\fR bookmark matching \fBsearch\fR criteria, and \fB
.B buku --random -S kernel debugging --export random.md
.EE
.PP
+.IP 46. 4
+Swap positions of records #4 and #5:
+.PP
+.EX
+.IP
+.B buku --swap 4 5
+.EE
+.PP
.SH AUTHOR
diff --git a/bukuserver/forms.py b/bukuserver/forms.py
index b9c60d2..6280f48 100644
--- a/bukuserver/forms.py
+++ b/bukuserver/forms.py
@@ -42,6 +42,11 @@ class BookmarkForm(FlaskForm):
fetch = HiddenField(filters=[bool])
+class SwapForm(FlaskForm):
+ id1 = HiddenField(filters=[int])
+ id2 = HiddenField(filters=[int])
+
+
class ApiTagForm(FlaskForm):
class Meta:
csrf = False
diff --git a/bukuserver/templates/bukuserver/bookmarks_list.html b/bukuserver/templates/bukuserver/bookmarks_list.html
index 18db15f..7325cc7 100644
--- a/bukuserver/templates/bukuserver/bookmarks_list.html
+++ b/bukuserver/templates/bukuserver/bookmarks_list.html
@@ -4,16 +4,62 @@
{% block head %}
{{ super() }}
{{ buku.close_if_popup() }}
+ <script>
+ function promptSwap(input, rowId, maxId={{count|tojson}}) {
+ let _id = input.value = prompt({{ _('Swap record #{} with record #')|tojson }}.replace('{}', rowId), rowId) || "";
+ let error = (!_id ? "" :
+ !/^[1-9][0-9]*$/.test(_id) ? {{ _("Not a valid record index: '{}'")|tojson }}.replace('{}', _id) :
+ _id > maxId ? {{ _('There are only {} records in total!')|tojson }}.replace('{}', maxId) :
+ _id == `${rowId}` ? {{ _('Swapping a record with itself has no effect!')|tojson }} : null);
+ error && alert(error);
+ return (error == null);
+ }
+ </script>
{% endblock %}
{% block model_menu_bar_before_filters %}
{{ super() }}
{% if data %}
- {% set _random = url_for('bookmark.details_view', modal=True, id='random', url=return_url, **(request.args|flt)) %}
+ {% 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>
{% endif %}
{% endblock %}
+{% macro swap_rows_action(icon, row_id, step=None) %} {# based on admin/model/row_actions.delete_row() #}
+<form class="icon" method="POST" action="{{ get_url('.swap') }}">
+ {% if csrf_token %}
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
+ {% endif %}
+ {% set _input = 'swap' + row_id|string %}
+ <input type="hidden" name="url" value="{{ return_url }}"/>
+ <input type="hidden" name="id1" value="{{ row_id }}"/>
+ <input type="hidden" name="id2"{% if step %} value="{{ row_id + step }}"{% else %} id="{{ _input }}"{% endif %}/>
+ <button title="{{ _('Swap with…') if not step else _('Move down') if step > 0 else _('Move up') }}"
+ {%- if not step %} onclick="return promptSwap({{_input}}, {{row_id}})"{% endif %}>
+ <span class="fa fa-{{icon}} glyphicon glyphicon-{{icon}}"></span>
+ </button>
+</form>
+{% endmacro %}
+
+{% block list_row_actions scoped %}
+ {% for action in list_row_actions %}
+ {{ 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">
+ {% if row.id < 2 %}
+ <div style="display: inline-block; 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) }}
+ {% if row.id < count %}
+ {{ swap_rows_action('arrow-down', row.id, +1) }}
+ {% endif %}
+ </div>
+ {% endif %}
+{% endblock %}
+
{% block tail %}
{{ buku.fix_translations('bookmarks') }}
{{ super() }}
diff --git a/bukuserver/translations/de/LC_MESSAGES/messages.po b/bukuserver/translations/de/LC_MESSAGES/messages.po
index a6ac1f2..a922f9b 100644
--- a/bukuserver/translations/de/LC_MESSAGES/messages.po
+++ b/bukuserver/translations/de/LC_MESSAGES/messages.po
@@ -158,11 +158,11 @@ msgstr "Schilder"
msgid "Description"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/forms.py:57
+#: /home/lex/Work/buku/bukuserver/forms.py:62
msgid "List of tags expected."
msgstr ""
-#: /home/lex/Work/buku/bukuserver/forms.py:84
+#: /home/lex/Work/buku/bukuserver/forms.py:89
msgid "Delete tags list from existing tags"
msgstr ""
@@ -206,7 +206,7 @@ msgid "Failed to create record."
msgstr ""
#: /home/lex/Work/buku/bukuserver/views.py:245
-#: /home/lex/Work/buku/bukuserver/views.py:546
+#: /home/lex/Work/buku/bukuserver/views.py:552
msgid "Failed to delete record."
msgstr ""
@@ -214,55 +214,55 @@ msgstr ""
msgid "Invalid search mode combination"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:342
+#: /home/lex/Work/buku/bukuserver/views.py:348
msgid "netloc match"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:375
+#: /home/lex/Work/buku/bukuserver/views.py:381
msgid "contain"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:376
+#: /home/lex/Work/buku/bukuserver/views.py:382
msgid "not contain"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:377
+#: /home/lex/Work/buku/bukuserver/views.py:383
msgid "number equal"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:378
+#: /home/lex/Work/buku/bukuserver/views.py:384
msgid "number not equal"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:379
+#: /home/lex/Work/buku/bukuserver/views.py:385
msgid "number greater than"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:380
+#: /home/lex/Work/buku/bukuserver/views.py:386
msgid "number smaller than"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:406
-#: /home/lex/Work/buku/bukuserver/views.py:564
+#: /home/lex/Work/buku/bukuserver/views.py:412
+#: /home/lex/Work/buku/bukuserver/views.py:570
msgid "Failed to update record."
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:421
+#: /home/lex/Work/buku/bukuserver/views.py:427
msgid "<UNTAGGED>"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:427
-#: /home/lex/Work/buku/bukuserver/views.py:464
+#: /home/lex/Work/buku/bukuserver/views.py:433
+#: /home/lex/Work/buku/bukuserver/views.py:470
msgctxt "tag"
msgid "Name"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:427
+#: /home/lex/Work/buku/bukuserver/views.py:433
msgctxt "tag"
msgid "Usage Count"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:527
+#: /home/lex/Work/buku/bukuserver/views.py:533
msgid "top most common"
msgstr ""
@@ -270,10 +270,38 @@ msgstr ""
msgid "Pick another"
msgstr ""
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:9
+msgid "Swap record #{} with record #"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:11
+msgid "Not a valid record index: '{}'"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:12
+msgid "There are only {} records in total!"
+msgstr ""
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:13
+msgid "Swapping a record with itself has no effect!"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:24
msgid "Random"
msgstr "Zufälliger"
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Swap with…"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move down"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move up"
+msgstr ""
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/home.html:14
msgid "Search bookmark"
msgstr ""
diff --git a/bukuserver/translations/fr/LC_MESSAGES/messages.po b/bukuserver/translations/fr/LC_MESSAGES/messages.po
index 1bc6d84..ac6c8e7 100644
--- a/bukuserver/translations/fr/LC_MESSAGES/messages.po
+++ b/bukuserver/translations/fr/LC_MESSAGES/messages.po
@@ -158,11 +158,11 @@ msgstr "Étiquettes"
msgid "Description"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/forms.py:57
+#: /home/lex/Work/buku/bukuserver/forms.py:62
msgid "List of tags expected."
msgstr ""
-#: /home/lex/Work/buku/bukuserver/forms.py:84
+#: /home/lex/Work/buku/bukuserver/forms.py:89
msgid "Delete tags list from existing tags"
msgstr ""
@@ -206,7 +206,7 @@ msgid "Failed to create record."
msgstr ""
#: /home/lex/Work/buku/bukuserver/views.py:245
-#: /home/lex/Work/buku/bukuserver/views.py:546
+#: /home/lex/Work/buku/bukuserver/views.py:552
msgid "Failed to delete record."
msgstr ""
@@ -214,55 +214,55 @@ msgstr ""
msgid "Invalid search mode combination"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:342
+#: /home/lex/Work/buku/bukuserver/views.py:348
msgid "netloc match"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:375
+#: /home/lex/Work/buku/bukuserver/views.py:381
msgid "contain"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:376
+#: /home/lex/Work/buku/bukuserver/views.py:382
msgid "not contain"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:377
+#: /home/lex/Work/buku/bukuserver/views.py:383
msgid "number equal"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:378
+#: /home/lex/Work/buku/bukuserver/views.py:384
msgid "number not equal"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:379
+#: /home/lex/Work/buku/bukuserver/views.py:385
msgid "number greater than"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:380
+#: /home/lex/Work/buku/bukuserver/views.py:386
msgid "number smaller than"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:406
-#: /home/lex/Work/buku/bukuserver/views.py:564
+#: /home/lex/Work/buku/bukuserver/views.py:412
+#: /home/lex/Work/buku/bukuserver/views.py:570
msgid "Failed to update record."
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:421
+#: /home/lex/Work/buku/bukuserver/views.py:427
msgid "<UNTAGGED>"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:427
-#: /home/lex/Work/buku/bukuserver/views.py:464
+#: /home/lex/Work/buku/bukuserver/views.py:433
+#: /home/lex/Work/buku/bukuserver/views.py:470
msgctxt "tag"
msgid "Name"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:427
+#: /home/lex/Work/buku/bukuserver/views.py:433
msgctxt "tag"
msgid "Usage Count"
msgstr ""
-#: /home/lex/Work/buku/bukuserver/views.py:527
+#: /home/lex/Work/buku/bukuserver/views.py:533
msgid "top most common"
msgstr ""
@@ -270,10 +270,38 @@ msgstr ""
msgid "Pick another"
msgstr ""
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:9
+msgid "Swap record #{} with record #"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:11
+msgid "Not a valid record index: '{}'"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:12
+msgid "There are only {} records in total!"
+msgstr ""
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:13
+msgid "Swapping a record with itself has no effect!"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:24
msgid "Random"
msgstr "Aléatoire"
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Swap with…"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move down"
+msgstr ""
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move up"
+msgstr ""
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/home.html:14
msgid "Search bookmark"
msgstr ""
diff --git a/bukuserver/translations/ru/LC_MESSAGES/messages.mo b/bukuserver/translations/ru/LC_MESSAGES/messages.mo
index 67ae319..f909f5a 100644
--- a/bukuserver/translations/ru/LC_MESSAGES/messages.mo
+++ b/bukuserver/translations/ru/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/bukuserver/translations/ru/LC_MESSAGES/messages.po b/bukuserver/translations/ru/LC_MESSAGES/messages.po
index 6f18bbe..c3c2f1f 100644
--- a/bukuserver/translations/ru/LC_MESSAGES/messages.po
+++ b/bukuserver/translations/ru/LC_MESSAGES/messages.po
@@ -158,11 +158,11 @@ msgstr "Теги"
msgid "Description"
msgstr "Описание"
-#: /home/lex/Work/buku/bukuserver/forms.py:57
+#: /home/lex/Work/buku/bukuserver/forms.py:62
msgid "List of tags expected."
msgstr "Список ожидаемых тегов"
-#: /home/lex/Work/buku/bukuserver/forms.py:84
+#: /home/lex/Work/buku/bukuserver/forms.py:89
msgid "Delete tags list from existing tags"
msgstr "Удалить список тегов из существующих"
@@ -206,7 +206,7 @@ msgid "Failed to create record."
msgstr "Ошибка создания записи."
#: /home/lex/Work/buku/bukuserver/views.py:245
-#: /home/lex/Work/buku/bukuserver/views.py:546
+#: /home/lex/Work/buku/bukuserver/views.py:552
msgid "Failed to delete record."
msgstr "Ошибка удаления записи."
@@ -214,55 +214,55 @@ msgstr "Ошибка удаления записи."
msgid "Invalid search mode combination"
msgstr "Некорректная комбинация фильтров поиска"
-#: /home/lex/Work/buku/bukuserver/views.py:342
+#: /home/lex/Work/buku/bukuserver/views.py:348
msgid "netloc match"
msgstr "на сайт"
-#: /home/lex/Work/buku/bukuserver/views.py:375
+#: /home/lex/Work/buku/bukuserver/views.py:381
msgid "contain"
msgstr "содержат"
-#: /home/lex/Work/buku/bukuserver/views.py:376
+#: /home/lex/Work/buku/bukuserver/views.py:382
msgid "not contain"
msgstr "не содержат"
-#: /home/lex/Work/buku/bukuserver/views.py:377
+#: /home/lex/Work/buku/bukuserver/views.py:383
msgid "number equal"
msgstr "количество равно"
-#: /home/lex/Work/buku/bukuserver/views.py:378
+#: /home/lex/Work/buku/bukuserver/views.py:384
msgid "number not equal"
msgstr "количество не равно"
-#: /home/lex/Work/buku/bukuserver/views.py:379
+#: /home/lex/Work/buku/bukuserver/views.py:385
msgid "number greater than"
msgstr "количество больше чем"
-#: /home/lex/Work/buku/bukuserver/views.py:380
+#: /home/lex/Work/buku/bukuserver/views.py:386
msgid "number smaller than"
msgstr "количество меньше чем"
-#: /home/lex/Work/buku/bukuserver/views.py:406
-#: /home/lex/Work/buku/bukuserver/views.py:564
+#: /home/lex/Work/buku/bukuserver/views.py:412
+#: /home/lex/Work/buku/bukuserver/views.py:570
msgid "Failed to update record."
msgstr "Ошибка обновления записи."
-#: /home/lex/Work/buku/bukuserver/views.py:421
+#: /home/lex/Work/buku/bukuserver/views.py:427
msgid "<UNTAGGED>"
msgstr "<БЕЗ ТЕГОВ>"
-#: /home/lex/Work/buku/bukuserver/views.py:427
-#: /home/lex/Work/buku/bukuserver/views.py:464
+#: /home/lex/Work/buku/bukuserver/views.py:433
+#: /home/lex/Work/buku/bukuserver/views.py:470
msgctxt "tag"
msgid "Name"
msgstr "Тег"
-#: /home/lex/Work/buku/bukuserver/views.py:427
+#: /home/lex/Work/buku/bukuserver/views.py:433
msgctxt "tag"
msgid "Usage Count"
msgstr "Число закладок"
-#: /home/lex/Work/buku/bukuserver/views.py:527
+#: /home/lex/Work/buku/bukuserver/views.py:533
msgid "top most common"
msgstr "самое распространённое"
@@ -270,10 +270,38 @@ msgstr "самое распространённое"
msgid "Pick another"
msgstr "Показать другую"
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:9
+msgid "Swap record #{} with record #"
+msgstr "Поменять местами запись #{} с записью #"
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:11
+msgid "Not a valid record index: '{}'"
+msgstr "Некорректный номер строки: '{}'"
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:12
+msgid "There are only {} records in total!"
+msgstr "Всего существует только {} записей!"
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:13
+msgid "Swapping a record with itself has no effect!"
+msgstr "Попытка поменять запись местами саму с собой ничего не даст!"
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:24
msgid "Random"
msgstr "Случайная"
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Swap with…"
+msgstr "Поменять местами с…"
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move down"
+msgstr "Сдвинуть вниз"
+
+#: /home/lex/Work/buku/bukuserver/templates/bukuserver/bookmarks_list.html:37
+msgid "Move up"
+msgstr "Сдвинуть вверх"
+
#: /home/lex/Work/buku/bukuserver/templates/bukuserver/home.html:14
msgid "Search bookmark"
msgstr "Искать закладку"
diff --git a/bukuserver/views.py b/bukuserver/views.py
index 8c37c8a..733de6e 100644
--- a/bukuserver/views.py
+++ b/bukuserver/views.py
@@ -297,6 +297,12 @@ class BookmarkModelView(BaseModelView, ApplyFiltersMixin):
def get_pk_value(self, model):
return model.id
+ @expose('/swap', methods=['POST'])
+ def swap(self):
+ form = forms.SwapForm()
+ self.bukudb.swap_recs(form.id1.data, form.id2.data)
+ return redirect(request.form.get('url', url_for('bookmark.index_view')))
+
def scaffold_list_columns(self):
return [x.name.lower() for x in BookmarkField]
diff --git a/tests/test_bukuDb.py b/tests/test_bukuDb.py
index 4c036fb..55a4a00 100644
--- a/tests/test_bukuDb.py
+++ b/tests/test_bukuDb.py
@@ -208,8 +208,15 @@ class TestBukuDb(unittest.TestCase):
for pair in zip(from_db[1:], bookmark):
self.assertEqual(*pair)
- # TODO: tags should be passed to the api as a sequence...
+ def test_swap_recs(self):
+ for bookmark in self.bookmarks:
+ _add_rec(self.bdb, *bookmark)
+ for id1, id2 in [(0, 1), (1, 4), (1, 1)]:
+ self.assertFalse(self.bdb.swap_recs(id1, id2), 'Not a valid index pair: (%d, %d)' % (id1, id2))
+ self.assertTrue(self.bdb.swap_recs(1, 3), 'This one should be valid') # 3, 2, 1
+ self.assertEqual([x[0] for x in reversed(self.bookmarks)], [x.url for x in self.bdb.get_rec_all()])
+ # TODO: tags should be passed to the api as a sequence...
def test_suggest_tags(self):
for bookmark in self.bookmarks:
_add_rec(self.bdb, *bookmark)