From ddade782855263812e80cd7d2597dcb0586b5d30 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Thu, 26 Jun 2025 02:23:51 +0530 Subject: [PATCH 1/8] meta: small change in menifest file and adds gitignore --- .gitignore | 20 ++++++++++++++++++++ frontend/manifest.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fefe6ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +Promotional +release +.env + +node_modules +.DS_Store +dist +coverage +.vscode +.idea +*.log +*.env.local +*.env.development.local +*.env.test.local +*.env.production.local +*.env.*.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* + diff --git a/frontend/manifest.json b/frontend/manifest.json index d874244..5665df8 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -4,7 +4,7 @@ "version": "1.0.1", "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", "author": "Dinesh Kumar Sutihar", - "permissions": ["activeTab", "scripting", "storage"], + "permissions": ["activeTab", "storage"], "host_permissions": ["https://*.workers.dev/"], "action": { "default_popup": "popup.html", From 803462443b051c3b0e32e1b24fad73c3cd68e0f9 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Thu, 26 Jun 2025 02:27:02 +0530 Subject: [PATCH 2/8] feat: adds prism package for color functionality --- frontend/packages/prism.css | 98 +++++++++++++++++++++++++++++++++++++ frontend/packages/prism.js | 16 ++++++ 2 files changed, 114 insertions(+) create mode 100644 frontend/packages/prism.css create mode 100644 frontend/packages/prism.js diff --git a/frontend/packages/prism.css b/frontend/packages/prism.css new file mode 100644 index 0000000..6163e9f --- /dev/null +++ b/frontend/packages/prism.css @@ -0,0 +1,98 @@ +/* PrismJS 1.30.0 +https://prismjs.com/download#themes=prism-tomorrow&languages=markup+css+clike+javascript+c+cpp+go+java+python+ruby+rust+typescript+typoscript */ +code[class*="language-"], +pre[class*="language-"] { + color: #ccc; + background: 0 0; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #2d2d2d; +} +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.block-comment, +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #999; +} +.token.punctuation { + color: #ccc; +} +.token.attr-name, +.token.deleted, +.token.namespace, +.token.tag { + color: #e2777a; +} +.token.function-name { + color: #6196cc; +} +.token.boolean, +.token.function, +.token.number { + color: #f08d49; +} +.token.class-name, +.token.constant, +.token.property, +.token.symbol { + color: #f8c555; +} +.token.atrule, +.token.builtin, +.token.important, +.token.keyword, +.token.selector { + color: #cc99cd; +} +.token.attr-value, +.token.char, +.token.regex, +.token.string, +.token.variable { + color: #7ec699; +} +.token.entity, +.token.operator, +.token.url { + color: #67cdcc; +} +.token.bold, +.token.important { + font-weight: 700; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +.token.inserted { + color: green; +} diff --git a/frontend/packages/prism.js b/frontend/packages/prism.js new file mode 100644 index 0000000..e272fe7 --- /dev/null +++ b/frontend/packages/prism.js @@ -0,0 +1,16 @@ +/* PrismJS 1.30.0 +https://prismjs.com/download#themes=prism-tomorrow&languages=markup+css+clike+javascript+c+cpp+go+java+python+ruby+rust+typescript+typoscript */ +var _self = "undefined" != typeof window ? window : "undefined" != typeof WorkerGlobalScope && self instanceof WorkerGlobalScope ? self : {}, Prism = function (e) { var n = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i, t = 0, r = {}, a = { manual: e.Prism && e.Prism.manual, disableWorkerMessageHandler: e.Prism && e.Prism.disableWorkerMessageHandler, util: { encode: function e(n) { return n instanceof i ? new i(n.type, e(n.content), n.alias) : Array.isArray(n) ? n.map(e) : n.replace(/&/g, "&").replace(/= g.reach); A += w.value.length, w = w.next) { var P = w.value; if (n.length > e.length) return; if (!(P instanceof i)) { var E, S = 1; if (y) { if (!(E = l(b, A, e, m)) || E.index >= e.length) break; var L = E.index, O = E.index + E[0].length, C = A; for (C += w.value.length; L >= C;)C += (w = w.next).value.length; if (A = C -= w.value.length, w.value instanceof i) continue; for (var j = w; j !== n.tail && (C < O || "string" == typeof j.value); j = j.next)S++, C += j.value.length; S--, P = e.slice(A, C), E.index -= A } else if (!(E = l(b, 0, P, m))) continue; L = E.index; var N = E[0], _ = P.slice(0, L), M = P.slice(L + N.length), W = A + P.length; g && W > g.reach && (g.reach = W); var I = w.prev; if (_ && (I = u(n, I, _), A += _.length), c(n, I, S), w = u(n, I, new i(f, p ? a.tokenize(N, p) : N, k, N)), M && u(n, w, M), S > 1) { var T = { cause: f + "," + d, reach: W }; o(e, n, t, w.prev, A, T), g && T.reach > g.reach && (g.reach = T.reach) } } } } } } function s() { var e = { value: null, prev: null, next: null }, n = { value: null, prev: e, next: null }; e.next = n, this.head = e, this.tail = n, this.length = 0 } function u(e, n, t) { var r = n.next, a = { value: t, prev: n, next: r }; return n.next = a, r.prev = a, e.length++, a } function c(e, n, t) { for (var r = n.next, a = 0; a < t && r !== e.tail; a++)r = r.next; n.next = r, r.prev = n, e.length -= a } if (e.Prism = a, i.stringify = function e(n, t) { if ("string" == typeof n) return n; if (Array.isArray(n)) { var r = ""; return n.forEach((function (n) { r += e(n, t) })), r } var i = { type: n.type, content: e(n.content, t), tag: "span", classes: ["token", n.type], attributes: {}, language: t }, l = n.alias; l && (Array.isArray(l) ? Array.prototype.push.apply(i.classes, l) : i.classes.push(l)), a.hooks.run("wrap", i); var o = ""; for (var s in i.attributes) o += " " + s + '="' + (i.attributes[s] || "").replace(/"/g, """) + '"'; return "<" + i.tag + ' class="' + i.classes.join(" ") + '"' + o + ">" + i.content + "" }, !e.document) return e.addEventListener ? (a.disableWorkerMessageHandler || e.addEventListener("message", (function (n) { var t = JSON.parse(n.data), r = t.language, i = t.code, l = t.immediateClose; e.postMessage(a.highlight(i, a.languages[r], r)), l && e.close() }), !1), a) : a; var g = a.util.currentScript(); function f() { a.manual || a.highlightAll() } if (g && (a.filename = g.src, g.hasAttribute("data-manual") && (a.manual = !0)), !a.manual) { var h = document.readyState; "loading" === h || "interactive" === h && g && g.defer ? document.addEventListener("DOMContentLoaded", f) : window.requestAnimationFrame ? window.requestAnimationFrame(f) : window.setTimeout(f, 16) } return a }(_self); "undefined" != typeof module && module.exports && (module.exports = Prism), "undefined" != typeof global && (global.Prism = Prism); +Prism.languages.markup = { comment: { pattern: //, greedy: !0 }, prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 }, doctype: { pattern: /"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i, greedy: !0, inside: { "internal-subset": { pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/, lookbehind: !0, greedy: !0, inside: null }, string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 }, punctuation: /^$|[[\]]/, "doctype-tag": /^DOCTYPE/i, name: /[^\s<>'"]+/ } }, cdata: { pattern: //i, greedy: !0 }, tag: { pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, greedy: !0, inside: { tag: { pattern: /^<\/?[^\s>\/]+/, inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ } }, "special-attr": [], "attr-value": { pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, inside: { punctuation: [{ pattern: /^=/, alias: "attr-equals" }, { pattern: /^(\s*)["']|["']$/, lookbehind: !0 }] } }, punctuation: /\/?>/, "attr-name": { pattern: /[^\s>\/]+/, inside: { namespace: /^[^\s>\/:]+:/ } } } }, entity: [{ pattern: /&[\da-z]{1,8};/i, alias: "named-entity" }, /&#x?[\da-f]{1,8};/i] }, Prism.languages.markup.tag.inside["attr-value"].inside.entity = Prism.languages.markup.entity, Prism.languages.markup.doctype.inside["internal-subset"].inside = Prism.languages.markup, Prism.hooks.add("wrap", (function (a) { "entity" === a.type && (a.attributes.title = a.content.replace(/&/, "&")) })), Object.defineProperty(Prism.languages.markup.tag, "addInlined", { value: function (a, e) { var s = {}; s["language-" + e] = { pattern: /(^$)/i, lookbehind: !0, inside: Prism.languages[e] }, s.cdata = /^$/i; var t = { "included-cdata": { pattern: //i, inside: s } }; t["language-" + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }; var n = {}; n[a] = { pattern: RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g, (function () { return a })), "i"), lookbehind: !0, greedy: !0, inside: t }, Prism.languages.insertBefore("markup", "cdata", n) } }), Object.defineProperty(Prism.languages.markup.tag, "addAttribute", { value: function (a, e) { Prism.languages.markup.tag.inside["special-attr"].push({ pattern: RegExp("(^|[\"'\\s])(?:" + a + ")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))", "i"), lookbehind: !0, inside: { "attr-name": /^[^\s=]+/, "attr-value": { pattern: /=[\s\S]+/, inside: { value: { pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/, lookbehind: !0, alias: [e, "language-" + e], inside: Prism.languages[e] }, punctuation: [{ pattern: /^=/, alias: "attr-equals" }, /"|'/] } } } }) } }), Prism.languages.html = Prism.languages.markup, Prism.languages.mathml = Prism.languages.markup, Prism.languages.svg = Prism.languages.markup, Prism.languages.xml = Prism.languages.extend("markup", {}), Prism.languages.ssml = Prism.languages.xml, Prism.languages.atom = Prism.languages.xml, Prism.languages.rss = Prism.languages.xml; +!function (s) { var e = /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/; s.languages.css = { comment: /\/\*[\s\S]*?\*\//, atrule: { pattern: RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|" + e.source + ")*?(?:;|(?=\\s*\\{))"), inside: { rule: /^@[\w-]+/, "selector-function-argument": { pattern: /(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/, lookbehind: !0, alias: "selector" }, keyword: { pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/, lookbehind: !0 } } }, url: { pattern: RegExp("\\burl\\((?:" + e.source + "|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)", "i"), greedy: !0, inside: { function: /^url/i, punctuation: /^\(|\)$/, string: { pattern: RegExp("^" + e.source + "$"), alias: "url" } } }, selector: { pattern: RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|" + e.source + ")*(?=\\s*\\{)"), lookbehind: !0 }, string: { pattern: e, greedy: !0 }, property: { pattern: /(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i, lookbehind: !0 }, important: /!important\b/i, function: { pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i, lookbehind: !0 }, punctuation: /[(){};:,]/ }, s.languages.css.atrule.inside.rest = s.languages.css; var t = s.languages.markup; t && (t.tag.addInlined("style", "css"), t.tag.addAttribute("style", "css")) }(Prism); +Prism.languages.clike = { comment: [{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0, greedy: !0 }, { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0, greedy: !0 }], string: { pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, greedy: !0 }, "class-name": { pattern: /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i, lookbehind: !0, inside: { punctuation: /[.\\]/ } }, keyword: /\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/, boolean: /\b(?:false|true)\b/, function: /\b\w+(?=\()/, number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i, operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, punctuation: /[{}[\];(),.:]/ }; +Prism.languages.javascript = Prism.languages.extend("clike", { "class-name": [Prism.languages.clike["class-name"], { pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/, lookbehind: !0 }], keyword: [{ pattern: /((?:^|\})\s*)catch\b/, lookbehind: !0 }, { pattern: /(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/, lookbehind: !0 }], function: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/, number: { pattern: RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"), lookbehind: !0 }, operator: /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/ }), Prism.languages.javascript["class-name"][0].pattern = /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/, Prism.languages.insertBefore("javascript", "keyword", { regex: { pattern: RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"), lookbehind: !0, greedy: !0, inside: { "regex-source": { pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/, lookbehind: !0, alias: "language-regex", inside: Prism.languages.regex }, "regex-delimiter": /^\/|\/$/, "regex-flags": /^[a-z]+$/ } }, "function-variable": { pattern: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/, alias: "function" }, parameter: [{ pattern: /(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/, lookbehind: !0, inside: Prism.languages.javascript }], constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/ }), Prism.languages.insertBefore("javascript", "string", { hashbang: { pattern: /^#!.*/, greedy: !0, alias: "comment" }, "template-string": { pattern: /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/, greedy: !0, inside: { "template-punctuation": { pattern: /^`|`$/, alias: "string" }, interpolation: { pattern: /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/, lookbehind: !0, inside: { "interpolation-punctuation": { pattern: /^\$\{|\}$/, alias: "punctuation" }, rest: Prism.languages.javascript } }, string: /[\s\S]+/ } }, "string-property": { pattern: /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m, lookbehind: !0, greedy: !0, alias: "property" } }), Prism.languages.insertBefore("javascript", "operator", { "literal-property": { pattern: /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m, lookbehind: !0, alias: "property" } }), Prism.languages.markup && (Prism.languages.markup.tag.addInlined("script", "javascript"), Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)", "javascript")), Prism.languages.js = Prism.languages.javascript; +Prism.languages.c = Prism.languages.extend("clike", { comment: { pattern: /\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/, greedy: !0 }, string: { pattern: /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/, greedy: !0 }, "class-name": { pattern: /(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/, lookbehind: !0 }, keyword: /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/, function: /\b[a-z_]\w*(?=\s*\()/i, number: /(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i, operator: />>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/ }), Prism.languages.insertBefore("c", "string", { char: { pattern: /'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/, greedy: !0 } }), Prism.languages.insertBefore("c", "string", { macro: { pattern: /(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im, lookbehind: !0, greedy: !0, alias: "property", inside: { string: [{ pattern: /^(#\s*include\s*)<[^>]+>/, lookbehind: !0 }, Prism.languages.c.string], char: Prism.languages.c.char, comment: Prism.languages.c.comment, "macro-name": [{ pattern: /(^#\s*define\s+)\w+\b(?!\()/i, lookbehind: !0 }, { pattern: /(^#\s*define\s+)\w+\b(?=\()/i, lookbehind: !0, alias: "function" }], directive: { pattern: /^(#\s*)[a-z]+/, lookbehind: !0, alias: "keyword" }, "directive-hash": /^#/, punctuation: /##|\\(?=[\r\n])/, expression: { pattern: /\S[\s\S]*/, inside: Prism.languages.c } } } }), Prism.languages.insertBefore("c", "function", { constant: /\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/ }), delete Prism.languages.c.boolean; +!function (e) { var t = /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/, n = "\\b(?!)\\w+(?:\\s*\\.\\s*\\w+)*\\b".replace(//g, (function () { return t.source })); e.languages.cpp = e.languages.extend("c", { "class-name": [{ pattern: RegExp("(\\b(?:class|concept|enum|struct|typename)\\s+)(?!)\\w+".replace(//g, (function () { return t.source }))), lookbehind: !0 }, /\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/, /\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i, /\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/], keyword: t, number: { pattern: /(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i, greedy: !0 }, operator: />>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/, boolean: /\b(?:false|true)\b/ }), e.languages.insertBefore("cpp", "string", { module: { pattern: RegExp('(\\b(?:import|module)\\s+)(?:"(?:\\\\(?:\r\n|[^])|[^"\\\\\r\n])*"|<[^<>\r\n]*>|' + "(?:\\s*:\\s*)?|:\\s*".replace(//g, (function () { return n })) + ")"), lookbehind: !0, greedy: !0, inside: { string: /^[<"][\s\S]+/, operator: /:/, punctuation: /\./ } }, "raw-string": { pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/, alias: "string", greedy: !0 } }), e.languages.insertBefore("cpp", "keyword", { "generic-function": { pattern: /\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i, inside: { function: /^\w+/, generic: { pattern: /<[\s\S]+/, alias: "class-name", inside: e.languages.cpp } } } }), e.languages.insertBefore("cpp", "operator", { "double-colon": { pattern: /::/, alias: "punctuation" } }), e.languages.insertBefore("cpp", "class-name", { "base-clause": { pattern: /(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/, lookbehind: !0, greedy: !0, inside: e.languages.extend("cpp", {}) } }), e.languages.insertBefore("inside", "double-colon", { "class-name": /\b[a-z_]\w*\b(?!\s*::)/i }, e.languages.cpp["base-clause"]) }(Prism); +Prism.languages.go = Prism.languages.extend("clike", { string: { pattern: /(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/, lookbehind: !0, greedy: !0 }, keyword: /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/, boolean: /\b(?:_|false|iota|nil|true)\b/, number: [/\b0(?:b[01_]+|o[0-7_]+)i?\b/i, /\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i, /(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i], operator: /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./, builtin: /\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/ }), Prism.languages.insertBefore("go", "string", { char: { pattern: /'(?:\\.|[^'\\\r\n]){0,10}'/, greedy: !0 } }), delete Prism.languages.go["class-name"]; +!function (e) { var n = /\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/, t = "(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*", s = { pattern: RegExp("(^|[^\\w.])" + t + "[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b"), lookbehind: !0, inside: { namespace: { pattern: /^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/, inside: { punctuation: /\./ } }, punctuation: /\./ } }; e.languages.java = e.languages.extend("clike", { string: { pattern: /(^|[^\\])"(?:\\.|[^"\\\r\n])*"/, lookbehind: !0, greedy: !0 }, "class-name": [s, { pattern: RegExp("(^|[^\\w.])" + t + "[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)"), lookbehind: !0, inside: s.inside }, { pattern: RegExp("(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)" + t + "[A-Z]\\w*\\b"), lookbehind: !0, inside: s.inside }], keyword: n, function: [e.languages.clike.function, { pattern: /(::\s*)[a-z_]\w*/, lookbehind: !0 }], number: /\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i, operator: { pattern: /(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m, lookbehind: !0 }, constant: /\b[A-Z][A-Z_\d]+\b/ }), e.languages.insertBefore("java", "string", { "triple-quoted-string": { pattern: /"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/, greedy: !0, alias: "string" }, char: { pattern: /'(?:\\.|[^'\\\r\n]){1,6}'/, greedy: !0 } }), e.languages.insertBefore("java", "class-name", { annotation: { pattern: /(^|[^.])@\w+(?:\s*\.\s*\w+)*/, lookbehind: !0, alias: "punctuation" }, generics: { pattern: /<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/, inside: { "class-name": s, keyword: n, punctuation: /[<>(),.:]/, operator: /[?&|]/ } }, import: [{ pattern: RegExp("(\\bimport\\s+)" + t + "(?:[A-Z]\\w*|\\*)(?=\\s*;)"), lookbehind: !0, inside: { namespace: s.inside.namespace, punctuation: /\./, operator: /\*/, "class-name": /\w+/ } }, { pattern: RegExp("(\\bimport\\s+static\\s+)" + t + "(?:\\w+|\\*)(?=\\s*;)"), lookbehind: !0, alias: "static", inside: { namespace: s.inside.namespace, static: /\b\w+$/, punctuation: /\./, operator: /\*/, "class-name": /\w+/ } }], namespace: { pattern: RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(//g, (function () { return n.source }))), lookbehind: !0, inside: { punctuation: /\./ } } }) }(Prism); +Prism.languages.python = { comment: { pattern: /(^|[^\\])#.*/, lookbehind: !0, greedy: !0 }, "string-interpolation": { pattern: /(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i, greedy: !0, inside: { interpolation: { pattern: /((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/, lookbehind: !0, inside: { "format-spec": { pattern: /(:)[^:(){}]+(?=\}$)/, lookbehind: !0 }, "conversion-option": { pattern: /![sra](?=[:}]$)/, alias: "punctuation" }, rest: null } }, string: /[\s\S]+/ } }, "triple-quoted-string": { pattern: /(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i, greedy: !0, alias: "string" }, string: { pattern: /(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i, greedy: !0 }, function: { pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g, lookbehind: !0 }, "class-name": { pattern: /(\bclass\s+)\w+/i, lookbehind: !0 }, decorator: { pattern: /(^[\t ]*)@\w+(?:\.\w+)*/m, lookbehind: !0, alias: ["annotation", "punctuation"], inside: { punctuation: /\./ } }, keyword: /\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/, builtin: /\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/, boolean: /\b(?:False|None|True)\b/, number: /\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i, operator: /[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/, punctuation: /[{}[\];(),.:]/ }, Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest = Prism.languages.python, Prism.languages.py = Prism.languages.python; +!function (e) { e.languages.ruby = e.languages.extend("clike", { comment: { pattern: /#.*|^=begin\s[\s\S]*?^=end/m, greedy: !0 }, "class-name": { pattern: /(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/, lookbehind: !0, inside: { punctuation: /[.\\]/ } }, keyword: /\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/, operator: /\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/, punctuation: /[(){}[\].,;]/ }), e.languages.insertBefore("ruby", "operator", { "double-colon": { pattern: /::/, alias: "punctuation" } }); var n = { pattern: /((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/, lookbehind: !0, inside: { content: { pattern: /^(#\{)[\s\S]+(?=\}$)/, lookbehind: !0, inside: e.languages.ruby }, delimiter: { pattern: /^#\{|\}$/, alias: "punctuation" } } }; delete e.languages.ruby.function; var t = "(?:" + ["([^a-zA-Z0-9\\s{(\\[<=])(?:(?!\\1)[^\\\\]|\\\\[^])*\\1", "\\((?:[^()\\\\]|\\\\[^]|\\((?:[^()\\\\]|\\\\[^])*\\))*\\)", "\\{(?:[^{}\\\\]|\\\\[^]|\\{(?:[^{}\\\\]|\\\\[^])*\\})*\\}", "\\[(?:[^\\[\\]\\\\]|\\\\[^]|\\[(?:[^\\[\\]\\\\]|\\\\[^])*\\])*\\]", "<(?:[^<>\\\\]|\\\\[^]|<(?:[^<>\\\\]|\\\\[^])*>)*>"].join("|") + ")", i = '(?:"(?:\\\\.|[^"\\\\\r\n])*"|(?:\\b[a-zA-Z_]\\w*|[^\\s\0-\\x7F]+)[?!]?|\\$.)'; e.languages.insertBefore("ruby", "keyword", { "regex-literal": [{ pattern: RegExp("%r" + t + "[egimnosux]{0,6}"), greedy: !0, inside: { interpolation: n, regex: /[\s\S]+/ } }, { pattern: /(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/, lookbehind: !0, greedy: !0, inside: { interpolation: n, regex: /[\s\S]+/ } }], variable: /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/, symbol: [{ pattern: RegExp("(^|[^:]):" + i), lookbehind: !0, greedy: !0 }, { pattern: RegExp("([\r\n{(,][ \t]*)" + i + "(?=:(?!:))"), lookbehind: !0, greedy: !0 }], "method-definition": { pattern: /(\bdef\s+)\w+(?:\s*\.\s*\w+)?/, lookbehind: !0, inside: { function: /\b\w+$/, keyword: /^self\b/, "class-name": /^\w+/, punctuation: /\./ } } }), e.languages.insertBefore("ruby", "string", { "string-literal": [{ pattern: RegExp("%[qQiIwWs]?" + t), greedy: !0, inside: { interpolation: n, string: /[\s\S]+/ } }, { pattern: /("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/, greedy: !0, inside: { interpolation: n, string: /[\s\S]+/ } }, { pattern: /<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i, alias: "heredoc-string", greedy: !0, inside: { delimiter: { pattern: /^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i, inside: { symbol: /\b\w+/, punctuation: /^<<[-~]?/ } }, interpolation: n, string: /[\s\S]+/ } }, { pattern: /<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i, alias: "heredoc-string", greedy: !0, inside: { delimiter: { pattern: /^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i, inside: { symbol: /\b\w+/, punctuation: /^<<[-~]?'|'$/ } }, string: /[\s\S]+/ } }], "command-literal": [{ pattern: RegExp("%x" + t), greedy: !0, inside: { interpolation: n, command: { pattern: /[\s\S]+/, alias: "string" } } }, { pattern: /`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/, greedy: !0, inside: { interpolation: n, command: { pattern: /[\s\S]+/, alias: "string" } } }] }), delete e.languages.ruby.string, e.languages.insertBefore("ruby", "number", { builtin: /\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/, constant: /\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/ }), e.languages.rb = e.languages.ruby }(Prism); +!function (e) { for (var a = "/\\*(?:[^*/]|\\*(?!/)|/(?!\\*)|)*\\*/", t = 0; t < 2; t++)a = a.replace(//g, (function () { return a })); a = a.replace(//g, (function () { return "[^\\s\\S]" })), e.languages.rust = { comment: [{ pattern: RegExp("(^|[^\\\\])" + a), lookbehind: !0, greedy: !0 }, { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0, greedy: !0 }], string: { pattern: /b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/, greedy: !0 }, char: { pattern: /b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/, greedy: !0 }, attribute: { pattern: /#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/, greedy: !0, alias: "attr-name", inside: { string: null } }, "closure-params": { pattern: /([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/, lookbehind: !0, greedy: !0, inside: { "closure-punctuation": { pattern: /^\||\|$/, alias: "punctuation" }, rest: null } }, "lifetime-annotation": { pattern: /'\w+/, alias: "symbol" }, "fragment-specifier": { pattern: /(\$\w+:)[a-z]+/, lookbehind: !0, alias: "punctuation" }, variable: /\$\w+/, "function-definition": { pattern: /(\bfn\s+)\w+/, lookbehind: !0, alias: "function" }, "type-definition": { pattern: /(\b(?:enum|struct|trait|type|union)\s+)\w+/, lookbehind: !0, alias: "class-name" }, "module-declaration": [{ pattern: /(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/, lookbehind: !0, alias: "namespace" }, { pattern: /(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/, lookbehind: !0, alias: "namespace", inside: { punctuation: /::/ } }], keyword: [/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/, /\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/], function: /\b[a-z_]\w*(?=\s*(?:::\s*<|\())/, macro: { pattern: /\b\w+!/, alias: "property" }, constant: /\b[A-Z_][A-Z_\d]+\b/, "class-name": /\b[A-Z]\w*\b/, namespace: { pattern: /(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/, inside: { punctuation: /::/ } }, number: /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/, boolean: /\b(?:false|true)\b/, punctuation: /->|\.\.=|\.{1,3}|::|[{}[\];(),:]/, operator: /[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/ }, e.languages.rust["closure-params"].inside.rest = e.languages.rust, e.languages.rust.attribute.inside.string = e.languages.rust.string }(Prism); +!function (e) { e.languages.typescript = e.languages.extend("javascript", { "class-name": { pattern: /(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/, lookbehind: !0, greedy: !0, inside: null }, builtin: /\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/ }), e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/, /\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/, /\btype\b(?=\s*(?:[\{*]|$))/), delete e.languages.typescript.parameter, delete e.languages.typescript["literal-property"]; var s = e.languages.extend("typescript", {}); delete s["class-name"], e.languages.typescript["class-name"].inside = s, e.languages.insertBefore("typescript", "function", { decorator: { pattern: /@[$\w\xA0-\uFFFF]+/, inside: { at: { pattern: /^@/, alias: "operator" }, function: /^[\s\S]+/ } }, "generic-function": { pattern: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/, greedy: !0, inside: { function: /^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/, generic: { pattern: /<[\s\S]+/, alias: "class-name", inside: s } } } }), e.languages.ts = e.languages.typescript }(Prism); +!function (E) { var n = /\b(?:ACT|ACTIFSUB|CARRAY|CASE|CLEARGIF|COA|COA_INT|CONSTANTS|CONTENT|CUR|EDITPANEL|EFFECT|EXT|FILE|FLUIDTEMPLATE|FORM|FRAME|FRAMESET|GIFBUILDER|GMENU|GMENU_FOLDOUT|GMENU_LAYERS|GP|HMENU|HRULER|HTML|IENV|IFSUB|IMAGE|IMGMENU|IMGMENUITEM|IMGTEXT|IMG_RESOURCE|INCLUDE_TYPOSCRIPT|JSMENU|JSMENUITEM|LLL|LOAD_REGISTER|NO|PAGE|RECORDS|RESTORE_REGISTER|TEMPLATE|TEXT|TMENU|TMENUITEM|TMENU_LAYERS|USER|USER_INT|_GIFBUILDER|global|globalString|globalVar)\b/; E.languages.typoscript = { comment: [{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0 }, { pattern: /(^|[^\\:= \t]|(?:^|[^= \t])[ \t]+)\/\/.*/, lookbehind: !0, greedy: !0 }, { pattern: /(^|[^"'])#.*/, lookbehind: !0, greedy: !0 }], function: [{ pattern: //, inside: { string: { pattern: /"[^"\r\n]*"|'[^'\r\n]*'/, inside: { keyword: n } }, keyword: { pattern: /INCLUDE_TYPOSCRIPT/ } } }, { pattern: /@import\s*(?:"[^"\r\n]*"|'[^'\r\n]*')/, inside: { string: /"[^"\r\n]*"|'[^'\r\n]*'/ } }], string: { pattern: /^([^=]*=[< ]?)(?:(?!\]\n).)*/, lookbehind: !0, inside: { function: /\{\$.*\}/, keyword: n, number: /^\d+$/, punctuation: /[,|:]/ } }, keyword: n, number: { pattern: /\b\d+\s*[.{=]/, inside: { operator: /[.{=]/ } }, tag: { pattern: /\.?[-\w\\]+\.?/, inside: { punctuation: /\./ } }, punctuation: /[{}[\];(),.:|]/, operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/ }, E.languages.tsconfig = E.languages.typoscript }(Prism); From 710fd91fd26091b7acb6f815d4bce34a0ae24bc0 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Thu, 26 Jun 2025 02:30:39 +0530 Subject: [PATCH 3/8] chore: resources is added on manifest file --- frontend/manifest.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/manifest.json b/frontend/manifest.json index 5665df8..c0b6713 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "CodeTranslateAI", - "version": "1.0.1", + "version": "1.1.0", "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", "author": "Dinesh Kumar Sutihar", "permissions": ["activeTab", "storage"], @@ -31,5 +31,11 @@ ], "background": { "service_worker": "background.js" - } + }, + "web_accessible_resources": [ + { + "resources": ["packages/prism/prism.js", "packages/prism/prism.css"], + "matches": [""] + } + ] } From 846a1d59a4d22c8fe5738b859150865484e95656 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Thu, 26 Jun 2025 13:12:02 +0530 Subject: [PATCH 4/8] feat: adds color feature and bit bug fix --- frontend/content.js | 159 ++++++++++++++++++++--------------------- frontend/manifest.json | 4 +- 2 files changed, 79 insertions(+), 84 deletions(-) diff --git a/frontend/content.js b/frontend/content.js index 7683219..dee0b9e 100644 --- a/frontend/content.js +++ b/frontend/content.js @@ -5,6 +5,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "ENABLE_PICKER") { if (!isPickerEnabled) enablePicker(); } + return true; }); function enablePicker() { @@ -37,39 +38,41 @@ function onMouseOut(e) { async function onClick(e) { e.preventDefault(); e.stopPropagation(); - const clickedElement = e.target; - const selectedCode = clickedElement.textContent; + const selectedCode = clickedElement.textContent.trim(); + if (!selectedCode) { + disablePicker(); + return; + } const cacheKey = `translation_${hashCode(selectedCode)}`; const originalWidth = clickedElement.getBoundingClientRect().width; disablePicker(); - const { targetLanguage } = await chrome.storage.sync.get('targetLanguage'); const lang = targetLanguage || 'Java'; - const cachedData = await getFromCache(cacheKey); if (cachedData && cachedData[lang]) { injectOrUpdateTranslations(cachedData, clickedElement, originalWidth); return; } - const loadingDiv = document.createElement('div'); loadingDiv.className = 'translator-loading'; loadingDiv.textContent = `Translating to ${lang}...`; + loadingDiv.style.width = `${originalWidth}px`; clickedElement.parentNode.insertBefore(loadingDiv, clickedElement.nextSibling); - chrome.runtime.sendMessage({ type: "TRANSLATE_CODE", code: selectedCode }, async (response) => { loadingDiv.remove(); - + if (chrome.runtime.lastError) { + console.error('CodeTranslateAI: Message port closed unexpectedly.', chrome.runtime.lastError.message); + alert('Error: Could not connect to the translation service.'); + return; + } if (response.error) { alert(`Error: ${response.error}`); } else if (response.translation) { const cleanedTranslation = response.translation.replace(/```[a-z]*\n/g, '').replace(/```/g, '').trim(); - const newData = cachedData || {}; newData[lang] = cleanedTranslation; await saveToCache(cacheKey, newData, 10); - injectOrUpdateTranslations(newData, clickedElement, originalWidth); } }); @@ -77,66 +80,66 @@ async function onClick(e) { function injectOrUpdateTranslations(translations, originalElement, width) { const componentStyles = ` - .tab-nav { - display: flex; - border-bottom: 1px solid #ccc; - background-color: #f0f0f0; - } - .tab-link { - padding: 10px 15px; - cursor: pointer; - border: none; - background-color: transparent; - font-size: 1em; - font-weight: 500; - color: #333; - border-bottom: 3px solid transparent; + .tab-nav { + display: flex; + border-bottom: 1px solid #ccc; + background-color: #f0f0f0; } - .tab-link:hover { - background-color: #e5e5e5; + .tab-link { + padding: 10px 15px; + cursor: pointer; + border: none; + background-color: transparent; + font-size: 1em; + /* --- FIX 1: Make inactive tabs more visible --- */ + font-weight: 500; + color: #555; + border-bottom: 3px solid transparent; } - .tab-link.active { - color: #007bff; - border-bottom: 3px solid #007bff; + .tab-link:hover { + background-color: #e5e5e5; } - .tab-content-area { - background-color: #fff; + .tab-link.active { + color: #007bff; + font-weight: 600; /* Make active tab slightly bolder */ + border-bottom: 3px solid #007bff; } - .tab-content { - display: none; + .tab-content { + display: none; } - .tab-content.active { - display: block; + .tab-content.active { + display: block; } - pre { - margin: 0; - padding: 16px; - white-space: pre-wrap; - word-wrap: break-word; - background-color: #f6f8fa; - border-top: 1px solid #ddd; - margin-bottom: 10px; + pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; } - code { - font-family: monospace; - font-size: 14px; - color: #24292e; - line-height: 1; + code { + font-family: monospace; + /* --- FIX 2: Made the code font size smaller --- */ + font-size: 0.8em; } `; + // The rest of the function remains exactly the same... let container = originalElement.nextElementSibling; if (!container || container.id !== 'my-code-translator-container') { container = document.createElement('div'); container.id = 'my-code-translator-container'; - const shadowRoot = container.attachShadow({ mode: 'open' }); + const prismTheme = document.createElement('link'); + prismTheme.rel = 'stylesheet'; + prismTheme.href = chrome.runtime.getURL('packages/prism.css'); + shadowRoot.appendChild(prismTheme); + const styleElement = document.createElement('style'); styleElement.textContent = componentStyles; shadowRoot.appendChild(styleElement); const uiWrapper = document.createElement('div'); + uiWrapper.className = 'ui-wrapper'; shadowRoot.appendChild(uiWrapper); originalElement.parentNode.insertBefore(container, originalElement.nextSibling); @@ -144,50 +147,51 @@ function injectOrUpdateTranslations(translations, originalElement, width) { container.style.width = `${width}px`; container.style.boxSizing = 'border-box'; - const shadowRoot = container.shadowRoot; - const uiWrapper = shadowRoot.querySelector('div'); + const uiWrapper = shadowRoot.querySelector('.ui-wrapper'); uiWrapper.innerHTML = ''; - const tabNav = document.createElement('div'); tabNav.className = 'tab-nav'; - const contentArea = document.createElement('div'); contentArea.className = 'tab-content-area'; - uiWrapper.appendChild(tabNav); uiWrapper.appendChild(contentArea); - - Object.keys(translations).forEach((lang, index) => { - const tabButton = document.createElement('button'); - tabButton.className = 'tab-link'; - tabButton.textContent = lang; - + Object.keys(translations).forEach(lang => { const contentPanel = document.createElement('div'); contentPanel.className = 'tab-content'; - + contentPanel.dataset.lang = lang; + const langClass = `language-${lang.toLowerCase()}`; const pre = document.createElement('pre'); + pre.className = langClass; const code = document.createElement('code'); + code.className = langClass; code.textContent = translations[lang]; pre.appendChild(code); contentPanel.appendChild(pre); - - tabNav.appendChild(tabButton); contentArea.appendChild(contentPanel); - - if (index === 0) { - tabButton.classList.add('active'); - contentPanel.classList.add('active'); - } - + }); + Object.keys(translations).forEach((lang, index) => { + const tabButton = document.createElement('button'); + tabButton.className = 'tab-link'; + tabButton.textContent = lang; tabButton.addEventListener('click', () => { shadowRoot.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); shadowRoot.querySelectorAll('.tab-content').forEach(panel => panel.classList.remove('active')); - tabButton.classList.add('active'); - contentPanel.classList.add('active'); + shadowRoot.querySelector(`.tab-content[data-lang="${lang}"]`).classList.add('active'); }); + tabNav.appendChild(tabButton); + if (index === 0) { + tabButton.click(); + } }); + try { + if (window.Prism) { + contentArea.querySelectorAll(`pre[class*="language-"]`).forEach(element => window.Prism.highlightElement(element)); + } + } catch (e) { + console.error('CodeTranslateAI: Error highlighting syntax.', e); + } } function hashCode(str) { @@ -202,25 +206,16 @@ function hashCode(str) { async function saveToCache(key, data, daysToExpire) { const expirationMs = daysToExpire * 24 * 60 * 60 * 1000; - const cacheItem = { - data: data, - expiresAt: Date.now() + expirationMs, - }; + const cacheItem = { data: data, expiresAt: Date.now() + expirationMs }; await chrome.storage.local.set({ [key]: cacheItem }); } async function getFromCache(key) { const result = await chrome.storage.local.get(key); const cacheItem = result[key]; - - if (!cacheItem) { - return null; - } - - if (Date.now() > cacheItem.expiresAt) { - await chrome.storage.local.remove(key); + if (!cacheItem || Date.now() > cacheItem.expiresAt) { + if (cacheItem) await chrome.storage.local.remove(key); return null; } - return cacheItem.data; } \ No newline at end of file diff --git a/frontend/manifest.json b/frontend/manifest.json index c0b6713..e298f90 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -25,7 +25,7 @@ "content_scripts": [ { "matches": [""], - "js": ["content.js"], + "js": ["packages/prism.js", "content.js"], "css": ["styles.css"] } ], @@ -34,7 +34,7 @@ }, "web_accessible_resources": [ { - "resources": ["packages/prism/prism.js", "packages/prism/prism.css"], + "resources": ["packages/prism.css"], "matches": [""] } ] From e1786d339ed691790044b34202f0b78691aa9107 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Fri, 27 Jun 2025 13:39:08 +0530 Subject: [PATCH 5/8] buld: makes code modular and adds esbuild for building project --- frontend/content.js | 221 ----------------- frontend/manifest.json | 7 +- frontend/package-lock.json | 469 ++++++++++++++++++++++++++++++++++++ frontend/package.json | 12 + frontend/scripts/cache.js | 25 ++ frontend/scripts/content.js | 57 +++++ frontend/scripts/picker.js | 35 +++ frontend/scripts/ui.js | 110 +++++++++ 8 files changed, 712 insertions(+), 224 deletions(-) delete mode 100644 frontend/content.js create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/scripts/cache.js create mode 100644 frontend/scripts/content.js create mode 100644 frontend/scripts/picker.js create mode 100644 frontend/scripts/ui.js diff --git a/frontend/content.js b/frontend/content.js deleted file mode 100644 index dee0b9e..0000000 --- a/frontend/content.js +++ /dev/null @@ -1,221 +0,0 @@ -let isPickerEnabled = false; -let hoveredElement = null; - -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === "ENABLE_PICKER") { - if (!isPickerEnabled) enablePicker(); - } - return true; -}); - -function enablePicker() { - isPickerEnabled = true; - document.body.style.cursor = 'crosshair'; - document.addEventListener('mouseover', onMouseOver); - document.addEventListener('mouseout', onMouseOut); - document.addEventListener('click', onClick, true); -} - -function disablePicker() { - isPickerEnabled = false; - document.body.style.cursor = 'default'; - if (hoveredElement) hoveredElement.classList.remove('translator-highlight'); - document.removeEventListener('mouseover', onMouseOver); - document.removeEventListener('mouseout', onMouseOut); - document.removeEventListener('click', onClick, true); -} - -function onMouseOver(e) { - hoveredElement = e.target; - hoveredElement.classList.add('translator-highlight'); -} - -function onMouseOut(e) { - e.target.classList.remove('translator-highlight'); - hoveredElement = null; -} - -async function onClick(e) { - e.preventDefault(); - e.stopPropagation(); - const clickedElement = e.target; - const selectedCode = clickedElement.textContent.trim(); - if (!selectedCode) { - disablePicker(); - return; - } - const cacheKey = `translation_${hashCode(selectedCode)}`; - const originalWidth = clickedElement.getBoundingClientRect().width; - disablePicker(); - const { targetLanguage } = await chrome.storage.sync.get('targetLanguage'); - const lang = targetLanguage || 'Java'; - const cachedData = await getFromCache(cacheKey); - if (cachedData && cachedData[lang]) { - injectOrUpdateTranslations(cachedData, clickedElement, originalWidth); - return; - } - const loadingDiv = document.createElement('div'); - loadingDiv.className = 'translator-loading'; - loadingDiv.textContent = `Translating to ${lang}...`; - loadingDiv.style.width = `${originalWidth}px`; - clickedElement.parentNode.insertBefore(loadingDiv, clickedElement.nextSibling); - chrome.runtime.sendMessage({ type: "TRANSLATE_CODE", code: selectedCode }, async (response) => { - loadingDiv.remove(); - if (chrome.runtime.lastError) { - console.error('CodeTranslateAI: Message port closed unexpectedly.', chrome.runtime.lastError.message); - alert('Error: Could not connect to the translation service.'); - return; - } - if (response.error) { - alert(`Error: ${response.error}`); - } else if (response.translation) { - const cleanedTranslation = response.translation.replace(/```[a-z]*\n/g, '').replace(/```/g, '').trim(); - const newData = cachedData || {}; - newData[lang] = cleanedTranslation; - await saveToCache(cacheKey, newData, 10); - injectOrUpdateTranslations(newData, clickedElement, originalWidth); - } - }); -} - -function injectOrUpdateTranslations(translations, originalElement, width) { - const componentStyles = ` - .tab-nav { - display: flex; - border-bottom: 1px solid #ccc; - background-color: #f0f0f0; - } - .tab-link { - padding: 10px 15px; - cursor: pointer; - border: none; - background-color: transparent; - font-size: 1em; - /* --- FIX 1: Make inactive tabs more visible --- */ - font-weight: 500; - color: #555; - border-bottom: 3px solid transparent; - } - .tab-link:hover { - background-color: #e5e5e5; - } - .tab-link.active { - color: #007bff; - font-weight: 600; /* Make active tab slightly bolder */ - border-bottom: 3px solid #007bff; - } - .tab-content { - display: none; - } - .tab-content.active { - display: block; - } - pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; - } - code { - font-family: monospace; - /* --- FIX 2: Made the code font size smaller --- */ - font-size: 0.8em; - } - `; - - // The rest of the function remains exactly the same... - let container = originalElement.nextElementSibling; - if (!container || container.id !== 'my-code-translator-container') { - container = document.createElement('div'); - container.id = 'my-code-translator-container'; - const shadowRoot = container.attachShadow({ mode: 'open' }); - - const prismTheme = document.createElement('link'); - prismTheme.rel = 'stylesheet'; - prismTheme.href = chrome.runtime.getURL('packages/prism.css'); - shadowRoot.appendChild(prismTheme); - - const styleElement = document.createElement('style'); - styleElement.textContent = componentStyles; - shadowRoot.appendChild(styleElement); - - const uiWrapper = document.createElement('div'); - uiWrapper.className = 'ui-wrapper'; - shadowRoot.appendChild(uiWrapper); - - originalElement.parentNode.insertBefore(container, originalElement.nextSibling); - } - - container.style.width = `${width}px`; - container.style.boxSizing = 'border-box'; - const shadowRoot = container.shadowRoot; - const uiWrapper = shadowRoot.querySelector('.ui-wrapper'); - uiWrapper.innerHTML = ''; - const tabNav = document.createElement('div'); - tabNav.className = 'tab-nav'; - const contentArea = document.createElement('div'); - contentArea.className = 'tab-content-area'; - uiWrapper.appendChild(tabNav); - uiWrapper.appendChild(contentArea); - Object.keys(translations).forEach(lang => { - const contentPanel = document.createElement('div'); - contentPanel.className = 'tab-content'; - contentPanel.dataset.lang = lang; - const langClass = `language-${lang.toLowerCase()}`; - const pre = document.createElement('pre'); - pre.className = langClass; - const code = document.createElement('code'); - code.className = langClass; - code.textContent = translations[lang]; - pre.appendChild(code); - contentPanel.appendChild(pre); - contentArea.appendChild(contentPanel); - }); - Object.keys(translations).forEach((lang, index) => { - const tabButton = document.createElement('button'); - tabButton.className = 'tab-link'; - tabButton.textContent = lang; - tabButton.addEventListener('click', () => { - shadowRoot.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); - shadowRoot.querySelectorAll('.tab-content').forEach(panel => panel.classList.remove('active')); - tabButton.classList.add('active'); - shadowRoot.querySelector(`.tab-content[data-lang="${lang}"]`).classList.add('active'); - }); - tabNav.appendChild(tabButton); - if (index === 0) { - tabButton.click(); - } - }); - try { - if (window.Prism) { - contentArea.querySelectorAll(`pre[class*="language-"]`).forEach(element => window.Prism.highlightElement(element)); - } - } catch (e) { - console.error('CodeTranslateAI: Error highlighting syntax.', e); - } -} - -function hashCode(str) { - let hash = 0; - for (let i = 0, len = str.length; i < len; i++) { - let chr = str.charCodeAt(i); - hash = (hash << 5) - hash + chr; - hash |= 0; - } - return hash; -} - -async function saveToCache(key, data, daysToExpire) { - const expirationMs = daysToExpire * 24 * 60 * 60 * 1000; - const cacheItem = { data: data, expiresAt: Date.now() + expirationMs }; - await chrome.storage.local.set({ [key]: cacheItem }); -} - -async function getFromCache(key) { - const result = await chrome.storage.local.get(key); - const cacheItem = result[key]; - if (!cacheItem || Date.now() > cacheItem.expiresAt) { - if (cacheItem) await chrome.storage.local.remove(key); - return null; - } - return cacheItem.data; -} \ No newline at end of file diff --git a/frontend/manifest.json b/frontend/manifest.json index e298f90..a126805 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "CodeTranslateAI", "version": "1.1.0", - "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", + "description": "Select code on a page and get instant AI-powered translation.", "author": "Dinesh Kumar Sutihar", "permissions": ["activeTab", "storage"], "host_permissions": ["https://*.workers.dev/"], @@ -25,12 +25,13 @@ "content_scripts": [ { "matches": [""], - "js": ["packages/prism.js", "content.js"], + "js": ["packages/prism.js", "dist/content.bundle.js"], "css": ["styles.css"] } ], "background": { - "service_worker": "background.js" + "service_worker": "background.js", + "type": "module" }, "web_accessible_resources": [ { diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..2587179 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,469 @@ +{ + "name": "frontend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "esbuild": "^0.25.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..ec2e9f9 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,12 @@ +{ + "name": "code-translate-ai", + "version": "1.2.0", + "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", + "author": "Dinesh Kumar Sutihar", + "scripts": { + "build": "esbuild scripts/content.js --bundle --outfile=dist/content.bundle.js" + }, + "devDependencies": { + "esbuild": "^0.25.8" + } +} diff --git a/frontend/scripts/cache.js b/frontend/scripts/cache.js new file mode 100644 index 0000000..a0cc5f8 --- /dev/null +++ b/frontend/scripts/cache.js @@ -0,0 +1,25 @@ +export function hashCode(str) { + let hash = 0; + for (let i = 0, len = str.length; i < len; i++) { + let chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; + } + return hash; +} + +export async function saveToCache(key, data, daysToExpire) { + const expirationMs = daysToExpire * 24 * 60 * 60 * 1000; + const cacheItem = { data: data, expiresAt: Date.now() + expirationMs }; + await chrome.storage.local.set({ [key]: cacheItem }); +} + +export async function getFromCache(key) { + const result = await chrome.storage.local.get(key); + const cacheItem = result[key]; + if (!cacheItem || Date.now() > cacheItem.expiresAt) { + if (cacheItem) await chrome.storage.local.remove(key); + return null; + } + return cacheItem.data; +} \ No newline at end of file diff --git a/frontend/scripts/content.js b/frontend/scripts/content.js new file mode 100644 index 0000000..709555f --- /dev/null +++ b/frontend/scripts/content.js @@ -0,0 +1,57 @@ +import { enablePicker } from './picker.js'; +import { hashCode, getFromCache, saveToCache } from './cache.js'; +import { injectOrUpdateTranslations } from './ui.js'; + +chrome.runtime.onMessage.addListener((message) => { + + if (message.type === "ENABLE_PICKER") { + enablePicker(handleElementClick); + } + return true; +}); + +async function handleElementClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const clickedElement = e.target; + const selectedCode = clickedElement.textContent?.trim(); + + if (!selectedCode) return; + + const cacheKey = `translation_${hashCode(selectedCode)}`; + const originalWidth = clickedElement.getBoundingClientRect().width; + const { targetLanguage } = await chrome.storage.sync.get('targetLanguage'); + const lang = targetLanguage || 'Java'; + const cachedData = await getFromCache(cacheKey); + + if (cachedData && cachedData[lang]) { + injectOrUpdateTranslations(cachedData, clickedElement, originalWidth); + return; + } + + const loadingDiv = document.createElement('div'); + loadingDiv.className = 'translator-loading'; + loadingDiv.textContent = `Translating to ${lang}...`; + loadingDiv.style.width = `${originalWidth}px`; + clickedElement.parentNode.insertBefore(loadingDiv, clickedElement.nextSibling); + + chrome.runtime.sendMessage({ type: "TRANSLATE_CODE", code: selectedCode }, async (response) => { + loadingDiv.remove(); + if (chrome.runtime.lastError || !response) { + alert(`Error: Could not connect to the translation service.`); + console.error(chrome.runtime.lastError?.message); + return; + } + + if (response.error) { + alert(`Error: ${response.error}`); + } else if (response.translation) { + const cleaned = response.translation.replace(/```[a-z]*\n/g, '').replace(/```/g, '').trim(); + const newData = cachedData || {}; + newData[lang] = cleaned; + await saveToCache(cacheKey, newData, 10); + injectOrUpdateTranslations(newData, clickedElement, originalWidth); + } + }); +} \ No newline at end of file diff --git a/frontend/scripts/picker.js b/frontend/scripts/picker.js new file mode 100644 index 0000000..bfd9012 --- /dev/null +++ b/frontend/scripts/picker.js @@ -0,0 +1,35 @@ +let hoveredElement = null; +let currentClickHandler = null; + +function onMouseOver(e) { + hoveredElement = e.target; + hoveredElement.classList.add('translator-highlight'); +} + +function onMouseOut(e) { + e.target.classList.remove('translator-highlight'); + hoveredElement = null; +} + +function disablePicker() { + document.body.style.cursor = 'default'; + if (hoveredElement) { + hoveredElement.classList.remove('translator-highlight'); + } + document.removeEventListener('mouseover', onMouseOver); + document.removeEventListener('mouseout', onMouseOut); + if (currentClickHandler) { + document.removeEventListener('click', currentClickHandler, true); + } +} + +export function enablePicker(onClickCallback) { + document.body.style.cursor = 'crosshair'; + currentClickHandler = (e) => { + disablePicker(); + onClickCallback(e); + }; + document.addEventListener('mouseover', onMouseOver); + document.addEventListener('mouseout', onMouseOut); + document.addEventListener('click', currentClickHandler, true); +} \ No newline at end of file diff --git a/frontend/scripts/ui.js b/frontend/scripts/ui.js new file mode 100644 index 0000000..e01a36c --- /dev/null +++ b/frontend/scripts/ui.js @@ -0,0 +1,110 @@ +export function injectOrUpdateTranslations(translations, originalElement, width) { + const componentStyles = ` + .tab-nav { + display: flex; + border-bottom: 1px solid #ccc; + background-color: #f0f0f0; + } + .tab-link { + padding: 10px 15px; + cursor: pointer; + border: none; + background-color: transparent; + font-size: 1em; + font-weight: 500; + color: #555; + border-bottom: 3px solid transparent; + } + .tab-link:hover { + background-color: #e5e5e5; + } + .tab-link.active { + color: #007bff; + font-weight: 600; + border-bottom: 3px solid #007bff; + } + .tab-content { + display: none; + } + .tab-content.active { + display: block; + } + pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; + } + code { + font-family: monospace; + font-size: 0.8em; + } + `; + + let container = originalElement.nextElementSibling; + + if (!container || container.id !== 'my-code-translator-container') { + container = document.createElement('div'); + container.id = 'my-code-translator-container'; + const shadowRoot = container.attachShadow({ mode: 'open' }); + const prismTheme = document.createElement('link'); + prismTheme.rel = 'stylesheet'; + prismTheme.href = chrome.runtime.getURL('packages/prism.css'); + shadowRoot.appendChild(prismTheme); + const styleElement = document.createElement('style'); + styleElement.textContent = componentStyles; + shadowRoot.appendChild(styleElement); + const uiWrapper = document.createElement('div'); + uiWrapper.className = 'ui-wrapper'; + shadowRoot.appendChild(uiWrapper); + originalElement.parentNode.insertBefore(container, originalElement.nextSibling); + } + + container.style.width = `${width}px`; + container.style.boxSizing = 'border-box'; + const shadowRoot = container.shadowRoot; + const uiWrapper = shadowRoot.querySelector('.ui-wrapper'); + uiWrapper.innerHTML = ''; + const tabNav = document.createElement('div'); + tabNav.className = 'tab-nav'; + const contentArea = document.createElement('div'); + contentArea.className = 'tab-content-area'; + uiWrapper.appendChild(tabNav); + uiWrapper.appendChild(contentArea); + Object.keys(translations).forEach(lang => { + const contentPanel = document.createElement('div'); + contentPanel.className = 'tab-content'; + contentPanel.dataset.lang = lang; + const langClass = `language-${lang.toLowerCase()}`; + const pre = document.createElement('pre'); + pre.className = langClass; + const code = document.createElement('code'); + code.className = langClass; + code.textContent = translations[lang]; + pre.appendChild(code); + contentPanel.appendChild(pre); + contentArea.appendChild(contentPanel); + }); + + Object.keys(translations).forEach((lang, index) => { + const tabButton = document.createElement('button'); + tabButton.className = 'tab-link'; + tabButton.textContent = lang; + tabButton.addEventListener('click', () => { + shadowRoot.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); + shadowRoot.querySelectorAll('.tab-content').forEach(panel => panel.classList.remove('active')); + tabButton.classList.add('active'); + shadowRoot.querySelector(`.tab-content[data-lang="${lang}"]`).classList.add('active'); + }); + tabNav.appendChild(tabButton); + if (index === 0) { + tabButton.click(); + } + }); + try { + if (window.Prism) { + contentArea.querySelectorAll(`pre[class*="language-"]`).forEach(element => window.Prism.highlightElement(element)); + } + } catch (e) { + console.error('CodeTranslateAI: Error highlighting syntax.', e); + } +} \ No newline at end of file From 4ce3040096a70fd72807350498795ed30ca08c95 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Fri, 27 Jun 2025 13:46:55 +0530 Subject: [PATCH 6/8] docs: update docs --- README.md | 105 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4a7299e..c9c7dc4 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,21 @@ CodeTranslateAI is a powerful Chrome extension that allows you to translate code ## ✨ Key Features - **On-the-Fly Translation:** Instantly translate code on platforms like Stack Overflow, Medium, and technical blogs. -- **Secure Backend:** Uses a serverless Cloudflare Worker so your AI API key is never exposed on the frontend. +- **Secure Serverless Backend:** Uses a Cloudflare Worker so your AI API key is never exposed on the frontend. - **Multi-Language Tabs:** Translate the same code block into multiple languages and switch between them easily. -- **Smart Caching:** Translations are cached in your browser for 10 days to reduce API calls and provide instant results for previously translated code. -- **Elegant UI:** A clean, non-intrusive UI that matches the width of the original code block and uses a tabbed layout for multiple translations. -- **Powered by Gemini:** Leverages the power of Google's Gemini AI for high-quality code translations. +- **Smart Caching:** Translations are cached in your browser for 10 days to reduce API calls and provide instant results. +- **Elegant & Isolated UI:** A clean UI that matches the width of the original code block and uses a Shadow DOM to prevent any style conflicts with the host page. +- **Powered by Gemini:** Leverages Google's Gemini AI for high-quality code translations with syntax highlighting. --- ## 🔧 Tech Stack - **Frontend:** - - Plain JavaScript (ES6+) + - Modular JavaScript (ES6+) + - **esbuild** (for bundling) - HTML5 & CSS3 - - Chrome Extension APIs (`storage`, `runtime`, `scripting`) + - Chrome Extension APIs (`storage`, `activeTab`) - Shadow DOM for style isolation. - **Backend:** - Cloudflare Workers @@ -34,13 +35,13 @@ To get a local copy up and running, follow these simple steps. ### Prerequisites -You must have Node.js and npm installed on your machine. +You must have **Node.js** and **npm** installed on your machine. - [Download Node.js](https://nodejs.org/) ### ⚙️ Part 1: Backend Setup (Cloudflare Worker) -1. **Clone the Repository** (or set up your backend folder). +1. **Clone the Repository** ```sh git clone https://github.com/dineshsutihar/CodeTranslateAI.git @@ -65,7 +66,9 @@ You must have Node.js and npm installed on your machine. 5. **Set the Secret Key** - - Run the following command and paste your Gemini API key when prompted. This stores it securely on Cloudflare. + - Run the following command and paste your Gemini API key when prompted. + + ```sh npx wrangler secret put GEMINI_API_KEY @@ -75,33 +78,71 @@ You must have Node.js and npm installed on your machine. - Deploy the backend to make it live. + + ```sh npx wrangler deploy ``` - - After deployment, **copy the URL** that Wrangler provides. It will look like `https://backend..dev`. + - After deployment, **copy the URL** that Wrangler provides. ### 🖥️ Part 2: Frontend Setup (Chrome Extension) -1. **Configure the Backend URL** +1. **Navigate to the Frontend Directory** + + ```sh + cd ../frontend + ``` + +2. **Install Dependencies** + + ```sh + npm install + ``` + +3. **Configure the Backend URL** - - Navigate to your Chrome extension folder (e.g., `my-code-translator`). - Open the `background.js` file. - Find the `BACKEND_URL` constant and **paste the Cloudflare Worker URL** you copied in the previous step. ```javascript // background.js - const BACKEND_URL = "https://backend..dev"; // PASTE YOUR URL HERE + const BACKEND_URL = "https://backend.exampledinesh.workers.dev"; // PASTE YOUR URL HERE + ``` + +4. **Build the Extension** + + - You must run the build command to bundle the modular JavaScript files. + + + + ```sh + npm run build ``` -2. **Load the Extension in Chrome** + This will create a `dist` folder containing the `content.bundle.js` file. + +5. **Load the Extension in Chrome** - Open Google Chrome and navigate to `chrome://extensions`. - - Enable **"Developer mode"** using the toggle in the top-right corner. + - Enable **"Developer mode"**. - Click the **"Load unpacked"** button. - - Select your Chrome extension folder (the one containing `manifest.json`). + - Select your Chrome extension folder (the `frontend` folder that contains `manifest.json`). -The **CodeTranslateAI** icon should now appear in your Chrome toolbar, and the extension is ready to use\! +The **CodeTranslateAI** icon should now appear in your Chrome toolbar\! + +--- + +## 💻 Development Workflow + +When you make changes to the frontend JavaScript files in the `scripts/` folder, you must re-bundle them before you can see the changes. + +1. Save your changes in any `.js` file inside the `scripts/` folder. +2. Run the build command in your terminal: + ```sh + npm run build + ``` +3. Go to `chrome://extensions` and click the **reload** button for the CodeTranslateAI extension. --- @@ -110,40 +151,24 @@ The **CodeTranslateAI** icon should now appear in your Chrome toolbar, and the e 1. Click the extension icon in the Chrome toolbar. 2. Select your desired target language from the dropdown menu. 3. Click the **"Enable Code Selector"** button. -4. Your cursor will change to a crosshair. Hover over any code block on a webpage and click on it. -5. A "Translating..." message will appear, followed by a new UI element containing the translated code. -6. If you translate the same block into another language, a new tab will be added to the UI. +4. Your cursor will change to a crosshair. Click on any code block on a webpage. +5. A "Translating..." message will appear, followed by the translated code in a new UI. --- ## 🐛 Debugging the Backend -If you encounter errors or the translation isn't working, the first step is to check the live logs from your Cloudflare Worker. This allows you to see what's happening on the server in real-time. - -1. **Navigate to your Backend Directory** - - - Open your terminal and change into the directory where your Cloudflare Worker code is located (e.g., `CodeTranslateAI/backend`). - -2. **Run the Tail Command** - - - Execute the following command to start streaming the logs: +If you encounter errors, check the live logs from your Cloudflare Worker. +1. **Navigate to your Backend Directory**. +2. **Run the Tail Command**: ```sh npx wrangler tail ``` - - - The terminal will connect and say `Connected to [worker-name], waiting for logs...`. - -3. **Trigger the Error** - - - With the log stream running, go to your browser and use the extension to perform an action that causes an error. - -4. **Check the Terminal** - - - Look back at your terminal. Any errors or log messages from your worker will appear instantly. Look for lines that start with `(error)`. This will give you the exact reason for the failure, such as an invalid API key or a quota issue. +3. **Trigger the Error** by using the extension in your browser and check the terminal for error messages. --- ## ⚖️ License -Distributed under the MIT License. See `LICENSE.txt` for more information. +Distributed under the MIT License. From 64481d3f6241ff3d72a3930deb59cfb96c833e07 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Sat, 28 Jun 2025 14:02:57 +0530 Subject: [PATCH 7/8] build: change in core stucture and and adds env --- frontend/.env.example | 1 + frontend/background.js | 4 ++-- frontend/build.js | 16 ++++++++++++++++ frontend/manifest.json | 4 ++-- frontend/package-lock.json | 18 +++++++++++++++++- frontend/package.json | 4 +++- 6 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 frontend/.env.example create mode 100644 frontend/build.js diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..88bf632 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +BACKEND_URL="https://backend.your-cloudflare-worker.workers.dev" \ No newline at end of file diff --git a/frontend/background.js b/frontend/background.js index 363c1b8..57ab698 100644 --- a/frontend/background.js +++ b/frontend/background.js @@ -1,7 +1,7 @@ -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { +chrome.runtime.onMessage.addListener((request, _, sendResponse) => { if (request.type === "TRANSLATE_CODE") { - const BACKEND_URL = "https://backend.dineshsutihar123.workers.dev"; + const BACKEND_URL = process.env.BACKEND_URL; chrome.storage.sync.get(['targetLanguage'], (result) => { const targetLanguage = result.targetLanguage || 'Java'; diff --git a/frontend/build.js b/frontend/build.js new file mode 100644 index 0000000..a27e7bb --- /dev/null +++ b/frontend/build.js @@ -0,0 +1,16 @@ +import esbuild from 'esbuild'; +import 'dotenv/config'; + +const define = {}; +for (const k in process.env) { + define[`process.env.${k}`] = JSON.stringify(process.env[k]); +} + +esbuild.build({ + entryPoints: ['scripts/content.js', 'background.js'], + bundle: true, + outdir: 'dist', + define: define, +}).catch(() => process.exit(1)); + +console.log('Build complete. Files are in the /dist folder.'); \ No newline at end of file diff --git a/frontend/manifest.json b/frontend/manifest.json index a126805..a2343e8 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -25,12 +25,12 @@ "content_scripts": [ { "matches": [""], - "js": ["packages/prism.js", "dist/content.bundle.js"], + "js": ["packages/prism.js", "dist/scripts/content.js"], "css": ["styles.css"] } ], "background": { - "service_worker": "background.js", + "service_worker": "dist/background.js", "type": "module" }, "web_accessible_resources": [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2587179..ff3f3a0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,10 +1,14 @@ { - "name": "frontend", + "name": "code-translate-ai", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "code-translate-ai", + "version": "1.2.0", "devDependencies": { + "dotenv": "^17.2.1", "esbuild": "^0.25.8" } }, @@ -424,6 +428,18 @@ "node": ">=18" } }, + "node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/esbuild": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index ec2e9f9..167c63d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,10 +3,12 @@ "version": "1.2.0", "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", "author": "Dinesh Kumar Sutihar", + "type": "module", "scripts": { - "build": "esbuild scripts/content.js --bundle --outfile=dist/content.bundle.js" + "build": "node build.js" }, "devDependencies": { + "dotenv": "^17.2.1", "esbuild": "^0.25.8" } } From 66ccdfbc080b53549a6b2cf379133a969304d9b1 Mon Sep 17 00:00:00 2001 From: Dinesh Kumar Sutihar Date: Sat, 28 Jun 2025 14:06:44 +0530 Subject: [PATCH 8/8] docs: update docs --- README.md | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c9c7dc4..acc5045 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,9 @@ CodeTranslateAI is a powerful Chrome extension that allows you to translate code ## ✨ Key Features - **On-the-Fly Translation:** Instantly translate code on platforms like Stack Overflow, Medium, and technical blogs. -- **Secure Serverless Backend:** Uses a Cloudflare Worker so your AI API key is never exposed on the frontend. +- **Secure Serverless Backend:** Uses a Cloudflare Worker. - **Multi-Language Tabs:** Translate the same code block into multiple languages and switch between them easily. - **Smart Caching:** Translations are cached in your browser for 10 days to reduce API calls and provide instant results. -- **Elegant & Isolated UI:** A clean UI that matches the width of the original code block and uses a Shadow DOM to prevent any style conflicts with the host page. - **Powered by Gemini:** Leverages Google's Gemini AI for high-quality code translations with syntax highlighting. --- @@ -17,7 +16,7 @@ CodeTranslateAI is a powerful Chrome extension that allows you to translate code - **Frontend:** - Modular JavaScript (ES6+) - - **esbuild** (for bundling) + - **esbuild** & **dotenv** (for bundling and environment variables) - HTML5 & CSS3 - Chrome Extension APIs (`storage`, `activeTab`) - Shadow DOM for style isolation. @@ -102,17 +101,45 @@ You must have **Node.js** and **npm** installed on your machine. 3. **Configure the Backend URL** - - Open the `background.js` file. - - Find the `BACKEND_URL` constant and **paste the Cloudflare Worker URL** you copied in the previous step. + - In the `frontend` folder, create a new file named `.env`. + - Add the Cloudflare Worker URL you copied in the previous step to this file: + + + + ``` + # .env file + BACKEND_URL="https://your-worker-url.workers.dev" + ``` + +4. **Create the Build Configuration** + + - In the `frontend` folder, create a file named `build.js` and add the following content. This file tells our build script how to use the `.env` variable. + + ```javascript - // background.js - const BACKEND_URL = "https://backend.exampledinesh.workers.dev"; // PASTE YOUR URL HERE + // build.js + import esbuild from "esbuild"; + import "dotenv/config"; + + const define = {}; + for (const k in process.env) { + define[`process.env.${k}`] = JSON.stringify(process.env[k]); + } + + esbuild + .build({ + entryPoints: ["scripts/content.js", "background.js"], + bundle: true, + outdir: "dist", + define: define, + }) + .catch(() => process.exit(1)); ``` -4. **Build the Extension** +5. **Build the Extension** - - You must run the build command to bundle the modular JavaScript files. + - Run the build command to bundle your scripts and inject the environment variable. @@ -120,9 +147,9 @@ You must have **Node.js** and **npm** installed on your machine. npm run build ``` - This will create a `dist` folder containing the `content.bundle.js` file. + This will create a `dist` folder containing your final `content.js` and `background.js` files. -5. **Load the Extension in Chrome** +6. **Load the Extension in Chrome** - Open Google Chrome and navigate to `chrome://extensions`. - Enable **"Developer mode"**. @@ -135,9 +162,7 @@ The **CodeTranslateAI** icon should now appear in your Chrome toolbar\! ## 💻 Development Workflow -When you make changes to the frontend JavaScript files in the `scripts/` folder, you must re-bundle them before you can see the changes. - -1. Save your changes in any `.js` file inside the `scripts/` folder. +1. Make any changes to your JavaScript files in the `scripts/` folder or `background.js`. 2. Run the build command in your terminal: ```sh npm run build