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 @@
+[](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 @@
+
+
+
+ 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. +
+ + + +