From b245ffac57445e4ef71e7cf5995796ca0dd0daf0 Mon Sep 17 00:00:00 2001 From: kaoru <679719+0x6b@users.noreply.github.com> Date: Sun, 4 Oct 2020 01:54:40 +0900 Subject: [PATCH] feat: add new advanced setting to embed images as base64 text --- README.md | 13 ++++++ packages/core/src/copy.js | 3 +- .../core/src/plugins/img-reference-style.js | 21 ++++++++++ packages/core/src/settings.html | 2 + packages/core/src/settings.js | 18 +++++++- packages/core/src/util.js | 42 ++++++++++++++++++- packages/firefox/manifest.json | 1 + 7 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/plugins/img-reference-style.js diff --git a/README.md b/README.md index 369189d..d2a2dfa 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ To change shortcut key, click gear icon on top-right and click **Manage Extensio - Title Substitution -- line separated texts which will be removed from title text. If you add ` - Mozilla | MDN` to the textbox, the copied text wil be: - From `[Add-ons - Mozilla | MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons)` - To `[Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons)` +- Embed `img`s (.gif, .jpg, .jpeg, .png, and .webp) as base64 -- images will be encoded as base64 text, instead of URL, and added at the end of copied text. Sometime it might fail but useful for backup. See [Permissions](#privacy). ## Contributing @@ -212,3 +213,15 @@ This extension is released under the MIT License. See [LICENSE](LICENSE) for det ## Privacy The add-on does not store any user data outside of the Firefox user profile. The conversion to markdown is solely done locally. The add-on never send user action/data to any server. + +### Permissions + +See [permissions - Mozilla | MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions) for more detail. + +| Permission | Optional | Description | +| ---------------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `activeTab` | | to run copy functionality at active tab | +| `clipboardWrite` | | to write to clipboard obviously | +| `contextMenus` | | to add context menus | +| `storage` | | to store preferences | +| `` | X | when [**Embed imgs (.gif, .jpg, .jpeg, .png, and .webp) as base64 text as possible** option](#advanced) is set, the extension requests this permission since sometimes referenced images are hosted other than current active tab's URL so `activeTab` permission is not sufficient. If the option's not set, the extension removes this permission. | diff --git a/packages/core/src/copy.js b/packages/core/src/copy.js index eed6860..cd83124 100644 --- a/packages/core/src/copy.js +++ b/packages/core/src/copy.js @@ -47,6 +47,7 @@ async function main() { ? false : options.linkWithoutStyling; options.img = typeof options.img === "undefined" ? false : options.img; + options.embedImage = typeof options.embedImage === "undefined" ? false : options.embedImage; options.titleSubstitution = typeof options.titleSubstitution === "undefined" ? "" @@ -75,7 +76,7 @@ async function main() { ? `${title} (${document.URL})` : `[${title}](${document.URL})`; let html = `${title}`; - let selection = getSelectionAsMarkdown(options); + let selection = await getSelectionAsMarkdown(options); if (selection.output !== "") { if (options["use-quote"]) { diff --git a/packages/core/src/plugins/img-reference-style.js b/packages/core/src/plugins/img-reference-style.js new file mode 100644 index 0000000..7a85083 --- /dev/null +++ b/packages/core/src/plugins/img-reference-style.js @@ -0,0 +1,21 @@ +export default function img(turndownService) { + turndownService.addRule("img", { + filter: ["img"], + references: [], + replacement: function (content, node) { + const id = this.references.length + 1; + let title = ""; + title = node.title ? node.title : node.alt ? node.alt : ""; + this.references.push("[img" + id + "]: " + node.src); + return "![" + title + "][img" + id + "]"; + }, + append: function () { + let references = ""; + if (this.references.length) { + references = "\n\n" + this.references.join("\n") + "\n\n"; + this.references = []; + } + return references; + }, + }); +} diff --git a/packages/core/src/settings.html b/packages/core/src/settings.html index 206b65c..71f551f 100644 --- a/packages/core/src/settings.html +++ b/packages/core/src/settings.html @@ -194,6 +194,8 @@

Markdown

Advanced

+ + diff --git a/packages/core/src/settings.js b/packages/core/src/settings.js index 2f4614a..16ad80e 100644 --- a/packages/core/src/settings.js +++ b/packages/core/src/settings.js @@ -45,6 +45,8 @@ document.addEventListener("DOMContentLoaded", () => { : result.linkWithoutStyling; document.querySelector("#img").checked = typeof result.img === "undefined" ? false : result.img; + document.querySelector("#embedImage").checked = + typeof result.embedImage === "undefined" ? false : result.embedImage; document.querySelector("#titleSubstitution").value = typeof result.titleSubstitution === "undefined" ? "" @@ -62,8 +64,21 @@ document.addEventListener("DOMContentLoaded", () => { ); }); -document.querySelector("form").addEventListener("submit", (e) => { +document.querySelector("form").addEventListener("submit", async (e) => { e.preventDefault(); + + let embedImage = document.querySelector("#embedImage").checked || false; + + if (embedImage) { + embedImage = await browser.permissions.request({ + origins: [""], + permissions: [], + }); + document.querySelector("#embedImage").checked = embedImage; + } else { + await browser.permissions.remove({ origins: [""] }); + } + browser.storage.local.set({ "use-quote": document.querySelector("#quote").checked, "link-to-source": document.querySelector("#link").checked, @@ -80,6 +95,7 @@ document.querySelector("form").addEventListener("submit", (e) => { gfm: document.querySelector("#gfm").checked, linkWithoutStyling: document.querySelector("#linkWithoutStyling").checked, img: document.querySelector("#img").checked, + embedImage, titleSubstitution: document.querySelector("#titleSubstitution").value, reduceListItemPadding: document.querySelector("#reduceListItemPadding") .value, diff --git a/packages/core/src/util.js b/packages/core/src/util.js index eafe041..511fdd0 100644 --- a/packages/core/src/util.js +++ b/packages/core/src/util.js @@ -2,6 +2,7 @@ import TurndownService from "turndown"; import turndownPluginMathJax from "./plugins/mathjax"; import turndownPluginGfmStrikethrough from "./plugins/gfm-strikethrough"; import turndownPluginImg from "./plugins/img"; +import turndownPluginImgReferenceStyle from "./plugins/img-reference-style"; import turndownPluginLinkWithoutStyling from "./plugins/link-without-styling"; import turndownPluginListItem from "./plugins/list-item"; import { tables, taskListItems } from "turndown-plugin-gfm"; @@ -9,7 +10,7 @@ import * as clipboard from "clipboard-polyfill"; const url = require("url"); -const getSelectionAsMarkdown = (options) => { +const getSelectionAsMarkdown = async (options) => { let turndownService = TurndownService(options); if (options.mathjax) { @@ -135,6 +136,25 @@ const getSelectionAsMarkdown = (options) => { } } + if (options.embedImage) { + turndownService.use(turndownPluginImgReferenceStyle); + for (let img of container.getElementsByTagName("img")) { + if ( + img.hasAttribute("src") && + img.getAttribute("src").startsWith("http") && + ( + img.getAttribute("src").split("?", 2)[0].endsWith("gif") || + img.getAttribute("src").split("?", 2)[0].endsWith("jpg") || + img.getAttribute("src").split("?", 2)[0].endsWith("jpeg") || + img.getAttribute("src").split("?", 2)[0].endsWith("png") || + img.getAttribute("src").split("?", 2)[0].endsWith("webp") + ) + ) { + img.setAttribute("src", await imgToDataUrl(img)); + } + } + } + html = container.innerHTML; } @@ -148,4 +168,24 @@ const doCopy = (text, html) => { clipboard.write(dt); }; +const imgToDataUrl = (image) => { + return new Promise((resolve) => { + let img = new Image(); + img.setAttribute("crossorigin", "anonymous"); + img.onload = function () { + let canvas = document.createElement("canvas"); + canvas.width = this.naturalWidth; + canvas.height = this.naturalHeight; + + canvas.getContext("2d").drawImage(this, 0, 0); + image.setAttribute("src", canvas.toDataURL("image/png")); + + resolve(image.src); + canvas = null; + }; + + img.src = image.getAttribute("src"); + }); +}; + export { getSelectionAsMarkdown, doCopy }; diff --git a/packages/firefox/manifest.json b/packages/firefox/manifest.json index 128a26f..5353cc9 100644 --- a/packages/firefox/manifest.json +++ b/packages/firefox/manifest.json @@ -40,6 +40,7 @@ } }, "permissions": ["activeTab", "clipboardWrite", "contextMenus", "storage"], + "optional_permissions": [""], "applications": { "gecko": { "id": "{db9a72da-7bc5-4805-bcea-da3cb1a15316}"