diff --git a/.DS_Store b/.DS_Store index 819bd07..4837e51 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/plugin/.DS_Store b/plugin/.DS_Store new file mode 100644 index 0000000..d8d844a Binary files /dev/null and b/plugin/.DS_Store differ diff --git a/plugin/Screenshot 640.png b/plugin/Screenshot 640.png new file mode 100644 index 0000000..4f76bf5 Binary files /dev/null and b/plugin/Screenshot 640.png differ diff --git a/plugin/background.js b/plugin/background.js new file mode 100644 index 0000000..61e00f1 --- /dev/null +++ b/plugin/background.js @@ -0,0 +1,145 @@ +importScripts("./js-base64/base64.js"); + +const harmonyURL = "https://harmonydata.ac.uk/app/#/"; + +const createHarmonyUrl = ({ questions, instrument_name }) => { + if ( + Array.isArray(questions) && + questions.length && + questions.every( + (q) => + typeof q === "string" || + q instanceof String || + (q.question_text && + (typeof q.question_text === "string" || + q.question_text instanceof String)) + ) + ) { + const qArray = questions.map((q, i) => { + return { + question_no: q.question_no || i, + question_text: q.question_text || q, + }; + }); + const iArray = { instrument_name: instrument_name, questions: qArray }; + return harmonyURL + "import/" + Base64.encode(JSON.stringify(iArray), true); + } else { + throw new Error( + "questions is not properly formatted - it must be an array of question texts, or an array of objects which each must have a question_text property" + ); + } +}; + +// Create context menu item +chrome.runtime.onInstalled.addListener(() => { + // Create different menu items for PDFs and regular pages + chrome.contextMenus.create({ + id: "sendToHarmony", + title: "Send to Harmony", + contexts: ["selection"], + }); + // Initialize history in storage + chrome.storage.local.set({ history: [] }); +}); + +// Function to open Harmony URL in new tab +function openHarmonyTab(url) { + chrome.tabs.create({ url: url }); +} + +// Listen for messages from popup +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "openHarmonyUrl") { + findOrCreateHarmonyTab(request.url); + return true; + } + if (request.action === "processPdfText") { + processSelection(request.text, request.tab); + return true; + } +}); + +chrome.contextMenus.onClicked.addListener(async function (info, tab) { + if (info.menuItemId === "sendToHarmony") { + if (tab?.id === -1 || tab?.url?.toLowerCase().includes("pdf")) { + // For PDF tabs, show popup + chrome.action.openPopup(); + return; + } + + // For non-PDF tabs, use scripting API + try { + const resultArray = await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + function: () => { + const selection = document.getSelection(); + return selection ? selection.toString() : ""; + }, + }); + const selectedText = resultArray[0]?.result || ""; + if (selectedText) { + processSelection(selectedText, tab); + } + } catch (error) { + console.error("Error getting selected text:", error); + chrome.action.setBadgeText({ text: "!" }); + chrome.action.setBadgeBackgroundColor({ color: "#F44336" }); + setTimeout(() => { + chrome.action.setBadgeText({ text: "" }); + }, 2000); + } + } +}); + +async function processSelection(selectedText, tab) { + if (!selectedText) { + return; // Handle cases where no text is selected + } + + // Process the selected text here... + const questionsArray = selectedText + .split(/\r?\n|\s*/i) + .filter((line) => line.trim() !== ""); + + try { + // Create the Harmony URL with the selected text as a question + const harmonyUrl = createHarmonyUrl({ + questions: questionsArray, + instrument_name: `Imported from ${tab.title} ${tab.url}`, + }); + + // Store in history + chrome.storage.local.get(["history"], function (result) { + const history = result.history || []; + history.unshift({ + text: + selectedText.substring(0, 100) + + (selectedText.length > 100 ? "..." : ""), + url: tab.url, + timestamp: new Date().toISOString(), + harmonyUrl: harmonyUrl, + }); + // Keep only last 10 items + if (history.length > 10) history.pop(); + chrome.storage.local.set({ history: history }); + }); + + // Open or update the Harmony tab + await findOrCreateHarmonyTab(harmonyUrl); + + // Show success notification + chrome.action.setBadgeText({ text: "✓" }); + chrome.action.setBadgeBackgroundColor({ color: "#4CAF50" }); + setTimeout(() => { + chrome.action.setBadgeText({ text: "" }); + }, 2000); + } catch (error) { + // Show error notification + chrome.action.setBadgeText({ text: "!" }); + chrome.action.setBadgeBackgroundColor({ color: "#F44336" }); + setTimeout(() => { + chrome.action.setBadgeText({ text: "" }); + }, 2000); + console.error("Error:", error); + } +} diff --git a/plugin/icons/128.png b/plugin/icons/128.png new file mode 100644 index 0000000..b966681 Binary files /dev/null and b/plugin/icons/128.png differ diff --git a/plugin/icons/16.png b/plugin/icons/16.png new file mode 100644 index 0000000..8a249d6 Binary files /dev/null and b/plugin/icons/16.png differ diff --git a/plugin/icons/48.png b/plugin/icons/48.png new file mode 100644 index 0000000..3473e60 Binary files /dev/null and b/plugin/icons/48.png differ diff --git a/plugin/icons/Thumbs.db b/plugin/icons/Thumbs.db new file mode 100644 index 0000000..cba85a4 Binary files /dev/null and b/plugin/icons/Thumbs.db differ diff --git a/plugin/js-base64/LICENSE.md b/plugin/js-base64/LICENSE.md new file mode 100644 index 0000000..fd579a4 --- /dev/null +++ b/plugin/js-base64/LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2014, Dan Kogai +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of {{{project}}} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugin/js-base64/README.md b/plugin/js-base64/README.md new file mode 100644 index 0000000..19b0709 --- /dev/null +++ b/plugin/js-base64/README.md @@ -0,0 +1,169 @@ +[![CI via GitHub Actions](https://github.com/dankogai/js-base64/actions/workflows/node.js.yml/badge.svg)](https://github.com/dankogai/js-base64/actions/workflows/node.js.yml) + +# base64.js + +Yet another [Base64] transcoder. + +[Base64]: http://en.wikipedia.org/wiki/Base64 + +## Install + +```shell +$ npm install --save js-base64 +``` + +## Usage + +### In Browser + +Locally… + +```html + +``` + +… or Directly from CDN. In which case you don't even need to install. + +```html + +``` + +This good old way loads `Base64` in the global context (`window`). Though `Base64.noConflict()` is made available, you should consider using ES6 Module to avoid tainting `window`. + +### As an ES6 Module + +locally… + +```javascript +import { Base64 } from 'js-base64'; +``` + +```javascript +// or if you prefer no Base64 namespace +import { encode, decode } from 'js-base64'; +``` + +or even remotely. + +```html + +``` + +```html + +``` + +### node.js (commonjs) + +```javascript +const {Base64} = require('js-base64'); +``` + +Unlike the case above, the global context is no longer modified. + +You can also use [esm] to `import` instead of `require`. + +[esm]: https://github.com/standard-things/esm + +```javascript +require=require('esm')(module); +import {Base64} from 'js-base64'; +``` + +## SYNOPSIS + +```javascript +let latin = 'dankogai'; +let utf8 = '小飼弾' +let u8s = new Uint8Array([100,97,110,107,111,103,97,105]); +Base64.encode(latin); // ZGFua29nYWk= +Base64.encode(latin, true); // ZGFua29nYWk skips padding +Base64.encodeURI(latin); // ZGFua29nYWk +Base64.btoa(latin); // ZGFua29nYWk= +Base64.btoa(utf8); // raises exception +Base64.fromUint8Array(u8s); // ZGFua29nYWk= +Base64.fromUint8Array(u8s, true); // ZGFua29nYW which is URI safe +Base64.encode(utf8); // 5bCP6aO85by+ +Base64.encode(utf8, true) // 5bCP6aO85by- +Base64.encodeURI(utf8); // 5bCP6aO85by- +``` + +```javascript +Base64.decode( 'ZGFua29nYWk=');// dankogai +Base64.decode( 'ZGFua29nYWk'); // dankogai +Base64.atob( 'ZGFua29nYWk=');// dankogai +Base64.atob( '5bCP6aO85by+');// '小飼弾' which is nonsense +Base64.toUint8Array('ZGFua29nYWk=');// u8s above +Base64.decode( '5bCP6aO85by+');// 小飼弾 +// note .decodeURI() is unnecessary since it accepts both flavors +Base64.decode( '5bCP6aO85by-');// 小飼弾 +``` + +```javascript +Base64.isValid(0); // false: 0 is not string +Base64.isValid(''); // true: a valid Base64-encoded empty byte +Base64.isValid('ZA=='); // true: a valid Base64-encoded 'd' +Base64.isValid('Z A='); // true: whitespaces are okay +Base64.isValid('ZA'); // true: padding ='s can be omitted +Base64.isValid('++'); // true: can be non URL-safe +Base64.isValid('--'); // true: or URL-safe +Base64.isValid('+-'); // false: can't mix both +``` + +### Built-in Extensions + +By default `Base64` leaves built-in prototypes untouched. But you can extend them as below. + +```javascript +// you have to explicitly extend String.prototype +Base64.extendString(); +// once extended, you can do the following +'dankogai'.toBase64(); // ZGFua29nYWk= +'小飼弾'.toBase64(); // 5bCP6aO85by+ +'小飼弾'.toBase64(true); // 5bCP6aO85by- +'小飼弾'.toBase64URI(); // 5bCP6aO85by- ab alias of .toBase64(true) +'小飼弾'.toBase64URL(); // 5bCP6aO85by- an alias of .toBase64URI() +'ZGFua29nYWk='.fromBase64(); // dankogai +'5bCP6aO85by+'.fromBase64(); // 小飼弾 +'5bCP6aO85by-'.fromBase64(); // 小飼弾 +'5bCP6aO85by-'.toUint8Array();// u8s above +``` + +```javascript +// you have to explicitly extend Uint8Array.prototype +Base64.extendUint8Array(); +// once extended, you can do the following +u8s.toBase64(); // 'ZGFua29nYWk=' +u8s.toBase64URI(); // 'ZGFua29nYWk' +u8s.toBase64URL(); // 'ZGFua29nYWk' an alias of .toBase64URI() +``` + +```javascript +// extend all at once +Base64.extendBuiltins() +``` + +## `.decode()` vs `.atob` (and `.encode()` vs `btoa()`) + +Suppose you have: + +``` +var pngBase64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; +``` + +Which is a Base64-encoded 1x1 transparent PNG, **DO NOT USE** `Base64.decode(pngBase64)`.  Use `Base64.atob(pngBase64)` instead.  `Base64.decode()` decodes to UTF-8 string while `Base64.atob()` decodes to bytes, which is compatible to browser built-in `atob()` (Which is absent in node.js).  The same rule applies to the opposite direction. + +Or even better, `Base64.toUint8Array(pngBase64)`. + +## Brief History + +* Since version 3.3 it is written in TypeScript. Now `base64.mjs` is compiled from `base64.ts` then `base64.js` is generated from `base64.mjs`. +* Since version 3.7 `base64.js` is ES5-compatible again (hence IE11-compatible). +* Since 3.0 `js-base64` switch to ES2015 module so it is no longer compatible with legacy browsers like IE (see above) diff --git a/plugin/js-base64/base64.d.mts b/plugin/js-base64/base64.d.mts new file mode 100644 index 0000000..e44249c --- /dev/null +++ b/plugin/js-base64/base64.d.mts @@ -0,0 +1,135 @@ +/** + * base64.ts + * + * Licensed under the BSD 3-Clause License. + * http://opensource.org/licenses/BSD-3-Clause + * + * References: + * http://en.wikipedia.org/wiki/Base64 + * + * @author Dan Kogai (https://github.com/dankogai) + */ +declare const version = "3.7.7"; +/** + * @deprecated use lowercase `version`. + */ +declare const VERSION = "3.7.7"; +/** + * polyfill version of `btoa` + */ +declare const btoaPolyfill: (bin: string) => string; +/** + * does what `window.btoa` of web browsers do. + * @param {String} bin binary string + * @returns {string} Base64-encoded string + */ +declare const _btoa: (bin: string) => string; +/** + * converts a Uint8Array to a Base64 string. + * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5 + * @returns {string} Base64 string + */ +declare const fromUint8Array: (u8a: Uint8Array, urlsafe?: boolean) => string; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-8 string + * @returns {string} UTF-16 string + */ +declare const utob: (u: string) => string; +/** + * converts a UTF-8-encoded string to a Base64 string. + * @param {boolean} [urlsafe] if `true` make the result URL-safe + * @returns {string} Base64 string + */ +declare const encode: (src: string, urlsafe?: boolean) => string; +/** + * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5. + * @returns {string} Base64 string + */ +declare const encodeURI: (src: string) => string; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-16 string + * @returns {string} UTF-8 string + */ +declare const btou: (b: string) => string; +/** + * polyfill version of `atob` + */ +declare const atobPolyfill: (asc: string) => string; +/** + * does what `window.atob` of web browsers do. + * @param {String} asc Base64-encoded string + * @returns {string} binary string + */ +declare const _atob: (asc: string) => string; +/** + * converts a Base64 string to a Uint8Array. + */ +declare const toUint8Array: (a: string) => Uint8Array; +/** + * converts a Base64 string to a UTF-8 string. + * @param {String} src Base64 string. Both normal and URL-safe are supported + * @returns {string} UTF-8 string + */ +declare const decode: (src: string) => string; +/** + * check if a value is a valid Base64 string + * @param {String} src a value to check + */ +declare const isValid: (src: any) => boolean; +/** + * extend String.prototype with relevant methods + */ +declare const extendString: () => void; +/** + * extend Uint8Array.prototype with relevant methods + */ +declare const extendUint8Array: () => void; +/** + * extend Builtin prototypes with relevant methods + */ +declare const extendBuiltins: () => void; +declare const gBase64: { + version: string; + VERSION: string; + atob: (asc: string) => string; + atobPolyfill: (asc: string) => string; + btoa: (bin: string) => string; + btoaPolyfill: (bin: string) => string; + fromBase64: (src: string) => string; + toBase64: (src: string, urlsafe?: boolean) => string; + encode: (src: string, urlsafe?: boolean) => string; + encodeURI: (src: string) => string; + encodeURL: (src: string) => string; + utob: (u: string) => string; + btou: (b: string) => string; + decode: (src: string) => string; + isValid: (src: any) => boolean; + fromUint8Array: (u8a: Uint8Array, urlsafe?: boolean) => string; + toUint8Array: (a: string) => Uint8Array; + extendString: () => void; + extendUint8Array: () => void; + extendBuiltins: () => void; +}; +export { version }; +export { VERSION }; +export { _atob as atob }; +export { atobPolyfill }; +export { _btoa as btoa }; +export { btoaPolyfill }; +export { decode as fromBase64 }; +export { encode as toBase64 }; +export { utob }; +export { encode }; +export { encodeURI }; +export { encodeURI as encodeURL }; +export { btou }; +export { decode }; +export { isValid }; +export { fromUint8Array }; +export { toUint8Array }; +export { extendString }; +export { extendUint8Array }; +export { extendBuiltins }; +export { gBase64 as Base64 }; diff --git a/plugin/js-base64/base64.d.ts b/plugin/js-base64/base64.d.ts new file mode 100644 index 0000000..e44249c --- /dev/null +++ b/plugin/js-base64/base64.d.ts @@ -0,0 +1,135 @@ +/** + * base64.ts + * + * Licensed under the BSD 3-Clause License. + * http://opensource.org/licenses/BSD-3-Clause + * + * References: + * http://en.wikipedia.org/wiki/Base64 + * + * @author Dan Kogai (https://github.com/dankogai) + */ +declare const version = "3.7.7"; +/** + * @deprecated use lowercase `version`. + */ +declare const VERSION = "3.7.7"; +/** + * polyfill version of `btoa` + */ +declare const btoaPolyfill: (bin: string) => string; +/** + * does what `window.btoa` of web browsers do. + * @param {String} bin binary string + * @returns {string} Base64-encoded string + */ +declare const _btoa: (bin: string) => string; +/** + * converts a Uint8Array to a Base64 string. + * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5 + * @returns {string} Base64 string + */ +declare const fromUint8Array: (u8a: Uint8Array, urlsafe?: boolean) => string; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-8 string + * @returns {string} UTF-16 string + */ +declare const utob: (u: string) => string; +/** + * converts a UTF-8-encoded string to a Base64 string. + * @param {boolean} [urlsafe] if `true` make the result URL-safe + * @returns {string} Base64 string + */ +declare const encode: (src: string, urlsafe?: boolean) => string; +/** + * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5. + * @returns {string} Base64 string + */ +declare const encodeURI: (src: string) => string; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-16 string + * @returns {string} UTF-8 string + */ +declare const btou: (b: string) => string; +/** + * polyfill version of `atob` + */ +declare const atobPolyfill: (asc: string) => string; +/** + * does what `window.atob` of web browsers do. + * @param {String} asc Base64-encoded string + * @returns {string} binary string + */ +declare const _atob: (asc: string) => string; +/** + * converts a Base64 string to a Uint8Array. + */ +declare const toUint8Array: (a: string) => Uint8Array; +/** + * converts a Base64 string to a UTF-8 string. + * @param {String} src Base64 string. Both normal and URL-safe are supported + * @returns {string} UTF-8 string + */ +declare const decode: (src: string) => string; +/** + * check if a value is a valid Base64 string + * @param {String} src a value to check + */ +declare const isValid: (src: any) => boolean; +/** + * extend String.prototype with relevant methods + */ +declare const extendString: () => void; +/** + * extend Uint8Array.prototype with relevant methods + */ +declare const extendUint8Array: () => void; +/** + * extend Builtin prototypes with relevant methods + */ +declare const extendBuiltins: () => void; +declare const gBase64: { + version: string; + VERSION: string; + atob: (asc: string) => string; + atobPolyfill: (asc: string) => string; + btoa: (bin: string) => string; + btoaPolyfill: (bin: string) => string; + fromBase64: (src: string) => string; + toBase64: (src: string, urlsafe?: boolean) => string; + encode: (src: string, urlsafe?: boolean) => string; + encodeURI: (src: string) => string; + encodeURL: (src: string) => string; + utob: (u: string) => string; + btou: (b: string) => string; + decode: (src: string) => string; + isValid: (src: any) => boolean; + fromUint8Array: (u8a: Uint8Array, urlsafe?: boolean) => string; + toUint8Array: (a: string) => Uint8Array; + extendString: () => void; + extendUint8Array: () => void; + extendBuiltins: () => void; +}; +export { version }; +export { VERSION }; +export { _atob as atob }; +export { atobPolyfill }; +export { _btoa as btoa }; +export { btoaPolyfill }; +export { decode as fromBase64 }; +export { encode as toBase64 }; +export { utob }; +export { encode }; +export { encodeURI }; +export { encodeURI as encodeURL }; +export { btou }; +export { decode }; +export { isValid }; +export { fromUint8Array }; +export { toUint8Array }; +export { extendString }; +export { extendUint8Array }; +export { extendBuiltins }; +export { gBase64 as Base64 }; diff --git a/plugin/js-base64/base64.js b/plugin/js-base64/base64.js new file mode 100644 index 0000000..dc837a7 --- /dev/null +++ b/plugin/js-base64/base64.js @@ -0,0 +1,314 @@ +// +// THIS FILE IS AUTOMATICALLY GENERATED! DO NOT EDIT BY HAND! +// +; +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' + ? module.exports = factory() + : typeof define === 'function' && define.amd + ? define(factory) : + // cf. https://github.com/dankogai/js-base64/issues/119 + (function () { + // existing version for noConflict() + var _Base64 = global.Base64; + var gBase64 = factory(); + gBase64.noConflict = function () { + global.Base64 = _Base64; + return gBase64; + }; + if (global.Meteor) { // Meteor.js + Base64 = gBase64; + } + global.Base64 = gBase64; + })(); +}((typeof self !== 'undefined' ? self + : typeof window !== 'undefined' ? window + : typeof global !== 'undefined' ? global + : this), function () { + 'use strict'; + /** + * base64.ts + * + * Licensed under the BSD 3-Clause License. + * http://opensource.org/licenses/BSD-3-Clause + * + * References: + * http://en.wikipedia.org/wiki/Base64 + * + * @author Dan Kogai (https://github.com/dankogai) + */ + var version = '3.7.7'; + /** + * @deprecated use lowercase `version`. + */ + var VERSION = version; + var _hasBuffer = typeof Buffer === 'function'; + var _TD = typeof TextDecoder === 'function' ? new TextDecoder() : undefined; + var _TE = typeof TextEncoder === 'function' ? new TextEncoder() : undefined; + var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var b64chs = Array.prototype.slice.call(b64ch); + var b64tab = (function (a) { + var tab = {}; + a.forEach(function (c, i) { return tab[c] = i; }); + return tab; + })(b64chs); + var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; + var _fromCC = String.fromCharCode.bind(String); + var _U8Afrom = typeof Uint8Array.from === 'function' + ? Uint8Array.from.bind(Uint8Array) + : function (it) { return new Uint8Array(Array.prototype.slice.call(it, 0)); }; + var _mkUriSafe = function (src) { return src + .replace(/=/g, '').replace(/[+\/]/g, function (m0) { return m0 == '+' ? '-' : '_'; }); }; + var _tidyB64 = function (s) { return s.replace(/[^A-Za-z0-9\+\/]/g, ''); }; + /** + * polyfill version of `btoa` + */ + var btoaPolyfill = function (bin) { + // console.log('polyfilled'); + var u32, c0, c1, c2, asc = ''; + var pad = bin.length % 3; + for (var i = 0; i < bin.length;) { + if ((c0 = bin.charCodeAt(i++)) > 255 || + (c1 = bin.charCodeAt(i++)) > 255 || + (c2 = bin.charCodeAt(i++)) > 255) + throw new TypeError('invalid character found'); + u32 = (c0 << 16) | (c1 << 8) | c2; + asc += b64chs[u32 >> 18 & 63] + + b64chs[u32 >> 12 & 63] + + b64chs[u32 >> 6 & 63] + + b64chs[u32 & 63]; + } + return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; + }; + /** + * does what `window.btoa` of web browsers do. + * @param {String} bin binary string + * @returns {string} Base64-encoded string + */ + var _btoa = typeof btoa === 'function' ? function (bin) { return btoa(bin); } + : _hasBuffer ? function (bin) { return Buffer.from(bin, 'binary').toString('base64'); } + : btoaPolyfill; + var _fromUint8Array = _hasBuffer + ? function (u8a) { return Buffer.from(u8a).toString('base64'); } + : function (u8a) { + // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326 + var maxargs = 0x1000; + var strs = []; + for (var i = 0, l = u8a.length; i < l; i += maxargs) { + strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs))); + } + return _btoa(strs.join('')); + }; + /** + * converts a Uint8Array to a Base64 string. + * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5 + * @returns {string} Base64 string + */ + var fromUint8Array = function (u8a, urlsafe) { + if (urlsafe === void 0) { urlsafe = false; } + return urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a); + }; + // This trick is found broken https://github.com/dankogai/js-base64/issues/130 + // const utob = (src: string) => unescape(encodeURIComponent(src)); + // reverting good old fationed regexp + var cb_utob = function (c) { + if (c.length < 2) { + var cc = c.charCodeAt(0); + return cc < 0x80 ? c + : cc < 0x800 ? (_fromCC(0xc0 | (cc >>> 6)) + + _fromCC(0x80 | (cc & 0x3f))) + : (_fromCC(0xe0 | ((cc >>> 12) & 0x0f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } + else { + var cc = 0x10000 + + (c.charCodeAt(0) - 0xD800) * 0x400 + + (c.charCodeAt(1) - 0xDC00); + return (_fromCC(0xf0 | ((cc >>> 18) & 0x07)) + + _fromCC(0x80 | ((cc >>> 12) & 0x3f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } + }; + var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; + /** + * @deprecated should have been internal use only. + * @param {string} src UTF-8 string + * @returns {string} UTF-16 string + */ + var utob = function (u) { return u.replace(re_utob, cb_utob); }; + // + var _encode = _hasBuffer + ? function (s) { return Buffer.from(s, 'utf8').toString('base64'); } + : _TE + ? function (s) { return _fromUint8Array(_TE.encode(s)); } + : function (s) { return _btoa(utob(s)); }; + /** + * converts a UTF-8-encoded string to a Base64 string. + * @param {boolean} [urlsafe] if `true` make the result URL-safe + * @returns {string} Base64 string + */ + var encode = function (src, urlsafe) { + if (urlsafe === void 0) { urlsafe = false; } + return urlsafe + ? _mkUriSafe(_encode(src)) + : _encode(src); + }; + /** + * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5. + * @returns {string} Base64 string + */ + var encodeURI = function (src) { return encode(src, true); }; + // This trick is found broken https://github.com/dankogai/js-base64/issues/130 + // const btou = (src: string) => decodeURIComponent(escape(src)); + // reverting good old fationed regexp + var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; + var cb_btou = function (cccc) { + switch (cccc.length) { + case 4: + var cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000; + return (_fromCC((offset >>> 10) + 0xD800) + + _fromCC((offset & 0x3FF) + 0xDC00)); + case 3: + return _fromCC(((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2))); + default: + return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1))); + } + }; + /** + * @deprecated should have been internal use only. + * @param {string} src UTF-16 string + * @returns {string} UTF-8 string + */ + var btou = function (b) { return b.replace(re_btou, cb_btou); }; + /** + * polyfill version of `atob` + */ + var atobPolyfill = function (asc) { + // console.log('polyfilled'); + asc = asc.replace(/\s+/g, ''); + if (!b64re.test(asc)) + throw new TypeError('malformed base64.'); + asc += '=='.slice(2 - (asc.length & 3)); + var u24, bin = '', r1, r2; + for (var i = 0; i < asc.length;) { + u24 = b64tab[asc.charAt(i++)] << 18 + | b64tab[asc.charAt(i++)] << 12 + | (r1 = b64tab[asc.charAt(i++)]) << 6 + | (r2 = b64tab[asc.charAt(i++)]); + bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) + : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) + : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); + } + return bin; + }; + /** + * does what `window.atob` of web browsers do. + * @param {String} asc Base64-encoded string + * @returns {string} binary string + */ + var _atob = typeof atob === 'function' ? function (asc) { return atob(_tidyB64(asc)); } + : _hasBuffer ? function (asc) { return Buffer.from(asc, 'base64').toString('binary'); } + : atobPolyfill; + // + var _toUint8Array = _hasBuffer + ? function (a) { return _U8Afrom(Buffer.from(a, 'base64')); } + : function (a) { return _U8Afrom(_atob(a).split('').map(function (c) { return c.charCodeAt(0); })); }; + /** + * converts a Base64 string to a Uint8Array. + */ + var toUint8Array = function (a) { return _toUint8Array(_unURI(a)); }; + // + var _decode = _hasBuffer + ? function (a) { return Buffer.from(a, 'base64').toString('utf8'); } + : _TD + ? function (a) { return _TD.decode(_toUint8Array(a)); } + : function (a) { return btou(_atob(a)); }; + var _unURI = function (a) { return _tidyB64(a.replace(/[-_]/g, function (m0) { return m0 == '-' ? '+' : '/'; })); }; + /** + * converts a Base64 string to a UTF-8 string. + * @param {String} src Base64 string. Both normal and URL-safe are supported + * @returns {string} UTF-8 string + */ + var decode = function (src) { return _decode(_unURI(src)); }; + /** + * check if a value is a valid Base64 string + * @param {String} src a value to check + */ + var isValid = function (src) { + if (typeof src !== 'string') + return false; + var s = src.replace(/\s+/g, '').replace(/={0,2}$/, ''); + return !/[^\s0-9a-zA-Z\+/]/.test(s) || !/[^\s0-9a-zA-Z\-_]/.test(s); + }; + // + var _noEnum = function (v) { + return { + value: v, enumerable: false, writable: true, configurable: true + }; + }; + /** + * extend String.prototype with relevant methods + */ + var extendString = function () { + var _add = function (name, body) { return Object.defineProperty(String.prototype, name, _noEnum(body)); }; + _add('fromBase64', function () { return decode(this); }); + _add('toBase64', function (urlsafe) { return encode(this, urlsafe); }); + _add('toBase64URI', function () { return encode(this, true); }); + _add('toBase64URL', function () { return encode(this, true); }); + _add('toUint8Array', function () { return toUint8Array(this); }); + }; + /** + * extend Uint8Array.prototype with relevant methods + */ + var extendUint8Array = function () { + var _add = function (name, body) { return Object.defineProperty(Uint8Array.prototype, name, _noEnum(body)); }; + _add('toBase64', function (urlsafe) { return fromUint8Array(this, urlsafe); }); + _add('toBase64URI', function () { return fromUint8Array(this, true); }); + _add('toBase64URL', function () { return fromUint8Array(this, true); }); + }; + /** + * extend Builtin prototypes with relevant methods + */ + var extendBuiltins = function () { + extendString(); + extendUint8Array(); + }; + var gBase64 = { + version: version, + VERSION: VERSION, + atob: _atob, + atobPolyfill: atobPolyfill, + btoa: _btoa, + btoaPolyfill: btoaPolyfill, + fromBase64: decode, + toBase64: encode, + encode: encode, + encodeURI: encodeURI, + encodeURL: encodeURI, + utob: utob, + btou: btou, + decode: decode, + isValid: isValid, + fromUint8Array: fromUint8Array, + toUint8Array: toUint8Array, + extendString: extendString, + extendUint8Array: extendUint8Array, + extendBuiltins: extendBuiltins + }; + // + // export Base64 to the namespace + // + // ES5 is yet to have Object.assign() that may make transpilers unhappy. + // gBase64.Base64 = Object.assign({}, gBase64); + gBase64.Base64 = {}; + Object.keys(gBase64).forEach(function (k) { return gBase64.Base64[k] = gBase64[k]; }); + return gBase64; +})); diff --git a/plugin/js-base64/base64.mjs b/plugin/js-base64/base64.mjs new file mode 100644 index 0000000..fe9cfa5 --- /dev/null +++ b/plugin/js-base64/base64.mjs @@ -0,0 +1,294 @@ +/** + * base64.ts + * + * Licensed under the BSD 3-Clause License. + * http://opensource.org/licenses/BSD-3-Clause + * + * References: + * http://en.wikipedia.org/wiki/Base64 + * + * @author Dan Kogai (https://github.com/dankogai) + */ +const version = '3.7.7'; +/** + * @deprecated use lowercase `version`. + */ +const VERSION = version; +const _hasBuffer = typeof Buffer === 'function'; +const _TD = typeof TextDecoder === 'function' ? new TextDecoder() : undefined; +const _TE = typeof TextEncoder === 'function' ? new TextEncoder() : undefined; +const b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +const b64chs = Array.prototype.slice.call(b64ch); +const b64tab = ((a) => { + let tab = {}; + a.forEach((c, i) => tab[c] = i); + return tab; +})(b64chs); +const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; +const _fromCC = String.fromCharCode.bind(String); +const _U8Afrom = typeof Uint8Array.from === 'function' + ? Uint8Array.from.bind(Uint8Array) + : (it) => new Uint8Array(Array.prototype.slice.call(it, 0)); +const _mkUriSafe = (src) => src + .replace(/=/g, '').replace(/[+\/]/g, (m0) => m0 == '+' ? '-' : '_'); +const _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\+\/]/g, ''); +/** + * polyfill version of `btoa` + */ +const btoaPolyfill = (bin) => { + // console.log('polyfilled'); + let u32, c0, c1, c2, asc = ''; + const pad = bin.length % 3; + for (let i = 0; i < bin.length;) { + if ((c0 = bin.charCodeAt(i++)) > 255 || + (c1 = bin.charCodeAt(i++)) > 255 || + (c2 = bin.charCodeAt(i++)) > 255) + throw new TypeError('invalid character found'); + u32 = (c0 << 16) | (c1 << 8) | c2; + asc += b64chs[u32 >> 18 & 63] + + b64chs[u32 >> 12 & 63] + + b64chs[u32 >> 6 & 63] + + b64chs[u32 & 63]; + } + return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; +}; +/** + * does what `window.btoa` of web browsers do. + * @param {String} bin binary string + * @returns {string} Base64-encoded string + */ +const _btoa = typeof btoa === 'function' ? (bin) => btoa(bin) + : _hasBuffer ? (bin) => Buffer.from(bin, 'binary').toString('base64') + : btoaPolyfill; +const _fromUint8Array = _hasBuffer + ? (u8a) => Buffer.from(u8a).toString('base64') + : (u8a) => { + // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326 + const maxargs = 0x1000; + let strs = []; + for (let i = 0, l = u8a.length; i < l; i += maxargs) { + strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs))); + } + return _btoa(strs.join('')); + }; +/** + * converts a Uint8Array to a Base64 string. + * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5 + * @returns {string} Base64 string + */ +const fromUint8Array = (u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a); +// This trick is found broken https://github.com/dankogai/js-base64/issues/130 +// const utob = (src: string) => unescape(encodeURIComponent(src)); +// reverting good old fationed regexp +const cb_utob = (c) => { + if (c.length < 2) { + var cc = c.charCodeAt(0); + return cc < 0x80 ? c + : cc < 0x800 ? (_fromCC(0xc0 | (cc >>> 6)) + + _fromCC(0x80 | (cc & 0x3f))) + : (_fromCC(0xe0 | ((cc >>> 12) & 0x0f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } + else { + var cc = 0x10000 + + (c.charCodeAt(0) - 0xD800) * 0x400 + + (c.charCodeAt(1) - 0xDC00); + return (_fromCC(0xf0 | ((cc >>> 18) & 0x07)) + + _fromCC(0x80 | ((cc >>> 12) & 0x3f)) + + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + + _fromCC(0x80 | (cc & 0x3f))); + } +}; +const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-8 string + * @returns {string} UTF-16 string + */ +const utob = (u) => u.replace(re_utob, cb_utob); +// +const _encode = _hasBuffer + ? (s) => Buffer.from(s, 'utf8').toString('base64') + : _TE + ? (s) => _fromUint8Array(_TE.encode(s)) + : (s) => _btoa(utob(s)); +/** + * converts a UTF-8-encoded string to a Base64 string. + * @param {boolean} [urlsafe] if `true` make the result URL-safe + * @returns {string} Base64 string + */ +const encode = (src, urlsafe = false) => urlsafe + ? _mkUriSafe(_encode(src)) + : _encode(src); +/** + * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5. + * @returns {string} Base64 string + */ +const encodeURI = (src) => encode(src, true); +// This trick is found broken https://github.com/dankogai/js-base64/issues/130 +// const btou = (src: string) => decodeURIComponent(escape(src)); +// reverting good old fationed regexp +const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; +const cb_btou = (cccc) => { + switch (cccc.length) { + case 4: + var cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)), offset = cp - 0x10000; + return (_fromCC((offset >>> 10) + 0xD800) + + _fromCC((offset & 0x3FF) + 0xDC00)); + case 3: + return _fromCC(((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2))); + default: + return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1))); + } +}; +/** + * @deprecated should have been internal use only. + * @param {string} src UTF-16 string + * @returns {string} UTF-8 string + */ +const btou = (b) => b.replace(re_btou, cb_btou); +/** + * polyfill version of `atob` + */ +const atobPolyfill = (asc) => { + // console.log('polyfilled'); + asc = asc.replace(/\s+/g, ''); + if (!b64re.test(asc)) + throw new TypeError('malformed base64.'); + asc += '=='.slice(2 - (asc.length & 3)); + let u24, bin = '', r1, r2; + for (let i = 0; i < asc.length;) { + u24 = b64tab[asc.charAt(i++)] << 18 + | b64tab[asc.charAt(i++)] << 12 + | (r1 = b64tab[asc.charAt(i++)]) << 6 + | (r2 = b64tab[asc.charAt(i++)]); + bin += r1 === 64 ? _fromCC(u24 >> 16 & 255) + : r2 === 64 ? _fromCC(u24 >> 16 & 255, u24 >> 8 & 255) + : _fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); + } + return bin; +}; +/** + * does what `window.atob` of web browsers do. + * @param {String} asc Base64-encoded string + * @returns {string} binary string + */ +const _atob = typeof atob === 'function' ? (asc) => atob(_tidyB64(asc)) + : _hasBuffer ? (asc) => Buffer.from(asc, 'base64').toString('binary') + : atobPolyfill; +// +const _toUint8Array = _hasBuffer + ? (a) => _U8Afrom(Buffer.from(a, 'base64')) + : (a) => _U8Afrom(_atob(a).split('').map(c => c.charCodeAt(0))); +/** + * converts a Base64 string to a Uint8Array. + */ +const toUint8Array = (a) => _toUint8Array(_unURI(a)); +// +const _decode = _hasBuffer + ? (a) => Buffer.from(a, 'base64').toString('utf8') + : _TD + ? (a) => _TD.decode(_toUint8Array(a)) + : (a) => btou(_atob(a)); +const _unURI = (a) => _tidyB64(a.replace(/[-_]/g, (m0) => m0 == '-' ? '+' : '/')); +/** + * converts a Base64 string to a UTF-8 string. + * @param {String} src Base64 string. Both normal and URL-safe are supported + * @returns {string} UTF-8 string + */ +const decode = (src) => _decode(_unURI(src)); +/** + * check if a value is a valid Base64 string + * @param {String} src a value to check + */ +const isValid = (src) => { + if (typeof src !== 'string') + return false; + const s = src.replace(/\s+/g, '').replace(/={0,2}$/, ''); + return !/[^\s0-9a-zA-Z\+/]/.test(s) || !/[^\s0-9a-zA-Z\-_]/.test(s); +}; +// +const _noEnum = (v) => { + return { + value: v, enumerable: false, writable: true, configurable: true + }; +}; +/** + * extend String.prototype with relevant methods + */ +const extendString = function () { + const _add = (name, body) => Object.defineProperty(String.prototype, name, _noEnum(body)); + _add('fromBase64', function () { return decode(this); }); + _add('toBase64', function (urlsafe) { return encode(this, urlsafe); }); + _add('toBase64URI', function () { return encode(this, true); }); + _add('toBase64URL', function () { return encode(this, true); }); + _add('toUint8Array', function () { return toUint8Array(this); }); +}; +/** + * extend Uint8Array.prototype with relevant methods + */ +const extendUint8Array = function () { + const _add = (name, body) => Object.defineProperty(Uint8Array.prototype, name, _noEnum(body)); + _add('toBase64', function (urlsafe) { return fromUint8Array(this, urlsafe); }); + _add('toBase64URI', function () { return fromUint8Array(this, true); }); + _add('toBase64URL', function () { return fromUint8Array(this, true); }); +}; +/** + * extend Builtin prototypes with relevant methods + */ +const extendBuiltins = () => { + extendString(); + extendUint8Array(); +}; +const gBase64 = { + version: version, + VERSION: VERSION, + atob: _atob, + atobPolyfill: atobPolyfill, + btoa: _btoa, + btoaPolyfill: btoaPolyfill, + fromBase64: decode, + toBase64: encode, + encode: encode, + encodeURI: encodeURI, + encodeURL: encodeURI, + utob: utob, + btou: btou, + decode: decode, + isValid: isValid, + fromUint8Array: fromUint8Array, + toUint8Array: toUint8Array, + extendString: extendString, + extendUint8Array: extendUint8Array, + extendBuiltins: extendBuiltins +}; +// makecjs:CUT // +export { version }; +export { VERSION }; +export { _atob as atob }; +export { atobPolyfill }; +export { _btoa as btoa }; +export { btoaPolyfill }; +export { decode as fromBase64 }; +export { encode as toBase64 }; +export { utob }; +export { encode }; +export { encodeURI }; +export { encodeURI as encodeURL }; +export { btou }; +export { decode }; +export { isValid }; +export { fromUint8Array }; +export { toUint8Array }; +export { extendString }; +export { extendUint8Array }; +export { extendBuiltins }; +// and finally, +export { gBase64 as Base64 }; diff --git a/plugin/js-base64/package.json b/plugin/js-base64/package.json new file mode 100644 index 0000000..477bbc5 --- /dev/null +++ b/plugin/js-base64/package.json @@ -0,0 +1,43 @@ +{ + "name": "js-base64", + "version": "3.7.7", + "description": "Yet another Base64 transcoder in pure-JS", + "main": "base64.js", + "module": "base64.mjs", + "types": "base64.d.ts", + "sideEffects": false, + "files": [ + "base64.js", + "base64.mjs", + "base64.d.ts", + "base64.d.mts" + ], + "exports": { + ".": { + "import": { + "types": "./base64.d.mts", + "default": "./base64.mjs" + }, + "require": { + "types": "./base64.d.ts", + "default": "./base64.js" + } + }, + "./package.json": "./package.json" + }, + "scripts": { + "test": "make clean && make test" + }, + "devDependencies": { + "@types/node": "^20.11.5", + "mocha": "^10.2.0", + "typescript": "^5.3.3" + }, + "repository": "git+https://github.com/dankogai/js-base64.git", + "keywords": [ + "base64", + "binary" + ], + "author": "Dan Kogai", + "license": "BSD-3-Clause" +} diff --git a/plugin/manifest.json b/plugin/manifest.json new file mode 100644 index 0000000..c549681 --- /dev/null +++ b/plugin/manifest.json @@ -0,0 +1,31 @@ +{ + "manifest_version": 3, + "name": "Send to Harmony", + "version": "1.0", + "description": "Send selected text to Harmony with a right-click. For PDFs, use the popup to paste your selected text.", + "permissions": ["contextMenus", "storage", "scripting", "activeTab", "tabs"], + "icons": { + "16": "icons/16.png", + "48": "icons/48.png", + "128": "icons/128.png" + }, + "background": { + "service_worker": "background.js" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/16.png", + "48": "icons/48.png", + "128": "icons/128.png" + }, + "context_menus": [ + { + "id": "sendToHarmony", + "title": "Send to Harmony", + "contexts": ["selection"] + } + ] + }, + "host_permissions": [] +} diff --git a/plugin/popup.html b/plugin/popup.html new file mode 100644 index 0000000..ac4e106 --- /dev/null +++ b/plugin/popup.html @@ -0,0 +1,109 @@ + + + + Send to Harmony + + + +
+ +

Send to Harmony

+

+ Select text you would like to harmonise, right-click, and choose "Send + to Harmony". +

+

+ Harmony will open in a new tab with your items ready to harmonise, vist + other websites to add further items to your harmonisation. +

+ + + +
+

Recent Imports

+
+
No imports yet
+
+
+
+ + + diff --git a/plugin/popup.js b/plugin/popup.js new file mode 100644 index 0000000..1ef2f02 --- /dev/null +++ b/plugin/popup.js @@ -0,0 +1,89 @@ +// Check if we're dealing with a PDF tab +chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + const currentTab = tabs[0]; + if (currentTab?.id === -1 || currentTab?.url?.toLowerCase().includes("pdf")) { + document.getElementById("pdfInput").style.display = "block"; + } +}); + +// Handle PDF text submission +document.getElementById("submitPdf").addEventListener("click", function () { + const text = document.getElementById("pdfText").value; + if (text) { + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + const currentTab = tabs[0]; + chrome.runtime.sendMessage({ + action: "processPdfText", + text: text, + tab: currentTab, + }); + window.close(); + }); + } +}); + +// Function to format relative time +function getRelativeTime(timestamp) { + const now = new Date(); + const past = new Date(timestamp); + const diffInSeconds = Math.floor((now - past) / 1000); + + if (diffInSeconds < 60) { + return "just now"; + } else if (diffInSeconds < 3600) { + const minutes = Math.floor(diffInSeconds / 60); + return `${minutes} minute${minutes > 1 ? "s" : ""} ago`; + } else if (diffInSeconds < 86400) { + const hours = Math.floor(diffInSeconds / 3600); + return `${hours} hour${hours > 1 ? "s" : ""} ago`; + } else { + const days = Math.floor(diffInSeconds / 86400); + return `${days} day${days > 1 ? "s" : ""} ago`; + } +} + +// Update history list +function updateHistory() { + chrome.storage.local.get(["history"], function (result) { + const historyList = document.getElementById("historyList"); + const history = result.history || []; + + if (history.length === 0) { + historyList.innerHTML = '
No imports yet
'; + return; + } + + historyList.innerHTML = history + .map( + (item) => ` +
+
${item.text}
+
From: ${item.url}
+
${getRelativeTime(item.timestamp)}
+
+ ` + ) + .join(""); + + // Make history items clickable to reopen in Harmony + const historyItems = historyList.getElementsByClassName("history-item"); + Array.from(historyItems).forEach((item, index) => { + item.style.cursor = "pointer"; + item.addEventListener("click", () => { + // Send message to background script to open URL + chrome.runtime.sendMessage({ + action: "openHarmonyUrl", + url: history[index].harmonyUrl, + }); + // Close the popup + window.close(); + }); + }); + }); +} + +// Update history when popup opens +document.addEventListener("DOMContentLoaded", updateHistory); + +// Update history every minute to refresh relative times +setInterval(updateHistory, 60000); diff --git a/src/components/Upload.js b/src/components/Upload.js index ab55f98..ea42b74 100644 --- a/src/components/Upload.js +++ b/src/components/Upload.js @@ -35,11 +35,9 @@ import { import { useHistory } from "react-router-dom"; import InlineFeedback from "./InlineFeedback"; import ExistingInstruments from "./ExistingInstruments"; -import { simplifyApi } from "../utilities/simplifyApi"; import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import pdfTableExtractor from "../utilities/pdf-table-extractor"; -import { useAuth } from "../contexts/AuthContext"; import { Base64 } from "js-base64"; export default function Upload({ @@ -49,7 +47,6 @@ export default function Upload({ existingInstruments, ReactGA, }) { - const { currentUser } = useAuth(); const [loading, setLoading] = useState(false); const [parseError, setParseError] = useState(false); const [matchError, setMatchError] = useState(false); @@ -59,7 +56,7 @@ export default function Upload({ const dirty = useRef(false); const localFileInfos = useRef(); const history = useHistory(); - const { match, parse, getSharedInstrument } = useData(); + const { parse, getSharedInstrument } = useData(); const { importId } = useParams(); ReactGA.send({