2020-01-04 00:18:51 +00:00
|
|
|
local BD = require("ui/bidi")
|
2014-04-23 14:19:29 +00:00
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
2016-06-26 00:53:08 +00:00
|
|
|
local NetworkMgr = require("ui/network/manager")
|
2015-10-03 06:18:47 +00:00
|
|
|
local DataStorage = require("datastorage")
|
|
|
|
local DocSettings = require("docsettings")
|
2021-12-25 09:24:48 +00:00
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
2014-04-23 14:19:29 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2020-06-27 15:35:25 +00:00
|
|
|
local logger = require("logger")
|
2016-07-05 14:15:52 +00:00
|
|
|
local util = require("ffi/util")
|
|
|
|
local Device = require("device")
|
2019-09-29 21:09:58 +00:00
|
|
|
local JoplinClient = require("JoplinClient")
|
2021-12-25 09:24:48 +00:00
|
|
|
local ReadwiseClient = require("ReadwiseClient")
|
2020-09-15 18:39:32 +00:00
|
|
|
local T = util.template
|
2014-04-23 14:19:29 +00:00
|
|
|
local _ = require("gettext")
|
2019-08-24 21:06:06 +00:00
|
|
|
local N_ = _.ngettext
|
2014-04-23 14:19:29 +00:00
|
|
|
local slt2 = require('slt2')
|
|
|
|
local MyClipping = require("clip")
|
2020-04-26 17:36:56 +00:00
|
|
|
local json = require("json")
|
2014-04-23 14:19:29 +00:00
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
local function getOrMigrateSettings()
|
|
|
|
local settings = G_reader_settings:readSetting("exporter")
|
|
|
|
if not settings then
|
|
|
|
-- migrate settings from old plugin and remove specific evernote ones.
|
|
|
|
settings = G_reader_settings:readSetting("evernote")
|
|
|
|
if type(settings) == "table" then
|
|
|
|
settings.domain = nil
|
|
|
|
settings.username = nil
|
|
|
|
settings.token = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return settings or {}
|
|
|
|
end
|
|
|
|
|
|
|
|
local Exporter = InputContainer:new{
|
|
|
|
name = "exporter",
|
2016-04-15 13:04:41 +00:00
|
|
|
notebook_name = _("KOReader Notes"),
|
2014-10-16 10:25:02 +00:00
|
|
|
notemarks = _("Note: "),
|
2015-10-03 06:18:47 +00:00
|
|
|
clipping_dir = DataStorage:getDataDir() .. "/clipboard",
|
2014-04-23 14:19:29 +00:00
|
|
|
|
2016-11-01 07:31:24 +00:00
|
|
|
evernote_token = nil,
|
|
|
|
notebook_guid = nil,
|
2014-04-23 14:19:29 +00:00
|
|
|
}
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:init()
|
2017-02-27 07:41:06 +00:00
|
|
|
self.text_clipping_file = self.clipping_dir .. "/KOReaderClipping.txt"
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_clipping_file = self.clipping_dir .. "/KOReaderClipping.json"
|
2021-08-30 07:11:23 +00:00
|
|
|
local settings = getOrMigrateSettings()
|
2014-04-23 14:19:29 +00:00
|
|
|
self.notebook_guid = settings.notebook
|
2019-09-29 21:09:58 +00:00
|
|
|
self.joplin_IP = settings.joplin_IP or "localhost"
|
|
|
|
self.joplin_port = settings.joplin_port or 41185
|
|
|
|
self.joplin_token = settings.joplin_token -- or your token
|
|
|
|
self.joplin_notebook_guid = settings.joplin_notebook_guid or nil
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_token = settings.readwise_token or nil
|
2014-10-16 10:25:02 +00:00
|
|
|
self.html_export = settings.html_export or false
|
2019-09-29 21:09:58 +00:00
|
|
|
self.joplin_export = settings.joplin_export or false
|
|
|
|
self.txt_export = settings.txt_export or false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = settings.json_export or false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = settings.readwise_export or false
|
2020-04-26 17:36:56 +00:00
|
|
|
--- @todo Is this if block necessary? Nowhere in the code they are assigned both true.
|
2019-09-29 21:09:58 +00:00
|
|
|
-- Do they check against external modifications to settings file?
|
|
|
|
|
2017-01-21 09:32:42 +00:00
|
|
|
if self.html_export then
|
|
|
|
self.txt_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
self.joplin_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
elseif self.txt_export then
|
|
|
|
self.joplin_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
elseif self.json_export then
|
|
|
|
self.joplin_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2017-01-21 09:32:42 +00:00
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
|
|
|
|
self.parser = MyClipping:new{
|
|
|
|
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
|
|
|
history_dir = "./history",
|
|
|
|
}
|
|
|
|
self.template = slt2.loadfile(self.path.."/note.tpl")
|
2016-07-05 14:15:52 +00:00
|
|
|
self:migrateClippings()
|
2021-08-30 07:11:23 +00:00
|
|
|
self.config = DocSettings:open(util.joinPath(self.clipping_dir, "exporter.sdr"))
|
2017-02-27 07:41:06 +00:00
|
|
|
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
2016-07-05 14:15:52 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:isDocless()
|
2017-01-09 08:32:10 +00:00
|
|
|
return self.ui == nil or self.ui.document == nil or self.view == nil
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:readyToExport()
|
|
|
|
return self.html_export ~= false or
|
2019-09-29 21:09:58 +00:00
|
|
|
self.txt_export ~= false or
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export ~= false or
|
2021-12-25 09:24:48 +00:00
|
|
|
self.joplin_export ~= false or
|
|
|
|
self.readwise_export ~= false
|
2017-01-09 08:32:10 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:migrateClippings()
|
2020-07-10 14:05:54 +00:00
|
|
|
if jit.os == "OSX" then return end
|
2016-07-05 14:15:52 +00:00
|
|
|
local old_dir = util.joinPath(util.realpath(util.joinPath(self.path, "..")),
|
|
|
|
"evernote.sdr")
|
|
|
|
if lfs.attributes(old_dir, "mode") == "directory" then
|
|
|
|
local mv_bin = Device:isAndroid() and "/system/bin/mv" or "/bin/mv"
|
|
|
|
return util.execute(mv_bin, old_dir, self.clipping_dir) == 0
|
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:addToMainMenu(menu_items)
|
|
|
|
menu_items.exporter = {
|
|
|
|
text = _("Export highlights"),
|
2014-04-23 14:19:29 +00:00
|
|
|
sub_item_table = {
|
2019-09-29 21:09:58 +00:00
|
|
|
{
|
|
|
|
text = _("Joplin") ,
|
|
|
|
checked_func = function() return self.joplin_export end,
|
|
|
|
sub_item_table ={
|
|
|
|
{
|
|
|
|
text = _("Set Joplin IP and Port"),
|
|
|
|
keep_menu_open = true,
|
|
|
|
callback = function()
|
|
|
|
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
|
|
|
local url_dialog
|
|
|
|
url_dialog = MultiInputDialog:new{
|
|
|
|
title = _("Set Joplin IP and port number"),
|
|
|
|
fields = {
|
|
|
|
{
|
|
|
|
text = self.joplin_IP,
|
|
|
|
input_type = "string"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = self.joplin_port,
|
|
|
|
input_type = "number"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(url_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("OK"),
|
|
|
|
callback = function()
|
|
|
|
local fields = url_dialog:getFields()
|
|
|
|
local ip = fields[1]
|
|
|
|
local port = tonumber(fields[2])
|
|
|
|
if ip ~= "" then
|
|
|
|
if port and port < 65355 then
|
|
|
|
self.joplin_IP = ip
|
|
|
|
self.joplin_port = port
|
|
|
|
end
|
|
|
|
self:saveSettings()
|
|
|
|
end
|
|
|
|
UIManager:close(url_dialog)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UIManager:show(url_dialog)
|
|
|
|
url_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Set authorization token"),
|
|
|
|
keep_menu_open = true,
|
|
|
|
callback = function()
|
|
|
|
local auth_dialog
|
2021-12-25 09:24:48 +00:00
|
|
|
auth_dialog = InputDialog:new{
|
2019-09-29 21:09:58 +00:00
|
|
|
title = _("Set authorization token for Joplin"),
|
2021-12-25 09:24:48 +00:00
|
|
|
input = self.joplin_token,
|
2019-09-29 21:09:58 +00:00
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(auth_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Set token"),
|
|
|
|
callback = function()
|
2021-12-25 09:24:48 +00:00
|
|
|
self.joplin_token = auth_dialog:getInputText()
|
2019-09-29 21:09:58 +00:00
|
|
|
self:saveSettings()
|
|
|
|
UIManager:close(auth_dialog)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UIManager:show(auth_dialog)
|
|
|
|
auth_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Export to Joplin"),
|
|
|
|
checked_func = function() return self.joplin_export end,
|
|
|
|
callback = function()
|
|
|
|
self.joplin_export = not self.joplin_export
|
|
|
|
if self.joplin_export then
|
|
|
|
self.html_export = false
|
|
|
|
self.txt_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
end
|
|
|
|
self:saveSettings()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Help"),
|
|
|
|
keep_menu_open = true,
|
|
|
|
callback = function()
|
|
|
|
UIManager:show(InfoMessage:new{
|
2021-12-25 09:24:48 +00:00
|
|
|
text = T(_([[You can enter your auth token on your computer by saving an empty token. Then quit KOReader, edit the exporter.joplin_token field in %1/settings.reader.lua after creating a backup, and restart KOReader once you're done.
|
2019-09-29 21:09:58 +00:00
|
|
|
|
|
|
|
To export to Joplin, you must forward the IP and port used by this plugin to the localhost:port on which Joplin is listening. This can be done with socat or a similar program. For example:
|
|
|
|
|
2020-01-05 08:47:33 +00:00
|
|
|
For Windows: netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=41185 connectaddress=localhost connectport=41184
|
2019-09-29 21:09:58 +00:00
|
|
|
|
|
|
|
For Linux: $socat tcp-listen:41185,reuseaddr,fork tcp:localhost:41184
|
|
|
|
|
2021-12-25 09:24:48 +00:00
|
|
|
For more information, please visit https://github.com/koreader/koreader/wiki/Highlight-export.]])
|
|
|
|
, BD.dirpath(DataStorage:getDataDir()))
|
|
|
|
})
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Readwise") ,
|
|
|
|
checked_func = function() return self.readwise_export end,
|
|
|
|
separator = true,
|
|
|
|
sub_item_table ={
|
|
|
|
{
|
|
|
|
text = _("Set authorization token"),
|
|
|
|
keep_menu_open = true,
|
|
|
|
callback = function()
|
|
|
|
local auth_dialog
|
|
|
|
auth_dialog = InputDialog:new{
|
|
|
|
title = _("Set authorization token for Readwise"),
|
|
|
|
input = self.readwise_token,
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(auth_dialog)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Set token"),
|
|
|
|
callback = function()
|
|
|
|
self.readwise_token = auth_dialog:getInputText()
|
|
|
|
self:saveSettings()
|
|
|
|
UIManager:close(auth_dialog)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UIManager:show(auth_dialog)
|
|
|
|
auth_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Export to Readwise"),
|
|
|
|
checked_func = function() return self.readwise_export end,
|
|
|
|
callback = function()
|
|
|
|
self.readwise_export = not self.readwise_export
|
|
|
|
if self.readwise_export then
|
|
|
|
self.html_export = false
|
|
|
|
self.txt_export = false
|
|
|
|
self.json_export = false
|
|
|
|
self.joplin_export = false
|
|
|
|
end
|
|
|
|
self:saveSettings()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Help"),
|
|
|
|
keep_menu_open = true,
|
|
|
|
callback = function()
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = T(_([[You can enter your auth token on your computer by saving an empty token. Then quit KOReader, edit the exporter.readwise_token field in %1/settings.reader.lua after creating a backup, and restart KOReader once you're done.
|
|
|
|
|
|
|
|
For more information, please visit https://github.com/koreader/koreader/wiki/Highlight-export.]])
|
2020-01-04 00:18:51 +00:00
|
|
|
, BD.dirpath(DataStorage:getDataDir()))
|
2019-09-29 21:09:58 +00:00
|
|
|
})
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2014-04-23 14:19:29 +00:00
|
|
|
{
|
|
|
|
text = _("Export all notes in this book"),
|
2014-05-01 10:38:43 +00:00
|
|
|
enabled_func = function()
|
2017-02-27 07:41:06 +00:00
|
|
|
return not self:isDocless() and self:readyToExport() and not self.txt_export
|
2014-05-01 10:38:43 +00:00
|
|
|
end,
|
2014-04-23 14:19:29 +00:00
|
|
|
callback = function()
|
2022-01-04 20:21:58 +00:00
|
|
|
local export_callback = function()
|
|
|
|
UIManager:nextTick(function()
|
|
|
|
self:exportCurrentNotes(self.view)
|
|
|
|
end)
|
2014-04-23 14:19:29 +00:00
|
|
|
|
2022-01-04 20:21:58 +00:00
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("Exporting may take several seconds…"),
|
|
|
|
timeout = 1,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
if self.joplin_export or self.readwise_export then
|
|
|
|
NetworkMgr:runWhenOnline(export_callback)
|
|
|
|
else
|
|
|
|
export_callback()
|
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Export all notes in your library"),
|
2014-05-01 10:38:43 +00:00
|
|
|
enabled_func = function()
|
2017-01-09 08:32:10 +00:00
|
|
|
return self:readyToExport()
|
2014-05-01 10:38:43 +00:00
|
|
|
end,
|
2014-04-23 14:19:29 +00:00
|
|
|
callback = function()
|
2022-01-04 20:21:58 +00:00
|
|
|
local export_callback = function()
|
|
|
|
UIManager:nextTick(function()
|
|
|
|
self:exportAllNotes()
|
|
|
|
end)
|
|
|
|
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("Exporting may take several minutes…"),
|
|
|
|
timeout = 1,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
if self.joplin_export or self.readwise_export then
|
|
|
|
NetworkMgr:runWhenOnline(export_callback)
|
|
|
|
else
|
|
|
|
export_callback()
|
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
|
2020-06-22 08:00:22 +00:00
|
|
|
end,
|
|
|
|
separator = true,
|
2014-04-23 14:19:29 +00:00
|
|
|
},
|
2020-04-26 17:36:56 +00:00
|
|
|
{
|
|
|
|
text = _("Export to local JSON files"),
|
|
|
|
checked_func = function() return self.json_export end,
|
|
|
|
callback = function()
|
|
|
|
self.json_export = not self.json_export
|
|
|
|
if self.json_export then
|
|
|
|
self.txt_export = false
|
|
|
|
self.html_export = false
|
|
|
|
self.joplin_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
end
|
|
|
|
self:saveSettings()
|
|
|
|
end
|
|
|
|
},
|
2014-10-16 10:25:02 +00:00
|
|
|
{
|
|
|
|
text = _("Export to local HTML files"),
|
|
|
|
checked_func = function() return self.html_export end,
|
|
|
|
callback = function()
|
|
|
|
self.html_export = not self.html_export
|
2019-09-29 21:09:58 +00:00
|
|
|
if self.html_export then
|
|
|
|
self.txt_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
self.joplin_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
end
|
2017-01-21 09:32:42 +00:00
|
|
|
self:saveSettings()
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Export to local clipping text file"),
|
|
|
|
checked_func = function() return self.txt_export end,
|
|
|
|
callback = function()
|
|
|
|
self.txt_export = not self.txt_export
|
2019-09-29 21:09:58 +00:00
|
|
|
if self.txt_export then
|
|
|
|
self.html_export = false
|
2020-04-26 17:36:56 +00:00
|
|
|
self.json_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
self.joplin_export = false
|
2021-12-25 09:24:48 +00:00
|
|
|
self.readwise_export = false
|
2019-09-29 21:09:58 +00:00
|
|
|
end
|
2017-01-16 13:50:36 +00:00
|
|
|
self:saveSettings()
|
2020-06-22 08:00:22 +00:00
|
|
|
end,
|
|
|
|
separator = true,
|
2014-10-16 10:25:02 +00:00
|
|
|
},
|
2017-01-21 09:32:42 +00:00
|
|
|
{
|
|
|
|
text = _("Purge history records"),
|
|
|
|
callback = function()
|
|
|
|
self.config:purge()
|
2020-01-25 16:28:13 +00:00
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("History records have been purged.\nAll notes will be exported again next time.\n"),
|
|
|
|
timeout = 2,
|
2017-01-21 09:32:42 +00:00
|
|
|
})
|
|
|
|
end
|
|
|
|
}
|
2014-04-23 14:19:29 +00:00
|
|
|
}
|
2017-02-28 21:46:32 +00:00
|
|
|
}
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:saveSettings()
|
2014-04-23 14:19:29 +00:00
|
|
|
local settings = {
|
|
|
|
notebook = self.notebook_guid,
|
2014-10-16 10:25:02 +00:00
|
|
|
html_export = self.html_export,
|
2017-01-21 09:32:42 +00:00
|
|
|
txt_export = self.txt_export,
|
2020-04-26 17:36:56 +00:00
|
|
|
json_export = self.json_export,
|
2019-09-29 21:09:58 +00:00
|
|
|
joplin_IP = self.joplin_IP,
|
|
|
|
joplin_port = self.joplin_port,
|
|
|
|
joplin_token = self.joplin_token,
|
|
|
|
joplin_notebook_guid = self.joplin_notebook_guid,
|
2021-12-25 09:24:48 +00:00
|
|
|
joplin_export = self.joplin_export,
|
|
|
|
readwise_token = self.readwise_token,
|
|
|
|
readwise_export = self.readwise_export
|
2014-04-23 14:19:29 +00:00
|
|
|
}
|
2021-08-30 07:11:23 +00:00
|
|
|
G_reader_settings:saveSetting("exporter", settings)
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:getExportNotebook(client)
|
2014-04-23 14:19:29 +00:00
|
|
|
local name = self.notebook_name
|
|
|
|
return client:findNotebookByTitle(name) or client:createNotebook(name).guid
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportCurrentNotes(view)
|
2014-04-23 14:19:29 +00:00
|
|
|
local clippings = self.parser:parseCurrentDoc(view)
|
2014-10-16 10:25:02 +00:00
|
|
|
self:exportClippings(clippings)
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:updateHistoryClippings(clippings, new_clippings)
|
2014-05-12 13:19:17 +00:00
|
|
|
-- update clippings from history clippings
|
2014-05-12 10:07:20 +00:00
|
|
|
for title, booknotes in pairs(new_clippings) do
|
|
|
|
for chapter_index, chapternotes in ipairs(booknotes) do
|
|
|
|
for note_index, note in ipairs(chapternotes) do
|
|
|
|
if clippings[title] == nil or clippings[title][chapter_index] == nil
|
|
|
|
or clippings[title][chapter_index][note_index] == nil
|
|
|
|
or clippings[title][chapter_index][note_index].page ~= note.page
|
|
|
|
or clippings[title][chapter_index][note_index].time ~= note.time
|
|
|
|
or clippings[title][chapter_index][note_index].text ~= note.text
|
|
|
|
or clippings[title][chapter_index][note_index].note ~= note.note then
|
2020-06-27 15:35:25 +00:00
|
|
|
logger.dbg("found new notes in history", booknotes.title)
|
2014-05-12 10:07:20 +00:00
|
|
|
clippings[title] = booknotes
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return clippings
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:updateMyClippings(clippings, new_clippings)
|
2014-05-12 13:19:17 +00:00
|
|
|
-- only new titles or new notes in My clippings are updated to clippings
|
|
|
|
-- since appending is the only way to modify notes in My Clippings
|
|
|
|
for title, booknotes in pairs(new_clippings) do
|
|
|
|
if clippings[title] == nil or #clippings[title] < #booknotes then
|
2020-06-27 15:35:25 +00:00
|
|
|
logger.dbg("found new notes in MyClipping", booknotes.title)
|
2014-05-12 13:19:17 +00:00
|
|
|
clippings[title] = booknotes
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return clippings
|
|
|
|
end
|
|
|
|
|
2020-01-25 16:28:13 +00:00
|
|
|
--[[--
|
|
|
|
Parses highlights and calls exporter functions.
|
|
|
|
|
|
|
|
Entry point for exporting highlights. User interface calls this function.
|
|
|
|
Parses current document and documents from history, passes them to exportClippings().
|
|
|
|
Highlight: Highlighted text or image in document, stored in "highlights" table in
|
|
|
|
documents sidecar file. Parser uses this table. If highlight._._.text field is empty parser uses
|
|
|
|
highlight._._.pboxes field to get an image instead.
|
|
|
|
Bookmarks: Data in bookmark explorer. Stored in "bookmarks" table of documents sidecar file. Every
|
|
|
|
field in bookmarks._ has "text" and "notes" fields When user edits a highlight or "renames" bookmark,
|
|
|
|
text field is created or updated. Parser looks to bookmarks._.text field for edited notes. bookmarks._.notes isn't used for exporting operations.
|
|
|
|
https://github.com/koreader/koreader/blob/605f6026bbf37856ee54741b8a0697337ca50039/plugins/evernote.koplugin/clip.lua#L229
|
|
|
|
Clippings: Parsed form of highlights, stored in clipboard/evernote.sdr/metadata.sdr.lua
|
|
|
|
for all documents. Used only for exporting bookmarks. Internal highlight or bookmark functions
|
2020-04-26 17:36:56 +00:00
|
|
|
does not use this table.
|
2020-01-25 16:28:13 +00:00
|
|
|
Booknotes: Every table in clippings table. clippings = {"title" = booknotes}
|
|
|
|
--]]
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportAllNotes()
|
2017-02-27 07:41:06 +00:00
|
|
|
-- Flush highlights of current document.
|
|
|
|
if not self:isDocless() then
|
|
|
|
self.ui:saveSettings()
|
|
|
|
end
|
2014-05-12 10:07:20 +00:00
|
|
|
local clippings = self.config:readSetting("clippings") or {}
|
2014-05-12 13:19:17 +00:00
|
|
|
clippings = self:updateHistoryClippings(clippings, self.parser:parseHistory())
|
|
|
|
clippings = self:updateMyClippings(clippings, self.parser:parseMyClippings())
|
2014-04-23 14:19:29 +00:00
|
|
|
-- remove blank entries
|
|
|
|
for title, booknotes in pairs(clippings) do
|
|
|
|
-- chapter number is zero
|
|
|
|
if #booknotes == 0 then
|
|
|
|
clippings[title] = nil
|
|
|
|
end
|
|
|
|
end
|
2020-06-27 15:35:25 +00:00
|
|
|
--logger.dbg("clippings", clippings)
|
2014-10-16 10:25:02 +00:00
|
|
|
self:exportClippings(clippings)
|
2014-05-12 10:07:20 +00:00
|
|
|
self.config:saveSetting("clippings", clippings)
|
|
|
|
self.config:flush()
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportClippings(clippings)
|
2017-01-21 09:32:42 +00:00
|
|
|
local exported_stamp
|
2019-09-29 21:09:58 +00:00
|
|
|
local joplin_client
|
2021-12-25 09:24:48 +00:00
|
|
|
local readwise_client
|
2021-08-30 07:11:23 +00:00
|
|
|
if self.html_export then
|
2017-01-21 09:32:42 +00:00
|
|
|
exported_stamp= "html"
|
2020-04-26 17:36:56 +00:00
|
|
|
elseif self.json_export then
|
|
|
|
exported_stamp= "json"
|
2017-01-21 09:32:42 +00:00
|
|
|
elseif self.txt_export then
|
2020-01-25 16:28:13 +00:00
|
|
|
os.remove(self.text_clipping_file)
|
2017-01-21 09:32:42 +00:00
|
|
|
exported_stamp = "txt"
|
2019-09-29 21:09:58 +00:00
|
|
|
elseif self.joplin_export then
|
|
|
|
exported_stamp = "joplin"
|
|
|
|
joplin_client = JoplinClient:new{
|
|
|
|
server_ip = self.joplin_IP,
|
|
|
|
server_port = self.joplin_port,
|
|
|
|
auth_token = self.joplin_token
|
|
|
|
}
|
|
|
|
---@todo Check if user deleted our notebook, in that case note
|
|
|
|
-- will end up in random folder in Joplin.
|
|
|
|
if not self.joplin_notebook_guid then
|
|
|
|
self.joplin_notebook_guid = joplin_client:createNotebook(self.notebook_name)
|
|
|
|
self:saveSettings()
|
|
|
|
end
|
2021-12-25 09:24:48 +00:00
|
|
|
elseif self.readwise_export then
|
|
|
|
exported_stamp = "readwise"
|
|
|
|
readwise_client = ReadwiseClient:new{
|
|
|
|
auth_token = self.readwise_token
|
|
|
|
}
|
2017-01-21 09:32:42 +00:00
|
|
|
else
|
|
|
|
assert("an exported_stamp is expected for a new export type")
|
2014-10-16 10:25:02 +00:00
|
|
|
end
|
|
|
|
|
2014-04-23 14:19:29 +00:00
|
|
|
local export_count, error_count = 0, 0
|
|
|
|
local export_title, error_title
|
|
|
|
for title, booknotes in pairs(clippings) do
|
2014-05-15 09:27:05 +00:00
|
|
|
if type(booknotes.exported) ~= "table" then
|
|
|
|
booknotes.exported = {}
|
|
|
|
end
|
|
|
|
-- check if booknotes are exported in this notebook
|
|
|
|
-- so that booknotes will still be exported after switching user account
|
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
|
|
|
-- Don't respect exported_stamp on txt export since it isn't possible to delete(update) prior clippings.
|
2020-04-26 17:36:56 +00:00
|
|
|
if booknotes.exported[exported_stamp] ~= true or self.txt_export or self.json_export then
|
2014-10-16 10:25:02 +00:00
|
|
|
local ok, err
|
|
|
|
if self.html_export then
|
2017-01-21 09:32:42 +00:00
|
|
|
ok, err = pcall(self.exportBooknotesToHTML, self, title, booknotes)
|
|
|
|
elseif self.txt_export then
|
|
|
|
ok, err = pcall(self.exportBooknotesToTXT, self, title, booknotes)
|
2020-04-26 17:36:56 +00:00
|
|
|
elseif self.json_export then
|
|
|
|
ok, err = pcall(self.exportBooknotesToJSON, self, title, booknotes)
|
2019-09-29 21:09:58 +00:00
|
|
|
elseif self.joplin_export then
|
|
|
|
ok, err = pcall(self.exportBooknotesToJoplin, self, joplin_client, title, booknotes)
|
2021-12-25 09:24:48 +00:00
|
|
|
elseif self.readwise_export then
|
|
|
|
ok, err = pcall(self.exportBooknotesToReadwise, self, readwise_client, title, booknotes)
|
2014-10-16 10:25:02 +00:00
|
|
|
end
|
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
|
|
|
-- Error reporting
|
2014-07-17 13:00:45 +00:00
|
|
|
if not ok and err and err:find("Transport not open") then
|
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
|
|
|
--- @note: No recursive callback because it feels fishy here...
|
|
|
|
NetworkMgr:beforeWifiAction()
|
2014-07-17 13:00:45 +00:00
|
|
|
return
|
|
|
|
elseif not ok and err then
|
2020-06-27 15:35:25 +00:00
|
|
|
logger.dbg("Error while exporting book", title, err)
|
2014-05-12 10:07:20 +00:00
|
|
|
error_count = error_count + 1
|
|
|
|
error_title = title
|
2014-07-17 13:00:45 +00:00
|
|
|
elseif ok then
|
2020-06-27 15:35:25 +00:00
|
|
|
logger.dbg("Exported notes in book:", title)
|
2014-05-12 10:07:20 +00:00
|
|
|
export_count = export_count + 1
|
|
|
|
export_title = title
|
2014-10-16 10:25:02 +00:00
|
|
|
booknotes.exported[exported_stamp] = true
|
2014-05-12 10:07:20 +00:00
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-19 16:33:45 +00:00
|
|
|
local msg = "Nothing was exported."
|
2014-04-23 14:19:29 +00:00
|
|
|
local all_count = export_count + error_count
|
|
|
|
if export_count > 0 and error_count == 0 then
|
2020-01-15 07:59:36 +00:00
|
|
|
msg = T(
|
|
|
|
N_("Exported notes from the book:\n%1",
|
|
|
|
"Exported notes from the book:\n%1\nand %2 others.",
|
|
|
|
all_count-1),
|
|
|
|
export_title,
|
|
|
|
all_count-1
|
|
|
|
)
|
2014-04-23 14:19:29 +00:00
|
|
|
elseif error_count > 0 then
|
2020-01-15 07:59:36 +00:00
|
|
|
msg = T(
|
|
|
|
N_("An error occurred while trying to export notes from the book:\n%1",
|
|
|
|
"Multiple errors occurred while trying to export notes from the book:\n%1\nand %2 others.",
|
|
|
|
error_count-1),
|
|
|
|
error_title,
|
|
|
|
error_count-1
|
|
|
|
)
|
2014-04-23 14:19:29 +00:00
|
|
|
end
|
2017-02-27 07:41:06 +00:00
|
|
|
if (self.html_export or self.txt_export) and export_count > 0 then
|
2020-09-15 18:39:32 +00:00
|
|
|
msg = msg .. T(_("\nNotes can be found in %1/."), BD.dirpath(util.realpath(self.clipping_dir)))
|
2017-01-21 09:32:42 +00:00
|
|
|
end
|
2014-04-23 14:19:29 +00:00
|
|
|
UIManager:show(InfoMessage:new{ text = msg })
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportBooknotesToHTML(title, booknotes)
|
2014-10-16 10:25:02 +00:00
|
|
|
local content = slt2.render(self.template, {
|
|
|
|
booknotes = booknotes,
|
|
|
|
notemarks = self.notemarks,
|
|
|
|
})
|
2020-06-27 15:35:25 +00:00
|
|
|
--logger.dbg("content", content)
|
2014-10-16 10:25:02 +00:00
|
|
|
local html = io.open(self.clipping_dir .. "/" .. title .. ".html", "w")
|
|
|
|
if html then
|
|
|
|
html:write(content)
|
|
|
|
html:close()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportBooknotesToJSON(title, booknotes)
|
2020-04-26 17:36:56 +00:00
|
|
|
local file = io.open(self.json_clipping_file, "a")
|
|
|
|
if file then
|
|
|
|
file:write(json.encode(booknotes))
|
|
|
|
file:write("\n")
|
|
|
|
file:close()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportBooknotesToTXT(title, booknotes)
|
2017-02-27 07:41:06 +00:00
|
|
|
-- Use wide_space to avoid crengine to treat it specially.
|
|
|
|
local wide_space = "\227\128\128"
|
|
|
|
local file = io.open(self.text_clipping_file, "a")
|
2017-01-21 09:32:42 +00:00
|
|
|
if file then
|
2017-02-27 07:41:06 +00:00
|
|
|
file:write(title .. "\n" .. wide_space .. "\n")
|
2017-01-21 09:32:42 +00:00
|
|
|
for _ignore1, chapter in ipairs(booknotes) do
|
|
|
|
if chapter.title then
|
2017-02-27 07:41:06 +00:00
|
|
|
file:write(wide_space .. chapter.title .. "\n" .. wide_space .. "\n")
|
2017-01-21 09:32:42 +00:00
|
|
|
end
|
|
|
|
for _ignore2, clipping in ipairs(chapter) do
|
2020-01-25 16:28:13 +00:00
|
|
|
file:write(wide_space .. wide_space ..
|
|
|
|
T(_("-- Page: %1, added on %2\n"),
|
|
|
|
clipping.page, os.date("%c", clipping.time)))
|
|
|
|
if clipping.text then
|
|
|
|
file:write(clipping.text)
|
|
|
|
end
|
2021-12-25 09:24:48 +00:00
|
|
|
if clipping.note then
|
|
|
|
file:write("\n---\n" .. clipping.note)
|
|
|
|
end
|
2020-01-25 16:28:13 +00:00
|
|
|
if clipping.image then
|
|
|
|
file:write(_("<An image>"))
|
2017-01-21 09:32:42 +00:00
|
|
|
end
|
2020-01-25 16:28:13 +00:00
|
|
|
file:write("\n-=-=-=-=-=-\n")
|
2017-01-21 09:32:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
file:write("\n")
|
|
|
|
file:close()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
function Exporter:exportBooknotesToJoplin(client, title, booknotes)
|
2019-09-29 21:09:58 +00:00
|
|
|
if not client:ping() then
|
|
|
|
error("Cannot reach Joplin server")
|
|
|
|
end
|
|
|
|
|
|
|
|
local note_guid = client:findNoteByTitle(title, self.joplin_notebook_guid)
|
|
|
|
local note = ""
|
|
|
|
for _, chapter in ipairs(booknotes) do
|
|
|
|
if chapter.title then
|
|
|
|
note = note .. "\n\t*" .. chapter.title .. "*\n\n * * *"
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, clipping in ipairs(chapter) do
|
|
|
|
note = note .. os.date("%Y-%m-%d %H:%M:%S \n", clipping.time)
|
2021-12-25 09:24:48 +00:00
|
|
|
note = note .. clipping.text
|
|
|
|
if clipping.note then
|
|
|
|
note = note .. "\n---\n" .. clipping.note
|
|
|
|
end
|
|
|
|
note = note .. "\n * * *\n"
|
2019-09-29 21:09:58 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if note_guid then
|
|
|
|
client:updateNote(note_guid, note)
|
|
|
|
else
|
|
|
|
client:createNote(title, note, self.joplin_notebook_guid)
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2021-12-25 09:24:48 +00:00
|
|
|
function Exporter:exportBooknotesToReadwise(client, title, booknotes)
|
|
|
|
client:createHighlights(booknotes)
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:11:23 +00:00
|
|
|
return Exporter
|