From 24d8baf3ec3a88feede49fd41d3997f1b84934d6 Mon Sep 17 00:00:00 2001 From: hius07 <62179190+hius07@users.noreply.github.com> Date: Thu, 21 Nov 2024 07:53:22 +0200 Subject: [PATCH] Notebook file (#12699) --- .../apps/filemanager/filemanagerbookinfo.lua | 184 +++++++++++++++++- frontend/apps/filemanager/filemanagerutil.lua | 28 +-- frontend/dispatcher.lua | 6 +- plugins/texteditor.koplugin/main.lua | 135 ++++++------- 4 files changed, 263 insertions(+), 90 deletions(-) diff --git a/frontend/apps/filemanager/filemanagerbookinfo.lua b/frontend/apps/filemanager/filemanagerbookinfo.lua index 17b5e591e..dd44f1cc1 100644 --- a/frontend/apps/filemanager/filemanagerbookinfo.lua +++ b/frontend/apps/filemanager/filemanagerbookinfo.lua @@ -12,17 +12,18 @@ local DocumentRegistry = require("document/documentregistry") local Event = require("ui/event") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") +local Notification = require("ui/widget/notification") local TextViewer = require("ui/widget/textviewer") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") -local Utf8Proc = require("ffi/utf8proc") +local ffiUtil = require("ffi/util") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") local util = require("util") local _ = require("gettext") local N_ = _.ngettext local Screen = Device.screen -local T = require("ffi/util").template +local T = ffiUtil.template local BookInfo = WidgetContainer:extend{ title = _("Book information"), @@ -73,6 +74,15 @@ function BookInfo:show(doc_settings_or_file, book_props) -- File section local has_sidecar = type(doc_settings_or_file) == "table" local file = has_sidecar and doc_settings_or_file:readSetting("doc_path") or doc_settings_or_file + self.is_current_doc = self.document and self.document.file == file + if not has_sidecar and self.is_current_doc then + doc_settings_or_file = self.ui.doc_settings + has_sidecar = true + end + if not has_sidecar and DocSettings:hasSidecarFile(file) then + doc_settings_or_file = DocSettings:open(file) + has_sidecar = true + end local folder, filename = util.splitFilePathName(file) local __, filetype = filemanagerutil.splitFileNameType(filename) local attr = lfs.attributes(file) @@ -172,7 +182,15 @@ function BookInfo:show(doc_settings_or_file, book_props) table.insert(kv_pairs, { _("Rating:"), ("★"):rep(rating) .. ("☆"):rep(self.rating_max - rating), hold_callback = summary_hold_callback }) table.insert(kv_pairs, { _("Review:"), summary.note or _("N/A"), - hold_callback = summary_hold_callback }) + hold_callback = summary_hold_callback, separator = true }) + + -- Notebook file + local notebook_file = self:getNotebookFile(doc_settings_or_file) + local notebook_file_callback = function() + self:showNotebookFileDialog(notebook_file, doc_settings_or_file, book_props) + end + table.insert(kv_pairs, { _("Notebook file:"), notebook_file:gsub(".*/", ""), + callback = notebook_file_callback }) local KeyValuePage = require("ui/widget/keyvaluepage") self.kvp_widget = KeyValuePage:new{ @@ -296,10 +314,7 @@ function BookInfo:findInProps(book_props, search_string, case_sensitive) elseif key == "description" then prop = util.htmlToPlainTextIfHtml(prop) end - if not case_sensitive then - prop = Utf8Proc.lowercase(util.fixUtf8(prop, "?")) - end - if prop:find(search_string) then + if util.stringSearch(prop, search_string, case_sensitive) ~= 0 then return true end end @@ -469,7 +484,7 @@ function BookInfo:setCustomMetadata(file, book_props, prop_key, prop_value) if prop_key == "title" then -- generate when resetting the customized title and original is empty book_props.display_title = book_props.title or filemanagerutil.splitFileNameType(file) end - if self.document and self.document.file == file then -- currently opened document + if self.is_current_doc then self.ui.doc_props[prop_key] = prop_value if prop_key == "title" then self.ui.doc_props.display_title = book_props.display_title @@ -666,6 +681,159 @@ function BookInfo:editSummary(doc_settings_or_file, book_props) input_dialog:onShowKeyboard(true) end +-- notebook file + +function BookInfo:getNotebookFile(doc_settings_or_file) + local notebook_file + if type(doc_settings_or_file) == "table" then + notebook_file = doc_settings_or_file:readSetting("notebook_file") + end + if notebook_file == nil then + notebook_file = G_reader_settings:readSetting("notebook_file") + if notebook_file == nil then + if type(doc_settings_or_file) == "table" then + notebook_file = doc_settings_or_file:readSetting("doc_path") .. ".txt" + elseif type(doc_settings_or_file) == "string" then + notebook_file = doc_settings_or_file .. ".txt" + else + local home_folder = G_reader_settings:readSetting("home_dir") or filemanagerutil.getDefaultDir() + notebook_file = ffiUtil.realpath(home_folder) .. "/notebook.txt" + end + end + end + return notebook_file +end + +function BookInfo:showNotebookFileDialog(notebook_file, doc_settings_or_file, book_props) + local has_sidecar = type(doc_settings_or_file) == "table" + local file = has_sidecar and doc_settings_or_file:readSetting("doc_path") or doc_settings_or_file + local function saveNotebookFile(new_notebook_file) + if not has_sidecar then + doc_settings_or_file = DocSettings:open(doc_settings_or_file) + end + doc_settings_or_file:saveSetting("notebook_file", new_notebook_file) + if not self.is_current_doc then + if new_notebook_file or doc_settings_or_file:readSetting("summary") then + doc_settings_or_file:flush() + else -- remove empty sidecar + doc_settings_or_file:purge(nil, { doc_settings = true }) -- keep custom + doc_settings_or_file = file -- to reopen bookinfo + end + self.summary_updated = true -- refresh FM + end + self.kvp_widget:onClose() + self:show(doc_settings_or_file, book_props) + end + + local button_dialog + local local_notebook_file = file .. ".txt" + local default_notebook_file = G_reader_settings:readSetting("notebook_file") + local buttons = { + { + { + text = _("Use default"), + enabled = default_notebook_file ~= nil and notebook_file ~= default_notebook_file, + callback = function() + UIManager:close(button_dialog) + saveNotebookFile(default_notebook_file) + end, + }, + { + text = _("Reset default"), + enabled = default_notebook_file ~= nil, + callback = function() + UIManager:close(button_dialog) + G_reader_settings:delSetting("notebook_file") + Notification:notify(_("Notebook file default location reset"), Notification.SOURCE_ALWAYS_SHOW) + end, + }, + }, + { + { + text = _("Use local"), + enabled = notebook_file ~= local_notebook_file, + callback = function() + UIManager:close(button_dialog) + saveNotebookFile(local_notebook_file) + end, + }, + { + text = _("Set as default"), + enabled = notebook_file ~= default_notebook_file, + callback = function() + UIManager:close(button_dialog) + G_reader_settings:saveSetting("notebook_file", notebook_file) + Notification:notify(_("Notebook file default location saved"), Notification.SOURCE_ALWAYS_SHOW) + end, + }, + }, + { + { + text = _("Choose"), + callback = function() + UIManager:close(button_dialog) + local PathChooser = require("ui/widget/pathchooser") + local path_chooser = PathChooser:new{ + path = notebook_file:match("(.*)/"), + select_directory = false, + onConfirm = saveNotebookFile, + } + UIManager:show(path_chooser) + end, + }, + { + text = _("Create"), + enabled = self.ui.texteditor ~= nil, + callback = function() + UIManager:close(button_dialog) + self.ui.texteditor:newFile(notebook_file, saveNotebookFile) + end, + }, + }, + {}, -- separator + { + { + text = _("View"), + enabled = lfs.attributes(notebook_file, "mode") == "file", + callback = function() + UIManager:close(button_dialog) + TextViewer.openFile(notebook_file) + end, + }, + { + text = _("Edit"), + enabled = self.ui.texteditor ~= nil, + callback = function() + UIManager:close(button_dialog) + self.ui.texteditor:openFile(notebook_file, saveNotebookFile) + end, + }, + }, + } + button_dialog = ButtonDialog:new{ + title = notebook_file, + title_align = "center", + buttons = buttons, + } + UIManager:show(button_dialog) +end + +function BookInfo:onShowNotebookFile() + local notebook_file = self:getNotebookFile(self.ui.doc_settings) + if self.ui.texteditor then + local function saveNotebookFile(new_notebook_file) + if self.ui.doc_settings ~= nil then + self.ui.doc_settings:saveSetting("notebook_file", new_notebook_file) + end + end + self.ui.texteditor:openFile(notebook_file, saveNotebookFile) + elseif lfs.attributes(notebook_file, "mode") == "file" then + TextViewer.openFile(notebook_file) + end +end + +-- book metadata (sdr) + function BookInfo:moveBookMetadata() -- called by filemanagermenu only local file_chooser = self.ui.file_chooser diff --git a/frontend/apps/filemanager/filemanagerutil.lua b/frontend/apps/filemanager/filemanagerutil.lua index 3b912a4d1..5d0eae224 100644 --- a/frontend/apps/filemanager/filemanagerutil.lua +++ b/frontend/apps/filemanager/filemanagerutil.lua @@ -7,11 +7,11 @@ local Device = require("device") local DocSettings = require("docsettings") local Event = require("ui/event") local UIManager = require("ui/uimanager") -local ffiutil = require("ffi/util") +local ffiUtil = require("ffi/util") local lfs = require("libs/libkoreader-lfs") local util = require("util") local _ = require("gettext") -local T = ffiutil.template +local T = ffiUtil.template local filemanagerutil = {} @@ -89,7 +89,7 @@ function filemanagerutil.resetDocumentSettings(file) last_page = true, last_xpointer = true, } - local file_abs_path = ffiutil.realpath(file) + local file_abs_path = ffiUtil.realpath(file) if file_abs_path then local doc_settings = DocSettings:open(file_abs_path) for k in pairs(doc_settings.data) do @@ -141,7 +141,7 @@ function filemanagerutil.genStatusButtonsRow(doc_settings_or_file, caller_callba local file, summary, status if type(doc_settings_or_file) == "table" then file = doc_settings_or_file:readSetting("doc_path") - summary = doc_settings_or_file:readSetting("summary", {}) + summary = doc_settings_or_file:readSetting("summary") or {} status = summary.status else file = doc_settings_or_file @@ -175,7 +175,7 @@ function filemanagerutil.genResetSettingsButton(doc_settings_or_file, caller_cal file = doc_settings_or_file:readSetting("doc_path") has_sidecar_file = true else - file = ffiutil.realpath(doc_settings_or_file) or doc_settings_or_file + file = ffiUtil.realpath(doc_settings_or_file) or doc_settings_or_file has_sidecar_file = DocSettings:hasSidecarFile(file) end local custom_cover_file = DocSettings:findCustomCoverFile(file) @@ -262,8 +262,8 @@ function filemanagerutil.genBookInformationButton(doc_settings_or_file, book_pro enabled = not button_disabled, callback = function() caller_callback() - local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") - FileManagerBookInfo:show(doc_settings_or_file, book_props and FileManagerBookInfo.extendProps(book_props)) + local ui = require("apps/reader/readerui").instance or require("apps/filemanager/filemanager").instance + ui.bookinfo:show(doc_settings_or_file, book_props and ui.bookinfo.extendProps(book_props)) end, } end @@ -275,8 +275,8 @@ function filemanagerutil.genBookCoverButton(file, book_props, caller_callback, b enabled = (not button_disabled and (not book_props or has_cover)) and true or false, callback = function() caller_callback() - local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") - FileManagerBookInfo:onShowBookCover(file) + local ui = require("apps/reader/readerui").instance or require("apps/filemanager/filemanager").instance + ui.bookinfo:onShowBookCover(file) end, } end @@ -289,8 +289,8 @@ function filemanagerutil.genBookDescriptionButton(file, book_props, caller_callb enabled = (not (button_disabled or book_props) or description) and true or false, callback = function() caller_callback() - local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") - FileManagerBookInfo:onShowBookDescription(description, file) + local ui = require("apps/reader/readerui").instance or require("apps/filemanager/filemanager").instance + ui.bookinfo:onShowBookDescription(description, file) end, } end @@ -305,17 +305,17 @@ function filemanagerutil.genExecuteScriptButton(file, caller_callback) caller_callback() local script_is_running_msg = InfoMessage:new{ -- @translators %1 is the script's programming language (e.g., shell or python), %2 is the filename - text = T(_("Running %1 script %2…"), util.getScriptType(file), BD.filename(ffiutil.basename(file))), + text = T(_("Running %1 script %2…"), util.getScriptType(file), BD.filename(ffiUtil.basename(file))), } UIManager:show(script_is_running_msg) UIManager:scheduleIn(0.5, function() local rv if Device:isAndroid() then Device:setIgnoreInput(true) - rv = os.execute("sh " .. ffiutil.realpath(file)) -- run by sh, because sdcard has no execute permissions + rv = os.execute("sh " .. ffiUtil.realpath(file)) -- run by sh, because sdcard has no execute permissions Device:setIgnoreInput(false) else - rv = os.execute(ffiutil.realpath(file)) + rv = os.execute(ffiUtil.realpath(file)) end UIManager:close(script_is_running_msg) if rv == 0 then diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index df2e64471..bf1f08422 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -56,7 +56,8 @@ local settingsList = { history_search = {category="none", event="SearchHistory", title=_("History search"), general=true}, favorites = {category="none", event="ShowColl", title=_("Favorites"), general=true}, collections = {category="none", event="ShowCollList", title=_("Collections"), general=true}, - filemanager = {category="none", event="Home", title=_("File browser"), general=true, separator=true}, + filemanager = {category="none", event="Home", title=_("File browser"), general=true}, + notebook_file = {category="none", event="ShowNotebookFile", title=_("Notebook file"), general=true, separator=true}, ---- dictionary_lookup = {category="none", event="ShowDictionaryLookup", title=_("Dictionary lookup"), general=true}, wikipedia_lookup = {category="none", event="ShowWikipediaLookup", title=_("Wikipedia lookup"), general=true, separator=true}, @@ -185,7 +186,7 @@ local settingsList = { book_status = {category="none", event="ShowBookStatus", title=_("Book status"), reader=true}, book_info = {category="none", event="ShowBookInfo", title=_("Book information"), reader=true}, book_description = {category="none", event="ShowBookDescription", title=_("Book description"), reader=true}, - book_cover = {category="none", event="ShowBookCover", title=_("Book cover"), reader=true, separator=true}, + book_cover = {category="none", event="ShowBookCover", title=_("Book cover"), reader=true}, ---- translate_page = {category="none", event="TranslateCurrentPage", title=_("Translate current page"), reader=true, separator=true}, ---- @@ -289,6 +290,7 @@ local dispatcher_menu_order = { "favorites", "collections", "filemanager", + "notebook_file", ---- "dictionary_lookup", "wikipedia_lookup", diff --git a/plugins/texteditor.koplugin/main.lua b/plugins/texteditor.koplugin/main.lua index cbb0ea589..ffe43cf9f 100644 --- a/plugins/texteditor.koplugin/main.lua +++ b/plugins/texteditor.koplugin/main.lua @@ -14,13 +14,13 @@ local PathChooser = require("ui/widget/pathchooser") local Trapper = require("ui/trapper") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") -local ffiutil = require("ffi/util") +local ffiUtil = require("ffi/util") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") local util = require("util") local _ = require("gettext") local Screen = require("device").screen -local T = ffiutil.template +local T = ffiUtil.template local TextEditor = WidgetContainer:extend{ name = "texteditor", @@ -60,8 +60,8 @@ function TextEditor:isFileTypeSupported(file) return true end -function TextEditor:openFile(file) - self:checkEditFile(file) +function TextEditor:openFile(file, caller_callback) + self:checkEditFile(file, nil, nil, caller_callback) end function TextEditor:loadSettings() @@ -72,7 +72,7 @@ function TextEditor:loadSettings() -- NOTE: addToHistory assigns a new object self.history = self.settings:readSetting("history") or {} self.last_view_pos = self.settings:readSetting("last_view_pos") or {} - self.last_path = self.settings:readSetting("last_path") or ffiutil.realpath(DataStorage:getDataDir()) + self.last_path = self.settings:readSetting("last_path") or ffiUtil.realpath(DataStorage:getDataDir()) self.font_face = self.settings:readSetting("font_face") or self.normal_font self.font_size = self.settings:readSetting("font_size") or self.default_font_size -- The font settings could be saved in G_reader_setting if we want them @@ -342,55 +342,54 @@ function TextEditor:addToHistory(file_path) self.history = new_history end -function TextEditor:newFile() +function TextEditor:newFile(new_path, caller_callback) self:loadSettings() - UIManager:show(ConfirmBox:new{ - text = _([[To start editing a new file, you will have to: - -- First choose a folder -- Then enter a name for the new file -- And start editing it - -Do you want to proceed?]]), - ok_text = _("Yes"), - cancel_text = _("No"), - ok_callback = function() - local path_chooser = PathChooser:new{ - select_file = false, - path = self.last_path, - onConfirm = function(dir_path) - local file_input - file_input = InputDialog:new{ - title = _("Enter filename"), - input = dir_path == "/" and "/" or dir_path .. "/", - buttons = {{ - { - text = _("Cancel"), - id = "close", - callback = function() - UIManager:close(file_input) - end, - }, - { - text = _("Edit"), - callback = function() - local file_path = file_input:getInputText() - UIManager:close(file_input) - -- Remember last_path - self.last_path = file_path:match("(.*)/") - if self.last_path == "" then self.last_path = "/" end - self:checkEditFile(file_path, false, true) - end, - }, - }}, - } - UIManager:show(file_input) - file_input:onShowKeyboard() - end, - } - UIManager:show(path_chooser) - end, - }) + new_path = new_path or (self.last_path == "/" and "/" or self.last_path .. "/") + local file_input + file_input = InputDialog:new{ + title = _("Enter filename"), + input = new_path, + buttons = { + { + { + text = _("Choose folder"), + callback = function() + UIManager:close(file_input) -- need to close keyboard + local path_chooser = PathChooser:new{ + select_file = false, + path = new_path:match("(.*)/"), + onConfirm = function(dir_path) + self:newFile(dir_path .. "/", caller_callback) + end, + } + UIManager:show(path_chooser) + end, + }, + }, + { + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(file_input) + end, + }, + { + text = _("Edit"), + callback = function() + local file_path = file_input:getInputText() + UIManager:close(file_input) + -- Remember last_path + self.last_path = file_path:match("(.*)/") + if self.last_path == "" then self.last_path = "/" end + self:checkEditFile(file_path, false, true, caller_callback) + end, + }, + }, + }, + } + UIManager:show(file_input) + file_input:onShowKeyboard() end function TextEditor:chooseFile() @@ -408,23 +407,23 @@ function TextEditor:chooseFile() UIManager:show(path_chooser) end -function TextEditor:checkEditFile(file_path, from_history, possibly_new_file) +function TextEditor:checkEditFile(file_path, from_history, possibly_new_file, caller_callback) self:loadSettings() local attr = lfs.attributes(file_path) if not possibly_new_file and not attr then UIManager:show(ConfirmBox:new{ - text = T(_("This file does not exist anymore:\n\n%1\n\nDo you want to create it and start editing it?"), BD.filepath(file_path)), + text = T(_("This file does not exist:\n\n%1\n\nDo you want to create it and start editing it?"), BD.filepath(file_path)), ok_text = _("Create"), ok_callback = function() -- go again thru there with possibly_new_file=true - self:checkEditFile(file_path, from_history, true) + self:checkEditFile(file_path, from_history, true, caller_callback) end, }) return end if attr then -- File exists: get its real path with symlink and ../ resolved - file_path = ffiutil.realpath(file_path) + file_path = ffiUtil.realpath(file_path) attr = lfs.attributes(file_path) end if attr then -- File exists @@ -451,11 +450,11 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file) BD.filepath(file_path), util.getFriendlySize(attr.size)), ok_text = _("Open"), ok_callback = function() - self:editFile(file_path, readonly) + self:editFile(file_path, readonly, caller_callback) end, }) else - self:editFile(file_path, readonly) + self:editFile(file_path, readonly, caller_callback) end else -- File does not exist -- Try to create it just to check if writing to it later is possible @@ -465,7 +464,7 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file) -- without saving in case the user has changed his mind. file:close() os.remove(file_path) - self:editFile(file_path) + self:editFile(file_path, nil, caller_callback) else UIManager:show(InfoMessage:new{ text = T(_("This file can not be created:\n\n%1\n\nReason: %2"), BD.filepath(file_path), err) @@ -475,12 +474,13 @@ function TextEditor:checkEditFile(file_path, from_history, possibly_new_file) end end -function TextEditor:saveFileContent(file_path, content) +function TextEditor:saveFileContent(file_path, content, caller_callback) local ok, err = util.writeToFile(content, file_path) if ok then if self.ui.file_chooser then self.ui.file_chooser:refreshPath() end + self.caller_callback = caller_callback -- will be called in self.input.close_callback logger.info("TextEditor: saved file", file_path) return true end @@ -498,7 +498,7 @@ function TextEditor:deleteFile(file_path) return false, err end -function TextEditor:editFile(file_path, readonly) +function TextEditor:editFile(file_path, readonly, caller_callback) self:addToHistory(file_path) local directory, filename = util.splitFilePathName(file_path) -- luacheck: no unused local filename_without_suffix, filetype = util.splitFileNameSuffix(filename) -- luacheck: no unused @@ -578,6 +578,9 @@ function TextEditor:editFile(file_path, readonly) if self.input.rotation_mode_backup and self.input.rotation_mode_backup ~= Screen:getRotationMode() then Screen:setRotationMode(self.input.rotation_mode_backup) end + if self.caller_callback then + self.caller_callback(file_path) + end self:execWhenDoneFunc() end, -- File saving callback @@ -588,7 +591,7 @@ function TextEditor:editFile(file_path, readonly) end if content and #content > 0 then if not is_lua then - local ok, err = self:saveFileContent(file_path, content) + local ok, err = self:saveFileContent(file_path, content, caller_callback) if ok then return true, _("File saved") else @@ -597,7 +600,7 @@ function TextEditor:editFile(file_path, readonly) end local parse_error = util.checkLuaSyntax(content) if not parse_error then - local ok, err = self:saveFileContent(file_path, content) + local ok, err = self:saveFileContent(file_path, content, caller_callback) if ok then return true, _("Lua syntax OK, file saved") else @@ -615,7 +618,7 @@ Do you really want to save to this file? %2]]), parse_error, BD.filepath(file_path)), _("Do not save"), _("Save anyway")) -- we'll get the safer "Do not save" on tap outside if save_anyway then - local ok, err = self:saveFileContent(file_path, content) + local ok, err = self:saveFileContent(file_path, content, caller_callback) if ok then return true, _("File saved") else @@ -639,7 +642,7 @@ Do you want to keep this file as empty, or do you prefer to delete it? return false, T(_("Failed deleting file: %1"), err) end else - local ok, err = self:saveFileContent(file_path, content) + local ok, err = self:saveFileContent(file_path, content, caller_callback) if ok then return true, _("File saved") else