diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 8dc63bdd2..cbad29e60 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -10,6 +10,7 @@ local TimeVal = require("ui/timeval") local Translator = require("ui/translator") local UIManager = require("ui/uimanager") local logger = require("logger") +local util = require("util") local _ = require("gettext") local C_ = _.pgettext local T = require("ffi/util").template @@ -797,30 +798,6 @@ function ReaderHighlight:lookup(selected_word, selected_link) end end -local function prettifyCss(css_text) - -- This is not perfect, but enough to make some ugly CSS readable. - -- Get rid of \t so we can use it as a replacement/hiding char - css_text = css_text:gsub("\t", " ") - -- Wrap and indent declarations - css_text = css_text:gsub("%s*{%s*", " {\n ") - css_text = css_text:gsub(";%s*}%s*", ";\n}\n") - css_text = css_text:gsub(";%s*([^}])", ";\n %1") - css_text = css_text:gsub("%s*}%s*", "\n}\n") - -- Cleanup declarations - css_text = css_text:gsub("{[^}]*}", function(s) - s = s:gsub("%s*:%s*", ": ") - -- Temporarily hide/replace ',' in declaration so they - -- are not matched and made multi-lines by followup gsub - s = s:gsub("%s*,%s*", "\t") - return s - end) - -- Have each selector (separated by ',') on a new line - css_text = css_text:gsub("%s*,%s*", " ,\n") - -- Restore hidden ',' in declarations - css_text = css_text:gsub("\t", ", ") - return css_text -end - function ReaderHighlight:viewSelectionHTML(debug_view) if self.ui.document.info.has_pages then return @@ -858,7 +835,7 @@ function ReaderHighlight:viewSelectionHTML(debug_view) -- the height of this section, we don't want to have to scroll -- many pages to get to the HTML content on the initial view.) html = html:gsub("(]*>)%s*(.-)%s*()", function(pre, css_text, post) - return pre .. "\n" .. prettifyCss(css_text) .. post + return pre .. "\n" .. util.prettifyCSS(css_text) .. post end) end local TextViewer = require("ui/widget/textviewer") @@ -887,7 +864,7 @@ function ReaderHighlight:viewSelectionHTML(debug_view) UIManager:close(cssviewer) UIManager:show(TextViewer:new{ title = css_files[i], - text = prettifyCss(css_text), + text = util.prettifyCSS(css_text), text_face = Font:getFace("smallinfont"), justified = false, para_direction_rtl = false, @@ -1248,7 +1225,7 @@ function ReaderHighlight:onHighlightSearch() logger.dbg("search highlight") self:highlightFromHoldPos() if self.selected_text then - local text = require("util").stripPunctuation(self.selected_text.text) + local text = util.stripPunctuation(self.selected_text.text) self.ui:handleEvent(Event:new("ShowSearchDialog", text)) end end diff --git a/frontend/apps/reader/modules/readerstyletweak.lua b/frontend/apps/reader/modules/readerstyletweak.lua index e3b2a18a3..28dee9e49 100644 --- a/frontend/apps/reader/modules/readerstyletweak.lua +++ b/frontend/apps/reader/modules/readerstyletweak.lua @@ -13,6 +13,7 @@ local GestureRange = require("ui/gesturerange") local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local MovableContainer = require("ui/widget/container/movablecontainer") +local Notification = require("ui/widget/notification") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") local UIManager = require("ui/uimanager") @@ -37,7 +38,7 @@ local TweakInfoWidget = InputContainer:new{ function TweakInfoWidget:init() local tweak = self.tweak if Device:isTouchDevice() then - self.ges_events.TapClose = { + self.ges_events.Tap = { GestureRange:new{ ges = "tap", range = Geom:new{ @@ -90,17 +91,19 @@ function TweakInfoWidget:init() f:close() end end - css = css:gsub("^%s+", ""):gsub("%s+$", "") - table.insert(content, FrameContainer:new{ + self.css_text = css:gsub("^%s+", ""):gsub("%s+$", "") + self.css_frame = FrameContainer:new{ bordersize = Size.border.thin, padding = Size.padding.large, TextBoxWidget:new{ - text = css, + text = self.css_text, face = Font:getFace("infont", 16), width = self.width - 2*Size.padding.large, para_direction_rtl = false, -- LTR } - }) + } + table.insert(content, self.css_frame) + if self.is_global_default then table.insert(content, VerticalSpan:new{ width = Size.padding.large, @@ -183,8 +186,19 @@ function TweakInfoWidget:onClose() return true end -function TweakInfoWidget:onTapClose(arg, ges) - if ges.pos:notIntersectWith(self.movable.dimen) then +function TweakInfoWidget:onTap(arg, ges) + if ges.pos:intersectWith(self.css_frame.dimen) and Device:hasClipboard() then + -- Tap inside CSS text copies it into clipboard (so it + -- can be pasted into the book-specific tweak editor) + -- (Add \n on both sides for easier pasting) + Device.input.setClipboardText("\n"..self.css_text.."\n") + UIManager:show(Notification:new{ + text = _("CSS text copied to clipboard"), + timeout = 2 + }) + return true + elseif ges.pos:notIntersectWith(self.movable.dimen) then + -- Tap outside closes widget self:onClose() return true end @@ -298,6 +312,10 @@ function ReaderStyleTweak:updateCssText(apply) css = css:gsub("^%s+", ""):gsub("%s+$", "") table.insert(css_snippets, css) end + if self.book_style_tweak and self.book_style_tweak_enabled then + self.nb_enabled_tweaks = self.nb_enabled_tweaks + 1 + table.insert(css_snippets, self.book_style_tweak) + end self.css_text = table.concat(css_snippets, "\n") logger.dbg("made tweak css:\n".. self.css_text .. "[END]") else @@ -315,6 +333,9 @@ function ReaderStyleTweak:onReadSettings(config) -- Default globally enabled style tweaks (for new installations) -- are defined in css_tweaks.lua self.global_tweaks = G_reader_settings:readSetting("style_tweaks") or CssTweaks.DEFAULT_GLOBAL_STYLE_TWEAKS + self.book_style_tweak = config:readSetting("book_style_tweak") -- string or nil + self.book_style_tweak_enabled = config:readSetting("book_style_tweak_enabled") + self.book_style_tweak_last_edit_pos = config:readSetting("book_style_tweak_last_edit_pos") self:updateCssText() end @@ -326,6 +347,9 @@ function ReaderStyleTweak:onSaveSettings() end self.ui.doc_settings:saveSetting("style_tweaks", util.tableSize(self.doc_tweaks) > 0 and self.doc_tweaks or nil) G_reader_settings:saveSetting("style_tweaks", self.global_tweaks) + self.ui.doc_settings:saveSetting("book_style_tweak", self.book_style_tweak) + self.ui.doc_settings:saveSetting("book_style_tweak_enabled", self.book_style_tweak_enabled) + self.ui.doc_settings:saveSetting("book_style_tweak_last_edit_pos", self.book_style_tweak_last_edit_pos) end function ReaderStyleTweak:init() @@ -363,11 +387,12 @@ You can enable individual tweaks on this book with a tap, or view more details a -- css_tweaks.lua, or like the one we build from user styletweaks -- directory files and sub-directories) local addTweakMenuItem - addTweakMenuItem = function(menu, item) + addTweakMenuItem = function(menu, item, max_per_page) if type(item) == "table" and #item > 0 then -- sub-menu local sub_item_table = {} + sub_item_table.max_per_page = max_per_page for _, it in ipairs(item) do - addTweakMenuItem(sub_item_table, it) -- recurse + addTweakMenuItem(sub_item_table, it, max_per_page) -- recurse end table.insert(menu, { text_func = function() @@ -515,7 +540,36 @@ You can enable individual tweaks on this book with a tap, or view more details a local if_empty_menu_title = _("Add your own tweaks in koreader/styletweaks/") process_tweaks_dir(user_styletweaks_dir, user_tweaks_table, if_empty_menu_title) self.tweaks_table[#self.tweaks_table].separator = true - addTweakMenuItem(self.tweaks_table, user_tweaks_table) + addTweakMenuItem(self.tweaks_table, user_tweaks_table, 6) + -- limit to 6 user tweaks per page + + -- Book-specific editable tweak + self.tweaks_table[#self.tweaks_table].separator = true + local book_tweak_item = { + text_func = function() + if self.book_style_tweak then + return _("Book-specific tweak (hold to edit)") + else + return _("Book-specific tweak") + end + end, + enabled_func = function() return self.enabled end, + checked_func = function() return self.book_style_tweak_enabled end, + callback = function(touchmenu_instance) + if self.book_style_tweak then + -- There is a tweak: toggle it on tap, like other tweaks + self.book_style_tweak_enabled = not self.book_style_tweak_enabled + self:updateCssText(true) -- apply it immediately + else + -- No tweak defined: launch editor + self:editBookTweak(touchmenu_instance) + end + end, + hold_callback = function(touchmenu_instance) + self:editBookTweak(touchmenu_instance) + end, + } + table.insert(self.tweaks_table, book_tweak_item) self.ui.menu:registerToMainMenu(self) end @@ -534,4 +588,207 @@ function ReaderStyleTweak:addToMainMenu(menu_items) } end +local BOOK_TWEAK_SAMPLE_CSS = [[ +p.someTitleClassName { text-indent: 0; } + +DIV.advertisement { display: none !important; } + +.footnoteContainerClassName { + font-size: 80%; + margin: 0 !important; + -cr-hint: footnote-inpage; +} +]] + +local BOOK_TWEAK_INPUT_HINT = T([[ +/* %1 */ + +%2]], _("You can add CSS snippets which will be applied only to this book."), BOOK_TWEAK_SAMPLE_CSS) + +function ReaderStyleTweak:editBookTweak(touchmenu_instance) + local InputDialog = require("ui/widget/inputdialog") + local editor -- our InputDialog instance + local tweak_button_id = "editBookTweakButton" + -- We add a button on the left, which can have 3 states/labels: + local BUTTON_USE_SAMPLE = _("Use sample") + local BUTTON_PRETTIFY = _("Prettify") + local BUTTON_CONDENSE = _("Condense") + -- Initial button state differs whether we already have some CSS content + local tweak_button_state = self.book_style_tweak and BUTTON_PRETTIFY or BUTTON_USE_SAMPLE + local toggle_tweak_button = function(state) + if state then -- use provided state + tweak_button_state = state + else -- natural toggling + if tweak_button_state == BUTTON_USE_SAMPLE then + tweak_button_state = BUTTON_PRETTIFY + elseif tweak_button_state == BUTTON_PRETTIFY then + tweak_button_state = BUTTON_CONDENSE + elseif tweak_button_state == BUTTON_CONDENSE then + tweak_button_state = BUTTON_PRETTIFY + end + end + local tweak_button = editor.button_table:getButtonById(tweak_button_id) + tweak_button:init() + editor:refreshButtons() + end + -- The Save and Close buttons default behaviour, how they trigger + -- the callbacks and how they show or not a notification, is not + -- the most convenient here. We try to tweak that a bit. + local SAVE_BUTTON_LABEL + if self.book_style_tweak_enabled or not self.book_style_tweak then + SAVE_BUTTON_LABEL = _("Apply") + else + SAVE_BUTTON_LABEL = _("Save") + end + -- This message might be shown by multiple notifications at the + -- same time: having it similar will make that unnoticed. + local NOT_MODIFIED_MSG = _("Book tweak not modified.") + editor = InputDialog:new{ + title = _("Edit book-specific style tweak"), + input = self.book_style_tweak or "", + input_hint = BOOK_TWEAK_INPUT_HINT, + input_face = Font:getFace("infont", 16), -- same as in TweakInfoWidget + para_direction_rtl = false, + lang = "en", + fullscreen = true, + condensed = true, + allow_newline = true, + cursor_at_end = false, + add_nav_bar = true, + scroll_by_pan = true, + buttons = {{ + -- First button on first row (row will be completed with Reset|Save|Close) + { + id = tweak_button_id, + text_func = function() + return tweak_button_state -- usable as a label + end, + callback = function() + if tweak_button_state == BUTTON_USE_SAMPLE then + editor:setInputText(BOOK_TWEAK_SAMPLE_CSS, true) + -- will have edited_callback() called, which will do toggle_tweak_button() + else + local css_text = editor:getInputText() + css_text = util.prettifyCSS(css_text, tweak_button_state == BUTTON_CONDENSE) + editor:setInputText(css_text, true) + toggle_tweak_button() + end + end, + }, + }}, + edited_callback = function() + if not editor then + -- We might be called while the InputDialog is being + -- initialized (so not yet assigned to 'editor') + return + end + if #editor:getInputText() == 0 then + -- No content: show "Use sample" + if tweak_button_state ~= BUTTON_USE_SAMPLE then + toggle_tweak_button(BUTTON_USE_SAMPLE) + end + else + -- Some content: get rid of "Use sample" to not risk + -- overriding content + if tweak_button_state == BUTTON_USE_SAMPLE then + toggle_tweak_button() + end + end + end, + -- Set/save view and cursor position callback + view_pos_callback = function(top_line_num, charpos) + -- This same callback is called with no argument to get initial position, + -- and with arguments to give back final position when closed. + if top_line_num and charpos then + self.book_style_tweak_last_edit_pos = {top_line_num, charpos} + else + local prev_pos = self.book_style_tweak_last_edit_pos + if type(prev_pos) == "table" and prev_pos[1] and prev_pos[2] then + return prev_pos[1], prev_pos[2] + end + return nil, nil -- no previous position known + end + end, + reset_button_text = _("Restore"), + reset_callback = function(content) -- Will add a Reset button + return self.book_style_tweak or "", _("Book tweak restored") + end, + save_button_text = SAVE_BUTTON_LABEL, + close_save_button_text = SAVE_BUTTON_LABEL, + save_callback = function(content, closing) -- Will add Save/Close buttons + if content and content == "" then + content = nil -- we store nil when empty + end + local was_empty = self.book_style_tweak == nil + local is_empty = content == nil + local tweak_updated = content ~= self.book_style_tweak + local should_apply = false + local msg -- returned and shown as a notification by InputDialog + if was_empty and not is_empty then + -- Tweak was empty, and so just created: enable book tweak + -- so it's immediately applied, and checked in the menu + self.book_style_tweak_enabled = true + should_apply = true + msg = _("Book tweak created, applying…") + elseif is_empty then + if not was_empty and self.book_style_tweak_enabled then + -- Tweak was enabled, but has been emptied: make it + -- disabled in the menu, but apply CSS without it + should_apply = true + msg = _("Book tweak removed, rendering…") + else + msg = _("Book tweak emptied and removed.") + end + self.book_style_tweak_enabled = false + elseif tweak_updated then + if self.book_style_tweak_enabled then + should_apply = true + msg = _("Book tweak updated, applying…") + else + msg = _("Book tweak saved (not enabled).") + end + else + msg = NOT_MODIFIED_MSG + end + self.book_style_tweak = content + -- We always close the editor when this callback is called. + -- If closing=true, InputDialog will call close_callback(). + -- If not, let's do it ourselves. + if not closing then + UIManager:close(editor) + end + if should_apply then + -- Let menu be closed and previous page be refreshed, + -- so one can see how the text is changed by the tweak. + touchmenu_instance:closeMenu() + UIManager:scheduleIn(0.2, function() + self:updateCssText(true) -- have it applied + end) + else + touchmenu_instance:updateItems() + end + editor.save_callback_called = true + return true, msg + end, + close_callback = function() + -- save_callback() will always have shown some notification, + -- so don't add another one + if not editor.save_callback_called then + UIManager:show(Notification:new{ + text = NOT_MODIFIED_MSG, + timeout = 2, + }) + -- This has to be the same message above and below: when + -- discarding, we can't prevent these 2 notifications from + -- being shown: having them identical will hide that. + end + end, + close_discarded_notif_text = NOT_MODIFIED_MSG; + + } + UIManager:show(editor) + editor:onShowKeyboard(true) + -- ignore first hold release, as we may be invoked from hold +end + return ReaderStyleTweak diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 4f7dccf85..c21ca9329 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -152,6 +152,7 @@ local InputDialog = InputContainer:new{ -- - on success: as the notification text instead of the default one -- - on failure: in an InfoMessage close_callback = nil, -- Called when closing (if discarded or saved, after save_callback if saved) + edited_callback = nil, -- Called on each text modification -- For use by TextEditor plugin: view_pos_callback = nil, -- Called with no arg to get initial top_line_num/charpos, @@ -466,8 +467,11 @@ function InputDialog:getInputValue() end end -function InputDialog:setInputText(text) +function InputDialog:setInputText(text, edited_state) self._input_widget:setText(text) + if edited_state ~= nil and self._buttons_edit_callback then + self._buttons_edit_callback(edited_state) + end end function InputDialog:isTextEditable() @@ -580,6 +584,9 @@ function InputDialog:_addSaveCloseButtons() if button("reset") then button("reset"):enable() end self:refreshButtons() end + if self.edited_callback then + self.edited_callback() + end end if self.reset_callback then -- if reset_callback provided, add button to restore diff --git a/frontend/util.lua b/frontend/util.lua index b90fa7972..ff544ac35 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -908,6 +908,47 @@ function util.htmlEscape(text) }) end +--- Prettify a CSS stylesheet +-- Not perfect, but enough to make some ugly CSS readable. +-- By default, each selector and each property is put on its own line. +-- With condensed=true, condense each full declaration on a single line. +-- +--- @string CSS string +--- @boolean condensed[opt=false] true to condense each declaration on a line +--- @treturn string the CSS prettified +function util.prettifyCSS(css_text, condensed) + if not condensed then + -- Get rid of \t so we can use it as a replacement/hiding char + css_text = css_text:gsub("\t", " ") + -- Wrap and indent declarations + css_text = css_text:gsub("%s*{%s*", " {\n ") + css_text = css_text:gsub(";%s*}%s*", ";\n}\n") + css_text = css_text:gsub(";%s*([^}])", ";\n %1") + css_text = css_text:gsub("%s*}%s*", "\n}\n") + -- Cleanup declarations + css_text = css_text:gsub("{[^}]*}", function(s) + s = s:gsub("%s*:%s*", ": ") + -- Temporarily hide/replace ',' in declaration so they + -- are not matched and made multi-lines by followup gsub + s = s:gsub("%s*,%s*", "\t") + return s + end) + -- Have each selector (separated by ',') on a new line + css_text = css_text:gsub("%s*,%s*", " ,\n") + -- Restore hidden ',' in declarations + css_text = css_text:gsub("\t", ", ") + else + -- Go thru previous method to have something standard to work on + css_text = util.prettifyCSS(css_text) + -- And condense that + css_text = css_text:gsub(" {\n ", " { ") + css_text = css_text:gsub(";\n ", "; ") + css_text = css_text:gsub("\n}", " }") + css_text = css_text:gsub(" ,\n", ", ") + end + return css_text +end + --- Escape list for shell usage --- @table args the list of arguments to escape --- @treturn string the escaped and concatenated arguments