From f301ca59b72c1f15e4dbfdd8933d7b350c16c6b6 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:21:24 +0300 Subject: [PATCH] Bookmarks: icon by type, combined view, filter, bulk remove (#8347) - Add an icon to distinguish between page bookmarks, plain highlights, and highlights with an added note - Bookmark details: show both highlighted text and added note - Bookmark list: allow filtering by type and/or by keyword - New bookmark selection mode, to allow multiple removals - New option: show separator line --- .../apps/reader/modules/readerbookmark.lua | 351 ++++++++++++++---- 1 file changed, 287 insertions(+), 64 deletions(-) diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 53aee9a6c..b6fa36d0e 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -1,5 +1,7 @@ local BD = require("ui/bidi") +local Blitbuffer = require("ffi/blitbuffer") local CenterContainer = require("ui/widget/container/centercontainer") +local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") local Event = require("ui/event") @@ -10,17 +12,22 @@ local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local TextViewer = require("ui/widget/textviewer") local UIManager = require("ui/uimanager") +local Utf8Proc = require("ffi/utf8proc") local logger = require("logger") local util = require("util") local _ = require("gettext") +local N_ = _.ngettext local Screen = require("device").screen local T = require("ffi/util").template -local PAGE_BOOKMARK_DISPLAY_PREFIX = "★ " -- distinguish page bookmarks from highlights and notes +-- mark the type of a bookmark with a symbol + non-expandable space +local DISPLAY_PREFIX = { + highlight = "\u{2592}\u{2002}", -- "medium shade" + note = "\u{F040}\u{2002}", -- "pencil" + bookmark = "\u{F097}\u{2002}", -- "empty bookmark" +} local ReaderBookmark = InputContainer:new{ - bm_menu_title = _("Bookmarks"), - bbm_menu_title = _("Bookmark browsing mode"), bookmarks_items_per_page_default = 14, bookmarks = nil, } @@ -52,9 +59,8 @@ function ReaderBookmark:init() end function ReaderBookmark:addToMainMenu(menu_items) - -- insert table to main reader menu menu_items.bookmarks = { - text = self.bm_menu_title, + text = _("Bookmarks"), callback = function() self:onShowBookmark() end, @@ -69,7 +75,7 @@ function ReaderBookmark:addToMainMenu(menu_items) end if self.ui.document.info.has_pages then menu_items.bookmark_browsing_mode = { - text = self.bbm_menu_title, + text = _("Bookmark browsing mode"), checked_func = function() return self.ui.paging.bookmark_flipping_mode end, callback = function(touchmenu_instance) self:enableBookmarkBrowsingMode() @@ -140,12 +146,12 @@ function ReaderBookmark:addToMainMenu(menu_items) end }, { - text = _("Add page number / timestamp to bookmark"), + text = _("Show separator between items"), checked_func = function() - return G_reader_settings:nilOrTrue("bookmarks_items_auto_text") + return G_reader_settings:isTrue("bookmarks_items_show_separator") end, callback = function() - G_reader_settings:flipNilOrTrue("bookmarks_items_auto_text") + G_reader_settings:flipNilOrFalse("bookmarks_items_show_separator") end }, { @@ -157,6 +163,15 @@ function ReaderBookmark:addToMainMenu(menu_items) G_reader_settings:flipNilOrTrue("bookmarks_items_reverse_sorting") end }, + { + text = _("Add page number / timestamp to bookmark"), + checked_func = function() + return G_reader_settings:nilOrTrue("bookmarks_items_auto_text") + end, + callback = function() + G_reader_settings:flipNilOrTrue("bookmarks_items_auto_text") + end + }, }, } end @@ -347,29 +362,42 @@ function ReaderBookmark:updateHighlightsIfNeeded() end function ReaderBookmark:onShowBookmark() + self.select_mode = false + self.filtered_mode = false self:updateHighlightsIfNeeded() -- build up item_table local item_table = {} local is_reverse_sorting = G_reader_settings:nilOrTrue("bookmarks_items_reverse_sorting") local num = #self.bookmarks + 1 for i, v in ipairs(self.bookmarks) do + local is_auto_text if v.text == nil or v.text == "" then + is_auto_text = true v.text = self:getBookmarkAutoText(v) + else + is_auto_text = self:isBookmarkAutoText(v) end -- bookmarks are internally sorted by descending page numbers local k = is_reverse_sorting and i or num - i item_table[k] = util.tableDeepCopy(v) - item_table[k].text_orig = v.text or v.notes - item_table[k].text = item_table[k].text_orig - if not v.highlighted then -- page bookmark - item_table[k].text = PAGE_BOOKMARK_DISPLAY_PREFIX .. item_table[k].text + if v.highlighted then + if is_auto_text then + item_table[k].type = "highlight" + else + item_table[k].type = "note" + end + else + item_table[k].type = "bookmark" end + item_table[k].text_orig = v.text or v.notes + item_table[k].text = DISPLAY_PREFIX[item_table[k].type] .. item_table[k].text_orig item_table[k].mandatory = self:getBookmarkPageString(v.page) end local items_per_page = G_reader_settings:readSetting("bookmarks_items_per_page") local items_font_size = G_reader_settings:readSetting("bookmarks_items_font_size", Menu.getItemFontSize(items_per_page)) local multilines_show_more_text = G_reader_settings:isTrue("bookmarks_items_multilines_show_more_text") + local show_separator = G_reader_settings:isTrue("bookmarks_items_show_separator") local bm_menu = Menu:new{ title = _("Bookmarks"), @@ -381,7 +409,7 @@ function ReaderBookmark:onShowBookmark() items_per_page = items_per_page, items_font_size = items_font_size, multilines_show_more_text = multilines_show_more_text, - line_color = require("ffi/blitbuffer").COLOR_WHITE, + line_color = show_separator and Blitbuffer.COLOR_DARK_GRAY or Blitbuffer.COLOR_WHITE, on_close_ges = { GestureRange:new{ ges = "two_finger_swipe", @@ -403,65 +431,255 @@ function ReaderBookmark:onShowBookmark() -- buid up menu widget method as closure local bookmark = self - function bm_menu:onMenuChoice(item) - bookmark.ui.link:addCurrentLocationToStack() - bookmark:gotoBookmark(item.page, item.pos0) + function bm_menu:onMenuSelect(item) + if self.select_mode then + if item.dim then + item.dim = nil + self.select_count = self.select_count - 1 + else + item.dim = true + self.select_count = self.select_count + 1 + end + bm_menu:updateItems() + else + bookmark.ui.link:addCurrentLocationToStack() + bookmark:gotoBookmark(item.page, item.pos0) + bm_menu.close_callback() + end end function bm_menu:onMenuHold(item) - self.textviewer = TextViewer:new{ - title = _("Bookmark details"), - text = item.notes, - width = self.textviewer_width, - height = self.textviewer_height, - buttons_table = { - { - { - text = _("Rename this bookmark"), + if self.select_mode then + local info_text + if self.select_count == 0 then + info_text = _("No bookmarks selected") + else + info_text = T(N_("Remove selected bookmark?", "Remove %1 selected bookmarks?", self.select_count), self.select_count) + end + UIManager:show(ConfirmBox:new{ + text = info_text, + ok_text = _("Remove"), + ok_callback = function() + self.select_mode = false + for i = #item_table, 1, -1 do + if item_table[i].dim then + bookmark:removeHighlight(item_table[i]) + table.remove(item_table, i) + end + end + bm_menu:switchItemTable(self.filtered_mode and _("Bookmarks (filtered)") or _("Bookmarks"), item_table, -1) + end, + other_buttons_first = true, + other_buttons = { + {{ + text = _("Deselect all"), callback = function() - bookmark:renameBookmark(item) - UIManager:close(self.textviewer) + for _, v in ipairs(item_table) do + if v.dim then + v.dim = nil + end + end + self.select_count = 0 + bm_menu:updateItems() end, }, { - text = _("Remove this bookmark"), + text = _("Select all"), callback = function() - UIManager:show(ConfirmBox:new{ - text = _("Remove this bookmark?"), - cancel_text = _("Cancel"), - cancel_callback = function() - return - end, - ok_text = _("Remove"), - ok_callback = function() - bookmark:removeHighlight(item) - -- Also update item_table, so we don't have to rebuilt it in full - for k, v in pairs(item_table) do - if v == item then - table.remove(item_table, k) - break - end - end - bm_menu:switchItemTable(nil, item_table, -1) - UIManager:close(self.textviewer) - end, - }) + for _, v in ipairs(item_table) do + v.dim = true + end + self.select_count = #item_table + bm_menu:updateItems() + end, + }}, + {{ + text = _("Exit select mode"), + callback = function() + for _, v in ipairs(item_table) do + if v.dim then + v.dim = nil + end + end + self.select_mode = false + bm_menu:switchItemTable(self.filtered_mode and _("Bookmarks (filtered)") or _("Bookmarks"), item_table) end, }, - }, - { { - text = _("Close"), - is_enter_default = true, + text = _("Select page"), callback = function() - UIManager:close(self.textviewer) + local item_first = (bm_menu.page - 1) * bm_menu.perpage + 1 + local item_last = math.min(item_first + bm_menu.perpage - 1, #item_table) + for i = item_first, item_last do + if item_table[i].dim == nil then + item_table[i].dim = true + self.select_count = self.select_count + 1 + end + end + bm_menu:updateItems() end, - }, + }}, }, + }) + else + local bm_view = T(_("Page: %1"), item.mandatory) .. " " .. T(_("Time: %1"), item.datetime) .. "\n\n" + if item.type == "bookmark" then + bm_view = bm_view .. item.text + else + bm_view = bm_view .. DISPLAY_PREFIX["highlight"] .. item.notes + if item.type == "note" then + bm_view = bm_view .. "\n\n" .. item.text + end + end + self.textviewer = TextViewer:new{ + title = _("Bookmark details"), + text = bm_view, + justified = G_reader_settings:nilOrTrue("dict_justify"), + buttons_table = { + { + { + text = _("Remove bookmark"), + callback = function() + UIManager:show(ConfirmBox:new{ + text = _("Remove bookmark?"), + ok_text = _("Remove"), + ok_callback = function() + bookmark:removeHighlight(item) + -- Also update item_table, so we don't have to rebuilt it in full + for k, v in ipairs(item_table) do + if item.datetime == v.datetime and item.page == v.page then + table.remove(item_table, k) + break + end + end + bm_menu:switchItemTable(nil, item_table, -1) + UIManager:close(self.textviewer) + end, + }) + end, + }, + { + text = bookmark:isHighlightAutoText(item) and _("Add note") or _("Edit note"), + callback = function() + bookmark:renameBookmark(item) + UIManager:close(self.textviewer) + end, + }, + }, + {}, + { + { + text = _("Bulk remove"), + callback = function() + self.select_mode = true + self.select_count = 0 + UIManager:close(self.textviewer) + bm_menu:switchItemTable(_("Bookmarks (select mode)"), item_table) + end, + }, + { + text = _("Filter bookmarks"), + callback = function() + UIManager:close(self.textviewer) + local input_dialog, check_button_bookmark, check_button_highlight, check_button_note + input_dialog = InputDialog:new{ + title = _("Filter bookmarks"), + input_hint = _("(containing text)"), + buttons = { + { + { + text = _("Close"), + callback = function() + UIManager:close(input_dialog) + end, + }, + { + text = _("Apply"), + is_enter_default = true, + callback = function() + if check_button_bookmark.checked + or check_button_highlight.checked + or check_button_note.checked then + local search_str = input_dialog:getInputText() + local is_search_str = false + if search_str ~= "" then + is_search_str = true + search_str = Utf8Proc.lowercase(util.fixUtf8(search_str, "?")) + end + for i = #item_table, 1, -1 do + local bm_item = item_table[i] + if (check_button_bookmark.checked and bm_item.type == "bookmark") + or (check_button_highlight.checked and bm_item.type == "highlight") + or (check_button_note.checked and bm_item.type == "note") then + if is_search_str then + local bm_text = bm_item.notes .. bm_item.text + bm_text = Utf8Proc.lowercase(util.fixUtf8(bm_text, "?")) + if not bm_text:find(search_str) then + table.remove(item_table, i) + end + end + else + table.remove(item_table, i) + end + end + UIManager:close(input_dialog) + bm_menu:switchItemTable(_("Bookmarks (filtered)"), item_table) + self.filtered_mode = true + end + end, + }, + }, + }, + } + check_button_highlight = CheckButton:new{ + text = " " .. DISPLAY_PREFIX["highlight"] .. _("highlights"), + checked = true, + parent = input_dialog, + max_width = input_dialog._input_widget.width, + callback = function() + check_button_highlight:toggleCheck() + end, + } + input_dialog:addWidget(check_button_highlight) + check_button_note = CheckButton:new{ + text = " " .. DISPLAY_PREFIX["note"] .. _("notes"), + checked = true, + parent = input_dialog, + max_width = input_dialog._input_widget.width, + callback = function() + check_button_note:toggleCheck() + end, + } + input_dialog:addWidget(check_button_note) + check_button_bookmark = CheckButton:new{ + text = " " .. DISPLAY_PREFIX["bookmark"] .. _("page bookmarks"), + checked = true, + parent = input_dialog, + max_width = input_dialog._input_widget.width, + callback = function() + check_button_bookmark:toggleCheck() + end, + } + input_dialog:addWidget(check_button_bookmark) + UIManager:show(input_dialog) + input_dialog:onShowKeyboard() + end, + }, + }, + { + { + text = _("Close"), + is_enter_default = true, + callback = function() + UIManager:close(self.textviewer) + end, + }, + }, + } } - } - UIManager:show(self.textviewer) - return true + UIManager:show(self.textviewer) + return true + end end bm_menu.close_callback = function() @@ -645,8 +863,8 @@ function ReaderBookmark:renameBookmark(item, from_highlight) bookmark = item end self.input = InputDialog:new{ - title = _("Rename bookmark"), - description = T(" " .. _("Page: %1") .. " " .. _("Time: %2"), bookmark.mandatory, bookmark.datetime), + title = _("Edit note"), + description = " " .. T(_("Page: %1"), bookmark.mandatory) .. " " .. T(_("Time: %1"), bookmark.datetime), input = bookmark.text_orig, allow_newline = true, cursor_at_end = false, @@ -660,12 +878,19 @@ function ReaderBookmark:renameBookmark(item, from_highlight) end, }, { - text = _("Rename"), + text = _("Save"), is_enter_default = true, callback = function() local value = self.input:getInputValue() if value == "" then -- blank input resets the 'text' field to auto-text value = self:getBookmarkAutoText(bookmark) + if bookmark.type == "note" then + bookmark.type = "highlight" + end + else + if bookmark.type == "highlight" then + bookmark.type = "note" + end end for __, bm in ipairs(self.bookmarks) do if bookmark.datetime == bm.datetime and bookmark.page == bm.page then @@ -681,9 +906,7 @@ function ReaderBookmark:renameBookmark(item, from_highlight) end UIManager:close(self.input) if not from_highlight then - if not bookmark.highlighted then - bookmark.text = PAGE_BOOKMARK_DISPLAY_PREFIX .. bookmark.text - end + bookmark.text = DISPLAY_PREFIX[bookmark.type] .. bookmark.text self.refresh() end break