quicktext-6.4.4/0000775000175000017500000000000015051542456014235 5ustar mechtildemechtildequicktext-6.4.4/updates.json0000664000175000017500000000071715051542456016602 0ustar mechtildemechtilde{ "addons": { "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}": { "updates": [ { "version": "6.4.3", "update_info_url": "https://github.com/jobisoft/quicktext/releases", "update_link": "https://github.com/jobisoft/quicktext/releases/download/v6.4.3/quicktext_6_4_3.xpi", "applications": { "gecko": { "strict_min_version": "128.0" } } } ] } } }quicktext-6.4.4/LICENSE0000664000175000017500000004052615051542456015251 0ustar mechtildemechtildeMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. quicktext-6.4.4/scripts/0000775000175000017500000000000015051542456015724 5ustar mechtildemechtildequicktext-6.4.4/scripts/compose.js0000664000175000017500000002146715051542456017741 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const alternatives = { "Enter": ["NumpadEnter"] } let keywords, keywordKey, shortcutTypeAdv, shortcutModifier, shortcuts; let advShortcutModifierIsDown = false; let advShortcutString = ""; let popoverShown = false; // ----------------------------------------------------------------------------- async function insertHtmlFragment(message) { // A normal space causes the selection to ignore the space. let space = message.extraSpace ? " " : ""; document.execCommand('insertHtml', false, `${message.insertHtml}${space}`); await handlerCursorTags(); } async function insertTextFragment(message) { let space = message.extraSpace ? " " : ""; document.execCommand('insertText', false, `${message.insertText}${space}`); await handlerCursorTags(); } function requestInsertTemplate(text) { return messenger.runtime.sendMessage({ command: "insertTemplate", group: text[0], text: text[1] }); } async function getSelection(mode) { let selection = window.getSelection(); if (mode == "TEXT") { return selection.toString(); } // https://stackoverflow.com/questions/5083682/get-selected-html-in-browser-via-javascript if (selection.rangeCount > 0) { // It may be beneficial to include the surrounding node // to copy the format // let wrapperNode = selection.anchorNode.parentElement.tagName; let range = selection.getRangeAt(0); let clonedSelection = range.cloneContents(); //let container = document.createElement(wrapperNode); //container.appendChild(clonedSelection); let div = document.createElement('div'); div.appendChild(clonedSelection); return div.innerHTML; } return ""; } async function handlerCursorTags() { const CURSOR = '[[CURSOR]]' try { let items = window.document.evaluate("//*", document, null, XPathResult.ANY_TYPE, null); let foundElements = []; let nextItem; do { if (nextItem && nextItem.childNodes.length > 0) { for (let node of nextItem.childNodes) { if (node.nodeType == 3 && node.nodeValue.includes(CURSOR)) { foundElements.push(node); } } } nextItem = items.iterateNext(); } while (nextItem) if (foundElements.length == 0) { return; } let selection = window.getSelection(); for (let foundElement of foundElements) { let startPos = -1; do { if (startPos != -1) { let range = document.createRange(); range.setStart(foundElement, startPos); range.setEnd(foundElement, startPos + CURSOR.length); selection.removeAllRanges(); selection.addRange(range); // execCommand() does not collapse the leading and trailing // spaces which selection.deleteFromDocument() does. All the // text altering functions from selection and range seem to // collapse spaces. document.execCommand('delete'); //selection.deleteFromDocument(); //selection.getRangeAt(0).deleteContents(); } startPos = foundElement.nodeValue.indexOf(CURSOR); } while (startPos != -1) } } catch (ex) { console.debug(ex); } } // ----------------------------------------------------------------------------- function hasMatchingModifier(e, modifier) { return ( e.altKey && modifier == "alt" || e.ctrlKey && modifier == "control" || e.metaKey && modifier == "meta" ) } function isMatchingModifier(e, modifier) { return ( e.key == "Alt" && modifier == "alt" || e.key == "Control" && modifier == "control" || e.key == "Meta" && modifier == "meta" ) } function isRealNumberKey(e) { return e.key.length == 1 && /^[0-9]$/i.test(e.key); } function keywordListener(e) { if (e.code == keywordKey || alternatives[keywordKey]?.includes(e.code)) { let selection = window.getSelection(); if (!(selection.rangeCount > 0)) { return; } // This gives us a range object of the currently selected text. let initialSelectionRange = selection.getRangeAt(0).cloneRange(); // Get the text from the beginning of the current node to the end of the // selection/cursor. We assume the keyword is not split between two nodes. let range = initialSelectionRange.cloneRange(); range.setStart(range.startContainer, 0); let lastWord = range.toString().split(" ").pop(); if (!lastWord || !keywords.hasOwnProperty(lastWord)) { return; } // We found a valid keyword, eat the keypress. e.stopPropagation(); e.preventDefault(); // Extend selection from the end of the current selection/cursor to the // beginning of the current word. selection.collapseToEnd(); selection.modify("extend", "backward", "word"); // Verify that the entire lastWord is selected, and extend the selection // if needed. This is needed since #hi is not fully extended as # is not // considered to be part of the word. while (selection.toString().length < lastWord.length) { selection.modify("extend", "backward", "character"); } // The following line will remove the keyword before we replace it. If we // do not do that, we see the keyword being selected and then replaced. // It does look interesting, but I keep it as it was before. document.execCommand('delete'); //selection.deleteFromDocument(); //selection.getRangeAt(0).deleteContents(); requestInsertTemplate(keywords[lastWord]) } } function shortcutKeyDown(e) { if (!hasMatchingModifier(e, shortcutModifier)) { return; } if (shortcutTypeAdv) { advShortcutModifierIsDown = true; if (isRealNumberKey(e)) { advShortcutString += e.key; // Eat keys if we acted upon them. e.stopPropagation(); e.preventDefault(); } } else if (isRealNumberKey(e) && shortcuts[e.key] && !e.repeat) { requestInsertTemplate(shortcuts[e.key]); // Eat keys if we acted upon them. e.stopPropagation(); e.preventDefault(); } } async function shortcutKeyUp(e) { if (advShortcutModifierIsDown && shortcutTypeAdv && isMatchingModifier(e, shortcutModifier)) { if (advShortcutString != "" && typeof shortcuts[advShortcutString] != "undefined") { requestInsertTemplate(shortcuts[advShortcutString]); } advShortcutModifierIsDown = false; advShortcutString = ""; } } async function getLatestPrefs() { const storage = await import(browser.runtime.getURL("/modules/storage.mjs")); keywordKey = await storage.getPref("keywordKey"); shortcutTypeAdv = await storage.getPref("shortcutTypeAdv"); shortcutModifier = await storage.getPref("shortcutModifier"); let rv = await messenger.runtime.sendMessage({ command: "getKeywordsAndShortcuts" }); keywords = rv.keywords; shortcuts = rv.shortcuts; } // ----------------------------------------------------------------------------- async function setup() { const storage = await import(browser.runtime.getURL("/modules/storage.mjs")); await getLatestPrefs(); window.addEventListener("keydown", shortcutKeyDown, true); window.addEventListener("keyup", shortcutKeyUp, true); window.addEventListener("keydown", keywordListener, false); new storage.StorageListener( { watchedPrefs: ["templates", "keywordKey", "shortcutTypeAdv", "shortcutModifier"], listener: (changes) => { getLatestPrefs(); } } ) } messenger.runtime.onMessage.addListener((message, sender) => { if (message.insertText) { return insertTextFragment(message); } if (message.insertHtml) { return insertHtmlFragment(message); } if (message.alertLabel) { return Promise.resolve(window.alert(message.alertLabel)); } if (message.getSelection) { return getSelection(message.getSelection) } if (message.isPopoverShown) { return Promise.resolve(popoverShown); } if (message.setPopoverShown) { popoverShown = message.popoverShownValue; return Promise.resolve(); } return false; }); setup(); console.log("Quicktext compose script loaded"); quicktext-6.4.4/scripts/background.js0000664000175000017500000002741415051542456020411 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as quicktext from "../modules/quicktext.mjs"; import * as storage from "../modules/storage.mjs"; import * as menus from "../modules/menus.mjs"; import * as utils from "../modules/utils.mjs"; browser.runtime.onInstalled.addListener(details => { let manifest = browser.runtime.getManifest(); if (!manifest.browser_specific_settings.gecko.update_url) { return }; if (details.reason == "update") { browser.notifications.create("qt-update", { type: "basic", title: "Quicktext v6", message: `Quicktext GitHub Edition was updated to v${manifest.version}. Click for details.`, }); } }); browser.runtime.onMessageExternal.addListener(({ register_script_addon, available_scripts }, { id }) => { if (register_script_addon && available_scripts && available_scripts.length > 0) { return utils.registerExternalScriptAddon(id, register_script_addon, available_scripts); } return false; }); browser.notifications.onClicked.addListener(notificationId => { switch (notificationId) { case "qt-deprecate-default-file-import": browser.tabs.create({ url: `https://github.com/jobisoft/quicktext/wiki/Centrally-manage-configurations-and-templates`, }); break; case "qt-update": browser.tabs.create({ url: `https://github.com/jobisoft/quicktext/releases/tag/v${browser.runtime.getManifest().version}`, }); break; case "qt-bad-entries": browser.Quicktext.openTemplateManager(); break; case "qt-incompatible-scripts": browser.tabs.create({ url: `https://github.com/jobisoft/quicktext/issues/451`, }); break; } }) // Legacy: Register global urls. await browser.LegacyHelper.registerGlobalUrls([ ["content", "quicktext", "xul_settings_dialog/"], ["resource", "quicktext", "."], ]); // Over the years, the storage concept has changed. await storage.migrate(); // Fix invalid options: // - reset the value of shortcutModifier to "alt", if it has not a valid value - see issue #177 const shortcutModifier = await storage.getPref("shortcutModifier"); if (!["alt", "control", "meta"].includes(shortcutModifier)) { await storage.setPref("shortcutModifier", "alt"); } // Legacy: The XML files will be kept for backup, but are read only if they have // not already been migrated to local storage. Uninstalling Quicktext (which // clears the storage) and installing it again, will re-import the XML files. // For the future, users have to be reminded to backup their templates. let templates = await storage.getTemplates(); if (!templates) { try { templates = await quicktext.readLegacyXmlTemplateFile().then(e => e.templates); console.log("Migrating XML template file to JSON stored in local storage."); await storage.setTemplates(templates); } catch { } } if (!templates) { templates = { groups: [], texts: [] }; await storage.setTemplates(templates); } let scripts = await storage.getScripts(); if (!scripts) { try { scripts = await quicktext.readXmlScriptFile().then(e => e.scripts); console.log("Migrating XML script file to JSON stored in local storage.") await storage.setScripts(scripts); } catch { } } if (!scripts) { scripts = []; await storage.setScripts(scripts); } // Remove managed templates. let cleanedTemplates = await utils.removeProtectedTemplates(templates); if (templates != cleanedTemplates) { templates = cleanedTemplates; await storage.setTemplates(templates); } // Remove managed scripts. let cleanedScripts = await utils.removeProtectedScripts(scripts); if (scripts != cleanedScripts) { scripts = cleanedScripts; await storage.setScripts(scripts); } // Startup import. const defaultImport = await storage.getPref("defaultImport"); if (defaultImport) { const defaultImports = defaultImport.split(";").map(e => e.trim()).reverse(); for (let path of defaultImports) { if (!path.match(/^(http|https):\/\//)) { browser.notifications.create("qt-deprecate-default-file-import", { type: "basic", title: "Quicktext v6", message: `Default imports from the local file system have been replaced by managed storage.\n(click for details)`, }); continue; } try { // Import XML or JSON config data from remote server. const data = await utils.fetchFileAsText(path); const imports = await quicktext.parseConfigFileData(data); if (imports.templates) { quicktext.mergeTemplates(templates, imports.templates, true); } if (imports.scripts) { quicktext.mergeScripts(scripts, imports.scripts, true); } } catch (e) { console.error(e); } } await storage.setTemplates(templates); await storage.setScripts(scripts); } // Startup import via managed storage. try { let { templates: managedTemplates } = await browser.storage.managed.get({ templates: null }); if (managedTemplates) { quicktext.mergeTemplates(templates, managedTemplates, true); } let { scripts: managedScripts } = await browser.storage.managed.get({ scripts: null }); if (managedScripts) { quicktext.mergeScripts(scripts, managedScripts, true); } await storage.setTemplates(templates); await storage.setScripts(scripts); } catch { // No managed storage. } // NotifyTools needed by Experiment code to access WebExtension code. messenger.NotifyTools.onNotifyBackground.addListener(async (info) => { switch (info.command) { case "setPref": return storage.setPref(info.pref, info.value); case "getPref": return storage.getPref(info.pref); case "getPrefWithManagedInfo": return storage.getPrefWithManagedInfo(info.pref); case "setScripts": return storage.setScripts(info.data); case "getScripts": return storage.getScripts(); case "setTemplates": return storage.setTemplates(info.data); case "getTemplates": return storage.getTemplates(); case "checkBadEntries": await utils.checkBadNameEntries(info.data.templates, info.data.scripts); return utils.checkDuplicatedEntries(info.data.templates, info.data.scripts); case "openWebPage": return browser.windows.openDefaultBrowser(info.url); case "getDateTimeFormat": return utils.getDateTimeFormat(info.data.format, info.data.timeStamp); case "parseTemplateFileForImport": return browser.Quicktext.readTextFile(info.path) .then(quicktext.parseConfigFileData) .then(parsedData => utils.removeProtectedTemplates(parsedData?.templates)) case "parseScriptFileForImport": return browser.Quicktext.readTextFile(info.path) .then(quicktext.parseConfigFileData) .then(parsedData => utils.removeProtectedScripts(parsedData?.scripts)) case "pickAndParseConfigFile": // Currently not used. Instead the settings window keeps using the legacy // file picker. return utils.pickFileFromDisc([3, 5]) .then(utils.getTextFileContent) .then(quicktext.parseConfigFileData); case "exportTemplates": return storage.getTemplates().then(templates => templates ? utils.writeFileToDisc(JSON.stringify({ templates }, null, 2), "templates.json") : null ); case "exportScripts": return storage.getScripts().then(scripts => scripts ? utils.writeFileToDisc(JSON.stringify({ scripts }, null, 2), "scripts.json") : null ); // Experiment toolbar actions from the compose window. case "insertFile": return messenger.tabs .query({ windowId: info.windowId, type: "messageCompose" }) .then(tabs => quicktext.insertFile(tabs[0].id, info.file, info.aType)); case "insertVariable": return messenger.tabs .query({ windowId: info.windowId, type: "messageCompose" }) .then(tabs => quicktext.insertVariable({ tabId: tabs[0].id, variable: info.aVar })); case "insertTemplate": return messenger.tabs .query({ windowId: info.windowId, type: "messageCompose" }) .then(tabs => quicktext.insertTemplate(tabs[0].id, info.group, info.text)); } }); // Listener for the compose script. messenger.runtime.onMessage.addListener((info, sender, sendResponse) => { // All these functions return Promises. switch (info.command) { case "getKeywordsAndShortcuts": return quicktext.getKeywordsAndShortcuts(); case "insertTemplate": return quicktext.insertTemplate(sender.tab.id, info.group, info.text); case "composeAPI": return browser.compose[info.func](sender.tab.id, ...info.params); case "messagesAPI": return browser.messages[info.func](...info.params); case "identitiesAPI": return browser.identities[info.func](...info.params); case "processTag": return quicktext.processTag({ tabId: info.tabId, tag: info.tag, variables: info.variables }); case "getTag": return quicktext.getTag({ tabId: info.tabId, tag: info.tag, variables: info.variables }); default: return false; } }); // Add entry to tools menu. browser.menus.create({ contexts: ["tools_menu"], onclick: () => browser.Quicktext.openTemplateManager(), title: browser.i18n.getMessage("quicktext.label"), }) // Add Quicktext composeBody context menu. await menus.buildComposeBodyMenu(); // Add listeners to open template manager. browser.composeAction.onClicked.addListener(tab => { browser.Quicktext.openTemplateManager() }); browser.browserAction.onClicked.addListener(tab => { browser.Quicktext.openTemplateManager() }); // TODO: Move this into a module. async function prepareComposeTab(tab) { if (tab.type != "messageCompose") { return; } // BUG: Thunderbird should wait with executeScript until tab is ready. // Getting the compose details works around this. await messenger.compose.getComposeDetails(tab.id); await messenger.tabs.executeScript(tab.id, { file: "/scripts/compose.js" }); } // Load compose script into all open compose windows. let composeTabs = await messenger.tabs.query({ type: "messageCompose" }); for (let composeTab of composeTabs) { await prepareComposeTab(composeTab); } // Load compose script into any new compose window being opened. messenger.tabs.onCreated.addListener(prepareComposeTab); // Remove saved state data after tab closed. messenger.tabs.onRemoved.addListener(tabId => { browser.storage.session.remove(`QuicktextStateData_${tabId}`); }); // Prevent sending, if a popover is shown. browser.compose.onBeforeSend.addListener(async (tab, details) => { let isPopoverShown = await messenger.tabs.sendMessage(tab.id, { isPopoverShown: true, }); return { cancel: isPopoverShown } }) // Legacy: Inject toolbar into all already open compose windows. let windows = await browser.windows.getAll({ windowTypes: ["messageCompose"] }) for (let window of windows) { await browser.QuicktextToolbar.injectLegacyToolbar(window.id); } // Legacy: Inject toolbar into any new compose window being opened. browser.windows.onCreated.addListener(async window => { if (window.type == "messageCompose") { await browser.QuicktextToolbar.injectLegacyToolbar(window.id); } }); // Legacy: Update toolbar if relevant settings changed. new storage.StorageListener( { watchedPrefs: ["templates", "menuCollapse", "shortcutModifier"], listener: async (changes) => { let windows = await browser.windows.getAll({ windowTypes: ["messageCompose"] }) windows.forEach(window => browser.QuicktextToolbar.updateLegacyToolbar(window.id)); } } ) // Check if templates or scripts are invalid. await utils.checkBadNameEntries(templates, scripts); await utils.checkDuplicatedEntries(templates, scripts); await utils.checkForIncompatibleScripts(scripts); await utils.checkForDeprecatedAttachmentUsage(templates); quicktext-6.4.4/resources/0000775000175000017500000000000015051542456016247 5ustar mechtildemechtildequicktext-6.4.4/resources/screenshoots/0000775000175000017500000000000015051542456020766 5ustar mechtildemechtildequicktext-6.4.4/resources/screenshoots/donate.png0000664000175000017500000001406315051542456022752 0ustar mechtildemechtildePNG  IHDR.'VsRGBgAMA a pHYsodIDATx^\ xT幞3kfd/@ $@6EQ]+\>^Q\jն׺V{tsP6Y,YٷIfϜ~79\ɛəyϿDREP(L&L h,~8f$IAV dAF8QB(YNP xp nZ7@]N$!V*zm LłpV\ HGd&9T5=l"RMbH5hSdDgTS@p:pT`M>ddD7T5=l4&:#GjcM5QEL"'j`?wH +oDB@qP+LV+8<YP.=ʥ`NP*gĠ(aI!^3H#kXdYՇÈ 4Q$"â ܝH h)륨*DFT!SZ5C1ZQ2ed*$ው9sFUWTiLX4Dc|FoV.=NrqFL<*{!]GtͺRF4YW„Kֹs P(q;W/źb% T>wT4lr<ڎRے{q@1Vʎ0Ĩ*h9ŻUx^Mht7 sB$n!q;Sb+Ԏ\2g.ASt;>T1~}x(s}kF􅷘ŻYi<\,yjygEєe \ht4I7SB[hpw~k @=NG!+@ۖ4D 0Gw|50t/}s ] wqz\@&o;a86+UGu?B_P٢0ȸKF}֟rެd>k22OprS.9?z!l!72<]zn:=TD7$ ; I3@.QСéy wq"Bs 7gQl@][ɫ--c:槉'׶M( IABqt)JXzNdLG G@ m67wA:R MnIжNT"5u7'?坲]59Rf@oë7Ml@vr$Y\jO3 =x2<:N.d] NmZXՕP˧\5O_[q߬zk 4z[SV^2?uyywg "+TKmi-%X%6}m Xl|278`!j UAX zȨ"uk]eR3ץ^yVyrkycU}Ǻ0/6?BUv[ndjh)1EyKQ6.}s|Ye~dDkveqtgb &1#O2M.^*ڿXgj{I/p_#! D * Gph]"*%S/?:fcc?|KGt9WYC$⽼Њ u䰂z.+tW,_ j{3xGgPn2 5́QS +k-)S o^RŐb'IҰFlLv,<}SCad1>6Ӛ]0\Mޖ ԬYdԣN?˂?}0߷_=*$Zv9s(?}TEo\#{! )`Sp9>iPet1#hgGW{Ͼև?x۟<^p[Tk|3ڷG?xp-%Uu55/<\;ݾ[Kx?zʷ6pIi P۝?~=tͣXⅪ;vMe $KHR #~_2.#0;nӣ>anE-},1jIc|6TɇR2mĪ\y`*=ɵ^Xuc'^i&d{#6~87s+bܚh lz>M)rl۪y]_ׂ^8|Ǫx/ՀE}ѝ+ySZ] FvtDŽіx8`/vhd͒ȯF U)pcetASON _u  +ƔM$pkEیE^Lm%푵ͷ70˪ ȴW,ywǰCSzpOcV5|bg/lp !$ A}{!C`~zsX CoIcĉ.lnfpa:.5If5LwyM4Zu^YͺTKƓM>GgDzM) sryd.'CJjX/+ň\Um0t?ōCYF/atLK~ʳEөxS(!N|:.h4㥩GŃ+`ᤖ57Ջх9d: .}v¥jIl>3Gm+bT[U~k΁pp^̗ca:1- k=[<̟\9sZAq(p-[*;#OGsfZ;,kclR ڪP\ieL$-S@4{mLz~`9ͬA0}|^IGv6xCfٔ7j8ڌq5'f񨆉ԣ4(J^Qx/8?J=nUXMMe`rI< $ Eqzqe{`7dX*qe~5K†c$c`X,i B@_  p18nAgCIkDsApC#oOf@<f]⸅-P]v}q!n@808i_;pVWt GXs./ 0 oyZ9=2ͨhZP5\4qd'?^V0N<2M)l3  fy7>cOʬd\hk8#cWcL5r&K/wdAʂ0$FW#9#&s &Mpx@\Eg/V9S0 GS8*&6ŃH6`?wH +o0A =BzP$ BpH '$us$̘HkVi髈TMo_U gKEHF0GrH#đɜ*2#դ^4& 5)&#̇ kc8R5p,]IENDB`quicktext-6.4.4/resources/screenshoots/quicktext-settings.png0000664000175000017500000001724415051542456025363 0ustar mechtildemechtildePNG  IHDRF#sRGBgAMA a pHYsod9IDATx^ \Te3ȋoJ)&WԖPӨ66[y涋msft7Vm376[ʚ+%ho# 39a`">e-6 5^ {F 8aշlؘtGuGmnwl n:aDOipjFLAځvc#C`w(_ָn ᘸ^ X%tYgv˧/W_#Ql}y[%@`gK9]{q fM ;Pa ;w gPzV !$,(XW{I7RGXpxP{0a;Vb\ ]Pر.V + xHX>UA z=ƍSuv.7[ }\ ݦm4^wlL*w T~cւze_֌ &r  J؏`_k 0P aWP @)HX aMpswkiV68c̿P}5h-{T*X kS ;Hքejz `/M +kkkky?=!C 5a3gDEE>oG]]]YY٭F %CÂZ- =%TxiR{ף M`q3'#omW@cؖ5~/Vllɼa޼yEE,IMPd $JAfl۶B8-> ]J'd"^nR, s<'0V ?Ӳ{C?N@UwlGFFNoooV;2M:*W!II}Ayrzj sV.z,)vT>:ޏzUu4]qT/xԫUlAβw1L UtB9UVjX^%ʻۃYXqb)]{F)bR `ŋY^xw_UuԩS 5=t_^2պQG_U*Gtw }OX@7O̜1Cu4iΗ'ϼ0#0`HԨÇ͘1SmMJ󡡡GZ=>sR,;qG­nႇV0^|8ao. {Qu]0k73~C+ (ˎ5=$e"##N s`;+\o[!ٚ양GlGyny駟 }g;)Cg4ж8WMhhUM KL 1 Vv[__K/},lj ok:[>Cbٔ K`EcX_;o8p`C~s\tᶖ/ǎ~Q|ӂHXCZSS~m>qBA=e|䩧x ^L2N8gRcTnoqe˂{\1a_\ڵk9S>Z%MFÛmus~3bwURO ͑#G֬Yl{eQ x_6:|Β7|a5`ʕ+h pAi_էeA#3lU'c5,Yx/rss?~$z }Lf~gP0}k a]-V)XɈ˕.  n/^HXg.^G~]/^š?FÞƺ -~g}恀#C}}}E:x/l߿o߾*X_rjmrR[_1cCuuuON8{ \ އ~dɒ&\ٱ3ȇCѶ0eSL}+((HbTuTx`:s̋/Hgi;~dc?Fmpȭ7xMTE>_վe?_\a\H~Tرcʕ-`yꩧ#" FGnXڲfƚLv 68qov6++kC`7nO}yJc4k9='5lAAڵkyE<~SRȺ|>j;w^=z(p3HOOIch-gFGq߾[o3$MXF\M/M Ԡ(g#7 #GTTT$''Z*))ro F!Ti]{C|榯۷vÆ ,T(d-)b]tiHHwdddJJJ`` PӮB)%_Nr<s<16A}ΆZ>U3|vRܢhe_N8núLC rAфeVP7HPo8CN%yy'w!|@M7ӌ|]vv>!LkA$^x4{ğ)uOTkqm1Mv~cr~T,/gv*] {[kfq$Շ:u*h;PHkӱvn}u{|tK FLEQc˖-ld|@iiitt4~$~ebӶe$w,2X]m݅  XSZ VwNV$뜰({cԴcrcXBӱC |BqJ˸xMV*H+OWt̄bf:.iA/Ju݈=a ǬlelVc«gKԈ/m2_m4\~ξ7ߤƎ;lVVV@@@ bƍ+//e,R[V'$$,Hf5*D.RWJ@J]!R%T^;YUmRZ=tŽ>YHŦ7 Qsg#$i~4&({ ,~k,O^nW $EmųݫJ͠ɭsRl2VZ(pc:ɟ&TylڴtUM:bL/izOJ/ײo۲]v-[QR>h:BJ zZ9]t>qhHao7ybmK~1!vKR(_/KD,%D嗔S)$au֞ũ}zfTmܞ7G=)殯uw9g^sCns~Um_:44oj…1^aԫhH6?W/tCcY讒6]/))y绋WF<4%z]>&W:YR&̙͛#8JjÆ 4ʕ+Y 7oZVW>2:;AUrpwb~NgҢͺtΩ AZDŽJcY$N3z.G^."k;?q<&h[Fxv1dʤEoCϮ눐iɺ߿?99ovALķ$~'NWjP&Je[M (gpoo ӧO'%љ]'{06OwY>Eƽٿ :m~!JUV9Vx=?҅ a ?$_tejuU{N^}]:f[?īkV ~C串^w]koP;ˣRMM[oy-.,_·n*JӜ*{k-ճlS+njXpv%.b `N2k_cf|fƍ+WC>/YaWd ; $]/=~)X)^]QY_%.tZF/Zrg(o}']I֛孯V!aE/V^MMM0Х:ʅ1c}}GMMo1}tyP4X%pJj/^^e\r-t'*YBm l԰ ʸW_}5)))&&fڵvӋ/8o޼9sP ?4򂂂`jnٲ5Cuܹs 4s+װf 6q{Q*c0+PQ~Q+w xy=iطo#MHfOa!K(U|5YےRR`´g?aWHHHO Y]]݂ (d?NF#/_f.DYiWiW_у,..fwxnvv6ܣZږaD+l%- .,lKʩJwqU^c;Ȑ0@dee#% NhOz5^- u)^Ԡx]:IRWRJfj;Rov69Fee]Snp=Ǿ B( $,RJA(]]]o&vW%ޭ $,RJA(C,@l3IENDB`quicktext-6.4.4/resources/screenshoots/usage.PNG0000664000175000017500000003177415051542456022454 0ustar mechtildemechtildePNG  IHDRsRGBgAMA a pHYsod3IDATx^}$AMR3*P@A) J kTblބ02 l :d@^ #2&F@6`Y:ɒd$dΖ3=t3=/;;[~駟m_;$I< q%I$KR$I,TA$IPA$BI$ U$I,ԅ?{;zUݐ-C$Ƀ\Ao\yW_6|o g>w_8m_~CWC$ɃA¯[$u6<ۢq[,6wu|4ǧ{ۯ ߞwoM}.q8NHֽ\Rfe{>9%km>4KsJLtY}:ϱIi$>KUOfm9~  s^ٺC^pnv.KxPcmm+qFO_\8:W9Ʋڵ#I..!K0"F;W΅՟{v<|1潷 ||:_|Cn^Wa#SQζ\EPj*Ώ$L.e4Uvų t;|o;pӵMo|Uع;︿}pmpg>~O'4կCj{u/(ycs78>:bʎhFX@~utyGl;1~ i,49< Tc k$U!m15mKmAj|g]g\N>_7%uOѩO$˳+}.Rf?=pA# ODO?]ub$~ɽ:+T[WO6kw{1Z4`,Ap±!jK/=m?:ޱm8~xnv=:Qwi^s[lK# Z9N;VlM;pI ɭORfC`7}+3;_|2u\.>zol zo;Of3AmAp`4)>m|̜sn[ڶ뜶S`sm/Ovi#Id#]쇹Was|_/>[?o3^|t8S_Jxɻu?~G҇}aؿ93:6ݎdhd3 "=f67?ع _;p,ji˻~ϵUnj?6I0ɭORfN |{;{+ŸeS}͟ g>χO?d?3[gmm8֜=GUHOO'4j=! T& ;K>4=ˍ؆F.^圻7"=׷{9$oWrJwwoD/Ɂ1W#,%KcP>'I,ե7x_T/} 3_#._g4ɅF8::>J$KuA57^ΏGw׼T _Ζ.?$ H$ã H$Y Id $I*$I H$Y Id $I: .\$IdA $I*$I H$Y Idv'ñx]©pT]~,Ǐ{rc7ڴ' 3]X8vrǙ؎_>Y켖}-Nq$k uN*`З%FWh+?54i.}>E$'Y 'b}~}cat܋2]F}r$] [e`RO1j!7g/@YuP?uho[van`:N}ipzA+w&c8z<ۖ~l媀2O瑜~:<\3yR68c}ϻd.$Ip\ Ǎ7ЄvLJGܢ7icq$([ V8"? mh}enulO׶.msϬgh;^OIw`.W5 q.[^ak,:2цyM?NApP?2h؞ںhXz&,I\eM;S:D4ϧ}VS0Xoǎ;U>0l<5$0toduL־-__O)31<$: riz:zU,ү#UXu.x}uarRmǎl{{OGVtіEML۶춵}F{zNs!Ir?y\$G\e;f4Wv/6WkomA0h͵mnqAӈ)pL\HOA$IPA$BI$ U$I,TA$IPA$BI$ W*|_n#I,AA0$I6$3H$KPl#I,AA0$I>|W"|~oYΕHA0m/KJ 铟Tx&oo=?Ir'{f x`7e/z׻w1=zq:xj$7f I1K#i6=A x;*T0WnG| _8|׹2Gbuܶk$e}LuNՕ8[o`t*9 X!~=h_ȕ_b2: O&dt~Ib}Lu[<ݶ,Woz]~wve ~ȿ6>0_)82#W" =A~#\Ib}Lu꣯n[+ Aj`\_~/QUϢİW`<Ǹ Xl (rwS$Y1O-OgՑU/{lxMJ`H Aui\ Gb_{ٸebܢ =A0^1 Ƒ< ,c6qp~C=s>cKs8{U>?{_pYxJ`j 1Ha3}ɡ:^a" =Ap Hc3>o~ī"7䛟~tv[_/?쥫_n7CkILjX=u^n[XY~ 䲌*)[j;×ǟ]|Wxʯ O#/p;:uoA \'sA0^.z:kI?/B}~Wux31vak^zic^ 9\/q~O5w9sŻyЯ\q/+gi\x]_X}їpսkz^]_/gSع-|=;o2[oWei BehbɟѸ` 5n  Y1Ł8o_z-]M-7['^]Gop?{'o/[W[A0ml_? Pq r]}5N??oÅw]^/kA8^P'…p~ww+$`f:X[[j(\$\,<$j/p.|γ;{3Ik.^5b  o8.?vuK…B-__ w~ccn?IA0mvFuq r=ճk;6C_Bo >v4\<+&_[A0mb-5@ug5|pfoip[×{i8{/em x{k_ Í;Ajp2W'ñS干ui%WO=Aٓzc'ۖgl~y$<ү_K.~g"\e<|O_.7>[޹/پБ<{O&grB̖]ܕ\KyW˼O$8sx86Ŷ腻+X?8?yI$yX=uh8zΰm?'χs88%khq_x3h}5OɹL{֍C=ݧr#WvMgRTs`S{mIpn1\t~J+s$tp51r}uzwAg5_s >Abl_(wuk9{`aLꃹ~g6o$|?5fcbɛݡo3\ϟvA$)N }Ő+/mhzquy6ID>صe7GɺI"M}tR?l%mGzԯ$&š3b3.K쇷`[{zxḜMƒe;t m.~uf9"1}m>ߧ경ԱO N]K۲NZ7^1_I߹7ÙA\v_=?c%"S#R07MIdCA0w@c~!Ir`f[w#&ݕFSƐ>I(f$I F$Y`fId m$I%Xl$I,]A$IPA$BI$ uof$I0q;$Id7 {oH$Y1 ?pi$I`~ xx$I0=I<9s\Zo㊇~8<#$I\ccٯ덏>(I$:e`n$I Id $I*$I H$Y In!CA$Iέ [ I[Ap5~~mƺ-᫾^Y6ldc{ܶԮ97ۃm;[Ȯ'mT$./#sVɖ <\rIծ4 ~#w0(Lk{q^:0M-IiӉ˚j\J[03i?\HVwծc:[ߒӆqJHRnq[" { ~sH6x1x7T G:G㶝oyyͭЦQ۶6wi_X۽mv2דudky\3_U4 !0A[sxq3):{ݮq=5^X $ڿH)_4?+/tjyY/¬mJZo[ 4}rC2v49>̠sn\CU-5F~ r>&|mMC+_j] e̻44u춼I]skZǂu>YIlUWf.Kik{>"zϤI_Iƍ}ns#u!W^y~IA#wrˋ5wjhٮU HPC`Ѣ..+ \AҦI_I.;F=uRȅ䗠qqN[^8: JkZsu׹vܶ9&Y10,3F=`*`[ώ3iWѮ{%SW]=lnkmycM]WCvwj8.n6Q_!]Jc38mL:^kd.'FAkuY>IK j |[n~Cm z\?í Ȃc$V$I,TA$IPA$BI$ U$I,TA$IPA$BI$ U$I,TA$IPA$BI$ U$I,TA@o&$Y< 4^ A6(A AAdCArPA6(A AAdCArPCFN|WAACo+l#TvZvwAAպo ub@ݲ}!$ΎVIF`_[Q3Lkco8V:pXכoocPR\T]2kc[b{cGm=>엝f "ذ4- r=lr~@3 muk_,W24 !0DIl.9S>7vX9?7a՗]`ϤM/4ښ@: 폈UK.wO V}U;uDicy,vƱG_Nv8}s xН8Il.<jTq $u6Jt]e `o.k x $u6Jt]eF@: %"hB Irͅ]~g}!$Ck3ھH\gsD׵_&nj$I.{*$y o{׭ Z!I۩A_[IPA$a3 `=4ϝ;.^zOp4s=ٳU8dop-Ta'FCd' x5Ta0 u AL'u]N:UM+_\=0q /[{jls#m/|n=Z?}CwL;8ĬI6sN䶭9;g`:]<rZЋ `2,&a:8% Q[g '~.=fl$V]b.a ~GpZ=/Tn{:̎0nt֎>2',7MGf,?#6"c=`ܞR.q4έ˵Ŵ1ꤺAk{6P'7r?Uu6KSphzY */fC9AEACd'AAX?AvR`IACd'A֏Al+>IA!i cA0G { 7pĉ* L5xMk㩧\)0);w.>}:r-U!i'xB)^x8 tԩjZex-k"^O?JNA0~S1\5G5 AI Џ/\렾.S! P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @" P( @2 wM$R<}(>$I,DA$IPA$W`|c $I@AQA$( *$ADA$W asc#l$nnu5w$l^l#f7:66{suk6uqʱ7 {F8ݶ I+EQ,P6#[aZ7y{Bfz[Uzy [GW$Ir m~Ǽ>a88 ]3o{W#mSI\+o:SW~+#/}G)zy};+7ʵ9slrQ{*9MaѪ18F\>=Fnݠމmϵ5ۯϷ(>k>Ne4tA$W 2%net $Il4mR@/?ZLQnԯwgν~%1eY]<.""""2(""""2(""""2(""""2(""""2(""""2\uk~#a7|gwr"""""Cso?~(4`xOfG~s[\V?;#?yۇWyósyo|W3OwCDDDDO/h?oCK oOW%>?M-~s_""""9}um{;H粘,ӵ2]V7+|hXw//]׽<,?>?(-?. wb_ τ g/Q{ /_ /c&/pr}"|s:}cJ5?MuN,/zheӻ׿=,gA 7>??ɻτ]>[ _Xdus1UQmo~1oa0UKr|M['w L>E:7R:FQ]V?>?_y??w߫3>W۟.p yO}??ҹϝR? oUnQb|("""-Q4}ֹtӻ ۞-jԧ>^z2/ŗ?_x.|χһgY_}|Y,)~/տ܈%_uM #W׹_Cqe$g_LoDDDuOgLSR*1(|_N'?~9|(J> ywχ/3~3ܽ߹=e5.VVbIN|[HQLoJe: og[uҚ  漪eK,` ""rkۛJMfVeuzyZW_}5oFYX}e|0;᏾,|77#,_YU""""S'ꚟ:7r:Va]V7l=_C§bŲ\j1/|"< _8|ɛO ?޹Ϙ7[|S,UܪvGlͷmm fo:]7b2?=CeE˶U2rs)uOgsQX{՘ ׅſU[᯾/ /Όc/y;ֿ?:פ V=Y*Pq۪J[N]r\)gEL pyI~yZ=~ ]_s2W_J}#"""2Y&3(@U%| 7 y{Ÿ_ ;~Wk__' < Y|~fSR&{~s;#_@7sd)Gg[XW.UVo}3?Ɵ ߹2 """rܹ*""""rQVEDDDdQVEDDDdQVEDDDdQVEDDDdQVEDDDdQVEDDDdQVEDDDdٔUeRV-eRVe,H\p2ě:o}5V}*t\ϲzpe,IA-j~#RVuicK{=V;[]d}r?#:4}`YlYq@0o#~qܽsnjdePp:ejm `.cqu`ur>U~yD/Z2D`>F(縅cjU<\~1+?Vm/9?{ۯ7.amU2`fzcpa#rtv{8NQh-U-(MY_5YeSV[DDDDdVQVEDDDdQVEDDDdQVEDDDdQVEDDDd9}֧~9|Y͔U9,ݱ6?朮ǠY=aTU銲z”U9ʲzZb7yj "ȱ疏NY zGYc{ KG%_dxozu.SVKF Ow/JUQV~l`\cY=_ĭQv٪Xp^륗쮻l]wX.:ݜS<澆,qTupeUDDDae5JN3׬['?^c:E{jkf@lMˊ9f3[V*{wNmߤa9VCI}H_s.W]FY /ɨ{&4)5YݳMwJɶO26#ΗUֱ:{{NR} 8nKc5oο}_yL?t("""ҕkYMJIkNYPrm%YYֱ^Ae5ZC>Ze3Y/T _XX-}YX;1w}NGd7cu𡬊Hwb̻LMk8 ,:*j,Iiez/H-WaY-j~ٰZySjtiF] """}26e#-5yyQc8$ٛY~2ҏ]{*/1GY .eZg,,e^{L_XxZFVe#tut*_h=fvGpUDYEQVEDD+ SVEDDأ0eUDDD= ;HWN:9FY,@² cQV-eRVaBs詧]UPRV0e&?U8LY !eSVaBsHYÔUPRV0e&?U8LY !ezEX,:\Uy{>\f( /4ͲynVuϻ=|F\heW_ oo|#njzY= 旿)nVn#2V>я.s(/W{xljUmu`z:Uϭ|8r稈ȡo/r=g+5Ϟ7BK/>u>P^V9F(s\(9ZYC}ueo~:]eufXfq8[WopCs(/Wzz\nwl]:o9 @]GSV5o10_y3lR F)i֙>\k9 f[V]VG"LCY )0 RV`e&ten RV`e&0*LHYaU (0!eEY}EN.]Uf6e_9(-oy,9eUd(ǡELe8(󢬊Le8(󢬊Le8(󢬊Le8(󢬊Le8(<.eww½Cɔb7ΰ\,^,.]/~Y٪0.euyp/ܹs7ܽsQTV!ӏ*02eq[UV;?t)3΃{w{6vSEY=(e&??t)̓pQ-GX8xν{BzhY}T;Ų~n~2N׷9TO;7_V{(uaY.kc{Zj},;}V 0uZ-q~϶uqyB2N}7չUPZN-qu*xb^*hv/)eIY-n'F5+3)eqMߴmqR1eu?/dV"C6\/ۦ,k_Uy`?n_f#wj3VwDLeJ3?t(L,yIQ$H(j!VVs,]#yìjkal˷8sMҢKs@M{vy h<\6#6hjy}deg2>L߃rz6CYae/".(Ka5KTٻ^^VE͖%YܽR{ 3{O=+UnY:3_Vh݇LzUXYneU2Qzoq/\F׋"7GY2)k҃FYMe`^[ZǼ\KQV 0/ʪDQV 0/ʪDQV 0/ʪDQVC|Qy%7*rj颬0(p[(0 RV`YծPE=]Uf6eTD5*\eUd(p=UPVEF CY!*\eUdZY=[-r}^OVa:'.p2h; Z)SVEFɍvX`v~׾oo'9\euܿ;ms?]  ]6erɕrdtUTF,u8RX:U w w\T.SV.}w ɕpd$5i#E e\Xje6XeKZfMiα\/euMV)M!da*OQ~艥-+iejeqDg=̩v1\U ~)ZWGb҂#cz[\euQVɋטZotVLKrt|]t9ٲ SVGN輫.H>F%C˒l~ɩ󗙚]rtuX}4^Uu?}VUʪI^deHUʪ쉲z(p=Ue*QVz("#DY롬e*2BU)".* 3 xY"'u9fQV]V(0!eQVaB* „UFY )0 RV`e&4]Y= 2ɍd:,Ͳ*ͲZ;of:=`e&4W\'-Vt#WY}rHa[ե]m*}C.7ۑrD^tukue*;E|-i}퐫nއ\(//շNW<[]ʜ8yC)Z{Ki,}q_۫ղXSfY{EٚKQ ]os!6.:_i:?W~Ws>7 :[g$ozIsL[V]w}V},mJ$ϳVf'/9yvm_YLႯ٦ZM[#;^l[d>!97׼5~w=ޑFF)Eٙ_5),7t:.M*Y=$?t:[V?GZϛGt]D9ΡIsp+QysH kZTwso4HcM+}3޴6(=緦}wT˚Ey4ԣve5AM-]Iey[Q2:P_v^eY:Fo4S&U_oo~}qSeۣI-q2+˓ksȶN˶ba("G[V.M(߈ZǛ;;?t:o_YG8&']Vv>ٲq `L.Y2#Q^7_p\o}e.te5ץ7aER ЛV(ʡ.Z}o]؎&qL+W{=c&@G_VKWTUl$h[KV왿'w6S&qg,G\<>ձgeƦNZN _bŵY^-z͵{L_IU;i9 ၲXv#Uy=/ri8 cSV{'-'Jdlof|'~sY= eXwM7zSd.Dq|t^mVgJUlUh-[e ٮ_> IRV{'-'Jd2>`:6 \Zn#$s/i˧ L`T[WM0)t^,&P*z`ȧ85j@*elFm7tJq}$6󫂺)tN+*2QYըLm]SZeyFAud|(.Mڋ7up2Uͮe඙uYS3mY#(e>}6#u9ͧ8I*Lh AY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaUte,˰>'yX/a:+nò|s0s:)0*gEX^)LOY DY=_/+SV Wa\vK ^bQ5[^2_)>V/VT!n3/:8ۜö\w{k8e&4mYW򲚔óXZrY-k#IܪۖtqrU/gwX |d뗩2.OfkT6_o։%E٦߱}{kse&4yY5Z0+ytYMWh6ďusF[]p*UЍBY6%/]|Џj9簻)7 pUMը˺`2|4/X旅HH./*Fo$p̔Ute>n*LHYڎ~*LvAQ=}(0!#DЏ RVUUJ„U"e?e&4]Y4SJIT~gͱ.6cG/e6(0ij]]._dr:ݮ ma_n*.o{3yg_Í=PVSVaBGWV[U1)?_ZuNc]˾s쒮;d1PVJY jٟKl^5;n.Gal/QHM/n>o_P lZx?U0_\tWq]QVaB*MRul~:KY絣kܾ*n6l].yl[:4}vu5ltl|6IAm#.N"uGY j>Ootr-%_tQ6]*=I mq=QVaBGUV HakVM?Nߌ;n|C}nGtR}ze?e&tleugzc|߳kT\YvL]KmJkX72eઔUs<iͷqtuݎ*qu(/.V*Lh>e)AY M[VH_:ZN3p]Vie?e&4]YeΔUU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&0*LHYaU (0!eQVaB* „UFY )0 RV`e&4vY9Uɘen eF)0eNY(pu*DYSVa$*\ #QVU WHU:eF)0eNY(30?]ߧGY( ?(tHSV9RVMY(*sB #QVOYUHY6eFΟ ̑ m*DY?eUA#eڔU:JS.bxsc0Oէ-i<9DYLSVj*˸|s].U& *).>a}^NUgظ_sቇyⱥr{(0)[FNOde :,Z1.'qjZLsϏ~yQ:*DYe'CXهϕ5ő}|eq^\I*qTuz|\~z^e߭88:c[GYL_V7yX/if9ZogZ:|fyCǤ;;ܜae5+E1JjS?AJ¾r3 EjԏcN1mU͔ ^oH \%϶pd핬u+q *Q:͈i /S{?iI8Tbmy~{]MwͺeqmK=Ϊc-Ǜt*VAMk17nwYmHnld :4Ulӥ,'>o=aFe5) Ḁ BJEva#nul[9]z^m~NƌXVPVa$* yTȴ>eǛAe竚trs-o/G[V~v g_@wL#M~,rUj1ˇ~̚I?f-Łeq: V=P V:/X]& :ަzT+z,=Fg9/esۥͿ(HfUV7d%oeyh)Sx,?]ǻlqť3 E=-mwQ1v`}*w~Zc.c$/{4yrnRYeF2MY*UaUhSVa$) )ЦHSV9RVMY( o{m죬HU:eF)0eNY(pu*DY-""2U:]/e&Ο 0/*LHY?e`^U:*(0!euUyQVaB)„SVEY MVVVaXYz~gaXy=:_e'HYe&4IY-eRPm@Y;e`^U$e5.aX?TV*(0IjYaN׷luvlr^6auVG3Xk^HOKYe&4MY1vEeX)T}*.,*)0j kV٪c֨jR9v;w*(0j| NqO.UyQVaBG9z(„/""2U WHU:eF)0eNY(ualr*DYEn RVOYe&Ο 0/*LHY?e`^U:*(0!euUyQVaB)„)gaXy=zY=='7%>-q\V3^z>jJy\I3uY=_/òhqͿ[!*W5yYw﷖ݽ_kzϸM?^U.KYu c,kU%ަ)IA-b l /t[OKiyX7#뺘r<~V1.(/U~˪Eua<+SߖeqX}tȧrv~]}_C/N&ۗYv2}X;^u[e0ijz{ȡrYLkV7fE\ĔU2UY1xz):RZN髪P6oPCˢlŗS鹷._GiU&6~Y=(\*2B*\U)rYʪQVU)rYʪQVU)rYʪQVU)rYʪQVC,"WMU.o6eUEY۩56.cen eQVaB* „UFY )0 RV`IG?+Sp^P^V=owVV?񏇏}cxu^z[[s ?ymts\۔Sa|½3a+e5ۖCU-uU`KY !eSVaBsHYÔUfKY`\VEDDDDLWmrdUmUmUmUmUmUmUmUdxQ `CygGeUDDD6*gNv(}QVEDDWBU(n{񢬊H(eUDDDzEYeu("""+(EY^QVGY/ʪ􊲺?xQVEDDW&)O=EGS]0-υ'j"=+?]<x._~QVEDDWF/=J j1Sge?>ܧʪXF/qTu3w\_YFT[EeUDDDN,ղ@-COJ~m_F?H_Vc{T0)YŵZ mMVĊ ufe(uzU?$h}mΘ*"""2MYSm T,Md5zQ:;z3˲wdcMVUVEDDW&-E{⡪Tyݎubj-]fWV\z(;H^Vzbr]GU[GqGãE)k_jz4UXkTf=et*"""2zYKG/4)Ͳcp۴5NfYVcaskZV/;H_V7-'eUDDDzEYeu("""+(EY^QVGY/ʪ􊲺?xQVEDDWQVNj*"""2ٟPՋHt3i~;Xtw1ʪ6ʪ6ʪ6ʪ6ʪ6ʪ6ʪ6ʪ6*""""2JyOB3IENDB`quicktext-6.4.4/resources/icons/0000775000175000017500000000000015051542456017362 5ustar mechtildemechtildequicktext-6.4.4/resources/icons/quicktextAboutWindow.xpm0000664000175000017500000000461615051542456024323 0ustar mechtildemechtilde/* XPM */ static char * settingsWindow_xpm[] = { "16 16 113 2", " c None", ". c #ED0D08", "+ c #EF0F07", "@ c #F01106", "# c #F4200B", "$ c #F11E0F", "% c #EE1707", "& c #F31C05", "* c #FC7251", "= c #F02112", "- c #EF1807", "; c #FC8750", "> c #F33816", ", c #FE6C39", "' c #FB8463", ") c #EF1B0F", "! c #FA5713", "~ c #FF8836", "{ c #FEA25C", "] c #F44927", "^ c #F73A0D", "/ c #FF8747", "( c #FD8C55", "_ c #FF8E3E", ": c #FF7405", "< c #FF7C03", "[ c #FFAA51", "} c #F5613B", "| c #F22506", "1 c #FF883E", "2 c #FF730E", "3 c #FF9946", "4 c #FF7C01", "5 c #FF8401", "6 c #FF8D01", "7 c #FFAD3E", "8 c #F77F51", "9 c #EF290A", "0 c #FD731E", "a c #FF8E30", "b c #FE994B", "c c #FEA548", "d c #FF8E03", "e c #FF9601", "f c #FF9E01", "g c #FFB42B", "h c #F99665", "i c #EE270F", "j c #F88843", "k c #FDC466", "l c #F64104", "m c #FEA352", "n c #F1320D", "o c #F34515", "p c #FFB44A", "q c #FF9F01", "r c #FFA604", "s c #FFAD0A", "t c #FFBB26", "u c #FEC96B", "v c #FFCB3E", "w c #FECA65", "x c #F12B06", "y c #F33905", "z c #F7691E", "A c #FFBF49", "B c #FFB40F", "C c #FFBB15", "D c #FFC21A", "E c #FFC920", "F c #FED058", "G c #FA9640", "H c #FFC543", "I c #FFBC15", "J c #FFC31B", "K c #FFD025", "L c #FFE15F", "M c #F14022", "N c #FBAF6C", "O c #FFBE1C", "P c #FFCA20", "Q c #FFD126", "R c #FFD72B", "S c #FFE975", "T c #F03811", "U c #FBAF6E", "V c #FFD05B", "W c #FFC72A", "X c #FFCA21", "Y c #FFD82C", "Z c #FFDF31", "` c #FFEE79", " . c #F24E17", ".. c #EE2109", "+. c #F77010", "@. c #FBA423", "#. c #FED458", "$. c #FFE172", "%. c #FFE46B", "&. c #FFE34A", "*. c #FFE637", "=. c #FFF26D", "-. c #F14C18", ";. c #F0390F", ">. c #F14612", ",. c #F3641F", "'. c #FAB05B", "). c #FDDB7D", "!. c #FFF778", "~. c #F25E1D", "{. c #F03F14", "]. c #F14918", "^. c #F03A14", ". ", "+ @ ", " # $ % ", " & * = - ; > ", " , ' ) ! ~ { ] ", " ^ / ( _ : < [ } ", " | 1 2 3 4 5 6 7 8 9 ", " 0 a b c d e f g h i j k ", " l m n o p q r s t u v w ", " x y z A s B C D E F ", " G H I J E K L ", " M N O J P Q R S T ", " U V W X Q Y Z ` .", " ..+.@.#.$.%.&.*.=.-.", " ;.>.,.'.).!.~.", " {.].^."}; quicktext-6.4.4/resources/icons/quicktextSettingsWindow.ico0000664000175000017500000001731615051542456025020 0ustar mechtildemechtilde h6   &(  @ 1?I: + .x9Fd[}x^ C !p#XrkJ7mLxn[*!&,1yN"@l &+u8T#_Z@C %_0|+9*5iI  X/r AR 2EJ &k>e Mgs0KH+e'Cf ?%>sF|>QC5 ) +  :GU>t|Q;a-  l9lcW6\'IQr!P8      ??(0 ` 1CL: 3e>O+rZ]  !K 7@L.Y|xZBOE 2Z*UrsdF136:>}J /Pp3 $'+/26:{; /`& #'+.2Di9 2i #'*.IV2r >^#'*PG+j*q"\#&[6 ?CLG"}H "a! ?"JfP < O1p+ T(` #,4U+J?hKo#az <<s|KCm* -7l]]UV:msx~e- 8`[''smkurx~*Ws' + 1:{_UOlrx}>A`% 3l IA@??quicktext-6.4.4/resources/icons/quicktextSettingsWindow.xpm0000664000175000017500000000461615051542456025051 0ustar mechtildemechtilde/* XPM */ static char * settingsWindow_xpm[] = { "16 16 113 2", " c None", ". c #ED0D08", "+ c #EF0F07", "@ c #F01106", "# c #F4200B", "$ c #F11E0F", "% c #EE1707", "& c #F31C05", "* c #FC7251", "= c #F02112", "- c #EF1807", "; c #FC8750", "> c #F33816", ", c #FE6C39", "' c #FB8463", ") c #EF1B0F", "! c #FA5713", "~ c #FF8836", "{ c #FEA25C", "] c #F44927", "^ c #F73A0D", "/ c #FF8747", "( c #FD8C55", "_ c #FF8E3E", ": c #FF7405", "< c #FF7C03", "[ c #FFAA51", "} c #F5613B", "| c #F22506", "1 c #FF883E", "2 c #FF730E", "3 c #FF9946", "4 c #FF7C01", "5 c #FF8401", "6 c #FF8D01", "7 c #FFAD3E", "8 c #F77F51", "9 c #EF290A", "0 c #FD731E", "a c #FF8E30", "b c #FE994B", "c c #FEA548", "d c #FF8E03", "e c #FF9601", "f c #FF9E01", "g c #FFB42B", "h c #F99665", "i c #EE270F", "j c #F88843", "k c #FDC466", "l c #F64104", "m c #FEA352", "n c #F1320D", "o c #F34515", "p c #FFB44A", "q c #FF9F01", "r c #FFA604", "s c #FFAD0A", "t c #FFBB26", "u c #FEC96B", "v c #FFCB3E", "w c #FECA65", "x c #F12B06", "y c #F33905", "z c #F7691E", "A c #FFBF49", "B c #FFB40F", "C c #FFBB15", "D c #FFC21A", "E c #FFC920", "F c #FED058", "G c #FA9640", "H c #FFC543", "I c #FFBC15", "J c #FFC31B", "K c #FFD025", "L c #FFE15F", "M c #F14022", "N c #FBAF6C", "O c #FFBE1C", "P c #FFCA20", "Q c #FFD126", "R c #FFD72B", "S c #FFE975", "T c #F03811", "U c #FBAF6E", "V c #FFD05B", "W c #FFC72A", "X c #FFCA21", "Y c #FFD82C", "Z c #FFDF31", "` c #FFEE79", " . c #F24E17", ".. c #EE2109", "+. c #F77010", "@. c #FBA423", "#. c #FED458", "$. c #FFE172", "%. c #FFE46B", "&. c #FFE34A", "*. c #FFE637", "=. c #FFF26D", "-. c #F14C18", ";. c #F0390F", ">. c #F14612", ",. c #F3641F", "'. c #FAB05B", "). c #FDDB7D", "!. c #FFF778", "~. c #F25E1D", "{. c #F03F14", "]. c #F14918", "^. c #F03A14", ". ", "+ @ ", " # $ % ", " & * = - ; > ", " , ' ) ! ~ { ] ", " ^ / ( _ : < [ } ", " | 1 2 3 4 5 6 7 8 9 ", " 0 a b c d e f g h i j k ", " l m n o p q r s t u v w ", " x y z A s B C D E F ", " G H I J E K L ", " M N O J P Q R S T ", " U V W X Q Y Z ` .", " ..+.@.#.$.%.&.*.=.-.", " ;.>.,.'.).!.~.", " {.].^."}; quicktext-6.4.4/resources/icons/quicktextAboutWindow.ico0000664000175000017500000001731615051542456024272 0ustar mechtildemechtilde h6   &(  @ 1?I: + .x9Fd[}x^ C !p#XrkJ7mLxn[*!&,1yN"@l &+u8T#_Z@C %_0|+9*5iI  X/r AR 2EJ &k>e Mgs0KH+e'Cf ?%>sF|>QC5 ) +  :GU>t|Q;a-  l9lcW6\'IQr!P8      ??(0 ` 1CL: 3e>O+rZ]  !K 7@L.Y|xZBOE 2Z*UrsdF136:>}J /Pp3 $'+/26:{; /`& #'+.2Di9 2i #'*.IV2r >^#'*PG+j*q"\#&[6 ?CLG"}H "a! ?"JfP < O1p+ T(` #,4U+J?hKo#az <<s|KCm* -7l]]UV:msx~e- 8`[''smkurx~*Ws' + 1:{_UOlrx}>A`% 3l IA@??quicktext-6.4.4/assets/0000775000175000017500000000000015051542456015537 5ustar mechtildemechtildequicktext-6.4.4/assets/icon16.png0000664000175000017500000000545215051542456017352 0ustar mechtildemechtildePNG  IHDRazTXtRaw profile type exifxڭW[v$)g$C$yj+c=gmtw ew%!#`yd}WwbD=G[g. FA|K"' ΎP $H8fT9K(FPHby5J\x@D$ nTBh%c%$)RҴ\UѠQf-ZcNYs%EcIEK.ʮ⠊*WH7iŖJC=u^H# yQ'L1Ì3MyY|m +tUV}frX㇩=O_ATIܜ1u3͙o6gJ͍aE/>o.16usoXS/)8]4Y)x^zu:hopa`խGGIBB)iC5Ow݄Z}$=(hԞI1HgɫgC䩯$%. ?LXچѣY2%e F :B48Ͻ|<+9X㙈b6ّܡ'ߡ]m$hՃ1B2s*3&rRz&l0jj,CF솃#I3cG~}+;y`FxNmNTY*6[kWMo+84Jlqǘbi7|7XdO?OpHz+sͤ)2c*R9co=b9TqL/ [QX9en:md3-򆾱2t8E| $j ~gQ1 kV7~%8j. _2g_tSkC.{ A ;6C}]"*Z3y YZZkb>_u9nKl"dA=+z$/)h4X'<~zGe2])97fcnt+y?0`XX$峥lϛI>Vb# h( n`/kZρ*^nW+hLa,YQDLnkrCRG@"#9B+gSz{'._2&} '!Սw!"yH|k@2~4wgTd E ~$RufC'A kBOYoi)B k]%eY pCSUq^I*]dР l0m5ho`I@X0˞4h,LZ@h Z0 \땅9y;݆k{a[^ւҕ$Y}hB/-1MZ jeJ^j̧8 !< H3&C[őItz>]XDy9iɴ94lf *!ǢS?digs*{LSŪa {6w(}ɬBVi:Z5ck;7Ga:VHm079kI{Y*)"Yf)-ƕHe/lcɲaIqmjdIzDe Qŧ{m=>zLcL&%)[z |=cJ'!igif˒rD̉^375brHن7Hb.t(ivTœNr{kEw&})U[ g)E6_O#dSOZc~֝IP-(u'8Ӛ"9eW5!ʚc,xĻ].SY ;[ݛ9KJ^@x*82U"Q=uQ6cU|'kDB͝Q e@xr:9ge4D,-뽬CY'`e#!,Sm(pa1dt8E̛Uj{.]Bz}p\/b/X" sH *a+l<"Ԙo+sPPπ h;EBh@$^%^Bȱ(KR9Hg(|$0y^0. &puF:ac JCP4CxPT ҇,IhY jK9r`bscZ |X-P,D"#D(D uLBW ΀\ab ]@$sz+dBW7|vH**9H P{vm diJ7sd?QuZT$H+Qj"Hm~Y1LD AYJhuu&OE%yIڇW)m-ggS 5eZmIb"kDzmd'䍦=yhip1%G钅22Z¯仳T!0?bC8z!&(W.WRT+-$2&AF DxϨɦ @%ʍ-LhElIW7ߌ '}X|XℭQ6\~`0 q` OÏ^Ӂt" 2^HܧQnoF<8_t4Sp?HB2FHz)U|IsJbuyp֫⧙@ yƴ sW>xS49+N-RniFJb`h8i`υ+EHsX$^Ge6Sɢ kc)u9G6VBQ,qHl+{ (Z8ȍ;gj9 K8?HTJT VzDJzAzrRdUy\: $m hZ85H"Idc1nڴzDT_1ZpH6NbAS ;eⴵG#YBâ5MrNpGM @M~$9ꌈ&ǘPIpN 54e^HsHd8Q *C]]*Bsvc(x/' u jJ`U&Q՗tzz4Bgؤau0Mt&K)B#-=AD@@1ń 3C>aQ̫uIja_kd/4-Wd(E3oX(MtzOڮ7I#F!kpguܹZ>yb܌ '606$(I$k@*RO벇%ܞ~Q]w[| + f2o NxRQ-l-FEVGeDn =H AI\h6&u`\d d NxZGNJQB7P Z _8ރim"- ɸxK\6ybrL W|UriS-"1Gb4T@ u8뿵+d6dD}N0z'dDgr1{RdjmRBO4/ig<,*(CA)ڂ^*Vڏ캽ԃ6Y}&S| :}XrbPPwTA-q:[ڬEg9A.|;p oIV8D1'Gj"X6J\f7Ӭףּ,73iMˣE_+ISNRo/r<Ä)Ox b?ɰ+-HQ 5 <伥,:9t̆ `%r65Q~qz38 e&.AEOyk*^q'[YC$ұS"LSbk zۊ "kWCi!KbB@UDMyD".^67NZxaΑ7616Vfuۤ{Vob}7n*1z4#zKess(ፉTwJ|W}UdI󬞏K X/#1C@bЩ?*:9&hUC9.J4e;@LtE94㨆IeU:[SvԭF wJiI2$EٺwfI$:Eu:HOғD=e{CB3, өpA׋Z܃&5ˋ|EQtH-(t. jN݆p#I 5:[m#id ]63|[鶨=ջ*,RI+v J(L.)2e]6e.Nu|6lA#J*`m$^J rۻ!57&*j!Gs8,3(?j|uګCȼw$)c։E)6S&9po; :w:J|+^s YpJFؐ_\;5:h_|,,^OrC_#9ge q)WVS}m֩fNmKh &O@ F3xQEiTƧ2@AD 7zx,E {w@Y0u-;[@k߲@ iL 6j.Ih[El@ 8* ? Gv*_2Pd*{4s3>E~Ï!d7dAq7ÚQ3SA V-VʧAV9B>xj*SN;" _G,Ds+%6~="^]56}ÿM g{ p[!Ppe·5/EA#2\5lvg%[Ljd/FLu!>[n:!  +  G;5NG姫3߶vl.>{o +Y_ԐFh*(Y\*RP0IgB1#6QtB@'**Flu X?l\u~*=Xe 6L8$d%W@KIWNg4$,Hw1Nm%|wqVjϵIUn$/84qj&wSR7:fR,lj&՘Ub'W3 Ċ9@pc[rWUq[֩@CJDshn"z8=EkY>S|Tt?IkBy~lܽwd>d2njcm;^7S0@A!5@>YmOҪ/Թ ?>RZDG°WPDŽ >y:Zkq"H2X9L-5@c$)x"SOc#3$?"_DH4RcJ!3kQ6LV^8h’~#RJMmNƔd>T׆Q{ny/^ )dkJp]|Q;UGMඝdѡ=D':Uч:%ɻ.8BfѬZ"aLwG׿۟Vf=}NQG?Tj TA *~EӢii $>4&&͋}&ݝ!浒X́ gs.&3lRPF2L7 &Guq&>4HPMX͏*5aԙcYC~lfJi$wܮMx%:[Xm6T[ X4ej)jG@*AF<CQ)G<mzY.mϜ zxFu.H6_+qvYf{S^ qh@p BkI1t2e=YfU(1iH$, ̾%v9b# acw O[_p3@bɎb[;^ɻl]:wfT~ 3DN9pܳxj{~oc/JM&*1aVCHJ2sa%(5 L~ )E-:L;Mjݏu~M+zHχ+Tl7,WOn% 7>w 9@ V"ـڿL|"pj} ici$Peuz#/:;AT)W03[/MzeWL֐/RYĴ:kʀa͋1hrdD\FNdpbruK2q%``O=GЍsq!"K%¢ $yTUE7i6Zv]ÊLaAK*Wց_.>f#u#]:\GtK=bT^ݎC|rZ..@[|.yAixa..5 "2KkXSLF̓s>lo4}mg0tNŃz-exr`,.{x0m(@1z@4C*{ L>=i|sn!ȅŒ$o3o[lfFZl4NAtV@jf"c0x.ᇙɔH|,Y v:uOjVd=c=ك Ɍg:6 lRNS1T x)<:Ph4WaԷ,&r2=8Tݴv0^342GR! fɃ#5'aɌ8V'u9#$mH?]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Välj fil att lägga till i bifogade filer" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Datum ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Välj fil att exportera till (UTF-8)" }, "extensionDescription": { "message": "Adds a toolbar with unlimited number of text to quickly insert. It is also possible to use variables like [[TO=firstname]]. With settings for everything." }, "fileNotUTF8": { "message": "The selected file does not seem to be UTF-8 encoded and will not loaded correctly. Please select a different file." }, "group": { "message": "Grupp" }, "importFile": { "message": "Välj fil att importera (UTF-8)" }, "inputText": { "message": "Ange värdet på $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Variabel värde" }, "insertFile": { "message": "Välj fil att infoga (UTF-8)" }, "insertImage": { "message": "Choose image file to insert" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Ny grupp" }, "newScript": { "message": "Nytt skript" }, "newTemplate": { "message": "Ny mall" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "g" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "ä" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "k" }, "quicktext.accesskey.general": { "message": "w" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "p" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "l" }, "quicktext.accesskey.save": { "message": "a" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "q" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Lägg till grupp" }, "quicktext.addScript.label": { "message": "Lägg till skript" }, "quicktext.addTemplate.label": { "message": "Lägg till mall" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Bifogade filer" }, "quicktext.browse.label": { "message": "Bläddra" }, "quicktext.cellularnumber.label": { "message": "Mobilnummer" }, "quicktext.clipboard.label": { "message": "Urklipp" }, "quicktext.close.label": { "message": "Stäng" }, "quicktext.collapseSetting.label": { "message": "Collapse group when it only contains one template" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Counter" }, "quicktext.cursor.label": { "message": "Markörposition" }, "quicktext.custom1.label": { "message": "Övrigt 1" }, "quicktext.custom2.label": { "message": "Övrigt 2" }, "quicktext.custom3.label": { "message": "Övrigt 3" }, "quicktext.custom4.label": { "message": "Övrigt 4" }, "quicktext.dateTime.label": { "message": "Datum/Tid" }, "quicktext.defaultImport.label": { "message": "Importeras vid uppstart" }, "quicktext.displayname.label": { "message": "Kortnamn" }, "quicktext.email.label": { "message": "E-post" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Export" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Faxnummer" }, "quicktext.file.label": { "message": "Arkiv" }, "quicktext.filename.label": { "message": "Filnamn" }, "quicktext.filenameAndSize.label": { "message": "Filnamn and storlek" }, "quicktext.firstname.label": { "message": "Förnamn" }, "quicktext.from.label": { "message": "Från" }, "quicktext.fullname.label": { "message": "Fullständiga namnet" }, "quicktext.general.label": { "message": "Allmänt" }, "quicktext.getScript.label": { "message": "Community Scripts" }, "quicktext.goToHomepage.label": { "message": "Gå till hemsidan" }, "quicktext.group.label": { "message": "Grupp" }, "quicktext.header.label": { "message": "Header (To, Cc, Bcc)" }, "quicktext.help.label": { "message": "Hjälp" }, "quicktext.scripthelp.label": { "message": "Inkompatibelt skript" }, "quicktext.homenumber.label": { "message": "Hemnummer" }, "quicktext.image.label": { "message": "Embedded HTML image" }, "quicktext.import.label": { "message": "Import" }, "quicktext.input.label": { "message": "Input value from user prompt" }, "quicktext.insertAs.label": { "message": "Infoga som" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Infoga fil som HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Infoga fil som text" }, "quicktext.insertfile.label": { "message": "Content from file (quicktext variables will be replaced)" }, "quicktext.jobtitle.label": { "message": "Arbetstitel" }, "quicktext.keyword.label": { "message": "Nyckelord" }, "quicktext.keywordKeySetting.label": { "message": "är den tangent jag vill använda för att aktivera nyckelord" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Efternamn" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "är den tangent jag vill använda för kortkommandon" }, "quicktext.nickname.label": { "message": "Smeknamn" }, "quicktext.none.label": { "message": "Inget" }, "quicktext.orgatt.label": { "message": "Original attachment info" }, "quicktext.orgheader.label": { "message": "Original header info" }, "quicktext.other.label": { "message": "Övrigt" }, "quicktext.remove.label": { "message": "Ta bort" }, "quicktext.resetcounter.label": { "message": "Återställ räknaren" }, "quicktext.showContextMenu.label": { "message": "Visa Quicktext-menyn vid högerklick" }, "quicktext.save.label": { "message": "Spara" }, "quicktext.script.label": { "message": "Skript" }, "quicktext.scripts.label": { "message": "Skript" }, "quicktext.selection.label": { "message": "Selection" }, "quicktext.settings.label": { "message": "Inställningar" }, "quicktext.settings.title": { "message": "Quicktext Inställningar" }, "quicktext.sharingScripts.label": { "message": "Dela skript" }, "quicktext.sharingTemplates.label": { "message": "Dela mallar" }, "quicktext.shortcut.label": { "message": "Kortkommando" }, "quicktext.shortcutTypeAdv.label": { "message": "Använd avancerat läge för kortkommandon" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Mellanslag" }, "quicktext.subject.label": { "message": "Ämne" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Mall" }, "quicktext.templates.label": { "message": "Mallar" }, "quicktext.text.label": { "message": "Text" }, "quicktext.title.label": { "message": "Titel" }, "quicktext.to.label": { "message": "Till" }, "quicktext.url.label": { "message": "Response from URL" }, "quicktext.variables.label": { "message": "Variabler" }, "quicktext.version.label": { "message": "Thunderbird version" }, "quicktext.workphone.label": { "message": "Jobbnummer" }, "remove": { "message": "Är du säker på att du vill tabort ”$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Du har inte sparat de ändringar du gjort. Vill du spara dem?" }, "saveMessageTitle": { "message": "Spara inställningar" }, "scriptError": { "message": "There was an error in your Quicktext script:" }, "scriptLine": { "message": "Line" }, "scriptNotFound": { "message": "Quicktext script “$P1$” not found!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Mall" }, "time": { "message": "Tid ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/cs/0000775000175000017500000000000015051542456016423 5ustar mechtildemechtildequicktext-6.4.4/_locales/cs/messages.json0000664000175000017500000002564615051542456021142 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Vyberte soubor, který se má přidat do příloh" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Datum ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Vyberte pro exportování (UTF-8)" }, "extensionDescription": { "message": "Přidá panel nástrojů do okna pro úpravy e-mailu, takže přidání standardního textu je rychlé a snadné. Je také možné použít proměnné jako [[TO=firstname]]." }, "fileNotUTF8": { "message": "Vybraný soubor zřejmě není kódovaný v UTF-8 a nebude správně načten. Vyberte prosím jiný soubor." }, "group": { "message": "Skupina" }, "importFile": { "message": "Vybarte soubor k importování (UTF-8)" }, "inputText": { "message": "Zadejte hodnotu $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Hodnota proměnné" }, "insertFile": { "message": "Vyberte soubor k vložení (UTF-8)" }, "insertImage": { "message": "Vyberte soubor obrázku ke vložení" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Nová skupina" }, "newScript": { "message": "Nový skript" }, "newTemplate": { "message": "Nová šablona" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "z" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "b" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "w" }, "quicktext.accesskey.help": { "message": "p" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "u" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "y" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Přidat skupinu" }, "quicktext.addScript.label": { "message": "Přidat skript" }, "quicktext.addTemplate.label": { "message": "Přidat šablonu" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Přílohy" }, "quicktext.browse.label": { "message": "Procházet" }, "quicktext.cellularnumber.label": { "message": "Mobilní telefon" }, "quicktext.clipboard.label": { "message": "Schránka" }, "quicktext.close.label": { "message": "Zavřít" }, "quicktext.collapseSetting.label": { "message": "Skrývat názvy skupin, pokud obsahují jen jednu šablonu" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Počítadlo" }, "quicktext.cursor.label": { "message": "Nastavit pozici kurzoru" }, "quicktext.custom1.label": { "message": "Vlastní 1" }, "quicktext.custom2.label": { "message": "Vlastní 2" }, "quicktext.custom3.label": { "message": "Vlastní 3" }, "quicktext.custom4.label": { "message": "Vlastní 4" }, "quicktext.dateTime.label": { "message": "Datum/čas" }, "quicktext.defaultImport.label": { "message": "Importovat po spuštění" }, "quicktext.displayname.label": { "message": "Zobrazované jméno" }, "quicktext.email.label": { "message": "E-mail" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Exportovat" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Fax" }, "quicktext.file.label": { "message": "Soubor" }, "quicktext.filename.label": { "message": "Název souboru" }, "quicktext.filenameAndSize.label": { "message": "Název souboru a velikost" }, "quicktext.firstname.label": { "message": "Jméno" }, "quicktext.from.label": { "message": "Od" }, "quicktext.fullname.label": { "message": "Celé jméno" }, "quicktext.general.label": { "message": "Obecné" }, "quicktext.getScript.label": { "message": "Komunitní skripty" }, "quicktext.goToHomepage.label": { "message": "Přejít na webové stránky" }, "quicktext.group.label": { "message": "Skupina" }, "quicktext.header.label": { "message": "Přidat záhlaví (komu, kopie, skrytá kopie)" }, "quicktext.help.label": { "message": "Nápověda" }, "quicktext.scripthelp.label": { "message": "Nekompatibilní skripts" }, "quicktext.homenumber.label": { "message": "Telefon domů" }, "quicktext.image.label": { "message": "Vložený HTML obrázek" }, "quicktext.import.label": { "message": "Importovat" }, "quicktext.input.label": { "message": "Vstupní hodnota zadaná uživatelem" }, "quicktext.insertAs.label": { "message": "Vložit jako" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Vložit soubor jako HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Vložit soubor jako text" }, "quicktext.insertfile.label": { "message": "Obsah ze souboru (Quicktext proměnné budou nahrazeny)" }, "quicktext.jobtitle.label": { "message": "Pozice" }, "quicktext.keyword.label": { "message": "Klíčové slovo" }, "quicktext.keywordKeySetting.label": { "message": "je klávesa, kterou chci aktivovat klíčové slovo" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Příjmení" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "je modifikátor, který chci používat pro klávesové zkratky" }, "quicktext.nickname.label": { "message": "Přezdívka" }, "quicktext.none.label": { "message": "Žádná" }, "quicktext.orgatt.label": { "message": "Informace o původní příloze" }, "quicktext.orgheader.label": { "message": "Informace o původním záhlaví" }, "quicktext.other.label": { "message": "Ostatní" }, "quicktext.remove.label": { "message": "Odstranit" }, "quicktext.resetcounter.label": { "message": "Vynulovat počítadlo" }, "quicktext.showContextMenu.label": { "message": "Zobrazovat Quicktext v nabídce pod pravým tlačítkem myši" }, "quicktext.save.label": { "message": "Uložit" }, "quicktext.script.label": { "message": "Skript" }, "quicktext.scripts.label": { "message": "Skripty" }, "quicktext.selection.label": { "message": "Výběr" }, "quicktext.settings.label": { "message": "Nastavení" }, "quicktext.settings.title": { "message": "Nastavení Quicktext" }, "quicktext.sharingScripts.label": { "message": "Sdílení skriptů" }, "quicktext.sharingTemplates.label": { "message": "Sdílení šablon" }, "quicktext.shortcut.label": { "message": "Zkratka" }, "quicktext.shortcutTypeAdv.label": { "message": "Používat pokročilý režim zkratek" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Mezera" }, "quicktext.subject.label": { "message": "Předmět" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Šablona" }, "quicktext.templates.label": { "message": "Šablony" }, "quicktext.text.label": { "message": "Text" }, "quicktext.title.label": { "message": "Název" }, "quicktext.to.label": { "message": "Komu" }, "quicktext.url.label": { "message": "Odpověď z URL" }, "quicktext.variables.label": { "message": "Proměnné" }, "quicktext.version.label": { "message": "Verze Thunderbirdu" }, "quicktext.workphone.label": { "message": "Telefon do zaměstnání" }, "remove": { "message": "Opravdu chcete odstranit “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Vaše změny nebyly uloženy. Chcete je uložit?" }, "saveMessageTitle": { "message": "Uložit nastavení" }, "scriptError": { "message": "Ve vašem Quicktext skriptu se objevila chyba:" }, "scriptLine": { "message": "Řádek" }, "scriptNotFound": { "message": "Quicktext script “$P1$” not found!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Šablona" }, "time": { "message": "Čas ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/ru/0000775000175000017500000000000015051542456016444 5ustar mechtildemechtildequicktext-6.4.4/_locales/ru/messages.json0000664000175000017500000003024615051542456021153 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Выберите файл для добавления во вложения" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Дата ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Выберите файл для экспорта в (UTF-8)" }, "extensionDescription": { "message": "Добавляет панель инструментов с неограниченным количеством текста для быстрой вставки. Также можно использовать такие переменные, как [[TO=firstname]]. С настройками для всего." }, "fileNotUTF8": { "message": "Выбранный файл не похож UTF-8 кодированный и загружен некорректно. Выберите другой файл." }, "group": { "message": "Группа" }, "importFile": { "message": "Выберите файл для импорта (UTF-8)" }, "inputText": { "message": "Введите значение $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Значение переменной" }, "insertFile": { "message": "Выберите файл для вставки (UTF-8)" }, "insertImage": { "message": "Выберите изображение для вставки" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Новая группа" }, "newScript": { "message": "Новый скрипт" }, "newTemplate": { "message": "Новый шаблон" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "Д" }, "quicktext.accesskey.addScript": { "message": "б" }, "quicktext.accesskey.addTemplate": { "message": "б" }, "quicktext.accesskey.close": { "message": "З" }, "quicktext.accesskey.communityScripts": { "message": "щ" }, "quicktext.accesskey.export": { "message": "Э" }, "quicktext.accesskey.file": { "message": "Ф" }, "quicktext.accesskey.general": { "message": "Г" }, "quicktext.accesskey.goToHomepage": { "message": "д" }, "quicktext.accesskey.help": { "message": "П" }, "quicktext.accesskey.import": { "message": "И" }, "quicktext.accesskey.remove": { "message": "У" }, "quicktext.accesskey.resetCounter": { "message": "С" }, "quicktext.accesskey.save": { "message": "р" }, "quicktext.accesskey.scripts": { "message": "к" }, "quicktext.accesskey.settings": { "message": "Н" }, "quicktext.accesskey.templates": { "message": "Ш" }, "quicktext.accesskeyScripts.export": { "message": "о" }, "quicktext.accesskeyScripts.import": { "message": "т" }, "quicktext.accesskeyTemplate.export": { "message": "к" }, "quicktext.accesskeyTemplate.import": { "message": "м" }, "quicktext.addGroup.label": { "message": "Добавить группу" }, "quicktext.addScript.label": { "message": "Добавить скрипт" }, "quicktext.addTemplate.label": { "message": "Добавить шаблон" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Вложения" }, "quicktext.browse.label": { "message": "Просмотр" }, "quicktext.cellularnumber.label": { "message": "Мобильный телефон" }, "quicktext.clipboard.label": { "message": "Буфер обмена" }, "quicktext.close.label": { "message": "Закрыть" }, "quicktext.collapseSetting.label": { "message": "Свернуть группу, если она содержит только один шаблон" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Счётчик" }, "quicktext.cursor.label": { "message": "Позиция курсора" }, "quicktext.custom1.label": { "message": "Прочее 1" }, "quicktext.custom2.label": { "message": "Прочее 2" }, "quicktext.custom3.label": { "message": "Прочее 3" }, "quicktext.custom4.label": { "message": "Прочее 4" }, "quicktext.dateTime.label": { "message": "Дата/Время" }, "quicktext.defaultImport.label": { "message": "Импорт при запуске" }, "quicktext.displayname.label": { "message": "Отображаемое имя" }, "quicktext.email.label": { "message": "E-mail" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Экспорт" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Факс" }, "quicktext.file.label": { "message": "Файл" }, "quicktext.filename.label": { "message": "Имя файла" }, "quicktext.filenameAndSize.label": { "message": "Имя файла и размер" }, "quicktext.firstname.label": { "message": "Имя" }, "quicktext.from.label": { "message": "От" }, "quicktext.fullname.label": { "message": "ФИО" }, "quicktext.general.label": { "message": "Главное" }, "quicktext.getScript.label": { "message": "Скрипты сообщества" }, "quicktext.goToHomepage.label": { "message": "На домашнюю страницу" }, "quicktext.group.label": { "message": "Группа" }, "quicktext.header.label": { "message": "Header (To, Cc, Bcc)" }, "quicktext.help.label": { "message": "Помощь" }, "quicktext.scripthelp.label": { "message": "Несовместимый сценарий" }, "quicktext.homenumber.label": { "message": "Домашний телефон" }, "quicktext.image.label": { "message": "Внедрённое HTML изображение" }, "quicktext.import.label": { "message": "Импорт" }, "quicktext.input.label": { "message": "Входящее значение из приглашения пользователя" }, "quicktext.insertAs.label": { "message": "Вставить как" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Вставить файл как HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Вставить файл как текст" }, "quicktext.insertfile.label": { "message": "Содержимое из файла (переменные quicktext будут заменены)" }, "quicktext.jobtitle.label": { "message": "Должность" }, "quicktext.keyword.label": { "message": "Kлючевое слово" }, "quicktext.keywordKeySetting.label": { "message": "клавиша для активации ключевого слова" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Фамилия" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "модификатор для сочетаний клавиш" }, "quicktext.nickname.label": { "message": "Псевдоним" }, "quicktext.none.label": { "message": "Пусто" }, "quicktext.orgatt.label": { "message": "Информация оригинального вложения" }, "quicktext.orgheader.label": { "message": "Информация оригинального заголовка" }, "quicktext.other.label": { "message": "Прочее" }, "quicktext.remove.label": { "message": "Удалить" }, "quicktext.resetcounter.label": { "message": "Сбросить счетчик" }, "quicktext.showContextMenu.label": { "message": "Просмотр Quicktext-меню по правому клику" }, "quicktext.save.label": { "message": "Сохранить" }, "quicktext.script.label": { "message": "Скрипт" }, "quicktext.scripts.label": { "message": "Скрипты" }, "quicktext.selection.label": { "message": "Выделение" }, "quicktext.settings.label": { "message": "Настройки" }, "quicktext.settings.title": { "message": "Настройки Quicktext" }, "quicktext.sharingScripts.label": { "message": "Обмен скриптами" }, "quicktext.sharingTemplates.label": { "message": "Обмен шаблонами" }, "quicktext.shortcut.label": { "message": "Сочетание" }, "quicktext.shortcutTypeAdv.label": { "message": "Использовать расширенный режим для ярлыков" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Пробел" }, "quicktext.subject.label": { "message": "Тема" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Шаблон" }, "quicktext.templates.label": { "message": "Шаблоны" }, "quicktext.text.label": { "message": "Текст" }, "quicktext.title.label": { "message": "Название" }, "quicktext.to.label": { "message": "Кому" }, "quicktext.url.label": { "message": "Ответ из URL" }, "quicktext.variables.label": { "message": "Переменные" }, "quicktext.version.label": { "message": "Версия Thunderbird" }, "quicktext.workphone.label": { "message": "Рабочий телефон" }, "remove": { "message": "Вы уверены, что хотите удалить “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Ваши изменения не были сохранены. Вы хотите сохранить их?" }, "saveMessageTitle": { "message": "Сохранить настройки" }, "scriptError": { "message": "Ошибка в Quicktext скрипте:" }, "scriptLine": { "message": "Строка" }, "scriptNotFound": { "message": "Quicktext скрипт “$P1$” не найден!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Шаблон" }, "time": { "message": "Время ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/pt_BR/0000775000175000017500000000000015051542456017024 5ustar mechtildemechtildequicktext-6.4.4/_locales/pt_BR/messages.json0000664000175000017500000002576715051542456021547 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Escolha o arquivo para adicionar aos anexos" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Data ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Escolha o arquivo para exportar (UTF-8)" }, "extensionDescription": { "message": "Adiciona uma barra de ferramentas com um número ilimitado de texto para inserir rapidamente. Também é possível usar variáveis como [[TO=firstname]]. Com configurações para tudo." }, "fileNotUTF8": { "message": "The selected file does not seem to be UTF-8 encoded and will not loaded correctly. Please select a different file." }, "group": { "message": "Grupo" }, "importFile": { "message": "Escolha o arquivo para importar (UTF-8)" }, "inputText": { "message": "Insira o valor de $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Valor variável" }, "insertFile": { "message": "Escolha o arquivo para inserir (UTF-8)" }, "insertImage": { "message": "Escolha o arquivo de imagem para inserir" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Novo grupo" }, "newScript": { "message": "Novo script" }, "newTemplate": { "message": "Novo modelo" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "a" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "q" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "r" }, "quicktext.accesskey.help": { "message": "u" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "i" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "d" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Adicionar grupo" }, "quicktext.addScript.label": { "message": "Adicionar script" }, "quicktext.addTemplate.label": { "message": "Adicionar modelo" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Anexos" }, "quicktext.browse.label": { "message": "Procurar" }, "quicktext.cellularnumber.label": { "message": "Número de Celular" }, "quicktext.clipboard.label": { "message": "Área de transferência" }, "quicktext.close.label": { "message": "Fechar" }, "quicktext.collapseSetting.label": { "message": "Recolher o grupo quando ele contém apenas um modelo" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Contador" }, "quicktext.cursor.label": { "message": "Posição do Cursor" }, "quicktext.custom1.label": { "message": "Personalizado 1" }, "quicktext.custom2.label": { "message": "Personalizado 2" }, "quicktext.custom3.label": { "message": "Personalizado 3" }, "quicktext.custom4.label": { "message": "Personalizado 4" }, "quicktext.dateTime.label": { "message": "Data/Tempo" }, "quicktext.defaultImport.label": { "message": "Importar na inicialização" }, "quicktext.displayname.label": { "message": "Nome em Exibição" }, "quicktext.email.label": { "message": "E-mail" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Exportar" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Número de fax" }, "quicktext.file.label": { "message": "Arquivo" }, "quicktext.filename.label": { "message": "Nome do arquivo" }, "quicktext.filenameAndSize.label": { "message": "Nome do arquivo e tamanho" }, "quicktext.firstname.label": { "message": "Primeiro nome" }, "quicktext.from.label": { "message": "De" }, "quicktext.fullname.label": { "message": "Nome completo" }, "quicktext.general.label": { "message": "Geral" }, "quicktext.getScript.label": { "message": "Scripts da comunidade" }, "quicktext.goToHomepage.label": { "message": "Ir para a página inicial" }, "quicktext.group.label": { "message": "Grupo" }, "quicktext.header.label": { "message": "Cabeçalho (To, Cc, Bcc)" }, "quicktext.help.label": { "message": "Ajuda" }, "quicktext.scripthelp.label": { "message": "Script incompatível" }, "quicktext.homenumber.label": { "message": "Número Residencial" }, "quicktext.image.label": { "message": "Embedded HTML image" }, "quicktext.import.label": { "message": "Importar" }, "quicktext.input.label": { "message": "Valor de entrada do prompt do usuário" }, "quicktext.insertAs.label": { "message": "Inserir como" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Inserir arquivo como HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Inserir arquivo como texto" }, "quicktext.insertfile.label": { "message": "Conteúdo do arquivo (as variáveis do quicktext serão substituídas)" }, "quicktext.jobtitle.label": { "message": "Titulo no trabalho" }, "quicktext.keyword.label": { "message": "Palavra-chave" }, "quicktext.keywordKeySetting.label": { "message": "é a chave que eu quero usar para ativar uma palavra-chave" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Último nome" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "é o modificador que eu quero usar para os atalhos do teclado" }, "quicktext.nickname.label": { "message": "Apelido" }, "quicktext.none.label": { "message": "Nenhum" }, "quicktext.orgatt.label": { "message": "Informações do anexo original" }, "quicktext.orgheader.label": { "message": "Informações do cabeçalho original" }, "quicktext.other.label": { "message": "Outros" }, "quicktext.remove.label": { "message": "Remover" }, "quicktext.resetcounter.label": { "message": "Reiniciar contador" }, "quicktext.showContextMenu.label": { "message": "Exibir menu de texto rápido com o botão direito do mouse" }, "quicktext.save.label": { "message": "Salvar" }, "quicktext.script.label": { "message": "Script" }, "quicktext.scripts.label": { "message": "Scripts" }, "quicktext.selection.label": { "message": "Seleção" }, "quicktext.settings.label": { "message": "Configurações" }, "quicktext.settings.title": { "message": "Configurações do Quicktext" }, "quicktext.sharingScripts.label": { "message": "Scripts compartilhandos" }, "quicktext.sharingTemplates.label": { "message": "Modelos compartilhandos" }, "quicktext.shortcut.label": { "message": "Atalho" }, "quicktext.shortcutTypeAdv.label": { "message": "Use o modo avançado para atalhos" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Espaço" }, "quicktext.subject.label": { "message": "Assunto" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Modelo" }, "quicktext.templates.label": { "message": "Modelos" }, "quicktext.text.label": { "message": "Texto simples" }, "quicktext.title.label": { "message": "Títlo" }, "quicktext.to.label": { "message": "Para" }, "quicktext.url.label": { "message": "Resposta da URL" }, "quicktext.variables.label": { "message": "Variáveis" }, "quicktext.version.label": { "message": "Versão do Thunderbird" }, "quicktext.workphone.label": { "message": "Número de Trabalho" }, "remove": { "message": "Tem certeza de que deseja remover “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Suas alterações não foram salvas. Deseja salvá-las?" }, "saveMessageTitle": { "message": "Salvar configurações" }, "scriptError": { "message": "There was an error in your Quicktext script:" }, "scriptLine": { "message": "Line" }, "scriptNotFound": { "message": "Quicktext script “$P1$” not found!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Modelo" }, "time": { "message": "Tempo ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/ja/0000775000175000017500000000000015051542456016410 5ustar mechtildemechtildequicktext-6.4.4/_locales/ja/messages.json0000664000175000017500000002664015051542456021122 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "添付するファイルを選択してください" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "日付 ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "ファイルのエクスポート先を選択 (UTF-8)" }, "extensionDescription": { "message": "定型文を素早く挿入できるツールバーをメール編集画面に追加します。[[TO=firstname]]のような変数も使用できます。" }, "fileNotUTF8": { "message": "選択されたファイルはUTF-8でないため正しく読み込まれません。別のファイルを選択してください。" }, "group": { "message": "グループ" }, "importFile": { "message": "インポートするファイルの選択 (UTF-8)" }, "inputText": { "message": "$P1$ の値を入力してください", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "タグの値" }, "insertFile": { "message": "挿入するファイルの選択 (UTF-8)" }, "insertImage": { "message": "挿入する画像を選択" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "新しいグループ" }, "newScript": { "message": "新しいスクリプト" }, "newTemplate": { "message": "新しいテンプレート" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "a" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "c" }, "quicktext.accesskey.export": { "message": "e" }, "quicktext.accesskey.file": { "message": "f" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "p" }, "quicktext.accesskey.import": { "message": "i" }, "quicktext.accesskey.remove": { "message": "d" }, "quicktext.accesskey.resetCounter": { "message": "r" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "t" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "グループを追加" }, "quicktext.addScript.label": { "message": "スクリプトを追加" }, "quicktext.addTemplate.label": { "message": "テンプレートを追加" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "添付ファイル" }, "quicktext.browse.label": { "message": "参照" }, "quicktext.cellularnumber.label": { "message": "携帯電話の番号" }, "quicktext.clipboard.label": { "message": "クリップボード" }, "quicktext.close.label": { "message": "閉じる" }, "quicktext.collapseSetting.label": { "message": "グループにテンプレートが1つしか含まれていない時は、グループとして表示しない" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "カウンター" }, "quicktext.cursor.label": { "message": "カーソルの位置" }, "quicktext.custom1.label": { "message": "カスタム 1" }, "quicktext.custom2.label": { "message": "カスタム 2" }, "quicktext.custom3.label": { "message": "カスタム 3" }, "quicktext.custom4.label": { "message": "カスタム 4" }, "quicktext.dateTime.label": { "message": "日付と時間" }, "quicktext.defaultImport.label": { "message": "起動時にインポート" }, "quicktext.displayname.label": { "message": "表示名" }, "quicktext.email.label": { "message": "メール" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "エクスポート" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "FAX 番号" }, "quicktext.file.label": { "message": "ファイル" }, "quicktext.filename.label": { "message": "ファイルの名前" }, "quicktext.filenameAndSize.label": { "message": "ファイルの名前と容量" }, "quicktext.firstname.label": { "message": "名" }, "quicktext.from.label": { "message": "差出人" }, "quicktext.fullname.label": { "message": "名前" }, "quicktext.general.label": { "message": "全般" }, "quicktext.getScript.label": { "message": "コミュニティスクリプト" }, "quicktext.goToHomepage.label": { "message": "ホームページへ移動" }, "quicktext.group.label": { "message": "グループ" }, "quicktext.header.label": { "message": "ヘッダー (To, Cc, Bcc)" }, "quicktext.help.label": { "message": "ヘルプ" }, "quicktext.scripthelp.label": { "message": "互換性のないスクリプトですか" }, "quicktext.homenumber.label": { "message": "自宅の電話番号" }, "quicktext.image.label": { "message": "埋め込みHTML画像" }, "quicktext.import.label": { "message": "インポート" }, "quicktext.input.label": { "message": "ユーザープロンプトからの入力値" }, "quicktext.insertAs.label": { "message": "挿入形式" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "ファイルを HTML として挿入" }, "quicktext.insertTextFromFileAsText.label": { "message": "ファイルをテキストとして挿入" }, "quicktext.insertfile.label": { "message": "ファイルの内容(quicktext変数が置き換えられます)" }, "quicktext.jobtitle.label": { "message": "肩書き" }, "quicktext.keyword.label": { "message": "キーワード" }, "quicktext.keywordKeySetting.label": { "message": "をキーワードの起動に使用する" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "姓" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "をキーボードショートカットの修飾キーとして使用する" }, "quicktext.nickname.label": { "message": "ニックネーム" }, "quicktext.none.label": { "message": "なし" }, "quicktext.orgatt.label": { "message": "オリジナル添付ファイル情報" }, "quicktext.orgheader.label": { "message": "オリジナルヘッダー情報" }, "quicktext.other.label": { "message": "その他" }, "quicktext.remove.label": { "message": "削除" }, "quicktext.resetcounter.label": { "message": "カウンターをリセット" }, "quicktext.showContextMenu.label": { "message": "コンテキストメニューに Quicktext を表示する" }, "quicktext.save.label": { "message": "保存" }, "quicktext.script.label": { "message": "スクリプト" }, "quicktext.scripts.label": { "message": "スクリプト" }, "quicktext.selection.label": { "message": "選択" }, "quicktext.settings.label": { "message": "設定" }, "quicktext.settings.title": { "message": "Quicktext の設定" }, "quicktext.sharingScripts.label": { "message": "共有スクリプト" }, "quicktext.sharingTemplates.label": { "message": "共有テンプレート" }, "quicktext.shortcut.label": { "message": "ショートカット" }, "quicktext.shortcutTypeAdv.label": { "message": "アドバンスモードでショートカットを使用" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Space" }, "quicktext.subject.label": { "message": "件名" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "テンプレート" }, "quicktext.templates.label": { "message": "テンプレート" }, "quicktext.text.label": { "message": "テキスト" }, "quicktext.title.label": { "message": "タイトル" }, "quicktext.to.label": { "message": "宛先" }, "quicktext.url.label": { "message": "URLからの応答" }, "quicktext.variables.label": { "message": "タグ" }, "quicktext.version.label": { "message": "Thunderbird のバージョン" }, "quicktext.workphone.label": { "message": "勤務先の電話番号" }, "remove": { "message": "「$P1$」 を削除してもよろしいですか?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "設定が変更されています。保存して終了しますか?" }, "saveMessageTitle": { "message": "設定の保存" }, "scriptError": { "message": "Quicktextスクリプトにエラーがあります:" }, "scriptLine": { "message": "行" }, "scriptNotFound": { "message": "Quicktextスクリプト 「$P1$」 が見つかりません!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "テンプレート" }, "time": { "message": "時間 ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/fr/0000775000175000017500000000000015051542456016425 5ustar mechtildemechtildequicktext-6.4.4/_locales/fr/messages.json0000664000175000017500000002605615051542456021140 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Choisir le fichier à ajouter aux pièces jointes" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Date ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Choisir le fichier de destination (UTF-8)" }, "extensionDescription": { "message": "Ajoute une barre d’outils à la fenêtre de composition de message afin d'insérer rapidement des modèles de texte. Il est également possible d'utiliser des variables telles que [[TO=firstname]]." }, "fileNotUTF8": { "message": "Le fichier sélectionné ne semble pas encodé UTF-8 et ne chargera pas correctement. Choisissez un autre fichier." }, "group": { "message": "Groupe" }, "importFile": { "message": "Choisir le fichier à importer (UTF-8)" }, "inputText": { "message": "Entrez la valeur de $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Valeur de la variable" }, "insertFile": { "message": "Choisir le fichier à insérer (UTF-8)" }, "insertImage": { "message": "Choisir le fichier d'image à insérer" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Nouveau groupe" }, "newScript": { "message": "Nouveau script" }, "newTemplate": { "message": "Nouveau modèle" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "e" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "f" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "w" }, "quicktext.accesskey.help": { "message": "d" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "p" }, "quicktext.accesskey.resetCounter": { "message": "u" }, "quicktext.accesskey.save": { "message": "e" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "s" }, "quicktext.accesskey.templates": { "message": "l" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "x" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Ajouter un groupe" }, "quicktext.addScript.label": { "message": "Ajouter un script" }, "quicktext.addTemplate.label": { "message": "Ajouter un modèle" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Pièces jointes" }, "quicktext.browse.label": { "message": "Parcourir" }, "quicktext.cellularnumber.label": { "message": "Portable" }, "quicktext.clipboard.label": { "message": "Presse-papiers" }, "quicktext.close.label": { "message": "Fermer" }, "quicktext.collapseSetting.label": { "message": "Masquer le groupe lorsqu'il ne contient qu'un seul modèle" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Compteur" }, "quicktext.cursor.label": { "message": "Position du curseur" }, "quicktext.custom1.label": { "message": "Personnalisé-1" }, "quicktext.custom2.label": { "message": "Personnalisé-2" }, "quicktext.custom3.label": { "message": "Personnalisé-3" }, "quicktext.custom4.label": { "message": "Personnalisé-4" }, "quicktext.dateTime.label": { "message": "Date/Heure" }, "quicktext.defaultImport.label": { "message": "Importé au démarrage" }, "quicktext.displayname.label": { "message": "Nom à afficher" }, "quicktext.email.label": { "message": "Courriel" }, "quicktext.enterKey.label": { "message": "Entrée" }, "quicktext.export.label": { "message": "Exporter" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Télécopieur" }, "quicktext.file.label": { "message": "Fichier" }, "quicktext.filename.label": { "message": "Nom du fichier" }, "quicktext.filenameAndSize.label": { "message": "Nom et taille du fichier" }, "quicktext.firstname.label": { "message": "Prénom" }, "quicktext.from.label": { "message": "De" }, "quicktext.fullname.label": { "message": "Nom complet" }, "quicktext.general.label": { "message": "Général" }, "quicktext.getScript.label": { "message": "Scripts partagés" }, "quicktext.goToHomepage.label": { "message": "Visiter le site Web" }, "quicktext.group.label": { "message": "Groupe" }, "quicktext.header.label": { "message": "En-têtes (Pour, Cc, Cci)" }, "quicktext.help.label": { "message": "Aide" }, "quicktext.scripthelp.label": { "message": "Script incompatible" }, "quicktext.homenumber.label": { "message": "Tél. domicile" }, "quicktext.image.label": { "message": "Image HTML intégrée" }, "quicktext.import.label": { "message": "Importer" }, "quicktext.input.label": { "message": "Entrez le texte à insérer" }, "quicktext.insertAs.label": { "message": "Insérer en" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Insérer un fichier en HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Insérer un fichier en texte" }, "quicktext.insertfile.label": { "message": "Contenu à partir d'un fichier (les variables Quicktext seront remplacées)" }, "quicktext.jobtitle.label": { "message": "Profession" }, "quicktext.keyword.label": { "message": "Mot-clé" }, "quicktext.keywordKeySetting.label": { "message": "est la touche que je veux utiliser pour activer un mot-clé" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Nom" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "est la touche que je veux utiliser pour les raccourcis clavier" }, "quicktext.nickname.label": { "message": "Surnom" }, "quicktext.none.label": { "message": "Aucun" }, "quicktext.orgatt.label": { "message": "Infos des pièces jointes d'origine" }, "quicktext.orgheader.label": { "message": "Infos des en-têtes d'origine" }, "quicktext.other.label": { "message": "Autres" }, "quicktext.remove.label": { "message": "Supprimer" }, "quicktext.resetcounter.label": { "message": "Réinitialiser le compteur" }, "quicktext.showContextMenu.label": { "message": "Afficher le menu de Quicktext dans le menu contextuel (clic droit)" }, "quicktext.save.label": { "message": "Enregistrer" }, "quicktext.script.label": { "message": "Script" }, "quicktext.scripts.label": { "message": "Scripts" }, "quicktext.selection.label": { "message": "Sélection" }, "quicktext.settings.label": { "message": "Paramètres" }, "quicktext.settings.title": { "message": "Paramètres de Quicktext" }, "quicktext.sharingScripts.label": { "message": "Partage de scripts" }, "quicktext.sharingTemplates.label": { "message": "Partage de modèles" }, "quicktext.shortcut.label": { "message": "Raccourci" }, "quicktext.shortcutTypeAdv.label": { "message": "Utiliser le mode avancé pour les raccourcis clavier" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Espace" }, "quicktext.subject.label": { "message": "Sujet" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Modèle" }, "quicktext.templates.label": { "message": "Modèles" }, "quicktext.text.label": { "message": "Texte" }, "quicktext.title.label": { "message": "Titre" }, "quicktext.to.label": { "message": "Pour" }, "quicktext.url.label": { "message": "Réponse à partir d'une URL" }, "quicktext.variables.label": { "message": "Variables" }, "quicktext.version.label": { "message": "Version de Thunderbird" }, "quicktext.workphone.label": { "message": "Tél. travail" }, "remove": { "message": "Voulez-vous vraiment supprimer « $P1$ » ?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Vos modifications n'ont pas été enregistrées. Souhaitez-vous les enregistrer ?" }, "saveMessageTitle": { "message": "Enregistrement des paramètres" }, "scriptError": { "message": "Il y a une erreur dans votre script Quicktext:" }, "scriptLine": { "message": "Ligne" }, "scriptNotFound": { "message": "Script Quicktext “$P1$” introuvable!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Modèle" }, "time": { "message": "Heure ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/en-US/0000775000175000017500000000000015051542456016745 5ustar mechtildemechtildequicktext-6.4.4/_locales/en-US/messages.json0000664000175000017500000002527615051542456021463 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Choose file to add to attachments" }, "controlKey": { "message": "Ctrl" }, "controlled-via-managed-storage": { "message": "Controlled via managed storage" }, "date": { "message": "Date ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Choose file to export to (UTF-8)" }, "extensionDescription": { "message": "Adds a toolbar with unlimited number of text to quickly insert. It is also possible to use variables like [[TO=firstname]]. With settings for everything." }, "fileNotUTF8": { "message": "The selected file does not seem to be UTF-8 encoded and will not loaded correctly. Please select a different file." }, "group": { "message": "Group" }, "importFile": { "message": "Choose file to import (UTF-8)" }, "inputText": { "message": "Input the value of $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Variable value" }, "insertFile": { "message": "Choose file to insert (UTF-8)" }, "insertImage": { "message": "Choose image file to insert" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "New group" }, "newScript": { "message": "New script" }, "newTemplate": { "message": "New template" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "f" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "p" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "u" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "t" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Add group" }, "quicktext.addScript.label": { "message": "Add script" }, "quicktext.addTemplate.label": { "message": "Add template" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Attachments" }, "quicktext.browse.label": { "message": "Browse" }, "quicktext.cellularnumber.label": { "message": "Cellularnumber" }, "quicktext.clipboard.label": { "message": "Clipboard" }, "quicktext.close.label": { "message": "Close" }, "quicktext.collapseSetting.label": { "message": "Collapse group when it only contains one template" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Counter" }, "quicktext.cursor.label": { "message": "Set cursor position" }, "quicktext.custom1.label": { "message": "Custom 1" }, "quicktext.custom2.label": { "message": "Custom 2" }, "quicktext.custom3.label": { "message": "Custom 3" }, "quicktext.custom4.label": { "message": "Custom 4" }, "quicktext.dateTime.label": { "message": "Date/Time" }, "quicktext.defaultImport.label": { "message": "Import on startup" }, "quicktext.displayname.label": { "message": "Displayname" }, "quicktext.email.label": { "message": "Email" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Export" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Faxnumber" }, "quicktext.file.label": { "message": "File" }, "quicktext.filename.label": { "message": "Filename" }, "quicktext.filenameAndSize.label": { "message": "Filename and size" }, "quicktext.firstname.label": { "message": "Firstname" }, "quicktext.from.label": { "message": "From" }, "quicktext.fullname.label": { "message": "Fullname" }, "quicktext.general.label": { "message": "General" }, "quicktext.getScript.label": { "message": "Community scripts" }, "quicktext.goToHomepage.label": { "message": "Go to homepage" }, "quicktext.group.label": { "message": "Group" }, "quicktext.header.label": { "message": "Add header (To, Cc, Bcc)" }, "quicktext.help.label": { "message": "Help" }, "quicktext.scripthelp.label": { "message": "Incompatible script" }, "quicktext.homenumber.label": { "message": "Homenumber" }, "quicktext.image.label": { "message": "Embedded HTML image" }, "quicktext.import.label": { "message": "Import" }, "quicktext.input.label": { "message": "Input value from user prompt" }, "quicktext.insertAs.label": { "message": "Insert as" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Insert file as HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Insert file as text" }, "quicktext.insertfile.label": { "message": "Content from file (quicktext variables will be replaced)" }, "quicktext.jobtitle.label": { "message": "Jobtitle" }, "quicktext.keyword.label": { "message": "Keyword" }, "quicktext.keywordKeySetting.label": { "message": "is the key I want to use for activating a keyword" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Lastname" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "is the modifier I want to use for the keyboard shortcuts" }, "quicktext.nickname.label": { "message": "Nickname" }, "quicktext.none.label": { "message": "None" }, "quicktext.orgatt.label": { "message": "Original attachment info" }, "quicktext.orgheader.label": { "message": "Original header info" }, "quicktext.other.label": { "message": "Other" }, "quicktext.remove.label": { "message": "Remove" }, "quicktext.resetcounter.label": { "message": "Reset counter" }, "quicktext.showContextMenu.label": { "message": "Show Quicktext-menu on right-click" }, "quicktext.save.label": { "message": "Save" }, "quicktext.script.label": { "message": "Script" }, "quicktext.scripts.label": { "message": "Scripts" }, "quicktext.selection.label": { "message": "Selection" }, "quicktext.settings.label": { "message": "Settings" }, "quicktext.settings.title": { "message": "Quicktext Settings" }, "quicktext.sharingScripts.label": { "message": "Sharing scripts" }, "quicktext.sharingTemplates.label": { "message": "Sharing templates" }, "quicktext.shortcut.label": { "message": "Shortcut" }, "quicktext.shortcutTypeAdv.label": { "message": "Use advanced mode for shortcuts" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Space" }, "quicktext.subject.label": { "message": "Subject" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Template" }, "quicktext.templates.label": { "message": "Templates" }, "quicktext.text.label": { "message": "Text" }, "quicktext.title.label": { "message": "Title" }, "quicktext.to.label": { "message": "To" }, "quicktext.url.label": { "message": "Response from URL" }, "quicktext.variables.label": { "message": "Variables" }, "quicktext.version.label": { "message": "Thunderbird version" }, "quicktext.workphone.label": { "message": "Worknumber" }, "remove": { "message": "Are you sure you want to remove “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Your changes has not been saved. Do you want to save them?" }, "saveMessageTitle": { "message": "Save settings" }, "scriptError": { "message": "There was an error in your Quicktext script:" }, "scriptLine": { "message": "Line" }, "scriptNotFound": { "message": "Quicktext script “$P1$” not found!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Template" }, "time": { "message": "Time ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/it/0000775000175000017500000000000015051542456016432 5ustar mechtildemechtildequicktext-6.4.4/_locales/it/messages.json0000664000175000017500000002611115051542456021135 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Scegli il file da aggiungere agli allegati" }, "controlKey": { "message": "Ctrl" }, "controlled-via-managed-storage": { "message": "Controllato tramite archiviazione gestita" }, "date": { "message": "Data ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Scegli il file in cui esportare (UTF-8)" }, "extensionDescription": { "message": "Aggiunge una barra degli strumenti con un numero illimitato di testi da inserire rapidamente. È anche possibile utilizzare variabili come [[TO=firstname]]. Con impostazioni per tutto." }, "fileNotUTF8": { "message": "Il file selezionato non sembra essere codificato in UTF-8 e non verrà caricato correttamente. Seleziona un altro file." }, "group": { "message": "Gruppo" }, "importFile": { "message": "Scegli il file da importare (UTF-8)" }, "inputText": { "message": "Inserisci il valore di $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Valore della variabile" }, "insertFile": { "message": "Scegli il file da inserire (UTF-8)" }, "insertImage": { "message": "Scegli il file immagine da inserire" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Nuovo Gruppo" }, "newScript": { "message": "Nuovo script" }, "newTemplate": { "message": "Nuovo template" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "f" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "p" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "u" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "t" }, "quicktext.accesskeyScripts.export": { "message": "p" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Aggiungi gruppo" }, "quicktext.addScript.label": { "message": "Aggiungi script" }, "quicktext.addTemplate.label": { "message": "Aggiungi template" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Allegati" }, "quicktext.browse.label": { "message": "Sfoglia" }, "quicktext.cellularnumber.label": { "message": "Numero di cellulare" }, "quicktext.clipboard.label": { "message": "Appunti" }, "quicktext.close.label": { "message": "Chiudi" }, "quicktext.collapseSetting.label": { "message": "Riduci il gruppo quando contiene un solo modello" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Contatore" }, "quicktext.cursor.label": { "message": "Imposta la posizione del cursore" }, "quicktext.custom1.label": { "message": "Personalizzato 1" }, "quicktext.custom2.label": { "message": "Personalizzato 2" }, "quicktext.custom3.label": { "message": "Personalizzato 3" }, "quicktext.custom4.label": { "message": "Personalizzato 4" }, "quicktext.dateTime.label": { "message": "Data/Ora" }, "quicktext.defaultImport.label": { "message": "Importa all'avvio" }, "quicktext.displayname.label": { "message": "Nome visualizzato" }, "quicktext.email.label": { "message": "Email" }, "quicktext.enterKey.label": { "message": "Invio" }, "quicktext.export.label": { "message": "Esporta" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Numero di fax" }, "quicktext.file.label": { "message": "File" }, "quicktext.filename.label": { "message": "Nome del file" }, "quicktext.filenameAndSize.label": { "message": "Nome file e dimensione" }, "quicktext.firstname.label": { "message": "Nome" }, "quicktext.from.label": { "message": "Da" }, "quicktext.fullname.label": { "message": "Nome completo" }, "quicktext.general.label": { "message": "Generale" }, "quicktext.getScript.label": { "message": "Script della community" }, "quicktext.goToHomepage.label": { "message": "Vai alla homepage" }, "quicktext.group.label": { "message": "Gruppo" }, "quicktext.header.label": { "message": "Aggiungi intestazione (A, Cc, Ccn)" }, "quicktext.help.label": { "message": "Aiuto" }, "quicktext.scripthelp.label": { "message": "Scrittura incompatibile" }, "quicktext.homenumber.label": { "message": "Numero di casa" }, "quicktext.image.label": { "message": "Immagine HTML incorporata" }, "quicktext.import.label": { "message": "Importa" }, "quicktext.input.label": { "message": "Valore immesso dall'utente tramite prompt" }, "quicktext.insertAs.label": { "message": "Inserisci come" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Inserisci file come HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Inserisci file come testo" }, "quicktext.insertfile.label": { "message": "Contenuto dal file (le variabili quicktext saranno sostituite)" }, "quicktext.jobtitle.label": { "message": "Titolo di lavoro" }, "quicktext.keyword.label": { "message": "Parola chiave" }, "quicktext.keywordKeySetting.label": { "message": "è il tasto che si vuole utilizzare per attivare una parola chiave" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Cognome" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "è il modificatore che voglio usare per le scorciatoie da tastiera" }, "quicktext.nickname.label": { "message": "Soprannome" }, "quicktext.none.label": { "message": "Nessuno" }, "quicktext.orgatt.label": { "message": "Informazioni sull'allegato originale" }, "quicktext.orgheader.label": { "message": "Informazioni dell'intestazione originale" }, "quicktext.other.label": { "message": "Altro" }, "quicktext.remove.label": { "message": "Rimuovi" }, "quicktext.resetcounter.label": { "message": "Azzera il contatore" }, "quicktext.showContextMenu.label": { "message": "Mostra il menu Quicktext al click destro" }, "quicktext.save.label": { "message": "Salva" }, "quicktext.script.label": { "message": "Script" }, "quicktext.scripts.label": { "message": "Script" }, "quicktext.selection.label": { "message": "Selezione" }, "quicktext.settings.label": { "message": "Impostazioni" }, "quicktext.settings.title": { "message": "Impostazioni Quicktext" }, "quicktext.sharingScripts.label": { "message": "Condivisione di script" }, "quicktext.sharingTemplates.label": { "message": "Condivisione dei modelli" }, "quicktext.shortcut.label": { "message": "Scorciatoia" }, "quicktext.shortcutTypeAdv.label": { "message": "Usa la modalità avanzata per le scorciatoie" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Spazio" }, "quicktext.subject.label": { "message": "Oggetto" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Modello" }, "quicktext.templates.label": { "message": "Modelli" }, "quicktext.text.label": { "message": "Testo" }, "quicktext.title.label": { "message": "Titolo" }, "quicktext.to.label": { "message": "A" }, "quicktext.url.label": { "message": "Risposta dall'URL" }, "quicktext.variables.label": { "message": "Variabili" }, "quicktext.version.label": { "message": "Versione di Thunderbird" }, "quicktext.workphone.label": { "message": "Numero di lavoro" }, "remove": { "message": "Sei sicuro di voler rimuovere “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Le modifiche non sono state salvate. Vuoi salvarle?" }, "saveMessageTitle": { "message": "Salva impostazioni" }, "scriptError": { "message": "Si è verificato un errore nello script Quicktext:" }, "scriptLine": { "message": "Linea" }, "scriptNotFound": { "message": "Script Quicktext “$P1$” non trovato!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Modello" }, "time": { "message": "Ora ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/hu/0000775000175000017500000000000015051542456016432 5ustar mechtildemechtildequicktext-6.4.4/_locales/hu/messages.json0000664000175000017500000002623315051542456021142 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Fájl kiválasztása mellékletek hozzáadása" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Dátum ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Fájl kiválasztás exportálása (UTF-8)" }, "extensionDescription": { "message": "Eszköztár, parancsfájlok és sablonok korlátlan lehetoségekkel, beleértve a [[TO=firstname]] változókat, a gyors szövegbeillesztéshez" }, "extensionName": { "message": "Gyorsszöveg" }, "fileNotUTF8": { "message": "Nem töltődik be a fájl megfelelően, mert úgy tűnik, hogy a kijelölt fájl nem UTF-8 kódolású. Jelöljön ki egy másik fájlt." }, "group": { "message": "Csoport" }, "importFile": { "message": "Fájl kiválasztás importálása (UTF-8)" }, "inputText": { "message": "A(z) $P1$ érték bevitele", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Változó érték" }, "insertFile": { "message": "Fájl kiválasztás beszúrása (UTF-8)" }, "insertImage": { "message": "Beillesztendő képfájl kiválasztása" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Új csoport" }, "newScript": { "message": "Új parancsfájl" }, "newTemplate": { "message": "Új sablon" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "z" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "t" }, "quicktext.accesskey.file": { "message": "f" }, "quicktext.accesskey.general": { "message": "n" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "g" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "i" }, "quicktext.accesskey.save": { "message": "c" }, "quicktext.accesskey.scripts": { "message": "p" }, "quicktext.accesskey.settings": { "message": "b" }, "quicktext.accesskey.templates": { "message": "o" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Csoport hozzáadása" }, "quicktext.addScript.label": { "message": "Parancsfájl hozzáadása" }, "quicktext.addTemplate.label": { "message": "Sablon hozzáadása" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Mellékletek" }, "quicktext.browse.label": { "message": "Tallózás…" }, "quicktext.cellularnumber.label": { "message": "Mobiltelefonszám" }, "quicktext.clipboard.label": { "message": "Vágólap" }, "quicktext.close.label": { "message": "Bezárás" }, "quicktext.collapseSetting.label": { "message": "Csoport összecsukása amikor egy sablont tartalmaz" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Számláló" }, "quicktext.cursor.label": { "message": "Kurzor helyezkedő" }, "quicktext.custom1.label": { "message": "1. egyéni" }, "quicktext.custom2.label": { "message": "2. egyéni" }, "quicktext.custom3.label": { "message": "3. egyéni" }, "quicktext.custom4.label": { "message": "4. egyéni" }, "quicktext.dateTime.label": { "message": "Dátum/idő" }, "quicktext.defaultImport.label": { "message": "Importálás indításkor" }, "quicktext.displayname.label": { "message": "Megjelenítendő név" }, "quicktext.email.label": { "message": "E-mail" }, "quicktext.enterKey.label": { "message": "Enter" }, "quicktext.export.label": { "message": "Exportálása" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Fax szám" }, "quicktext.file.label": { "message": "Fájl" }, "quicktext.filename.label": { "message": "Fájlnév" }, "quicktext.filenameAndSize.label": { "message": "Fájlnév és fájlméret" }, "quicktext.firstname.label": { "message": "Keresztnév" }, "quicktext.from.label": { "message": "Feladó" }, "quicktext.fullname.label": { "message": "Teljes név" }, "quicktext.general.label": { "message": "Általános" }, "quicktext.getScript.label": { "message": "Közösségi parancsfájlok" }, "quicktext.goToHomepage.label": { "message": "Ugrás a honlapra" }, "quicktext.group.label": { "message": "Csoport" }, "quicktext.header.label": { "message": "Fejléc (Címzett [To], Másolat [Cc], Rejtett másolat [Bcc])" }, "quicktext.help.label": { "message": "Súgó" }, "quicktext.scripthelp.label": { "message": "Nem kompatibilis szkript" }, "quicktext.homenumber.label": { "message": "Otthoni szám" }, "quicktext.image.label": { "message": "Beágyazott HTML-kép" }, "quicktext.import.label": { "message": "Importálása" }, "quicktext.input.label": { "message": "Bemeneti érték a parancssorból" }, "quicktext.insertAs.label": { "message": "Irányított beszúrása…" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Fájl beszúrása mint HTML formátum" }, "quicktext.insertTextFromFileAsText.label": { "message": "Fájl beszúrása mint szöveg formátum" }, "quicktext.insertfile.label": { "message": "Tartalom a fájlból (gyorsszöveg változók lecseréli)" }, "quicktext.jobtitle.label": { "message": "Munka megnevezése" }, "quicktext.keyword.label": { "message": "Kulcsszó" }, "quicktext.keywordKeySetting.label": { "message": "billentyű a kulcsszó aktiválásához" }, "quicktext.label": { "message": "Gyorsszöveg" }, "quicktext.lastname.label": { "message": "Vezetéknév" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "billentyű módosít a billentyűparancsot" }, "quicktext.nickname.label": { "message": "Becenév" }, "quicktext.none.label": { "message": "Nincs" }, "quicktext.orgatt.label": { "message": "Eredeti melléklet adatai" }, "quicktext.orgheader.label": { "message": "Eredeti fejléc adatai" }, "quicktext.other.label": { "message": "Más" }, "quicktext.remove.label": { "message": "Eltávolítás" }, "quicktext.resetcounter.label": { "message": "Számláló visszaállítása" }, "quicktext.showContextMenu.label": { "message": "Jobb kattintással a gyorsszöveg menü megnyitása" }, "quicktext.save.label": { "message": "Mentés" }, "quicktext.script.label": { "message": "Parancsfájl" }, "quicktext.scripts.label": { "message": "Parancsfájlok" }, "quicktext.selection.label": { "message": "Kijelölés" }, "quicktext.settings.label": { "message": "Beállítások" }, "quicktext.settings.title": { "message": "Gyorsszöveg beállításai" }, "quicktext.sharingScripts.label": { "message": "Parancsfájlok megosztása" }, "quicktext.sharingTemplates.label": { "message": "Sablonok megosztása" }, "quicktext.shortcut.label": { "message": "Gyorsbillentyű" }, "quicktext.shortcutTypeAdv.label": { "message": "Használja a speciális módot a gyorsbillentyűkhöz" }, "quicktext.shortname.label": { "message": "Gyorsszöveg" }, "quicktext.spaceKey.label": { "message": "Szóköz" }, "quicktext.subject.label": { "message": "Tárgy" }, "quicktext.tabKey.label": { "message": "Fül" }, "quicktext.template.label": { "message": "Sablon" }, "quicktext.templates.label": { "message": "Sablonok" }, "quicktext.text.label": { "message": "Szöveg" }, "quicktext.title.label": { "message": "Cím" }, "quicktext.to.label": { "message": "Címzett" }, "quicktext.url.label": { "message": "Válasz az URL-címtől" }, "quicktext.variables.label": { "message": "Változók" }, "quicktext.version.label": { "message": "Thunderbird verzió" }, "quicktext.workphone.label": { "message": "Munkahelyi szám" }, "remove": { "message": "Biztos benne, hogy el akarja távolítani a(z) „$P1$”-t?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "A módosításokat nincs elmentve. Szeretné menteni azokat?" }, "saveMessageTitle": { "message": "Beállítások mentése" }, "scriptError": { "message": "Hiba történt a gyorsszöveg parancsfájlban:" }, "scriptLine": { "message": "Sor" }, "scriptNotFound": { "message": "Gyorsszöveg „$P1$”-parancsfájl nem található!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Sablon" }, "time": { "message": "Idő ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/de/0000775000175000017500000000000015051542456016406 5ustar mechtildemechtildequicktext-6.4.4/_locales/de/messages.json0000664000175000017500000002567715051542456021131 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "Dieses Eingabefeld ist veraltet. Verwenden Sie stattdessen [[ATTACHMENT=FILE|]] direkt im Vorlagentext." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Datei zum Anhängen auswählen" }, "controlKey": { "message": "Strg" }, "date": { "message": "Datum ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Datei zum Exportieren auswählen (UTF-8)" }, "extensionDescription": { "message": "Fügt eine Symbolleiste zum E-Mail-Bearbeitenfenster hinzu, sodass Textbausteine einfach und schnell hinzugefügt werden können. Es ist auch möglich, Variablen wie [[TO=firstname]] zu verwenden." }, "fileNotUTF8": { "message": "Die ausgewählte Datei scheint nicht UTF-8-codiert zu sein und kann nicht korrekt geladen werden. Bitte wählen Sie eine andere Datei." }, "group": { "message": "Gruppe" }, "importFile": { "message": "Datei zum Importieren auswählen (UTF-8)" }, "inputText": { "message": "Wert für $P1$ eingeben", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Variablenwert" }, "insertFile": { "message": "Datei zum Einfügen auswählen (UTF-8)" }, "insertImage": { "message": "Bilddatei zum Einfügen auswählen" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Neue Gruppe" }, "newScript": { "message": "Neues Skript" }, "newTemplate": { "message": "Neue Vorlage" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "r" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "d" }, "quicktext.accesskey.general": { "message": "a" }, "quicktext.accesskey.goToHomepage": { "message": "w" }, "quicktext.accesskey.help": { "message": "h" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "f" }, "quicktext.accesskey.resetCounter": { "message": "u" }, "quicktext.accesskey.save": { "message": "s" }, "quicktext.accesskey.scripts": { "message": "k" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "v" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Gruppe hinzufügen" }, "quicktext.addScript.label": { "message": "Skript hinzufügen" }, "quicktext.addTemplate.label": { "message": "Vorlage hinzufügen" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Anhang" }, "quicktext.browse.label": { "message": "Suchen" }, "quicktext.cellularnumber.label": { "message": "Mobil" }, "quicktext.clipboard.label": { "message": "Zwischenablage" }, "quicktext.close.label": { "message": "Schließen" }, "quicktext.collapseSetting.label": { "message": "Zusammenklappen, wenn die Gruppe nur eine Vorlage enthält" }, "quicktext.controlKey.label": { "message": "Strg" }, "quicktext.counter.label": { "message": "Zähler" }, "quicktext.cursor.label": { "message": "Eingabemarke positionieren" }, "quicktext.custom1.label": { "message": "Benutzerdef. 1" }, "quicktext.custom2.label": { "message": "Benutzerdef. 2" }, "quicktext.custom3.label": { "message": "Benutzerdef. 3" }, "quicktext.custom4.label": { "message": "Benutzerdef. 4" }, "quicktext.dateTime.label": { "message": "Datum/Zeit" }, "quicktext.defaultImport.label": { "message": "Beim Start importieren" }, "quicktext.displayname.label": { "message": "Anzeigename" }, "quicktext.email.label": { "message": "E-Mail" }, "quicktext.enterKey.label": { "message": "Eingabe" }, "quicktext.export.label": { "message": "Exportieren" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Fax" }, "quicktext.file.label": { "message": "Datei" }, "quicktext.filename.label": { "message": "Dateiname" }, "quicktext.filenameAndSize.label": { "message": "Dateiname und -größe" }, "quicktext.firstname.label": { "message": "Vorname" }, "quicktext.from.label": { "message": "Von" }, "quicktext.fullname.label": { "message": "vollständiger Name" }, "quicktext.general.label": { "message": "Allgemein" }, "quicktext.getScript.label": { "message": "Gemeinsame Skripte" }, "quicktext.goToHomepage.label": { "message": "Zur Website" }, "quicktext.group.label": { "message": "Gruppe" }, "quicktext.header.label": { "message": "Kopfzeile (To, Cc, Bcc) hinzufügen" }, "quicktext.help.label": { "message": "Hilfe" }, "quicktext.scripthelp.label": { "message": "Inkompatibles Skript" }, "quicktext.homenumber.label": { "message": "Tel. Privat" }, "quicktext.image.label": { "message": "Eingebettetes HTML-Bild" }, "quicktext.import.label": { "message": "Importieren" }, "quicktext.input.label": { "message": "Eingabewert von einer Eingabeaufforderung" }, "quicktext.insertAs.label": { "message": "Einfügen als" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Datei als HTML einfügen" }, "quicktext.insertTextFromFileAsText.label": { "message": "Datei als Text einfügen" }, "quicktext.insertfile.label": { "message": "Inhalt einer Datei (Quicktext-Variablen werden ersetzt)" }, "quicktext.jobtitle.label": { "message": "Titel (dienstlich)" }, "quicktext.keyword.label": { "message": "Schlüsselwort" }, "quicktext.keywordKeySetting.label": { "message": "zum Aktivieren eines Schlüsselwortes verwenden" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Name" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "für Tastenkürzel verwenden" }, "quicktext.nickname.label": { "message": "Spitzname" }, "quicktext.none.label": { "message": "Keine" }, "quicktext.orgatt.label": { "message": "Ursprüngliche Anhanginformationen" }, "quicktext.orgheader.label": { "message": "Ursprüngliche Kopfzeileninformationen" }, "quicktext.other.label": { "message": "Andere" }, "quicktext.remove.label": { "message": "Entfernen" }, "quicktext.resetcounter.label": { "message": "Zähler zurücksetzen" }, "quicktext.showContextMenu.label": { "message": "Quicktext-Menü bei Rechtsklick anzeigen" }, "quicktext.save.label": { "message": "Speichern" }, "quicktext.script.label": { "message": "Skript" }, "quicktext.scripts.label": { "message": "Skripte" }, "quicktext.selection.label": { "message": "Markierter Text" }, "quicktext.settings.label": { "message": "Einstellungen" }, "quicktext.settings.title": { "message": "Quicktext-Einstellungen" }, "quicktext.sharingScripts.label": { "message": "Skripte" }, "quicktext.sharingTemplates.label": { "message": "Vorlagen" }, "quicktext.shortcut.label": { "message": "Tastenkürzel" }, "quicktext.shortcutTypeAdv.label": { "message": "Erweiterten Modus für Tastaturkürzel verwenden" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Leertaste" }, "quicktext.subject.label": { "message": "Betreff" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Vorlage" }, "quicktext.templates.label": { "message": "Vorlagen" }, "quicktext.text.label": { "message": "Text" }, "quicktext.title.label": { "message": "Titel" }, "quicktext.to.label": { "message": "An" }, "quicktext.url.label": { "message": "Rückgabewert einer URL-Anfrage" }, "quicktext.variables.label": { "message": "Variablen" }, "quicktext.version.label": { "message": "Thunderbird-Version" }, "quicktext.workphone.label": { "message": "Tel. dienstlich" }, "remove": { "message": "Sind Sie sicher, dass „$P1$“ entfernt werden soll?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Ihre Änderungen wurden noch nicht gespeichert. Jetzt speichern?" }, "saveMessageTitle": { "message": "Einstellungen speichern" }, "scriptError": { "message": "In Ihrem Quicktext-Skript ist ein Fehler aufgetreten:" }, "scriptLine": { "message": "Zeile" }, "scriptNotFound": { "message": "Quicktext-Skript „$P1$“ nicht gefunden!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Vorlage" }, "time": { "message": "Uhrzeit ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/_locales/es/0000775000175000017500000000000015051542456016425 5ustar mechtildemechtildequicktext-6.4.4/_locales/es/messages.json0000664000175000017500000002567015051542456021141 0ustar mechtildemechtilde{ "deprecated_attachment_field": { "message": "This input field is deprecated. Use [[ATTACHMENT=FILE|]] directly in the body of the template instead." }, "altKey": { "message": "Alt" }, "attachmentFile": { "message": "Elegir archivo para añadir a los adjuntos" }, "controlKey": { "message": "Ctrl" }, "date": { "message": "Fecha ($P1$)", "placeholders": { "P1": { "content": "$1" } } }, "exportFile": { "message": "Elegir archivo para exportar a (UTF-8)" }, "extensionDescription": { "message": "Agrega una barra de herramientas con un número ilimitado de texto para insertar rápidamente. También es posible usar variables como [[TO=firstname]]. Con ajustes para todo." }, "fileNotUTF8": { "message": "El archivo seleccionado paraece no estar codificado en UTF-8 y no se ha cargado correctamente. Por favor, selecciona nuevo archivo." }, "group": { "message": "Grupo" }, "importFile": { "message": "Elegir archivo a importar (UTF-8)" }, "inputText": { "message": "Introducir el valor de $P1$", "placeholders": { "P1": { "content": "$1" } } }, "inputTitle": { "message": "Valor variable" }, "insertFile": { "message": "Elegir archivo a insertar (UTF-8)" }, "insertImage": { "message": "Elegir archivo de imagen a insertar" }, "metaKey": { "message": "Meta" }, "newGroup": { "message": "Nuevo grupo" }, "newScript": { "message": "Nuevo script" }, "newTemplate": { "message": "Nueva plantilla" }, "quicktext.HTML.label": { "message": "HTML" }, "quicktext.accesskey.addGroup": { "message": "p" }, "quicktext.accesskey.addScript": { "message": "t" }, "quicktext.accesskey.addTemplate": { "message": "l" }, "quicktext.accesskey.close": { "message": "c" }, "quicktext.accesskey.communityScripts": { "message": "s" }, "quicktext.accesskey.export": { "message": "o" }, "quicktext.accesskey.file": { "message": "a" }, "quicktext.accesskey.general": { "message": "g" }, "quicktext.accesskey.goToHomepage": { "message": "h" }, "quicktext.accesskey.help": { "message": "y" }, "quicktext.accesskey.import": { "message": "r" }, "quicktext.accesskey.remove": { "message": "v" }, "quicktext.accesskey.resetCounter": { "message": "d" }, "quicktext.accesskey.save": { "message": "u" }, "quicktext.accesskey.scripts": { "message": "s" }, "quicktext.accesskey.settings": { "message": "n" }, "quicktext.accesskey.templates": { "message": "t" }, "quicktext.accesskeyScripts.export": { "message": "x" }, "quicktext.accesskeyScripts.import": { "message": "m" }, "quicktext.accesskeyTemplate.export": { "message": "e" }, "quicktext.accesskeyTemplate.import": { "message": "i" }, "quicktext.addGroup.label": { "message": "Agregar grupo" }, "quicktext.addScript.label": { "message": "Agregar script" }, "quicktext.addTemplate.label": { "message": "Agregar plantilla" }, "quicktext.altKey.label": { "message": "Alt" }, "quicktext.attachments.label": { "message": "Adjuntos" }, "quicktext.browse.label": { "message": "Navegar" }, "quicktext.cellularnumber.label": { "message": "Número de móvil" }, "quicktext.clipboard.label": { "message": "Portapapeles" }, "quicktext.close.label": { "message": "Cerrar" }, "quicktext.collapseSetting.label": { "message": "Colapsar grupos cuando solo contengan una plantilla" }, "quicktext.controlKey.label": { "message": "Ctrl" }, "quicktext.counter.label": { "message": "Counter" }, "quicktext.cursor.label": { "message": "Posición del cursos" }, "quicktext.custom1.label": { "message": "Personalizado 1" }, "quicktext.custom2.label": { "message": "Personalizado 2" }, "quicktext.custom3.label": { "message": "Personalizado 3" }, "quicktext.custom4.label": { "message": "Personalizado 4" }, "quicktext.dateTime.label": { "message": "Fecha/Hora" }, "quicktext.defaultImport.label": { "message": "Importar al arrancar" }, "quicktext.displayname.label": { "message": "Nombre mostrado" }, "quicktext.email.label": { "message": "Email" }, "quicktext.enterKey.label": { "message": "Intro" }, "quicktext.export.label": { "message": "Exportar" }, "quicktext.f11Key.label": { "message": "F11" }, "quicktext.f12Key.label": { "message": "F12" }, "quicktext.f2Key.label": { "message": "F2" }, "quicktext.f4Key.label": { "message": "F4" }, "quicktext.f5Key.label": { "message": "F5" }, "quicktext.f8Key.label": { "message": "F8" }, "quicktext.faxnumber.label": { "message": "Número de fax" }, "quicktext.file.label": { "message": "Archivo" }, "quicktext.filename.label": { "message": "Nombre del fichero" }, "quicktext.filenameAndSize.label": { "message": "Nombre del fichero y tamaño" }, "quicktext.firstname.label": { "message": "Nombre" }, "quicktext.from.label": { "message": "De" }, "quicktext.fullname.label": { "message": "Nombre completo" }, "quicktext.general.label": { "message": "General" }, "quicktext.getScript.label": { "message": "Community Scripts" }, "quicktext.goToHomepage.label": { "message": "Ir a la página de inicio" }, "quicktext.group.label": { "message": "Grupo" }, "quicktext.header.label": { "message": "Cabecera (A, Cc, Bcc)" }, "quicktext.help.label": { "message": "Ayuda" }, "quicktext.scripthelp.label": { "message": "¿Guión incompatible" }, "quicktext.homenumber.label": { "message": "Número de casa" }, "quicktext.image.label": { "message": "Embedded HTML image" }, "quicktext.import.label": { "message": "Importar" }, "quicktext.input.label": { "message": "Input value from user prompt" }, "quicktext.insertAs.label": { "message": "Insertar como" }, "quicktext.insertTextFromFileAsHTML.label": { "message": "Insertar archivo como HTML" }, "quicktext.insertTextFromFileAsText.label": { "message": "Insertar archivo como texto" }, "quicktext.insertfile.label": { "message": "Content from file (quicktext variables will be replaced)" }, "quicktext.jobtitle.label": { "message": "Trabajo" }, "quicktext.keyword.label": { "message": "Palabra clave" }, "quicktext.keywordKeySetting.label": { "message": "es la tecla que quiero usar para activar una palabra clave" }, "quicktext.label": { "message": "Quicktext" }, "quicktext.lastname.label": { "message": "Apellidos" }, "quicktext.metaKey.label": { "message": "Meta" }, "quicktext.modifierSetting.label": { "message": "es el modificador que quiero usar para el acceso directo del teclado" }, "quicktext.nickname.label": { "message": "Apodo" }, "quicktext.none.label": { "message": "Ninguno" }, "quicktext.orgatt.label": { "message": "Original attachment info" }, "quicktext.orgheader.label": { "message": "Original header info" }, "quicktext.other.label": { "message": "Otro" }, "quicktext.remove.label": { "message": "Eliminar" }, "quicktext.resetcounter.label": { "message": "Reiniciar contador" }, "quicktext.showContextMenu.label": { "message": "Ver menu-Quicktext con la pulsación del botón derecho del ratón" }, "quicktext.save.label": { "message": "Guardar" }, "quicktext.script.label": { "message": "Script" }, "quicktext.scripts.label": { "message": "Scripts" }, "quicktext.selection.label": { "message": "Selección" }, "quicktext.settings.label": { "message": "Opciones" }, "quicktext.settings.title": { "message": "Opciones de Quicktext" }, "quicktext.sharingScripts.label": { "message": "Compartiendo scripts" }, "quicktext.sharingTemplates.label": { "message": "Compartiendo plantillas" }, "quicktext.shortcut.label": { "message": "Acceso directo" }, "quicktext.shortcutTypeAdv.label": { "message": "Usar el modo avanzado para los accesos directos" }, "quicktext.shortname.label": { "message": "Quicktext" }, "quicktext.spaceKey.label": { "message": "Espacio" }, "quicktext.subject.label": { "message": "Asunto" }, "quicktext.tabKey.label": { "message": "Tab" }, "quicktext.template.label": { "message": "Plantilla" }, "quicktext.templates.label": { "message": "Plantillas" }, "quicktext.text.label": { "message": "Texto" }, "quicktext.title.label": { "message": "Título" }, "quicktext.to.label": { "message": "A" }, "quicktext.url.label": { "message": "Response from URL" }, "quicktext.variables.label": { "message": "Variables" }, "quicktext.version.label": { "message": "Versión del Thunderbird" }, "quicktext.workphone.label": { "message": "Número de trabajo" }, "remove": { "message": "¿Estas seguro que quieres eliminar “$P1$”?", "placeholders": { "P1": { "content": "$1" } } }, "saveMessage": { "message": "Tus cambios no se han guardado. ¿Quieres guardar los cambios?" }, "saveMessageTitle": { "message": "Guardar opciones" }, "scriptError": { "message": "Existe un error en tu script Quicktext:" }, "scriptLine": { "message": "Linea" }, "scriptNotFound": { "message": "¡Script de Quicktext “$P1$” no encontrado!", "placeholders": { "P1": { "content": "$1" } } }, "template": { "message": "Plantilla" }, "time": { "message": "Hora ($P1$)", "placeholders": { "P1": { "content": "$1" } } } } quicktext-6.4.4/README.md0000664000175000017500000000260615051542456015520 0ustar mechtildemechtilde[Quicktext](https://addons.thunderbird.net/addon/quicktext/) is a [Thunderbird](https://www.thunderbird.net/) extension, which has been created by Emil Hesslow. However, he was no longer able to update Quicktext or provide any kind of support. Thankfully he changed the license of Quicktext to MPL 2.0, so I could continue to update the extension and make it work with the most recent version of Thunderbird. I will try to keep Quicktext going, but support will be limited. You may use the [issue section](https://github.com/jobisoft/quicktext/issues) of this repository to report bugs. You may also discuss Quicktext related issues with other users at https://discourse.mozilla.org/c/thunderbird. **Note**: Emil maintained two versions of Quicktext, a free “Standard” version and a paid “Pro” version with additional features. Since he released both versions under MPL 2.0, the [official version of Quicktext](https://addons.thunderbird.net/addon/quicktext/) is based on the “Pro” version. However, the name of the extension remains just “Quicktext”. There is no longer a seperate Quicktext Pro version. More information and usage descriptions can be found in the [wiki](https://github.com/jobisoft/quicktext/wiki) of this repository. **The latest release and a change log can be found on [addons.thunderbird.net](https://addons.thunderbird.net/en-US/thunderbird/addon/quicktext/versions/)**. quicktext-6.4.4/modules/0000775000175000017500000000000015051542456015705 5ustar mechtildemechtildequicktext-6.4.4/modules/storage.mjs0000664000175000017500000001172615051542456020073 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const defaultPrefs = { "counter": 0, "templateFolder": "", "defaultImport": "", "menuCollapse": true, "toolbar": true, "popup": true, "keywordKey": "Tab", "shortcutModifier": "alt", "shortcutTypeAdv": false, "collapseState": "" }; const managedPrefs = [ "defaultImport", "menuCollapse", "popup", "keywordKey", "shortcutModifier", "shortcutTypeAdv", ]; async function getManagedPref(aName) { if (!managedPrefs.includes(aName)) { return undefined; } try { let override = await browser.storage.managed.get({ [aName]: undefined }); return override[aName]; } catch { // No managed storage available. } return undefined; } async function getLocalPref(aName, aFallback = undefined) { const defaultPref = Object.hasOwn(defaultPrefs, aName) ? defaultPrefs[aName] : aFallback return browser.storage.local .get({ [aName]: defaultPref }) .then(o => o[aName]); } export async function getPrefWithManagedInfo(aName, aFallback = undefined) { let managedPref = await getManagedPref(aName); if (managedPref !== undefined) { return { value: managedPref, isManaged: true } } let localPref = await getLocalPref(aName, aFallback); return { value: localPref, isManaged: false } } export async function getPref(aName, aFallback = undefined) { const managedPref = await getManagedPref(aName); if (managedPref !== undefined) { return managedPref; } return getLocalPref(aName, aFallback); } export async function setPref(aName, aValue) { await browser.storage.local.set({ [aName]: aValue }); } export async function clearPref(aName) { await browser.storage.local.remove(aName); } export async function setTemplates(templates) { await browser.storage.local.set({ templates: JSON.stringify(templates) }); } export async function getTemplates() { return browser.storage.local.get({ templates: null }).then( e => e.templates ? JSON.parse(e.templates) : null); } export async function setScripts(scripts) { await browser.storage.local.set({ scripts: JSON.stringify(scripts) }); } export async function getScripts() { return browser.storage.local.get({ scripts: null }).then( e => e.scripts ? JSON.parse(e.scripts) : null); } export async function migrate() { // Migrate options from sync to local storage, as sync storage can only hold // 100 KB which will not be enough for templates. const { userPrefs: syncUserPrefs } = await browser.storage.sync.get({ userPrefs: undefined }); if (syncUserPrefs) { await browser.storage.local.set({ userPrefs: syncUserPrefs }); await browser.storage.sync.remove("userPrefs"); } // Migrate from userPrefs/defaultPrefs objects to *.value and *.default. const { userPrefs: v1UserPrefs } = await browser.storage.local.get({ userPrefs: undefined }); if (v1UserPrefs) { for (let [key, value] of Object.entries(v1UserPrefs)) { await browser.storage.local.set({ [`${key}.value`]: value }); } await browser.storage.local.remove("userPrefs"); } const { defaultPrefs: v1DefaultPrefs } = await browser.storage.local.get({ defaultPrefs: undefined }); if (v1DefaultPrefs) { await browser.storage.local.remove("defaultPrefs"); } // Migrate from *.value and *.default to simple values. for (let aName of Object.keys(defaultPrefs)) { const aValue = await browser.storage.local .get({ [`${aName}.value`]: undefined }) .then(o => o[`${aName}.value`]); if (aValue !== undefined) { await browser.storage.local.remove(`${aName}.value`); await browser.storage.local.set({ [aName]: aValue }); } await browser.storage.local.remove(`${aName}.default`); await browser.storage.local.remove(`${aName}.managed.value`); } } export class StorageListener { #watchedPrefs = []; #listener = null; #timeoutId; #changedWatchedPrefs = {}; #eventEmitter() { this.#listener(this.#changedWatchedPrefs); this.#changedWatchedPrefs = {} } #eventCollapse = async (changes, area) => { if (area == "local") { for (let [key, value] of Object.entries(changes)) { const watchedPref = this.#watchedPrefs.find(p => key == p); // Do not monitor managed prefs. let managedPref = await getManagedPref(key); if (managedPref !== undefined) { continue; } if (watchedPref && value.oldValue != value.newValue) { this.#changedWatchedPrefs[watchedPref] = value; } } if (Object.keys(this.#changedWatchedPrefs).length > 0) { window.clearTimeout(this.#timeoutId); this.#timeoutId = window.setTimeout(() => this.#eventEmitter(), 500); } } } constructor(options = {}) { this.#watchedPrefs = options.watchedPrefs || []; this.#listener = options.listener; browser.storage.onChanged.addListener(this.#eventCollapse); } }quicktext-6.4.4/modules/menus.mjs0000664000175000017500000002247315051542456017557 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as quicktext from "/modules/quicktext.mjs"; import * as storage from "/modules/storage.mjs"; import * as utils from "/modules/utils.mjs"; let composeContextEntries = []; export async function buildComposeBodyMenu() { await processMenuData(await getComposeBodyMenuData()); // Update the menus before showing them. messenger.menus.onShown.addListener(async () => { await updateDateTimeMenus(); messenger.menus.refresh(); }); new storage.StorageListener( { watchedPrefs: ["templates", "popup", "menuCollapse"], listener: async (changes) => { // Throw away the menu. for (let entry of composeContextEntries.reverse()) { await messenger.menus.remove(entry); } composeContextEntries = []; const popup = await storage.getPref("popup"); if (popup) { await processMenuData(await getComposeBodyMenuData()); } } } ) } async function processMenuData(menuData, parentId) { for (let entry of menuData) { let createData = {} createData.id = parentId ? `${parentId}.${entry.id}` : entry.id; if (entry.type == "separator") { createData.type = entry.type; } else { createData.title = entry.title ? entry.title : messenger.i18n.getMessage(`quicktext.${entry.id}.label`); } if (entry.contexts) createData.contexts = entry.contexts; if (entry.visible) createData.visible = entry.visible; if (entry.onclick) createData.onclick = entry.onclick; if (parentId) createData.parentId = parentId; const created = Promise.withResolvers() const id = messenger.menus.create(createData, () => { let receivedError = browser.runtime.lastError; if (receivedError) { console.error(receivedError); } created.resolve(); }); await created.promise; if (id != createData.id) { console.error(`Menu with requested id <${createData.id}> was created as <${id}>`) } if (composeContextEntries.includes(id)) { console.error(`Menu with id <${id}} exists already!`) } else { composeContextEntries.push(id); } if (entry.id && entry.children) { await processMenuData(entry.children, createData.id); } } } async function getContactMenuData(type) { let fields = ["firstname", "lastname", "fullname", "displayname", "nickname", "email", "workphone", "faxnumber", "cellularnumber", "jobtitle", "custom1", "custom2", "custom3", "custom4"]; let children = []; for (let field of fields) { children.push({ id: field, onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: `${type}=${field}` }) }) } return children; } async function getComposeBodyMenuData() { let menuData = []; let contexts = ["compose_body", "compose_action_menu"]; let templates = await storage.getTemplates(); for (let i = 0; i < templates.groups.length; i++) { let children = []; for (let j = 0; j < templates.texts[i].length; j++) { children.push({ id: `group-${i}-text-${j}`, title: templates.texts[i][j].name, onclick: (info, tab) => quicktext.insertTemplate(tab.id, i, j) }); } // Ignore this group, if it has now children. if (children.length == 0) { continue; } // If this group has only a single child, and menuCollapse is true, print // only that. if (await storage.getPref("menuCollapse") && children.length == 1) { menuData.push({ contexts, ...children[0] }); continue; } menuData.push({ contexts, id: `group-${i}`, title: templates.groups[i].name, children }); } if (templates.groups.length > 0) { menuData.push({ contexts, id: `group-separator`, type: "separator" }); } let now = Date.now(); menuData.push( { contexts, id: "variables", children: [ { id: "to", children: await getContactMenuData("TO") }, { id: "from", children: await getContactMenuData("FROM") }, { id: "attachments", children: [ { id: "filename", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'ATT=name' }) }, { id: "filenameAndSize", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'ATT=full' }) }, ] }, { id: "dateTime", children: [ { id: "date", title: getDateTimeMenuTitle("date-short", now), onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: "DATE" }) }, { id: "date-long", title: getDateTimeMenuTitle("date-long", now), onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: "DATE=long" }) }, { id: "date-month", title: getDateTimeMenuTitle("date-monthname", now), onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: "DATE=monthname" }) }, { id: "time", title: getDateTimeMenuTitle("time-noseconds", now), onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: "TIME" }) }, { id: "time-seconds", title: getDateTimeMenuTitle("time-seconds", now), onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: "TIME=seconds" }) } ] }, { id: "other", children: [ { id: "clipboard", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'CLIPBOARD' }) }, { id: "counter", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'COUNTER' }) }, { id: "subject", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'SUBJECT' }) }, { id: "version", onclick: (info, tab) => quicktext.insertVariable({ tabId: tab.id, variable: 'VERSION' }) }, ] } ] }, { contexts, id: "other", children: [ { id: "insertTextFromFileAsText", onclick: (info, tab) => quicktext.insertContentFromFile(tab.id, "text/plain") }, { id: "insertTextFromFileAsHTML", onclick: (info, tab) => quicktext.insertContentFromFile(tab.id, "text/html") }, ] }, { contexts, id: "separator", type: "separator", }, { contexts, id: "settings", title: messenger.i18n.getMessage("quicktext.settings.title"), onclick: (info, tab) => messenger.Quicktext.openTemplateManager() }, ); return menuData; } function getDateTimeMenuTitle(field, timeStamp) { const fieldType = field.split("-")[0]; return messenger.i18n.getMessage(fieldType, utils.getDateTimeFormat(field, timeStamp)); } async function updateDateTimeMenus() { let fields = ["date-short", "date-long", "date-monthname", "time-noseconds", "time-seconds"]; let menus = ["variables.dateTime."]; let now = Date.now(); for (let menu of menus) { for (let field of fields) { const title = getDateTimeMenuTitle(field, now); await messenger.menus.update(`${menu}${field}`, { title }) } } }quicktext-6.4.4/modules/utils.mjs0000664000175000017500000004106615051542456017567 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export async function registerExternalScriptAddon(id, name, scripts) { let externalScripts = await browser.storage.session .get({ externalScripts: [] }) .then(rv => rv.externalScripts.filter(e => e.id != id)); externalScripts.push({ id, name, scripts, }); await browser.storage.session.set({ externalScripts }); console.log("registered external scripts", externalScripts) } export function getDateTimeFormat(format, timeStamp) { let options = {}; options["date-short"] = { dateStyle: "short" }; options["date-long"] = { dateStyle: "full" }; options["date-monthname"] = { month: "long" }; options["time-noseconds"] = { timeStyle: "short" }; options["time-seconds"] = { timeStyle: "medium" }; return new Intl.DateTimeFormat(messenger.i18n.getUILanguage(), options[format.toLowerCase()]).format(timeStamp) } export function trimString(aStr) { if (!aStr) return ""; return aStr.toString().replace(/(^\s+)|(\s+$)/g, '') } export async function parseDisplayName(addr) { let [rv] = await browser.messengerUtilities.parseMailboxString(addr); return { name: rv?.name || "", email: rv?.email || addr, } } export function replaceText(tag, value, text, { collapseLineBreaks }) { const escapedTag = escapeRegExp(tag); if (value != "") { return text.replace(new RegExp(escapedTag, 'g'), value); } // If value is "", we collapse a leading spaces and optionally linebreaks. Do not use global mode // here, but force this function to be called on each tag (even if used multiple times), so the // fallback regexp can cleanup a line until it is matching the single tag regexp and correctly // removes the entire line. if (collapseLineBreaks) { // Match lines with a single empty tag and optional whitespaces. const singleTagRegExp = new RegExp(`(^|\\r?\\n)( )*${escapedTag}( )*(\\r?\\n|$)`, 'm'); const collapsed = text.replace(singleTagRegExp, (match, leadingLB, leadingWSP, trailingWSP, trailingLB, offset, fullText) => { // leadingLB and trailingLB are either "" or a line break. If we're matching two // line breaks (one before, one after), preserve one. return leadingLB && trailingLB ? leadingLB : ""; }); if (collapsed !== text) { return collapsed; } } // Match empty tags anywhere with optional single space before the tag. return text.replace(new RegExp(`( )?${escapedTag}`, ''), value); } function escapeRegExp(aStr) { return aStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } export function removeBadHTML(aStr) { // Remove the head-tag aStr = aStr.replace(/]*)>.*<\/head>/gim, ''); // Remove html and body tags aStr = aStr.replace(/<(|\/)(head|body)(| [^>]*)>/gim, ''); return aStr; } export function getTypeFromExtension(filename) { let ext = filename.substring(filename.lastIndexOf('.')).toLowerCase(); // Extracted from https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#Image_types switch (ext) { case ".apng": return "image/apng"; case ".bmp": return "image/bmp"; case ".gif": return "image/gif"; case ".ico": case ".cur": return "image/x-icon"; case ".jpg": case ".jpeg": case ".jfif": case ".pjpeg": case ".pjp": return "image/jpeg"; case ".png": return "image/png"; case ".svg": return "image/svg+xml"; case ".tif": case ".tiff": return "image/tiff"; case ".webp": return "image/webp"; default: return "application/octet-stream"; } } export function uint8ArrayToBase64(bytes) { return btoa( bytes.reduce((acc, current) => acc + String.fromCharCode(current), "") ); } export function getLeafName(fileName) { return fileName.split('\\').pop().split('/').pop(); } export async function writeFileToDisc(data, filename) { const blob = new Blob([data], { type: "application/json" }); const url = URL.createObjectURL(blob); try { let id = null; const { promise, resolve } = Promise.withResolvers(); const listener = delta => { if (id == delta.id && delta.state?.current === 'complete') { browser.downloads.onChanged.removeListener(listener); resolve(); } } browser.downloads.onChanged.addListener(listener); id = await browser.downloads.download({ url, filename, saveAs: true, }); await promise; } catch (error) { console.error("Error downloading the file:", error); } URL.revokeObjectURL(url); } export async function pickFileFromDisc(aTypes) { let picker = Promise.withResolvers(); // Hidden input to open file dialog. const inputElement = document.createElement("input"); inputElement.setAttribute("type", "file"); inputElement.addEventListener("change", () => { picker.resolve(inputElement.files) }, false); inputElement.addEventListener("cancel", () => { picker.resolve([]) }, false); let acceptedFileTypes = [] for (let aType of aTypes) { switch (aType) { case 0: // TXT files case "text/plain": acceptedFileTypes.push("text/plain"); break; case 1: // HTML files case "text/html": acceptedFileTypes.push("text/html"); break; case 2: // arbitrary files break; case 3: // legacy Quicktext XML files acceptedFileTypes.push(".xml"); break; case 4: // image files acceptedFileTypes.push("images/*"); break; case 5: // JSON acceptedFileTypes.push(".json"); break; default: // attachments break; } } inputElement.setAttribute("accept", acceptedFileTypes.join(", ")); inputElement.click(); const [file] = await picker.promise; inputElement.remove(); return file; } export async function getTextFileContent(file) { const content = await new Promise(resolve => { const reader = new FileReader(); reader.onloadend = function (evt) { if (evt.target.readyState == FileReader.DONE) { var filedata = evt.target.result; resolve(filedata); } }; reader.readAsText(file) }) return content; } export async function fetchFileAsFile(url, name) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`); } const blob = await response.blob(); const filename = name ?? getLeafName(url); const contentType = blob.type || getTypeFromExtension(filename) return new File([blob], filename, { type: contentType }); } export async function fetchFileAsText(url) { try { const response = await fetch(url); if (response?.ok) { return await response.text(); } throw new Error('Network response was not ok'); } catch (ex) { console.error('There was a problem with the fetch operation:', ex); } } export async function fetchFileAsDataUrl(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); } const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } export async function openPopup(tabId, config) { let status = "none"; let popup = Promise.withResolvers(); let popupId; let parentId = await browser.tabs.get(tabId).then(tab => tab.windowId); let lastFocusedWindow = parentId; const dimension = ({ top, left, width, height }) => { const excessWidth = 100; const excessHeight = 100; return { top: top + Math.round(0.5 * excessWidth), left: left + Math.round(0.5 * excessHeight), width: width - excessWidth, height: height - excessHeight, } } const onRemovedListener = windowId => { if (windowId == popupId) { status = "closed"; popup.resolve(); } }; const onFocusChangedListener = async windowId => { if (status != "active") { return; } let currentlyFocusedWindow = lastFocusedWindow; lastFocusedWindow = windowId; // GOAL: Force our popup window to always be directly above the parent. if (windowId == popupId && currentlyFocusedWindow != parentId) { await browser.windows.update(parentId, { focused: true }); } // GOAL: We want to allow switching away from the popup to a different // window, but if the parent is focused, bring us back in front. if (windowId == parentId) { await browser.windows.update(popupId, { focused: true, ...dimension(await browser.windows.get(parentId)) }); } }; const onMessageListener = (info, sender, sendResponse) => { if (sender.tab.windowId != popupId) { return false; } switch (info?.action) { case "config": status = "active"; return Promise.resolve(config); case "close": popup.resolve(info.rv); status = "closed"; return Promise.resolve(); case "isPopoverShown": return Promise.resolve(true); } return false; } browser.runtime.onMessage.addListener(onMessageListener); browser.windows.onRemoved.addListener(onRemovedListener); browser.windows.onFocusChanged.addListener(onFocusChangedListener); popupId = await browser.windows.create({ url: "/html/popup.html", type: "popup", allowScriptsToClose: true, ...dimension(await browser.windows.get(parentId)) }).then(window => window.id); await messenger.tabs.sendMessage(tabId, { setPopoverShown: true, popoverShownValue: true, }); let rv = await popup.promise; browser.runtime.onMessage.removeListener(onMessageListener); browser.windows.onRemoved.removeListener(onRemovedListener); browser.windows.onFocusChanged.removeListener(onFocusChangedListener); await messenger.tabs.sendMessage(tabId, { setPopoverShown: true, popoverShownValue: false, }); return rv; } // Keep this function async, so it can be used in then-chaining. export async function removeProtectedTemplates(templates) { if (!templates) { return null; } const protectedTemplatesIndices = new Set( templates.groups.reduce((indices, value, index) => { if (value.protected) { indices.push(index); } return indices; }, []) ); if (protectedTemplatesIndices.size > 0) { templates = { groups: templates.groups.filter((_, index) => !protectedTemplatesIndices.has(index)), texts: templates.texts.filter((_, index) => !protectedTemplatesIndices.has(index)) } } return templates; } // Keep this function async, so it can be used in then-chaining. export async function removeProtectedScripts(scripts) { if (!scripts) { return null; } const protectedScriptsIndices = new Set( scripts.reduce((indices, value, index) => { if (value.protected) { indices.push(index); } return indices; }, []) ); if (protectedScriptsIndices.size > 0) { scripts = scripts.filter((_, index) => !protectedScriptsIndices.has(index)); } return scripts; } export async function checkBadNameEntries(templates, scripts) { const badSubstrings = ["|", "[[", "]]"]; let badEntries = 0; if (templates?.groups) { badEntries += templates.groups.filter(e => badSubstrings.some(sub => e.name.includes(sub))).length; } if (templates?.texts) { badEntries += templates.texts.flat().filter(e => badSubstrings.some(sub => e.name.includes(sub))).length; } if (scripts) { badEntries += scripts.filter(e => badSubstrings.some(sub => e.name.includes(sub))).length } if (badEntries > 0) { browser.notifications.create("qt-bad-entries", { type: "basic", title: "Quicktext v6", message: `Some of your template, group or script names include one or more forbidden chars ("|", "[[" or "]]"). These entries will not work.`, }); } } const createNotification = async message => { await browser.notifications.create( "qt-duplicated-entries", { type: "basic", title: "Quicktext v6", message }); console.warn(`[Quicktext v6] ${message}`) } export async function checkDuplicatedEntries(templates, scripts) { const findDuplicates = array => { const seen = new Set(); const duplicates = new Set(); for (const item of array) { if (seen.has(item)) { duplicates.add(item); } else { seen.add(item); } } return [...duplicates]; } const scriptNames = Array.isArray(scripts) ? scripts.map(e => e.name.trim()) : [] const duplicatedScriptNames = findDuplicates(scriptNames); if (duplicatedScriptNames.length) { await createNotification( `Invalid script data, multiple scripts with the same name: ${duplicatedScriptNames.join(", ")}` ); } const groupNames = Array.isArray(templates?.groups) ? templates.groups.map(e => e.name.trim()) : [] const duplicatedGroupNames = findDuplicates(groupNames); if (duplicatedGroupNames.length) { await createNotification( `Invalid template data, multiple groups with the same name: ${duplicatedGroupNames.join(", ")}` ); } if (Array.isArray(templates?.texts)) { if (templates.texts.length != groupNames.length) { await createNotification( `Invalid template data, number of groups does not match number of template groups.` ); } for (let i = 0; i < templates.texts.length; i++) { const textNames = templates.texts[i].map(e => e.name.trim()); const duplicatedNames = findDuplicates(textNames); if (duplicatedNames.length) { await createNotification( `Invalid template data, multiple templates in group "${groupNames[i]}" with the same name: ${duplicatedNames.join(", ")}` ) } } } } export async function checkForIncompatibleScripts(scripts) { const targets = ["this.mWindow", "this.mVariables", "this.mQuicktext"]; const incompatibleScripts = scripts.filter(s => targets.some(target => s.script.includes(target)) ); if (incompatibleScripts.length > 0) { browser.notifications.create("qt-incompatible-scripts", { type: "basic", title: "Quicktext v6 - Incompatible Scripts!", message: `Some of your scripts (for example ${incompatibleScripts.map(s => `'${s.name}'`).slice(0, 2).join(" and ")}) are incompatible with Quicktext v6. Click for more details.`, }); } } export async function checkForDeprecatedAttachmentUsage(templates) { const groupNames = Array.isArray(templates?.groups) ? templates.groups.map(e => e.name.trim()) : [] if (templates?.texts) { for (let i = 0; i < templates.texts.length; i++) { const badEntries = templates.texts[i].filter(e => e.attachments).map( e => e.name.trim() ); if (badEntries.length) { await createNotification( `Some of your templates in group "${groupNames[i]}" use the deprecated attachments field instead of the ATTACHMENT tag: ${badEntries.join(", ")}` ); } } } }quicktext-6.4.4/modules/quicktext.mjs0000664000175000017500000002560415051542456020450 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as storage from "/modules/storage.mjs"; import * as utils from "/modules/utils.mjs"; import { QuicktextParser } from "/modules/quicktextParser.mjs"; // Helper export async function readLegacyXmlTemplateFile() { let templateFolder = await storage.getPref("templateFolder"); let { templateFilePath } = await browser.Quicktext.getQuicktextFilePaths(templateFolder); let xmlData = await browser.Quicktext.readTextFile(templateFilePath); return parseLegacyXmlData(xmlData); } export async function readXmlScriptFile() { let templateFolder = await storage.getPref("templateFolder"); let { scriptFilePath } = await browser.Quicktext.getQuicktextFilePaths(templateFolder); let xmlData = await browser.Quicktext.readTextFile(scriptFilePath); return parseLegacyXmlData(xmlData); } export async function parseLegacyXmlData(xmlData) { const domParser = new DOMParser(); const dom = domParser.parseFromString(xmlData, "text/xml"); const version = dom.documentElement.getAttribute("version"); const foundGroups = []; const foundTexts = []; const foundScripts = []; switch (version) { case "2": const filetype = getTagValue(dom.documentElement, "filetype"); switch (filetype) { case "scripts": { const elems = dom.documentElement.getElementsByTagName("script"); for (let i = 0; i < elems.length; i++) { let tmp = { name: getTagValue(elems[i], "name"), script: getTagValue(elems[i], "body"), protected: false }; foundScripts.push(tmp); } } break; case "": case "templates": { const elems = dom.documentElement.getElementsByTagName("menu"); for (let i = 0; i < elems.length; i++) { let tmp = { name: getTagValue(elems[i], "title"), protected: false }; foundGroups.push(tmp); const subTexts = []; const textsNodes = elems[i].getElementsByTagName("texts"); if (textsNodes.length > 0) { const subElems = textsNodes[0].getElementsByTagName("text"); for (let j = 0; j < subElems.length; j++) { let tmp = { name: getTagValue(subElems[j], "name"), text: getTagValue(subElems[j], "body"), shortcut: subElems[j].getAttribute("shortcut"), type: subElems[j].getAttribute("type") == "0" ? "text/plain" : "text/html", keyword: getTagValue(subElems[j], "keyword"), subject: getTagValue(subElems[j], "subject"), attachments: getTagValue(subElems[j], "attachments"), }; subTexts.push(tmp); } } foundTexts.push(subTexts); } } break; default: // Alert the user that the importer don't understand the filetype break; } break; default: console.error("invalid data format", xmlData) return; } const imports = {} if (foundScripts.length > 0) { imports.scripts = []; for (let i = 0; i < foundScripts.length; i++) { imports.scripts.push(foundScripts[i]); } } if (foundGroups.length > 0 && foundTexts.length > 0) { imports.templates = {}; imports.templates.groups = []; for (let i = 0; i < foundGroups.length; i++) { imports.templates.groups.push(foundGroups[i]); } imports.templates.texts = []; for (let i = 0; i < foundTexts.length; i++) { imports.templates.texts.push(foundTexts[i]); } } return imports; } export async function parseConfigFileData(fileData) { let errors = []; try { return JSON.parse(fileData); } catch (e) { errors.push(e); } try { return await parseLegacyXmlData(fileData); } catch (e) { errors.push(e); } console.error("Failed to parse config file, does not seem to be a supported JSON or XML format", errors); } function getTagValue(aElem, aTag) { const tagElem = aElem.getElementsByTagName(aTag); if (tagElem.length > 0) { // can't be used anymore as sometimes there are several CDATA entries - see removeIllegalCharsCDATA // return tagElem[0].firstChild.nodeValue; let result = ''; for (const child of tagElem[0].childNodes) { result = result + child.nodeValue; } return result; } return ""; } // ---- MERGE export function mergeTemplates(templates, importedTemplates, forceProtected = false) { if (importedTemplates.groups && importedTemplates.texts && importedTemplates.texts.length > 0 && importedTemplates.groups.length == importedTemplates.texts.length) { // If a group exists already, import into the existing group. templates.groups.forEach((group, existingGroupIdx) => { let groupImportIdx = importedTemplates.groups.findIndex(i => i.name == group.name); if (groupImportIdx != -1) { console.log(`Found existing group ${group.name} in imported groups.`) templates.groups[existingGroupIdx] = importedTemplates.groups[groupImportIdx]; templates.groups[existingGroupIdx].protected = forceProtected; importedTemplates.groups.splice(groupImportIdx, 1); // Handle texts of this group: // merge imports.texts[groupImportIdx] into templates.texts[existingGroupIdx] templates.texts[existingGroupIdx].forEach((text, existingTextIndex) => { let textImportIdx = importedTemplates.texts[groupImportIdx].findIndex(i => i.name == text.name); if (textImportIdx != -1) { console.log(`Replacing text ${text.name} with imported version.`) templates.texts[existingGroupIdx][existingTextIndex] = importedTemplates.texts[groupImportIdx][textImportIdx]; importedTemplates.texts[groupImportIdx].splice(textImportIdx, 1); } }); // Add remaining texts to this group. templates.texts[existingGroupIdx].push(...importedTemplates.texts[groupImportIdx]); importedTemplates.texts.splice(groupImportIdx, 1); } }); // Add remaining new templates. templates.texts.push(...importedTemplates.texts); templates.groups.push(...importedTemplates.groups.map(g => ({ ...g, protected: forceProtected }))); } } export function mergeScripts(scripts, importedScripts, forceProtected = false) { if (importedScripts && importedScripts.length > 0) { // Overwrite local existing versions. scripts.forEach((script, existingScriptIdx) => { let importScriptIdx = importedScripts.findIndex(i => i.name == script.name); if (importScriptIdx != -1) { console.log(`Replacing script ${script.name} with imported version.`) scripts[existingScriptIdx] = importedScripts[importScriptIdx]; scripts[existingScriptIdx].protected = forceProtected; importedScripts.splice(importScriptIdx, 1); } }); // Add the remaining new scripts. scripts.push(...importedScripts.map(g => ({ ...g, protected: forceProtected }))); } } // ---- INSERT async function getQuicktextParser({ tabId }) { const templates = await storage.getTemplates(); const scripts = await storage.getScripts(); return new QuicktextParser(tabId, templates, scripts); } export async function insertTemplate(tabId, groupIdx, textIdx) { const qParser = await getQuicktextParser({ tabId }); const group = qParser.templates.groups[groupIdx]; const text = qParser.templates.texts[groupIdx][textIdx]; await qParser.clearNonPersistentData(); await insertSubject({ qParser, subject: text.subject }); await insertAttachments({ qParser, attachments: text.attachments }); await qParser.parseAndInsert(`[[TEXT=${group.name}|${text.name}]]`); } export async function insertVariable({ tabId, variable }) { const qParser = await getQuicktextParser({ tabId }) await qParser.clearNonPersistentData(); await qParser.parseAndInsert(`[[${variable}]]`); } async function insertSubject({ qParser, subject }) { if (!subject) { return; } let parsedSubject = await qParser.parse(subject); if (parsedSubject && !parsedSubject.match(/^\s+$/)) { await qParser.setDetail("subject", parsedSubject); } } async function insertAttachments({ qParser, attachments }) { let parsedAttachments = await qParser.parse(attachments); for (let attachment of parsedAttachments.split(";")) { if (!attachment) { continue; } let bytes = await browser.Quicktext.readBinaryFile(attachment); let leafName = utils.getLeafName(attachment); let type = utils.getTypeFromExtension(leafName); let file = new File([bytes], leafName, { type }); await qParser.addAttachment(file); }; } export async function insertContentFromFile(aTabId, insertMode) { let file = await utils.pickFileFromDisc([insertMode]); if (file) { return insertFile(aTabId, file, insertMode); } } export async function insertFile(tabId, file, insertMode) { const content = await utils.getTextFileContent(file); if (!content) { return; } let qParser = await getQuicktextParser({ tabId }); // The content of the file gets parsed as well. Nested templates which force // text insert mode affect the entire insert operation. let parsedContent = await qParser.process_file_content(content, { insertMode, stripHtmlComments: false, }); await qParser.insertBody(parsedContent, { extraSpace: false }); } // This function is called from outside and needs to use data of an existing // parser export async function getTag({ tabId, tag, variables }) { let qParser = await getQuicktextParser({ tabId }) return await qParser[`get_${tag.toLowerCase()}`](variables); } // This function is called from outside and needs to use data of an existing // parser export async function processTag({ tabId, tag, variables }) { let qParser = await getQuicktextParser({ tabId }) return await qParser[`process_${tag.toLowerCase()}`](variables); } // ---- TEMPLATE // This is defined async, so it can be used in an runtime.onMessage listener // without further logic to return a Promise. export async function getKeywordsAndShortcuts() { let templates = await storage.getTemplates(); let keywords = {}; let shortcuts = {}; for (let i = 0; i < templates.groups.length; i++) { for (let j = 0; j < templates.texts[i].length; j++) { let text = templates.texts[i][j]; let shortcut = text.shortcut; if (shortcut != "" && typeof shortcuts[shortcut] == "undefined") { shortcuts[shortcut] = [i, j]; } let keyword = text.keyword; if (keyword != "" && typeof keywords[keyword] == "undefined") keywords[keyword] = [i, j]; } } return { keywords, shortcuts }; } quicktext-6.4.4/modules/quicktextParser.mjs0000664000175000017500000011606715051542456021631 0ustar mechtildemechtilde/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as utils from "/modules/utils.mjs"; import * as storage from "/modules/storage.mjs"; const allowedTags = [ 'ALERT', 'ATT', 'ATTACHMENT', 'CLIPBOARD', 'COUNTER', 'CSCRIPT', 'DATE', 'ESCRIPT', 'FILE', 'IMAGE', 'FROM', 'INPUT', 'ORGATT', 'ORGHEADER', 'SCRIPT', 'SUBJECT', 'TEXT', 'TIME', 'TO', 'URL', 'VERSION', 'SELECTION', 'HEADER' ]; // These tags do not generate content and should be collapsed with a leading line break. const collapsingTags = [ 'ALERT', 'ATTACHMENT', 'HEADER' ] // The value of these tags are persistent and only computed once per tab. All other // tags are computed once per template insertion and then re-use the computed value. // If another template is inserted (or the same template again), the state is cleared. const persistentTags = ['COUNTER', 'ORGATT', 'ORGHEADER', 'VERSION']; // TODO: Some tags (subject, att, from, to) are currently not cached, because they // can be modified in scripts or by other tags. If we find a reliable method // to update the cache using onChange events, we could cache these and declare // them as persistent tags. export class QuicktextParser { constructor(aTabId, templates, scripts) { this.mTabId = aTabId; this.mTemplates = templates; this.mScripts = scripts; this.mStaticDetails = null; //TODO: Evaluate if these these values SHOULD be preserved (as getters/setters // into local storage) // Insert the content as text/plain into an html composer (verbatim). // Can only be changed by the current template or nested templates which by // definition use the same QuicktextParser. This value is currently not saved // nor restored. this.mForceAsText = false; // The template insertion type (text/html or text/plain). this.mInsertType = null; } async parseAndInsert(str) { const parsed = await this.parse(str); if (parsed) { await this.insertBody(parsed, { extraSpace: false }); } } async insertBody(aStr, options = {}) { let { isPlainText } = await this.getStaticDetails(); let extraSpace = options?.extraSpace !== false; if (isPlainText || this.mForceAsText) { await messenger.tabs.sendMessage(this.mTabId, { insertText: aStr, extraSpace, }); } else { await messenger.tabs.sendMessage(this.mTabId, { insertHtml: utils.removeBadHTML(aStr), extraSpace, }); } } get tabId() { return this.mTabId } get scripts() { return this.mScripts; } get templates() { return this.mTemplates; } async getStateData() { return browser.storage.session .get({ [`QuicktextStateData_${this.mTabId}`]: {} }) .then(rv => rv[`QuicktextStateData_${this.mTabId}`]); } async setStateData(value) { return browser.storage.session .set({ [`QuicktextStateData_${this.mTabId}`]: value }); } async clearNonPersistentData() { let stateData = await this.getStateData(); for (let key of Object.keys(stateData)) { if (persistentTags.includes(key)) { continue; } delete stateData[key]; } await this.setStateData(stateData); } async loadStates(itemsWithDefaults) { const stateData = await this.getStateData(); // Shallow clone so we don’t mutate the original. const result = { ...itemsWithDefaults }; for (const key of Object.keys(result)) { if (Object.hasOwn(stateData, key)) { result[key] = stateData[key]; } } return result; } async saveStates(items) { let stateData = await this.getStateData(); for (let [item, value] of Object.entries(items)) { stateData[item] = value; } await this.setStateData(stateData); } async getStaticDetails() { if (!this.mStaticDetails) { this.mStaticDetails = await browser.compose.getComposeDetails(this.mTabId); } return this.mStaticDetails } async getDetails() { return browser.compose.getComposeDetails(this.mTabId); } async setDetail(name, newValue) { await browser.compose.setComposeDetails(this.mTabId, { [name]: newValue }); } async addDetail(name, newValue) { let values = await browser.compose .getComposeDetails(this.tabId) .then(details => details[name]); if (!Array.isArray(values)) { values = [values]; } if (values.includes(newValue)) { return; } values.push(newValue); await browser.compose.setComposeDetails(this.mTabId, { [name]: values }); } async addAttachment(file) { await browser.compose.addAttachment(this.mTabId, { file }) } // These process functions get the data and mostly saves their state, // so if the data is requested again, it is quick. // Not all tags have a process function. // The get-functions takes the data from the process-functions and // returns string depending of what aVariables is. async get_header(aVariables) { if (aVariables.length == 0) { return ""; } let name = aVariables[0].toLowerCase(); switch (name) { case "to": case "cc": case "bcc": await this.addDetail(name, aVariables[1]); break; case "reply-to": await this.addDetail("replyTo", aVariables[1]); break; case "from": case "subject": await this.setDetail(name, aVariables[1]); break; } return ""; } async get_script(aVariables) { return this.process_script(aVariables); } async process_script(aVariables) { if (aVariables.length == 0) return ""; let scriptName = aVariables.shift(); // Looks through all scripts and tries to find the one we look for. for (let script of this.mScripts) { if (script.name == scriptName) { let returnValue = ""; try { // MV2 - allows code injection via strings. returnValue = await browser.tabs.executeScript(this.mTabId, { code: `(async function (tabId, sVariables) { this.identities = {}; for (let func of [ "get", "getDefault", "list" ]) { this.identities[func] = (...params) => browser.runtime.sendMessage({ command: "identitiesAPI", func, params, }) } this.compose = {}; for (let func of [ "getComposeDetails", "setComposeDetails", "addAttachment", "removeAttachment", "updateAttachment", "getAttachmentFile", "listAttachments", "getActiveDictionaries", "setActiveDictionaries", "beginNew", "beginForward", "beginReply", ]) { this.compose[func] = (...params) => browser.runtime.sendMessage({ command: "composeAPI", func, params, }) } this.messages = {}; for (let func of [ "get", "getFull", "getRaw", "listAttachments", "listInlineTextParts", "getAttachmentFile", ]) { this.messages[func] = (...params) => browser.runtime.sendMessage({ command: "messagesAPI", func, params, }) } this.quicktext = { tabId, variables: sVariables, processTag: (tag, ...variables) => browser.runtime.sendMessage({ command: "processTag", tabId, tag, variables, }), getTag: (tag, ...variables) => browser.runtime.sendMessage({ command: "getTag", tabId, tag, variables, }), }; ${script.script}; }).call({}, ${this.mTabId},${JSON.stringify(aVariables)});`, }).then(rv => rv[0] ? rv[0] : ""); // MV3 - No string support :-(. /* returnValue = await browser.scripting.executeScript({ target: { tabId: this.mTabId }, args: [this.mTabId], func: new Function("tabId",`return tabId;`), }).then(rv => rv[0].result); */ // UNSAFE EVAL - Blocked by CPG, banned on ATN. // "content_security_policy": "script-src 'self' 'unsafe-eval'", /* let scope = {} scope.mDetails = await this.getDetails(); scope.mVariables = aVariables; scope.mQuicktext = this; scope.mTabId = this.mTabId; const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor; const func = new AsyncFunction('with(this) { ' + script.script + ' }'); returnValue = await func.call(scope); */ } catch (e) { if (this.mTabId) { await messenger.tabs.sendMessage(this.mTabId, { alertLabel: `[${script.name}] ${browser.i18n.getMessage("scriptError")}\n${e.name}: ${e.message}`, }); } } return returnValue || ""; } } // If we reach this point, the user requested an non-existing script. await messenger.tabs.sendMessage(this.mTabId, { alertLabel: browser.i18n.getMessage("scriptNotFound", [scriptName]), }); return ""; } async get_escript(aVariables) { return this.process_escript(aVariables); } async process_escript(aVariables) { if (aVariables.length < 2) return ""; let [extensionId, scriptName, ...scriptArgs] = aVariables; let transmission = Promise.withResolvers(); try { let port = browser.runtime.connect(extensionId, { name: "quicktext" }); port.onMessage.addListener(async message => { switch (message.command) { case "evaluatedScript": transmission.resolve(message.evaluatedScript); break; case "processTag": { let processedTag = await this[`process_${message.tag.toLowerCase()}`](message.variables); port.postMessage({ command: "processedTag", processedTag }); } break; case "getTag": { let gotTag = await this[`get_${message.tag.toLowerCase()}`](message.variables); port.postMessage({ command: "gotTag", gotTag }); } break; } }); port.postMessage({ command: "evaluateScript", scriptName, scriptArgs, tabId: this.mTabId, }); let rv = await transmission.promise; port.disconnect(); return rv ? rv : ""; } catch (ex) { console.error(`Failed to request script from <${extensionId}>`, ex) } return ""; } async get_cscript(aVariables) { return this.process_cscript(aVariables); } async process_cscript(aVariables) { return this.process_escript(["quicktext.scripts@community.jobisoft.de", ...aVariables]); } // This needs the permission, otherwise requests to remote pages // will fail due to CORS. async process_url(aVariables) { if (aVariables.length == 0) { return ""; } let url = aVariables.shift(); if (url == "") { return ""; } let debug = true; let method = "post"; let post = []; if (aVariables.length > 0) { let variables = aVariables.shift().split(";"); for (let k = 0; k < variables.length; k++) { let tag = variables[k].toLowerCase(); let data = null; switch (tag) { case 'to': case 'att': case 'orgheader': case 'orgatt': data = await this["process_" + tag](); if (typeof data != 'undefined') { for (let i in data) for (let j in data[i]) post.push(tag + '[' + i + '][' + j + ']=' + data[i][j]); } break; case 'from': case 'version': case 'date': case 'time': data = await this["process_" + tag](); if (typeof data != 'undefined') { for (let i in data) post.push(tag + '[' + i + ']=' + data[i]); } break; case 'subject': case 'clipboard': case 'selection': case 'counter': data = await this["process_" + tag](); if (typeof data != 'undefined') post.push(tag + '=' + data); break; case 'post': case 'get': case 'options': method = tag; break; case 'debug': debug = true; break; } } } let response = new Promise(resolve => { let req = new XMLHttpRequest(); req.open(method, url, true); if (method == "post") req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.ontimeout = function () { if (debug) { resolve("Quicktext timeout"); } else { resolve() } }; req.onerror = function () { if (debug) { resolve(`Quicktext global error: ${req.status}`); } else { resolve() } }; req.onload = function () { if (req.status == 200) { resolve(req.responseText); } else if (debug) { resolve(`Quicktext onLoad error: ${req.status}`); } else { resolve(); } }; let postdata = method == "post" ? post.map(encodeURIComponent).join("&") : null; req.send(postdata); }); return response; } async get_url(aVariables) { return this.process_url(aVariables); } async get_file(aVariables) { return this.process_file(aVariables); } async process_file(aVariables) { if (aVariables.length > 0 && aVariables[0] != "") { // Tries to open the file and returning the content. try { let content = await browser.Quicktext.readTextFile(aVariables[0]); let insertMode = aVariables.length > 1 && aVariables[1].includes("force_as_text") ? "text/plain" : "text/html"; let stripHtmlComments = aVariables.length > 1 && aVariables[1].includes("strip_html_comments"); return this.process_file_content(content, { insertMode, stripHtmlComments }); } catch (e) { console.error(e); } } return ""; } async process_file_content(content, options) { let insertMode = options?.insertMode ?? "text/html"; let stripHtmlComments = options?.stripHtmlComments == false; let { isPlainText } = await this.getStaticDetails(); if (insertMode == "text/plain" && isPlainText == false) { this.mForceAsText = true; } if (stripHtmlComments) { content = content.replace(/)/g, ''); } return this.parse(content); } async process_image_content(aVariables) { let [mode, source, type] = aVariables; let mode_lc = mode.toLowerCase(); // The first parameter is optional, defaults to FILE. if (!["url", "file"].includes(mode_lc)) { type = source; source = mode; mode_lc = "file"; } if (!type) { type = "tag" } let src = ""; if (mode && source && type) { // Tries to open the file and return the content try { switch (mode_lc) { case "url": { src = await utils.fetchFileAsDataUrl(source); break; } case "file": { let bytes = await browser.Quicktext.readBinaryFile(source); let leafName = utils.getLeafName(source); let type = utils.getTypeFromExtension(leafName); let binContent = utils.uint8ArrayToBase64(bytes); src = "data:" + type + ";filename=" + leafName + ";base64," + binContent; break; } } } catch (e) { console.error(e); } } if (src) { return (type == "tag") ? "" : src; } return ""; } async get_image(aVariables) { let { isPlainText } = await this.getStaticDetails(); if (!isPlainText) { // image tag may only be added in html mode return this.process_image_content(aVariables); } else { return ""; } } async process_selection(aVariables) { let { isPlainText } = await this.getStaticDetails(); if (isPlainText) { return messenger.tabs.sendMessage(this.mTabId, { getSelection: "TEXT", }); } else { return messenger.tabs.sendMessage(this.mTabId, { getSelection: "HTML", }); } } async get_selection(aVariables) { return this.process_selection(aVariables); } async process_text(aVariables) { if (aVariables.length < 2) return ""; // Looks after the group and text-name and returns // the text from it for (let i = 0; i < this.mTemplates.groups.length; i++) { if (aVariables[0] == this.mTemplates.groups[i].name) { for (let j = 0; j < this.mTemplates.texts[i].length; j++) { let text = this.mTemplates.texts[i][j]; if (aVariables[1] == text.name) { let content = text.text; // Force insertion mode to TEXT if the template requests it. // This will affect also the "parent" template, if the current // template is a nested template, because the entire parsed string // will be inserted in one go. let { isPlainText } = await this.getStaticDetails(); if ( (text.type == "text/plain" || (aVariables.length > 2 && aVariables[2].includes("force_as_text"))) && isPlainText == false ) { this.mForceAsText = true; } // The template insertion type (text/html or text/plain). this.mInsertType = text.type; if (aVariables.length > 2 && aVariables[2].includes("strip_html_comments")) { content = content.replace(/)/g, ''); } return content; } } } } return ""; } async get_text(aVariables) { return this.process_text(aVariables); } async process_input(aVariables) { const inputState = `INPUT_${aVariables[0]}`; let states = await this.loadStates({ [inputState]: { checked: false, data: "" } }); if (!states[inputState].checked) { let rv; let label = browser.i18n.getMessage("inputText", [aVariables[0]]); let value = aVariables[2] ?? ""; // There are two types of input: select and text. if (aVariables[1] == 'select') { let values = value.split(";"); rv = await utils.openPopup(this.mTabId, { selectLabel: label, selectValues: values, }); } else { rv = await utils.openPopup(this.mTabId, { promptLabel: label, promptValue: value, }); } // Note: Empty is cancel. if (rv) { states[inputState].data = rv; states[inputState].checked = true; await this.saveStates(states); } } return states[inputState].data; } async get_input(aVariables) { return this.process_input(aVariables); } async process_alert(aVariables) { messenger.tabs.sendMessage(this.mTabId, { alertLabel: aVariables[0], }); } async get_alert(aVariables) { // An alert does not stop the evaluation. this.process_alert(aVariables); return ""; } async preprocess_org() { let states = await this.loadStates({ "ORGHEADER": { checked: false, data: {} }, "ORGATT": { checked: false, data: [] }, }); if (!states["ORGHEADER"].checked || !states["ORGATT"].checked) { states["ORGHEADER"].checked = true; states["ORGATT"].checked = true; let { relatedMessageId } = await this.getStaticDetails(); if (relatedMessageId) { // Store all headers in states["ORGHEADER"]. let data = await browser.messages.getFull(relatedMessageId); for (let [name, value] of Object.entries(data.headers)) { if (!Object.hasOwn(states["ORGHEADER"].data, name)) { states["ORGHEADER"].data[name] = []; } states["ORGHEADER"].data[name].push(...value); } // Store all attachments in states["ORGATT"]. let attachments = await browser.messages.listAttachments(relatedMessageId); for (let attachment of attachments) { states["ORGATT"].data.push(attachment); // {contentType, name, size, partName} } } await this.saveStates(states) } return { orgHeaderState: states["ORGHEADER"], orgAttState: states["ORGATT"] } } async process_orgheader(aVariables) { const { orgHeaderState } = await this.preprocess_org(); return orgHeaderState.data; } async get_orgheader(aVariables) { if (aVariables.length == 0) { return ""; } let data = await this.process_orgheader(aVariables); let name = aVariables[0].toLowerCase(); let seperator = aVariables.length > 1 ? aVariables[1].replace(/\\n/g, "\n").replace(/\\t/g, "\t") : ", " // data is array of objects, reduce to array of specific object member. if (data[name]) { return data[name].join(seperator); } return ""; } async process_orgatt(aVariables) { const { orgAttState } = await this.preprocess_org(); return orgAttState.data; } async get_orgatt(aVariables) { let data = await this.process_orgatt(aVariables); let seperator = aVariables.length > 0 ? aVariables[0].replace(/\\n/g, "\n").replace(/\\t/g, "\t") : ", " // data is array of objects {contentType, name, size, partName}, reduce to // array of specific object member. return data.map(a => a["name"]).join(seperator); } async process_version(aVariables) { let states = await this.loadStates({ "VERSION": { checked: false, data: {} } }); if (!states["VERSION"].checked) { let info = await browser.runtime.getBrowserInfo(); states["VERSION"].checked = true; states["VERSION"].data['number'] = info.version; states["VERSION"].data['full'] = `${info.name} ${info.version}`; await this.saveStates(states); } return states["VERSION"].data; } async get_version(aVariables = []) { let data = await this.process_version(aVariables); if (aVariables.length < 1) { aVariables.push("full"); } if (Object.hasOwn(data, aVariables[0])) { return data[aVariables[0]]; } return ""; } async process_att(aVariables) { // We cache known attachments, but not the return value itself, since // attachments can be removed/added by scripts. // Note: We do have onAttachmentAdded/onAttachmentRemoved. let att = []; let updated = false; let states = await this.loadStates({ "ATT": { data: {} } }); let attachments = await browser.compose.listAttachments(this.mTabId); for (let attachment of attachments) { if (!Object.hasOwn(states["ATT"], attachment.id)) { let file = await browser.compose.getAttachmentFile(attachment.id); states['ATT'][attachment.id] = [file.name, file.size, file.lastModified]; updated = true; } att.push(states["ATT"][attachment.id]); } if (updated) { await this.saveStates(states); } return att; } async get_att(aVariables) { let data = await this.process_att(aVariables); if (data.length > 0) { let value = []; for (let i in data) { if (aVariables[0] == "full") value.push(data[i][0] + " (" + await browser.messengerUtilities.formatFileSize(data[i][1]) + ")"); else if (aVariables[0] == "modified") value.push(data[i][2]) else value.push(data[i][0]); } if (aVariables.length < 2) aVariables[1] = ", "; return utils.trimString(value.join(aVariables[1].replace(/\\n/g, "\n").replace(/\\t/g, "\t"))); } return ""; } async process_attachment(aVariables) { let [mode, source, name] = aVariables; let mode_lc = mode.toLowerCase(); // The first parameter is optional, defaults to FILE. if (!["url", "file"].includes(mode_lc)) { name = source; source = mode; mode_lc = "file"; } switch (mode_lc) { case "url": { let file = await utils.fetchFileAsFile(source, name); await this.addAttachment(file); break; } case "file": { let bytes = await browser.Quicktext.readBinaryFile(source); let leafName = name ?? utils.getLeafName(source); let type = utils.getTypeFromExtension(leafName); let file = new File([bytes], leafName, { type }); await this.addAttachment(file); break; } } return ""; } async get_attachment(aVariables) { return this.process_attachment(aVariables); } async process_subject(aVariables) { // For now we do not cache the subject. Since scripts can change it, we // need a global onChange event in order to cache and update it correctly. let { subject } = await this.getDetails(); return subject; } async get_subject(aVariables) { return this.process_subject(aVariables); } async preprocess_datetime() { let states = await this.loadStates({ "TIME": { checked: false, data: {} }, "DATE": { checked: false, data: {} }, }); if (!states["TIME"].checked || !states["DATE"].checked) { states["DATE"].checked = true; states["TIME"].checked = true; let timeStamp = new Date(); for (let field of ["long", "short", "monthname"]) { states["DATE"].data[field] = utils.trimString(utils.getDateTimeFormat(`date-${field}`, timeStamp)); } for (let field of ["seconds", "noseconds"]) { states["TIME"].data[field] = utils.trimString(utils.getDateTimeFormat(`time-${field}`, timeStamp)); } await this.saveStates(states); } return { timeState: states["TIME"], dateState: states["DATE"], }; } async process_date(aVariables) { const { dateState } = await this.preprocess_datetime(); return dateState.data; } async process_time(aVariables) { const { timeState } = await this.preprocess_datetime(); return timeState.data; } async get_date(aVariables) { let data = await this.process_date(aVariables); if (aVariables.length < 1) aVariables[0] = "short"; if (Object.hasOwn(data, aVariables[0])) { return data[aVariables[0]]; } return ""; } async get_time(aVariables) { let data = await this.process_time(aVariables); if (aVariables.length < 1) aVariables[0] = "noseconds"; if (Object.hasOwn(data, aVariables[0])) { return data[aVariables[0]]; } return ""; } async process_clipboard() { let states = await this.loadStates({ "CLIPBOARD": { checked: false, data: {} } }); if (!states["CLIPBOARD"].checked) { states['CLIPBOARD'].data.plain = await navigator.clipboard.readText(); const html = await navigator.clipboard.read().then(items => items.find( item => item.types.includes("text/html") )); if (html) { states['CLIPBOARD'].data.html = await html.getType("text/html").then( v => v.text() ); } await this.saveStates(states); } return states['CLIPBOARD'].data; } async get_clipboard(aVariables) { const { isPlainText } = await this.getStaticDetails(); const data = await this.process_clipboard(); const parameter = aVariables?.[0]?.toLowerCase?.(); const getFormat = (parameter) => { switch (parameter) { case "auto": // Auto should never paste verbatim html code into the composer. The // insert type must be text/html and the composer must support html. return (!isPlainText && this.mInsertType == "text/html") ? "html" : "plain"; case "html": return "html"; case "plain": default: return "plain" } } return utils.trimString(data[getFormat(parameter)] || data.plain); } async process_counter(aVariables) { let states = await this.loadStates({ "COUNTER": { checked: false, data: null } }); if (!states["COUNTER"].checked) { states['COUNTER'].checked = true; states['COUNTER'].data = (await storage.getPref("counter")) + 1; await storage.setPref("counter", states['COUNTER'].data); await this.saveStates(states); } return states['COUNTER'].data; } async get_counter(aVariables) { return this.process_counter(aVariables); } async process_from(aVariables) { // For now we do not cache FROM, since it can be changed by scripts. We need // a global on change event for the used identity in order to cache FROM. // Note: We do have onIdentityChanged let details = await this.getDetails(); let identity = await browser.identities.get(details.identityId); let states = {}; states['FROM'] = {}; states['FROM'].data = { 'email': identity.email, 'displayname': identity.name, 'firstname': '', 'lastname': '' }; await this.getcarddata_from(identity, states); return states['FROM'].data; } async getcarddata_from(identity, states) { // 1. TODO: CardBook -> need cardbook api // ... // 2. search identity email let cards = await browser.contacts.quickSearch({ includeRemote: false, searchString: identity.email.toLowerCase() }) let card = cards.find(c => c.type == "contact"); // 3. TODO: vcard of identity if (!card && identity.escapedVCard) { //card = manager.escapedVCardToAbCard(aIdentity.escapedVCard); } if (!card) { return; } // Get directly stored props first. for (let [name, value] of Object.entries(card.properties)) { // For backward compatibility, use lowercase props. states['FROM'].data[name.toLowerCase()] = value; } states['FROM'].data['fullname'] = utils.trimString(states['FROM'].data['firstname'] + " " + states['FROM'].data['lastname']); } async get_from(aVariables) { let data = await this.process_from(aVariables); if (Object.hasOwn(data, aVariables[0])) { return utils.trimString(data[aVariables[0]]); } return ""; } async process_to(aVariables) { // For now we do not cache TO, since it can be changed by scripts or by // the HEADER tag. let states = {}; states['TO'] = {}; states['TO'].data = { 'email': [], 'firstname': [], 'lastname': [], 'fullname': [] }; let details = await this.getDetails(); let emailAddresses = Array.isArray(details.to) ? details.to : [details.to]; for (let i = 0; i < emailAddresses.length; i++) { // TODO: Add code for getting info about all people in a mailing list let contactData = await utils.parseDisplayName(emailAddresses[i]); let k = states['TO'].data['email'].length; states['TO'].data['email'][k] = contactData.email.toLowerCase(); states['TO'].data['fullname'][k] = utils.trimString(contactData.name); states['TO'].data['firstname'][k] = ""; states['TO'].data['lastname'][k] = ""; await this.getcarddata_to(k, states); let validParts = [states['TO'].data['firstname'][k], states['TO'].data['lastname'][k]].filter(e => e.trim() != ""); if (validParts.length == 0) { // if no first and last name, generate them from fullname let parts = states['TO'].data['fullname'][k].replace(/,/g, ", ").split(" ").filter(e => e.trim() != ""); states['TO'].data['firstname'][k] = parts.length > 1 ? utils.trimString(parts.splice(0, 1)) : ""; states['TO'].data['lastname'][k] = utils.trimString(parts.join(" ")); } else { // if we have a first and/or last name (which can only happen if read from card), generate fullname from it states['TO'].data['fullname'][k] = validParts.join(" "); } // swap names if wrong if (states['TO'].data['firstname'][k].endsWith(",")) { let temp_firstname = states['TO'].data['firstname'][k].replace(/,/g, ""); let temp_lastname = states['TO'].data['lastname'][k]; states['TO'].data['firstname'][k] = temp_lastname; states['TO'].data['lastname'][k] = temp_firstname; // rebuild fullname states['TO'].data['fullname'][k] = [states['TO'].data['firstname'][k], states['TO'].data['lastname'][k]].join(" "); } } return states['TO'].data; } async getcarddata_to(aIndex, states) { // 1. CardBook -> need cardbook api // ... // take card value, if it exists // 2. search identity email let cards = await browser.contacts.quickSearch({ includeRemote: false, searchString: states['TO'].data['email'][aIndex].toLowerCase() }) let card = cards.find(c => c.type == "contact"); if (card != null) { // Get directly stored props first. for (let [name, value] of Object.entries(card.properties)) { let lowerCaseName = name.toLowerCase(); if (!Object.hasOwn(states['TO'].data, lowerCaseName)) { states['TO'].data[lowerCaseName] = [] } if (value != "" || !Object.hasOwn(states['TO'].data[lowerCaseName], aIndex) || states['TO'].data[lowerCaseName][aIndex] == "") { states['TO'].data[lowerCaseName][aIndex] = utils.trimString(value); } } } return states; } async get_to(aVariables) { let data = await this.process_to(aVariables); if (Object.hasOwn(data, aVariables[0])) { // use ", " as default seperator let mainSep = (aVariables.length > 1) ? aVariables[1].replace(/\\n/g, "\n").replace(/\\t/g, "\t") : ", "; let lastSep = (aVariables.length > 2) ? aVariables[2].replace(/\\n/g, "\n").replace(/\\t/g, "\t") : mainSep; // clone the data, so we can work on it without mod the source object let entries = data[aVariables[0]].slice(0); let last = entries.pop(); // build the final string let all = []; if (entries.length > 0) all.push(entries.join(mainSep)); all.push(last); return all.join(lastSep); } return ""; } // ------------------------------------------------------------------------- async parse(aStr) { try { // Reparse the text until there is no difference in the text // or that we parse 100 times (so we don't make an infinitive loop) let oldStr; let count = 0; do { count++; oldStr = aStr; aStr = await this.parseText(aStr); } while (aStr != oldStr && count < 20); return aStr; } catch (ex) { console.log(ex); } } async parseText(aStr) { let tags = getTags(aStr); // If we don't find any tags there will be no changes to the string so return. if (tags.length == 0) return aStr; // Replace all tags with there right contents for (let i = 0; i < tags.length; i++) { let value = ""; let variable_limit = -1; switch (tags[i].tagName.toLowerCase()) { case 'att': case 'clipboard': case 'selection': case 'counter': case 'date': case 'subject': case 'time': case 'version': case 'orgatt': variable_limit = 0; break; case 'alert': case 'file': case 'image': case 'from': case 'input': case 'orgheader': case 'script': case 'cscript': case 'to': case 'url': case 'attachment': variable_limit = 1; break; case 'text': case 'header': case 'escript': variable_limit = 2; break; } // if the method "get_[tagname]" exists and there is enough arguments we call it if (typeof this["get_" + tags[i].tagName.toLowerCase()] == "function" && variable_limit >= 0 && tags[i].variables.length >= variable_limit) { value = await this["get_" + tags[i].tagName.toLowerCase()](tags[i].variables); } aStr = utils.replaceText(tags[i].tag, value, aStr, { collapseLineBreaks: collapsingTags.includes(tags[i].tagName) }); } return aStr; } } function getTags(aStr) { // We only get the beginning of the tag. // This is because we want to handle recursive use of tags. // Sorting to test for longer tags first (ATTACHMENT vs ATT). let rexp = new RegExp("\\[\\[((" + allowedTags.sort((a, b) => b.length - a.length).join("|") + ")(\\_[a-z]+)?)", "ig"); let results = []; let result = null; while ((result = rexp.exec(aStr))) results.push(result); // If we did't find any tags we return. if (results.length == 0) return []; // Take care of the tags starting with the last one. let hits = []; results.reverse(); let strLen = aStr.length; for (let i = 0; i < results.length; i++) { let tmpHit = {}; tmpHit.tag = results[i][0]; tmpHit.variables = []; // if the tagname contains a "_"-char that means // that is an old tag and we need to translate it // to a tagname and a variable let pos = results[i][1].indexOf("_"); if (pos > 0) { tmpHit.variables.push(results[i][1].substr(pos + 1).toLowerCase()); tmpHit.tagName = results[i][1].substring(0, pos); } else tmpHit.tagName = results[i][1]; // Get the end of the starttag. pos = results[i].index + results[i][1].length + 2; // If the tag ended here we're done. if (aStr.substr(pos, 2) == "]]") { tmpHit.tag += "]]"; hits = addTag(hits, tmpHit); } // If there are arguments we get them. else if (aStr[pos] == "=") { // We go through until we find ]] but we must have went // through the same amount of [ and ] before. So if there // is an tag in the middle we just jump over it. pos++; let bracketCount = 0; let ready = false; let vars = ""; while (!ready && pos < strLen) { if (aStr[pos] == "[") bracketCount++; if (aStr[pos] == "]") { bracketCount--; if (bracketCount == -1 && aStr[pos + 1] == "]") { ready = true; break; } } vars += aStr[pos]; pos++; } // If we found the end we parse the arguments. if (ready) { tmpHit.tag += "=" + vars + "]]"; vars = vars.split("|"); for (let j = 0; j < vars.length; j++) tmpHit.variables.push(vars[j]); // Adds the tag hits = addTag(hits, tmpHit); } } // We don't want to go over this tag again strLen = results[i].index; } hits.reverse(); return hits; } // Checks if the tag isn't added before. // We just want to handle all unique tags once function addTag(aTags, aNewTag) { for (let i = 0; i < aTags.length; i++) if (aTags[i].tag == aNewTag.tag) return aTags; aTags.push(aNewTag); return aTags; } quicktext-6.4.4/manifest.json0000664000175000017500000000511415051542456016737 0ustar mechtildemechtilde{ "manifest_version": 2, "browser_specific_settings": { "gecko": { "id": "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}", "update_url": "https://raw.githubusercontent.com/jobisoft/quicktext/refs/heads/Main/updates.json", "strict_min_version": "128.0", "strict_max_version": "143.*" } }, "name": "Quicktext", "version": "6.4.4", "author": "John Bieling", "homepage_url": "https://github.com/jobisoft/quicktext", "default_locale": "en-US", "description": "__MSG_extensionDescription__", "icons": { "16": "assets/icon16.png", "24": "assets/icon24.png", "32": "assets/icon.png" }, "compose_action": { "default_title": "Quicktext", "type": "menu", "default_label": "", "default_icon": { "16": "assets/icon16.png", "24": "assets/icon24.png", "32": "assets/icon.png" } }, "browser_action": { "default_title": "Quicktext", "default_label": "", "default_icon": { "16": "assets/icon16.png", "24": "assets/icon24.png", "32": "assets/icon.png" } }, "permissions": [ "storage", "menus", "compose", "clipboardRead", "accountsRead", "addressBooks", "messagesRead", "downloads", "notifications", "" ], "background": { "type": "module", "scripts": [ "scripts/background.js" ] }, "experiment_apis": { "LegacyHelper": { "schema": "api/LegacyHelper/schema.json", "parent": { "scopes": [ "addon_parent" ], "paths": [ [ "LegacyHelper" ] ], "script": "api/LegacyHelper/implementation.js" } }, "NotifyTools": { "schema": "api/NotifyTools/schema.json", "parent": { "scopes": [ "addon_parent" ], "paths": [ [ "NotifyTools" ] ], "script": "api/NotifyTools/implementation.js", "events": [ "startup" ] } }, "Quicktext": { "schema": "api/Quicktext/schema.json", "parent": { "scopes": [ "addon_parent" ], "paths": [ [ "Quicktext" ] ], "script": "api/Quicktext/implementation.js" } }, "QuicktextToolbar": { "schema": "api/QuicktextToolbar/schema.json", "parent": { "scopes": [ "addon_parent" ], "paths": [ [ "QuicktextToolbar" ] ], "script": "api/QuicktextToolbar/implementation.js" } } } } quicktext-6.4.4/NEXT.md0000664000175000017500000000313715051542456015341 0ustar mechtildemechtilde[x] load content of template and script files from background via Experiment [x] parse templates and script files from background [x] swap in compose script code and make it use files loaded from background [x] make settings window update/reparse templates [x] eliminate template setter [x] replace and properly implement file picker [x] replace most of utils.mjs and email-addresses.mjs with messengerUtilities API [x] implement choice via popover [x] migrate templates to local storage (keep XML files as backup) and decouple XUL settings dialog from Experiment usage as much as possible [x] properly implement startup imports [x] rename group to groups everywhere [x] find a preliminary solution for scripts, looks like this will not work without breaking existing scripts :-( (they have to use WebExtension code) [x] replace popover by popup, composer gets into an invalid state and many other issues [x] change composer toolbar Experiment to use templates from storage [x] change composer toolbar Experiment to use notifytools to trigger actions [x] implement toolbar reload on template change and time change [x] replace compose action button by menu typed action button [x] remove as much legacy Quicktext code as possible [ ] move composer toolbar Experiment into a separate add-on [x] make template manager use files loaded from background [ ] replace XUL template manager with HTML template manager [ ] add gallery for attachments, images and text files, which are currently references via file system paths, which will not work as a pure webextension [x] keyword + special seems to eat the leading spacequicktext-6.4.4/CONTRIBUTORS.md0000664000175000017500000000053315051542456016515 0ustar mechtildemechtilde## Original Creator * Emil Hesslow ## Current Maintainer * John Bieling ## Contributors * John Bieling * Sebastian Haberey * Emil Hesslow * R Kent James * Philippe Lieser * Christian Schneider * Janning Vygen * bgeiring * drakulis * Peñalara Software S.L. * Samuel Plentz ## Translators * Alexey Sinitsyn (ru) * Óvári (hu) * Ryota Murai (ja)quicktext-6.4.4/xul_settings_dialog/0000775000175000017500000000000015051542456020304 5ustar mechtildemechtildequicktext-6.4.4/xul_settings_dialog/wzQuicktextScript.sys.mjs0000664000175000017500000000130715051542456025364 0ustar mechtildemechtildeexport class wzQuicktextScript { constructor(config) { this.mName = config?.mName || config?.name || ""; this.mScript = config?.mScript || config?.script || ""; this.mProtected = config?.mProtected || config?.protected || false; } get name() { return this.mName; } set name(aName) { if (typeof aName != 'undefined') return this.mName = aName; } get script() { return this.mScript; } set script(aScript) { if (typeof aScript != 'undefined') return this.mScript = aScript; } get protected() { return this.mProtected; } set protected(aProtected) { if (typeof aProtected != 'undefined') return this.mProtected = aProtected; } clone() { return new wzQuicktextScript(this); } }quicktext-6.4.4/xul_settings_dialog/i18n.js0000664000175000017500000000373215051542456021426 0ustar mechtildemechtilde/* * This file is provided by the addon-developer-support repository at * https://github.com/thundernest/addon-developer-support * * For usage descriptions, please check: * https://github.com/thundernest/addon-developer-support/tree/master/scripts/i18n * * Version 1.1 * * Derived from: * http://github.com/piroor/webextensions-lib-l10n * * Original license: * The MIT License, Copyright (c) 2016-2019 YUKI "Piro" Hiroshi * */ var i18n = { updateString(string) { let re = new RegExp(this.keyPrefix + "(.+?)__", "g"); return string.replace(re, (matched) => { const key = matched.slice(this.keyPrefix.length, -2); let rv = this.extension ? this.extension.localeData.localizeMessage(key) : messenger.i18n.getMessage(key); return rv || matched; }); }, updateSubtree(node) { const texts = document.evaluate( 'descendant::text()[contains(self::text(), "' + this.keyPrefix + '")]', node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (let i = 0, maxi = texts.snapshotLength; i < maxi; i++) { const text = texts.snapshotItem(i); if (text.nodeValue.includes(this.keyPrefix)) text.nodeValue = this.updateString(text.nodeValue); } const attributes = document.evaluate( 'descendant::*/attribute::*[contains(., "' + this.keyPrefix + '")]', node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (let i = 0, maxi = attributes.snapshotLength; i < maxi; i++) { const attribute = attributes.snapshotItem(i); if (attribute.value.includes(this.keyPrefix)) attribute.value = this.updateString(attribute.value); } }, updateDocument(options = {}) { this.extension = null; this.keyPrefix = "__MSG_"; if (options) { if (options.extension) this.extension = options.extension; if (options.keyPrefix) this.keyPrefix = options.keyPrefix; } this.updateSubtree(document); }, }; quicktext-6.4.4/xul_settings_dialog/settings.js0000664000175000017500000020111115051542456022476 0ustar mechtildemechtildevar { ExtensionParent } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs" ); var extension = ExtensionParent.GlobalManager.getExtension( "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}" ); var { wzQuicktextGroup } = ChromeUtils.importESModule( `chrome://quicktext/content/wzQuicktextGroup.sys.mjs?v=${extension.manifest.version}` ); var { wzQuicktextTemplate } = ChromeUtils.importESModule( `chrome://quicktext/content/wzQuicktextTemplate.sys.mjs?v=${extension.manifest.version}` ); var { wzQuicktextScript } = ChromeUtils.importESModule( `chrome://quicktext/content/wzQuicktextScript.sys.mjs?v=${extension.manifest.version}` ); const kFileShortcuts = ['ProfD', 'UsrDocs', 'Home', 'Desk', 'Pers']; const OS = Services.appinfo.OS; Services.scriptloader.loadSubScript("resource://quicktext/api/NotifyTools/notifyTools.js", window, "UTF-8"); function isIncompatibleScript(script) { const targets = ["this.mWindow", "this.mVariables", "this.mQuicktext"]; return script && targets.some(target => script.script.includes(target)); } // This exists for historic reasons, but all of it is related to the settings // dialog as well. var gQuicktext = { mGroup: [], mTexts: [], mScripts: [], mEditingGroup: [], mEditingTexts: [], mEditingScripts: [], mViewPopup: false, mCollapseGroup: true, mDefaultImport: "", mKeywordKey: "Tab", mShortcutModifier: "alt", mShortcutTypeAdv: false, mObserverList: [], mCollapseState: "", mSelectionContent: "", mSelectionContentHtml: "", mCurrentTemplate: "", get viewToolbar() { return this.mViewToolbar; }, set viewToolbar(aViewToolbar) { this.mViewToolbar = aViewToolbar; notifyTools.notifyBackground({ command: "setPref", pref: "toolbar", value: aViewToolbar }); this.notifyObservers("updatetoolbar", ""); return this.mViewToolbar; }, get viewPopup() { return this.mViewPopup; }, set viewPopup(aViewPopup) { this.mViewPopup = aViewPopup; notifyTools.notifyBackground({ command: "setPref", pref: "popup", value: aViewPopup }); return this.mViewPopup; }, get collapseGroup() { return this.mCollapseGroup; }, set collapseGroup(aCollapseGroup) { this.mCollapseGroup = aCollapseGroup; notifyTools.notifyBackground({ command: "setPref", pref: "menuCollapse", value: aCollapseGroup }); this.notifyObservers("updatesettings", ""); return this.mCollapseGroup; }, get defaultImport() { return this.mDefaultImport; }, set defaultImport(aDefaultImport) { this.mDefaultImport = aDefaultImport; notifyTools.notifyBackground({ command: "setPref", pref: "defaultImport", value: aDefaultImport }); return this.mDefaultImport; }, get keywordKey() { return this.mKeywordKey; }, set keywordKey(aKeywordKey) { this.mKeywordKey = aKeywordKey; notifyTools.notifyBackground({ command: "setPref", pref: "keywordKey", value: aKeywordKey }); return this.mKeywordKey; }, get shortcutModifier() { return this.mShortcutModifier; }, set shortcutModifier(aShortcutModifier) { this.mShortcutModifier = aShortcutModifier; notifyTools.notifyBackground({ command: "setPref", pref: "shortcutModifier", value: aShortcutModifier }); return this.mShortcutModifier; }, get collapseState() { return this.mCollapseState; }, set collapseState(aCollapseState) { this.mCollapseState = aCollapseState; notifyTools.notifyBackground({ command: "setPref", pref: "collapseState", value: aCollapseState }); return this.mCollapseState; }, get shortcutTypeAdv() { if (OS.substr(0, 3).toLowerCase() == "mac" || (OS.substr(0, 3).toLowerCase() == "win" && this.mShortcutModifier == "alt")) return false; return this.mShortcutTypeAdv; }, set shortcutTypeAdv(aShortcutTypeAdv) { this.mShortcutTypeAdv = aShortcutTypeAdv; notifyTools.notifyBackground({ command: "setPref", pref: "shortcutTypeAdv", value: aShortcutTypeAdv }); return this.mShortcutTypeAdv; }, loadSettings: async function () { const scripts = await notifyTools.notifyBackground({ command: "getScripts" }); const { groups, texts } = await notifyTools.notifyBackground({ command: "getTemplates" }); this.mScripts = (scripts || []).map(e => new wzQuicktextScript(e)); this.mGroup = (groups || []).map(e => new wzQuicktextGroup(e)); this.mTexts = [] // The templates are grouped. for (let groupTexts of (texts || [])) { this.mTexts.push(groupTexts.map(e => new wzQuicktextTemplate(e))) } // Get prefs this.mViewToolbar = await notifyTools.notifyBackground({ command: "getPref", pref: "toolbar" }); this.mCollapseState = await notifyTools.notifyBackground({ command: "getPref", pref: "collapseState" }); this.mCollapseGroup = await notifyTools.notifyBackground({ command: "getPref", pref: "menuCollapse" }); this.mViewPopup = await notifyTools.notifyBackground({ command: "getPref", pref: "popup" }); this.mKeywordKey = await notifyTools.notifyBackground({ command: "getPref", pref: "keywordKey" }); this.mShortcutTypeAdv = await notifyTools.notifyBackground({ command: "getPref", pref: "shortcutTypeAdv" }); this.mShortcutModifier = await notifyTools.notifyBackground({ command: "getPref", pref: "shortcutModifier" }); this.mDefaultImport = await notifyTools.notifyBackground({ command: "getPref", pref: "defaultImport" }); // Managed prefs cannot be changed by the user. for (let { pref, elemId, m } of [ { pref: "menuCollapse", elemId: "checkbox-collapseGroup", m: this.mCollapseGroup }, { pref: "popup", elemId: "checkbox-viewPopup", m: this.mViewPopup }, { pref: "keywordKey", elemId: "select-keywordKey", m: this.mKeywordKey }, { pref: "shortcutTypeAdv", elemId: "checkbox-shortcutTypeAdv", m: this.mShortcutTypeAdv }, { pref: "shortcutModifier", elemId: "select-shortcutModifier", m: this.mShortcutModifier }, { pref: "defaultImport", elemId: "text-defaultImport", m: this.mDefaultImport }, ]) { const { value, isManaged } = await notifyTools.notifyBackground({ command: "getPrefWithManagedInfo", pref }); m = value; if (isManaged) { const element = document.getElementById(elemId); element.setAttribute("disabled", "true"); element.setAttribute('tooltiptext', extension.localeData.localizeMessage("controlled-via-managed-storage")); } } this.startEditing(); // Notify that settings has been changed this.notifyObservers("updatesettings", ""); }, // Rename the legacy quicktext class members. prettify(data) { function prettifier(value) { if (Array.isArray(value)) { let pArray = []; for (let entry of value) { pArray.push(prettifier(entry)) } return pArray; } if (typeof value === 'object' && value !== null) { let pObj = {}; for (let [k, v] of Object.entries(value)) { const pKey = k.startsWith("m") ? k[1].toLowerCase() + k.slice(2) : k; pObj[pKey] = prettifier(v); } return pObj; } return value; } return prettifier(data); }, saveSettings: async function () { // Save prefs. await notifyTools.notifyBackground({ command: "setPref", pref: "toolbar", value: this.mViewToolbar }); await notifyTools.notifyBackground({ command: "setPref", pref: "menuCollapse", value: this.mCollapseGroup }); await notifyTools.notifyBackground({ command: "setPref", pref: "keywordKey", value: this.mKeywordKey }); await notifyTools.notifyBackground({ command: "setPref", pref: "popup", value: this.mViewPopup }); await notifyTools.notifyBackground({ command: "setPref", pref: "shortcutTypeAdv", value: this.mShortcutTypeAdv }); await notifyTools.notifyBackground({ command: "setPref", pref: "shortcutModifier", value: this.mShortcutModifier }); await notifyTools.notifyBackground({ command: "setPref", pref: "collapseState", value: this.mCollapseState }); await notifyTools.notifyBackground({ command: "setPref", pref: "defaultImport", value: this.mDefaultImport }); // Save templates and scripts. this.endEditing(); const templates = { texts: this.prettify(this.mTexts), groups: this.prettify(this.mGroup) } const scripts = this.prettify(this.mScripts); await notifyTools.notifyBackground({ command: "setScripts", data: scripts }); await notifyTools.notifyBackground({ command: "setTemplates", data: templates }); await notifyTools.notifyBackground({ command: "checkBadEntries", data: { scripts, templates } }); this.startEditing(); this.notifyObservers("updatesettings", ""); }, addGroup: function (aName, aEditingMode) { var tmp = new wzQuicktextGroup(); tmp.name = aName; tmp.protected = false; if (aEditingMode) { this.mEditingGroup.push(tmp); this.mEditingTexts.push([]); } else { this.mGroup.push(tmp); this.mTexts.push([]); } }, removeGroup: function (aRow, aEditingMode) { if (aEditingMode) { this.mEditingGroup.splice(aRow, 1); this.mEditingTexts.splice(aRow, 1); } else { this.mGroup.splice(aRow, 1); this.mTexts.splice(aRow, 1); } }, getGroup: function (aGroupIndex, aEditingMode) { if (aEditingMode) { if (typeof this.mEditingGroup[aGroupIndex] != 'undefined') return this.mEditingGroup[aGroupIndex]; } else { if (typeof this.mGroup[aGroupIndex] != 'undefined') return this.mGroup[aGroupIndex]; } }, getGroupLength: function (aEditingMode) { if (aEditingMode) return this.mEditingGroup.length; else return this.mGroup.length; }, moveGroup: function (aFromIndex, aToIndex, aEditingMode) { if (aEditingMode) { var tmpGroup = this.mEditingGroup.splice(aFromIndex, 1)[0]; var tmpTexts = this.mEditingTexts.splice(aFromIndex, 1)[0]; if (aToIndex > aFromIndex) { this.mEditingGroup.splice(aToIndex - 1, 0, tmpGroup); this.mEditingTexts.splice(aToIndex - 1, 0, tmpTexts); } else { this.mEditingGroup.splice(aToIndex, 0, tmpGroup); this.mEditingTexts.splice(aToIndex, 0, tmpTexts); } } else { var tmpGroup = this.mGroup.splice(aFromIndex, 1)[0]; var tmpTexts = this.mTexts.splice(aFromIndex, 1)[0]; if (aToIndex > aFromIndex) { this.mGroup.splice(aToIndex - 1, 0, tmpGroup); this.mTexts.splice(aToIndex - 1, 0, tmpTexts); } else { this.mGroup.splice(aToIndex, 0, tmpGroup); this.mTexts.splice(aToIndex, 0, tmpTexts); } } }, addText: function (aGroupIndex, aName, aEditingMode) { var tmp = new wzQuicktextTemplate(); tmp.name = aName; tmp.shortcut = ""; if (aEditingMode) this.mEditingTexts[aGroupIndex].push(tmp); else this.mTexts[aGroupIndex].push(tmp); }, removeText: function (aGroupIndex, aRow, aEditingMode) { if (aEditingMode) this.mEditingTexts[aGroupIndex].splice(aRow, 1); else this.mTexts[aGroupIndex].splice(aRow, 1); }, getText: function (aGroupIndex, aTextIndex, aEditingMode) { if (aEditingMode) { if (typeof this.mEditingTexts[aGroupIndex][aTextIndex] != 'undefined') return this.mEditingTexts[aGroupIndex][aTextIndex]; } else { if (typeof this.mTexts[aGroupIndex][aTextIndex] != 'undefined') return this.mTexts[aGroupIndex][aTextIndex]; } }, getTextLength: function (aGroupIndex, aEditingMode) { if (aEditingMode) { if (this.mEditingTexts[aGroupIndex]) return this.mEditingTexts[aGroupIndex].length; } else { if (this.mTexts[aGroupIndex]) return this.mTexts[aGroupIndex].length; } return 0; }, doTextExists: function (aGroupIndex, aTextIndex, aEditingMode) { if (aEditingMode) return (typeof this.mEditingTexts[aGroupIndex][aTextIndex] != 'undefined') ? true : false; else return (typeof this.mTexts[aGroupIndex][aTextIndex] != 'undefined') ? true : false; }, moveText: function (aFromGroupIndex, aFromTextIndex, aToGroupIndex, aToTextIndex, aEditingMode) { if (aEditingMode) { var tmpText = this.mEditingTexts[aFromGroupIndex].splice(aFromTextIndex, 1)[0]; if (aFromGroupIndex == aToGroupIndex && aFromTextIndex < aToTextIndex) this.mEditingTexts[aToGroupIndex].splice(aToTextIndex - 1, 0, tmpText); else this.mEditingTexts[aToGroupIndex].splice(aToTextIndex, 0, tmpText); } else { var tmpText = this.mTexts[aFromGroupIndex].splice(aFromTextIndex, 1)[0]; if (aFromGroupIndex == aToGroupIndex && aFromTextIndex < aToTextIndex) this.mTexts[aFromGroupIndex].splice(aToTextIndex - 1, 0, tmpText); else this.mTexts[aFromGroupIndex].splice(aToTextIndex, 0, tmpText); } }, addScript: function (aName, aEditingMode) { var tmp = new wzQuicktextScript(); tmp.name = aName; tmp.protected = false; if (aEditingMode) this.mEditingScripts.push(tmp); else this.mScripts.push(tmp); }, removeScript: function (aIndex, aEditingMode) { if (aEditingMode) this.mEditingScripts.splice(aIndex, 1); else this.mScripts.splice(aIndex, 1); }, getScript: function (aIndex, aEditingMode) { if (aEditingMode) { if (typeof this.mEditingScripts[aIndex] != 'undefined') return this.mEditingScripts[aIndex]; } else { if (typeof this.mScripts[aIndex] != 'undefined') return this.mScripts[aIndex]; } }, getScriptLength: function (aEditingMode) { if (aEditingMode) return this.mEditingScripts.length; else return this.mScripts.length; }, // Create temporary vars that hold the edited-but-not-yet-saved scripts and // templates. startEditing: function () { this.mEditingGroup = []; this.mEditingTexts = []; for (var i = 0; i < this.mGroup.length; i++) { this.mEditingGroup[i] = this.mGroup[i].clone(); this.mEditingTexts[i] = []; if (this.mTexts[i]) for (var j = 0; j < this.mTexts[i].length; j++) this.mEditingTexts[i][j] = this.mTexts[i][j].clone(); } this.mEditingScripts = []; for (var i = 0; i < this.mScripts.length; i++) { this.mEditingScripts[i] = this.mScripts[i].clone(); } }, // When the editing ended, move the values back to the original vars. endEditing: function () { this.mGroup = []; this.mTexts = []; for (var i = 0; i < this.mEditingGroup.length; i++) { this.mGroup[i] = this.mEditingGroup[i].clone(); this.mTexts[i] = []; if (this.mEditingTexts[i]) for (var j = 0; j < this.mEditingTexts[i].length; j++) this.mTexts[i][j] = this.mEditingTexts[i][j].clone(); } this.mScripts = []; for (var i = 0; i < this.mEditingScripts.length; i++) { this.mScripts[i] = this.mEditingScripts[i].clone(); } }, /* * FILE FUNCTIONS */ async pickFile(aTypes, aMode, aTitle) { let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(Components.interfaces.nsIFilePicker); switch (aMode) { case 1: // save filePicker.init(window.browsingContext, aTitle, filePicker.modeSave); break; default: // open filePicker.init(window.browsingContext, aTitle, filePicker.modeOpen); break; } for (let aType of aTypes) { switch (aType) { case 0: // insert TXT file filePicker.appendFilters(filePicker.filterText); filePicker.defaultExtension = "txt"; break; case 1: // insert HTML file filePicker.appendFilters(filePicker.filterHTML); filePicker.defaultExtension = "html"; break; case 2: // insert file break; case 3: // Quicktext XML file filePicker.appendFilter("Quicktext XML Export", "*.xml"); filePicker.defaultExtension = "xml"; break; case 4: // images filePicker.appendFilters(filePicker.filterImages); case 5: // JSON filePicker.appendFilter("Quicktext JSON Export", "*.json"); filePicker.defaultExtension = "json"; default: // attachments break; } } filePicker.appendFilters(filePicker.filterAll); let rv = await new Promise(function (resolve, reject) { filePicker.open(result => { resolve(result); }); }); if (rv == filePicker.returnOK || rv == filePicker.returnReplace) { return filePicker.file; } else { return null; } }, importTemplates(templates) { let importedGroup = (templates?.groups || []).map(e => new wzQuicktextGroup(e)); let importedTexts = []; // The templates are grouped. for (let texts of templates?.texts || []) { importedTexts.push(texts.map(e => new wzQuicktextTemplate(e))) } // Imports are not not saved directly, but imported as unsaved changes. for (var i = 0; i < importedGroup.length; i++) this.mEditingGroup.push(importedGroup[i]); for (var i = 0; i < importedTexts.length; i++) this.mEditingTexts.push(importedTexts[i]); }, importScripts(scripts) { let importedScripts = scripts.map(e => new wzQuicktextScript(e)); // Imports are not not saved directly, but imported as unsaved changes. for (var i = 0; i < importedScripts.length; i++) this.mEditingScripts.push(importedScripts[i]); }, /* * OBSERVERS */ addObserver: function (aObserver) { this.mObserverList.push(aObserver); }, removeObserver: function (aObserver) { for (var i = 0; i < this.mObserverList.length; i++) { if (this.mObserverList[i] == aObserver) this.mObserverList.splice(i, 1); } }, notifyObservers: function (aTopic, aData) { for (var i = 0; i < this.mObserverList.length; i++) this.mObserverList[i].observe(this, aTopic, aData); } } var settingsDialog = { mChangesMade: false, mTextChangesMade: [], mScriptChangesMade: [], mGeneralChangesMade: [], mTreeArray: [], mCollapseState: [], mScriptIndex: null, mPickedIndex: null, init: async function () { await window.i18n.updateDocument({ extension }); document.getElementById('quicktextSettingsWindow').setAttribute("OS", Services.appinfo.OS); console.log("Adding attribute 'OS' = '" + Services.appinfo.OS + "' to settings dialog element."); gQuicktext.addObserver(this); await gQuicktext.loadSettings() var states = gQuicktext.collapseState; if (states != "") { states = states.split(/;/); for (var i = 0; i < states.length; i++) this.mCollapseState[i] = (states[i] == "1"); } var groupLength = gQuicktext.getGroupLength(true); if (states.length < groupLength) { for (var i = states.length; i < groupLength; i++) this.mCollapseState[i] = true; } document.getElementById('tabbox-main').selectedIndex = 1; document.getElementById('text-keyword').addEventListener("keypress", function (e) { settingsDialog.noSpaceForKeyword(e); }, false); this.disableSave(); document.getElementById("savebutton").addEventListener("command", function (e) { settingsDialog.save(); }, false); document.getElementById("closebutton").addEventListener("command", function (e) { settingsDialog.close(true); }, false); document.getElementById("helpbutton").addEventListener("command", function (e) { settingsDialog.openHomepage(); }, false); document.getElementById("scripthelpbutton").addEventListener("command", function (e) { settingsDialog.openScriptHelp(); }, false); // Update boxHeightOffset let scriptListElem = document.getElementById('script-list'); let elementHeight = scriptListElem.getBoundingClientRect().height; boxHeightOffset = window.innerHeight - elementHeight; }, unload: function () { gQuicktext.removeObserver(this); var states = []; for (var i = 0; i < this.mCollapseState.length; i++) states[i] = (this.mCollapseState[i]) ? "1" : ""; gQuicktext.collapseState = states.join(";"); document.getElementById('text-keyword').removeEventListener("keypress", function (e) { settingsDialog.noSpaceForKeyword(e); }, false); }, close: async function (aClose) { this.saveText(); this.saveScript(); if (this.mChangesMade) { promptService = Services.prompt; if (promptService) { result = promptService.confirmEx(window, extension.localeData.localizeMessage("saveMessageTitle"), extension.localeData.localizeMessage("saveMessage"), (promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0) + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1) + (promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_2), null, null, null, null, { value: 0 }); switch (result) { // Cancel case 1: return false; // Save case 0: await this.save(); break; // Quit case 2: break; } } } if (aClose) window.close(); return true; }, save: async function () { this.saveText(); this.saveScript(); if (document.getElementById("checkbox-viewPopup")) gQuicktext.viewPopup = document.getElementById("checkbox-viewPopup").checked; if (document.getElementById("text-defaultImport")) gQuicktext.defaultImport = document.getElementById("text-defaultImport").value; if (document.getElementById("select-shortcutModifier")) gQuicktext.shortcutModifier = document.getElementById("select-shortcutModifier").value; if (document.getElementById("checkbox-shortcutTypeAdv")) gQuicktext.shortcutTypeAdv = document.getElementById("checkbox-shortcutTypeAdv").checked; if (document.getElementById("select-keywordKey")) gQuicktext.keywordKey = document.getElementById("select-keywordKey").value; if (document.getElementById("checkbox-collapseGroup")) gQuicktext.collapseGroup = document.getElementById("checkbox-collapseGroup").checked; await gQuicktext.saveSettings(); this.mChangesMade = false; this.mTextChangesMade = []; this.mScriptChangesMade = []; this.mGeneralChangesMade = []; this.disableSave(); await this.updateGUI(); }, saveText: function () { if (this.mPickedIndex != null) { if (this.mPickedIndex[1] > -1) { // The title is updated in the onchange event. //var title = document.getElementById('text-title').value; //if (title.replace(/[\s]/g, '') == "") // title = extension.localeData.localizeMessage("newTemplate"); //this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'name', title); this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'text', document.getElementById('text').value); if (gQuicktext.shortcutTypeAdv) this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'shortcut', document.getElementById('text-shortcutAdv').value); else this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'shortcut', document.getElementById('text-shortcutBasic').value); this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'type', document.getElementById('text-type').value); this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'keyword', document.getElementById('text-keyword').value.replace(/[\s]/g, '')); this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'subject', document.getElementById('text-subject').value); this.saveTextCell(this.mPickedIndex[0], this.mPickedIndex[1], 'attachments', document.getElementById('text-attachments').value); } else { // The title is updated in the onchange event. //var title = document.getElementById('text-title').value; //if (title.replace(/[\s]/g, '') == "") // title = extension.localeData.localizeMessage("newGroup"); //this.saveGroupCell(this.mPickedIndex[0], 'name', title); } } }, saveTextCell: function (aGroupIndex, aTextIndex, aColumn, aValue) { var text = gQuicktext.getText(aGroupIndex, aTextIndex, true); if (typeof text[aColumn] != "undefined" && text[aColumn] != aValue) { text[aColumn] = aValue; this.changesMade(); return true; } return false; }, saveGroupCell: function (aGroupIndex, aColumn, aValue) { var group = gQuicktext.getGroup(aGroupIndex, true); if (typeof group[aColumn] != "undefined" && group[aColumn] != aValue) { group[aColumn] = aValue; this.changesMade(); return true; } return false; }, saveScript: function () { if (this.mScriptIndex != null) { // The title is updated in the onchange event. //var title = document.getElementById('script-title').value; //if (title.replace(/[\s]/g, '') == "") // title = extension.localeData.localizeMessage("newScript"); //this.saveScriptCell(this.mScriptIndex, 'name', title); this.saveScriptCell(this.mScriptIndex, 'script', document.getElementById('script').value); } }, saveScriptCell: function (aIndex, aColumn, aValue) { var script = gQuicktext.getScript(aIndex, true); if (typeof script[aColumn] != "undefined" && script[aColumn] != aValue) { script[aColumn] = aValue; this.changesMade(); return true; } return false; }, noSpaceForKeyword: function (e) { if (e.charCode == KeyEvent.DOM_VK_SPACE) { e.stopPropagation(); e.preventDefault(); } }, checkForGeneralChanges: function (aIndex) { var ids = ['checkbox-viewPopup', 'checkbox-collapseGroup', 'select-shortcutModifier', 'checkbox-shortcutTypeAdv', 'select-keywordKey', 'text-defaultImport']; var type = ['checked', 'checked', 'value', 'checked', 'value', 'value']; var keys = ['viewPopup', 'collapseGroup', 'shortcutModifier', 'shortcutTypeAdv', 'keywordKey', 'defaultImport']; if (typeof ids[aIndex] == 'undefined') return; var value = document.getElementById(ids[aIndex])[type[aIndex]]; if (gQuicktext[keys[aIndex]] != value) this.generalChangeMade(aIndex); else this.noGeneralChangeMade(aIndex); }, checkForTextChanges: function (aIndex) { if (!this.mPickedIndex) return; var ids = ['text-title', 'text', 'text-shortcutBasic', 'text-type', 'text-keyword', 'text-subject', 'text-attachments']; var keys = ['name', 'text', 'shortcut', 'type', 'keyword', 'subject', 'attachments']; if (gQuicktext.shortcutTypeAdv) ids[2] = 'text-shortcutAdv'; let element = document.getElementById(ids[aIndex]) var value = element.value; switch (aIndex) { case 0: if (this.mPickedIndex[1] > -1) { if (value.replace(/[\s]/g, '') == "") { value = extension.localeData.localizeMessage("newTemplate"); } // Prevent duplicated names. value = this.makeUnique(value, gQuicktext.mEditingTexts[this.mPickedIndex[0]].map(t => t.mName)); } else { if (value.replace(/[\s]/g, '') == "") { value = extension.localeData.localizeMessage("newGroup"); } // Prevent duplicated names. value = this.makeUnique(value, gQuicktext.mEditingGroup.map(g => g.mName)) } break; case 2: if (gQuicktext.shortcutTypeAdv) { value = value.replace(/[^\d]/g, ''); element.value = value; } case 4: value = value.replace(/[\s]/g, ''); element.value = value; break; case 6: document.getElementById("deprecated_attachment").style.display = document.getElementById('text-attachments').value ? "" : "none" break; } if (this.mPickedIndex[1] > -1) { if (gQuicktext.getText(this.mPickedIndex[0], this.mPickedIndex[1], true)[keys[aIndex]] != value) { this.textChangeMade(aIndex); } else { this.noTextChangeMade(aIndex); } } else { if (gQuicktext.getGroup(this.mPickedIndex[0], true)[keys[aIndex]] != value) { this.textChangeMade(aIndex); } else { this.noTextChangeMade(aIndex); } } // Auto-save names. if (aIndex == 0) { if (this.mPickedIndex[1] > -1) { gQuicktext.mEditingTexts[this.mPickedIndex[0]][this.mPickedIndex[1]].mName = value; } else { gQuicktext.mEditingGroup[this.mPickedIndex[0]].mName = value; } } if (aIndex == 0 || aIndex == 2) { var selectedIndex = document.getElementById('group-tree').view.selection.currentIndex; if (aIndex == 0) { this.mTreeArray[selectedIndex][6] = value; } else { this.mTreeArray[selectedIndex][7] = value; } document.getElementById('group-tree').invalidateRow(selectedIndex); this.updateVariableGUI(); } }, checkForScriptChanges: function (aIndex) { if (this.mScriptIndex == null) return; var ids = ['script-title', 'script']; var keys = ['name', 'script']; var value = document.getElementById(ids[aIndex]).value; switch (aIndex) { case 0: if (value.replace(/[\s]/g, '') == "") { value = extension.localeData.localizeMessage("newScript"); } // Prevent duplicated names. value = this.makeUnique(value, gQuicktext.mEditingScripts.map(t => t.mName)); break; } if (gQuicktext.getScript(this.mScriptIndex, true)[keys[aIndex]] != value) this.scriptChangeMade(aIndex); else this.noScriptChangeMade(aIndex); if (aIndex == 0) { // Auto-save names. gQuicktext.mEditingScripts[this.mScriptIndex].mName = value; this.updateVariableGUI(); var listItem = document.getElementById('script-list').getItemAtIndex(this.mScriptIndex); listItem.firstChild.value = value; } }, changesMade: function () { this.mChangesMade = true; this.enableSave(); }, anyChangesMade: function () { if (this.textChangesMade() || this.scriptChangesMade() || this.generalChangesMade()) return true; return false; }, generalChangesMade: function () { for (var i = 0; i < this.mGeneralChangesMade.length; i++) { if (typeof this.mGeneralChangesMade[i] != "undefined" && this.mGeneralChangesMade[i] == true) return true; } return false; }, generalChangeMade: function (aIndex) { this.enableSave(); this.mGeneralChangesMade[aIndex] = true; }, noGeneralChangeMade: function (aIndex) { this.mGeneralChangesMade[aIndex] = false; if (!this.mChangesMade && !this.anyChangesMade()) this.disableSave(); }, textChangesMade: function () { for (var i = 0; i < this.mTextChangesMade.length; i++) { if (typeof this.mTextChangesMade[i] != "undefined" && this.mTextChangesMade[i] == true) return true; } return false; }, textChangeMade: function (aIndex) { this.enableSave(); this.mTextChangesMade[aIndex] = true; }, noTextChangeMade: function (aIndex) { this.mTextChangesMade[aIndex] = false; if (!this.mChangesMade && !this.anyChangesMade()) this.disableSave(); }, scriptChangesMade: function () { for (var i = 0; i < this.mScriptChangesMade.length; i++) { if (typeof this.mScriptChangesMade[i] != "undefined" && this.mScriptChangesMade[i] == true) return true; } return false; }, scriptChangeMade: function (aIndex) { this.enableSave(); this.mScriptChangesMade[aIndex] = true; }, noScriptChangeMade: function (aIndex) { this.mScriptChangesMade[aIndex] = false; if (!this.mChangesMade && !this.anyChangesMade()) this.disableSave(); }, shortcutModifierChange: function () { var state = (OS.substr(0, 3).toLowerCase() == "mac" || (OS.substr(0, 3).toLowerCase() == "win" && document.getElementById('select-shortcutModifier').value == "alt")); document.getElementById('checkbox-shortcutTypeAdv').disabled = state; }, /* * GUI CHANGES */ updateGUI: async function () { const dateTimeFormat = async (format, timeStamp) => { return notifyTools.notifyBackground({ command: "getDateTimeFormat", data: { format, timeStamp } }); } // Set the date/time in the variablemenu var timeStamp = new Date(); let fields = ["date-short", "date-long", "date-monthname", "time-noseconds", "time-seconds"]; for (let i = 0; i < fields.length; i++) { let field = fields[i]; let fieldType = field.split("-")[0]; if (document.getElementById(field)) { document.getElementById(field).setAttribute( "label", extension.localeData.localizeMessage(fieldType, [await dateTimeFormat(field, timeStamp)]) ); } } // Update info in the generalsettings tab if (document.getElementById("checkbox-viewPopup")) document.getElementById("checkbox-viewPopup").checked = gQuicktext.viewPopup; if (document.getElementById("checkbox-collapseGroup")) document.getElementById("checkbox-collapseGroup").checked = gQuicktext.collapseGroup; if (document.getElementById("select-shortcutModifier")) document.getElementById("select-shortcutModifier").value = gQuicktext.shortcutModifier; if (document.getElementById("checkbox-shortcutTypeAdv")) { var elem = document.getElementById("checkbox-shortcutTypeAdv"); elem.checked = gQuicktext.shortcutTypeAdv; this.shortcutModifierChange(); } if (document.getElementById("text-defaultImport")) document.getElementById("text-defaultImport").value = gQuicktext.defaultImport; if (document.getElementById("select-keywordKey")) document.getElementById("select-keywordKey").value = gQuicktext.keywordKey; // Update the variable menu this.updateVariableGUI(); // Update Script list this.updateScriptGUI(); // Update the tree this.buildTreeGUI(); // Update the remove and add buttons this.updateButtonStates(); }, updateVariableGUI: function () { // Set all other text in the variablemenu var topParent = document.getElementById('quicktext-other-texts'); for (var i = topParent.childNodes.length - 1; i >= 0; i--) topParent.removeChild(topParent.childNodes[i]); var groupLength = gQuicktext.getGroupLength(true); if (groupLength > 0) { topParent.removeAttribute('hidden'); parent = document.createXULElement("menupopup"); parent = topParent.appendChild(parent); for (var i = 0; i < groupLength; i++) { var textLength = gQuicktext.getTextLength(i, true); if (textLength > 0) { var group = gQuicktext.getGroup(i, true); var groupElem = document.createXULElement("menu"); groupElem.setAttribute('label', group.name); groupElem = parent.appendChild(groupElem); groupParent = document.createXULElement("menupopup"); groupParent = groupElem.appendChild(groupParent); for (var j = 0; j < textLength; j++) { var textElem = document.createXULElement("menuitem"); var text = gQuicktext.getText(i, j, true); textElem.setAttribute('label', text.name); textElem.setAttribute('group', group.name); textElem.addEventListener("command", function () { settingsDialog.insertVariable("TEXT=" + this.getAttribute("group") + "|" + this.getAttribute("label")); }); textElem = groupParent.appendChild(textElem); } } } } else topParent.setAttribute('hidden', true); var topParent = document.getElementById('variables-scripts'); for (var i = topParent.childNodes.length - 1; i >= 0; i--) topParent.removeChild(topParent.childNodes[i]); var scriptLength = gQuicktext.getScriptLength(true); if (scriptLength > 0) { topParent.removeAttribute('hidden'); parent = document.createXULElement("menupopup"); parent = topParent.appendChild(parent); for (var i = 0; i < scriptLength; i++) { var script = gQuicktext.getScript(i, true); var textElem = document.createXULElement("menuitem"); textElem.setAttribute('label', script.name); textElem.addEventListener("command", function () { settingsDialog.insertVariable("SCRIPT=" + this.getAttribute("label")); }); textElem = parent.appendChild(textElem); } } else topParent.setAttribute('hidden', true); }, updateScriptGUI: function () { // Update the listmenu in the scripttab and the variable-menu var scriptLength = gQuicktext.getScriptLength(true); listElem = document.getElementById('script-list'); var selectedIndex = listElem.selectedIndex; var oldLength = listElem.getRowCount(); if (scriptLength > 0) { for (var i = 0; i < scriptLength; i++) { var script = gQuicktext.getScript(i, true); if (i < oldLength) { var listItem = listElem.getItemAtIndex(i); listItem.firstChild.value = script.name; listItem.value = i; let isIncompatible = isIncompatibleScript(script); if (listItem.children.length > 1 && !isIncompatible) { listItem.children[1].remove(); } else if (listItem.children.length == 1 && isIncompatible) { let newItemWarning = document.createXULElement("label"); newItemWarning.value = "⚠️"; listItem.appendChild(newItemWarning); }; } else { // Keep the height of the script list fixed, prevent it growing. let elementHeight = listElem.getBoundingClientRect().height; let newItem = document.createXULElement("richlistitem"); newItem.value = i; let newItemLabel = document.createXULElement("label"); newItemLabel.value = script.name; newItem.appendChild(newItemLabel); if (isIncompatibleScript(script)) { let newItemWarning = document.createXULElement("label"); newItemWarning.value = "⚠️"; newItem.appendChild(newItemWarning); } listElem.appendChild(newItem); listElem.style.height = `${elementHeight}px`; } } } if (oldLength > scriptLength) { for (var i = scriptLength; i < oldLength; i++) listElem.getItemAtIndex(scriptLength).remove(); } if (selectedIndex >= 0) listElem.selectedIndex = selectedIndex; else if (scriptLength > 0) listElem.selectedIndex = 0; else listElem.selectedIndex = -1; this.pickScript(); }, disableShortcuts: function (aShortcut) { var grouplist = document.getElementById('popup-shortcutBasic'); for (var i = 0; i <= 10; i++) grouplist.childNodes[i].removeAttribute("disabled"); var groupLength = gQuicktext.getGroupLength(true); for (var i = 0; i < groupLength; i++) { var textLength = gQuicktext.getTextLength(i, true); for (var j = 0; j < textLength; j++) { var shortcut = gQuicktext.getText(i, j, true).shortcut; var selectedIndex = (shortcut == "0") ? 10 : shortcut; if (shortcut != "" && shortcut != aShortcut && grouplist.childNodes[selectedIndex]) grouplist.childNodes[selectedIndex].setAttribute("disabled", true); } } }, disableSave: function () { document.getElementById("savebutton").setAttribute("disabled", true); }, enableSave: function () { document.getElementById("savebutton").removeAttribute("disabled"); }, /* * Update the treeview */ makeTreeArray: function () { this.mTreeArray = []; var k = 0; var groupLength = gQuicktext.getGroupLength(true); if (this.mCollapseState.length < groupLength) { for (var i = this.mCollapseState.length; i < groupLength; i++) this.mCollapseState[i] = true; } else if (this.mCollapseState.length > groupLength) this.mCollapseState.splice(groupLength, this.mCollapseState.length - groupLength); for (var i = 0; i < groupLength; i++) { var groupIndex = k; var textLength = gQuicktext.getTextLength(i, true); this.mTreeArray[k] = [i, -1, 0, -1, true, textLength, gQuicktext.getGroup(i, true).name, '']; k++; if (!this.mCollapseState[i]) continue; for (var j = 0; j < textLength; j++) { var text = gQuicktext.getText(i, j, true); var shortcut = text.shortcut; this.mTreeArray[k] = [i, j, 1, groupIndex, false, 0, text.name, shortcut]; k++; } } }, updateTreeGUI: function () { // maybe }, buildTreeGUI: function () { this.makeTreeArray(); var treeview = { rowCount: this.mTreeArray.length, lastIndex: null, isContainer: function (aRow) { return (settingsDialog.mTreeArray[aRow][1] == -1); }, isContainerOpen: function (aRow) { return settingsDialog.mCollapseState[settingsDialog.mTreeArray[aRow][0]]; }, isContainerEmpty: function (aRow) { return (settingsDialog.mTreeArray[aRow][5] == 0); }, isSeparator: function (aRow) { return false; }, isSorted: function (aRow) { return false; }, isEditable: function (aRow) { return false; }, hasNextSibling: function (aRow, aAfter) { return (settingsDialog.mTreeArray[aAfter + 1] && settingsDialog.mTreeArray[aRow][2] == settingsDialog.mTreeArray[aAfter + 1][2] && settingsDialog.mTreeArray[aRow][3] == settingsDialog.mTreeArray[aAfter + 1][3]); }, getLevel: function (aRow) { return settingsDialog.mTreeArray[aRow][2]; }, getImageSrc: function (aRow, aCol) { return null; }, getParentIndex: function (aRow) { return settingsDialog.mTreeArray[aRow][3]; }, getRowProperties: function (aRow, aProps) { }, getCellProperties: function (aRow, aCol, aProps) { }, getColumnProperties: function (aColid, aCol, aProps) { }, getProgressMode: function (aRow, aCol) { }, getCellValue: function (aRow, aCol) { return null; }, canDropBeforeAfter: function (aRow, aBefore) { if (aBefore) return this.canDrop(aRow, -1); return this.canDrop(aRow, 1); }, canDropOn: function (aRow) { return this.canDrop(aRow, 0); }, canDrop: function (aRow, aOrient) { var index = document.getElementById('group-tree').view.selection.currentIndex; if (index == aRow) return false; // Can only drop templates on groups if (aOrient == 0) { if (settingsDialog.mTreeArray[index][2] > 0 && settingsDialog.mTreeArray[aRow][2] == 0) return true; else return false; } // Take care if we drag a group if (settingsDialog.mTreeArray[index][2] == 0) { if (aOrient < 0 && settingsDialog.mTreeArray[aRow][2] == 0) return true; if (aOrient > 0 && settingsDialog.mTreeArray.length - 1 == aRow) return true; } // Take care if we drag a template else { if (settingsDialog.mTreeArray[aRow][2] > 0) return true; } return false; }, drop: function (aRow, aOrient) { settingsDialog.saveText(); settingsDialog.mPickedIndex = null; var selectIndex = -1; var index = document.getElementById('group-tree').view.selection.currentIndex; // Droping a group if (settingsDialog.mTreeArray[index][2] == 0) { var textLength = gQuicktext.getTextLength(settingsDialog.mTreeArray[index][0], true); if (!settingsDialog.mCollapseState[settingsDialog.mTreeArray[index][0]]) textLength = 0; if (aOrient > 0) { gQuicktext.moveGroup(settingsDialog.mTreeArray[index][0], gQuicktext.getGroupLength(true), true); var state = settingsDialog.mCollapseState.splice(settingsDialog.mTreeArray[index][0], 1); state = (state == "false") ? false : true; settingsDialog.mCollapseState.push(state); selectIndex = settingsDialog.mTreeArray.length - textLength - 1; } else { gQuicktext.moveGroup(settingsDialog.mTreeArray[index][0], settingsDialog.mTreeArray[aRow][0], true); var state = settingsDialog.mCollapseState.splice(settingsDialog.mTreeArray[index][0], 1); state = (state == "false") ? false : true; settingsDialog.mCollapseState.splice(settingsDialog.mTreeArray[aRow][0], 0, state); selectIndex = (aRow > index) ? aRow - textLength - 1 : aRow; } } // Droping a template else { switch (aOrient) { case 0: var textLength = gQuicktext.getTextLength(settingsDialog.mTreeArray[aRow][0], true); gQuicktext.moveText(settingsDialog.mTreeArray[index][0], settingsDialog.mTreeArray[index][1], settingsDialog.mTreeArray[aRow][0], textLength, true); selectIndex = (settingsDialog.mTreeArray[index][0] == settingsDialog.mTreeArray[aRow][0] || aRow > index) ? aRow + textLength : aRow + textLength + 1; break; case 1: gQuicktext.moveText(settingsDialog.mTreeArray[index][0], settingsDialog.mTreeArray[index][1], settingsDialog.mTreeArray[aRow][0], settingsDialog.mTreeArray[aRow][1] + 1, true); selectIndex = (aRow > index) ? aRow : aRow + 1; break; default: gQuicktext.moveText(settingsDialog.mTreeArray[index][0], settingsDialog.mTreeArray[index][1], settingsDialog.mTreeArray[aRow][0], settingsDialog.mTreeArray[aRow][1], true); selectIndex = (aRow > index) ? aRow - 1 : aRow; break; } } settingsDialog.makeTreeArray(); document.getElementById('group-tree').invalidate(); document.getElementById('group-tree').view.selection.select(selectIndex); settingsDialog.changesMade(); }, getCellText: function (aRow, aCol) { colName = (aCol.id) ? aCol.id : aCol; if (colName == "group") { return settingsDialog.mTreeArray[aRow][6]; } else if (colName == "shortcut" && settingsDialog.mTreeArray[aRow][1] > -1) { return settingsDialog.mTreeArray[aRow][7]; } return ""; }, toggleOpenState: function (aRow) { var state = settingsDialog.mCollapseState[settingsDialog.mTreeArray[aRow][0]]; settingsDialog.mCollapseState[settingsDialog.mTreeArray[aRow][0]] = !state; settingsDialog.makeTreeArray(); var treeObject = document.getElementById('group-tree'); if (state) treeObject.rowCountChanged(aRow, -settingsDialog.mTreeArray[aRow][5]); else treeObject.rowCountChanged(aRow, settingsDialog.mTreeArray[aRow][5]); treeObject.invalidate(); document.getElementById('group-tree').view.selection.select(aRow); }, setTree: function (aTreebox) { this.treebox = aTreebox; } } var firstVisibleRow = document.getElementById('group-tree').getFirstVisibleRow(); var selectedIndex = document.getElementById('group-tree').view.selection.currentIndex; if (selectedIndex == -1 && this.mTreeArray.length) selectedIndex = 0; document.getElementById('group-tree').view = treeview; document.getElementById('group-tree').scrollToRow(firstVisibleRow); this.selectTreeRow(selectedIndex); this.pickText(); }, selectTreeRow: function (aRow) { document.getElementById('group-tree').view.selection.select(aRow); document.getElementById('group-tree').ensureRowIsVisible(aRow); }, updateButtonStates: function () { // Update the add-buttons if (this.mTreeArray.length) { var index = document.getElementById('group-tree').view.selection.currentIndex; if (this.mTreeArray[index] && gQuicktext.getGroup(this.mTreeArray[index][0], true).protected) { document.getElementById("group-button-remove").setAttribute("disabled", true); document.getElementById("group-button-add-text").setAttribute("disabled", true); } else { document.getElementById("group-button-remove").removeAttribute("disabled"); document.getElementById("group-button-add-text").removeAttribute("disabled"); } } else { document.getElementById('group-button-add-text').setAttribute("disabled", true); document.getElementById('group-button-remove').setAttribute("disabled", true); } let scriptIndex = document.getElementById('script-list').value; let script = gQuicktext.getScript(scriptIndex, true); if (gQuicktext.getScriptLength(true) && !script.protected) document.getElementById('script-button-remove').removeAttribute("disabled"); else document.getElementById('script-button-remove').setAttribute("disabled", true); }, /* * INSERT VARIABLES */ insertVariable: function (aStr) { var textbox = document.getElementById("text-subject"); if (!textbox.getAttribute("focused")) var textbox = document.getElementById("text"); var selStart = textbox.selectionStart; var selEnd = textbox.selectionEnd; var selLength = textbox.textLength; var s1 = (textbox.value).substring(0, selStart); var s2 = (textbox.value).substring(selEnd, selLength) textbox.value = s1 + "[[" + aStr + "]]" + s2; var selNewStart = selStart + 4 + aStr.length; textbox.setSelectionRange(selNewStart, selNewStart); this.enableSave(); }, // TODO: Hardcoding files is no longer possible in pure WebExt, either Exp only or gallery. insertAttachmentVariable: async function () { if ((file = await gQuicktext.pickFile([2], 0, extension.localeData.localizeMessage("attachmentFile"))) != null) { this.insertVariable('ATTACHMENT=FILE|' + file.path); } this.enableSave(); }, // TODO: Hardcoding files is no longer possible in pure WebExt, either Exp only or gallery. insertFileVariable: async function () { if ((file = await gQuicktext.pickFile([2], 0, extension.localeData.localizeMessage("insertFile"))) != null) { this.insertVariable('FILE=' + file.path); } this.enableSave(); }, // TODO: Hardcoding files is no longer possible in pure WebExt, either Exp only or gallery. insertImageVariable: async function () { if ((file = await gQuicktext.pickFile([4], 0, extension.localeData.localizeMessage("insertImage"))) != null) { this.insertVariable('IMAGE=FILE|' + file.path); } this.enableSave(); }, /* * IMPORT/EXPORT FUNCTIONS */ exportTemplatesToFile: async function () { await notifyTools.notifyBackground({ command: "exportTemplates" }); window.focus(); }, importTemplatesFromFile: async function () { // We use the legacy file picker here, because the user event handler sometimes // gets lost when piped through notify Tools. // const parsedData = await notifyTools.notifyBackground({ command: "pickAndParseConfigFile" }); const file = await gQuicktext.pickFile([5, 3], 0, extension.localeData.localizeMessage("importFile")); if (!file) return; const templates = await notifyTools.notifyBackground({ command: "parseTemplateFileForImport", path: file.path }); if (!templates) return; this.saveText(); this.saveScript(); var length = this.mTreeArray.length; gQuicktext.importTemplates(templates) this.changesMade(); this.makeTreeArray(); document.getElementById('group-tree').rowCountChanged(length - 1, this.mTreeArray.length - length); this.updateButtonStates(); }, exportScriptsToFile: async function () { await notifyTools.notifyBackground({ command: "exportScripts" }); window.focus(); }, importScriptsFromFile: async function () { // We use the legacy file picker here, because the user event handler sometimes // gets lost when piped through notify Tools. // const parsedData = await notifyTools.notifyBackground({ command: "pickAndParseConfigFile" }); const file = await gQuicktext.pickFile([5, 3], 0, extension.localeData.localizeMessage("importFile")); if (!file) return; const scripts = await notifyTools.notifyBackground({ command: "parseScriptFileForImport", path: file.path }); if (!scripts) return; this.saveText(); this.saveScript(); gQuicktext.importScripts(scripts) this.changesMade(); this.updateScriptGUI(); this.updateButtonStates(); }, browseAttachment: async function () { if ((file = await gQuicktext.pickFile([], 0, extension.localeData.localizeMessage("attachmentFile"))) != null) { var filePath = file.path; var attachments = document.getElementById('text-attachments').value; if (attachments != "") document.getElementById('text-attachments').value = attachments + ";" + filePath; else document.getElementById('text-attachments').value = filePath; this.checkForTextChanges(6); } }, pickScript: function () { var index = document.getElementById('script-list').value; if (index == null) { document.getElementById('script-title').value = ""; document.getElementById('script').value = ""; this.mScriptIndex = null; document.getElementById('script-title').disabled = true; document.getElementById('script').hidden = true; return; } document.getElementById('script').hidden = false; if (this.mScriptIndex != index) { if (this.scriptChangesMade()) { this.changesMade(); this.mScriptChangesMade = []; } this.saveScript(); } this.mScriptIndex = index; var script = gQuicktext.getScript(index, true); let disabled = script.protected; document.getElementById('script-title').value = script.name; document.getElementById('script').value = script.script; document.getElementById('script-title').disabled = disabled; document.getElementById('script').disabled = disabled; if (disabled) document.getElementById('script-button-remove').setAttribute("disabled", true); else document.getElementById('script-button-remove').removeAttribute("disabled"); if (isIncompatibleScript(script)) { document.getElementById('scripthelpbutton').style.display = "block"; document.getElementById('scriptwarning').style.display = "block"; } else { document.getElementById('scripthelpbutton').style.display = "none"; document.getElementById('scriptwarning').style.display = "none"; } }, pickText: function () { var index = document.getElementById('group-tree').view.selection.currentIndex; if (!this.mTreeArray[index]) { document.getElementById('text-caption').textContent = extension.localeData.localizeMessage("group"); document.getElementById('text-title').value = ""; this.showElement("group", true); this.mPickedIndex = null; return; } groupIndex = this.mTreeArray[index][0]; textIndex = this.mTreeArray[index][1]; if (this.mPickedIndex && this.textChangesMade()) { this.changesMade(); this.mTextChangesMade = []; this.saveText(); } this.mPickedIndex = [groupIndex, textIndex]; if (textIndex > -1) { var text = gQuicktext.getText(groupIndex, textIndex, true); document.getElementById('text-caption').textContent = extension.localeData.localizeMessage("template"); document.getElementById('text-title').value = text.name; document.getElementById('text').value = text.text; document.getElementById('text-keyword').value = text.keyword; document.getElementById('text-subject').value = text.subject; document.getElementById('text-attachments').value = text.attachments; document.getElementById("deprecated_attachment").style.display = text.attachments ? "" : "none" document.getElementById('label-shortcutModifier').value = extension.localeData.localizeMessage(document.getElementById('select-shortcutModifier').value + "Key") + "+"; if (gQuicktext.shortcutTypeAdv) { var elem = document.getElementById('text-shortcutAdv'); elem.value = text.shortcut; elem.hidden = false; document.getElementById('text-shortcutBasic').hidden = true; } else { var shortcut = text.shortcut; var elem = document.getElementById('text-shortcutBasic'); if (shortcut < 10) elem.selectedIndex = (shortcut == "0") ? 10 : shortcut; else elem.selectedIndex = 0; elem.hidden = false; document.getElementById('text-shortcutAdv').hidden = true; this.disableShortcuts(shortcut); } document.getElementById('text-type').selectedIndex = text.type == "text/html" ? 1 : 0; } else { document.getElementById('text-caption').textContent = extension.localeData.localizeMessage("group"); document.getElementById("text-title").value = gQuicktext.getGroup(groupIndex, true).name; document.getElementById("text").value = ""; document.getElementById("text-keyword").value = ""; document.getElementById("text-subject").value = ""; document.getElementById("text-attachments").value = ""; document.getElementById("deprecated_attachment").style.display = "none"; } var disabled = false; if (gQuicktext.getGroup(groupIndex, true).protected) { document.getElementById("group-button-remove").setAttribute("disabled", true); document.getElementById("group-button-add-text").setAttribute("disabled", true); disabled = true; } else { document.getElementById("group-button-remove").removeAttribute("disabled"); document.getElementById("group-button-add-text").removeAttribute("disabled"); } if (textIndex < 0) this.showElement("group", disabled); else this.showElement("text", disabled); }, showElement: function (aType, aDisabled) { var elements = document.getElementsByAttribute("candisable", "true"); for (var i = 0; i < elements.length; i++) { if (aDisabled) elements[i].setAttribute("disabled", true); else elements[i].removeAttribute("disabled"); } var elements = document.getElementsByAttribute("showfor", "*"); for (var i = 0; i < elements.length; i++) { var types = elements[i].getAttribute("showfor").split(","); var found = false; for (var type = 0; type < types.length; type++) { if (types[type] == aType) found = true; } if (found) elements[i].hidden = false; else elements[i].hidden = true; } }, makeUnique: function (name, arr) { let sanitizedName = name .replaceAll("|", "/") .replaceAll("[[", "{[") .replaceAll("]]", "]}"); let suffix = 1; let unique = sanitizedName; while (arr.includes(unique)) { suffix++; unique = `${sanitizedName} #${suffix}` } return unique; }, /* * Add/Remove groups/templates */ addGroup: function () { this.saveText(); let title = this.makeUnique( extension.localeData.localizeMessage("newGroup"), gQuicktext.mEditingGroup.map(g => g.mName) ); gQuicktext.addGroup(title, true); this.mCollapseState.push(true); this.makeTreeArray(); var treeObject = document.getElementById('group-tree'); treeObject.rowCountChanged(this.mTreeArray.length - 1, 1); treeObject.invalidateRow(this.mTreeArray.length - 1); selectedIndex = this.mTreeArray.length - 1; this.selectTreeRow(selectedIndex); this.updateButtonStates(); this.changesMade(); var titleElem = document.getElementById('text-title'); titleElem.focus(); titleElem.setSelectionRange(0, title.length); }, addText: function () { this.saveText(); var groupIndex = -1; if (this.mPickedIndex) groupIndex = this.mPickedIndex[0]; var groupLength = gQuicktext.getGroupLength(true); if (groupIndex == -1) { if (groupLength == 0) return; else groupIndex = 0; } let title = this.makeUnique( extension.localeData.localizeMessage("newTemplate"), gQuicktext.mEditingTexts[groupIndex].map(t => t.mName) ); gQuicktext.addText(groupIndex, title, true); this.makeTreeArray(); var selectedIndex = -1; for (var i = 0; i <= groupIndex; i++) { selectedIndex++; if (this.mCollapseState[i]) selectedIndex += gQuicktext.getTextLength(i, true); } var treeObject = document.getElementById('group-tree'); treeObject.rowCountChanged(selectedIndex - 1, 1); treeObject.invalidateRow(selectedIndex); this.selectTreeRow(selectedIndex); this.updateButtonStates(); this.changesMade(); var titleElem = document.getElementById('text-title'); titleElem.focus(); titleElem.setSelectionRange(0, title.length); }, removeText: function () { this.saveText(); if (this.mPickedIndex) { var groupIndex = this.mPickedIndex[0]; var textIndex = this.mPickedIndex[1]; var title = gQuicktext.getGroup(groupIndex, true).name; if (textIndex > -1) title = gQuicktext.getText(groupIndex, textIndex, true).name; if (confirm(extension.localeData.localizeMessage("remove", [title]))) { this.mPickedIndex = null; var textLength = gQuicktext.getTextLength(groupIndex, true); var selectedIndex = document.getElementById('group-tree').view.selection.currentIndex; var moveSelectionUp = false; if (this.mTreeArray[selectedIndex + 1] && this.mTreeArray[selectedIndex + 1][2] < this.mTreeArray[selectedIndex][2]) moveSelectionUp = true; var treeObject = document.getElementById('group-tree'); if (textIndex == -1) { gQuicktext.removeGroup(groupIndex, true); if (this.mCollapseState[groupIndex]) treeObject.rowCountChanged(selectedIndex, -(textLength + 1)); else treeObject.rowCountChanged(selectedIndex, -1); this.makeTreeArray(); treeObject.invalidate(); } else { gQuicktext.removeText(groupIndex, textIndex, true); treeObject.rowCountChanged(selectedIndex, -1); this.makeTreeArray(); treeObject.invalidate(); } this.updateVariableGUI(); this.updateButtonStates(); this.changesMade(); var selectedRow = false; if (moveSelectionUp) { selectedRow = true; this.selectTreeRow(selectedIndex - 1); } var rowCount = this.mTreeArray.length - 1; if (selectedIndex > rowCount || selectedIndex == -1) { selectedRow = true; this.selectTreeRow(rowCount); } if (!selectedRow) this.selectTreeRow(selectedIndex); } } }, addScript: function () { this.saveScript(); let title = this.makeUnique( extension.localeData.localizeMessage("newScript"), gQuicktext.mEditingScripts.map(s => s.mName) ); gQuicktext.addScript(title, true); this.updateScriptGUI(); this.updateButtonStates(); var listElem = document.getElementById('script-list'); selectedIndex = listElem.getRowCount() - 1; listElem.selectedIndex = selectedIndex; this.changesMade(); var titleElem = document.getElementById('script-title'); titleElem.focus(); titleElem.setSelectionRange(0, title.length); }, removeScript: function () { this.saveScript(); var scriptIndex = document.getElementById('script-list').value; if (scriptIndex != null) { var title = gQuicktext.getScript(scriptIndex, true).name; if (confirm(extension.localeData.localizeMessage("remove", [title]))) { gQuicktext.removeScript(scriptIndex, true); this.changesMade(); if (gQuicktext.getScriptLength(true) > 0) { var selectedIndex = document.getElementById('script-list').selectedIndex - 1; if (selectedIndex < 0) selectedIndex = 0; this.mScriptIndex = selectedIndex; } else { this.mScriptIndex = null; selectedIndex = -1; } document.getElementById('script-list').selectedIndex = selectedIndex; this.updateScriptGUI(); this.updateVariableGUI(); this.updateButtonStates(); } } }, /* * Other actions */ getCommunityScripts: function () { notifyTools.notifyBackground({ command: "openWebPage", url: "https://github.com/jobisoft/quicktext/wiki/Community-scripts" }); }, openHomepage: function () { notifyTools.notifyBackground({ command: "openWebPage", url: "https://github.com/jobisoft/quicktext/wiki/" }); }, openScriptHelp: function () { notifyTools.notifyBackground({ command: "openWebPage", url: "https://github.com/jobisoft/quicktext/issues/451" }); }, resetCounter: function () { notifyTools.notifyBackground({ command: "setPref", pref: "counter", value: 0 }); }, /* * OBSERVERS */ observe: function (aSubject, aTopic, aData) { if (aTopic == "updatesettings") { this.updateGUI(); } } } window.addEventListener("DOMContentLoaded", () => settingsDialog.init()); window.addEventListener("unload", () => settingsDialog.unload()); window.addEventListener('resize', () => { // Keep the known offset between window size and script list size, to make it // correctly adjust to the changed window size. let listElem = document.getElementById('script-list'); listElem.style.height = `${Math.max(150, window.innerHeight - boxHeightOffset)}px`; }); quicktext-6.4.4/xul_settings_dialog/settings.xhtml0000664000175000017500000006600215051542456023226 0ustar mechtildemechtilde __MSG_quicktext.settings.title__
__MSG_quicktext.settings.label__
__MSG_quicktext.defaultImport.label__
__MSG_quicktext.sharingTemplates.label__
__MSG_quicktext.sharingScripts.label__
__MSG_quicktext.counter.label__
__MSG_quicktext.title.label__
__MSG_quicktext.template.label__
__MSG_quicktext.title.label__
__MSG_quicktext.script.label__ ⚠️ __MSG_quicktext.scripthelp.label__ (Quicktext v6)
` ); await window.quicktextToolbar.load(); }, async updateLegacyToolbar(windowId) { let { window } = context.extension.windowManager.get(windowId); if (window.quicktextToolbar) { window.quicktextToolbar.update(); } } }, }; } onShutdown(isAppShutdown) { if (isAppShutdown) { return; // the application gets unloaded anyway } const { extension } = this; for (const window of Services.wm.getEnumerator("msgcompose")) { if (window) { let elements = Array.from( window.document.querySelectorAll( '[data-extension-injected="' + extension.id + '"]' ) ); for (let element of elements) { element.remove(); } if (window.quicktextToolbar) { window.quicktextToolbar.unload(); window.quicktextToolbar = null; } } } } }; exports.QuicktextToolbar = QuicktextToolbar; })(this);quicktext-6.4.4/api/QuicktextToolbar/composerToolbar.css0000664000175000017500000000027515051542456024202 0ustar mechtildemechtilde#quicktext-toolbar button { border: 0px; margin-left: 2px; margin-right: 2px; min-width: auto; } #quicktext-toolbar { border-bottom: 1px solid var(--splitter-color); } quicktext-6.4.4/api/QuicktextToolbar/composerToolbar.js0000664000175000017500000001754715051542456024040 0ustar mechtildemechtildevar quicktextToolbar = { windowId: null, extension: null, async dateTimeFormat(format, timeStamp) { return this.notifyTools.notifyBackground({ command: "getDateTimeFormat", data: { format, timeStamp } }); }, getPrettyKeyName(key) { return this.extension.localeData.localizeMessage(`quicktext.${key}Key.label`) }, async load() { Services.scriptloader.loadSubScript("resource://quicktext/api/NotifyTools/notifyTools.js", quicktextToolbar, "UTF-8"); const { ExtensionParent } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionParent.sys.mjs" ); this.extension = ExtensionParent.GlobalManager.getExtension( "{8845E3B3-E8FB-40E2-95E9-EC40294818C4}" ); await this.update(); document.getElementById("quicktext-variables-popup").addEventListener( "popupshowing", () => this.updateTimeMenus(), true ); }, unload() { }, async updateTimeMenus() { // Set the date/time in the variable menu. var timeStamp = new Date(); let fields = ["date-short", "date-long", "date-monthname", "time-noseconds", "time-seconds"]; for (let i = 0; i < fields.length; i++) { let field = fields[i]; let fieldType = field.split("-")[0]; if (document.getElementById(field)) { document.getElementById(field).setAttribute( "label", this.extension.localeData.localizeMessage(fieldType, [await this.dateTimeFormat(field, timeStamp)]) ); } } }, async update() { this.updateTimeMenus(); // Empty all shortcuts and keywords ????? this.mShortcuts = {}; this.mKeywords = {}; // Update the toolbar var toolbar = document.getElementById("quicktext-templates-toolbar"); if (toolbar != null) { // Clear template toolbar. var length = toolbar.children.length; for (var i = length - 1; i >= 0; i--) { toolbar.removeChild(toolbar.children[i]); } // Rebuild template groups (the leftmost entries) const templates = await this.notifyTools.notifyBackground({ command: "getTemplates" }); const collapseGroup = await this.notifyTools.notifyBackground({ command: "getPref", pref: "menuCollapse" }); const shortcutModifier = await this.notifyTools.notifyBackground({ command: "getPref", pref: "shortcutModifier" }); var groupLength = templates.groups.length; for (var i = 0; i < groupLength; i++) { var textLength = templates.texts[i].length; if (textLength) { // Add first level element, this will be either a menu or a button (if // only one text in this group). var toolbarbuttonGroup; let t = document.createXULElement("button"); // Add a tabindex of -1 (not reachable via sequential keyboard navigation). // see: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex t.setAttribute("tabindex", "-1"); if (textLength == 1 && collapseGroup) { toolbarbuttonGroup = toolbar.appendChild(t); toolbarbuttonGroup.setAttribute("label", templates.texts[i][0].name); toolbarbuttonGroup.setAttribute("i", i); toolbarbuttonGroup.setAttribute("j", 0); toolbarbuttonGroup.setAttribute("class", "customEventListenerForDynamicMenu"); } else { t.setAttribute("type", "menu"); toolbarbuttonGroup = toolbar.appendChild(t); toolbarbuttonGroup.setAttribute("label", templates.groups[i].name); var menupopup = toolbarbuttonGroup.appendChild(document.createXULElement("menupopup")); // Add second level elements: all found texts of this group. for (var j = 0; j < textLength; j++) { var text = templates.texts[i][j]; var toolbarbutton = document.createXULElement("menuitem"); toolbarbutton.setAttribute("label", text.name); toolbarbutton.setAttribute("i", i); toolbarbutton.setAttribute("j", j); toolbarbutton.setAttribute("class", "customEventListenerForDynamicMenu"); var shortcut = text.shortcut; if (shortcut != "") { if (shortcut == 10) shortcut = 0; toolbarbutton.setAttribute("acceltext", `${this.getPrettyKeyName(shortcutModifier)} + ${shortcut}`); } menupopup.appendChild(toolbarbutton); } } toolbarbuttonGroup = null; // Update the keyshortcuts. for (var j = 0; j < textLength; j++) { var text = templates.texts[i][j]; var shortcut = text.shortcut; if (shortcut != "" && typeof this.mShortcuts[shortcut] == "undefined") this.mShortcuts[shortcut] = [i, j]; var keyword = text.keyword; if (keyword != "" && typeof this.mKeywords[keyword] == "undefined") this.mKeywords[keyword] = [i, j]; } } } } // Add event listeners. let items = document.getElementsByClassName("customEventListenerForDynamicMenu"); for (let i = 0; i < items.length; i++) { items[i].addEventListener("command", function () { quicktextToolbar.insertTemplate(this.getAttribute("i"), this.getAttribute("j")); }, true); } this.visibleToolbar(); }, // This may not needed, since the toolbar will be an extra add-on and the toolbar // will only be shown, when the extra add-on is installed? The "toolbar" pref has // no config UI at he moment. async visibleToolbar() { const toolbar = await this.notifyTools.notifyBackground({ command: "getPref", pref: "toolbar" }); if (toolbar) { document.getElementById("quicktext-toolbar").removeAttribute("collapsed"); } else { document.getElementById("quicktext-toolbar").setAttribute("collapsed", true); } }, focusMessageBody() { let editor = GetCurrentEditorElement();//document.getElementsByTagName("editor"); if (editor) { editor.focus(); } }, async insertVariable(aVar) { this.focusMessageBody(); await this.notifyTools.notifyBackground({ command: "insertVariable", aVar, windowId: this.windowId }); }, async insertTemplate(group, text) { this.focusMessageBody(); await this.notifyTools.notifyBackground({ command: "insertTemplate", group, text, windowId: this.windowId }); }, async insertContentFromFile(aType) { const file = await this.pickFile(aType, /* open */ 0, ""); // browser.i18n.getMessage("insertFile") this.focusMessageBody(); if (file) { await this.notifyTools.notifyBackground({ command: "insertFile", aType, file, windowId: this.windowId }); } }, async pickFile(aType, aMode, aTitle) { let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(Components.interfaces.nsIFilePicker); switch (aMode) { case 1: // save filePicker.init(window.browsingContext, aTitle, filePicker.modeSave); break; default: // open filePicker.init(window.browsingContext, aTitle, filePicker.modeOpen); break; } switch (aType) { case 0: // insert TXT file filePicker.appendFilters(filePicker.filterText); filePicker.defaultExtension = "txt"; break; case 1: // insert HTML file filePicker.appendFilters(filePicker.filterHTML); filePicker.defaultExtension = "html"; break; } filePicker.appendFilters(filePicker.filterAll); let rv = await new Promise(function (resolve, reject) { filePicker.open(result => { resolve(result); }); }); if (rv == filePicker.returnOK || rv == filePicker.returnReplace) { // Create DOM File from real file. const file = await File.createFromNsIFile(filePicker.file); return file; } else { return null; } } } quicktext-6.4.4/api/LegacyHelper/0000775000175000017500000000000015051542456017352 5ustar mechtildemechtildequicktext-6.4.4/api/LegacyHelper/schema.json0000664000175000017500000000200215051542456021477 0ustar mechtildemechtilde[ { "namespace": "LegacyHelper", "functions": [ { "name": "registerGlobalUrls", "type": "function", "async": true, "description": "Register folders which should be available as legacy chrome:// urls or resource:// urls", "parameters": [ { "name": "data", "type": "array", "items": { "type": "array", "items": { "type": "string" } }, "description": "Array of manifest url definitions (content, locale or resource)" } ] }, { "name": "openDialog", "type": "function", "parameters": [ { "name": "name", "type": "string", "description": "name of the new dialog" }, { "name": "path", "type": "string", "description": "path of the dialog to be opened" } ] } ] } ]quicktext-6.4.4/api/LegacyHelper/implementation.js0000664000175000017500000000626715051542456022750 0ustar mechtildemechtilde/* * This file is provided by the webext-support repository at * https://github.com/thunderbird/webext-support * * Version 1.1 * - registerGlobalUrls() is now async, to be able to properly await registration * * Version 1.0 * - initial release * * Author: * - John Bieling (john@thunderbird.net) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // Using a closure to not leak anything but the API to the outside world. (function (exports) { const aomStartup = Cc[ "@mozilla.org/addons/addon-manager-startup;1" ].getService(Ci.amIAddonManagerStartup); const resProto = Cc[ "@mozilla.org/network/protocol;1?name=resource" ].getService(Ci.nsISubstitutingProtocolHandler); const chromeHandlers = []; const resourceUrls = []; var LegacyHelper = class extends ExtensionCommon.ExtensionAPI { getAPI(context) { return { LegacyHelper: { registerGlobalUrls(data) { const manifestURI = Services.io.newURI( "manifest.json", null, context.extension.rootURI ); for (let entry of data) { // [ "resource", "shortname" , "path" ] switch (entry[0]) { case "resource": { let uri = Services.io.newURI( entry[2], null, context.extension.rootURI ); resProto.setSubstitutionWithFlags( entry[1], uri, resProto.ALLOW_CONTENT_ACCESS ); resourceUrls.push(entry[1]); } break; case "content": case "locale": { let handle = aomStartup.registerChrome( manifestURI, [entry] ); chromeHandlers.push(handle); } break; default: console.warn(`LegacyHelper: Unsupported url type: ${entry[0]}`) } } }, openDialog(name, path) { let window = Services.wm.getMostRecentWindow("mail:3pane"); window.openDialog( path, name, "chrome,resizable,centerscreen" ); }, }, }; } onShutdown(isAppShutdown) { if (isAppShutdown) { return; // the application gets unloaded anyway } for (let chromeHandler of chromeHandlers) { if (chromeHandler) { chromeHandler.destruct(); chromeHandler = null; } } for (let resourceUrl of resourceUrls) { resProto.setSubstitution( resourceUrl, null ); } // Flush all caches. Services.obs.notifyObservers(null, "startupcache-invalidate"); } }; exports.LegacyHelper = LegacyHelper; })(this);quicktext-6.4.4/api/LegacyHelper/README.md0000664000175000017500000000360615051542456020636 0ustar mechtildemechtilde## Objective This API is a temporary helper while converting legacy extensions to modern WebExtensions. It allows to register `resource://` URLs, which are needed to load custom system modules (*.sys.mjs), and `chrome://` URLs, which are needed to open legacy XUL dialogs. ## Usage Add the [LegacyHelper API](https://github.com/thunderbird/webext-support/tree/master/experiments/LegacyHelper) to your add-on. Your `manifest.json` needs an entry like this: ```json "experiment_apis": { "LegacyHelper": { "schema": "api/LegacyHelper/schema.json", "parent": { "scopes": ["addon_parent"], "paths": [["LegacyHelper"]], "script": "api/LegacyHelper/implementation.js" } } }, ``` ## API Functions This API provides the following functions: ### async registerGlobalUrls(data) Register `chrome://*/content/` and `resource://*/` URLs. The function accepts a `data` parameter, which is an array of URL definition items. For example: ```javascript await browser.LegacyHelper.registerGlobalUrls([ ["content", "myaddon", "chrome/content/"], ["resource", "myaddon", "modules/"], ]); ``` This registers the following URLs: * `chrome://myaddon/content/` pointing to the `/chrome/content/` folder (the `/content/` part in the URL is fix and does not depend on the name of the folder it is pointing to) * `resource://myaddon/` pointing to the `/modules/` folder. To register a `resource://` URL which points to the root folder, use `.` instead". ### async openDialog(name, path) Open a XUL dialog. The `name` parameter is a unique name identifying the dialog. If the dialog with that name is already open, it will be focused instead of being re-opened. The `path` parameter is a `chrome://*/content/` URL pointing to the XUL dialog file (*.xul or *.xhtml). ```javascript browser.LegacyHelper.openDialog( "XulAddonOptions", "chrome://myaddon/content/options.xhtml" ); ``` quicktext-6.4.4/html/0000775000175000017500000000000015051542456015201 5ustar mechtildemechtildequicktext-6.4.4/html/popup.html0000664000175000017500000000035415051542456017234 0ustar mechtildemechtilde Quicktext Popup quicktext-6.4.4/html/popup.css0000664000175000017500000000121615051542456017056 0ustar mechtildemechtilde:popover-open { width: 300px; height: 200px; border-radius: 10px; border-width: 3px; padding: 0; } ::backdrop { backdrop-filter: brightness(30%); } #quicktext-popover { padding: 20px; border: 1px solid red; } #quicktext-popover-buttons { display: flex; justify-content: flex-end; width: 100%; } .quicktext-popover-btn { margin-top: 10px; margin-left: 10px; } #quicktext-popover-select { height: auto; width: 100%; } #quicktext-popover-prompt { width: calc(100% - 6px); padding: 3px; margin: 0; border: 0; } #quicktext-popover-prompt-wrapper { border: 1px solid grey; }quicktext-6.4.4/html/popup.js0000664000175000017500000001206415051542456016705 0ustar mechtildemechtildeclass QuicktextPopover { userInput = Promise.withResolvers(); get popover() { return document.getElementById("quicktext-popover"); } show() { this.popover.showPopover(); } result() { return this.userInput.promise; } } async function openQuicktextPopover(type, label, values) { let popover; if (type == "prompt") { popover = new class extends QuicktextPopover { show() { document.body.insertAdjacentHTML("beforeend", `
${label}
`); document.getElementById("quicktext-popover-cancel").addEventListener( "click", () => this.userInput.resolve() ); document.getElementById("quicktext-popover-ok").addEventListener( "click", () => this.userInput.resolve(this.value) ); document.addEventListener("keydown", e => this.keydownEventHandler(e)) this.prompt.focus(); super.show(); } get prompt() { return document.getElementById("quicktext-popover-prompt"); } get value() { return this.prompt.value; } set value(v) { this.prompt.value = v; } keydownEventHandler(e) { switch (e.code) { case "Escape": this.userInput.resolve(); break; case "Enter": case "NumpadEnter": this.userInput.resolve(this.value); break; } } } } else if (type == "select") { popover = new class extends QuicktextPopover { show() { document.body.insertAdjacentHTML("beforeend", `
${label}
`); document.getElementById("quicktext-popover-cancel").addEventListener( "click", () => this.userInput.resolve() ); document.getElementById("quicktext-popover-ok").addEventListener( "click", () => this.userInput.resolve(this.select.value) ); document.addEventListener("keydown", e => this.keydownEventHandler(e)) this.select.addEventListener("dblclick", e => this.dblclickEventHandler(e)) this.select.focus(); super.show(); } get select() { return document.getElementById("quicktext-popover-select"); } keydownEventHandler(e) { switch (e.code) { case "Escape": this.userInput.resolve(); break; case "Enter": case "NumpadEnter": this.userInput.resolve(this.select.value); break; } } dblclickEventHandler(e) { this.userInput.resolve(this.select.value); } } } else { console.error(`Unsupported popover type: ${type}`); return ""; } popover.show(); return popover.result(); } let config = await browser.runtime.sendMessage({ action: "config" }); if (config.selectLabel) { let rv = await openQuicktextPopover("select", config.selectLabel, config.selectValues); await browser.runtime.sendMessage({ action: "close", rv }); } else if (config.promptLabel) { let rv = await openQuicktextPopover("prompt", config.promptLabel, config.promptValue); await browser.runtime.sendMessage({ action: "close", rv }); } window.close();