From c8012b22c7b6208b3c2808fa025a8e75eab7736d Mon Sep 17 00:00:00 2001 From: "h.u.g.u.rp" Date: Sun, 16 Jul 2023 06:24:56 +0200 Subject: [PATCH 1/3] add class OpenSearchDescription({}), class I4kFind({}).osd --- src/index.js | 156 +++++++++++++++++++++++++++++++++------ src/tests/open-search.js | 54 ++++++++++++++ 2 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 src/tests/open-search.js diff --git a/src/index.js b/src/index.js index 57829da..e3f7676 100644 --- a/src/index.js +++ b/src/index.js @@ -52,6 +52,7 @@ export const DEFAULT_SYMBOLS = { r4pr: "https://radio4000.com/{}/play/random", sheet: "https://docs.google.com/spreadsheets/create?title={}", gmail: "https://mail.google.com/mail/#inbox?compose=new&title={}", + gpt: "https://chat.openai.com/?model=gpt-4", note: "https://note.internet4000.com/note?content={}", wr: "https://en.wikipedia.org/wiki/Special:Random", wri: "https://commons.wikimedia.org/wiki/Special:Random/File", @@ -102,11 +103,100 @@ export const DEFAULT_SYMBOLS = { }, }; -const App = { - localStorageKey: "i4find", - queryParamName: "q", - documentationUrl: "https://github.com/internet4000/find", - symbols: DEFAULT_SYMBOLS, +/* generate the OSD needed to register as a browser search engine */ +export class OpenSearchDescription { + get attributes() { + return [ + "shortName", + "description", + "tags", + "contact", + "templateHTML", + "templateXML", + "image", + "longName", + "exampleSearchTerms", + "developer", + "attribution", + "syndicationRight", + "adultContent", + "language", + "outputEncoding", + "inputEncoding", + ]; + } + get config() { + return this.attributes.reduce((acc, val) => { + acc[val] = this[val]; + return acc; + }, {}); + } + constructor(config) { + this.attributes.forEach((attr) => { + const isSet = Object.hasOwnProperty(attr); + const val = isSet ? config[attr] : DEFAULT_OSD[attr]; + this[attr] = val; + }); + } + + exportJSON() { + return JSON.stringify({ ...this.config }, null, 2); + } + + exportXML() { + const config = { ...this.config }; + return ` + + ${config.shortName} + ${config.description} + ${config.tags} + ${config.contact} + + + ${config.image} + ${config.longName} + + ${config.developer} + ${config.attribution} + ${config.syndicationRight} + ${config.adultContent} + ${config.language} + ${config.outputEncoding} + ${config.inputEncoding} +`; + } + + download(filename, type) { + if (isBrowser()) { + const data = type === "json" ? this.toJSON() : this.toXML(); + const blob = new Blob([data], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.download = filename; + link.href = url; + link.click(); + } else if (isNode()) { + process.stdout.write(type === "json" ? this.toJSON() : this.toXML()); + } + } +} + +/* the logic for search engines and actions */ +export class I4kFind { + constructor({ + symbols, + queryParamName, + documentationUrl, + localStorageKey, + osd, + } = {}) { + this.localStorageKey = localStorageKey || "i4find"; + this.queryParamName = queryParamName || "q"; + this.documentationUrl = + documentationUrl || "https://github.com/internet4000/find"; + this.symbols = symbols || DEFAULT_SYMBOLS; + this.osd = osd || new OpenSearchDescription(DEFAULT_OSD); + } // add a new user engine // to the list of user defined engines in user symbols @@ -117,7 +207,7 @@ const App = { } else { console.error("symbol", symbol, "does not exist in", symbols); } - }, + } // add a new user engine // to the list of user defined engines in user symbols @@ -127,7 +217,7 @@ const App = { delete symbols[symbol].engines[engineId]; this.setUserSymbols(symbols); } - }, + } // replaces the placeholder `{}` in a url, with the query, if any // otherwise just returns the url @@ -157,7 +247,7 @@ const App = { }); return url; - }, + } // To get an engine url from its engine id, // also pass a list of symbols and a symbol @@ -165,7 +255,7 @@ const App = { const symbolEngines = symbols[symbol]; const engineUrl = symbolEngines.engines[engineId]; return engineUrl; - }, + } // returns a result url string to open // default to "search for help if only a symbol" @@ -184,7 +274,7 @@ const App = { } const engineUrl = this.getEngineUrl(symbols, symbol, engineId); return this.replaceUrlPlaceholders(engineUrl, userQuery); - }, + } // is there a symbol in this symbol group? `!ex` return `!` ! checkForSymbol(symbolGroup) { @@ -192,7 +282,7 @@ const App = { symbol = symbolGroup.charAt(0); return availableSymbols.indexOf(symbol) >= 0 ? symbol : false; - }, + } // is an engine available in a array of symbolGroups getSymbolsForEngine(symbolGroups, symbol, engineId) { @@ -212,7 +302,7 @@ const App = { }); if (!filteredGroups.length) return false; return filteredGroups[0]; - }, + } // param: // - userQuery: string `!m new york city` @@ -271,12 +361,12 @@ const App = { } return returnData; - }, + } // check whether URL starts with a scheme checkUrl(url) { return url.startsWith("//") || url.includes("://"); - }, + } openUrl(url) { // replace history state @@ -292,7 +382,7 @@ const App = { // noop } return url; - }, + } // takes a string, request query of a user, decode the request // and open the "correct destination site" with the user requested query @@ -305,7 +395,7 @@ const App = { this.openUrl(result); } return result; - }, + } init() { /* do-not extract user query/search from window url query param, @@ -330,7 +420,7 @@ const App = { } } return result; - }, + } // get the user symbols from local storage // or returns an empty new set of symbols @@ -349,7 +439,7 @@ const App = { } return JSON.parse(JSON.stringify(storageSymbols)); - }, + } // saves a new set of user symbols to local storage setUserSymbols(newSymbols) { @@ -357,7 +447,7 @@ const App = { const newSymbolsString = JSON.stringify(newSymbols); localStorage.setItem(this.localStorageKey, newSymbolsString); // cannot send event from here; we might be in browser/node - }, + } // generates new userSymbols from copying original symbols // to be used with Find default symbols (Find.symbols) @@ -371,7 +461,7 @@ const App = { } }); return symbols; - }, + } help() { // write user documentation @@ -380,9 +470,33 @@ const App = { console.info("- Usage: Find.getUserSymbols()"); console.info("- Usage:", "#add ! ex https://example.org/?search={}"); console.info("— Explore the window.Find object"); - }, + } +} + +/* find's default OSD */ +export const DEFAULT_OSD = { + shortName: "Find", + description: "Find anything anywhere", + tags: "find now productivity search", + contact: "internet4000.com", + templateHTML: "https://internet4000.github.io/find/#q={searchTerms}", + templateXML: "https://internet4000.github.io/find/opensearch.xml", + image: + "https://internet4000.github.io/find/public/favicons/favicon-32x32.png", + longName: + "Customize the browser omnibox URL bar with custom search engines and actions", + exampleSearchTerms: "test", + developer: "Internet4000", + attribution: "public domain", + syndicationRight: "open", + adultContent: "false", + language: "en-us", + outputEncoding: "UTF-8", + inputEncoding: "UTF-8", }; +const App = new I4kFind(); + /* handle node input if any */ if (!isBrowser && isNode && process.argv.length > 2) { const userArg = process.argv.slice(2).join(" "); diff --git a/src/tests/open-search.js b/src/tests/open-search.js new file mode 100644 index 0000000..5428998 --- /dev/null +++ b/src/tests/open-search.js @@ -0,0 +1,54 @@ +import test from "ava"; +import Find, { OpenSearchDescription } from "../../src/index.js"; + +const CONFIG_EXPORT = { + shortName: "Find", + description: "Find anything anywhere", + tags: "find now productivity search", + contact: "internet4000.com", + templateHTML: "https://internet4000.github.io/find/#q={searchTerms}", + templateXML: "https://internet4000.github.io/find/opensearch.xml", + image: + "https://internet4000.github.io/find/public/favicons/favicon-32x32.png", + longName: + "Customize the browser omnibox URL bar with custom search engines and actions", + exampleSearchTerms: "test", + developer: "Internet4000", + attribution: "public domain", + syndicationRight: "open", + adultContent: "false", + language: "en-us", + outputEncoding: "UTF-8", + inputEncoding: "UTF-8", +}; + +const XML_EXPORT = ` + + Find + Find anything anywhere + find now productivity search + internet4000.com + + + https://internet4000.github.io/find/public/favicons/favicon-32x32.png + Customize the browser omnibox URL bar with custom search engines and actions + + Internet4000 + public domain + open + false + en-us + UTF-8 + UTF-8 +`; + +test("OpenSearchDescription is in Find with a config", (t) => { + t.like(Find.osd.config, CONFIG_EXPORT); +}); +test("OpenSearch can export to JSON", (t) => { + console.log(Find.osd.exportJSON(), JSON.stringify(CONFIG_EXPORT)); + t.like(JSON.parse(Find.osd.exportJSON()), CONFIG_EXPORT); +}); +test("OpenSearch can export to XML", (t) => { + t.deepEqual(Find.osd.exportXML(), XML_EXPORT); +}); From 4027417f52997dacb02131c8d2bdab2ef9beee48 Mon Sep 17 00:00:00 2001 From: "h.u.g.u.rp" Date: Sun, 16 Jul 2023 06:40:20 +0200 Subject: [PATCH 2/3] add src/scripts/opensearch-xml.js openSearchXml() > opensearch.xml --- package.json | 3 ++- src/scripts/opensearch-xml.js | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/scripts/opensearch-xml.js diff --git a/package.json b/package.json index b581e15..e7302ff 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "type": "module", "scripts": { "dev": "serve -p 3000", - "test": "ava" + "test": "ava", + "opensearch": "node ./src/scripts/opensearch-xml.js" }, "repository": { "type": "git", diff --git a/src/scripts/opensearch-xml.js b/src/scripts/opensearch-xml.js new file mode 100644 index 0000000..fef9f93 --- /dev/null +++ b/src/scripts/opensearch-xml.js @@ -0,0 +1,10 @@ +import Find from "../index.js"; + +const openSearchXml = () => { + console.log(Find.osd); + return process.stdout.write(Find.osd.exportXML()); +}; + +openSearchXml(); + +export default openSearchXml; From 949fd1f2f900a41834da9311da6fafeaff85f693 Mon Sep 17 00:00:00 2001 From: "h.u.g.u.rp" Date: Sun, 16 Jul 2023 07:56:47 +0200 Subject: [PATCH 3/3] try auto-generate public/opensearch.xml --- .github/workflows/static.yml | 3 ++- package.json | 23 ++++++++++++++++++ public/opensearch.xml | 18 ++++---------- src/index.js | 2 +- src/scripts/opensearch-xml.js | 44 +++++++++++++++++++++++++++++++---- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 2bd90b5..ffcb0e0 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -31,13 +31,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - run: npm run opensearch public/opensearch.xml - name: Setup Pages uses: actions/configure-pages@v3 - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: # Upload entire repository - path: '.' + path: "." - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 diff --git a/package.json b/package.json index e7302ff..0fcd4b7 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,28 @@ "files": [ "src/tests/**/*" ] + }, + "i4k-find": { + "localStorageKey": "i4find", + "queryParamName": "q", + "documentationUrl": "https://github.com/internet4000/find", + "osd": { + "shortName": "Find", + "description": "Find anything anywhere", + "tags": "find now productivity search", + "contact": "https://github.com/internet4000", + "templateHTML": "https://internet4000.github.io/find/#q={searchTerms}", + "templateXML": "https://internet4000.github.io/find/opensearch.xml", + "image": "https://internet4000.github.io/find/public/favicons/favicon-32x32.png", + "longName": "Customize the browser omnibox URL bar with custom search engines and actions", + "exampleSearchTerms": "test", + "developer": "Internet4000", + "attribution": "public domain", + "syndicationRight": "open", + "adultContent": "false", + "language": "en-us", + "outputEncoding": "UTF-8", + "inputEncoding": "UTF-8" + } } } diff --git a/public/opensearch.xml b/public/opensearch.xml index 4b4783a..4c02a2a 100644 --- a/public/opensearch.xml +++ b/public/opensearch.xml @@ -4,24 +4,16 @@ Find anything anywhere find now productivity search internet4000.com - - + + https://internet4000.github.io/find/public/favicons/favicon-32x32.png - - When you're looking for something, and you know where to find it, - don't lose a second, let's go! Find! - + Customize the browser omnibox URL bar with custom search engines and actions Internet4000 - - public domain - + public domain open false en-us UTF-8 UTF-8 - + \ No newline at end of file diff --git a/src/index.js b/src/index.js index e3f7676..7e8234b 100644 --- a/src/index.js +++ b/src/index.js @@ -195,7 +195,7 @@ export class I4kFind { this.documentationUrl = documentationUrl || "https://github.com/internet4000/find"; this.symbols = symbols || DEFAULT_SYMBOLS; - this.osd = osd || new OpenSearchDescription(DEFAULT_OSD); + this.osd = new OpenSearchDescription(osd || DEFAULT_OSD); } // add a new user engine diff --git a/src/scripts/opensearch-xml.js b/src/scripts/opensearch-xml.js index fef9f93..2dfee0d 100644 --- a/src/scripts/opensearch-xml.js +++ b/src/scripts/opensearch-xml.js @@ -1,10 +1,44 @@ -import Find from "../index.js"; +import { I4kFind } from "../index.js"; +import packageJson from "../../package.json" assert { type: "json" }; -const openSearchXml = () => { - console.log(Find.osd); - return process.stdout.write(Find.osd.exportXML()); +import fs from "fs/promises"; +import path from "path"; + +const pkgName = packageJson["name"]; +const config = packageJson[pkgName]; + +const readUserPackageJson = async () => { + const packageJsonPath = path.join(process.cwd(), "package.json"); + const data = await fs.readFile(packageJsonPath, "utf-8"); + return JSON.parse(data); +}; + +const newUserConfig = async () => { + const userPackageJson = await readUserPackageJson(); + return userPackageJson[pkgName]; +}; + +const openSearchXml = async (outputPath = "opensearch.xml") => { + let newConfig; + try { + newConfig = await newUserConfig(); + } catch (e) { + console.error(e); + newConfig = config; + } + const find = new I4kFind(newConfig); + const xmlOutput = find.osd.exportXML(); + if (outputPath) { + try { + path.resolve(process.cwd(), outputPath); + const localPath = path.join(process.cwd(), outputPath); + await fs.writeFile(localPath, xmlOutput); + } catch (e) { + console.error("Unable to write opensearch.xml", e); + } + } }; -openSearchXml(); +openSearchXml(process.argv[2]).catch((e) => console.error(e)); export default openSearchXml;