From 77ca18122fb2e3f487388c0988e8c763844e5625 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 10 Jan 2024 23:07:48 +0700 Subject: [PATCH 01/29] Fix a typo in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2d13911..55c3c19d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ It uses the following libraries: The Common Mark icon courtesy of https://github.com/dcurtis/markdown-mark ## Pricing -This is an open-source extension I made *for fun*. It's intention is to be completely free. +This is an open-source extension I made *for fun*. Its intention is to be completely free. It's free on Firefox, Edge and Chrome (and other Chromium browsers), but unfortunately for Safari there is a yearly developer fee, so I've decided to charge a small price for the Safari version to help cover that cost. @@ -77,4 +77,4 @@ in my hat via the following: - Add support for websites using MathJax 3 (thanks @LeLocTai !) - Moved previous version history into [CHANGELOG.md](./CHANGELOG.md) -> Previous version history is recorded in [CHANGELOG.md](./CHANGELOG.md) \ No newline at end of file +> Previous version history is recorded in [CHANGELOG.md](./CHANGELOG.md) From d0078e9c83e89f758d0411a11d9a1ffb2be1bcbe Mon Sep 17 00:00:00 2001 From: nekton Date: Tue, 6 Feb 2024 07:54:19 +0900 Subject: [PATCH 02/29] fix filename issue when saving to obsidian use formatTitle(article) instead of article.title in obsidian func --- src/background/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/background.js b/src/background/background.js index 5b5445f5..c8002a22 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -955,7 +955,7 @@ async function copyMarkdownFromContext(info, tab) { } else if(info.menuItemId == "copy-markdown-obsidian") { const article = await getArticleFromContent(tab.id, info.menuItemId == "copy-markdown-obsidian"); - const title = article.title; + const title = await formatTitle(article); const options = await getOptions(); const obsidianVault = options.obsidianVault; const obsidianFolder = await formatObsidianFolder(article); From 3d598c167c599359206bb15fd894444d5cb0e855 Mon Sep 17 00:00:00 2001 From: nekton39 Date: Tue, 6 Feb 2024 08:18:13 +0900 Subject: [PATCH 03/29] fix filename issue when saving to obsidian use formatTitle(article) instead of article.title in obsidian func --- src/background/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/background.js b/src/background/background.js index c8002a22..334a0fb3 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -965,7 +965,7 @@ async function copyMarkdownFromContext(info, tab) { } else if(info.menuItemId == "copy-markdown-obsall") { const article = await getArticleFromContent(tab.id, info.menuItemId == "copy-markdown-obsall"); - const title = article.title; + const title = await formatTitle(article); const options = await getOptions(); const obsidianVault = options.obsidianVault; const obsidianFolder = await formatObsidianFolder(article); From 0abc8267a888c2b4490d88cac50dcff97518e46a Mon Sep 17 00:00:00 2001 From: 2gn <101851090+2gn@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:16:13 +0900 Subject: [PATCH 04/29] Detailed instructions on how to use obsidian integration. fixes #302 --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2d13911..c47f7fab 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,22 @@ The extension is available for [Firefox](https://addons.mozilla.org/en-GB/firefo # Obsidian Integration For integration with obsidian, you need to install and enable community plugins named "Advanced Obsidian URI". This plugin help us to bypass character limitation in URL. Because it's using clipboard as the source for creating new file. -More information: https://vinzent03.github.io/obsidian-advanced-uri/ +More information about Advanced Obsidian URI plugin: https://vinzent03.github.io/obsidian-advanced-uri/ + +You need to do some configurations in order to use this integration. +
+ Steps to follow + + 1. Left-Click on the extension + 2. Click on the gear icon to open the configuration menu + 3. Scroll down to "Obsidian integration" section and turn "enable obsidian integration" on. + 4. Fill out the form below (Obsidian vault name and Obsidian folder name.) + 5. Right-click on the extension and open the menu + 6. In "MarkDownload - Markdown Web Clipper", select "Send Tab to Obsidian" + +
+ + # External Libraries It uses the following libraries: @@ -77,4 +92,4 @@ in my hat via the following: - Add support for websites using MathJax 3 (thanks @LeLocTai !) - Moved previous version history into [CHANGELOG.md](./CHANGELOG.md) -> Previous version history is recorded in [CHANGELOG.md](./CHANGELOG.md) \ No newline at end of file +> Previous version history is recorded in [CHANGELOG.md](./CHANGELOG.md) From 5d8075c73d59bcb87557cd5366937a378e3113a8 Mon Sep 17 00:00:00 2001 From: WetHat Date: Wed, 27 Mar 2024 15:42:26 +0100 Subject: [PATCH 05/29] Prevent Readability from removing the element if has a 'class' attribute which matches removal criteria. Removal of the HTML element causies Markdownload to fail because: * the pageTitle is undefined * basicUri point to chrome-extension://nghfdnngoejlhedogdpikdlkmdfoojii/_generated_background_page.html rather than the actual web page * all local links are incorrect --- src/background/background.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/background/background.js b/src/background/background.js index 5b5445f5..7e1bf3fc 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -746,6 +746,12 @@ async function getArticleFromDom(domString) { header.outerHTML = header.outerHTML; }); + // Prevent Readability from removing the element if has a 'class' attribute + // which matches removal criteria. + // Note: The document element is guaranteed to be the HTML tag because the 'text/html' + // mime type was used when the DOM was created. + dom.documentElement.removeAttribute('class') + // simplify the dom into an article const article = new Readability(dom).parse(); From 94b3daef6de6c1335f4f9bc5bfc88a2079892697 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 09:38:45 +0100 Subject: [PATCH 06/29] Make sure the 'href' attribute of the base tag points to the actual web page's base. Some website specify the href of the base tag as '/'. This causes the JDOMParser to set: * dom.baseURI = 'chrome-extension://nghfdnngoejlhedogdpikdlkmdfoojii/' * dom.documentURI = 'chrome-extension://nghfdnngoejlhedogdpikdlkmdfoojii/_generated_background_page.html' when it parse HTML text in the background context and subsequently Readability.js genarates garbage for local links --- src/contentScript/contentScript.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js index 1ef736ef..2a889f06 100644 --- a/src/contentScript/contentScript.js +++ b/src/contentScript/contentScript.js @@ -6,27 +6,31 @@ function notifyExtension() { function getHTMLOfDocument() { // if the document doesn't have a "base" element make one // this allows the DOM parser in future steps to fix relative uris - let baseEl = document.createElement('base'); - // check for a existing base elements let baseEls = document.head.getElementsByTagName('base'); + let baseEl; + if (baseEls.length > 0) { baseEl = baseEls[0]; - } - // if we don't find one, append this new one. - else { + } else { + baseEl = document.createElement('base'); document.head.append(baseEl); } - // if the base element doesn't have a href, use the current location - if (!baseEl.getAttribute('href')) { + // make sure the 'base' element always has a good 'href` + // attribute so that the DOMParser generates usable + // baseURI and documentURI properties when used in the + // background context. + + let href = baseEl.getAttribute('href'); + + if (!href || !href.startsWith(window.location.origin)) { baseEl.setAttribute('href', window.location.href); } - - // remove the hidden content from the page + // remove the hidden content from the page removeHiddenNodes(document.body); - + // get the content of the page as a string return document.documentElement.outerHTML; } @@ -56,7 +60,6 @@ function removeHiddenNodes(root) { } return root } - // code taken from here: https://stackoverflow.com/a/5084044/304786 function getHTMLOfSelection() { From 31e7c39ecf648590367ee0e54d8ac6c519fc4cb5 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 11:58:05 +0100 Subject: [PATCH 07/29] Update to 7.1.3 --- src/background/turndown.js | 82 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/background/turndown.js b/src/background/turndown.js index f77de140..bf5a62f2 100644 --- a/src/background/turndown.js +++ b/src/background/turndown.js @@ -1,7 +1,7 @@ var TurndownService = (function () { 'use strict'; - function extend(destination) { + function extend (destination) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { @@ -11,15 +11,15 @@ var TurndownService = (function () { return destination } - function repeat(character, count) { + function repeat (character, count) { return Array(count + 1).join(character) } - function trimLeadingNewlines(string) { + function trimLeadingNewlines (string) { return string.replace(/^\n*/, '') } - function trimTrailingNewlines(string) { + function trimTrailingNewlines (string) { // avoid match-at-end regexp bottleneck, see #370 var indexEnd = string.length; while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--; @@ -35,7 +35,7 @@ var TurndownService = (function () { 'TFOOT', 'TH', 'THEAD', 'TR', 'UL' ]; - function isBlock(node) { + function isBlock (node) { return is(node, blockElements) } @@ -44,11 +44,11 @@ var TurndownService = (function () { 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR' ]; - function isVoid(node) { + function isVoid (node) { return is(node, voidElements) } - function hasVoid(node) { + function hasVoid (node) { return has(node, voidElements) } @@ -57,19 +57,19 @@ var TurndownService = (function () { 'AUDIO', 'VIDEO' ]; - function isMeaningfulWhenBlank(node) { + function isMeaningfulWhenBlank (node) { return is(node, meaningfulWhenBlankElements) } - function hasMeaningfulWhenBlank(node) { + function hasMeaningfulWhenBlank (node) { return has(node, meaningfulWhenBlankElements) } - function is(node, tagNames) { + function is (node, tagNames) { return tagNames.indexOf(node.nodeName) >= 0 } - function has(node, tagNames) { + function has (node, tagNames) { return ( node.getElementsByTagName && tagNames.some(function (tagName) { @@ -335,7 +335,7 @@ var TurndownService = (function () { } }; - function cleanAttribute(attribute) { + function cleanAttribute (attribute) { return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '' } @@ -343,7 +343,7 @@ var TurndownService = (function () { * Manages a collection of rules used to convert HTML to Markdown */ - function Rules(options) { + function Rules (options) { this.options = options; this._keep = []; this._remove = []; @@ -399,7 +399,7 @@ var TurndownService = (function () { } }; - function findRule(rules, node, options) { + function findRule (rules, node, options) { for (var i = 0; i < rules.length; i++) { var rule = rules[i]; if (filterValue(rule, node, options)) return rule @@ -407,7 +407,7 @@ var TurndownService = (function () { return void 0 } - function filterValue(rule, node, options) { + function filterValue (rule, node, options) { var filter = rule.filter; if (typeof filter === 'string') { if (filter === node.nodeName.toLowerCase()) return true @@ -452,7 +452,7 @@ var TurndownService = (function () { * * @param {Object} options */ - function collapseWhitespace(options) { + function collapseWhitespace (options) { var element = options.element; var isBlock = options.isBlock; var isVoid = options.isVoid; @@ -473,7 +473,7 @@ var TurndownService = (function () { var text = node.data.replace(/[ \r\n\t]+/g, ' '); if ((!prevText || / $/.test(prevText.data)) && - !keepLeadingWs && text[0] === ' ') { + !keepLeadingWs && text[0] === ' ') { text = text.substr(1); } @@ -527,7 +527,7 @@ var TurndownService = (function () { * @param {Node} node * @return {Node} node */ - function remove(node) { + function remove (node) { var next = node.nextSibling || node.parentNode; node.parentNode.removeChild(node); @@ -544,7 +544,7 @@ var TurndownService = (function () { * @param {Function} isPre * @return {Node} */ - function next(prev, current, isPre) { + function next (prev, current, isPre) { if ((prev && prev.parentNode === current) || isPre(current)) { return current.nextSibling || current.parentNode } @@ -562,7 +562,7 @@ var TurndownService = (function () { * Parsing HTML strings */ - function canParseHTMLNatively() { + function canParseHTMLNatively () { var Parser = root.DOMParser; var canParse = false; @@ -573,13 +573,13 @@ var TurndownService = (function () { if (new Parser().parseFromString('', 'text/html')) { canParse = true; } - } catch (e) { } + } catch (e) {} return canParse } - function createHTMLParser() { - var Parser = function () { }; + function createHTMLParser () { + var Parser = function () {}; { if (shouldUseActiveX()) { @@ -604,19 +604,19 @@ var TurndownService = (function () { return Parser } - function shouldUseActiveX() { + function shouldUseActiveX () { var useActiveX = false; try { document.implementation.createHTMLDocument('').open(); } catch (e) { - if (window.ActiveXObject) useActiveX = true; + if (root.ActiveXObject) useActiveX = true; } return useActiveX } var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser(); - function RootNode(input, options) { + function RootNode (input, options) { var root; if (typeof input === 'string') { var doc = htmlParser().parseFromString( @@ -641,16 +641,16 @@ var TurndownService = (function () { } var _htmlParser; - function htmlParser() { + function htmlParser () { _htmlParser = _htmlParser || new HTMLParser(); return _htmlParser } - function isPreOrCode(node) { + function isPreOrCode (node) { return node.nodeName === 'PRE' || node.nodeName === 'CODE' } - function Node(node, options) { + function Node (node, options) { node.isBlock = isBlock(node); node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode; node.isBlank = isBlank(node); @@ -658,7 +658,7 @@ var TurndownService = (function () { return node } - function isBlank(node) { + function isBlank (node) { return ( !isVoid(node) && !isMeaningfulWhenBlank(node) && @@ -668,7 +668,7 @@ var TurndownService = (function () { ) } - function flankingWhitespace(node, options) { + function flankingWhitespace (node, options) { if (node.isBlock || (options.preformattedCode && node.isCode)) { return { leading: '', trailing: '' } } @@ -688,8 +688,8 @@ var TurndownService = (function () { return { leading: edges.leading, trailing: edges.trailing } } - function edgeWhitespace(string) { - var m = string.match(/^(([ \t\r\n]*)(\s*))[\s\S]*?((\s*?)([ \t\r\n]*))$/); + function edgeWhitespace (string) { + var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/); return { leading: m[1], // whole string for whitespace-only strings leadingAscii: m[2], @@ -700,7 +700,7 @@ var TurndownService = (function () { } } - function isFlankedByWhitespace(side, node, options) { + function isFlankedByWhitespace (side, node, options) { var sibling; var regExp; var isFlanked; @@ -742,7 +742,7 @@ var TurndownService = (function () { [/^(\d+)\. /g, '$1\\. '] ]; - function TurndownService(options) { + function TurndownService (options) { if (!(this instanceof TurndownService)) return new TurndownService(options) var defaults = { @@ -876,7 +876,7 @@ var TurndownService = (function () { * @type String */ - function process(parentNode) { + function process (parentNode) { var self = this; return reduce.call(parentNode.childNodes, function (output, node) { node = new Node(node, self.options); @@ -900,7 +900,7 @@ var TurndownService = (function () { * @type String */ - function postProcess(output) { + function postProcess (output) { var self = this; this.rules.forEach(function (rule) { if (typeof rule.append === 'function') { @@ -919,7 +919,7 @@ var TurndownService = (function () { * @type String */ - function replacementForNode(node) { + function replacementForNode (node) { var rule = this.rules.forNode(node); var content = process.call(this, node); var whitespace = node.flankingWhitespace; @@ -940,7 +940,7 @@ var TurndownService = (function () { * @type String */ - function join(output, replacement) { + function join (output, replacement) { var s1 = trimTrailingNewlines(output); var s2 = trimLeadingNewlines(replacement); var nls = Math.max(output.length - s1.length, replacement.length - s2.length); @@ -957,7 +957,7 @@ var TurndownService = (function () { * @type String|Object|Array|Boolean|Number */ - function canConvert(input) { + function canConvert (input) { return ( input != null && ( typeof input === 'string' || @@ -970,4 +970,4 @@ var TurndownService = (function () { return TurndownService; -}()); \ No newline at end of file +}()); From 419f91b387f4b4fafef5373aba67cc4fa1e6c4b4 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 12:10:21 +0100 Subject: [PATCH 08/29] Readability component updated to 0.5.0 --- src/background/Readability.js | 71 +++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/src/background/Readability.js b/src/background/Readability.js index ce06df45..b745aa01 100644 --- a/src/background/Readability.js +++ b/src/background/Readability.js @@ -1,4 +1,3 @@ -/*eslint-env es6:false*/ /* * Copyright (c) 2010 Arc90 Inc * @@ -54,6 +53,7 @@ function Readability(doc, options) { return el.innerHTML; }; this._disableJSONLD = !!options.disableJSONLD; + this._allowedVideoRegex = options.allowedVideoRegex || this.REGEXPS.videos; // Start with all flags set this._flags = this.FLAG_STRIP_UNLIKELYS | @@ -73,12 +73,7 @@ function Readability(doc, options) { return `<${node.localName} ${attrPairs}>`; }; this.log = function () { - if (typeof dump !== "undefined") { - var msg = Array.prototype.map.call(arguments, function(x) { - return (x && x.nodeName) ? logNode(x) : x; - }).join(" "); - dump("Reader: (Readability) " + msg + "\n"); - } else if (typeof console !== "undefined") { + if (typeof console !== "undefined") { let args = Array.from(arguments, arg => { if (arg && arg.nodeType == this.ELEMENT_NODE) { return logNode(arg); @@ -87,6 +82,12 @@ function Readability(doc, options) { }); args.unshift("Reader: (Readability)"); console.log.apply(console, args); + } else if (typeof dump !== "undefined") { + /* global dump */ + var msg = Array.prototype.map.call(arguments, function(x) { + return (x && x.nodeName) ? logNode(x) : x; + }).join(" "); + dump("Reader: (Readability) " + msg + "\n"); } }; } else { @@ -140,6 +141,9 @@ Readability.prototype = { hashUrl: /^#.+/, srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g, b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i, + // Commas as used in Latin, Sindhi, Chinese and various other scripts. + // see: https://en.wikipedia.org/wiki/Comma#Comma_variants + commas: /\u002C|\u060C|\uFE50|\uFE10|\uFE11|\u2E41|\u2E34|\u2E32|\uFF0C/g, // See: https://schema.org/Article jsonLdArticleTypes: /^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/ }, @@ -909,6 +913,12 @@ Readability.prototype = { continue; } + // User is not able to see elements applied with both "aria-modal = true" and "role = dialog" + if (node.getAttribute("aria-modal") == "true" && node.getAttribute("role") == "dialog") { + node = this._removeAndGetNext(node); + continue; + } + // Check to see if this node is a byline, and remove it if it is. if (this._checkByline(node, matchString)) { node = this._removeAndGetNext(node); @@ -1023,7 +1033,7 @@ Readability.prototype = { contentScore += 1; // Add points for any commas within this paragraph. - contentScore += innerText.split(",").length; + contentScore += innerText.split(this.REGEXPS.commas).length; // For every 100 characters in this paragraph, add another point. Up to 3 points. contentScore += Math.min(Math.floor(innerText.length / 100), 3); @@ -1437,6 +1447,9 @@ Readability.prototype = { ) { metadata.siteName = parsed.publisher.name.trim(); } + if (typeof parsed.datePublished === "string") { + metadata.datePublished = parsed.datePublished.trim(); + } return; } catch (err) { this.log(err.message); @@ -1460,7 +1473,7 @@ Readability.prototype = { var metaElements = this._doc.getElementsByTagName("meta"); // property is a space-separated list of values - var propertyPattern = /\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi; + var propertyPattern = /\s*(article|dc|dcterm|og|twitter)\s*:\s*(author|creator|description|published_time|title|site_name)\s*/gi; // name is a single value var namePattern = /^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i; @@ -1531,12 +1544,17 @@ Readability.prototype = { metadata.siteName = jsonld.siteName || values["og:site_name"]; + // get article published time + metadata.publishedTime = jsonld.datePublished || + values["article:published_time"] || null; + // in many sites the meta value is escaped with HTML entities, // so here we need to unescape it metadata.title = this._unescapeHtmlEntities(metadata.title); metadata.byline = this._unescapeHtmlEntities(metadata.byline); metadata.excerpt = this._unescapeHtmlEntities(metadata.excerpt); metadata.siteName = this._unescapeHtmlEntities(metadata.siteName); + metadata.publishedTime = this._unescapeHtmlEntities(metadata.publishedTime); return metadata; }, @@ -1642,12 +1660,7 @@ Readability.prototype = { * @param Element **/ _removeScripts: function(doc) { - this._removeNodes(this._getAllNodesWithTag(doc, ["script"]), function(scriptNode) { - scriptNode.nodeValue = ""; - scriptNode.removeAttribute("src"); - return true; - }); - this._removeNodes(this._getAllNodesWithTag(doc, ["noscript"])); + this._removeNodes(this._getAllNodesWithTag(doc, ["script", "noscript"])); }, /** @@ -1837,13 +1850,13 @@ Readability.prototype = { if (isEmbed) { // First, check the elements attributes to see if any of them contain youtube or vimeo for (var i = 0; i < element.attributes.length; i++) { - if (this.REGEXPS.videos.test(element.attributes[i].value)) { + if (this._allowedVideoRegex.test(element.attributes[i].value)) { return false; } } // For embed with tag, check inner HTML as well. - if (element.tagName === "object" && this.REGEXPS.videos.test(element.innerHTML)) { + if (element.tagName === "object" && this._allowedVideoRegex.test(element.innerHTML)) { return false; } } @@ -2112,13 +2125,13 @@ Readability.prototype = { for (var i = 0; i < embeds.length; i++) { // If this embed has attribute that matches video regex, don't delete it. for (var j = 0; j < embeds[i].attributes.length; j++) { - if (this.REGEXPS.videos.test(embeds[i].attributes[j].value)) { + if (this._allowedVideoRegex.test(embeds[i].attributes[j].value)) { return false; } } // For embed with tag, check inner HTML as well. - if (embeds[i].tagName === "object" && this.REGEXPS.videos.test(embeds[i].innerHTML)) { + if (embeds[i].tagName === "object" && this._allowedVideoRegex.test(embeds[i].innerHTML)) { return false; } @@ -2136,6 +2149,21 @@ Readability.prototype = { (!isList && weight < 25 && linkDensity > 0.2) || (weight >= 25 && linkDensity > 0.5) || ((embedCount === 1 && contentLength < 75) || embedCount > 1); + // Allow simple lists of images to remain in pages + if (isList && haveToRemove) { + for (var x = 0; x < node.children.length; x++) { + let child = node.children[x]; + // Don't filter in lists with li's that contain more than one child + if (child.children.length > 1) { + return haveToRemove; + } + } + let li_count = node.getElementsByTagName("li").length; + // Only allow the list to remain if every li contains an image + if (img == li_count) { + return false; + } + } return haveToRemove; } return false; @@ -2205,6 +2233,7 @@ Readability.prototype = { _isProbablyVisible: function(node) { // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes. return (!node.style || node.style.display != "none") + && (!node.style || node.style.visibility != "hidden") && !node.hasAttribute("hidden") //check for "fallback-image" so that wikimedia math images are displayed && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1)); @@ -2273,11 +2302,13 @@ Readability.prototype = { textContent: textContent, length: textContent.length, excerpt: metadata.excerpt, - siteName: metadata.siteName || this._articleSiteName + siteName: metadata.siteName || this._articleSiteName, + publishedTime: metadata.publishedTime }; } }; if (typeof module === "object") { + /* global module */ module.exports = Readability; } From be04cbd1a4c31411228f3d464eb720e3a12c6734 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 19:20:59 +0100 Subject: [PATCH 09/29] Provide a defaut titl if a web page has none --- src/contentScript/contentScript.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js index 1ef736ef..c92631b0 100644 --- a/src/contentScript/contentScript.js +++ b/src/contentScript/contentScript.js @@ -4,6 +4,14 @@ function notifyExtension() { } function getHTMLOfDocument() { + // make sure a title tag exists so that pageTitle os not empty + if (!document.title) { + let titleEl = document.createElement('title'); + document.head.append(titleEl); + // prepate the default text + titleEl.innerText = window.location.hostname + "╱"+ window.location.pathname.replace('/','╱') + } + // if the document doesn't have a "base" element make one // this allows the DOM parser in future steps to fix relative uris let baseEl = document.createElement('base'); From f9ca896703b3dfcc8b4e1091973f6ded133250f5 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 20:07:38 +0100 Subject: [PATCH 10/29] Generate a document title element if none is presentaso that pageTitle has a value and download can succeed. --- src/contentScript/contentScript.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js index c92631b0..3f269e47 100644 --- a/src/contentScript/contentScript.js +++ b/src/contentScript/contentScript.js @@ -4,12 +4,13 @@ function notifyExtension() { } function getHTMLOfDocument() { - // make sure a title tag exists so that pageTitle os not empty - if (!document.title) { + // make sure a title tag exists so that pageTitle is not empty and + // a filename can be genarated. + if (document.head.getElementsByTagName('title') == 0) { let titleEl = document.createElement('title'); + // prepate a good default text (the text displayed in the window title) + titleEl.innerText = document.title; document.head.append(titleEl); - // prepate the default text - titleEl.innerText = window.location.hostname + "╱"+ window.location.pathname.replace('/','╱') } // if the document doesn't have a "base" element make one From d2eb59477875f743f75b365e064c8e5458f07a71 Mon Sep 17 00:00:00 2001 From: WetHat Date: Thu, 28 Mar 2024 20:10:53 +0100 Subject: [PATCH 11/29] Title element presence check fixed --- src/contentScript/contentScript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js index 3f269e47..d195421f 100644 --- a/src/contentScript/contentScript.js +++ b/src/contentScript/contentScript.js @@ -6,7 +6,7 @@ function notifyExtension() { function getHTMLOfDocument() { // make sure a title tag exists so that pageTitle is not empty and // a filename can be genarated. - if (document.head.getElementsByTagName('title') == 0) { + if (document.head.getElementsByTagName('title').length == 0) { let titleEl = document.createElement('title'); // prepate a good default text (the text displayed in the window title) titleEl.innerText = document.title; From 0484f3d8cd6c4f95eb2aaf00e2c2f33a9e41a3a1 Mon Sep 17 00:00:00 2001 From: WetHat Date: Tue, 2 Apr 2024 20:39:57 +0200 Subject: [PATCH 12/29] Fixed flattened code blocks. This occured because the 'codel' element was ripped out from its owning 'pre' and the code.innerText lost all spacings and linefeeds. In addition a second fix was needed because all tabs where stripped in the turndown function (bachground.js). This caused code to loose indent and look unreadable. --- src/background/background.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/background/background.js b/src/background/background.js index 5b5445f5..7e3a9248 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -150,11 +150,7 @@ function turndown(content, options, article) { var code; if (language) { - var div = document.createElement('div'); - document.body.appendChild(div); - div.appendChild(node); code = node.innerText; - div.remove(); } else { code = node.innerHTML; } @@ -206,7 +202,7 @@ function turndown(content, options, article) { // strip out non-printing special characters which CodeMirror displays as a red dot // see: https://codemirror.net/doc/manual.html#option_specialChars - markdown = markdown.replace(/[\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, ''); + markdown = markdown.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, ''); return { markdown: markdown, imageList: imageList }; } From 1bfcd7f7764a38220c8f876c98d13f897ba4404b Mon Sep 17 00:00:00 2001 From: WetHat Date: Wed, 3 Apr 2024 08:23:19 +0200 Subject: [PATCH 13/29] Return plain text (rather than html) if called for `
` element which do not contain a `` cild element. This allows
 `
` blocks to be rendered as markdown fenced blocks.

---
 src/background/background.js | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/background/background.js b/src/background/background.js
index 7e3a9248..f0a60803 100644
--- a/src/background/background.js
+++ b/src/background/background.js
@@ -147,26 +147,20 @@ function turndown(content, options, article) {
     const langMatch = node.id?.match(/code-lang-(.+)/);
     const language = langMatch?.length > 0 ? langMatch[1] : '';
 
-    var code;
+    const code = node.innerText;
 
-    if (language) {
-      code = node.innerText;
-    } else {
-      code = node.innerHTML;
-    }
-
-    var fenceChar = options.fence.charAt(0);
-    var fenceSize = 3;
-    var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
+    const fenceChar = options.fence.charAt(0);
+    let fenceSize = 3;
+    const fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
 
-    var match;
+    let match;
     while ((match = fenceInCodeRegex.exec(code))) {
       if (match[0].length >= fenceSize) {
         fenceSize = match[0].length + 1;
       }
     }
 
-    var fence = repeat(fenceChar, fenceSize);
+    const fence = repeat(fenceChar, fenceSize);
 
     return (
       '\n\n' + fence + language + '\n' +

From 156fde1e6b5d06d2a7304eff5dadbe06d5230917 Mon Sep 17 00:00:00 2001
From: WetHat 
Date: Wed, 3 Apr 2024 13:50:31 +0200
Subject: [PATCH 14/29] Do not creeate a fenced block if `
` tag contains
 an image

---
 src/background/background.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/background/background.js b/src/background/background.js
index f0a60803..711404b3 100644
--- a/src/background/background.js
+++ b/src/background/background.js
@@ -185,7 +185,11 @@ function turndown(content, options, article) {
 
   // handle 
 as code blocks
   turndownService.addRule('pre', {
-    filter: (node, tdopts) => node.nodeName == 'PRE' && (!node.firstChild || node.firstChild.nodeName != 'CODE'),
+    filter: (node, tdopts) => {
+      return node.nodeName == 'PRE'
+             && (!node.firstChild || node.firstChild.nodeName != 'CODE')
+             && !node.querySelector('img');
+    },
     replacement: (content, node, tdopts) => {
       return convertToFencedCodeBlock(node, tdopts);
     }

From aa8ac756bfd768ec40daf1502af21aea5891a1d6 Mon Sep 17 00:00:00 2001
From: Gabe Dunn 
Date: Mon, 3 Jun 2024 15:01:45 -0600
Subject: [PATCH 15/29] add lowercase and uppercase parameterizations

---
 src/background/background.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/background/background.js b/src/background/background.js
index 5b5445f5..43a503b4 100644
--- a/src/background/background.js
+++ b/src/background/background.js
@@ -275,6 +275,8 @@ function textReplace(string, article, disallowedChars = null) {
       if (s && disallowedChars) s = this.generateValidFileName(s, disallowedChars);
 
       string = string.replace(new RegExp('{' + key + '}', 'g'), s)
+        .replace(new RegExp('{' + key + ':lower}', 'g'), s.toLowerCase())
+        .replace(new RegExp('{' + key + ':upper}', 'g'), s.toUpperCase())
         .replace(new RegExp('{' + key + ':kebab}', 'g'), s.replace(/ /g, '-').toLowerCase())
         .replace(new RegExp('{' + key + ':snake}', 'g'), s.replace(/ /g, '_').toLowerCase())
         .replace(new RegExp('{' + key + ':camel}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toLowerCase()))

From 60a1e718321baec534437e77d8c56f3846075b6e Mon Sep 17 00:00:00 2001
From: Gabe Dunn 
Date: Mon, 3 Jun 2024 15:04:39 -0600
Subject: [PATCH 16/29] add docs on lower and upper case parameterizations

---
 src/options/options.html | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/options/options.html b/src/options/options.html
index 71d5eccf..a50b4c09 100644
--- a/src/options/options.html
+++ b/src/options/options.html
@@ -44,6 +44,8 @@ 

Custom text ℹ️

Additionally, you can 'parameterize' any of the text variables (other than date and keywords) by using the following syntax:

    +
  • {variable:lower} - lower case: Every word lower case
  • +
  • {variable:upper} - UPPER CASE: Every word upper case
  • {variable:pascal} - PascalCase: Every word capital
  • {variable:camel} - camelCase: every word capital except the first word
  • {variable:kebab} - kebab-case: hyphens between words, all lowercase
  • From 528940ed054cfbf073335120c467fce20fdebb6f Mon Sep 17 00:00:00 2001 From: Nate H Date: Tue, 20 Aug 2024 17:18:05 -0400 Subject: [PATCH 17/29] Added additional parameterization options --- CHANGELOG.md | 6 + src/background/background.js | 4 + src/manifest.json | 2 +- src/options/options.html | 3 + src/package-lock.json | 2265 +++++++++++++++++++--------------- 5 files changed, 1290 insertions(+), 990 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b3f067..3aecb4fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # Changelog +## 3.3.1 +- Added additional template casing variables that retain original letter casing. For example, for an article titled "Different Types of Casing - Source":: + - `{pageTitle:mixed-kebab}` — "Different-Types-of-Casing---Source" + - `{pageTitle:mixed_snake}` — "Different_Types_of_Casing_-_Source" + - `{pageTitle:obsidian-cal}` — "Different-Types-of-Casing-Source" This option is for additional compatibility with the Obsidian Consistent Attachment Location plugin. + ## 3.3.0 - Remove hidden content before exporting (thanks @nhaouari !). This allows you to use a different extension (e.g. Adblock) to hide elements that would otherwise clutter up your export - Fixes for Obsidian integration in Safari (thanks @aancw !) diff --git a/src/background/background.js b/src/background/background.js index 5b5445f5..6c4f78a2 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -276,7 +276,11 @@ function textReplace(string, article, disallowedChars = null) { string = string.replace(new RegExp('{' + key + '}', 'g'), s) .replace(new RegExp('{' + key + ':kebab}', 'g'), s.replace(/ /g, '-').toLowerCase()) + .replace(new RegExp('{' + key + ':mixed-kebab}', 'g'), s.replace(/ /g, '-')) .replace(new RegExp('{' + key + ':snake}', 'g'), s.replace(/ /g, '_').toLowerCase()) + .replace(new RegExp('{' + key + ':mixed_snake}', 'g'), s.replace(/ /g, '_')) + // For Obsidian Custom Attachment Location plugin, we need to replace spaces with hyphens, but also remove any double hyphens. + .replace(new RegExp('{' + key + ':obsidian-cal}', 'g'), s.replace(/ /g, '-').replace(/-{2,}/g, "-")) .replace(new RegExp('{' + key + ':camel}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toLowerCase())) .replace(new RegExp('{' + key + ':pascal}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toUpperCase())) } diff --git a/src/manifest.json b/src/manifest.json index 037dc7d5..31b63706 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "MarkDownload - Markdown Web Clipper", - "version": "3.3.0", + "version": "3.3.1", "author": "Gordon Pedsersen", "description": "This extension works like a web clipper, but it downloads articles in markdown format.", "icons": { diff --git a/src/options/options.html b/src/options/options.html index 71d5eccf..2b7e84e8 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -47,7 +47,10 @@

    Custom text ℹ️

  • {variable:pascal} - PascalCase: Every word capital
  • {variable:camel} - camelCase: every word capital except the first word
  • {variable:kebab} - kebab-case: hyphens between words, all lowercase
  • +
  • {variable:mixed-kebab} - kebab-case: hyphens between words, no case changes
  • {variable:snake} - snake_case: underscores between words, all lowercase
  • +
  • {variable:mixed_snake} - snake_case: underscores between words, no case changes
  • +
  • {variable:obsidian-cal} - Compatibility for Obsidan Custom Attachment Location: hyphens between words, no case changes, double hypens removed

diff --git a/src/package-lock.json b/src/package-lock.json index 0e2176e8..d7ce449b 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "src", "devDependencies": { "web-ext": "^7.4.0" } @@ -115,12 +116,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", - "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "dev": true, "dependencies": { - "regenerator-runtime": "^0.13.10" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -165,16 +166,52 @@ "node": ">= 0.10.4" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -204,6 +241,35 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -222,14 +288,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -250,15 +336,60 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@mdn/browser-compat-data": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.17.tgz", - "integrity": "sha512-aA+rFHhXmq14GVIcEWNk8OntLEOQFwEZk9ZgG5VcDquz+pQhIjJPXacR+rwL9Z0Elfg909EcRRHC96p06/CNUg==", + "version": "5.5.29", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.5.29.tgz", + "integrity": "sha512-NHdG3QOiAsxh8ygBSKMa/WaNJwpNt87uVqW+S2RlnSqgeRdk+L3foNWTX6qd0I3NHSlCFb47rgopeNCJtRDY5A==", "dev": true }, "node_modules/@nodelib/fs.scandir": { @@ -296,6 +427,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.1.tgz", @@ -358,14 +499,20 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "dependencies": { "@types/node": "*" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -379,9 +526,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -400,57 +547,57 @@ } }, "node_modules/addons-linter": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-5.23.0.tgz", - "integrity": "sha512-Vo6+5YlM2Ge3yYMY+gNg9Smcfcl1J0ZMfGVXnGJjUwDVHuszHVIvurunQuJURnO4FR1gi4Vy1sWye8ArRL5LOw==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-6.28.0.tgz", + "integrity": "sha512-fCTjXL/yG4hwq74JG8tQdrvEu0OvGrEN9yU+Df0020RDtHl3g/tTCyMeC4G1uyk8IuyMzp4myCBNnOGC7MWSQQ==", "dev": true, "dependencies": { - "@mdn/browser-compat-data": "5.2.17", + "@fluent/syntax": "0.19.0", + "@mdn/browser-compat-data": "5.5.29", "addons-moz-compare": "1.3.0", - "addons-scanner-utils": "8.1.0", - "ajv": "8.11.2", - "ajv-merge-patch": "5.0.1", + "addons-scanner-utils": "9.10.1", + "ajv": "8.13.0", "chalk": "4.1.2", "cheerio": "1.0.0-rc.12", "columnify": "1.6.0", "common-tags": "1.8.2", - "deepmerge": "4.2.2", - "eslint": "8.28.0", - "eslint-plugin-no-unsanitized": "4.0.1", - "eslint-visitor-keys": "3.3.0", - "espree": "9.4.1", + "deepmerge": "4.3.1", + "eslint": "8.57.0", + "eslint-plugin-no-unsanitized": "4.0.2", + "eslint-visitor-keys": "4.0.0", + "espree": "10.0.1", "esprima": "4.0.1", - "fluent-syntax": "0.13.0", - "glob": "8.0.3", - "image-size": "1.0.2", + "fast-json-patch": "3.1.1", + "glob": "10.4.1", + "image-size": "1.1.1", "is-mergeable-object": "1.1.1", "jed": "1.1.1", + "json-merge-patch": "1.0.2", "os-locale": "5.0.0", - "pino": "8.7.0", - "postcss": "8.4.19", + "pino": "8.20.0", "relaxed-json": "1.0.3", - "semver": "7.3.8", + "semver": "7.6.2", "sha.js": "2.4.11", "source-map-support": "0.5.21", "tosource": "1.0.0", "upath": "2.0.1", - "yargs": "17.6.2", + "yargs": "17.7.2", "yauzl": "2.10.0" }, "bin": { "addons-linter": "bin/addons-linter" }, "engines": { - "node": ">=12.21.0" + "node": ">=16.0.0" } }, "node_modules/addons-linter/node_modules/addons-scanner-utils": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/addons-scanner-utils/-/addons-scanner-utils-8.1.0.tgz", - "integrity": "sha512-wxqnmmrzZXjz4B4GZbFcR4HJxTP+pGu+C6iOjMJdmL6mgZZey/UEVJlU6XTxu8GdGv0bUoJxyIioH8XEDxy91Q==", + "version": "9.10.1", + "resolved": "https://registry.npmjs.org/addons-scanner-utils/-/addons-scanner-utils-9.10.1.tgz", + "integrity": "sha512-Tz9OUQx9Ja0TyQ+H2GakB9KlJ50myI6ESBGRlA8N80nHBzMjjPRFGm0APADSaCd5NP74SrFtEvL4TRpDwZXETA==", "dev": true, "dependencies": { - "@types/yauzl": "2.10.0", + "@types/yauzl": "2.10.3", "common-tags": "1.8.2", "first-chunk-stream": "3.0.0", "strip-bom-stream": "4.0.0", @@ -458,9 +605,9 @@ "yauzl": "2.10.0" }, "peerDependencies": { - "body-parser": "1.20.1", - "express": "4.18.2", - "node-fetch": "2.6.7", + "body-parser": "1.20.2", + "express": "4.18.3", + "node-fetch": "2.6.11", "safe-compare": "1.1.4" }, "peerDependenciesMeta": { @@ -487,41 +634,53 @@ "balanced-match": "^1.0.0" } }, + "node_modules/addons-linter/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/addons-linter/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/addons-linter/node_modules/minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/addons-linter/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "optional": true, "peer": true, @@ -540,6 +699,50 @@ } } }, + "node_modules/addons-linter/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/addons-linter/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/addons-linter/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/addons-moz-compare": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/addons-moz-compare/-/addons-moz-compare-1.3.0.tgz", @@ -547,43 +750,30 @@ "dev": true }, "node_modules/adm-zip": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", - "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.15.tgz", + "integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==", "dev": true, "engines": { - "node": ">=6.0" + "node": ">=12.0" } }, "node_modules/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-merge-patch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ajv-merge-patch/-/ajv-merge-patch-5.0.1.tgz", - "integrity": "sha512-0UP3aJCzfzBOkmLR+EinJDCfg6DNtprj3bVPo7JJNgUpZMKt097t9xxQOWFGRoB4JvKKIHE2qe0HkVaS/HyrjQ==", - "dev": true, - "dependencies": { - "fast-json-patch": "^2.0.6", - "json-merge-patch": "^1.0.2" - }, - "peerDependencies": { - "ajv": ">=8.0.0" - } - }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -713,9 +903,9 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", + "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", "dev": true }, "node_modules/balanced-match": { @@ -932,9 +1122,9 @@ } }, "node_modules/camelcase": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.0.tgz", - "integrity": "sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, "engines": { "node": ">=14.16" @@ -1217,9 +1407,9 @@ } }, "node_modules/core-js": { - "version": "3.25.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.3.tgz", - "integrity": "sha512-y1hvKXmPHvm5B7w4ln1S4uc9eV/O5+iFExSRUimnvIph11uaizFR8LFMdONN8hG3P2pipUfX4Y/fR8rAEtcHcQ==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz", + "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==", "dev": true, "hasInstallScript": true, "funding": { @@ -1315,9 +1505,9 @@ } }, "node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, "engines": { "node": ">= 12" @@ -1410,9 +1600,9 @@ } }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1511,14 +1701,14 @@ } }, "node_modules/domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dev": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" @@ -1594,9 +1784,9 @@ } }, "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "engines": { "node": ">=0.12" @@ -1630,9 +1820,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -1663,49 +1853,48 @@ } }, "node_modules/eslint": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz", - "integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1719,18 +1908,18 @@ } }, "node_modules/eslint-plugin-no-unsanitized": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.1.tgz", - "integrity": "sha512-y/lAMWnPPC7RYuUdxlEL/XiCL8FehN9h9s3Kjqbp/Kv0i9NZs+IXSC2kS546Fa4Bumwy31HlVS/OdWX0Kxb5Xg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.2.tgz", + "integrity": "sha512-Pry0S9YmHoz8NCEMRQh7N0Yexh2MYCNPIlrV52hTmS7qXnTghWsjXouF08bgsrrZqaW9tt1ZiK3j5NEmPE+EjQ==", "dev": true, "peerDependencies": { "eslint": "^6 || ^7 || ^8" } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1738,42 +1927,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ajv": { @@ -1792,36 +1960,53 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/eslint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/eslint/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.11.3", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1841,9 +2026,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -1945,21 +2130,9 @@ "dev": true }, "node_modules/fast-json-patch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", - "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^2.0.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fast-json-patch/node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", "dev": true }, "node_modules/fast-json-stable-stringify": { @@ -1975,18 +2148,18 @@ "dev": true }, "node_modules/fast-redact": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", - "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2053,16 +2226,16 @@ } }, "node_modules/firefox-profile": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.2.2.tgz", - "integrity": "sha512-3kI17Xl9dL9AeRkpV1yahsJ+UbekkPtlQswKrIsTY1NLgxtEOR4R19rjGGz5+7/rP8Jt6fvxHk+Bai9R6Eai3w==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.3.2.tgz", + "integrity": "sha512-/C+Eqa0YgIsQT2p66p7Ygzqe7NlE/GNTbhw2SBCm5V3OsWDPITNdTPEcH2Q2fe7eMpYYNPKdUcuVioZBZiR6kA==", "dev": true, "dependencies": { "adm-zip": "~0.5.x", "fs-extra": "~9.0.1", "ini": "~2.0.0", "minimist": "^1.2.5", - "xml2js": "~0.4.23" + "xml2js": "^0.5.0" }, "bin": { "firefox-profile": "lib/cli.js" @@ -2102,12 +2275,13 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -2115,18 +2289,37 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/fluent-syntax": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/fluent-syntax/-/fluent-syntax-0.13.0.tgz", - "integrity": "sha512-0Bk1AsliuYB550zr4JV9AYhsETsD3ELXUQzdXGJfIc1Ni/ukAfBdQInDhVMYJUaT2QxoamNslwkYF7MlOrPUwg==", + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">=8.9.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/forever-agent": { @@ -2174,9 +2367,9 @@ } }, "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -2184,7 +2377,7 @@ "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, "node_modules/fs.realpath": { @@ -2194,9 +2387,9 @@ "dev": true }, "node_modules/fx-runner": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.3.0.tgz", - "integrity": "sha512-5b37H4GCyhF+Nf8xk9mylXoDq4wb7pbGAXxlCXp/631UTeeZomWSYcEGXumY4wk8g2QAqjPMGdWW+RbNt8PUcA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.4.0.tgz", + "integrity": "sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==", "dev": true, "dependencies": { "commander": "2.9.0", @@ -2328,9 +2521,9 @@ } }, "node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2391,10 +2584,10 @@ "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/growly": { @@ -2470,9 +2663,9 @@ } }, "node_modules/htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -2483,15 +2676,15 @@ ], "dependencies": { "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "domutils": "^3.0.1", - "entities": "^4.3.0" + "entities": "^4.4.0" } }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "node_modules/http-signature": { @@ -2552,18 +2745,18 @@ ] }, "node_modules/ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", "dev": true, "dependencies": { "queue": "6.0.2" @@ -2572,7 +2765,7 @@ "image-size": "bin/image-size.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.x" } }, "node_modules/immediate": { @@ -2851,29 +3044,34 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jed": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz", "integrity": "sha512-z35ZSEcXHxLW4yumw0dF6L464NT36vmx3wxJw8MDpraBcWuNVgUPZgPJKcu1HekNgwlMFNqol7i/IpSbjhqwqA==", "dev": true }, - "node_modules/jose": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.1.tgz", - "integrity": "sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "node_modules/jose": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz", + "integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==", "dev": true, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "url": "https://github.com/sponsors/panva" } }, "node_modules/js-tokens": { @@ -2958,34 +3156,19 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dev": true, "dependencies": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsprim": { @@ -3004,15 +3187,15 @@ } }, "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "node_modules/jwa": { @@ -3037,9 +3220,9 @@ } }, "node_modules/keyv": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", - "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -3143,40 +3326,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "node_modules/lodash.merge": { @@ -3185,12 +3338,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -3204,16 +3351,10 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/make-error": { "version": "1.3.6", @@ -3319,6 +3460,15 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -3453,18 +3603,6 @@ "dev": true, "optional": true }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3501,9 +3639,9 @@ } }, "node_modules/node-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", - "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", "dev": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -3596,10 +3734,13 @@ } }, "node_modules/on-exit-leak-free": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", - "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/once": { "version": "1.4.0", @@ -3626,9 +3767,9 @@ } }, "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "dependencies": { "define-lazy-prop": "^2.0.0", @@ -3643,9 +3784,9 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { "deep-is": "^0.1.3", @@ -3653,7 +3794,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -3848,6 +3989,22 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3860,28 +4017,22 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "node_modules/pino": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", - "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", + "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", "dev": true, "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "v1.0.0", + "pino-abstract-transport": "^1.1.0", "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0", + "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.1.0", + "sonic-boom": "^3.7.0", "thread-stream": "^2.0.0" }, "bin": { @@ -3889,9 +4040,9 @@ } }, "node_modules/pino-abstract-transport": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", - "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", "dev": true, "dependencies": { "readable-stream": "^4.0.0", @@ -3899,50 +4050,36 @@ } }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", - "process": "^0.11.10" + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/pino-std-serializers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", - "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "node_modules/pino-abstract-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "safe-buffer": "~5.2.0" } }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3968,9 +4105,9 @@ "dev": true }, "node_modules/process-warning": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.0.0.tgz", - "integrity": "sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", "dev": true }, "node_modules/promise-toolbox": { @@ -4008,9 +4145,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -4153,18 +4290,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/registry-auth-token": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", @@ -4451,9 +4576,9 @@ "optional": true }, "node_modules/safe-stable-stringify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", - "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", "dev": true, "engines": { "node": ">=10" @@ -4466,19 +4591,16 @@ "dev": true }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4501,14 +4623,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true }, "node_modules/sha.js": { "version": "2.4.11", @@ -4557,17 +4676,18 @@ "dev": true }, "node_modules/sign-addon": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sign-addon/-/sign-addon-5.1.0.tgz", - "integrity": "sha512-fag/csbsw25WpW+G+uWE6rRImSjlfwQNjuP28fFhvXpfW+kXccxl/o1QEW+hXtTidwpysksb7Y0B8UCeMkYkSA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/sign-addon/-/sign-addon-5.3.0.tgz", + "integrity": "sha512-7nHlCzhQgVMLBNiXVEgbG/raq48awOW0lYMN5uo1BaB3mp0+k8M8pvDwbfTlr3apcxZJsk9HQsAW1POwoJugpQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "dependencies": { "common-tags": "1.8.2", - "core-js": "3.25.3", + "core-js": "3.29.0", "deepcopy": "2.1.0", "es6-error": "4.1.1", "es6-promisify": "7.0.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "9.0.0", "mz": "2.7.0", "request": "2.88.2", "source-map-support": "0.5.21", @@ -4581,9 +4701,9 @@ "dev": true }, "node_modules/sonic-boom": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz", - "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "dependencies": { "atomic-sleep": "^1.0.0" @@ -4598,15 +4718,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -4641,18 +4752,18 @@ } }, "node_modules/split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "engines": { "node": ">= 10.x" } }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -4729,6 +4840,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -4768,6 +4900,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz", @@ -4866,9 +5011,9 @@ } }, "node_modules/thread-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.2.0.tgz", - "integrity": "sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", "dev": true, "dependencies": { "real-require": "^0.2.0" @@ -4953,9 +5098,9 @@ } }, "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "engines": { "node": ">=4" @@ -5004,9 +5149,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -5129,42 +5274,42 @@ } }, "node_modules/web-ext": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-7.4.0.tgz", - "integrity": "sha512-dT2HJaGNXxRNuOtzaVBtEULccL0kM2SN1ark1NnN/ZSlbucobBxCDj6119iki72YyuXpaXZCJGqfZtVf1Znocg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-7.12.0.tgz", + "integrity": "sha512-h+uWOYBlHlPKy5CqxuZKocgOdL8J7I4ctMw/rAGbQl7jq7tr+NmY/Lhh2FPMSlJ1Y0T2VeUqwBVighK0MM1+zA==", "dev": true, "dependencies": { - "@babel/runtime": "7.20.1", + "@babel/runtime": "7.21.0", "@devicefarmer/adbkit": "3.2.3", - "addons-linter": "5.23.0", + "addons-linter": "6.28.0", "bunyan": "1.8.15", - "camelcase": "7.0.0", + "camelcase": "7.0.1", "chrome-launcher": "0.15.1", "debounce": "1.2.1", "decamelize": "6.0.0", "es6-error": "4.1.1", - "firefox-profile": "4.2.2", - "fs-extra": "10.1.0", - "fx-runner": "1.3.0", + "firefox-profile": "4.3.2", + "fs-extra": "11.1.0", + "fx-runner": "1.4.0", "import-fresh": "3.3.0", - "jose": "4.11.1", + "jose": "4.13.1", "mkdirp": "1.0.4", "multimatch": "6.0.0", "mz": "2.7.0", - "node-fetch": "3.3.0", + "node-fetch": "3.3.1", "node-notifier": "10.0.1", - "open": "8.4.0", + "open": "8.4.2", "parse-json": "6.0.2", "promise-toolbox": "0.21.0", - "sign-addon": "5.1.0", + "sign-addon": "5.3.0", "source-map-support": "0.5.21", "strip-bom": "5.0.0", "strip-json-comments": "5.0.0", "tmp": "0.2.1", "update-notifier": "6.0.2", "watchpack": "2.4.0", - "ws": "8.11.0", - "yargs": "17.6.2", + "ws": "8.13.0", + "yargs": "17.7.1", "zip-dir": "2.0.0" }, "bin": { @@ -5176,9 +5321,9 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, "engines": { "node": ">= 8" @@ -5247,18 +5392,18 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", - "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { "ansi-styles": "^6.1.0", @@ -5272,6 +5417,44 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -5330,16 +5513,16 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -5363,9 +5546,9 @@ } }, "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dev": true, "dependencies": { "sax": ">=0.6.0", @@ -5393,16 +5576,10 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -5571,12 +5748,12 @@ } }, "@babel/runtime": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", - "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.10" + "regenerator-runtime": "^0.13.11" } }, "@devicefarmer/adbkit": { @@ -5606,16 +5783,39 @@ "integrity": "sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==", "dev": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -5635,6 +5835,23 @@ "uri-js": "^4.2.2" } }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5649,14 +5866,26 @@ } } }, + "@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true + }, + "@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true + }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" } }, @@ -5667,15 +5896,46 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "@mdn/browser-compat-data": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.17.tgz", - "integrity": "sha512-aA+rFHhXmq14GVIcEWNk8OntLEOQFwEZk9ZgG5VcDquz+pQhIjJPXacR+rwL9Z0Elfg909EcRRHC96p06/CNUg==", + "version": "5.5.29", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.5.29.tgz", + "integrity": "sha512-NHdG3QOiAsxh8ygBSKMa/WaNJwpNt87uVqW+S2RlnSqgeRdk+L3foNWTX6qd0I3NHSlCFb47rgopeNCJtRDY5A==", "dev": true }, "@nodelib/fs.scandir": { @@ -5704,6 +5964,13 @@ "fastq": "^1.6.0" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@pnpm/network.ca-file": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.1.tgz", @@ -5751,14 +6018,20 @@ "dev": true }, "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "requires": { "@types/node": "*" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -5769,9 +6042,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true }, "acorn-jsx": { @@ -5782,51 +6055,51 @@ "requires": {} }, "addons-linter": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-5.23.0.tgz", - "integrity": "sha512-Vo6+5YlM2Ge3yYMY+gNg9Smcfcl1J0ZMfGVXnGJjUwDVHuszHVIvurunQuJURnO4FR1gi4Vy1sWye8ArRL5LOw==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-6.28.0.tgz", + "integrity": "sha512-fCTjXL/yG4hwq74JG8tQdrvEu0OvGrEN9yU+Df0020RDtHl3g/tTCyMeC4G1uyk8IuyMzp4myCBNnOGC7MWSQQ==", "dev": true, "requires": { - "@mdn/browser-compat-data": "5.2.17", + "@fluent/syntax": "0.19.0", + "@mdn/browser-compat-data": "5.5.29", "addons-moz-compare": "1.3.0", - "addons-scanner-utils": "8.1.0", - "ajv": "8.11.2", - "ajv-merge-patch": "5.0.1", + "addons-scanner-utils": "9.10.1", + "ajv": "8.13.0", "chalk": "4.1.2", "cheerio": "1.0.0-rc.12", "columnify": "1.6.0", "common-tags": "1.8.2", - "deepmerge": "4.2.2", - "eslint": "8.28.0", - "eslint-plugin-no-unsanitized": "4.0.1", - "eslint-visitor-keys": "3.3.0", - "espree": "9.4.1", + "deepmerge": "4.3.1", + "eslint": "8.57.0", + "eslint-plugin-no-unsanitized": "4.0.2", + "eslint-visitor-keys": "4.0.0", + "espree": "10.0.1", "esprima": "4.0.1", - "fluent-syntax": "0.13.0", - "glob": "8.0.3", - "image-size": "1.0.2", + "fast-json-patch": "3.1.1", + "glob": "10.4.1", + "image-size": "1.1.1", "is-mergeable-object": "1.1.1", "jed": "1.1.1", + "json-merge-patch": "1.0.2", "os-locale": "5.0.0", - "pino": "8.7.0", - "postcss": "8.4.19", + "pino": "8.20.0", "relaxed-json": "1.0.3", - "semver": "7.3.8", + "semver": "7.6.2", "sha.js": "2.4.11", "source-map-support": "0.5.21", "tosource": "1.0.0", "upath": "2.0.1", - "yargs": "17.6.2", + "yargs": "17.7.2", "yauzl": "2.10.0" }, "dependencies": { "addons-scanner-utils": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/addons-scanner-utils/-/addons-scanner-utils-8.1.0.tgz", - "integrity": "sha512-wxqnmmrzZXjz4B4GZbFcR4HJxTP+pGu+C6iOjMJdmL6mgZZey/UEVJlU6XTxu8GdGv0bUoJxyIioH8XEDxy91Q==", + "version": "9.10.1", + "resolved": "https://registry.npmjs.org/addons-scanner-utils/-/addons-scanner-utils-9.10.1.tgz", + "integrity": "sha512-Tz9OUQx9Ja0TyQ+H2GakB9KlJ50myI6ESBGRlA8N80nHBzMjjPRFGm0APADSaCd5NP74SrFtEvL4TRpDwZXETA==", "dev": true, "requires": { - "@types/yauzl": "2.10.0", + "@types/yauzl": "2.10.3", "common-tags": "1.8.2", "first-chunk-stream": "3.0.0", "strip-bom-stream": "4.0.0", @@ -5843,38 +6116,76 @@ "balanced-match": "^1.0.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" } }, "minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "optional": true, "peer": true, "requires": { "whatwg-url": "^5.0.0" } + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } } } }, @@ -5885,31 +6196,21 @@ "dev": true }, "adm-zip": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", - "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.15.tgz", + "integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==", "dev": true }, "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-merge-patch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ajv-merge-patch/-/ajv-merge-patch-5.0.1.tgz", - "integrity": "sha512-0UP3aJCzfzBOkmLR+EinJDCfg6DNtprj3bVPo7JJNgUpZMKt097t9xxQOWFGRoB4JvKKIHE2qe0HkVaS/HyrjQ==", - "dev": true, - "requires": { - "fast-json-patch": "^2.0.6", - "json-merge-patch": "^1.0.2" + "uri-js": "^4.4.1" } }, "ansi-align": { @@ -6013,9 +6314,9 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", + "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", "dev": true }, "balanced-match": { @@ -6166,9 +6467,9 @@ "dev": true }, "camelcase": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.0.tgz", - "integrity": "sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true }, "caseless": { @@ -6383,9 +6684,9 @@ } }, "core-js": { - "version": "3.25.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.3.tgz", - "integrity": "sha512-y1hvKXmPHvm5B7w4ln1S4uc9eV/O5+iFExSRUimnvIph11uaizFR8LFMdONN8hG3P2pipUfX4Y/fR8rAEtcHcQ==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.0.tgz", + "integrity": "sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==", "dev": true }, "core-util-is": { @@ -6451,9 +6752,9 @@ } }, "data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true }, "debounce": { @@ -6516,9 +6817,9 @@ } }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, "defaults": { @@ -6584,14 +6885,14 @@ } }, "domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dev": true, "requires": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "domhandler": "^5.0.3" } }, "dot-prop": { @@ -6654,9 +6955,9 @@ } }, "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, "error-ex": { @@ -6681,9 +6982,9 @@ "dev": true }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "escape-goat": { @@ -6699,49 +7000,48 @@ "dev": true }, "eslint": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz", - "integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", - "grapheme-splitter": "^1.0.4", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { @@ -6757,69 +7057,63 @@ "uri-js": "^4.2.2" } }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true } } }, "eslint-plugin-no-unsanitized": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.1.tgz", - "integrity": "sha512-y/lAMWnPPC7RYuUdxlEL/XiCL8FehN9h9s3Kjqbp/Kv0i9NZs+IXSC2kS546Fa4Bumwy31HlVS/OdWX0Kxb5Xg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.2.tgz", + "integrity": "sha512-Pry0S9YmHoz8NCEMRQh7N0Yexh2MYCNPIlrV52hTmS7qXnTghWsjXouF08bgsrrZqaW9tt1ZiK3j5NEmPE+EjQ==", "dev": true, "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true }, "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.11.3", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^4.0.0" } }, "esprima": { @@ -6829,9 +7123,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -6906,21 +7200,10 @@ "dev": true }, "fast-json-patch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", - "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - }, - "dependencies": { - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", - "dev": true - } - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -6935,15 +7218,15 @@ "dev": true }, "fast-redact": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", - "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -6988,16 +7271,16 @@ } }, "firefox-profile": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.2.2.tgz", - "integrity": "sha512-3kI17Xl9dL9AeRkpV1yahsJ+UbekkPtlQswKrIsTY1NLgxtEOR4R19rjGGz5+7/rP8Jt6fvxHk+Bai9R6Eai3w==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.3.2.tgz", + "integrity": "sha512-/C+Eqa0YgIsQT2p66p7Ygzqe7NlE/GNTbhw2SBCm5V3OsWDPITNdTPEcH2Q2fe7eMpYYNPKdUcuVioZBZiR6kA==", "dev": true, "requires": { "adm-zip": "~0.5.x", "fs-extra": "~9.0.1", "ini": "~2.0.0", "minimist": "^1.2.5", - "xml2js": "~0.4.23" + "xml2js": "^0.5.0" }, "dependencies": { "fs-extra": { @@ -7027,26 +7310,39 @@ "dev": true }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "fluent-syntax": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/fluent-syntax/-/fluent-syntax-0.13.0.tgz", - "integrity": "sha512-0Bk1AsliuYB550zr4JV9AYhsETsD3ELXUQzdXGJfIc1Ni/ukAfBdQInDhVMYJUaT2QxoamNslwkYF7MlOrPUwg==", - "dev": true + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } }, "forever-agent": { "version": "0.6.1", @@ -7081,9 +7377,9 @@ } }, "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -7098,9 +7394,9 @@ "dev": true }, "fx-runner": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.3.0.tgz", - "integrity": "sha512-5b37H4GCyhF+Nf8xk9mylXoDq4wb7pbGAXxlCXp/631UTeeZomWSYcEGXumY4wk8g2QAqjPMGdWW+RbNt8PUcA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.4.0.tgz", + "integrity": "sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==", "dev": true, "requires": { "commander": "2.9.0", @@ -7201,9 +7497,9 @@ } }, "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -7248,10 +7544,10 @@ "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "growly": { @@ -7309,21 +7605,21 @@ "dev": true }, "htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, "requires": { "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "domutils": "^3.0.1", - "entities": "^4.3.0" + "entities": "^4.4.0" } }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-signature": { @@ -7360,15 +7656,15 @@ "dev": true }, "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "image-size": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", - "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", "dev": true, "requires": { "queue": "6.0.2" @@ -7572,6 +7868,16 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jed": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz", @@ -7579,15 +7885,9 @@ "dev": true }, "jose": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.1.tgz", - "integrity": "sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==", - "dev": true - }, - "js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz", + "integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==", "dev": true }, "js-tokens": { @@ -7667,29 +7967,15 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dev": true, "requires": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "semver": "^7.3.8" } }, "jsprim": { @@ -7705,15 +7991,15 @@ } }, "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "jwa": { @@ -7738,9 +8024,9 @@ } }, "keyv": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", - "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -7825,40 +8111,10 @@ "p-locate": "^5.0.0" } }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.merge": { @@ -7867,12 +8123,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -7880,13 +8130,10 @@ "dev": true }, "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "make-error": { "version": "1.3.6", @@ -7968,6 +8215,12 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -8073,12 +8326,6 @@ "dev": true, "optional": true }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8099,9 +8346,9 @@ "dev": true }, "node-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz", - "integrity": "sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", "dev": true, "requires": { "data-uri-to-buffer": "^4.0.0", @@ -8166,9 +8413,9 @@ "dev": true }, "on-exit-leak-free": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", - "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", "dev": true }, "once": { @@ -8190,9 +8437,9 @@ } }, "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "requires": { "define-lazy-prop": "^2.0.0", @@ -8201,9 +8448,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -8211,7 +8458,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "os-locale": { @@ -8343,6 +8590,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -8355,35 +8612,29 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "pino": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", - "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz", + "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==", "dev": true, "requires": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "v1.0.0", + "pino-abstract-transport": "^1.1.0", "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0", + "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.1.0", + "sonic-boom": "^3.7.0", "thread-stream": "^2.0.0" } }, "pino-abstract-transport": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", - "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", "dev": true, "requires": { "readable-stream": "^4.0.0", @@ -8391,36 +8642,35 @@ }, "dependencies": { "readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", - "process": "^0.11.10" + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" } } } }, "pino-std-serializers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", - "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", "dev": true }, - "postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8440,9 +8690,9 @@ "dev": true }, "process-warning": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.0.0.tgz", - "integrity": "sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", "dev": true }, "promise-toolbox": { @@ -8477,9 +8727,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "pupa": { @@ -8585,12 +8835,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "registry-auth-token": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", @@ -8790,9 +9034,9 @@ "optional": true }, "safe-stable-stringify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", - "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", "dev": true }, "safer-buffer": { @@ -8802,19 +9046,16 @@ "dev": true }, "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true }, "semver-diff": { "version": "4.0.0", @@ -8825,10 +9066,10 @@ "semver": "^7.3.5" } }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true }, "sha.js": { @@ -8869,17 +9110,17 @@ "dev": true }, "sign-addon": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sign-addon/-/sign-addon-5.1.0.tgz", - "integrity": "sha512-fag/csbsw25WpW+G+uWE6rRImSjlfwQNjuP28fFhvXpfW+kXccxl/o1QEW+hXtTidwpysksb7Y0B8UCeMkYkSA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/sign-addon/-/sign-addon-5.3.0.tgz", + "integrity": "sha512-7nHlCzhQgVMLBNiXVEgbG/raq48awOW0lYMN5uo1BaB3mp0+k8M8pvDwbfTlr3apcxZJsk9HQsAW1POwoJugpQ==", "dev": true, "requires": { "common-tags": "1.8.2", - "core-js": "3.25.3", + "core-js": "3.29.0", "deepcopy": "2.1.0", "es6-error": "4.1.1", "es6-promisify": "7.0.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "9.0.0", "mz": "2.7.0", "request": "2.88.2", "source-map-support": "0.5.21", @@ -8893,9 +9134,9 @@ "dev": true }, "sonic-boom": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz", - "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", "dev": true, "requires": { "atomic-sleep": "^1.0.0" @@ -8907,12 +9148,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -8943,15 +9178,15 @@ } }, "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -9030,6 +9265,25 @@ } } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -9039,6 +9293,15 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz", @@ -9110,9 +9373,9 @@ } }, "thread-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.2.0.tgz", - "integrity": "sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", "dev": true, "requires": { "real-require": "^0.2.0" @@ -9182,9 +9445,9 @@ } }, "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true }, "type-fest": { @@ -9218,9 +9481,9 @@ } }, "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true }, "upath": { @@ -9319,49 +9582,49 @@ } }, "web-ext": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-7.4.0.tgz", - "integrity": "sha512-dT2HJaGNXxRNuOtzaVBtEULccL0kM2SN1ark1NnN/ZSlbucobBxCDj6119iki72YyuXpaXZCJGqfZtVf1Znocg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-7.12.0.tgz", + "integrity": "sha512-h+uWOYBlHlPKy5CqxuZKocgOdL8J7I4ctMw/rAGbQl7jq7tr+NmY/Lhh2FPMSlJ1Y0T2VeUqwBVighK0MM1+zA==", "dev": true, "requires": { - "@babel/runtime": "7.20.1", + "@babel/runtime": "7.21.0", "@devicefarmer/adbkit": "3.2.3", - "addons-linter": "5.23.0", + "addons-linter": "6.28.0", "bunyan": "1.8.15", - "camelcase": "7.0.0", + "camelcase": "7.0.1", "chrome-launcher": "0.15.1", "debounce": "1.2.1", "decamelize": "6.0.0", "es6-error": "4.1.1", - "firefox-profile": "4.2.2", - "fs-extra": "10.1.0", - "fx-runner": "1.3.0", + "firefox-profile": "4.3.2", + "fs-extra": "11.1.0", + "fx-runner": "1.4.0", "import-fresh": "3.3.0", - "jose": "4.11.1", + "jose": "4.13.1", "mkdirp": "1.0.4", "multimatch": "6.0.0", "mz": "2.7.0", - "node-fetch": "3.3.0", + "node-fetch": "3.3.1", "node-notifier": "10.0.1", - "open": "8.4.0", + "open": "8.4.2", "parse-json": "6.0.2", "promise-toolbox": "0.21.0", - "sign-addon": "5.1.0", + "sign-addon": "5.3.0", "source-map-support": "0.5.21", "strip-bom": "5.0.0", "strip-json-comments": "5.0.0", "tmp": "0.2.1", "update-notifier": "6.0.2", "watchpack": "2.4.0", - "ws": "8.11.0", - "yargs": "17.6.2", + "ws": "8.13.0", + "yargs": "17.7.1", "zip-dir": "2.0.0" } }, "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true }, "webidl-conversions": { @@ -9415,15 +9678,15 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.0.1.tgz", - "integrity": "sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "requires": { "ansi-styles": "^6.1.0", @@ -9454,6 +9717,36 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -9473,9 +9766,9 @@ } }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "requires": {} }, @@ -9486,9 +9779,9 @@ "dev": true }, "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dev": true, "requires": { "sax": ">=0.6.0", @@ -9507,16 +9800,10 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "requires": { "cliui": "^8.0.1", From acaf7293b15d36d118f9b153be4f49faba2aa227 Mon Sep 17 00:00:00 2001 From: Rick C <4494839+rickdoesdev@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:53:33 +1000 Subject: [PATCH 18/29] fix #340 clean extra whitespace in generateValidFileName --- src/background/background.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/background/background.js b/src/background/background.js index 5b5445f5..7951a13f 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -352,6 +352,10 @@ function generateValidFileName(title, disallowedChars = null) { // and non-breaking spaces (thanks @Licat) var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' '); + var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' ') + // remove leading/trailing whitespace that can cause issues when using {pageTitle} in a download path + .trim(); + if (disallowedChars) { for (let c of disallowedChars) { if (`[\\^$.|?*+()`.includes(c)) c = `\\${c}`; From 71fb3b81b63f98d4341c0aa9a997bf12b778a956 Mon Sep 17 00:00:00 2001 From: Rick C <4494839+rickdoesdev@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:54:00 +1000 Subject: [PATCH 19/29] fix erroneous assignment to const --- src/background/background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/background/background.js b/src/background/background.js index 7951a13f..1fa311c5 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -350,9 +350,9 @@ function generateValidFileName(title, disallowedChars = null) { // remove < > : " / \ | ? * var illegalRe = /[\/\?<>\\:\*\|":]/g; // and non-breaking spaces (thanks @Licat) - var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' '); - var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' ') + // collapse extra whitespace + .replace(new RegExp(/\s+/, 'g'), ' ') // remove leading/trailing whitespace that can cause issues when using {pageTitle} in a download path .trim(); @@ -457,7 +457,7 @@ async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsF // download images (if enabled) if (options.downloadImages) { // get the relative path of the markdown file (if any) for image path - const destPath = mdClipsFolder + title.substring(0, title.lastIndexOf('/')); + let destPath = mdClipsFolder + title.substring(0, title.lastIndexOf('/')); if(destPath && !destPath.endsWith('/')) destPath += '/'; Object.entries(imageList).forEach(async ([src, filename]) => { // start the download of the image From 7e8cc1a2156a5b413db133677641c3f8a23cd39d Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Fri, 23 Aug 2024 11:26:11 +1000 Subject: [PATCH 20/29] Updating version and readme for 3.4.0 --- CHANGELOG.md | 20 +++++++--- README.md | 38 ++++++++----------- src/manifest.json | 2 +- .../project.pbxproj | 8 ++-- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aecb4fb..a901b0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,18 @@ # Changelog -## 3.3.1 -- Added additional template casing variables that retain original letter casing. For example, for an article titled "Different Types of Casing - Source":: - - `{pageTitle:mixed-kebab}` — "Different-Types-of-Casing---Source" - - `{pageTitle:mixed_snake}` — "Different_Types_of_Casing_-_Source" - - `{pageTitle:obsidian-cal}` — "Different-Types-of-Casing-Source" This option is for additional compatibility with the Obsidian Consistent Attachment Location plugin. - +## 3.4.0 +- Fixed extra spaces in titles which could cause issues (thanks @rickdoesdev !) +- Fixed an issue with image paths in some circumstances (thanks @rickdoesdev !) +- Added parametersizations for "mixed-kebab" and "mixed_snake" which retain original casing but replace spaces (thanks @NSHenry !) + - Also added a special "obsidian-cal" parameterization which is the same as "mixed-kebab" with duplicate `-` removed for additional compatibility with the Obsidian Consistent Attachment Location plugin (thanks @NSHenry !) +- Added lowecase and uppercase options to parameterizations (thanks @redxtech !) +- Updated Turndown to v7.1.3 (thanks @WeHat !) +- Updated Readability to v0.5.0 (thanks @WeHat !) +- Fixed some issues with code block parsing and formatting (thanks @WeHat !) +- Fixed an issue with some sites missing a proper title (thanks @WeHat !) +- Fixed an issue with bad base urls causing issues with links in certain circumstances (thanks @WeHat !) +- Fixed an issue with readability removing everything in certain circumstances (thanks @WeHat !) +- Send properly configured title to the Obsidian integration (thanks @nekton39 !) +- Updates to the README (thanks @2gn and @eugenesvk !) ## 3.3.0 - Remove hidden content before exporting (thanks @nhaouari !). This allows you to use a different extension (e.g. Adblock) to hide elements that would otherwise clutter up your export - Fixes for Obsidian integration in Safari (thanks @aancw !) diff --git a/README.md b/README.md index ff410a74..840a361e 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ You need to do some configurations in order to use this integration. # External Libraries It uses the following libraries: -- [Readability.js](https://github.com/mozilla/readability) by Mozilla in version from commit [1fde3ac626bc4c2e5e54daa57c57d48b7ed9c574](https://github.com/mozilla/readability/commit/1fde3ac626bc4c2e5e54daa57c57d48b7ed9c574). This library is also used for the Firefox Reader View and it simplifies the page so that only the important parts are clipped. (Licensed under Apache License Version 2.0) -- [Turndown](https://github.com/mixmark-io/turndown) by Dom Christie in version 7.1.1 is used to convert the simplified HTML (from Readability.js) into markdown. (Licensed under MIT License) +- [Readability.js](https://github.com/mozilla/readability) by Mozilla version [0.5.0](https://github.com/mozilla/readability/releases/tag/0.5.0). This library is also used for the Firefox Reader View and it simplifies the page so that only the important parts are clipped. (Licensed under Apache License Version 2.0) +- [Turndown](https://github.com/mixmark-io/turndown) by Dom Christie in version [7.1.3](https://github.com/mixmark-io/turndown/releases/tag/v7.1.3) is used to convert the simplified HTML (from Readability.js) into markdown. (Licensed under MIT License) - [Moment.js](https://momentjs.com) version 2.29.4 used to format dates in template variables # Permissions @@ -71,25 +71,19 @@ in my hat via the following: [![Paypal](https://img.shields.io/badge/paypal-deathau-yellow?style=social&logo=paypal)](https://paypal.me/deathau) # Version History -## 3.3.0 -- Remove hidden content before exporting (thanks @nhaouari !). This allows you to use a different extension (e.g. Adblock) to hide elements that would otherwise clutter up your export -- Fixes for Obsidian integration in Safari (thanks @aancw !) -- Keep a few more HTML tags that have no markdown equivalent (`u`, `ins`, `del`, `small`, `big`) (thanks @mnaoumov !) -- Add support for KaTeX formulas parsing (thanks @mnaoumov !) -- Fixed saving for options when imported from file (and show a little 'saved' indicator) -- Added a toggle for downloading images in the context menu and popup -- Added a link to the options in the popup -- Added some basic error handling to the popup -- Changes to how html inside code blocks is handled (thanks @mnaumov !) -- Treat codehilite without specified language as plaintext (thanks @mnaoumov !) -- Ensure sequential line breaks in `
` are preserved in code blocks (thanks @mnaumov !)
-- Update user guide link in README to point to GitHub
-- Added keyboard shortcuts to copy selection / current tab to obsidian (user-definable in browsers that support that) (thanks @legolasdimir and @likeablob !)
-- Select multiple tabs (hold crtl/cmd) then copy all tab urls as a markdown link list via keyboard shortcut or context menu (thanks @romanPrignon !)
-- Allow users to include custom text such like `{date:YYYY-MM-DD}/`` in their Obsidian Folder Name setting (thanks @likeablob !)
-- Fixed a small typo in the user guide (thanks @devon-research !)
-- Fix for missing headings on sites like Substack (thanks @eactisgrosso !)
-- Add support for websites using MathJax 3 (thanks @LeLocTai !)
-- Moved previous version history into [CHANGELOG.md](./CHANGELOG.md)
+## 3.4.0
+- Fixed extra spaces in titles which could cause issues (thanks @rickdoesdev !)
+- Fixed an issue with image paths in some circumstances (thanks @rickdoesdev !)
+- Added parametersizations for "mixed-kebab" and "mixed_snake" which retain original casing but replace spaces (thanks @NSHenry !)
+  - Also added a special "obsidian-cal" parameterization which is the same as "mixed-kebab" with duplicate `-` removed for additional compatibility with the Obsidian Consistent Attachment Location plugin (thanks @NSHenry !)
+- Added lowecase and uppercase options to parameterizations (thanks @redxtech !)
+- Updated Turndown to v7.1.3 (thanks @WeHat !)
+- Updated Readability to v0.5.0 (thanks @WeHat !)
+- Fixed some issues with code block parsing and formatting (thanks @WeHat !)
+- Fixed an issue with some sites missing a proper title (thanks @WeHat !)
+- Fixed an issue with bad base urls causing issues with links in certain circumstances (thanks @WeHat !)
+- Fixed an issue with readability removing everything in certain circumstances (thanks @WeHat !)
+- Send properly configured title to the Obsidian integration (thanks @nekton39 !)
+- Updates to the README (thanks @2gn and @eugenesvk !)
 
 > Previous version history is recorded in [CHANGELOG.md](./CHANGELOG.md)
diff --git a/src/manifest.json b/src/manifest.json
index 31b63706..19a581cc 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -1,7 +1,7 @@
 {
   "manifest_version": 2,
   "name": "MarkDownload - Markdown Web Clipper",
-  "version": "3.3.1",
+  "version": "3.4.0",
   "author": "Gordon Pedsersen",
   "description": "This extension works like a web clipper, but it downloads articles in markdown format.",
   "icons": {
diff --git a/xcode/MarkDownload - Markdown Web Clipper/MarkDownload - Markdown Web Clipper.xcodeproj/project.pbxproj b/xcode/MarkDownload - Markdown Web Clipper/MarkDownload - Markdown Web Clipper.xcodeproj/project.pbxproj
index 6dbeea6c..b6ee2564 100644
--- a/xcode/MarkDownload - Markdown Web Clipper/MarkDownload - Markdown Web Clipper.xcodeproj/project.pbxproj	
+++ b/xcode/MarkDownload - Markdown Web Clipper/MarkDownload - Markdown Web Clipper.xcodeproj/project.pbxproj	
@@ -467,7 +467,7 @@
 				CODE_SIGN_ENTITLEMENTS = "MarkDownload - Markdown Web Clipper/MarkDownload___Markdown_Web_Clipper.entitlements";
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				CURRENT_PROJECT_VERSION = 4;
+				CURRENT_PROJECT_VERSION = 4.0.1;
 				DEVELOPMENT_TEAM = 4JYKNQ5WRZ;
 				INFOPLIST_FILE = "MarkDownload - Markdown Web Clipper/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -475,7 +475,7 @@
 					"@executable_path/../Frameworks",
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.14;
-				MARKETING_VERSION = 3.2.0;
+				MARKETING_VERSION = 3.4.0;
 				PRODUCT_BUNDLE_IDENTIFIER = au.death.MarkDownload;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;
@@ -491,7 +491,7 @@
 				CODE_SIGN_ENTITLEMENTS = "MarkDownload - Markdown Web Clipper/MarkDownload___Markdown_Web_Clipper.entitlements";
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				CURRENT_PROJECT_VERSION = 4;
+				CURRENT_PROJECT_VERSION = 4.0.1;
 				DEVELOPMENT_TEAM = 4JYKNQ5WRZ;
 				INFOPLIST_FILE = "MarkDownload - Markdown Web Clipper/Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -499,7 +499,7 @@
 					"@executable_path/../Frameworks",
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.14;
-				MARKETING_VERSION = 3.2.0;
+				MARKETING_VERSION = 3.4.0;
 				PRODUCT_BUNDLE_IDENTIFIER = au.death.MarkDownload;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_VERSION = 5.0;

From 1adab2a89de2e3011b209b08b04ba3860baceccc Mon Sep 17 00:00:00 2001
From: TianleLin <940428567qq@gmail.com>
Date: Wed, 10 Sep 2025 15:31:52 +0800
Subject: [PATCH 21/29] fix v3

---
 UPGRADE_NOTES.md                   | 101 +++++
 src/background/service-worker.js   | 668 +++++++++++++++++++++++++++++
 src/contentScript/contentScript.js | 497 ++++++++++++++++++++-
 src/contentScript/pageContext.js   |  26 +-
 src/manifest.json                  |  51 ++-
 5 files changed, 1311 insertions(+), 32 deletions(-)
 create mode 100644 UPGRADE_NOTES.md
 create mode 100644 src/background/service-worker.js

diff --git a/UPGRADE_NOTES.md b/UPGRADE_NOTES.md
new file mode 100644
index 00000000..da611208
--- /dev/null
+++ b/UPGRADE_NOTES.md
@@ -0,0 +1,101 @@
+# MarkDownload Manifest V3 升级说明
+
+## 已完成的修改
+
+### 1. Manifest.json 升级
+- 从 Manifest V2 升级到 Manifest V3
+- 更新权限:将 `` 移动到 `host_permissions`
+- 将 `browser_action` 更改为 `action`
+- 将 `background.scripts` 更改为 `background.service_worker`
+- 添加了 `scripting` 权限以支持动态脚本注入
+- 添加了 `content_scripts` 声明以自动注入内容脚本
+- **修复**: 移除了 `options_ui` 中不兼容的 `chrome_style` 和 `browser_style` 选项
+- **修复**: 将命令 `_execute_browser_action` 改为 `_execute_action`
+
+### 2. Background Script 转换为 Service Worker
+- 创建了新的 `src/background/service-worker.js` 文件
+- 将所有依赖脚本使用 `importScripts()` 导入
+- 更新了所有 Chrome API 调用以适配 Manifest V3
+- 使用 `chrome.scripting.executeScript()` 替代 `chrome.tabs.executeScript()`
+- 添加了详细的调试日志用于问题排查
+
+### 3. 内容脚本改进
+- 添加了详细的调试日志
+- 改进了DOM处理和错误处理
+- 增强了剪贴板操作的兼容性
+- 添加了更好的浏览器API兼容性检查
+
+### 4. 调试功能
+- 在所有关键函数中添加了console.log输出
+- 添加了错误处理和回退机制
+- 提供了详细的操作状态信息
+
+## 测试建议
+
+### 1. 加载扩展
+1. 打开 Chrome 浏览器
+2. 导航到 `chrome://extensions/`
+3. 启用"开发者模式"
+4. 点击"加载已解压的扩展程序"
+5. 选择 `src` 目录
+
+### 2. 测试功能
+1. **基本测试**:
+   - 右键点击页面,检查是否出现 MarkDownload 上下文菜单
+   - 点击扩展图标,检查弹出窗口是否正常显示
+
+2. **核心功能测试**:
+   - 测试"Download Tab As Markdown"
+   - 测试"Copy Tab As Markdown"
+   - 测试选中文本后的"Copy Selection As Markdown"
+
+3. **调试检查**:
+   - 打开 Chrome 开发者工具
+   - 检查 Console 标签页中的 MarkDownload 日志输出
+   - Service Worker 日志可在扩展管理页面的"Service Worker"链接中查看
+
+### 3. 常见问题排查
+
+#### 如果扩展无法加载:
+1. 检查 manifest.json 语法是否正确
+2. 确保所有引用的文件都存在
+3. 查看扩展管理页面的错误信息
+
+**已修复的常见错误:**
+- ✅ "The chrome_style option cannot be used with manifest version 3" - 已从 `options_ui` 中移除 `chrome_style` 和 `browser_style`
+- ✅ "_execute_browser_action command not recognized" - 已更改为 `_execute_action`
+
+#### 如果功能不工作:
+1. 检查 Service Worker 是否运行(在扩展管理页面)
+2. 查看浏览器控制台的错误信息
+3. 检查 MarkDownload 的调试日志
+
+#### 如果权限问题:
+1. 确保网站允许执行脚本
+2. 检查是否在受限页面(如 chrome://)上使用
+3. 验证 host_permissions 是否正确设置
+
+## 技术细节
+
+### Service Worker 变化
+- 不再是持久的背景页面,而是事件驱动的
+- 需要使用 `importScripts()` 导入依赖
+- API 调用从 `browser.*` 改为 `chrome.*`
+
+### 内容脚本注入
+- 从动态注入改为声明式注入
+- 使用 `chrome.scripting.executeScript()` 进行必要时的动态注入
+- 改进了脚本可用性检查
+
+### 权限模型
+- `` 权限移至 `host_permissions`
+- 添加了 `scripting` 权限支持动态脚本执行
+
+## 已知限制
+- 在某些受保护的页面(如 chrome:// 页面)上无法工作
+- Service Worker 在空闲时会被暂停,但会在需要时自动恢复
+
+## 版本兼容性
+- 支持 Chrome 88+ (Manifest V3 最低要求)
+- 向后兼容原有功能
+- 保持与 Firefox 的兼容性(通过 browser-polyfill)
diff --git a/src/background/service-worker.js b/src/background/service-worker.js
new file mode 100644
index 00000000..88ff5a6e
--- /dev/null
+++ b/src/background/service-worker.js
@@ -0,0 +1,668 @@
+// MarkDownload Service Worker for Manifest V3
+// Debug logging enabled
+console.log('MarkDownload Service Worker starting...');
+
+// Import all required scripts for service worker
+try {
+  importScripts(
+    '../browser-polyfill.min.js',
+    'apache-mime-types.js',
+    'moment.min.js', 
+    'turndown.js',
+    'turndown-plugin-gfm.js',
+    'Readability.js'
+  );
+  console.log('MarkDownload: All scripts imported successfully');
+} catch (e) {
+  console.error('MarkDownload: Failed to import scripts:', e);
+}
+
+// Default options (from default-options.js)
+const defaultOptions = {
+  headingStyle: "atx",
+  hr: "___",
+  bulletListMarker: "-",
+  codeBlockStyle: "fenced",
+  fence: "```",
+  emDelimiter: "_",
+  strongDelimiter: "**",
+  linkStyle: "inlined",
+  linkReferenceStyle: "full",
+  imageStyle: "markdown",
+  imageRefStyle: "inlined",
+  frontmatter: "---\ncreated: {date:YYYY-MM-DDTHH:mm:ss} (UTC {date:Z})\ntags: [{keywords}]\nsource: {baseURI}\nauthor: {byline}\n---\n\n# {pageTitle}\n\n> ## Excerpt\n> {excerpt}\n\n---",
+  backmatter: "",
+  title: "{pageTitle}",
+  includeTemplate: false,
+  saveAs: false,
+  downloadImages: false,
+  imagePrefix: '{pageTitle}/',
+  mdClipsFolder: null,
+  disallowedChars: '[]#^',
+  downloadMode: 'downloadsApi',
+  turndownEscape: true,
+  contextMenus: true,
+  obsidianIntegration: false,
+  obsidianVault: "",
+  obsidianFolder: "",
+};
+
+// Function to get options from storage
+async function getOptions() {
+  let options = defaultOptions;
+  try {
+    const result = await chrome.storage.sync.get(defaultOptions);
+    options = { ...defaultOptions, ...result };
+  } catch (err) {
+    console.error('MarkDownload: Failed to get options:', err);
+  }
+  if (!chrome.downloads) options.downloadMode = 'contentLink';
+  return options;
+}
+
+// Service Worker Event Listeners
+chrome.runtime.onStartup.addListener(() => {
+  console.log('MarkDownload: Extension startup');
+  createMenus().catch(e => console.error('MarkDownload: Failed to create menus on startup:', e));
+});
+
+chrome.runtime.onInstalled.addListener((details) => {
+  console.log('MarkDownload: Extension installed/updated:', details.reason);
+  createMenus().catch(e => console.error('MarkDownload: Failed to create menus on install:', e));
+});
+
+// Keep service worker alive
+chrome.runtime.onSuspend.addListener(() => {
+  console.log('MarkDownload: Service Worker is being suspended');
+});
+
+chrome.runtime.onSuspendCanceled.addListener(() => {
+  console.log('MarkDownload: Service Worker suspension cancelled');
+});
+
+// Log platform info for debugging
+chrome.runtime.getPlatformInfo().then(platformInfo => {
+  console.log('MarkDownload Platform Info:', platformInfo);
+  
+  // Try to get browser info if available
+  if (chrome.runtime.getBrowserInfo) {
+    chrome.runtime.getBrowserInfo().then(browserInfo => {
+      console.log('MarkDownload Browser Info:', browserInfo);
+    }).catch(e => console.warn('MarkDownload: Could not get browser info:', e));
+  }
+}).catch(e => {
+  console.warn('MarkDownload: Could not get platform info:', e);
+});
+
+// Initialize TurndownService prototype
+if (typeof TurndownService !== 'undefined') {
+  TurndownService.prototype.defaultEscape = TurndownService.prototype.escape;
+  console.log('MarkDownload: TurndownService initialized');
+}
+
+// Core helper functions
+function cleanAttribute(attribute) {
+  return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '';
+}
+
+function validateUri(href, baseURI) {
+  try {
+    new URL(href);
+  } catch {
+    const baseUri = new URL(baseURI);
+    if (href.startsWith('/')) {
+      href = baseUri.origin + href;
+    } else {
+      href = baseUri.href + (baseUri.href.endsWith('/') ? '' : '') + href;
+    }
+  }
+  return href;
+}
+
+// Helper functions moved to content script
+
+// Image processing functions moved to content script
+
+// Text replacement functions moved to content script
+
+// Polyfill for replaceAll if needed
+if (!String.prototype.replaceAll) {
+  String.prototype.replaceAll = function(str, newStr) {
+    if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
+      return this.replace(str, newStr);
+    }
+    return this.replace(new RegExp(str, 'g'), newStr);
+  };
+}
+
+// Context menu creation function
+async function createMenus() {
+  console.log('MarkDownload: Creating context menus');
+  const options = await getOptions();
+  
+  try {
+    await chrome.contextMenus.removeAll();
+  } catch (e) {
+    console.warn('MarkDownload: Failed to remove existing menus:', e);
+  }
+
+  if (options.contextMenus) {
+    const menuItems = [
+      {
+        id: "download-markdown-alltabs",
+        title: "Download All Tabs as Markdown",
+        contexts: ["all"]
+      },
+      {
+        id: "separator-0",
+        type: "separator", 
+        contexts: ["all"]
+      },
+      {
+        id: "download-markdown-selection",
+        title: "Download Selection As Markdown",
+        contexts: ["selection"]
+      },
+      {
+        id: "download-markdown-all",
+        title: "Download Tab As Markdown",
+        contexts: ["all"]
+      },
+      {
+        id: "separator-1",
+        type: "separator",
+        contexts: ["all"]
+      },
+      {
+        id: "copy-markdown-selection",
+        title: "Copy Selection As Markdown",
+        contexts: ["selection"]
+      },
+      {
+        id: "copy-markdown-link",
+        title: "Copy Link As Markdown",
+        contexts: ["link"]
+      },
+      {
+        id: "copy-markdown-image",
+        title: "Copy Image As Markdown", 
+        contexts: ["image"]
+      },
+      {
+        id: "copy-markdown-all",
+        title: "Copy Tab As Markdown",
+        contexts: ["all"]
+      },
+      {
+        id: "copy-tab-as-markdown-link",
+        title: "Copy Tab URL as Markdown Link",
+        contexts: ["all"]
+      },
+      {
+        id: "separator-2", 
+        type: "separator",
+        contexts: ["all"]
+      },
+      {
+        id: "toggle-includeTemplate",
+        type: "checkbox",
+        title: "Include front/back template",
+        contexts: ["all"],
+        checked: options.includeTemplate
+      },
+      {
+        id: "toggle-downloadImages",
+        type: "checkbox", 
+        title: "Download Images",
+        contexts: ["all"],
+        checked: options.downloadImages
+      }
+    ];
+
+    if (options.obsidianIntegration) {
+      menuItems.splice(-2, 0, 
+        {
+          id: "copy-markdown-obsidian",
+          title: "Send Text selection to Obsidian",
+          contexts: ["selection"]
+        },
+        {
+          id: "copy-markdown-obsall", 
+          title: "Send Tab to Obsidian",
+          contexts: ["all"]
+        },
+        {
+          id: "separator-3",
+          type: "separator",
+          contexts: ["all"]
+        }
+      );
+    }
+
+    for (const item of menuItems) {
+      try {
+        await chrome.contextMenus.create(item);
+        console.log('MarkDownload: Created menu item:', item.id, '- Title:', item.title);
+      } catch (e) {
+        console.warn('MarkDownload: Failed to create menu item:', item.id, e);
+      }
+    }
+    console.log('MarkDownload: Context menus created successfully');
+    
+    // Test: List all created menus for debugging
+    try {
+      const result = await new Promise((resolve) => {
+        chrome.contextMenus.query({}, resolve);
+      });
+      console.log('MarkDownload: Verified menus exist:', result?.length || 'query not supported');
+    } catch (e) {
+      console.log('MarkDownload: Menu verification not available');
+    }
+  }
+}
+
+// Main Turndown conversion function
+// Turndown conversion function moved to content script
+
+// Message handling from content scripts and popup
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+  console.log('MarkDownload: Received message:', message.type, 'from tab:', sender.tab?.id);
+  
+  // Handle message asynchronously
+  (async () => {
+    try {
+      await handleMessage(message, sender, sendResponse);
+    } catch (error) {
+      console.error('MarkDownload: Message handling error:', error);
+      sendResponse({ error: error.message });
+    }
+  })();
+  
+  return true; // Indicate async response
+});
+
+// Handle messages from content scripts and popup
+async function handleMessage(message, sender, sendResponse) {
+  try {
+    if (message.type === "clip") {
+      console.log('MarkDownload: Processing clip message');
+      
+      if (!sender.tab?.id) {
+        sendResponse({ error: 'No valid tab ID' });
+        return;
+      }
+      
+      // Use complete processing in content script (including markdown conversion)
+      const result = await getCompleteMarkdownFromContent(sender.tab.id, message.clipSelection);
+      
+      if (!result) {
+        sendResponse({ error: 'Failed to process complete article' });
+        return;
+      }
+
+      // Format folder
+      const mdClipsFolder = await formatMdClipsFolder(result.article);
+
+      // Send response back
+      sendResponse({
+        type: "display.md",
+        markdown: result.markdown,
+        article: result.article,
+        imageList: result.imageList,
+        mdClipsFolder: mdClipsFolder
+      });
+    } else if (message.type === "download") {
+      console.log('MarkDownload: Processing download message');
+      await downloadMarkdown(message.markdown, message.title, sender.tab.id, message.imageList, message.mdClipsFolder);
+      sendResponse({ success: true });
+    } else {
+      sendResponse({ error: 'Unknown message type: ' + message.type });
+    }
+  } catch (error) {
+    console.error('MarkDownload: Error handling message:', error);
+    sendResponse({ error: error.message });
+  }
+}
+
+// Context menu click handlers  
+chrome.contextMenus.onClicked.addListener(async (info, tab) => {
+  console.log('MarkDownload: Context menu clicked:', info.menuItemId, 'on tab:', tab.id);
+  console.log('MarkDownload: Full info object:', info);
+  console.log('MarkDownload: Tab object:', tab);
+  
+  try {
+    if (info.menuItemId.startsWith("copy-markdown")) {
+      console.log('MarkDownload: Routing to copyMarkdownFromContext');
+      await copyMarkdownFromContext(info, tab);
+    } else if (info.menuItemId === "download-markdown-alltabs") {
+      console.log('MarkDownload: Routing to downloadMarkdownForAllTabs');
+      await downloadMarkdownForAllTabs(info);
+    } else if (info.menuItemId.startsWith("download-markdown")) {
+      console.log('MarkDownload: Routing to downloadMarkdownFromContext');
+      await downloadMarkdownFromContext(info, tab);
+    } else if (info.menuItemId.startsWith("copy-tab-as-markdown-link")) {
+      console.log('MarkDownload: Routing to copyTabAsMarkdownLink');
+      await copyTabAsMarkdownLink(tab);
+    } else if (info.menuItemId.startsWith("toggle-")) {
+      console.log('MarkDownload: Routing to toggleSetting');
+      await toggleSetting(info.menuItemId.split('-')[1]);
+    } else {
+      console.warn('MarkDownload: Unhandled menu item:', info.menuItemId);
+    }
+    console.log('MarkDownload: Context menu action completed for:', info.menuItemId);
+  } catch (error) {
+    console.error('MarkDownload: Context menu action failed:', info.menuItemId, error);
+  }
+});
+
+// Command handlers
+chrome.commands.onCommand.addListener((command) => {
+  console.log('MarkDownload: Command executed:', command);
+  
+  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+    const tab = tabs[0];
+    
+    if (command === "download_tab_as_markdown") {
+      downloadMarkdownFromContext({ menuItemId: "download-markdown-all" }, tab);
+    } else if (command === "copy_tab_as_markdown") {
+      copyMarkdownFromContext({ menuItemId: "copy-markdown-all" }, tab);
+    } else if (command === "copy_selection_as_markdown") {
+      copyMarkdownFromContext({ menuItemId: "copy-markdown-selection" }, tab);
+    } else if (command === "copy_tab_as_markdown_link") {
+      copyTabAsMarkdownLink(tab);
+    } else if (command === "copy_selection_to_obsidian") {
+      copyMarkdownFromContext({ menuItemId: "copy-markdown-obsidian" }, tab);
+    } else if (command === "copy_tab_to_obsidian") {
+      copyMarkdownFromContext({ menuItemId: "copy-markdown-obsall" }, tab);
+    }
+  });
+});
+
+// Helper function to execute scripts in tabs (Manifest V3 compatible)
+async function executeScript(tabId, func, args = []) {
+  try {
+    const result = await chrome.scripting.executeScript({
+      target: { tabId: tabId },
+      func: func,
+      args: args
+    });
+    return result[0]?.result;
+  } catch (error) {
+    console.error('MarkDownload: Failed to execute script:', error);
+    throw error;
+  }
+}
+
+// Helper function to inject content script if needed
+async function ensureContentScript(tabId) {
+  try {
+    const result = await executeScript(tabId, () => {
+      return typeof getSelectionAndDom === 'function';
+    });
+    
+    if (!result) {
+      console.log('MarkDownload: Injecting content script into tab:', tabId);
+      await chrome.scripting.executeScript({
+        target: { tabId: tabId },
+        files: ['browser-polyfill.min.js', 'contentScript/contentScript.js']
+      });
+    }
+  } catch (error) {
+    console.error('MarkDownload: Failed to ensure content script:', error);
+    throw error;
+  }
+}
+
+// Function to get complete article and markdown from content script
+async function getCompleteMarkdownFromContent(tabId, selection = false) {
+  try {
+    console.log('MarkDownload: Getting complete markdown from tab:', tabId, 'selection:', selection);
+    await ensureContentScript(tabId);
+    
+    // Get options from storage
+    const options = await getOptions();
+    
+    // Get DOM content
+    const result = await executeScript(tabId, () => {
+      return getSelectionAndDom();
+    });
+    
+    if (!result || !result.dom) {
+      console.error('MarkDownload: No DOM content received');
+      return null;
+    }
+    
+    // Process complete article in content script (including markdown conversion)
+    const completeResult = await executeScript(tabId, (domString, options, selection) => {
+      return processCompleteArticle(domString, options, selection);
+    }, [result.dom, options, selection]);
+    
+    if (!completeResult) {
+      console.error('MarkDownload: Failed to process complete article');
+      return null;
+    }
+    
+    console.log('MarkDownload: Complete markdown processed successfully, title:', completeResult.title, 'markdown length:', completeResult.markdown.length);
+    return completeResult;
+  } catch (error) {
+    console.error('MarkDownload: Failed to get complete markdown from content:', error);
+    return null;
+  }
+}
+
+// Note: DOM processing has been moved to content script to avoid Service Worker limitations
+
+// DOM-dependent functions have been moved to content script
+
+async function formatMdClipsFolder(article) {
+  let options = await getOptions();
+  let mdClipsFolder = '';
+  if (options.mdClipsFolder && options.downloadMode == 'downloadsApi') {
+    mdClipsFolder = textReplace(options.mdClipsFolder, article, options.disallowedChars);
+    mdClipsFolder = mdClipsFolder.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/');
+    if (!mdClipsFolder.endsWith('/')) mdClipsFolder += '/';
+  }
+  return mdClipsFolder;
+}
+
+// Download function
+async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsFolder = '') {
+  console.log('MarkDownload: Starting download process');
+  const options = await getOptions();
+  
+  if (options.downloadMode == 'downloadsApi' && chrome.downloads) {
+    const url = URL.createObjectURL(new Blob([markdown], {
+      type: "text/markdown;charset=utf-8"
+    }));
+
+    try {
+      if (mdClipsFolder && !mdClipsFolder.endsWith('/')) mdClipsFolder += '/';
+      
+      const id = await chrome.downloads.download({
+        url: url,
+        filename: mdClipsFolder + title + ".md",
+        saveAs: options.saveAs
+      });
+
+      // Handle download completion
+      const downloadListener = (delta) => {
+        if (delta.id === id && delta.state && delta.state.current == "complete") {
+          chrome.downloads.onChanged.removeListener(downloadListener);
+          URL.revokeObjectURL(url);
+        }
+      };
+      chrome.downloads.onChanged.addListener(downloadListener);
+    } catch (err) {
+      console.error("MarkDownload: Download failed", err);
+    }
+  } else {
+    // Fallback to content link method
+    try {
+      await ensureContentScript(tabId);
+      const filename = mdClipsFolder + generateValidFileName(title, options.disallowedChars) + ".md";
+      const base64Data = btoa(encodeURIComponent(markdown).replace(/%([0-9A-F]{2})/g, function (match, p1) {
+        return String.fromCharCode('0x' + p1);
+      }));
+      
+      await executeScript(tabId, (filename, data) => {
+        const link = document.createElement('a');
+        link.download = filename;
+        link.href = `data:text/markdown;base64,${data}`;
+        link.click();
+      }, [filename, base64Data]);
+    } catch (error) {
+      console.error("MarkDownload: Content link download failed:", error);
+    }
+  }
+}
+
+// Context menu action handlers
+async function downloadMarkdownFromContext(info, tab) {
+  try {
+    console.log('MarkDownload: Download from context menu');
+    
+    // Get complete markdown result from content script
+    const result = await getCompleteMarkdownFromContent(tab.id, info.menuItemId === "download-markdown-selection");
+    if (!result) {
+      console.error('MarkDownload: Failed to get complete markdown result');
+      return;
+    }
+    
+    const mdClipsFolder = await formatMdClipsFolder(result.article);
+    
+    await downloadMarkdown(result.markdown, result.title, tab.id, result.imageList, mdClipsFolder);
+  } catch (error) {
+    console.error('MarkDownload: Download from context failed:', error);
+  }
+}
+
+async function copyMarkdownFromContext(info, tab) {
+  try {
+    console.log('MarkDownload: Copy from context menu, menuItemId:', info.menuItemId);
+    await ensureContentScript(tab.id);
+
+    if (info.menuItemId === "copy-markdown-link") {
+      console.log('MarkDownload: Processing link copy');
+      const options = await getOptions();
+      options.frontmatter = options.backmatter = '';
+      const article = await getArticleFromContent(tab.id, false);
+      if (!article) {
+        console.error('MarkDownload: Failed to get article for link');
+        return;
+      }
+      const { markdown } = turndown(`${info.linkText || info.selectionText}`, { ...options, downloadImages: false }, article);
+      console.log('MarkDownload: Link markdown generated, length:', markdown.length);
+      
+      await executeScript(tab.id, (text) => {
+        console.log('MarkDownload: Copying link to clipboard, length:', text.length);
+        navigator.clipboard.writeText(text).then(() => {
+          console.log('MarkDownload: Link copied to clipboard successfully');
+        }).catch(err => {
+          console.error('MarkDownload: Failed to copy link to clipboard:', err);
+        });
+      }, [markdown]);
+    } else if (info.menuItemId === "copy-markdown-image") {
+      console.log('MarkDownload: Processing image copy');
+      await executeScript(tab.id, (url) => {
+        console.log('MarkDownload: Copying image to clipboard:', url);
+        navigator.clipboard.writeText(`![](${url})`).then(() => {
+          console.log('MarkDownload: Image copied to clipboard successfully');
+        }).catch(err => {
+          console.error('MarkDownload: Failed to copy image to clipboard:', err);
+        });
+      }, [info.srcUrl]);
+    } else {
+      console.log('MarkDownload: Processing general copy, selection:', info.menuItemId === "copy-markdown-selection");
+      
+      // Get complete markdown result (including conversion) from content script
+      const result = await getCompleteMarkdownFromContent(tab.id, info.menuItemId === "copy-markdown-selection");
+      if (!result) {
+        console.error('MarkDownload: Failed to get complete markdown result');
+        return;
+      }
+      
+      console.log('MarkDownload: Complete result retrieved, title:', result.title, 'markdown length:', result.markdown?.length || 0);
+      
+      if (!result.markdown) {
+        console.error('MarkDownload: No markdown content received');
+        return;
+      }
+      
+      // Copy to clipboard via content script
+      await executeScript(tab.id, (markdown) => {
+        console.log('MarkDownload ContentScript: Copying markdown to clipboard, length:', markdown?.length);
+        
+        if (!markdown) {
+          console.error('MarkDownload ContentScript: No markdown content to copy');
+          return false;
+        }
+        
+        return copyToClipboard(markdown);
+      }, [result.markdown]);
+      
+      console.log('MarkDownload: Copy operation completed');
+    }
+  } catch (error) {
+    console.error('MarkDownload: Copy from context failed:', error);
+  }
+}
+
+async function copyTabAsMarkdownLink(tab) {
+  try {
+    console.log('MarkDownload: Copy tab as markdown link');
+    const result = await getCompleteMarkdownFromContent(tab.id, false);
+    if (!result) {
+      console.error('MarkDownload: Failed to get article for link');
+      return;
+    }
+    
+    await executeScript(tab.id, (title, url) => {
+      const linkMarkdown = `[${title}](${url})`;
+      console.log('MarkDownload ContentScript: Copying link to clipboard:', linkMarkdown);
+      return copyToClipboard(linkMarkdown);
+    }, [result.title, result.article.baseURI]);
+  } catch (error) {
+    console.error('MarkDownload: Copy tab link failed:', error);
+  }
+}
+
+async function downloadMarkdownForAllTabs(info) {
+  try {
+    console.log('MarkDownload: Download all tabs as markdown');
+    const tabs = await chrome.tabs.query({ currentWindow: true });
+    
+    for (const tab of tabs) {
+      try {
+        await downloadMarkdownFromContext(info, tab);
+      } catch (e) {
+        console.warn('MarkDownload: Failed to download tab:', tab.id, e);
+      }
+    }
+  } catch (error) {
+    console.error('MarkDownload: Download all tabs failed:', error);
+  }
+}
+
+// Settings toggle
+async function toggleSetting(setting, options = null) {
+  if (options == null) {
+    options = await getOptions();
+  }
+  
+  options[setting] = !options[setting];
+  await chrome.storage.sync.set(options);
+  
+  if (setting === "includeTemplate") {
+    chrome.contextMenus.update("toggle-includeTemplate", {
+      checked: options.includeTemplate
+    });
+  }
+  
+  if (setting === "downloadImages") {
+    chrome.contextMenus.update("toggle-downloadImages", {
+      checked: options.downloadImages
+    });
+  }
+}
+
+console.log('MarkDownload Service Worker fully loaded and ready');
diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js
index 0a56afa7..5af2e1ea 100644
--- a/src/contentScript/contentScript.js
+++ b/src/contentScript/contentScript.js
@@ -1,15 +1,396 @@
 function notifyExtension() {
     // send a message that the content should be clipped
-    browser.runtime.sendMessage({ type: "clip", dom: content});
+    console.log('MarkDownload ContentScript: Notifying extension');
+    const content = getHTMLOfDocument();
+    console.log('MarkDownload ContentScript: DOM content length:', content.length);
+    
+    if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
+        window.browser = chrome;
+    }
+    
+    browser.runtime.sendMessage({ 
+        type: "clip", 
+        dom: content
+    }).then(response => {
+        console.log('MarkDownload ContentScript: Response received:', response);
+    }).catch(error => {
+        console.error('MarkDownload ContentScript: Error sending message:', error);
+    });
+}
+
+// 初始化 TurndownService
+if (typeof TurndownService !== 'undefined') {
+    TurndownService.prototype.defaultEscape = TurndownService.prototype.escape;
+    console.log('MarkDownload ContentScript: TurndownService initialized');
+}
+
+// Helper functions moved from service worker
+function cleanAttribute(attribute) {
+    return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '';
+}
+
+function validateUri(href, baseURI) {
+    try {
+        new URL(href);
+    } catch {
+        const baseUri = new URL(baseURI);
+        if (href.startsWith('/')) {
+            href = baseUri.origin + href;
+        } else {
+            href = baseUri.href + (baseUri.href.endsWith('/') ? '' : '') + href;
+        }
+    }
+    return href;
+}
+
+function generateValidFileName(title, disallowedChars = null) {
+    if (!title) return title;
+    title = title + '';
+    
+    var illegalRe = /[\/\?<>\\:\*\|":]/g;
+    var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' ')
+        .replace(new RegExp(/\s+/, 'g'), ' ')
+        .trim();
+
+    if (disallowedChars) {
+        for (let c of disallowedChars) {
+            if (`[\\^$.|?*+()`.includes(c)) c = `\\${c}`;
+            name = name.replace(new RegExp(c, 'g'), '');
+        }
+    }
+    
+    return name;
+}
+
+function getImageFilename(src, options, prependFilePath = true) {
+    const slashPos = src.lastIndexOf('/');
+    const queryPos = src.indexOf('?');
+    let filename = src.substring(slashPos + 1, queryPos > 0 ? queryPos : src.length);
+
+    let imagePrefix = (options.imagePrefix || '');
+
+    if (prependFilePath && options.title.includes('/')) {
+        imagePrefix = options.title.substring(0, options.title.lastIndexOf('/') + 1) + imagePrefix;
+    } else if (prependFilePath) {
+        imagePrefix = options.title + (imagePrefix.startsWith('/') ? '' : '/') + imagePrefix;
+    }
+    
+    if (filename.includes(';base64,')) {
+        filename = 'image.' + filename.substring(0, filename.indexOf(';'));
+    }
+    
+    let extension = filename.substring(filename.lastIndexOf('.'));
+    if (extension == filename) {
+        filename = filename + '.idunno';
+    }
+
+    filename = generateValidFileName(filename, options.disallowedChars);
+    return imagePrefix + filename;
+}
+
+function textReplace(string, article, disallowedChars = null) {
+    for (const key in article) {
+        if (article.hasOwnProperty(key) && key != "content") {
+            let s = (article[key] || '') + '';
+            if (s && disallowedChars) s = generateValidFileName(s, disallowedChars);
+
+            string = string.replace(new RegExp('{' + key + '}', 'g'), s)
+                .replace(new RegExp('{' + key + ':lower}', 'g'), s.toLowerCase())
+                .replace(new RegExp('{' + key + ':upper}', 'g'), s.toUpperCase())
+                .replace(new RegExp('{' + key + ':kebab}', 'g'), s.replace(/ /g, '-').toLowerCase())
+                .replace(new RegExp('{' + key + ':mixed-kebab}', 'g'), s.replace(/ /g, '-'))
+                .replace(new RegExp('{' + key + ':snake}', 'g'), s.replace(/ /g, '_').toLowerCase())
+                .replace(new RegExp('{' + key + ':mixed_snake}', 'g'), s.replace(/ /g, '_'))
+                .replace(new RegExp('{' + key + ':obsidian-cal}', 'g'), s.replace(/ /g, '-').replace(/-{2,}/g, "-"))
+                .replace(new RegExp('{' + key + ':camel}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toLowerCase()))
+                .replace(new RegExp('{' + key + ':pascal}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toUpperCase()));
+        }
+    }
+
+    // Replace date formats
+    const now = new Date();
+    const dateRegex = /{date:(.+?)}/g;
+    const matches = string.match(dateRegex);
+    if (matches && matches.forEach) {
+        matches.forEach(match => {
+            const format = match.substring(6, match.length - 1);
+            if (typeof moment !== 'undefined') {
+                const dateString = moment(now).format(format);
+                string = string.replaceAll(match, dateString);
+            } else {
+                string = string.replaceAll(match, now.toISOString());
+            }
+        });
+    }
+
+    // Replace keywords
+    const keywordRegex = /{keywords:?(.*)?}/g;
+    const keywordMatches = string.match(keywordRegex);
+    if (keywordMatches && keywordMatches.forEach) {
+        keywordMatches.forEach(match => {
+            let seperator = match.substring(10, match.length - 1);
+            try {
+                seperator = JSON.parse(JSON.stringify(seperator).replace(/\\\\/g, '\\'));
+            } catch { }
+            const keywordsString = (article.keywords || []).join(seperator);
+            string = string.replace(new RegExp(match.replace(/\\/g, '\\\\'), 'g'), keywordsString);
+        });
+    }
+
+    string = string.replace(/{(.*?)}/g, '');
+    return string;
+}
+
+// Main turndown function
+function turndownContent(content, options, article) {
+    console.log('MarkDownload ContentScript: Converting content to markdown');
+    
+    if (options.turndownEscape) TurndownService.prototype.escape = TurndownService.prototype.defaultEscape;
+    else TurndownService.prototype.escape = s => s;
+
+    var turndownService = new TurndownService(options);
+    turndownService.use(turndownPluginGfm.gfm);
+    turndownService.keep(['iframe', 'sub', 'sup', 'u', 'ins', 'del', 'small', 'big']);
+
+    let imageList = {};
+    
+    // Add image rule
+    turndownService.addRule('images', {
+        filter: function (node, tdopts) {
+            if (node.nodeName == 'IMG' && node.getAttribute('src')) {
+                let src = node.getAttribute('src');
+                node.setAttribute('src', validateUri(src, article.baseURI));
+                
+                if (options.downloadImages) {
+                    let imageFilename = getImageFilename(src, options, false);
+                    if (!imageList[src] || imageList[src] != imageFilename) {
+                        let i = 1;
+                        while (Object.values(imageList).includes(imageFilename)) {
+                            const parts = imageFilename.split('.');
+                            if (i == 1) parts.splice(parts.length - 1, 0, i++);
+                            else parts.splice(parts.length - 2, 1, i++);
+                            imageFilename = parts.join('.');
+                        }
+                        imageList[src] = imageFilename;
+                    }
+                    
+                    const obsidianLink = options.imageStyle.startsWith("obsidian");
+                    const localSrc = options.imageStyle === 'obsidian-nofolder'
+                        ? imageFilename.substring(imageFilename.lastIndexOf('/') + 1)
+                        : imageFilename.split('/').map(s => obsidianLink ? s : encodeURI(s)).join('/');
+                    
+                    if(options.imageStyle != 'originalSource' && options.imageStyle != 'base64') {
+                        node.setAttribute('src', localSrc);
+                    }
+                    return true;
+                } else return true;
+            }
+            return false;
+        },
+        replacement: function (content, node, tdopts) {
+            if (options.imageStyle == 'noImage') return '';
+            else if (options.imageStyle.startsWith('obsidian')) return `![[${node.getAttribute('src')}]]`;
+            else {
+                var alt = cleanAttribute(node.getAttribute('alt'));
+                var src = node.getAttribute('src') || '';
+                var title = cleanAttribute(node.getAttribute('title'));
+                var titlePart = title ? ' "' + title + '"' : '';
+                if (options.imageRefStyle == 'referenced') {
+                    var id = this.references.length + 1;
+                    this.references.push('[fig' + id + ']: ' + src + titlePart);
+                    return '![' + alt + '][fig' + id + ']';
+                }
+                else return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '';
+            }
+        },
+        references: [],
+        append: function (options) {
+            var references = '';
+            if (this.references.length) {
+                references = '\n\n' + this.references.join('\n') + '\n\n';
+                this.references = [];
+            }
+            return references;
+        }
+    });
+
+    // Add link rule
+    turndownService.addRule('links', {
+        filter: (node, tdopts) => {
+            if (node.nodeName == 'A' && node.getAttribute('href')) {
+                const href = node.getAttribute('href');
+                node.setAttribute('href', validateUri(href, article.baseURI));
+                return options.linkStyle == 'stripLinks';
+            }
+            return false;
+        },
+        replacement: (content, node, tdopts) => content
+    });
+
+    // Add math rule
+    turndownService.addRule('mathjax', {
+        filter(node, options) {
+            return article.math && article.math.hasOwnProperty(node.id);
+        },
+        replacement(content, node, options) {
+            const math = article.math[node.id];
+            let tex = math.tex.trim().replaceAll('\xa0', '');
+
+            if (math.inline) {
+                tex = tex.replaceAll('\n', ' ');
+                return `$${tex}$`;
+            } else {
+                return `$$\n${tex}\n$$`;
+            }
+        }
+    });
+
+    let markdown = options.frontmatter + turndownService.turndown(content) + options.backmatter;
+    
+    // Strip out non-printing special characters
+    markdown = markdown.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, '');
+    
+    return { markdown: markdown, imageList: imageList };
+}
+
+// Complete article processing function
+function processCompleteArticle(domString, options, selection = false) {
+    console.log('MarkDownload ContentScript: Processing complete article');
+    
+    try {
+        // Step 1: Process DOM to article
+        const article = processArticleFromDOM(domString);
+        
+        if (!article) {
+            throw new Error('Failed to process article from DOM');
+        }
+
+        // Step 2: Apply selection if provided
+        const selectionData = getHTMLOfSelection();
+        if (selection && selectionData) {
+            article.content = selectionData;
+        }
+        
+        // Step 3: Prepare options
+        if (options.includeTemplate) {
+            options.frontmatter = textReplace(options.frontmatter, article) + '\n';
+            options.backmatter = '\n' + textReplace(options.backmatter, article);
+        } else {
+            options.frontmatter = options.backmatter = '';
+        }
+
+        options.imagePrefix = textReplace(options.imagePrefix, article, options.disallowedChars)
+            .split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/');
+
+        // Step 4: Convert to markdown
+        const result = turndownContent(article.content, options, article);
+        
+        // Step 5: Format title
+        let title = textReplace(options.title, article, options.disallowedChars + '/');
+        title = title.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/');
+        
+        console.log('MarkDownload ContentScript: Article processing complete, title:', title, 'markdown length:', result.markdown.length);
+        
+        return {
+            markdown: result.markdown,
+            imageList: result.imageList,
+            title: title,
+            article: article
+        };
+    } catch (error) {
+        console.error('MarkDownload ContentScript: Error processing complete article:', error);
+        throw error;
+    }
+}
+
+// 新增:处理来自Service Worker的DOM处理请求
+function processArticleFromDOM(domString) {
+    console.log('MarkDownload ContentScript: Processing DOM to article');
+    
+    try {
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(domString, "text/html");
+
+        if (dom.documentElement.nodeName == "parsererror") {
+            console.error("MarkDownload ContentScript: DOM parsing error");
+            throw new Error("DOM parsing failed");
+        }
+
+        const math = {};
+        
+        // 简化的数学公式处理
+        if (dom.body) {
+            dom.body.querySelectorAll('h1, h2, h3, h4, h5, h6')?.forEach(header => {
+                header.className = '';
+            });
+            dom.documentElement.removeAttribute('class');
+        }
+
+        // 使用 Readability 提取文章
+        const article = new Readability(dom).parse();
+
+        if (!article) {
+            throw new Error("Failed to parse article from DOM");
+        }
+
+        // 添加元数据
+        article.baseURI = dom.baseURI || window.location.href;
+        article.pageTitle = dom.title || document.title || 'Untitled';
+        
+        try {
+            const url = new URL(article.baseURI);
+            article.hash = url.hash;
+            article.host = url.host;
+            article.origin = url.origin;
+            article.hostname = url.hostname;
+            article.pathname = url.pathname;
+            article.port = url.port;
+            article.protocol = url.protocol;
+            article.search = url.search;
+        } catch (e) {
+            console.warn('MarkDownload ContentScript: Failed to parse URL info:', e);
+        }
+
+        if (dom.head) {
+            article.keywords = dom.head.querySelector('meta[name="keywords"]')?.content?.split(',')?.map(s => s.trim());
+
+            dom.head.querySelectorAll('meta[name][content], meta[property][content]')?.forEach(meta => {
+                const key = (meta.getAttribute('name') || meta.getAttribute('property'));
+                const val = meta.getAttribute('content');
+                if (key && val && !article[key]) {
+                    article[key] = val;
+                }
+            });
+        }
+
+        article.math = math;
+        console.log('MarkDownload ContentScript: Article processed successfully, title:', article.title);
+        return article;
+    } catch (error) {
+        console.error('MarkDownload ContentScript: Error processing DOM:', error);
+        throw error;
+    }
 }
 
 function getHTMLOfDocument() {
+    console.log('MarkDownload ContentScript: Starting document processing');
+    console.log('MarkDownload ContentScript: Page URL:', window.location.href);
+    console.log('MarkDownload ContentScript: Page title:', document.title);
+    
+    try {
     // make sure a title tag exists so that pageTitle is not empty and
-    // a filename can be genarated.
+        // a filename can be generated.
+        if (!document.head) {
+            console.warn('MarkDownload ContentScript: No document head found, creating one');
+            const head = document.createElement('head');
+            document.documentElement.insertBefore(head, document.body);
+        }
+        
     if (document.head.getElementsByTagName('title').length == 0) {
+            console.log('MarkDownload ContentScript: Adding missing title tag');
         let titleEl = document.createElement('title');
-        // prepate a good default text (the text displayed in the window title)
-        titleEl.innerText = document.title;
+            // prepare a good default text (the text displayed in the window title)
+            titleEl.innerText = document.title || 'Untitled';
         document.head.append(titleEl);
     }
 
@@ -22,6 +403,7 @@ function getHTMLOfDocument() {
     if (baseEls.length > 0) {
         baseEl = baseEls[0];
     } else {
+            console.log('MarkDownload ContentScript: Adding base element');
         baseEl = document.createElement('base');
         document.head.append(baseEl);
     }
@@ -34,14 +416,33 @@ function getHTMLOfDocument() {
     let href = baseEl.getAttribute('href');
 
     if (!href || !href.startsWith(window.location.origin)) {
+            console.log('MarkDownload ContentScript: Setting base href to:', window.location.href);
         baseEl.setAttribute('href', window.location.href);
     }
 
     // remove the hidden content from the page
+        if (document.body) {
+            console.log('MarkDownload ContentScript: Removing hidden nodes');
     removeHiddenNodes(document.body);
+        } else {
+            console.warn('MarkDownload ContentScript: No document body found');
+        }
 
     // get the content of the page as a string
-    return document.documentElement.outerHTML;
+        const htmlContent = document.documentElement.outerHTML;
+        console.log('MarkDownload ContentScript: Generated HTML length:', htmlContent.length);
+        
+        // Basic validation
+        if (!htmlContent || htmlContent.length < 100) {
+            console.warn('MarkDownload ContentScript: Generated HTML seems too small');
+        }
+        
+        return htmlContent;
+    } catch (error) {
+        console.error('MarkDownload ContentScript: Error processing document:', error);
+        // Return basic HTML as fallback
+        return `${document.title || 'Error'}

Error processing page content.

`; + } } // code taken from here: https://www.reddit.com/r/javascript/comments/27bcao/anyone_have_a_method_for_finding_all_the_hidden/ @@ -97,24 +498,93 @@ function getHTMLOfSelection() { } function getSelectionAndDom() { + console.log('MarkDownload ContentScript: Getting selection and DOM'); + + try { + const selection = getHTMLOfSelection(); + const dom = getHTMLOfDocument(); + + console.log('MarkDownload ContentScript: Selection length:', selection.length); + console.log('MarkDownload ContentScript: DOM length:', dom.length); + + return { + selection: selection, + dom: dom + }; + } catch (error) { + console.error('MarkDownload ContentScript: Error getting selection and DOM:', error); return { - selection: getHTMLOfSelection(), + selection: '', dom: getHTMLOfDocument() + }; } } // This function must be called in a visible page, such as a browserAction popup // or a content script. Calling it in a background page has no effect! function copyToClipboard(text) { - navigator.clipboard.writeText(text); + console.log('MarkDownload ContentScript: Copying to clipboard, length:', text.length); + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(() => { + console.log('MarkDownload ContentScript: Successfully copied to clipboard'); + }).catch(error => { + console.error('MarkDownload ContentScript: Failed to copy to clipboard:', error); + + // Fallback method + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.select(); + textArea.setSelectionRange(0, 99999); + document.execCommand('copy'); + document.body.removeChild(textArea); + console.log('MarkDownload ContentScript: Fallback copy successful'); + } catch (fallbackError) { + console.error('MarkDownload ContentScript: Fallback copy failed:', fallbackError); + } + }); + } else { + console.warn('MarkDownload ContentScript: Clipboard API not available, using fallback'); + // Fallback method for older browsers + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.select(); + textArea.setSelectionRange(0, 99999); + document.execCommand('copy'); + document.body.removeChild(textArea); + console.log('MarkDownload ContentScript: Fallback copy successful'); + } catch (error) { + console.error('MarkDownload ContentScript: All copy methods failed:', error); + } + } } function downloadMarkdown(filename, text) { + console.log('MarkDownload ContentScript: Starting download:', filename, 'Data length:', text.length); + + try { let datauri = `data:text/markdown;base64,${text}`; var link = document.createElement('a'); link.download = filename; link.href = datauri; + link.style.display = 'none'; + document.body.appendChild(link); link.click(); + document.body.removeChild(link); + console.log('MarkDownload ContentScript: Download triggered successfully'); + } catch (error) { + console.error('MarkDownload ContentScript: Download failed:', error); + } } function downloadImage(filename, url) { @@ -163,8 +633,21 @@ function downloadImage(filename, url) { */ } +// Initialize content script with debugging +console.log('MarkDownload ContentScript: Initializing on', window.location.href); + +// Ensure browser polyfill compatibility +if (typeof browser === 'undefined' && typeof chrome !== 'undefined') { + window.browser = chrome; +} + (function loadPageContextScript(){ + try { var s = document.createElement('script'); s.src = browser.runtime.getURL('contentScript/pageContext.js'); (document.head||document.documentElement).appendChild(s); + console.log('MarkDownload ContentScript: Page context script loaded'); + } catch (error) { + console.error('MarkDownload ContentScript: Failed to load page context script:', error); + } })() diff --git a/src/contentScript/pageContext.js b/src/contentScript/pageContext.js index 90525dbf..d7c008fe 100644 --- a/src/contentScript/pageContext.js +++ b/src/contentScript/pageContext.js @@ -1,11 +1,25 @@ function addLatexToMathJax3() { - if (!MathJax?.startup?.document?.math) - return + // Check if MathJax exists and has the required properties + if (typeof MathJax === 'undefined' || !MathJax?.startup?.document?.math) { + console.log('MarkDownload PageContext: MathJax not available, skipping LaTeX processing'); + return; + } - for (math of MathJax.startup.document.math) - { - math.typesetRoot.setAttribute("markdownload-latex", math.math) + try { + console.log('MarkDownload PageContext: Processing MathJax LaTeX'); + for (const math of MathJax.startup.document.math) { + if (math.typesetRoot && math.math) { + math.typesetRoot.setAttribute("markdownload-latex", math.math); + } + } + console.log('MarkDownload PageContext: MathJax LaTeX processing completed'); + } catch (error) { + console.warn('MarkDownload PageContext: Error processing MathJax LaTeX:', error); } } -addLatexToMathJax3() + +// Only run if we're in a proper browser context +if (typeof window !== 'undefined') { + addLatexToMathJax3(); +} diff --git a/src/manifest.json b/src/manifest.json index 19a581cc..b61d2fa7 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,8 +1,8 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "MarkDownload - Markdown Web Clipper", "version": "3.4.0", - "author": "Gordon Pedsersen", + "author": "Gordon Pedsensen", "description": "This extension works like a web clipper, but it downloads articles in markdown format.", "icons": { "16": "icons/favicon-16x16.png", @@ -13,14 +13,17 @@ "512": "icons/favicon-512x512.png" }, "permissions": [ - "", "activeTab", "downloads", "storage", "contextMenus", - "clipboardWrite" + "clipboardWrite", + "scripting" ], - "browser_action": { + "host_permissions": [ + "" + ], + "action": { "default_title": "MarkDownload", "default_popup": "popup/popup.html", "default_icon": { @@ -31,26 +34,36 @@ } }, "background": { - "scripts": [ - "browser-polyfill.min.js", - "background/apache-mime-types.js", - "background/moment.min.js", - "background/turndown.js", - "background/turndown-plugin-gfm.js", - "/background/Readability.js", - "shared/context-menus.js", - "shared/default-options.js", - "background/background.js" - ] + "service_worker": "background/service-worker.js" }, + "content_scripts": [ + { + "matches": [""], + "js": [ + "browser-polyfill.min.js", + "background/apache-mime-types.js", + "background/moment.min.js", + "background/turndown.js", + "background/turndown-plugin-gfm.js", + "background/Readability.js", + "shared/default-options.js", + "contentScript/contentScript.js" + ], + "run_at": "document_end" + } + ], + "web_accessible_resources": [ + { + "resources": ["contentScript/pageContext.js"], + "matches": [""] + } + ], "options_ui": { "page": "options/options.html", - "browser_style": false, - "chrome_style": false, "open_in_tab": false }, "commands": { - "_execute_browser_action": { + "_execute_action": { "suggested_key": { "default": "Alt+Shift+M" } From 83a9a1ac85493ff7ee38218d35b2142abd3a7f2a Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Wed, 10 Sep 2025 17:57:51 +0800 Subject: [PATCH 22/29] fix send to obsidian --- src/background/service-worker.js | 154 ++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 88ff5a6e..88acd577 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -119,7 +119,80 @@ function validateUri(href, baseURI) { return href; } -// Helper functions moved to content script +// Helper functions for Obsidian integration +function generateValidFileName(title, disallowedChars = null) { + if (!title) return title; + title = title + ''; + + var illegalRe = /[\/\?<>\\:\*\|":]/g; + var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' ') + .replace(new RegExp(/\s+/, 'g'), ' ') + .trim(); + + if (disallowedChars) { + for (let c of disallowedChars) { + if (`[\\^$.|?*+()`.includes(c)) c = `\\${c}`; + name = name.replace(new RegExp(c, 'g'), ''); + } + } + + return name; +} + +function textReplace(string, article, disallowedChars = null) { + for (const key in article) { + if (article.hasOwnProperty(key) && key != "content") { + let s = (article[key] || '') + ''; + if (s && disallowedChars) s = generateValidFileName(s, disallowedChars); + + string = string.replace(new RegExp('{' + key + '}', 'g'), s) + .replace(new RegExp('{' + key + ':lower}', 'g'), s.toLowerCase()) + .replace(new RegExp('{' + key + ':upper}', 'g'), s.toUpperCase()) + .replace(new RegExp('{' + key + ':kebab}', 'g'), s.replace(/ /g, '-').toLowerCase()) + .replace(new RegExp('{' + key + ':mixed-kebab}', 'g'), s.replace(/ /g, '-')) + .replace(new RegExp('{' + key + ':snake}', 'g'), s.replace(/ /g, '_').toLowerCase()) + .replace(new RegExp('{' + key + ':mixed_snake}', 'g'), s.replace(/ /g, '_')) + .replace(new RegExp('{' + key + ':obsidian-cal}', 'g'), s.replace(/ /g, '-').replace(/-{2,}/g, "-")) + .replace(new RegExp('{' + key + ':camel}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toLowerCase())) + .replace(new RegExp('{' + key + ':pascal}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toUpperCase())); + } + } + + // Replace date formats + const now = new Date(); + const dateRegex = /{date:(.+?)}/g; + const matches = string.match(dateRegex); + if (matches && matches.forEach) { + matches.forEach(match => { + const format = match.substring(6, match.length - 1); + if (typeof moment !== 'undefined') { + const dateString = moment(now).format(format); + string = string.replaceAll(match, dateString); + } else { + // Fallback if moment is not available + string = string.replaceAll(match, now.toISOString()); + } + }); + } + + // Replace keywords + const keywordRegex = /{keywords:?(.*)?}/g; + const keywordMatches = string.match(keywordRegex); + if (keywordMatches && keywordMatches.forEach) { + keywordMatches.forEach(match => { + let seperator = match.substring(10, match.length - 1); + try { + seperator = JSON.parse(JSON.stringify(seperator).replace(/\\\\/g, '\\')); + } catch { } + const keywordsString = (article.keywords || []).join(seperator); + string = string.replace(new RegExp(match.replace(/\\/g, '\\\\'), 'g'), keywordsString); + }); + } + + // Replace anything left in curly braces + string = string.replace(/{(.*?)}/g, ''); + return string; +} // Image processing functions moved to content script @@ -331,7 +404,10 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { console.log('MarkDownload: Tab object:', tab); try { - if (info.menuItemId.startsWith("copy-markdown")) { + if (info.menuItemId === "copy-markdown-obsidian" || info.menuItemId === "copy-markdown-obsall") { + console.log('MarkDownload: Routing to sendToObsidian'); + await sendToObsidian(info, tab); + } else if (info.menuItemId.startsWith("copy-markdown")) { console.log('MarkDownload: Routing to copyMarkdownFromContext'); await copyMarkdownFromContext(info, tab); } else if (info.menuItemId === "download-markdown-alltabs") { @@ -371,9 +447,9 @@ chrome.commands.onCommand.addListener((command) => { } else if (command === "copy_tab_as_markdown_link") { copyTabAsMarkdownLink(tab); } else if (command === "copy_selection_to_obsidian") { - copyMarkdownFromContext({ menuItemId: "copy-markdown-obsidian" }, tab); + sendToObsidian({ menuItemId: "copy-markdown-obsidian" }, tab); } else if (command === "copy_tab_to_obsidian") { - copyMarkdownFromContext({ menuItemId: "copy-markdown-obsall" }, tab); + sendToObsidian({ menuItemId: "copy-markdown-obsall" }, tab); } }); }); @@ -665,4 +741,74 @@ async function toggleSetting(setting, options = null) { } } +// Obsidian integration function (using clipboard method like original) +async function sendToObsidian(info, tab) { + try { + console.log('MarkDownload: Sending to Obsidian, menuItemId:', info.menuItemId); + + // Get complete markdown result from content script + const result = await getCompleteMarkdownFromContent(tab.id, info.menuItemId === "copy-markdown-obsidian"); + if (!result) { + console.error('MarkDownload: Failed to get complete markdown result for Obsidian'); + return; + } + + console.log('MarkDownload: Obsidian result retrieved, title:', result.title, 'markdown length:', result.markdown?.length || 0); + + if (!result.markdown) { + console.error('MarkDownload: No markdown content received for Obsidian'); + return; + } + + // Get Obsidian options + const options = await getOptions(); + if (!options.obsidianIntegration) { + console.error('MarkDownload: Obsidian integration not enabled'); + return; + } + + // Step 1: Copy markdown to clipboard (like original implementation) + console.log('MarkDownload: Copying markdown to clipboard for Obsidian'); + await executeScript(tab.id, (markdown) => { + console.log('MarkDownload ContentScript: Copying markdown to clipboard for Obsidian'); + return copyToClipboard(markdown); + }, [result.markdown]); + + // Step 2: Prepare Obsidian URI using clipboard method + const vault = options.obsidianVault || ''; + const folder = options.obsidianFolder || ''; + + // Format folder like original implementation + let obsidianFolder = ''; + if (folder) { + // Use text replacement and filename generation like original + obsidianFolder = result.article ? + textReplace(folder, result.article, options.disallowedChars) : folder; + obsidianFolder = obsidianFolder.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/'); + if (!obsidianFolder.endsWith('/')) obsidianFolder += '/'; + } + + // Generate filename like original + const filename = generateValidFileName(result.title, options.disallowedChars); + const filepath = obsidianFolder + filename; + + // Create Obsidian URI using clipboard method (like original) + const obsidianUri = 'obsidian://advanced-uri?' + + 'vault=' + encodeURIComponent(vault) + + '&clipboard=true' + + '&mode=new' + + '&filepath=' + encodeURIComponent(filepath); + + console.log('MarkDownload: Opening Obsidian URI (clipboard method):', obsidianUri); + + // Open Obsidian URI using chrome.tabs.create (Manifest V3 compatible) + await chrome.tabs.create({ url: obsidianUri }); + console.log('MarkDownload: Obsidian URI opened successfully'); + + console.log('MarkDownload: Obsidian operation completed'); + } catch (error) { + console.error('MarkDownload: Send to Obsidian failed:', error); + } +} + console.log('MarkDownload Service Worker fully loaded and ready'); From 0a24d161e357263fddf6824ee882d920e6ab1db2 Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Wed, 10 Sep 2025 18:00:25 +0800 Subject: [PATCH 23/29] fix send to obsidian --- src/background/service-worker.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 88acd577..647d2f88 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -801,9 +801,28 @@ async function sendToObsidian(info, tab) { console.log('MarkDownload: Opening Obsidian URI (clipboard method):', obsidianUri); - // Open Obsidian URI using chrome.tabs.create (Manifest V3 compatible) - await chrome.tabs.create({ url: obsidianUri }); - console.log('MarkDownload: Obsidian URI opened successfully'); + // Try to open Obsidian URI directly + try { + // Method 1: Try to update current tab (most direct) + await chrome.tabs.update(tab.id, { url: obsidianUri }); + console.log('MarkDownload: Obsidian URI opened successfully via tab update'); + } catch (error) { + console.log('MarkDownload: Tab update failed, trying alternative method:', error); + + // Method 2: Fallback to creating new tab + try { + await chrome.tabs.create({ url: obsidianUri }); + console.log('MarkDownload: Obsidian URI opened successfully via new tab'); + } catch (error2) { + console.error('MarkDownload: All methods failed:', error2); + + // Method 3: Final fallback - show notification with manual instructions + await executeScript(tab.id, (uri) => { + alert('MarkDownload: Content copied to clipboard!\n\nTo open in Obsidian:\n1. Open Obsidian\n2. Create new note\n3. Paste content (Ctrl+V)\n\nOr click OK to try opening Obsidian URI again.'); + window.open(uri, '_blank'); + }, [obsidianUri]); + } + } console.log('MarkDownload: Obsidian operation completed'); } catch (error) { From 0283e135bad7b4568c1571ee52a52bff0df1ef4d Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Wed, 10 Sep 2025 18:17:40 +0800 Subject: [PATCH 24/29] fix download --- src/background/service-worker.js | 37 +++++------- src/popup/popup.html | 2 +- src/popup/popup.js | 100 +++++++++++++++---------------- 3 files changed, 66 insertions(+), 73 deletions(-) diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 647d2f88..f5c9df39 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -339,7 +339,7 @@ async function createMenus() { // Message handling from content scripts and popup chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - console.log('MarkDownload: Received message:', message.type, 'from tab:', sender.tab?.id); + console.log('MarkDownload: Received message:', message.type, 'from tab:', sender.tab?.id, 'message.tabId:', message?.tabId); // Handle message asynchronously (async () => { @@ -360,13 +360,16 @@ async function handleMessage(message, sender, sendResponse) { if (message.type === "clip") { console.log('MarkDownload: Processing clip message'); - if (!sender.tab?.id) { + // Prefer tabId from message (popup), fallback to sender.tab.id + const tabId = message.tabId || sender.tab?.id; + if (!tabId) { sendResponse({ error: 'No valid tab ID' }); return; } + console.log('MarkDownload: Using tab ID for clip:', tabId); // Use complete processing in content script (including markdown conversion) - const result = await getCompleteMarkdownFromContent(sender.tab.id, message.clipSelection); + const result = await getCompleteMarkdownFromContent(tabId, message.clipSelection); if (!result) { sendResponse({ error: 'Failed to process complete article' }); @@ -376,17 +379,20 @@ async function handleMessage(message, sender, sendResponse) { // Format folder const mdClipsFolder = await formatMdClipsFolder(result.article); - // Send response back - sendResponse({ + // Send response back (for request) and also broadcast for popup listeners + const responsePayload = { type: "display.md", markdown: result.markdown, article: result.article, imageList: result.imageList, mdClipsFolder: mdClipsFolder - }); + }; + try { sendResponse(responsePayload); } catch {} + try { chrome.runtime.sendMessage(responsePayload); } catch {} } else if (message.type === "download") { console.log('MarkDownload: Processing download message'); - await downloadMarkdown(message.markdown, message.title, sender.tab.id, message.imageList, message.mdClipsFolder); + const tabId = message.tabId || sender.tab?.id; + await downloadMarkdown(message.markdown, message.title, tabId, message.imageList, message.mdClipsFolder); sendResponse({ success: true }); } else { sendResponse({ error: 'Unknown message type: ' + message.type }); @@ -547,27 +553,16 @@ async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsF const options = await getOptions(); if (options.downloadMode == 'downloadsApi' && chrome.downloads) { - const url = URL.createObjectURL(new Blob([markdown], { - type: "text/markdown;charset=utf-8" - })); + // Use data URL directly; createObjectURL is not available in MV3 service worker + const dataUrl = 'data:text/markdown;charset=utf-8,' + encodeURIComponent(markdown); try { if (mdClipsFolder && !mdClipsFolder.endsWith('/')) mdClipsFolder += '/'; - const id = await chrome.downloads.download({ - url: url, + url: dataUrl, filename: mdClipsFolder + title + ".md", saveAs: options.saveAs }); - - // Handle download completion - const downloadListener = (delta) => { - if (delta.id === id && delta.state && delta.state.current == "complete") { - chrome.downloads.onChanged.removeListener(downloadListener); - URL.revokeObjectURL(url); - } - }; - chrome.downloads.onChanged.addListener(downloadListener); } catch (err) { console.error("MarkDownload: Download failed", err); } diff --git a/src/popup/popup.html b/src/popup/popup.html index 54eaaee4..62caac63 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -26,7 +26,7 @@ Download selected
- + diff --git a/src/popup/popup.js b/src/popup/popup.js index 773e857e..6ae6a921 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -97,40 +97,45 @@ const showOrHideClipOption = selection => { } const clipSite = id => { - return browser.tabs.executeScript(id, { code: "getSelectionAndDom()" }) - .then((result) => { - if (result && result[0]) { - showOrHideClipOption(result[0].selection); - let message = { - type: "clip", - dom: result[0].dom, - selection: result[0].selection - } - return browser.storage.sync.get(defaultOptions).then(options => { - browser.runtime.sendMessage({ - ...message, - ...options - }); - }).catch(err => { - console.error(err); - showError(err) - return browser.runtime.sendMessage({ - ...message, - ...defaultOptions - }); - }).catch(err => { - console.error(err); - showError(err) - }); + return chrome.scripting.executeScript({ + target: { tabId: id }, + func: () => { + return getSelectionAndDom(); + } + }).then((result) => { + if (result && result[0] && result[0].result) { + showOrHideClipOption(result[0].result.selection); + let message = { + type: "clip", + dom: result[0].result.dom, + selection: result[0].result.selection, + tabId: id } - }).catch(err => { - console.error(err); - showError(err) - }); + return chrome.storage.sync.get(defaultOptions).then(options => { + chrome.runtime.sendMessage({ + ...message, + ...options + }); + }).catch(err => { + console.error(err); + showError(err) + return chrome.runtime.sendMessage({ + ...message, + ...defaultOptions + }); + }).catch(err => { + console.error(err); + showError(err) + }); + } + }).catch(err => { + console.error(err); + showError(err) + }); } -// inject the necessary scripts -browser.storage.sync.get(defaultOptions).then(options => { +// Initialize popup +chrome.storage.sync.get(defaultOptions).then(options => { checkInitialSettings(options); document.getElementById("selected").addEventListener("click", (e) => { @@ -150,49 +155,42 @@ browser.storage.sync.get(defaultOptions).then(options => { toggleDownloadImages(options); }); - return browser.tabs.query({ + return chrome.tabs.query({ currentWindow: true, active: true }); }).then((tabs) => { var id = tabs[0].id; var url = tabs[0].url; - browser.tabs.executeScript(id, { - file: "/browser-polyfill.min.js" - }) - .then(() => { - return browser.tabs.executeScript(id, { - file: "/contentScript/contentScript.js" - }); - }).then( () => { - console.info("Successfully injected MarkDownload content script"); - return clipSite(id); - }).catch( (error) => { - console.error(error); - showError(error); - }); + + // Content script is already injected via manifest, just call clipSite + console.info("MarkDownload popup: Content script should be available"); + return clipSite(id); +}).catch( (error) => { + console.error(error); + showError(error); }); // listen for notifications from the background page -browser.runtime.onMessage.addListener(notify); +chrome.runtime.onMessage.addListener(notify); //function to send the download message to the background page function sendDownloadMessage(text) { if (text != null) { - - return browser.tabs.query({ + return chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { + const activeTab = tabs[0]; var message = { type: "download", markdown: text, title: document.getElementById("title").value, - tab: tabs[0], + tabId: activeTab?.id, imageList: imageList, mdClipsFolder: mdClipsFolder }; - return browser.runtime.sendMessage(message); + return chrome.runtime.sendMessage(message); }); } } From 83c6faef2af5815e9e853545716ed5e79c869285 Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Wed, 10 Sep 2025 18:44:04 +0800 Subject: [PATCH 25/29] fix download image and add copy button --- src/background/service-worker.js | 40 +++++++++++++++++++++- src/contentScript/contentScript.js | 5 ++- src/options/options.js | 22 ++++++------ src/popup/popup.html | 1 + src/popup/popup.js | 50 +++++++++++++++++++++++---- src/shared/context-menus.js | 54 +++++++++++++++--------------- src/shared/default-options.js | 4 +-- 7 files changed, 127 insertions(+), 49 deletions(-) diff --git a/src/background/service-worker.js b/src/background/service-worker.js index f5c9df39..ee6afd36 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -550,27 +550,63 @@ async function formatMdClipsFolder(article) { // Download function async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsFolder = '') { console.log('MarkDownload: Starting download process'); + console.log('MarkDownload: Download params - title:', title, 'tabId:', tabId, 'imageList keys:', Object.keys(imageList).length, 'mdClipsFolder:', mdClipsFolder); + const options = await getOptions(); + console.log('MarkDownload: Options - downloadMode:', options.downloadMode, 'downloadImages:', options.downloadImages, 'saveAs:', options.saveAs); if (options.downloadMode == 'downloadsApi' && chrome.downloads) { + console.log('MarkDownload: Using downloads API'); // Use data URL directly; createObjectURL is not available in MV3 service worker const dataUrl = 'data:text/markdown;charset=utf-8,' + encodeURIComponent(markdown); + console.log('MarkDownload: Data URL length:', dataUrl.length); try { if (mdClipsFolder && !mdClipsFolder.endsWith('/')) mdClipsFolder += '/'; + const filename = mdClipsFolder + title + ".md"; + console.log('MarkDownload: Downloading file:', filename); + const id = await chrome.downloads.download({ url: dataUrl, - filename: mdClipsFolder + title + ".md", + filename: filename, saveAs: options.saveAs }); + console.log('MarkDownload: Download started with ID:', id); + + // Download images (if enabled) + if (options.downloadImages && Object.keys(imageList).length > 0) { + console.log('MarkDownload: Downloading images:', Object.keys(imageList).length); + // Get the relative path of the markdown file (if any) for image path + let destPath = mdClipsFolder + title.substring(0, title.lastIndexOf('/')); + if(destPath && !destPath.endsWith('/')) destPath += '/'; + + Object.entries(imageList).forEach(async ([src, filename]) => { + try { + // Start the download of the image + const imgId = await chrome.downloads.download({ + url: src, + // Set a destination path (relative to md file) + filename: destPath ? destPath + filename : filename, + saveAs: false + }); + console.log('MarkDownload: Image download started:', filename); + } catch (imgErr) { + console.error('MarkDownload: Image download failed:', src, imgErr); + } + }); + } } catch (err) { console.error("MarkDownload: Download failed", err); + console.error("MarkDownload: Error details:", err.message, err.stack); } } else { + console.log('MarkDownload: Using content link fallback method'); // Fallback to content link method try { await ensureContentScript(tabId); const filename = mdClipsFolder + generateValidFileName(title, options.disallowedChars) + ".md"; + console.log('MarkDownload: Fallback filename:', filename); + const base64Data = btoa(encodeURIComponent(markdown).replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode('0x' + p1); })); @@ -581,8 +617,10 @@ async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsF link.href = `data:text/markdown;base64,${data}`; link.click(); }, [filename, base64Data]); + console.log('MarkDownload: Fallback download completed'); } catch (error) { console.error("MarkDownload: Content link download failed:", error); + console.error("MarkDownload: Fallback error details:", error.message, error.stack); } } } diff --git a/src/contentScript/contentScript.js b/src/contentScript/contentScript.js index 5af2e1ea..df13fa0a 100644 --- a/src/contentScript/contentScript.js +++ b/src/contentScript/contentScript.js @@ -159,9 +159,11 @@ function turndownContent(content, options, article) { filter: function (node, tdopts) { if (node.nodeName == 'IMG' && node.getAttribute('src')) { let src = node.getAttribute('src'); + console.log('MarkDownload ContentScript: Found image:', src); node.setAttribute('src', validateUri(src, article.baseURI)); if (options.downloadImages) { + console.log('MarkDownload ContentScript: Download images enabled, processing image:', src); let imageFilename = getImageFilename(src, options, false); if (!imageList[src] || imageList[src] != imageFilename) { let i = 1; @@ -172,6 +174,7 @@ function turndownContent(content, options, article) { imageFilename = parts.join('.'); } imageList[src] = imageFilename; + console.log('MarkDownload ContentScript: Added image to list:', src, '->', imageFilename); } const obsidianLink = options.imageStyle.startsWith("obsidian"); @@ -289,7 +292,7 @@ function processCompleteArticle(domString, options, selection = false) { let title = textReplace(options.title, article, options.disallowedChars + '/'); title = title.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/'); - console.log('MarkDownload ContentScript: Article processing complete, title:', title, 'markdown length:', result.markdown.length); + console.log('MarkDownload ContentScript: Article processing complete, title:', title, 'markdown length:', result.markdown.length, 'images found:', Object.keys(result.imageList).length); return { markdown: result.markdown, diff --git a/src/options/options.js b/src/options/options.js index 24485e1f..eb839293 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -42,22 +42,22 @@ const saveOptions = e => { const save = () => { const spinner = document.getElementById("spinner"); spinner.style.display = "block"; - browser.storage.sync.set(options) + chrome.storage.sync.set(options) .then(() => { - browser.contextMenus.update("toggle-includeTemplate", { + chrome.contextMenus.update("toggle-includeTemplate", { checked: options.includeTemplate }); try { - browser.contextMenus.update("tabtoggle-includeTemplate", { + chrome.contextMenus.update("tabtoggle-includeTemplate", { checked: options.includeTemplate }); } catch { } - browser.contextMenus.update("toggle-downloadImages", { + chrome.contextMenus.update("toggle-downloadImages", { checked: options.downloadImages }); try { - browser.contextMenus.update("tabtoggle-downloadImages", { + chrome.contextMenus.update("tabtoggle-downloadImages", { checked: options.downloadImages }); } catch { } @@ -96,10 +96,10 @@ const setCurrentChoice = result => { // if browser doesn't support the download api (i.e. Safari) // we have to use contentLink download mode - if (!browser.downloads) { + if (!chrome.downloads) { options.downloadMode = 'contentLink'; document.querySelectorAll("[name='downloadMode']").forEach(el => el.disabled = true) - document.querySelector('#downloadMode p').innerText = "The Downloas API is unavailable in this browser." + document.querySelector('#downloadMode p').innerText = "The Downloads API is unavailable in this browser." } const downloadImages = options.downloadImages && options.downloadMode == 'downloadsApi'; @@ -149,7 +149,7 @@ const restoreOptions = () => { console.error(error); } - browser.storage.sync.get(defaultOptions).then(setCurrentChoice, onError); + chrome.storage.sync.get(defaultOptions).then(setCurrentChoice, onError); } function textareaInput(){ @@ -201,7 +201,7 @@ const inputChange = e => { let lines = ev.target.result; options = JSON.parse(lines); setCurrentChoice(options); - browser.contextMenus.removeAll() + chrome.contextMenus.removeAll() createMenus() save(); refereshElements(); @@ -214,7 +214,7 @@ const inputChange = e => { if (key == "contextMenus") { if (value) { createMenus() } - else { browser.contextMenus.removeAll() } + else { chrome.contextMenus.removeAll() } } save(); @@ -240,7 +240,7 @@ const buttonClick = (e) => { var d = new Date(); var datestring = d.getFullYear() + "-" + ("0" + (d.getMonth() + 1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2); - browser.downloads.download({ + chrome.downloads.download({ url: url, saveAs: true, filename: `MarkDownload-export-${datestring}.json` diff --git a/src/popup/popup.html b/src/popup/popup.html index 62caac63..07a21960 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -24,6 +24,7 @@ Download Download selected + Copy
diff --git a/src/popup/popup.js b/src/popup/popup.js index 6ae6a921..c19d0ad0 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -24,6 +24,7 @@ cm.on("cursorActivity", (cm) => { }); document.getElementById("download").addEventListener("click", download); document.getElementById("downloadSelection").addEventListener("click", downloadSelection); +document.getElementById("copy").addEventListener("click", copyToClipboard); const defaultOptions = { includeTemplate: false, @@ -48,7 +49,7 @@ const toggleClipSelection = options => { options.clipSelection = !options.clipSelection; document.querySelector("#selected").classList.toggle("checked"); document.querySelector("#document").classList.toggle("checked"); - browser.storage.sync.set(options).then(() => clipSite()).catch((error) => { + chrome.storage.sync.set(options).then(() => clipSite()).catch((error) => { console.error(error); }); } @@ -56,12 +57,12 @@ const toggleClipSelection = options => { const toggleIncludeTemplate = options => { options.includeTemplate = !options.includeTemplate; document.querySelector("#includeTemplate").classList.toggle("checked"); - browser.storage.sync.set(options).then(() => { - browser.contextMenus.update("toggle-includeTemplate", { + chrome.storage.sync.set(options).then(() => { + chrome.contextMenus.update("toggle-includeTemplate", { checked: options.includeTemplate }); try { - browser.contextMenus.update("tabtoggle-includeTemplate", { + chrome.contextMenus.update("tabtoggle-includeTemplate", { checked: options.includeTemplate }); } catch { } @@ -74,12 +75,12 @@ const toggleIncludeTemplate = options => { const toggleDownloadImages = options => { options.downloadImages = !options.downloadImages; document.querySelector("#downloadImages").classList.toggle("checked"); - browser.storage.sync.set(options).then(() => { - browser.contextMenus.update("toggle-downloadImages", { + chrome.storage.sync.set(options).then(() => { + chrome.contextMenus.update("toggle-downloadImages", { checked: options.downloadImages }); try { - browser.contextMenus.update("tabtoggle-downloadImages", { + chrome.contextMenus.update("tabtoggle-downloadImages", { checked: options.downloadImages }); } catch { } @@ -210,6 +211,41 @@ async function downloadSelection(e) { } } +// event handler for copy button +async function copyToClipboard(e) { + e.preventDefault(); + try { + const textToCopy = cm.getValue(); + await navigator.clipboard.writeText(textToCopy); + + // Show feedback to user + const copyButton = document.getElementById("copy"); + const originalText = copyButton.textContent; + copyButton.textContent = "Copied!"; + copyButton.style.backgroundColor = "#4CAF50"; + + // Reset button after 2 seconds + setTimeout(() => { + copyButton.textContent = originalText; + copyButton.style.backgroundColor = ""; + }, 2000); + + } catch (error) { + console.error('Failed to copy text: ', error); + + // Fallback: show error feedback + const copyButton = document.getElementById("copy"); + const originalText = copyButton.textContent; + copyButton.textContent = "Copy Failed"; + copyButton.style.backgroundColor = "#f44336"; + + setTimeout(() => { + copyButton.textContent = originalText; + copyButton.style.backgroundColor = ""; + }, 2000); + } +} + //function that handles messages from the injected script into the site function notify(message) { // message for displaying markdown diff --git a/src/shared/context-menus.js b/src/shared/context-menus.js index fd049aef..50ede958 100644 --- a/src/shared/context-menus.js +++ b/src/shared/context-menus.js @@ -2,49 +2,49 @@ async function createMenus() { const options = await getOptions(); - browser.contextMenus.removeAll(); + chrome.contextMenus.removeAll(); if (options.contextMenus) { // tab menu (chrome does not support this) try { - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "download-markdown-tab", title: "Download Tab as Markdown", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "tab-download-markdown-alltabs", title: "Download All Tabs as Markdown", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link-tab", title: "Copy Tab URL as Markdown Link", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link-all-tab", title: "Copy All Tab URLs as Markdown Link List", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link-selected-tab", title: "Copy Selected Tab URLs as Markdown Link List", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "tab-separator-1", type: "separator", contexts: ["tab"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "tabtoggle-includeTemplate", type: "checkbox", title: "Include front/back template", @@ -52,7 +52,7 @@ async function createMenus() { checked: options.includeTemplate }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "tabtoggle-downloadImages", type: "checkbox", title: "Download Images", @@ -63,73 +63,73 @@ async function createMenus() { } // add the download all tabs option to the page context menu as well - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "download-markdown-alltabs", title: "Download All Tabs as Markdown", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "separator-0", type: "separator", contexts: ["all"] }, () => { }); // download actions - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "download-markdown-selection", title: "Download Selection As Markdown", contexts: ["selection"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "download-markdown-all", title: "Download Tab As Markdown", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "separator-1", type: "separator", contexts: ["all"] }, () => { }); // copy to clipboard actions - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-selection", title: "Copy Selection As Markdown", contexts: ["selection"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-link", title: "Copy Link As Markdown", contexts: ["link"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-image", title: "Copy Image As Markdown", contexts: ["image"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-all", title: "Copy Tab As Markdown", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link", title: "Copy Tab URL as Markdown Link", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link-all", title: "Copy All Tab URLs as Markdown Link List", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-tab-as-markdown-link-selected", title: "Copy Selected Tab URLs as Markdown Link List", contexts: ["all"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "separator-2", type: "separator", contexts: ["all"] @@ -137,25 +137,25 @@ async function createMenus() { if(options.obsidianIntegration){ // copy to clipboard actions - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-obsidian", title: "Send Text selection to Obsidian", contexts: ["selection"] }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "copy-markdown-obsall", title: "Send Tab to Obsidian", contexts: ["all"] }, () => { }); } - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "separator-3", type: "separator", contexts: ["all"] }, () => { }); // options - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "toggle-includeTemplate", type: "checkbox", title: "Include front/back template", @@ -163,7 +163,7 @@ async function createMenus() { checked: options.includeTemplate }, () => { }); - browser.contextMenus.create({ + chrome.contextMenus.create({ id: "toggle-downloadImages", type: "checkbox", title: "Download Images", diff --git a/src/shared/default-options.js b/src/shared/default-options.js index 6a309b3f..b532ccbb 100644 --- a/src/shared/default-options.js +++ b/src/shared/default-options.js @@ -32,10 +32,10 @@ const defaultOptions = { async function getOptions() { let options = defaultOptions; try { - options = await browser.storage.sync.get(defaultOptions); + options = await chrome.storage.sync.get(defaultOptions); } catch (err) { console.error(err); } - if (!browser.downloads) options.downloadMode = 'contentLink'; + if (!chrome.downloads) options.downloadMode = 'contentLink'; return options; } \ No newline at end of file From 02eb5c61dd8f6c5079e55f6c7f9aef40c248e73c Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Wed, 10 Sep 2025 23:23:28 +0800 Subject: [PATCH 26/29] delete v2 code, delete tab, fix service work --- src/background/background.js | 1026 ------------------------------ src/background/service-worker.js | 65 +- src/options/options.html | 1 - src/options/options.js | 18 +- src/popup/popup.js | 30 +- src/shared/context-menus.js | 174 ----- 6 files changed, 65 insertions(+), 1249 deletions(-) delete mode 100644 src/background/background.js delete mode 100644 src/shared/context-menus.js diff --git a/src/background/background.js b/src/background/background.js deleted file mode 100644 index 32b71387..00000000 --- a/src/background/background.js +++ /dev/null @@ -1,1026 +0,0 @@ -// log some info -browser.runtime.getPlatformInfo().then(async platformInfo => { - const browserInfo = browser.runtime.getBrowserInfo ? await browser.runtime.getBrowserInfo() : "Can't get browser info" - console.info(platformInfo, browserInfo); -}); - -// add notification listener for foreground page messages -browser.runtime.onMessage.addListener(notify); -// create context menus -createMenus() - -TurndownService.prototype.defaultEscape = TurndownService.prototype.escape; - -// function to convert the article content to markdown using Turndown -function turndown(content, options, article) { - - if (options.turndownEscape) TurndownService.prototype.escape = TurndownService.prototype.defaultEscape; - else TurndownService.prototype.escape = s => s; - - var turndownService = new TurndownService(options); - - turndownService.use(turndownPluginGfm.gfm) - - turndownService.keep(['iframe', 'sub', 'sup', 'u', 'ins', 'del', 'small', 'big']); - - let imageList = {}; - // add an image rule - turndownService.addRule('images', { - filter: function (node, tdopts) { - // if we're looking at an img node with a src - if (node.nodeName == 'IMG' && node.getAttribute('src')) { - - // get the original src - let src = node.getAttribute('src') - // set the new src - node.setAttribute('src', validateUri(src, article.baseURI)); - - // if we're downloading images, there's more to do. - if (options.downloadImages) { - // generate a file name for the image - let imageFilename = getImageFilename(src, options, false); - if (!imageList[src] || imageList[src] != imageFilename) { - // if the imageList already contains this file, add a number to differentiate - let i = 1; - while (Object.values(imageList).includes(imageFilename)) { - const parts = imageFilename.split('.'); - if (i == 1) parts.splice(parts.length - 1, 0, i++); - else parts.splice(parts.length - 2, 1, i++); - imageFilename = parts.join('.'); - } - // add it to the list of images to download later - imageList[src] = imageFilename; - } - // check if we're doing an obsidian style link - const obsidianLink = options.imageStyle.startsWith("obsidian"); - // figure out the (local) src of the image - const localSrc = options.imageStyle === 'obsidian-nofolder' - // if using "nofolder" then we just need the filename, no folder - ? imageFilename.substring(imageFilename.lastIndexOf('/') + 1) - // otherwise we may need to modify the filename to uri encode parts for a pure markdown link - : imageFilename.split('/').map(s => obsidianLink ? s : encodeURI(s)).join('/') - - // set the new src attribute to be the local filename - if(options.imageStyle != 'originalSource' && options.imageStyle != 'base64') node.setAttribute('src', localSrc); - // pass the filter if we're making an obsidian link (or stripping links) - return true; - } - else return true - } - // don't pass the filter, just output a normal markdown link - return false; - }, - replacement: function (content, node, tdopts) { - // if we're stripping images, output nothing - if (options.imageStyle == 'noImage') return ''; - // if this is an obsidian link, so output that - else if (options.imageStyle.startsWith('obsidian')) return `![[${node.getAttribute('src')}]]`; - // otherwise, output the normal markdown link - else { - var alt = cleanAttribute(node.getAttribute('alt')); - var src = node.getAttribute('src') || ''; - var title = cleanAttribute(node.getAttribute('title')); - var titlePart = title ? ' "' + title + '"' : ''; - if (options.imageRefStyle == 'referenced') { - var id = this.references.length + 1; - this.references.push('[fig' + id + ']: ' + src + titlePart); - return '![' + alt + '][fig' + id + ']'; - } - else return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' - } - }, - references: [], - append: function (options) { - var references = ''; - if (this.references.length) { - references = '\n\n' + this.references.join('\n') + '\n\n'; - this.references = []; // Reset references - } - return references - } - - }); - - // add a rule for links - turndownService.addRule('links', { - filter: (node, tdopts) => { - // check that this is indeed a link - if (node.nodeName == 'A' && node.getAttribute('href')) { - // get the href - const href = node.getAttribute('href'); - // set the new href - node.setAttribute('href', validateUri(href, article.baseURI)); - // if we are to strip links, the filter needs to pass - return options.linkStyle == 'stripLinks'; - } - // we're not passing the filter, just do the normal thing. - return false; - }, - // if the filter passes, we're stripping links, so just return the content - replacement: (content, node, tdopts) => content - }); - - // handle multiple lines math - turndownService.addRule('mathjax', { - filter(node, options) { - return article.math.hasOwnProperty(node.id); - }, - replacement(content, node, options) { - const math = article.math[node.id]; - let tex = math.tex.trim().replaceAll('\xa0', ''); - - if (math.inline) { - tex = tex.replaceAll('\n', ' '); - return `$${tex}$`; - } - else - return `$$\n${tex}\n$$`; - } - }); - - function repeat(character, count) { - return Array(count + 1).join(character); - } - - function convertToFencedCodeBlock(node, options) { - node.innerHTML = node.innerHTML.replaceAll('', '
'); - const langMatch = node.id?.match(/code-lang-(.+)/); - const language = langMatch?.length > 0 ? langMatch[1] : ''; - - const code = node.innerText; - - const fenceChar = options.fence.charAt(0); - let fenceSize = 3; - const fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm'); - - let match; - while ((match = fenceInCodeRegex.exec(code))) { - if (match[0].length >= fenceSize) { - fenceSize = match[0].length + 1; - } - } - - const fence = repeat(fenceChar, fenceSize); - - return ( - '\n\n' + fence + language + '\n' + - code.replace(/\n$/, '') + - '\n' + fence + '\n\n' - ) - } - - turndownService.addRule('fencedCodeBlock', { - filter: function (node, options) { - return ( - options.codeBlockStyle === 'fenced' && - node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - ); - }, - replacement: function (content, node, options) { - return convertToFencedCodeBlock(node.firstChild, options); - } - }); - - // handle
 as code blocks
-  turndownService.addRule('pre', {
-    filter: (node, tdopts) => {
-      return node.nodeName == 'PRE'
-             && (!node.firstChild || node.firstChild.nodeName != 'CODE')
-             && !node.querySelector('img');
-    },
-    replacement: (content, node, tdopts) => {
-      return convertToFencedCodeBlock(node, tdopts);
-    }
-  });
-
-  let markdown = options.frontmatter + turndownService.turndown(content)
-      + options.backmatter;
-
-  // strip out non-printing special characters which CodeMirror displays as a red dot
-  // see: https://codemirror.net/doc/manual.html#option_specialChars
-  markdown = markdown.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, '');
-  
-  return { markdown: markdown, imageList: imageList };
-}
-
-function cleanAttribute(attribute) {
-  return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
-}
-
-function validateUri(href, baseURI) {
-  // check if the href is a valid url
-  try {
-    new URL(href);
-  }
-  catch {
-    // if it's not a valid url, that likely means we have to prepend the base uri
-    const baseUri = new URL(baseURI);
-
-    // if the href starts with '/', we need to go from the origin
-    if (href.startsWith('/')) {
-      href = baseUri.origin + href
-    }
-    // otherwise we need to go from the local folder
-    else {
-      href = baseUri.href + (baseUri.href.endsWith('/') ? '/' : '') + href
-    }
-  }
-  return href;
-}
-
-function getImageFilename(src, options, prependFilePath = true) {
-  const slashPos = src.lastIndexOf('/');
-  const queryPos = src.indexOf('?');
-  let filename = src.substring(slashPos + 1, queryPos > 0 ? queryPos : src.length);
-
-  let imagePrefix = (options.imagePrefix || '');
-
-  if (prependFilePath && options.title.includes('/')) {
-    imagePrefix = options.title.substring(0, options.title.lastIndexOf('/') + 1) + imagePrefix;
-  }
-  else if (prependFilePath) {
-    imagePrefix = options.title + (imagePrefix.startsWith('/') ? '' : '/') + imagePrefix
-  }
-  
-  if (filename.includes(';base64,')) {
-    // this is a base64 encoded image, so what are we going to do for a filename here?
-    filename = 'image.' + filename.substring(0, filename.indexOf(';'));
-  }
-  
-  let extension = filename.substring(filename.lastIndexOf('.'));
-  if (extension == filename) {
-    // there is no extension, so we need to figure one out
-    // for now, give it an 'idunno' extension and we'll process it later
-    filename = filename + '.idunno';
-  }
-
-  filename = generateValidFileName(filename, options.disallowedChars);
-
-  return imagePrefix + filename;
-}
-
-// function to replace placeholder strings with article info
-function textReplace(string, article, disallowedChars = null) {
-  for (const key in article) {
-    if (article.hasOwnProperty(key) && key != "content") {
-      let s = (article[key] || '') + '';
-      if (s && disallowedChars) s = this.generateValidFileName(s, disallowedChars);
-
-      string = string.replace(new RegExp('{' + key + '}', 'g'), s)
-        .replace(new RegExp('{' + key + ':lower}', 'g'), s.toLowerCase())
-        .replace(new RegExp('{' + key + ':upper}', 'g'), s.toUpperCase())
-        .replace(new RegExp('{' + key + ':kebab}', 'g'), s.replace(/ /g, '-').toLowerCase())
-        .replace(new RegExp('{' + key + ':mixed-kebab}', 'g'), s.replace(/ /g, '-'))
-        .replace(new RegExp('{' + key + ':snake}', 'g'), s.replace(/ /g, '_').toLowerCase())
-        .replace(new RegExp('{' + key + ':mixed_snake}', 'g'), s.replace(/ /g, '_'))
-        // For Obsidian Custom Attachment Location plugin, we need to replace spaces with hyphens, but also remove any double hyphens.
-        .replace(new RegExp('{' + key + ':obsidian-cal}', 'g'), s.replace(/ /g, '-').replace(/-{2,}/g, "-"))
-        .replace(new RegExp('{' + key + ':camel}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toLowerCase()))
-        .replace(new RegExp('{' + key + ':pascal}', 'g'), s.replace(/ ./g, (str) => str.trim().toUpperCase()).replace(/^./, (str) => str.toUpperCase()))
-    }
-  }
-
-  // replace date formats
-  const now = new Date();
-  const dateRegex = /{date:(.+?)}/g
-  const matches = string.match(dateRegex);
-  if (matches && matches.forEach) {
-    matches.forEach(match => {
-      const format = match.substring(6, match.length - 1);
-      const dateString = moment(now).format(format);
-      string = string.replaceAll(match, dateString);
-    });
-  }
-
-  // replace keywords
-  const keywordRegex = /{keywords:?(.*)?}/g
-  const keywordMatches = string.match(keywordRegex);
-  if (keywordMatches && keywordMatches.forEach) {
-    keywordMatches.forEach(match => {
-      let seperator = match.substring(10, match.length - 1)
-      try {
-        seperator = JSON.parse(JSON.stringify(seperator).replace(/\\\\/g, '\\'));
-      }
-      catch { }
-      const keywordsString = (article.keywords || []).join(seperator);
-      string = string.replace(new RegExp(match.replace(/\\/g, '\\\\'), 'g'), keywordsString);
-    })
-  }
-
-  // replace anything left in curly braces
-  const defaultRegex = /{(.*?)}/g
-  string = string.replace(defaultRegex, '')
-
-  return string;
-}
-
-// function to convert an article info object into markdown
-async function convertArticleToMarkdown(article, downloadImages = null) {
-  const options = await getOptions();
-  if (downloadImages != null) {
-    options.downloadImages = downloadImages;
-  }
-
-  // substitute front and backmatter templates if necessary
-  if (options.includeTemplate) {
-    options.frontmatter = textReplace(options.frontmatter, article) + '\n';
-    options.backmatter = '\n' + textReplace(options.backmatter, article);
-  }
-  else {
-    options.frontmatter = options.backmatter = '';
-  }
-
-  options.imagePrefix = textReplace(options.imagePrefix, article, options.disallowedChars)
-    .split('/').map(s=>generateValidFileName(s, options.disallowedChars)).join('/');
-
-  let result = turndown(article.content, options, article);
-  if (options.downloadImages && options.downloadMode == 'downloadsApi') {
-    // pre-download the images
-    result = await preDownloadImages(result.imageList, result.markdown);
-  }
-  return result;
-}
-
-// function to turn the title into a valid file name
-function generateValidFileName(title, disallowedChars = null) {
-  if (!title) return title;
-  else title = title + '';
-  // remove < > : " / \ | ? * 
-  var illegalRe = /[\/\?<>\\:\*\|":]/g;
-  // and non-breaking spaces (thanks @Licat)
-  var name = title.replace(illegalRe, "").replace(new RegExp('\u00A0', 'g'), ' ')
-      // collapse extra whitespace
-      .replace(new RegExp(/\s+/, 'g'), ' ')
-      // remove leading/trailing whitespace that can cause issues when using {pageTitle} in a download path
-      .trim();
-
-  if (disallowedChars) {
-    for (let c of disallowedChars) {
-      if (`[\\^$.|?*+()`.includes(c)) c = `\\${c}`;
-      name = name.replace(new RegExp(c, 'g'), '');
-    }
-  }
-  
-  return name;
-}
-
-async function preDownloadImages(imageList, markdown) {
-  const options = await getOptions();
-  let newImageList = {};
-  // originally, I was downloading the markdown file first, then all the images
-  // however, in some cases we need to download images *first* so we can get the
-  // proper file extension to put into the markdown.
-  // so... here we are waiting for all the downloads and replacements to complete
-  await Promise.all(Object.entries(imageList).map(([src, filename]) => new Promise((resolve, reject) => {
-        // we're doing an xhr so we can get it as a blob and determine filetype
-        // before the final save
-        const xhr = new XMLHttpRequest();
-        xhr.open('GET', src);
-        xhr.responseType = "blob";
-        xhr.onload = async function () {
-          // here's the returned blob
-          const blob = xhr.response;
-
-          if (options.imageStyle == 'base64') {
-            var reader = new FileReader();
-            reader.onloadend = function () {
-              markdown = markdown.replaceAll(src, reader.result)
-              resolve()
-            }
-            reader.readAsDataURL(blob);
-          }
-          else {
-
-            let newFilename = filename;
-            if (newFilename.endsWith('.idunno')) {
-              // replace any unknown extension with a lookup based on mime type
-              newFilename = filename.replace('.idunno', '.' + mimedb[blob.type]);
-
-              // and replace any instances of this in the markdown
-              // remember to url encode for replacement if it's not an obsidian link
-              if (!options.imageStyle.startsWith("obsidian")) {
-                markdown = markdown.replaceAll(filename.split('/').map(s => encodeURI(s)).join('/'), newFilename.split('/').map(s => encodeURI(s)).join('/'))
-              }
-              else {
-                markdown = markdown.replaceAll(filename, newFilename)
-              }
-            }
-
-            // create an object url for the blob (no point fetching it twice)
-            const blobUrl = URL.createObjectURL(blob);
-
-            // add this blob into the new image list
-            newImageList[blobUrl] = newFilename;
-
-            // resolve this promise now
-            // (the file might not be saved yet, but the blob is and replacements are complete)
-            resolve();
-          }
-        };
-        xhr.onerror = function () {
-          reject('A network error occurred attempting to download ' + src);
-        };
-        xhr.send();
-  })));
-
-  return { imageList: newImageList, markdown: markdown };
-}
-
-// function to actually download the markdown file
-async function downloadMarkdown(markdown, title, tabId, imageList = {}, mdClipsFolder = '') {
-  // get the options
-  const options = await getOptions();
-  
-  // download via the downloads API
-  if (options.downloadMode == 'downloadsApi' && browser.downloads) {
-    
-    // create the object url with markdown data as a blob
-    const url = URL.createObjectURL(new Blob([markdown], {
-      type: "text/markdown;charset=utf-8"
-    }));
-  
-    try {
-
-      if(mdClipsFolder && !mdClipsFolder.endsWith('/')) mdClipsFolder += '/';
-      // start the download
-      const id = await browser.downloads.download({
-        url: url,
-        filename: mdClipsFolder + title + ".md",
-        saveAs: options.saveAs
-      });
-
-      // add a listener for the download completion
-      browser.downloads.onChanged.addListener(downloadListener(id, url));
-
-      // download images (if enabled)
-      if (options.downloadImages) {
-        // get the relative path of the markdown file (if any) for image path
-        let destPath = mdClipsFolder + title.substring(0, title.lastIndexOf('/'));
-        if(destPath && !destPath.endsWith('/')) destPath += '/';
-        Object.entries(imageList).forEach(async ([src, filename]) => {
-          // start the download of the image
-          const imgId = await browser.downloads.download({
-            url: src,
-            // set a destination path (relative to md file)
-            filename: destPath ? destPath + filename : filename,
-            saveAs: false
-          })
-          // add a listener (so we can release the blob url)
-          browser.downloads.onChanged.addListener(downloadListener(imgId, src));
-        });
-      }
-    }
-    catch (err) {
-      console.error("Download failed", err);
-    }
-  }
-  // // download via obsidian://new uri
-  // else if (options.downloadMode == 'obsidianUri') {
-  //   try {
-  //     await ensureScripts(tabId);
-  //     let uri = 'obsidian://new?';
-  //     uri += `${options.obsidianPathType}=${encodeURIComponent(title)}`;
-  //     if (options.obsidianVault) uri += `&vault=${encodeURIComponent(options.obsidianVault)}`;
-  //     uri += `&content=${encodeURIComponent(markdown)}`;
-  //     let code = `window.location='${uri}'`;
-  //     await browser.tabs.executeScript(tabId, {code: code});
-  //   }
-  //   catch (error) {
-  //     // This could happen if the extension is not allowed to run code in
-  //     // the page, for example if the tab is a privileged page.
-  //     console.error("Failed to execute script: " + error);
-  //   };
-    
-  // }
-  // download via content link
-  else {
-    try {
-      await ensureScripts(tabId);
-      const filename = mdClipsFolder + generateValidFileName(title, options.disallowedChars) + ".md";
-      const code = `downloadMarkdown("${filename}","${base64EncodeUnicode(markdown)}");`
-      await browser.tabs.executeScript(tabId, {code: code});
-    }
-    catch (error) {
-      // This could happen if the extension is not allowed to run code in
-      // the page, for example if the tab is a privileged page.
-      console.error("Failed to execute script: " + error);
-    };
-  }
-}
-
-function downloadListener(id, url) {
-  const self = (delta) => {
-    if (delta.id === id && delta.state && delta.state.current == "complete") {
-      // detatch this listener
-      browser.downloads.onChanged.removeListener(self);
-      //release the url for the blob
-      URL.revokeObjectURL(url);
-    }
-  }
-  return self;
-}
-
-function base64EncodeUnicode(str) {
-  // Firstly, escape the string using encodeURIComponent to get the UTF-8 encoding of the characters, 
-  // Secondly, we convert the percent encodings into raw bytes, and add it to btoa() function.
-  const utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
-    return String.fromCharCode('0x' + p1);
-  });
-
-  return btoa(utf8Bytes);
-}
-
-//function that handles messages from the injected script into the site
-async function notify(message) {
-  const options = await this.getOptions();
-  // message for initial clipping of the dom
-  if (message.type == "clip") {
-    // get the article info from the passed in dom
-    const article = await getArticleFromDom(message.dom);
-
-    // if selection info was passed in (and we're to clip the selection)
-    // replace the article content
-    if (message.selection && message.clipSelection) {
-      article.content = message.selection;
-    }
-    
-    // convert the article to markdown
-    const { markdown, imageList } = await convertArticleToMarkdown(article);
-
-    // format the title
-    article.title = await formatTitle(article);
-
-    // format the mdClipsFolder
-    const mdClipsFolder = await formatMdClipsFolder(article);
-
-    // display the data in the popup
-    await browser.runtime.sendMessage({ type: "display.md", markdown: markdown, article: article, imageList: imageList, mdClipsFolder: mdClipsFolder});
-  }
-  // message for triggering download
-  else if (message.type == "download") {
-    downloadMarkdown(message.markdown, message.title, message.tab.id, message.imageList, message.mdClipsFolder);
-  }
-}
-
-browser.commands.onCommand.addListener(function (command) {
-  const tab = browser.tabs.getCurrent()
-  if (command == "download_tab_as_markdown") {
-    const info = { menuItemId: "download-markdown-all" };
-    downloadMarkdownFromContext(info, tab);
-  }
-  else if (command == "copy_tab_as_markdown") {
-    const info = { menuItemId: "copy-markdown-all" };
-    copyMarkdownFromContext(info, tab);
-  }
-  else if (command == "copy_selection_as_markdown") {
-    const info = { menuItemId: "copy-markdown-selection" };
-    copyMarkdownFromContext(info, tab);
-  }
-  else if (command == "copy_tab_as_markdown_link") {
-    copyTabAsMarkdownLink(tab);
-  }
-  else if (command == "copy_selected_tab_as_markdown_link") {
-    copySelectedTabAsMarkdownLink(tab);
-  }
-  else if (command == "copy_selection_to_obsidian") {
-    const info = { menuItemId: "copy-markdown-obsidian" };
-    copyMarkdownFromContext(info, tab);
-  }
-  else if (command == "copy_tab_to_obsidian") {
-    const info = { menuItemId: "copy-markdown-obsall" };
-    copyMarkdownFromContext(info, tab);
-  }
-});
-
-// click handler for the context menus
-browser.contextMenus.onClicked.addListener(function (info, tab) {
-  // one of the copy to clipboard commands
-  if (info.menuItemId.startsWith("copy-markdown")) {
-    copyMarkdownFromContext(info, tab);
-  }
-  else if (info.menuItemId == "download-markdown-alltabs" || info.menuItemId == "tab-download-markdown-alltabs") {
-    downloadMarkdownForAllTabs(info);
-  }
-  // one of the download commands
-  else if (info.menuItemId.startsWith("download-markdown")) {
-    downloadMarkdownFromContext(info, tab);
-  }
-  // copy tab as markdown link
-  else if (info.menuItemId.startsWith("copy-tab-as-markdown-link-all")) {
-    copyTabAsMarkdownLinkAll(tab);
-  }
-  // copy only selected tab as markdown link
-  else if (info.menuItemId.startsWith("copy-tab-as-markdown-link-selected")) {
-    copySelectedTabAsMarkdownLink(tab);
-  }
-  else if (info.menuItemId.startsWith("copy-tab-as-markdown-link")) {
-    copyTabAsMarkdownLink(tab);
-  }
-  // a settings toggle command
-  else if (info.menuItemId.startsWith("toggle-") || info.menuItemId.startsWith("tabtoggle-")) {
-    toggleSetting(info.menuItemId.split('-')[1]);
-  }
-});
-
-// this function toggles the specified option
-async function toggleSetting(setting, options = null) {
-  // if there's no options object passed in, we need to go get one
-  if (options == null) {
-      // get the options from storage and toggle the setting
-      await toggleSetting(setting, await getOptions());
-  }
-  else {
-    // toggle the option and save back to storage
-    options[setting] = !options[setting];
-    await browser.storage.sync.set(options);
-    if (setting == "includeTemplate") {
-      browser.contextMenus.update("toggle-includeTemplate", {
-        checked: options.includeTemplate
-      });
-      try {
-        browser.contextMenus.update("tabtoggle-includeTemplate", {
-          checked: options.includeTemplate
-        });
-      } catch { }
-    }
-    
-    if (setting == "downloadImages") {
-      browser.contextMenus.update("toggle-downloadImages", {
-        checked: options.downloadImages
-      });
-      try {
-        browser.contextMenus.update("tabtoggle-downloadImages", {
-          checked: options.downloadImages
-        });
-      } catch { }
-    }
-  }
-}
-
-// this function ensures the content script is loaded (and loads it if it isn't)
-async function ensureScripts(tabId) {
-  const results = await browser.tabs.executeScript(tabId, { code: "typeof getSelectionAndDom === 'function';" })
-  // The content script's last expression will be true if the function
-  // has been defined. If this is not the case, then we need to run
-  // pageScraper.js to define function getSelectionAndDom.
-  if (!results || results[0] !== true) {
-    await browser.tabs.executeScript(tabId, {file: "/contentScript/contentScript.js"});
-  }
-}
-
-// get Readability article info from the dom passed in
-async function getArticleFromDom(domString) {
-  // parse the dom
-  const parser = new DOMParser();
-  const dom = parser.parseFromString(domString, "text/html");
-
-  if (dom.documentElement.nodeName == "parsererror") {
-    console.error("error while parsing");
-  }
-
-  const math = {};
-
-  const storeMathInfo = (el, mathInfo) => {
-    let randomId = URL.createObjectURL(new Blob([]));
-    randomId = randomId.substring(randomId.length - 36);
-    el.id = randomId;
-    math[randomId] = mathInfo;
-  };
-
-  dom.body.querySelectorAll('script[id^=MathJax-Element-]')?.forEach(mathSource => {
-    const type = mathSource.attributes.type.value
-    storeMathInfo(mathSource, {
-      tex: mathSource.innerText,
-      inline: type ? !type.includes('mode=display') : false
-    });
-  });
-
-  dom.body.querySelectorAll('[markdownload-latex]')?.forEach(mathJax3Node =>  {
-    const tex = mathJax3Node.getAttribute('markdownload-latex')
-    const display = mathJax3Node.getAttribute('display')
-    const inline = !(display && display === 'true')
-
-    const mathNode = document.createElement(inline ? "i" : "p")
-    mathNode.textContent = tex;
-    mathJax3Node.parentNode.insertBefore(mathNode, mathJax3Node.nextSibling)
-    mathJax3Node.parentNode.removeChild(mathJax3Node)
-
-    storeMathInfo(mathNode, {
-      tex: tex,
-      inline: inline
-    });
-  });
-
-  dom.body.querySelectorAll('.katex-mathml')?.forEach(kaTeXNode => {
-    storeMathInfo(kaTeXNode, {
-      tex: kaTeXNode.querySelector('annotation').textContent,
-      inline: true
-    });
-  });
-
-  dom.body.querySelectorAll('[class*=highlight-text],[class*=highlight-source]')?.forEach(codeSource => {
-    const language = codeSource.className.match(/highlight-(?:text|source)-([a-z0-9]+)/)?.[1]
-    if (codeSource.firstChild.nodeName == "PRE") {
-      codeSource.firstChild.id = `code-lang-${language}`
-    }
-  });
-
-  dom.body.querySelectorAll('[class*=language-]')?.forEach(codeSource => {
-    const language = codeSource.className.match(/language-([a-z0-9]+)/)?.[1]
-    codeSource.id = `code-lang-${language}`;
-  });
-
-  dom.body.querySelectorAll('pre br')?.forEach(br => {
-    // we need to keep 
tags because they are removed by Readability.js - br.outerHTML = ''; - }); - - dom.body.querySelectorAll('.codehilite > pre')?.forEach(codeSource => { - if (codeSource.firstChild.nodeName !== 'CODE' && !codeSource.className.includes('language')) { - codeSource.id = `code-lang-text`; - } - }); - - dom.body.querySelectorAll('h1, h2, h3, h4, h5, h6')?.forEach(header => { - // Readability.js will strip out headings from the dom if certain words appear in their className - // See: https://github.com/mozilla/readability/issues/807 - header.className = ''; - header.outerHTML = header.outerHTML; - }); - - // Prevent Readability from removing the element if has a 'class' attribute - // which matches removal criteria. - // Note: The document element is guaranteed to be the HTML tag because the 'text/html' - // mime type was used when the DOM was created. - dom.documentElement.removeAttribute('class') - - // simplify the dom into an article - const article = new Readability(dom).parse(); - - // get the base uri from the dom and attach it as important article info - article.baseURI = dom.baseURI; - // also grab the page title - article.pageTitle = dom.title; - // and some URL info - const url = new URL(dom.baseURI); - article.hash = url.hash; - article.host = url.host; - article.origin = url.origin; - article.hostname = url.hostname; - article.pathname = url.pathname; - article.port = url.port; - article.protocol = url.protocol; - article.search = url.search; - - - // make sure the dom has a head - if (dom.head) { - // and the keywords, should they exist, as an array - article.keywords = dom.head.querySelector('meta[name="keywords"]')?.content?.split(',')?.map(s => s.trim()); - - // add all meta tags, so users can do whatever they want - dom.head.querySelectorAll('meta[name][content], meta[property][content]')?.forEach(meta => { - const key = (meta.getAttribute('name') || meta.getAttribute('property')) - const val = meta.getAttribute('content') - if (key && val && !article[key]) { - article[key] = val; - } - }) - } - - article.math = math - - // return the article - return article; -} - -// get Readability article info from the content of the tab id passed in -// `selection` is a bool indicating whether we should just get the selected text -async function getArticleFromContent(tabId, selection = false) { - // run the content script function to get the details - const results = await browser.tabs.executeScript(tabId, { code: "getSelectionAndDom()" }); - - // make sure we actually got a valid result - if (results && results[0] && results[0].dom) { - const article = await getArticleFromDom(results[0].dom, selection); - - // if we're to grab the selection, and we've selected something, - // replace the article content with the selection - if (selection && results[0].selection) { - article.content = results[0].selection; - } - - //return the article - return article; - } - else return null; -} - -// function to apply the title template -async function formatTitle(article) { - let options = await getOptions(); - - let title = textReplace(options.title, article, options.disallowedChars + '/'); - title = title.split('/').map(s=>generateValidFileName(s, options.disallowedChars)).join('/'); - return title; -} - -async function formatMdClipsFolder(article) { - let options = await getOptions(); - - let mdClipsFolder = ''; - if (options.mdClipsFolder && options.downloadMode == 'downloadsApi') { - mdClipsFolder = textReplace(options.mdClipsFolder, article, options.disallowedChars); - mdClipsFolder = mdClipsFolder.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/'); - if (!mdClipsFolder.endsWith('/')) mdClipsFolder += '/'; - } - - return mdClipsFolder; -} - -async function formatObsidianFolder(article) { - let options = await getOptions(); - - let obsidianFolder = ''; - if (options.obsidianFolder) { - obsidianFolder = textReplace(options.obsidianFolder, article, options.disallowedChars); - obsidianFolder = obsidianFolder.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/'); - if (!obsidianFolder.endsWith('/')) obsidianFolder += '/'; - } - - return obsidianFolder; -} - -// function to download markdown, triggered by context menu -async function downloadMarkdownFromContext(info, tab) { - await ensureScripts(tab.id); - const article = await getArticleFromContent(tab.id, info.menuItemId == "download-markdown-selection"); - const title = await formatTitle(article); - const { markdown, imageList } = await convertArticleToMarkdown(article); - // format the mdClipsFolder - const mdClipsFolder = await formatMdClipsFolder(article); - await downloadMarkdown(markdown, title, tab.id, imageList, mdClipsFolder); - -} - -// function to copy a tab url as a markdown link -async function copyTabAsMarkdownLink(tab) { - try { - await ensureScripts(tab.id); - const article = await getArticleFromContent(tab.id); - const title = await formatTitle(article); - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard("[${title}](${article.baseURI})")` }); - // await navigator.clipboard.writeText(`[${title}](${article.baseURI})`); - } - catch (error) { - // This could happen if the extension is not allowed to run code in - // the page, for example if the tab is a privileged page. - console.error("Failed to copy as markdown link: " + error); - }; -} - -// function to copy all tabs as markdown links -async function copyTabAsMarkdownLinkAll(tab) { - try { - const options = await getOptions(); - options.frontmatter = options.backmatter = ''; - const tabs = await browser.tabs.query({ - currentWindow: true - }); - - const links = []; - for(const tab of tabs) { - await ensureScripts(tab.id); - const article = await getArticleFromContent(tab.id); - const title = await formatTitle(article); - const link = `${options.bulletListMarker} [${title}](${article.baseURI})` - links.push(link) - }; - - const markdown = links.join(`\n`) - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard(${JSON.stringify(markdown)})` }); - - } - catch (error) { - // This could happen if the extension is not allowed to run code in - // the page, for example if the tab is a privileged page. - console.error("Failed to copy as markdown link: " + error); - }; -} - -// function to copy only selected tabs as markdown links -async function copySelectedTabAsMarkdownLink(tab) { - try { - const options = await getOptions(); - options.frontmatter = options.backmatter = ''; - const tabs = await browser.tabs.query({ - currentWindow: true, - highlighted: true - }); - - const links = []; - for (const tab of tabs) { - await ensureScripts(tab.id); - const article = await getArticleFromContent(tab.id); - const title = await formatTitle(article); - const link = `${options.bulletListMarker} [${title}](${article.baseURI})` - links.push(link) - }; - - const markdown = links.join(`\n`) - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard(${JSON.stringify(markdown)})` }); - - } - catch (error) { - // This could happen if the extension is not allowed to run code in - // the page, for example if the tab is a privileged page. - console.error("Failed to copy as markdown link: " + error); - }; -} - -// function to copy markdown to the clipboard, triggered by context menu -async function copyMarkdownFromContext(info, tab) { - try{ - await ensureScripts(tab.id); - - const platformOS = navigator.platform; - var folderSeparator = ""; - if(platformOS.indexOf("Win") === 0){ - folderSeparator = "\\"; - }else{ - folderSeparator = "/"; - } - - if (info.menuItemId == "copy-markdown-link") { - const options = await getOptions(); - options.frontmatter = options.backmatter = ''; - const article = await getArticleFromContent(tab.id, false); - const { markdown } = turndown(`${info.linkText || info.selectionText}`, { ...options, downloadImages: false }, article); - await browser.tabs.executeScript(tab.id, {code: `copyToClipboard(${JSON.stringify(markdown)})`}); - } - else if (info.menuItemId == "copy-markdown-image") { - await browser.tabs.executeScript(tab.id, {code: `copyToClipboard("![](${info.srcUrl})")`}); - } - else if(info.menuItemId == "copy-markdown-obsidian") { - const article = await getArticleFromContent(tab.id, info.menuItemId == "copy-markdown-obsidian"); - const title = await formatTitle(article); - const options = await getOptions(); - const obsidianVault = options.obsidianVault; - const obsidianFolder = await formatObsidianFolder(article); - const { markdown } = await convertArticleToMarkdown(article, downloadImages = false); - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard(${JSON.stringify(markdown)})` }); - await chrome.tabs.update({url: "obsidian://advanced-uri?vault=" + obsidianVault + "&clipboard=true&mode=new&filepath=" + obsidianFolder + generateValidFileName(title)}); - } - else if(info.menuItemId == "copy-markdown-obsall") { - const article = await getArticleFromContent(tab.id, info.menuItemId == "copy-markdown-obsall"); - const title = await formatTitle(article); - const options = await getOptions(); - const obsidianVault = options.obsidianVault; - const obsidianFolder = await formatObsidianFolder(article); - const { markdown } = await convertArticleToMarkdown(article, downloadImages = false); - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard(${JSON.stringify(markdown)})` }); - await browser.tabs.update({url: "obsidian://advanced-uri?vault=" + obsidianVault + "&clipboard=true&mode=new&filepath=" + obsidianFolder + generateValidFileName(title)}); - } - else { - const article = await getArticleFromContent(tab.id, info.menuItemId == "copy-markdown-selection"); - const { markdown } = await convertArticleToMarkdown(article, downloadImages = false); - await browser.tabs.executeScript(tab.id, { code: `copyToClipboard(${JSON.stringify(markdown)})` }); - } - } - catch (error) { - // This could happen if the extension is not allowed to run code in - // the page, for example if the tab is a privileged page. - console.error("Failed to copy text: " + error); - }; -} - -async function downloadMarkdownForAllTabs(info) { - const tabs = await browser.tabs.query({ - currentWindow: true - }); - tabs.forEach(tab => { - downloadMarkdownFromContext(info, tab); - }); -} - -/** - * String.prototype.replaceAll() polyfill - * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ - * @author Chris Ferdinandi - * @license MIT - */ -if (!String.prototype.replaceAll) { - String.prototype.replaceAll = function(str, newStr){ - - // If a regex pattern - if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') { - return this.replace(str, newStr); - } - - // If a string - return this.replace(new RegExp(str, 'g'), newStr); - - }; -} diff --git a/src/background/service-worker.js b/src/background/service-worker.js index ee6afd36..07e425e4 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -220,7 +220,8 @@ async function createMenus() { } if (options.contextMenus) { - const menuItems = [ + // Page context menus + const pageMenuItems = [ { id: "download-markdown-alltabs", title: "Download All Tabs as Markdown", @@ -272,28 +273,20 @@ async function createMenus() { contexts: ["all"] }, { - id: "separator-2", - type: "separator", + id: "copy-tab-as-markdown-link-all", + title: "Copy All Tab URLs as Markdown Link List", contexts: ["all"] }, { - id: "toggle-includeTemplate", - type: "checkbox", - title: "Include front/back template", - contexts: ["all"], - checked: options.includeTemplate - }, - { - id: "toggle-downloadImages", - type: "checkbox", - title: "Download Images", - contexts: ["all"], - checked: options.downloadImages + id: "copy-tab-as-markdown-link-selected", + title: "Copy Selected Tab URLs as Markdown Link List", + contexts: ["all"] } ]; + // Add Obsidian integration menus if enabled if (options.obsidianIntegration) { - menuItems.splice(-2, 0, + pageMenuItems.push( { id: "copy-markdown-obsidian", title: "Send Text selection to Obsidian", @@ -303,23 +296,43 @@ async function createMenus() { id: "copy-markdown-obsall", title: "Send Tab to Obsidian", contexts: ["all"] - }, - { - id: "separator-3", - type: "separator", - contexts: ["all"] } ); } - for (const item of menuItems) { + // Add separator and toggle options + pageMenuItems.push( + { + id: "separator-2", + type: "separator", + contexts: ["all"] + }, + { + id: "toggle-includeTemplate", + type: "checkbox", + title: "Include front/back template", + contexts: ["all"], + checked: options.includeTemplate + }, + { + id: "toggle-downloadImages", + type: "checkbox", + title: "Download Images", + contexts: ["all"], + checked: options.downloadImages + } + ); + + // Create page context menus + for (const item of pageMenuItems) { try { await chrome.contextMenus.create(item); - console.log('MarkDownload: Created menu item:', item.id, '- Title:', item.title); + console.log('MarkDownload: Created page menu item:', item.id, '- Title:', item.title); } catch (e) { - console.warn('MarkDownload: Failed to create menu item:', item.id, e); + console.warn('MarkDownload: Failed to create page menu item:', item.id, e); } } + console.log('MarkDownload: Context menus created successfully'); // Test: List all created menus for debugging @@ -394,6 +407,10 @@ async function handleMessage(message, sender, sendResponse) { const tabId = message.tabId || sender.tab?.id; await downloadMarkdown(message.markdown, message.title, tabId, message.imageList, message.mdClipsFolder); sendResponse({ success: true }); + } else if (message.type === "recreateMenus") { + console.log('MarkDownload: Recreating context menus'); + await createMenus(); + sendResponse({ success: true }); } else { sendResponse({ error: 'Unknown message type: ' + message.type }); } diff --git a/src/options/options.html b/src/options/options.html index c7dae766..c767b863 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -377,7 +377,6 @@

Import / Export

- diff --git a/src/options/options.js b/src/options/options.js index eb839293..ab342d9b 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -47,20 +47,10 @@ const save = () => { chrome.contextMenus.update("toggle-includeTemplate", { checked: options.includeTemplate }); - try { - chrome.contextMenus.update("tabtoggle-includeTemplate", { - checked: options.includeTemplate - }); - } catch { } chrome.contextMenus.update("toggle-downloadImages", { checked: options.downloadImages }); - try { - chrome.contextMenus.update("tabtoggle-downloadImages", { - checked: options.downloadImages - }); - } catch { } }) .then(() => { document.querySelectorAll(".status").forEach(statusEl => { @@ -202,7 +192,8 @@ const inputChange = e => { options = JSON.parse(lines); setCurrentChoice(options); chrome.contextMenus.removeAll() - createMenus() + // Notify service worker to recreate menus + chrome.runtime.sendMessage({ type: "recreateMenus" }).catch(() => {}); save(); refereshElements(); }; @@ -213,7 +204,10 @@ const inputChange = e => { options[key] = value; if (key == "contextMenus") { - if (value) { createMenus() } + if (value) { + // Notify service worker to recreate menus + chrome.runtime.sendMessage({ type: "recreateMenus" }).catch(() => {}); + } else { chrome.contextMenus.removeAll() } } diff --git a/src/popup/popup.js b/src/popup/popup.js index c19d0ad0..aac5396b 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -49,7 +49,16 @@ const toggleClipSelection = options => { options.clipSelection = !options.clipSelection; document.querySelector("#selected").classList.toggle("checked"); document.querySelector("#document").classList.toggle("checked"); - chrome.storage.sync.set(options).then(() => clipSite()).catch((error) => { + chrome.storage.sync.set(options).then(() => { + // Get current tab ID and call clipSite + chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { + if (tabs[0]) { + clipSite(tabs[0].id); + } + }).catch(error => { + console.error('Failed to get tab ID:', error); + }); + }).catch((error) => { console.error(error); }); } @@ -61,12 +70,14 @@ const toggleIncludeTemplate = options => { chrome.contextMenus.update("toggle-includeTemplate", { checked: options.includeTemplate }); - try { - chrome.contextMenus.update("tabtoggle-includeTemplate", { - checked: options.includeTemplate - }); - } catch { } - return clipSite() + // Get current tab ID and call clipSite + chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { + if (tabs[0]) { + clipSite(tabs[0].id); + } + }).catch(error => { + console.error('Failed to get tab ID:', error); + }); }).catch((error) => { console.error(error); }); @@ -79,11 +90,6 @@ const toggleDownloadImages = options => { chrome.contextMenus.update("toggle-downloadImages", { checked: options.downloadImages }); - try { - chrome.contextMenus.update("tabtoggle-downloadImages", { - checked: options.downloadImages - }); - } catch { } }).catch((error) => { console.error(error); }); diff --git a/src/shared/context-menus.js b/src/shared/context-menus.js deleted file mode 100644 index 50ede958..00000000 --- a/src/shared/context-menus.js +++ /dev/null @@ -1,174 +0,0 @@ -// create the context menus -async function createMenus() { - const options = await getOptions(); - - chrome.contextMenus.removeAll(); - - if (options.contextMenus) { - - // tab menu (chrome does not support this) - try { - chrome.contextMenus.create({ - id: "download-markdown-tab", - title: "Download Tab as Markdown", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "tab-download-markdown-alltabs", - title: "Download All Tabs as Markdown", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link-tab", - title: "Copy Tab URL as Markdown Link", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link-all-tab", - title: "Copy All Tab URLs as Markdown Link List", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link-selected-tab", - title: "Copy Selected Tab URLs as Markdown Link List", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "tab-separator-1", - type: "separator", - contexts: ["tab"] - }, () => { }); - - chrome.contextMenus.create({ - id: "tabtoggle-includeTemplate", - type: "checkbox", - title: "Include front/back template", - contexts: ["tab"], - checked: options.includeTemplate - }, () => { }); - - chrome.contextMenus.create({ - id: "tabtoggle-downloadImages", - type: "checkbox", - title: "Download Images", - contexts: ["tab"], - checked: options.downloadImages - }, () => { }); - } catch { - - } - // add the download all tabs option to the page context menu as well - chrome.contextMenus.create({ - id: "download-markdown-alltabs", - title: "Download All Tabs as Markdown", - contexts: ["all"] - }, () => { }); - chrome.contextMenus.create({ - id: "separator-0", - type: "separator", - contexts: ["all"] - }, () => { }); - - // download actions - chrome.contextMenus.create({ - id: "download-markdown-selection", - title: "Download Selection As Markdown", - contexts: ["selection"] - }, () => { }); - chrome.contextMenus.create({ - id: "download-markdown-all", - title: "Download Tab As Markdown", - contexts: ["all"] - }, () => { }); - - chrome.contextMenus.create({ - id: "separator-1", - type: "separator", - contexts: ["all"] - }, () => { }); - - // copy to clipboard actions - chrome.contextMenus.create({ - id: "copy-markdown-selection", - title: "Copy Selection As Markdown", - contexts: ["selection"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-markdown-link", - title: "Copy Link As Markdown", - contexts: ["link"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-markdown-image", - title: "Copy Image As Markdown", - contexts: ["image"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-markdown-all", - title: "Copy Tab As Markdown", - contexts: ["all"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link", - title: "Copy Tab URL as Markdown Link", - contexts: ["all"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link-all", - title: "Copy All Tab URLs as Markdown Link List", - contexts: ["all"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-tab-as-markdown-link-selected", - title: "Copy Selected Tab URLs as Markdown Link List", - contexts: ["all"] - }, () => { }); - - chrome.contextMenus.create({ - id: "separator-2", - type: "separator", - contexts: ["all"] - }, () => { }); - - if(options.obsidianIntegration){ - // copy to clipboard actions - chrome.contextMenus.create({ - id: "copy-markdown-obsidian", - title: "Send Text selection to Obsidian", - contexts: ["selection"] - }, () => { }); - chrome.contextMenus.create({ - id: "copy-markdown-obsall", - title: "Send Tab to Obsidian", - contexts: ["all"] - }, () => { }); - } - chrome.contextMenus.create({ - id: "separator-3", - type: "separator", - contexts: ["all"] - }, () => { }); - - // options - chrome.contextMenus.create({ - id: "toggle-includeTemplate", - type: "checkbox", - title: "Include front/back template", - contexts: ["all"], - checked: options.includeTemplate - }, () => { }); - - chrome.contextMenus.create({ - id: "toggle-downloadImages", - type: "checkbox", - title: "Download Images", - contexts: ["all"], - checked: options.downloadImages - }, () => { }); - } -} \ No newline at end of file From 400ebb11ff514ea0d0ff8dfaa1fae3ef99d0455e Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Thu, 11 Sep 2025 18:39:09 +0800 Subject: [PATCH 27/29] optimize popup --- src/background/service-worker.js | 43 ++ src/options/options.html | 2 +- src/popup/popup.css | 1009 ++++++++++++++++++++++++++---- src/popup/popup.html | 143 ++++- src/popup/popup.js | 697 ++++++++++++++++++--- 5 files changed, 1658 insertions(+), 236 deletions(-) diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 07e425e4..4b32bc04 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -411,6 +411,49 @@ async function handleMessage(message, sender, sendResponse) { console.log('MarkDownload: Recreating context menus'); await createMenus(); sendResponse({ success: true }); + } else if (message.type === "sendToObsidian") { + try { + console.log('MarkDownload: Received sendToObsidian request from popup'); + const tabId = message.tabId || sender.tab?.id; + if (!tabId) { + sendResponse({ error: 'No active tab' }); + return; + } + // Ensure content script and copy markdown to clipboard + await ensureContentScript(tabId); + await executeScript(tabId, (markdown) => copyToClipboard(markdown), [message.markdown]); + // Build Obsidian URI using options supplied or stored + const options = message.options || await getOptions(); + if (!options.obsidianIntegration) { + sendResponse({ error: 'Obsidian integration not enabled' }); + return; + } + const folder = options.obsidianFolder || ''; + const vault = options.obsidianVault || ''; + let obsidianFolder = ''; + if (folder) { + const article = { title: message.title || 'Untitled', baseURI: '' }; + obsidianFolder = textReplace(folder, article, options.disallowedChars); + obsidianFolder = obsidianFolder.split('/').map(s => generateValidFileName(s, options.disallowedChars)).join('/'); + if (!obsidianFolder.endsWith('/')) obsidianFolder += '/'; + } + const filename = generateValidFileName(message.title || 'Untitled', options.disallowedChars); + const filepath = obsidianFolder + filename; + const obsidianUri = 'obsidian://advanced-uri?vault=' + encodeURIComponent(vault) + '&clipboard=true&mode=new&filepath=' + encodeURIComponent(filepath); + try { + await chrome.tabs.update(tabId, { url: obsidianUri }); + } catch (err) { + try { + await chrome.tabs.create({ url: obsidianUri }); + } catch (err2) { + console.error('MarkDownload: Failed to open Obsidian URI:', err2); + } + } + sendResponse({ success: true }); + } catch (err) { + console.error('MarkDownload: sendToObsidian handler error:', err); + sendResponse({ error: err?.message || String(err) }); + } } else { sendResponse({ error: 'Unknown message type: ' + message.type }); } diff --git a/src/options/options.html b/src/options/options.html index c767b863..f3bec89e 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -163,7 +163,7 @@

Obsidian Path Type

- +

diff --git a/src/popup/popup.css b/src/popup/popup.css index 2d7ef0c7..ee45f02f 100644 --- a/src/popup/popup.css +++ b/src/popup/popup.css @@ -1,207 +1,998 @@ :root { - --focus: orange; - --bg: #fff;/*#0A001F;*/ - --fg: #000; - --border: #eee; + /* Color Palette */ --primary: #4A90E2; - --fg-primary: #eee; - --bg-primary: #ECF5FF; - --border-primary: #4A90E2; - --font-size: 14px; + --primary-hover: #357ABD; + --primary-light: #ECF5FF; + --secondary: #6C757D; + --success: #28A745; + --warning: #FFC107; + --danger: #DC3545; + + /* Light Theme */ + --bg: #FFFFFF; + --bg-secondary: #F8F9FA; + --bg-tertiary: #E9ECEF; + --fg: #212529; + --fg-secondary: #6C757D; + --fg-muted: #ADB5BD; + --border: #DEE2E6; + --border-light: #E9ECEF; + --shadow: rgba(0, 0, 0, 0.1); + --shadow-hover: rgba(0, 0, 0, 0.15); + + /* Typography */ + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + --font-size-xs: 11px; + --font-size-sm: 12px; + --font-size-base: 14px; + --font-size-lg: 16px; + --font-size-xl: 18px; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-bold: 600; + + /* Spacing */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 12px; + --spacing-lg: 16px; + --spacing-xl: 20px; + --spacing-xxl: 24px; + + /* Border Radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + + /* Transitions */ + --transition-fast: 0.15s ease; + --transition-normal: 0.3s ease; + --transition-slow: 0.5s ease; } -@media(prefers-color-scheme: dark) { +/* Dark Theme */ +@media (prefers-color-scheme: dark) { :root { - --focus: orange; - --bg: #2A2A2E; - --fg: #f8f8f8; - --border: #555; - --primary: #4A90E2; - --fg-primary: #eee; - --bg-primary: hsl(212, 80%, 30%); - --border-primary: #4A90E2; - --pre-bg: #474749; + --bg: #1A1D23; + --bg-secondary: #2D3139; + --bg-tertiary: #3A3F4A; + --fg: #FFFFFF; + --fg-secondary: #B8BCC5; + --fg-muted: #8B949E; + --border: #404956; + --border-light: #404956; + --shadow: rgba(0, 0, 0, 0.3); + --shadow-hover: rgba(0, 0, 0, 0.4); + --primary-light: rgba(74, 144, 226, 0.1); } + .cm-s-xq-dark.CodeMirror { - background-color: var(--bg); + background-color: var(--bg-secondary); + border-color: var(--border); } } -/* Works on Firefox */ +/* Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +*:focus { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* Scrollbar Styles */ * { scrollbar-width: thin; - scrollbar-color: var(--pre-bg) var(--bg); + scrollbar-color: var(--border) transparent; } -/* Works on Chrome, Edge, and Safari */ *::-webkit-scrollbar { - width: 12px; + width: 6px; + height: 6px; } *::-webkit-scrollbar-track { - background: var(--bg); + background: transparent; } *::-webkit-scrollbar-thumb { background-color: var(--border); border-radius: 20px; + border: none; } -* { - margin: 0; - padding: 0; - box-sizing: border-box; +*::-webkit-scrollbar-thumb:hover { + background-color: var(--fg-muted); } +/* Body */ body { - width: 350px; - height: 500px; - border-radius: 0.5em; + width: 500px; + height: 650px; + font-family: var(--font-family); + font-size: var(--font-size-base); + background: var(--bg); + color: var(--fg); overflow: hidden; - font-family: sans-serif; + position: relative; + margin: 0; + padding: 0; +} + +/* Loading Overlay */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; background: var(--bg); - font-size: var(--font-size); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); } -@keyframes spinner { - to { - transform: rotate(360deg); - } +.spinner-container { + text-align: center; } -* { - box-sizing: border-box; + +.spinner { + width: 32px; + height: 32px; + border: 3px solid var(--border-light); + border-top: 3px solid var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto var(--spacing-md); } -*:focus { - border: 3px dotted var(--focus-color) !important; +.spinner-container p { + color: var(--fg-secondary); + font-size: var(--font-size-sm); + margin: 0; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Main Container */ +.main-container { + display: none; + flex-direction: column; + height: 100vh; + max-height: 650px; + overflow: hidden; + position: relative; +} + +/* Header */ +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md) var(--spacing-lg) var(--spacing-sm); + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%); + color: white; + position: relative; + overflow: hidden; + flex: 0 0 auto; /* fixed height section */ } -#spinner:before { +.header::before { content: ''; position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - margin-top: -10px; - margin-left: -10px; - border-radius: 50%; - border-top: 2px solid var(--primary); - border-right: 2px solid transparent; - animation: spinner .6s linear infinite; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); + animation: headerGlow 8s ease-in-out infinite alternate; } -.CodeMirror { - border: none; - padding: 0.5em; - flex: 1; +.header::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.3) 50%, transparent 100%); + z-index: 1; } -input#title { - padding: 0.5em; - color: var(--fg); - background-color: var(--bg); - border: 1px solid var(--border); - font-size: 1.01em; +@keyframes headerGlow { + 0% { + transform: translate(-10%, -10%) rotate(0deg); + opacity: 0.5; + } + 100% { + transform: translate(10%, 10%) rotate(5deg); + opacity: 0.8; + } } +.logo { + position: relative; + z-index: 2; +} + +.header-actions { + position: relative; + z-index: 2; +} + +.logo h1 { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-bold); + margin: 0; + letter-spacing: -0.5px; +} + +.logo .subtitle { + font-size: var(--font-size-xs); + opacity: 0.8; + font-weight: var(--font-weight-normal); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.header-actions { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.icon-button { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-md); + color: white; + text-decoration: none; + transition: var(--transition-fast); +} + +.icon-button:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +/* --- Legacy structure compatibility (anchor-based UI) --- */ +/* Container + spinner for old markup */ #container { width: 100%; height: 100%; display: none; flex-direction: column; } + +/* Removed duplicate spinner - use .loading-overlay .spinner instead */ + .row { + display: flex; flex-direction: row; - display:flex; } +/* Anchor buttons */ a.button { display: block; - padding: 0.5em 0; + padding: 0.5em 0.75em; background-color: var(--primary); - color: var(--fg-primary); + color: #fff; text-align: center; text-decoration: none; - line-height: 1em; - border: 1px solid var(--bg); - border-collapse: collapse; + line-height: 1.2em; + border: 1px solid var(--primary); + border-radius: var(--radius-md); + margin: 6px 8px 0; + transition: var(--transition-fast); +} + +a.button:hover { + background-color: var(--primary-hover); + border-color: var(--primary-hover); } +/* Toggle anchors */ +.button.toggle { + position: relative; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + box-shadow: 0 2px 4px var(--shadow); +} + +.button.toggle:hover { + box-shadow: 0 4px 8px var(--shadow-hover); +} + +.button.toggle.checked { + background: var(--primary-light); + border-color: var(--primary); +} + +.button.toggle.active { + background: var(--primary-light); + border-color: var(--primary); +} + +/* Leading check dot inside toggle anchors */ +.check { + background: var(--border-light); + border-radius: 50%; + content: ''; + height: 18px; + left: 10px; + position: absolute; + top: calc(50% - 9px); + width: 18px; +} + +.checked .check { + background-color: var(--primary); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIyLjAyOTY4IC00MC4wOTAzIDI2IDIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yNy45NzQzLC0zNi4xMjcyYzAsMC40NDY0MjggLTAuMTU2MjUsMC44MjU4OTMgLTAuNDY4NzUsMS4xMzgzOWwtMTIuMTIwNSwxMi4xMjA1bC0yLjI3Njc5LDIuMjc2NzljLTAuMzEyNSwwLjMxMjUgLTAuNjkxOTY0LDAuNDY4NzUgLTEuMTM4MzksMC40Njg3NWMtMC40NDY0MjgsMCAtMC44MjU4OTMsLTAuMTU2MjUgLTEuMTM4MzksLTAuNDY4NzVsLTIuMjc2NzksLTIuMjc2NzlMLTYuMDYwMjcsLTYuMDYwMjdjLTAuMzEyNSwtMC4zMTI1IC0wLjQ2ODc1LC0wLjY5MTk2NSAtMC40Njg3NSwtMS4xMzgzOWMwLC0wLjQ0NjQyOSAwLjE1NjI1LC0wLjgyNTg5MyAwLjQ2ODc1LC0xLjEzODM5bDIuMjc2NzksLTIuMjc2NzlDMTAuMzEyNSwtNDIuMzEyNSAxMC42ODc5LC00Mi40Njg3NSAxMS4xMzgzOSwtNDIuNDY4NzVjMC40NDY0MjksMCAwLjgyNTg5MywwLjE1NjI1IDEuMTM4MzksMC40Njg3NWw0LjkyMTg4LDQuOTM4NjJsMTAuOTgyMSwtMTAuOTk4OWMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI4LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsMi4yNzY3OCwyLjI3Njc5YzAuMzEyNSwwLjMxMjUgMC40Njg3NSwwLjY5MTk2NCAwLjQ2ODc1LDEuMTM4MzlaIiBmaWxsPSIjZmZmIi8+PC9zdmc+'); + background-repeat: no-repeat; + background-position: center; + background-size: 12px; +} + +.active .check { + background-color: var(--primary); + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIyLjAyOTY4IC00MC4wOTAzIDI2IDIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yNy45NzQzLC0zNi4xMjcyYzAsMC40NDY0MjggLTAuMTU2MjUsMC44MjU4OTMgLTAuNDY4NzUsMS4xMzgzOWwtMTIuMTIwNSwxMi4xMjA1bC0yLjI3Njc5LDIuMjc2NzljLTAuMzEyNSwwLjMxMjUgLTAuNjkxOTY0LDAuNDY4NzUgLTEuMTM4MzksMC40Njg3NWMtMC40NDY0MjgsMCAtMC44MjU4OTMsLTAuMTU2MjUgLTEuMTM4MzksLTAuNDY4NzVsLTIuMjc2NzksLTIuMjc2NzlMLTYuMDYwMjcsLTYuMDYwMjdjLTAuMzEyNSwtMC4zMTI1IC0wLjQ2ODc1LC0wLjY5MTk2NSAtMC40Njg3NSwtMS4xMzgzOWMwLC0wLjQ0NjQyOSAwLjE1NjI1LC0wLjgyNTg5MyAwLjQ2ODc1LC0xLjEzODM5bDIuMjc2NzksLTIuMjc2NzlDMTAuMzEyNSwtNDIuMzEyNSAxMC42ODc5LC00Mi40Njg3NSAxMS4xMzgzOSwtNDIuNDY4NzVjMC40NDY0MjksMCAwLjgyNTg5MywwLjE1NjI1IDEuMTM4MzksMC40Njg3NWw0LjkyMTg4LDQuOTM4NjJsMTAuOTgyMSwtMTAuOTk4OWMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI4LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsMi4yNzY3OCwyLjI3Njc5YzAuMzEyNSwwLjMxMjUgMC40Njg3NSwwLjY5MTk2NCAwLjQ2ODc1LDEuMTM4MzlaIiBmaWxsPSIjZmZmIi8+PC9zdmc+'); + background-repeat: no-repeat; + background-position: center; + background-size: 12px; +} + +/* Layout details for legacy */ +#selected, #document { flex: 1; } +#clipOption { display: none; } +#downloadSelection { display: none; background-color: var(--primary-light); color: var(--fg); border-color: var(--primary); } + +/* Gear options button in legacy UI */ #options { display: block; position: absolute; - top:2px; - right:2px; + top: 6px; + right: 6px; font-size: 0; text-decoration: none; background-color: var(--primary); - color: var(--fg-primary); - border-radius: 2rem; + color: #fff; + border-radius: 1rem; width: 2rem; height: 2rem; text-align: center; line-height: 2rem; - font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; } -#options::before { - content: '⚙'; - font-size: 1rem; +#options::before { content: '⚙'; font-size: 1rem; } + +/* Title input in legacy UI */ +input#title { + margin: 8px; + padding: 0.5em; + color: var(--fg); + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: 1.01em; +} + +/* Quick Settings */ +.quick-settings { + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + padding: var(--spacing-sm) var(--spacing-md); + flex: 0 0 auto; /* fixed height section */ +} + +.quick-settings h3 { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + margin-bottom: var(--spacing-sm); + color: var(--fg); +} + +.settings-grid { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.setting-item { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.setting-item label:first-child { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--fg); +} + +/* Toggle Switch */ +.toggle-switch { + display: flex; + align-items: center; + gap: var(--spacing-md); + cursor: pointer; + user-select: none; } -#downloadSelection { - background-color: var(--bg-primary); +.toggle-switch input[type="checkbox"] { display: none; } -#selected, #document { - flex:1; +.slider { + position: relative; + width: 44px; + height: 24px; + background: var(--border); + border-radius: 12px; + transition: var(--transition-normal); + flex-shrink: 0; } -#clipOption { - display:none; +.slider::before { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + transition: var(--transition-normal); + box-shadow: 0 2px 4px var(--shadow); } +.toggle-switch input:checked + .slider { + background: var(--primary); +} +.toggle-switch input:checked + .slider::before { + transform: translateX(20px); +} -.button.toggle { +.toggle-switch .label { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-normal); + color: var(--fg-secondary); +} + +/* Button Group */ +.button-group { + display: flex; + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.toggle-button { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + background: var(--bg); + border: none; + border-right: 1px solid var(--border); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--fg-secondary); + cursor: pointer; + transition: var(--transition-fast); +} + +.toggle-button:last-child { + border-right: none; +} + +.toggle-button:hover { + background: var(--bg-tertiary); +} + +.toggle-button.active { + background: var(--primary); + color: white; +} + +.toggle-button svg { + flex-shrink: 0; +} + +/* Format Preview Styles */ +.format-preview { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 10px; + line-height: 1.2; + text-align: center; + white-space: nowrap; + overflow: hidden; +} + +/* Three-button groups */ +.button-group .toggle-button:first-child:nth-last-child(3), +.button-group .toggle-button:first-child:nth-last-child(3) ~ .toggle-button { + flex: 1; + font-size: var(--font-size-xs); + padding: var(--spacing-xs) var(--spacing-sm); +} + +/* Action Section - now at top */ +.action-section { + padding: var(--spacing-sm) var(--spacing-md); + background: var(--bg-secondary); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + flex: 0 0 auto; /* fixed height section */ +} + +/* Content Section - contains title and preview */ +.content-section { + flex: 1 1 0; + display: flex; + flex-direction: column; + overflow: hidden; /* prevent page scroll */ + min-height: 200px; /* ensure content section is always visible */ + max-height: none; /* let it take remaining space */ +} + +/* Title Section - now inside content section */ +.title-section { + flex: 0 0 auto; + padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm); + background: var(--bg); +} + +.content-header { + margin-bottom: var(--spacing-sm); +} + +.title-label { + display: block; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--fg); + margin-bottom: var(--spacing-sm); +} + +.title-input { + width: 100%; + padding: var(--spacing-md); + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--font-size-base); + color: var(--fg); + transition: var(--transition-fast); +} + +.title-input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-light); +} + +.title-input::placeholder { + color: var(--fg-muted); +} + +/* Editor Container */ +.editor-container { + flex: 1 1 0; + display: flex; + flex-direction: column; background: var(--bg); border: 1px solid var(--border); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border-radius: var(--radius-md); + overflow: hidden; + min-height: 180px; /* ensure preview always visible */ + max-height: none; /* let it expand */ position: relative; - color:var(--fg); + margin: 0 var(--spacing-md) var(--spacing-md); +} + +/* Make textarea/editor area scroll independently */ +.editor-container > textarea, +.editor-container > .CodeMirror { + min-height: 0; + flex: 1 1 auto; +} + +.editor-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md) var(--spacing-lg); + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + flex: 0 0 auto; +} + +.editor-label { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--fg); +} + +.editor-actions { + display: flex; + gap: var(--spacing-sm); +} + +.action-button { + display: flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + font-size: var(--font-size-xs); + color: var(--fg-secondary); cursor: pointer; - padding: 10px 10px 10px 30px; + transition: var(--transition-fast); +} + +.action-button:hover { + background: var(--bg-tertiary); + border-color: var(--primary); + color: var(--primary); +} + +.action-button.secondary { + background: var(--bg); +} + +/* Copy button text animation */ +#copy-text { + transition: all 0.2s ease-in-out; display: inline-block; - font-weight: 600; - transition: .3s ease all; } -.button.toggle:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); +#copy-text.changing { + opacity: 0; + transform: translateY(-5px); } -.button.toggle.checked { - background: var(--bg-primary); - border-color: var(--border-primary); +/* Copy button hover effect */ +#copy:hover #copy-text { + transform: translateX(2px); } -.check { - background: var(--bg-primary); - border-radius: 50%; - content:''; - height: 20px; - left: 10px; - position: absolute; - top: calc(50% - 10px); - transition: .3s ease background-color; - width: 20px; +.markdown-editor { + flex: 1 1 0; + min-height: 0; /* let flex container control height */ + height: 100%; /* fill container */ + padding: var(--spacing-md); + background: var(--bg); + border: none; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: var(--font-size-sm); + line-height: 1.5; + color: var(--fg); + resize: none; + outline: none; + overflow-y: auto; /* enable vertical scrolling */ + overflow-x: hidden; + box-sizing: border-box; } -.checked .check { - background-color: var(--primary); - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIyLjAyOTY4IC00MC4wOTAzIDI2IDIwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48IS0tR2VuZXJhdGVkIGJ5IElKU1ZHIChodHRwczovL2dpdGh1Yi5jb20vaWNvbmphci9JSlNWRyktLT48cGF0aCBkPSJNMjcuOTc0MywtMzYuMTI3MmMwLDAuNDQ2NDI4IC0wLjE1NjI1LDAuODI1ODkzIC0wLjQ2ODc1LDEuMTM4MzlsLTEyLjEyMDUsMTIuMTIwNWwtMi4yNzY3OSwyLjI3Njc5Yy0wLjMxMjUsMC4zMTI1IC0wLjY5MTk2NCwwLjQ2ODc1IC0xLjEzODM5LDAuNDY4NzVjLTAuNDQ2NDI4LDAgLTAuODI1ODkzLC0wLjE1NjI1IC0xLjEzODM5LC0wLjQ2ODc1bC0yLjI3Njc5LC0yLjI3Njc5bC02LjA2MDI3LC02LjA2MDI3Yy0wLjMxMjUsLTAuMzEyNSAtMC40Njg3NSwtMC42OTE5NjUgLTAuNDY4NzUsLTEuMTM4MzljMCwtMC40NDY0MjkgMC4xNTYyNSwtMC44MjU4OTMgMC40Njg3NSwtMS4xMzgzOWwyLjI3Njc5LC0yLjI3Njc5YzAuMzEyNSwtMC4zMTI1IDAuNjkxOTY1LC0wLjQ2ODc1IDEuMTM4MzksLTAuNDY4NzVjMC40NDY0MjksMCAwLjgyNTg5MywwLjE1NjI1IDEuMTM4MzksMC40Njg3NWw0LjkyMTg4LDQuOTM4NjJsMTAuOTgyMSwtMTAuOTk4OWMwLjMxMjUsLTAuMzEyNSAwLjY5MTk2NCwtMC40Njg3NSAxLjEzODM5LC0wLjQ2ODc1YzAuNDQ2NDI4LDAgMC44MjU4OTMsMC4xNTYyNSAxLjEzODM5LDAuNDY4NzVsMi4yNzY3OCwyLjI3Njc5YzAuMzEyNSwwLjMxMjUgMC40Njg3NSwwLjY5MTk2NCAwLjQ2ODc1LDEuMTM4MzlaIiB0cmFuc2Zvcm09InNjYWxlKDEuMDAxOTgpIiBmaWxsPSIjZmZmIj48L3BhdGg+PC9zdmc+'); - background-repeat: no-repeat; - background-position: center; - background-size: 12px; +.markdown-editor::placeholder { + color: var(--fg-muted); + font-style: italic; +} + +/* CodeMirror Integration */ +.CodeMirror { + flex: 1 1 0; + height: 100% !important; /* fill container completely */ + border: none; + background: var(--bg); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: var(--font-size-sm); + line-height: 1.5; + overflow: hidden; /* let CodeMirror handle scrolling internally */ +} + +.CodeMirror-focused .CodeMirror-cursor { + border-left: 2px solid var(--primary); +} + +.CodeMirror-scroll { + height: 100% !important; + overflow-y: auto !important; + overflow-x: hidden !important; } + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: var(--bg); +} + +/* Hide duplicate scrollbars */ +.CodeMirror-vscrollbar { + display: block !important; +} + +.CodeMirror-hscrollbar { + display: none !important; +} + +/* Ensure smooth scrolling */ +.CodeMirror-scroll { + scroll-behavior: smooth; +} + +.CodeMirror-selected { + background: var(--primary-light); +} + + +.button-row { + display: flex; + gap: var(--spacing-md); + align-items: center; +} + + +.primary-button { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-lg) var(--spacing-xl); + background: var(--primary); + border: none; + border-radius: var(--radius-md); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: white; + cursor: pointer; + transition: var(--transition-fast); + box-shadow: 0 2px 4px var(--shadow); +} + +.primary-button:hover { + background: var(--primary-hover); + transform: translateY(-1px); + box-shadow: 0 4px 8px var(--shadow-hover); +} + +.primary-button:active { + transform: translateY(0); + box-shadow: 0 2px 4px var(--shadow); +} + +.secondary-button { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-lg) var(--spacing-xl); + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--fg); + cursor: pointer; + transition: var(--transition-fast); +} + +.secondary-button:hover { + background: var(--bg-tertiary); + border-color: var(--primary); + color: var(--primary); + transform: translateY(-1px); +} + +/* Status Bar - stays at bottom */ +.status-bar { + padding: var(--spacing-sm) var(--spacing-lg); + background: var(--bg-tertiary); + border-top: 1px solid var(--border); + text-align: center; + transition: var(--transition-fast); + flex: 0 0 auto; /* keep at bottom */ +} + +.status-bar.pulse { + animation: statusPulse 0.5s ease-out; +} + +#status-text { + font-size: var(--font-size-xs); + color: var(--fg-muted); + transition: var(--transition-fast); +} + +@keyframes statusPulse { + 0% { + transform: scale(1); + background: var(--bg-tertiary); + } + 50% { + transform: scale(1.02); + background: var(--primary-light); + } + 100% { + transform: scale(1); + background: var(--bg-tertiary); + } +} + +/* Button State Classes */ +.action-button.success, +.primary-button.success, +.secondary-button.success { + background: var(--success); + border-color: var(--success); + color: white; +} + +.action-button.error, +.primary-button.error, +.secondary-button.error { + background: var(--danger); + border-color: var(--danger); + color: white; +} + +.action-button.warning, +.primary-button.warning, +.secondary-button.warning { + background: var(--warning); + border-color: var(--warning); + color: white; +} + +/* Utility Classes */ +.hidden { + display: none !important; +} + +.success { + color: var(--success); +} + +.error { + color: var(--danger); +} + +.warning { + color: var(--warning); +} + +/* Loading state for buttons */ +.primary-button:disabled, +.secondary-button:disabled, +.action-button:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* Animation Classes */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.fade-in { + animation: fadeIn var(--transition-normal) ease-out; +} + +.slide-in { + animation: slideIn var(--transition-normal) ease-out; +} + +/* Responsive adjustments */ +@media (max-width: 480px) { + body { + width: 450px; + height: 600px; + } + + .header { + padding: var(--spacing-sm) var(--spacing-md); + } + + .quick-settings { + padding: var(--spacing-xs) var(--spacing-sm); + } + + .content-section { + padding: var(--spacing-sm); + min-height: 150px; + } + + .action-section { + padding: var(--spacing-xs) var(--spacing-sm); + } + + .button-row { + gap: var(--spacing-sm); + } +} + +/* Focus states for accessibility */ +.toggle-switch:focus-within .slider { + box-shadow: 0 0 0 3px var(--primary-light); +} + +.toggle-button:focus { + box-shadow: 0 0 0 3px var(--primary-light); +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + :root { + --border: #000000; + --shadow: rgba(0, 0, 0, 0.5); + } + + @media (prefers-color-scheme: dark) { + :root { + --border: #FFFFFF; + } + } +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .header::before { + animation: none !important; + opacity: 0.3; + } + + .status-bar.pulse { + animation: none !important; + } + + .primary-button:hover, + .secondary-button:hover, + .action-button:hover, + .toggle-button:hover, + .icon-button:hover { + transform: none !important; + } +} \ No newline at end of file diff --git a/src/popup/popup.html b/src/popup/popup.html index 07a21960..a7ceded2 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -1,36 +1,135 @@ - + + MarkDownload - -
- - Download Images - open options -
- Selected Text - Entire Document + + +
+
+
+

Processing page...

+
+
+ + +
+ +
+ + +
+ + +
+

Quick Settings

+
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ Markdown Preview +
+ +
+
+ +
+
+ + +
+ Ready +
- Include front/back template - - - Download - Download selected - Copy -
-
- - - - - + + + + + + \ No newline at end of file diff --git a/src/popup/popup.js b/src/popup/popup.js index aac5396b..27460e36 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -3,52 +3,216 @@ var selectedText = null; var imageList = null; var mdClipsFolder = ''; +var cm = null; const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; -// set up event handlers -const cm = CodeMirror.fromTextArea(document.getElementById("md"), { - theme: darkMode ? "xq-dark" : "xq-light", - mode: "markdown", - lineWrapping: true -}); -cm.on("cursorActivity", (cm) => { - const somethingSelected = cm.somethingSelected(); - var a = document.getElementById("downloadSelection"); - if (somethingSelected) { - if(a.style.display != "block") a.style.display = "block"; +// Initialize CodeMirror when DOM is ready +const initializeCodeMirror = () => { + const textarea = document.getElementById("md"); + if (textarea && !cm) { + cm = CodeMirror.fromTextArea(textarea, { + theme: darkMode ? "xq-dark" : "xq-light", + mode: "markdown", + lineWrapping: true, + lineNumbers: false, + readOnly: false, + viewportMargin: Infinity, + scrollbarStyle: "native" + }); + + cm.on("cursorActivity", (cm) => { + const somethingSelected = cm.somethingSelected(); + const copyText = document.getElementById("copy-text"); + + if (somethingSelected) { + if (copyText && copyText.textContent !== "Copy Selection") { + animateTextChange(copyText, "Copy Selection"); + } + } else { + if (copyText && copyText.textContent !== "Copy") { + animateTextChange(copyText, "Copy"); + } + } + }); + + // Refresh CodeMirror after initialization + setTimeout(() => { + cm.refresh(); + }, 100); } - else { - if(a.style.display != "none") a.style.display = "none"; +}; + +// Animate text change with smooth transition +const animateTextChange = (element, newText) => { + if (!element) return; + + element.classList.add('changing'); + + setTimeout(() => { + element.textContent = newText; + element.classList.remove('changing'); + }, 100); +}; + +// Update status bar +const updateStatus = (message, type = 'info') => { + const statusText = document.getElementById('status-text'); + const statusBar = document.getElementById('status-bar'); + + if (statusText) { + statusText.textContent = message; + statusText.className = type; // success, error, warning + + // Add pulse animation for visual feedback + if (statusBar && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + statusBar.classList.add('pulse'); + setTimeout(() => { + statusBar.classList.remove('pulse'); + }, 500); + } + + // Auto-clear status after 3 seconds for success messages + if (type === 'success') { + setTimeout(() => { + statusText.textContent = 'Ready'; + statusText.className = ''; + }, 3000); + } } +}; + +// Add basic event listeners (will be supplemented by setupEventListeners) +document.addEventListener('DOMContentLoaded', () => { + document.getElementById("download")?.addEventListener("click", download); + document.getElementById("copy")?.addEventListener("click", copyToClipboard); + document.getElementById("sendToObsidian")?.addEventListener("click", sendToObsidian); }); -document.getElementById("download").addEventListener("click", download); -document.getElementById("downloadSelection").addEventListener("click", downloadSelection); -document.getElementById("copy").addEventListener("click", copyToClipboard); -const defaultOptions = { +// Provide a local defaultOptions fallback in case shared script isn't loaded +const localDefaultOptions = { + headingStyle: "atx", + hr: "___", + bulletListMarker: "-", + codeBlockStyle: "fenced", + fence: "```", + emDelimiter: "_", + strongDelimiter: "**", + linkStyle: "inlined", + linkReferenceStyle: "full", + imageStyle: "markdown", + imageRefStyle: "inlined", + frontmatter: "", + backmatter: "", + title: "{pageTitle}", includeTemplate: false, - clipSelection: true, - downloadImages: false + saveAs: false, + downloadImages: false, + imagePrefix: '{pageTitle}/', + mdClipsFolder: null, + disallowedChars: '[]#^', + downloadMode: 'downloadsApi', + turndownEscape: true, + contextMenus: true, + obsidianIntegration: false, + obsidianVault: "", + obsidianFolder: "" +}; + +// Use shared defaultOptions if present, otherwise fallback +const baseDefaults = (typeof defaultOptions !== 'undefined') ? defaultOptions : localDefaultOptions; + +// Add clipSelection to the default options +const popupDefaultOptions = { + ...baseDefaults, + clipSelection: true } const checkInitialSettings = options => { - if (options.includeTemplate) - document.querySelector("#includeTemplate").classList.add("checked"); + // Update includeTemplate state (supports input checkbox or legacy anchor) + const includeTemplateEl = document.querySelector("#includeTemplate"); + if (includeTemplateEl) { + if (includeTemplateEl.tagName === 'INPUT') { + includeTemplateEl.checked = !!options.includeTemplate; + } else { + includeTemplateEl.classList.toggle('checked', !!options.includeTemplate); + } + } - if (options.downloadImages) - document.querySelector("#downloadImages").classList.add("checked"); + // Update downloadImages state (supports input checkbox or legacy anchor) + const downloadImagesEl = document.querySelector("#downloadImages"); + if (downloadImagesEl) { + if (downloadImagesEl.tagName === 'INPUT') { + downloadImagesEl.checked = !!options.downloadImages; + } else { + downloadImagesEl.classList.toggle('checked', !!options.downloadImages); + } + } + + // Update toggle buttons for clip selection + const selectedBtn = document.querySelector("#selected"); + const documentBtn = document.querySelector("#document"); + + if (selectedBtn && documentBtn) { + if (options.clipSelection) { + selectedBtn.classList.add("active"); + documentBtn.classList.remove("active"); + } else { + selectedBtn.classList.remove("active"); + documentBtn.classList.add("active"); + } + } - if (options.clipSelection) - document.querySelector("#selected").classList.add("checked"); - else - document.querySelector("#document").classList.add("checked"); + // Update heading style buttons + const atxBtn = document.querySelector("#heading-atx"); + const setextBtn = document.querySelector("#heading-setext"); + + if (atxBtn && setextBtn) { + if (options.headingStyle === 'atx') { + atxBtn.classList.add("active"); + setextBtn.classList.remove("active"); + } else { + atxBtn.classList.remove("active"); + setextBtn.classList.add("active"); + } + } + + // Update link style buttons + const inlinedBtn = document.querySelector("#link-inlined"); + const referencedBtn = document.querySelector("#link-referenced"); + const stripBtn = document.querySelector("#link-strip"); + + [inlinedBtn, referencedBtn, stripBtn].forEach(btn => { + if (btn) btn.classList.remove("active"); + }); + + if (options.linkStyle === 'inlined' && inlinedBtn) { + inlinedBtn.classList.add("active"); + } else if (options.linkStyle === 'referenced' && referencedBtn) { + referencedBtn.classList.add("active"); + } else if (options.linkStyle === 'stripLinks' && stripBtn) { + stripBtn.classList.add("active"); + } } -const toggleClipSelection = options => { - options.clipSelection = !options.clipSelection; - document.querySelector("#selected").classList.toggle("checked"); - document.querySelector("#document").classList.toggle("checked"); +const toggleClipSelection = (options, value) => { + options.clipSelection = (value === 'selection'); + + const selectedBtn = document.querySelector("#selected"); + const documentBtn = document.querySelector("#document"); + + if (selectedBtn && documentBtn) { + if (options.clipSelection) { + selectedBtn.classList.add("active"); + documentBtn.classList.remove("active"); + } else { + selectedBtn.classList.remove("active"); + documentBtn.classList.add("active"); + } + } + + updateStatus("Updating content selection...", "info"); + chrome.storage.sync.set(options).then(() => { // Get current tab ID and call clipSite chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { @@ -57,15 +221,26 @@ const toggleClipSelection = options => { } }).catch(error => { console.error('Failed to get tab ID:', error); + updateStatus("Error updating selection", "error"); }); }).catch((error) => { console.error(error); + updateStatus("Failed to save settings", "error"); }); } const toggleIncludeTemplate = options => { - options.includeTemplate = !options.includeTemplate; - document.querySelector("#includeTemplate").classList.toggle("checked"); + const el = document.querySelector("#includeTemplate"); + if (!el) return; + + if (el.tagName === 'INPUT') { + options.includeTemplate = !!el.checked; + } else { + options.includeTemplate = !options.includeTemplate; + el.classList.toggle('checked', !!options.includeTemplate); + } + updateStatus("Updating template settings...", "info"); + chrome.storage.sync.set(options).then(() => { chrome.contextMenus.update("toggle-includeTemplate", { checked: options.includeTemplate @@ -77,21 +252,105 @@ const toggleIncludeTemplate = options => { } }).catch(error => { console.error('Failed to get tab ID:', error); + updateStatus("Error updating content", "error"); }); }).catch((error) => { console.error(error); + updateStatus("Failed to save settings", "error"); }); } const toggleDownloadImages = options => { - options.downloadImages = !options.downloadImages; - document.querySelector("#downloadImages").classList.toggle("checked"); + const el = document.querySelector("#downloadImages"); + if (!el) return; + + if (el.tagName === 'INPUT') { + options.downloadImages = !!el.checked; + } else { + options.downloadImages = !options.downloadImages; + el.classList.toggle('checked', !!options.downloadImages); + } + updateStatus("Updating image settings...", "info"); + chrome.storage.sync.set(options).then(() => { chrome.contextMenus.update("toggle-downloadImages", { checked: options.downloadImages }); + updateStatus("Settings saved", "success"); + }).catch((error) => { + console.error(error); + updateStatus("Failed to save settings", "error"); + }); +} + +const toggleHeadingStyle = (options, value) => { + options.headingStyle = value; + + const atxBtn = document.querySelector("#heading-atx"); + const setextBtn = document.querySelector("#heading-setext"); + + if (atxBtn && setextBtn) { + if (value === 'atx') { + atxBtn.classList.add("active"); + setextBtn.classList.remove("active"); + } else { + atxBtn.classList.remove("active"); + setextBtn.classList.add("active"); + } + } + + updateStatus("Updating heading style...", "info"); + + chrome.storage.sync.set(options).then(() => { + // Get current tab ID and re-clip to show changes + chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { + if (tabs[0]) { + clipSite(tabs[0].id); + } + }).catch(error => { + console.error('Failed to get tab ID:', error); + }); + updateStatus("Heading style updated", "success"); + }).catch((error) => { + console.error(error); + updateStatus("Failed to save settings", "error"); + }); +} + +const toggleLinkStyle = (options, value) => { + options.linkStyle = value; + + const inlinedBtn = document.querySelector("#link-inlined"); + const referencedBtn = document.querySelector("#link-referenced"); + const stripBtn = document.querySelector("#link-strip"); + + [inlinedBtn, referencedBtn, stripBtn].forEach(btn => { + if (btn) btn.classList.remove("active"); + }); + + if (value === 'inlined' && inlinedBtn) { + inlinedBtn.classList.add("active"); + } else if (value === 'referenced' && referencedBtn) { + referencedBtn.classList.add("active"); + } else if (value === 'stripLinks' && stripBtn) { + stripBtn.classList.add("active"); + } + + updateStatus("Updating link style...", "info"); + + chrome.storage.sync.set(options).then(() => { + // Get current tab ID and re-clip to show changes + chrome.tabs.query({ currentWindow: true, active: true }).then(tabs => { + if (tabs[0]) { + clipSite(tabs[0].id); + } + }).catch(error => { + console.error('Failed to get tab ID:', error); + }); + updateStatus("Link style updated", "success"); }).catch((error) => { console.error(error); + updateStatus("Failed to save settings", "error"); }); } const showOrHideClipOption = selection => { @@ -118,7 +377,7 @@ const clipSite = id => { selection: result[0].result.selection, tabId: id } - return chrome.storage.sync.get(defaultOptions).then(options => { + return chrome.storage.sync.get(popupDefaultOptions).then(options => { chrome.runtime.sendMessage({ ...message, ...options @@ -128,7 +387,7 @@ const clipSite = id => { showError(err) return chrome.runtime.sendMessage({ ...message, - ...defaultOptions + ...popupDefaultOptions }); }).catch(err => { console.error(err); @@ -142,40 +401,122 @@ const clipSite = id => { } // Initialize popup -chrome.storage.sync.get(defaultOptions).then(options => { - checkInitialSettings(options); +const initializePopup = () => { + updateStatus("Loading settings...", "info"); - document.getElementById("selected").addEventListener("click", (e) => { - e.preventDefault(); - toggleClipSelection(options); - }); - document.getElementById("document").addEventListener("click", (e) => { - e.preventDefault(); - toggleClipSelection(options); - }); - document.getElementById("includeTemplate").addEventListener("click", (e) => { - e.preventDefault(); - toggleIncludeTemplate(options); - }); - document.getElementById("downloadImages").addEventListener("click", (e) => { - e.preventDefault(); - toggleDownloadImages(options); + chrome.storage.sync.get(popupDefaultOptions).then(options => { + checkInitialSettings(options); + setupEventListeners(options); + + return chrome.tabs.query({ + currentWindow: true, + active: true + }); + }).then((tabs) => { + if (tabs && tabs[0]) { + const id = tabs[0].id; + const url = tabs[0].url; + + updateStatus("Processing page...", "info"); + console.info("MarkDownload popup: Content script should be available"); + return clipSite(id); + } else { + throw new Error("No active tab found"); + } + }).catch((error) => { + console.error(error); + showError(error); }); +}; + +// Setup event listeners +const setupEventListeners = (options) => { + // Toggle buttons for clip selection + const selectedBtn = document.getElementById("selected"); + const documentBtn = document.getElementById("document"); - return chrome.tabs.query({ - currentWindow: true, - active: true - }); -}).then((tabs) => { - var id = tabs[0].id; - var url = tabs[0].url; - - // Content script is already injected via manifest, just call clipSite - console.info("MarkDownload popup: Content script should be available"); - return clipSite(id); -}).catch( (error) => { - console.error(error); - showError(error); + if (selectedBtn) { + selectedBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleClipSelection(options, 'selection'); + }); + } + + if (documentBtn) { + documentBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleClipSelection(options, 'document'); + }); + } + + // Heading style buttons + const headingAtxBtn = document.getElementById("heading-atx"); + const headingSetextBtn = document.getElementById("heading-setext"); + + if (headingAtxBtn) { + headingAtxBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleHeadingStyle(options, 'atx'); + }); + } + + if (headingSetextBtn) { + headingSetextBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleHeadingStyle(options, 'setext'); + }); + } + + // Link style buttons + const linkInlinedBtn = document.getElementById("link-inlined"); + const linkReferencedBtn = document.getElementById("link-referenced"); + const linkStripBtn = document.getElementById("link-strip"); + + if (linkInlinedBtn) { + linkInlinedBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleLinkStyle(options, 'inlined'); + }); + } + + if (linkReferencedBtn) { + linkReferencedBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleLinkStyle(options, 'referenced'); + }); + } + + if (linkStripBtn) { + linkStripBtn.addEventListener("click", (e) => { + e.preventDefault(); + toggleLinkStyle(options, 'stripLinks'); + }); + } + + // Checkbox event listeners + const includeTemplateEl = document.getElementById("includeTemplate"); + if (includeTemplateEl) { + const evt = (includeTemplateEl.tagName === 'INPUT') ? 'change' : 'click'; + includeTemplateEl.addEventListener(evt, (e) => { + e.preventDefault(); + toggleIncludeTemplate(options); + }); + } + + const downloadImagesEl = document.getElementById("downloadImages"); + if (downloadImagesEl) { + const evt2 = (downloadImagesEl.tagName === 'INPUT') ? 'change' : 'click'; + downloadImagesEl.addEventListener(evt2, (e) => { + e.preventDefault(); + toggleDownloadImages(options); + }); + } +}; + +// Call initialization when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + initializeCodeMirror(); + initializePopup(); }); // listen for notifications from the background page @@ -209,45 +550,66 @@ async function download(e) { window.close(); } -// event handler for download selected button -async function downloadSelection(e) { - e.preventDefault(); - if (cm.somethingSelected()) { - await sendDownloadMessage(cm.getSelection()); - } -} // event handler for copy button async function copyToClipboard(e) { e.preventDefault(); + if (!cm) { + updateStatus("Editor not initialized", "error"); + return; + } + try { - const textToCopy = cm.getValue(); + // Check if there's a selection, if so copy selection, otherwise copy all + const hasSelection = cm.somethingSelected(); + const textToCopy = hasSelection ? cm.getSelection() : cm.getValue(); + + if (!textToCopy.trim()) { + updateStatus("No content to copy", "warning"); + return; + } + await navigator.clipboard.writeText(textToCopy); // Show feedback to user const copyButton = document.getElementById("copy"); - const originalText = copyButton.textContent; - copyButton.textContent = "Copied!"; - copyButton.style.backgroundColor = "#4CAF50"; + const originalHTML = copyButton.innerHTML; + const feedbackText = hasSelection ? "Selection Copied!" : "Copied!"; + + copyButton.innerHTML = ` + + + + ${feedbackText} + `; + copyButton.classList.add("success"); + updateStatus("Content copied to clipboard", "success"); // Reset button after 2 seconds setTimeout(() => { - copyButton.textContent = originalText; - copyButton.style.backgroundColor = ""; + copyButton.innerHTML = originalHTML; + copyButton.classList.remove("success"); }, 2000); } catch (error) { console.error('Failed to copy text: ', error); - // Fallback: show error feedback + // Show error feedback const copyButton = document.getElementById("copy"); - const originalText = copyButton.textContent; - copyButton.textContent = "Copy Failed"; - copyButton.style.backgroundColor = "#f44336"; + const originalHTML = copyButton.innerHTML; + + copyButton.innerHTML = ` + + + + Failed + `; + copyButton.classList.add("error"); + updateStatus("Failed to copy content", "error"); setTimeout(() => { - copyButton.textContent = originalText; - copyButton.style.backgroundColor = ""; + copyButton.innerHTML = originalHTML; + copyButton.classList.remove("error"); }, 2000); } } @@ -256,27 +618,154 @@ async function copyToClipboard(e) { function notify(message) { // message for displaying markdown if (message.type == "display.md") { - - // set the values from the message - //document.getElementById("md").value = message.markdown; - cm.setValue(message.markdown); - document.getElementById("title").value = message.article.title; - imageList = message.imageList; - mdClipsFolder = message.mdClipsFolder; + if (!cm) { + // Initialize CodeMirror if not already done + initializeCodeMirror(); + } - // show the hidden elements - document.getElementById("container").style.display = 'flex'; - document.getElementById("spinner").style.display = 'none'; - // focus the download button - document.getElementById("download").focus(); - cm.refresh(); + if (cm && message.markdown) { + // set the values from the message + cm.setValue(message.markdown); + setTimeout(() => cm.refresh(), 50); + + // Set the title + const titleInput = document.getElementById("title"); + if (titleInput && message.article && message.article.title) { + titleInput.value = message.article.title; + } + + imageList = message.imageList; + mdClipsFolder = message.mdClipsFolder; + + // show the main container and hide spinner + const container = document.getElementById("container"); + const spinner = document.getElementById("spinner"); + + if (container) { + container.style.display = 'flex'; + container.classList.add('fade-in'); + } + if (spinner) { + spinner.style.display = 'none'; + } + + // focus the download button and refresh CodeMirror + const downloadBtn = document.getElementById("download"); + if (downloadBtn) { + downloadBtn.focus(); + } + + if (cm) { + setTimeout(() => cm.refresh(), 100); + } + + updateStatus("Page processed successfully", "success"); + } } } function showError(err) { - // show the hidden elements - document.getElementById("container").style.display = 'flex'; - document.getElementById("spinner").style.display = 'none'; - cm.setValue(`Error clipping the page\n\n${err}`) + console.error("MarkDownload Error:", err); + + // show the main container and hide spinner + const container = document.getElementById("container"); + const spinner = document.getElementById("spinner"); + + if (container) { + container.style.display = 'flex'; + container.classList.add('fade-in'); + } + if (spinner) { + spinner.style.display = 'none'; + } + + // Initialize CodeMirror if not already done + if (!cm) { + initializeCodeMirror(); + } + + // Show error message in editor + if (cm) { + const errorMessage = `# Error Processing Page\n\n**Error Details:**\n\`\`\`\n${err}\n\`\`\`\n\n**Troubleshooting:**\n- Try refreshing the page and clipping again\n- Check if the page has finished loading\n- Some pages may not be supported\n- Check browser console for more details`; + cm.setValue(errorMessage); + setTimeout(() => cm.refresh(), 100); + } + + updateStatus(`Error: ${err}`, "error"); +} + +// event handler for send to Obsidian button +async function sendToObsidian(e) { + e.preventDefault(); + if (!cm) { + updateStatus("Editor not initialized", "error"); + return; + } + try { + const markdownContent = cm.getValue(); + if (!markdownContent.trim()) { + updateStatus("No content to send", "warning"); + return; + } + + updateStatus("Sending to Obsidian...", "info"); + + const tabs = await chrome.tabs.query({ currentWindow: true, active: true }); + const options = await chrome.storage.sync.get(popupDefaultOptions); + if (!options.obsidianIntegration) { + updateStatus("Obsidian integration not enabled. Enable it in Options.", "error"); + return; + } + if (!tabs[0]) { + updateStatus("No active tab found", "error"); + return; + } + + const titleInput = document.getElementById("title"); + const title = titleInput ? titleInput.value.trim() : "Untitled"; + + const response = await chrome.runtime.sendMessage({ + type: "sendToObsidian", + markdown: markdownContent, + title: title, + tabId: tabs[0].id, + options: options + }); + + if (response && response.success) { + updateStatus("Successfully sent to Obsidian!", "success"); + const obsidianButton = document.getElementById("sendToObsidian"); + const originalHTML = obsidianButton.innerHTML; + obsidianButton.innerHTML = ` + + + + Sent! + `; + obsidianButton.classList.add("success"); + setTimeout(() => { + obsidianButton.innerHTML = originalHTML; + obsidianButton.classList.remove("success"); + }, 3000); + } else { + throw new Error(response?.error || "Failed to send to Obsidian"); + } + } catch (error) { + console.error('Failed to send to Obsidian: ', error); + updateStatus(`Failed to send to Obsidian: ${error.message}`, "error"); + const obsidianButton = document.getElementById("sendToObsidian"); + const originalHTML = obsidianButton.innerHTML; + obsidianButton.innerHTML = ` + + + + Failed + `; + obsidianButton.classList.add("error"); + setTimeout(() => { + obsidianButton.innerHTML = originalHTML; + obsidianButton.classList.remove("error"); + }, 3000); + } } From 7fc9e1136d6264bb8931563f8e7dafa97475887f Mon Sep 17 00:00:00 2001 From: TianleLin <940428567qq@gmail.com> Date: Fri, 12 Sep 2025 00:53:08 +0800 Subject: [PATCH 28/29] fix download image --- src/background/jszip.min.js | 13 ++ src/background/service-worker.js | 36 +++- src/package-lock.json | 73 +++----- src/package.json | 3 + src/popup/popup.css | 226 ++++++++++++++++++++++++ src/popup/popup.html | 284 +++++++++++++++++++------------ src/popup/popup.js | 85 +++++++++ 7 files changed, 557 insertions(+), 163 deletions(-) create mode 100644 src/background/jszip.min.js diff --git a/src/background/jszip.min.js b/src/background/jszip.min.js new file mode 100644 index 00000000..ff4cfd5e --- /dev/null +++ b/src/background/jszip.min.js @@ -0,0 +1,13 @@ +/*! + +JSZip v3.10.1 - A JavaScript class for generating and reading zip files + + +(c) 2009-2016 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/main/LICENSE +*/ + +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e>2,s=(3&t)<<4|r>>4,a=1>6:64,o=2>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function A(e,t){var r,n="";for(r=0;r>>=8;return n}function n(e,t,r,n,i,s){var a,o,h=e.file,u=e.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),c=I.transformTo("string",O.utf8encode(h.name)),d=h.comment,p=I.transformTo("string",s(d)),m=I.transformTo("string",O.utf8encode(d)),_=c.length!==h.name.length,g=m.length!==d.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===i?(C=798,z|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(e){return 63&(e||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+c,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(n,4)+f+b+p}}var I=e("../utils"),i=e("../stream/GenericWorker"),O=e("../utf8"),B=e("../crc32"),R=e("../signature");function s(e,t,r,n){i.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,i),s.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,i.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},s.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=n(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=n(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return R.DATA_DESCRIPTOR+A(e.crc32,4)+A(e.compressedSize,4)+A(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),u=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,o){return new a.Promise(function(t,r){var n=[],i=e._internalType,s=e._outputType,a=e._mimeType;e.on("data",function(e,t){n.push(e),o&&o(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return u.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return h.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(h.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(h.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}(t),i=t;n!==t.length&&(h.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,n),l.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,a){"use strict";var o=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),u=e("./external");function n(e){return e}function l(e,t){for(var r=0;r>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return l(e,e.length)},r.binstring2buf=function(e){for(var t=new h.Buf8(e.length),r=0,n=t.length;r>10&1023,o[n++]=56320|1023&i)}return l(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var h,c=e("../utils/common"),u=e("./trees"),d=e("./adler32"),p=e("./crc32"),n=e("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,i=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=n[t],t}function T(e){return(e<<1)-(4e.avail_out&&(r=e.avail_out),0!==r&&(c.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){u._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&sh&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,i,s,a,o,h,u,l,f=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-z)){for(c.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,t=r=e.hash_size;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(t=r=f;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);i+=f}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,h=e.strstart+e.lookahead,u=i,l=void 0,l=a.avail_in,u=x)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x)if(n=u._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=u._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===l)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?_:(e.state.gzhead=t,m):_},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5>8&255),U(n,n.gzhead.time>>16&255),U(n,n.gzhead.time>>24&255),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(U(n,255&n.gzhead.extra.length),U(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(U(n,0),U(n,0),U(n,0),U(n,0),U(n,0),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,3),n.status=E);else{var a=v+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=E,P(n,a),0!==n.strstart&&(P(n,e.adler>>>16),P(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending!==n.pending_buf_size));)U(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&F(e),n.pending+2<=n.pending_buf_size&&(U(n,255&e.adler),U(n,e.adler>>8&255),e.adler=0,n.status=E)):n.status=E),0!==n.pending){if(F(e),0===e.avail_out)return n.last_flush=-1,m}else if(0===e.avail_in&&T(t)<=T(r)&&t!==f)return R(e,-5);if(666===n.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==l&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===l)return A;break}if(e.match_length=0,r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===l)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=u._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):h[n.level].func(n,t);if(o!==O&&o!==B||(n.status=666),o===A||o===O)return 0===e.avail_out&&(n.last_flush=-1),m;if(o===I&&(1===t?u._tr_align(n):5!==t&&(u._tr_stored_block(n,0,0,!1),3===t&&(D(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),F(e),0===e.avail_out))return n.last_flush=-1,m}return t!==f?m:n.wrap<=0?1:(2===n.wrap?(U(n,255&e.adler),U(n,e.adler>>8&255),U(n,e.adler>>16&255),U(n,e.adler>>24&255),U(n,255&e.total_in),U(n,e.total_in>>8&255),U(n,e.total_in>>16&255),U(n,e.total_in>>24&255)):(P(n,e.adler>>>16),P(n,65535&e.adler)),F(e),0=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new c.Buf8(r.w_size),c.arraySet(u,t,l-r.w_size,r.w_size,0),t=u,l=r.w_size),a=e.avail_in,o=e.next_in,h=e.input,e.avail_in=l,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,i=r.lookahead-(x-1);r.ins_h=(r.ins_h<>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<>>=y,p-=y),p<15&&(d+=z[n++]<>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<>>=y,p-=y,(y=s-a)>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){e.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l>>=_,l-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l>>=_)),u>>>=3,l-=3}else{for(z=_+7;l>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(hd?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u>=7;n>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){"use strict";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i="[object process]"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage("","*"),r.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",r.addEventListener?r.addEventListener("message",d,!1):r.attachEvent("onmessage",d),function(e){r.postMessage(a+e,"*")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(s=l.documentElement,function(e){var t=l.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),r=0;r 0) { + const zip = new JSZip(); + let safeTitle = title.replace(/[\\/|\\:*?"<>]/g, ' '); + const imageFolder = zip.folder("images"); + + // loop through the image list and replace the images in the markdown + for (const [src, filename] of Object.entries(imageList)) { + const localImagePath = "images/" + filename; + markdown = markdown.replaceAll(src, localImagePath); + imageFolder.file(filename, fetch(src).then(res => res.blob())); + } + + // add the markdown file to the zip + zip.file(safeTitle + '.md', markdown); + + // generate the zip and download it + const content = await zip.generateAsync({ type: 'base64' }); + const dataUrl = 'data:application/zip;base64,' + content; + + const id = await chrome.downloads.download({ + url: dataUrl, + filename: mdClipsFolder + safeTitle + ".zip", + saveAs: options.saveAs + }); + + return; + } + if (options.downloadMode == 'downloadsApi' && chrome.downloads) { console.log('MarkDownload: Using downloads API'); // Use data URL directly; createObjectURL is not available in MV3 service worker - const dataUrl = 'data:text/markdown;charset=utf-8,' + encodeURIComponent(markdown); +const dataUrl = 'data:text/markdown;charset=utf-8,' + encodeURIComponent(markdown); console.log('MarkDownload: Data URL length:', dataUrl.length); try { diff --git a/src/package-lock.json b/src/package-lock.json index d7ce449b..0fc36389 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -4,7 +4,9 @@ "requires": true, "packages": { "": { - "name": "src", + "dependencies": { + "jszip": "^3.10.1" + }, "devDependencies": { "web-ext": "^7.4.0" } @@ -1420,8 +1422,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -2771,8 +2772,7 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -2821,8 +2821,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -3029,8 +3028,7 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "node_modules/isexe": { "version": "2.0.0", @@ -3190,7 +3188,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -3272,7 +3270,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, "dependencies": { "immediate": "~3.0.5" } @@ -3904,8 +3901,7 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -4101,8 +4097,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/process-warning": { "version": "3.0.0", @@ -4258,7 +4253,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4272,8 +4266,7 @@ "node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/real-require": { "version": "0.2.0", @@ -4626,8 +4619,7 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "node_modules/sha.js": { "version": "2.4.11", @@ -4812,7 +4804,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4820,8 +4811,7 @@ "node_modules/string_decoder/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/string-width": { "version": "5.1.2", @@ -5219,8 +5209,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/uuid": { "version": "8.3.2", @@ -6692,8 +6681,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cross-spawn": { "version": "7.0.3", @@ -7673,8 +7661,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", @@ -7711,8 +7698,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -7853,8 +7839,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -7994,7 +7979,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -8064,7 +8048,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, "requires": { "immediate": "~3.0.5" } @@ -8529,8 +8512,7 @@ "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", @@ -8686,8 +8668,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "process-warning": { "version": "3.0.0", @@ -8804,7 +8785,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8818,8 +8798,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -9069,8 +9048,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "sha.js": { "version": "2.4.11", @@ -9224,7 +9202,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -9232,8 +9209,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -9534,8 +9510,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "8.3.2", diff --git a/src/package.json b/src/package.json index 2231dc45..4b0a8349 100644 --- a/src/package.json +++ b/src/package.json @@ -9,5 +9,8 @@ }, "devDependencies": { "web-ext": "^7.4.0" + }, + "dependencies": { + "jszip": "^3.10.1" } } diff --git a/src/popup/popup.css b/src/popup/popup.css index ee45f02f..9f2d2bb1 100644 --- a/src/popup/popup.css +++ b/src/popup/popup.css @@ -749,6 +749,232 @@ input#title { align-items: center; } +/* Button with Overlay Help */ +.button-with-overlay-help { + position: relative; +} + +.button-with-overlay-help:hover .overlay-help-icon { + opacity: 1; + transform: scale(1.05); +} + +.overlay-help-icon { + position: absolute; + top: 4px; + right: 4px; + width: 20px; + height: 20px; + background: rgba(255, 255, 255, 0.9); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + opacity: 0.7; + color: var(--primary); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(4px); +} + +.overlay-help-icon:hover { + opacity: 1; + transform: scale(1.1); + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +} + +/* Dark mode support for overlay help */ +@media (prefers-color-scheme: dark) { + .overlay-help-icon { + background: rgba(0, 0, 0, 0.8); + color: var(--primary-light); + } + + .overlay-help-icon:hover { + background: rgba(0, 0, 0, 0.9); + } +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); + animation: fadeIn 0.2s ease-out; +} + +.modal-content { + background: var(--bg); + border-radius: var(--radius-lg); + width: 90%; + max-width: 480px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); + animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg); + border-bottom: 1px solid var(--border); + background: var(--bg-secondary); + border-radius: var(--radius-lg) var(--radius-lg) 0 0; +} + +.modal-header h3 { + margin: 0; + font-size: var(--font-size-lg); + font-weight: var(--font-weight-medium); + color: var(--fg); +} + +.close-button { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: var(--fg-muted); + padding: var(--spacing-xs); + border-radius: var(--radius-sm); + transition: var(--transition-fast); + line-height: 1; +} + +.close-button:hover { + background: var(--bg-tertiary); + color: var(--fg); +} + +.modal-body { + padding: var(--spacing-lg); +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Setup Steps */ +.setup-step { + display: flex; + gap: var(--spacing-md); + margin-bottom: var(--spacing-lg); + align-items: flex-start; +} + +.step-number { + background: var(--primary); + color: white; + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: var(--font-weight-medium); + font-size: var(--font-size-sm); + flex-shrink: 0; +} + +.step-content { + flex: 1; +} + +.step-content h4 { + margin: 0 0 var(--spacing-sm) 0; + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + color: var(--fg); +} + +.step-content p { + margin: 0; + color: var(--fg-muted); + line-height: 1.5; +} + +.quick-setup { + margin-top: var(--spacing-md); + padding: var(--spacing-md); + background: var(--bg-secondary); + border-radius: var(--radius-md); + border: 1px solid var(--border); +} + +.quick-setup label { + display: block; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--fg); + margin-bottom: var(--spacing-xs); + margin-top: var(--spacing-sm); +} + +.quick-setup label:first-child { + margin-top: 0; +} + +.setup-input { + width: 100%; + padding: var(--spacing-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg); + color: var(--fg); + font-size: var(--font-size-sm); + margin-bottom: var(--spacing-sm); + transition: var(--transition-fast); + box-sizing: border-box; +} + +.setup-input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--primary-light); +} + +.setup-button { + background: var(--primary); + color: white; + border: none; + padding: var(--spacing-sm) var(--spacing-lg); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + transition: var(--transition-fast); + margin-top: var(--spacing-sm); +} + +.setup-button:hover { + background: var(--primary-hover); + transform: translateY(-1px); +} + .primary-button { flex: 1; diff --git a/src/popup/popup.html b/src/popup/popup.html index a7ceded2..7d381fdc 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -1,135 +1,197 @@ - - - - - MarkDownload - - - - - - - -
-
-
-

Processing page...

-
+ + + + MarkDownload + + + + + + + + +
+
+
+

Processing page...

+
- -
- -
-
- - -
-

Quick Settings

-
-
- -
-
- -
-
- -
- - -
+
+ +
+ +
-
+
+ - -
-
- - + +
+ +
+ + + +
+ +
+
+ +
-
+
- -
- -
-
- - + +
+
+ Markdown Preview +
+
- - -
-
- Markdown Preview -
- + +
+
+ + +
+ Ready +
+
+ + + + +
+ + +
+ +
+ 📝 Template Variables & Examples + + Configure how image files are named when downloaded. Available variables: +
    +
  • {pageTitle} - Article title (same as used for markdown filename)
  • +
  • {imageIndex} - Image sequence number (001, 002, 003...)
  • +
  • {originalFilename} - Original image filename (without extension)
  • +
  • {ext} - File extension (png, jpg, gif, etc.)
  • +
  • {byline} - Author information
  • +
  • Plus all other article variables: {title}, {excerpt}, {date:FORMAT}, etc.
  • +
+ +

Formatting options (same as title template):

+
    +
  • :kebab - kebab-case (article-title)
  • +
  • :snake - snake_case (article_title)
  • +
  • :camel - camelCase (articleTitle)
  • +
  • :pascal - PascalCase (ArticleTitle)
  • +
  • :lower - lowercase
  • +
  • :upper - UPPERCASE
  • +
+ +

Examples:

+
    +
  • {pageTitle:kebab}_{imageIndex}.{ext} → article-title_001.jpg
  • +
  • {pageTitle:snake}_{imageIndex}.{ext} → article_title_002.png
  • +
  • {byline:kebab}_{pageTitle:camel}_{imageIndex}.{ext} → john-doe_articleTitle_003.gif
  • +
  • img_{imageIndex}_{originalFilename}.{ext} → img_004_screenshot.jpg
  • +
+
+
diff --git a/src/options/options.js b/src/options/options.js index ab342d9b..9c5c75b0 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -14,6 +14,7 @@ const saveOptions = e => { saveAs: document.querySelector("[name='saveAs']").checked, downloadImages: document.querySelector("[name='downloadImages']").checked, imagePrefix: document.querySelector("[name='imagePrefix']").value, + imageFilenameTemplate: document.querySelector("[name='imageFilenameTemplate']").value, mdClipsFolder: document.querySelector("[name='mdClipsFolder']").value, turndownEscape: document.querySelector("[name='turndownEscape']").checked, contextMenus: document.querySelector("[name='contextMenus']").checked, @@ -108,6 +109,7 @@ const setCurrentChoice = result => { document.querySelector("[name='saveAs']").checked = options.saveAs; document.querySelector("[name='downloadImages']").checked = options.downloadImages; document.querySelector("[name='imagePrefix']").value = options.imagePrefix; + document.querySelector("[name='imageFilenameTemplate']").value = options.imageFilenameTemplate || '{pageTitle:kebab}_{imageIndex}.{ext}'; document.querySelector("[name='mdClipsFolder']").value = result.mdClipsFolder; document.querySelector("[name='turndownEscape']").checked = options.turndownEscape; document.querySelector("[name='contextMenus']").checked = options.contextMenus; @@ -170,6 +172,8 @@ const refereshElements = () => { const downloadImages = options.downloadImages && options.downloadMode == 'downloadsApi'; show(document.getElementById("imagePrefix"), downloadImages); + show(document.getElementById("imageFilenameTemplate"), downloadImages); + show(document.querySelector(".template-help"), downloadImages); document.getElementById('markdown').disabled = !downloadImages; document.getElementById('base64').disabled = !downloadImages; diff --git a/src/popup/popup.js b/src/popup/popup.js index 318e51a5..8d541aba 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -126,6 +126,7 @@ const localDefaultOptions = { saveAs: false, downloadImages: false, imagePrefix: '{pageTitle}/', + imageFilenameTemplate: '{pageTitle:kebab}_{imageIndex}.{ext}', mdClipsFolder: null, disallowedChars: '[]#^', downloadMode: 'downloadsApi', diff --git a/src/shared/default-options.js b/src/shared/default-options.js index b532ccbb..4e6664e7 100644 --- a/src/shared/default-options.js +++ b/src/shared/default-options.js @@ -18,6 +18,7 @@ const defaultOptions = { saveAs: false, downloadImages: false, imagePrefix: '{pageTitle}/', + imageFilenameTemplate: '{pageTitle:kebab}_{imageIndex}.{ext}', mdClipsFolder: null, disallowedChars: '[]#^', downloadMode: 'downloadsApi',