diff --git a/src/ChromeExtension/css/prism.css b/src/ChromeExtension/css/prism.css new file mode 100644 index 0000000..44ab5b0 --- /dev/null +++ b/src/ChromeExtension/css/prism.css @@ -0,0 +1,3 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+markdown */ +code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;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}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} diff --git a/src/ChromeExtension/js/can-load-saved-replies.js b/src/ChromeExtension/js/can-load-saved-replies.js new file mode 100644 index 0000000..0ff57a6 --- /dev/null +++ b/src/ChromeExtension/js/can-load-saved-replies.js @@ -0,0 +1,15 @@ +const canLoadSavedRepliesForURL = async (url) =>{ + + const configs = await getConfigsFromLocalStorage(); + + for(let config of configs){ + + const result = canLoadRepliesForUrl(config, url); + + if(result){ + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/ChromeExtension/js/create-element.js b/src/ChromeExtension/js/create-element.js new file mode 100644 index 0000000..3cbac82 --- /dev/null +++ b/src/ChromeExtension/js/create-element.js @@ -0,0 +1,24 @@ +const createElement = (tagName, { children, className, ...attributes } = {}) => { + + const element = document.createElement(tagName); + + if (children) { + + element.append(...children); + } + + if (className) { + + element.className = className; + } + + for (const [key, value] of Object.entries(attributes)) { + + if (value) { + + element.setAttribute([key], value) + } + } + + return element; +} \ No newline at end of file diff --git a/src/ChromeExtension/pages/content-script/content-script-elements.js b/src/ChromeExtension/js/elements.js similarity index 88% rename from src/ChromeExtension/pages/content-script/content-script-elements.js rename to src/ChromeExtension/js/elements.js index a58ea00..9106d93 100644 --- a/src/ChromeExtension/pages/content-script/content-script-elements.js +++ b/src/ChromeExtension/js/elements.js @@ -1,27 +1,3 @@ -const createElement = (tagName, { children, className, ...attributes } = {}) => { - - const element = document.createElement(tagName); - - if (children) { - - element.append(...children); - } - - if (className) { - - element.className = className; - } - - for (const [key, value] of Object.entries(attributes)) { - - if (value) { - element.setAttribute(key, value) - } - } - - return element; -} - const createSavedRepliesUl = (savedReplies) => { const repliesUl = createElement("ul", { diff --git a/src/ChromeExtension/js/events.js b/src/ChromeExtension/js/events.js new file mode 100644 index 0000000..713f86e --- /dev/null +++ b/src/ChromeExtension/js/events.js @@ -0,0 +1,4 @@ +const CAN_LOAD_SAVED_REPLIES_CHANGED = "CanLoadSavedRepliesChanged"; + +const createCanLoadSavedRepliesChangedEvent = (canLoadSavedReplies) => + createEvent(CAN_LOAD_SAVED_REPLIES_CHANGED, {canLoadSavedReplies:canLoadSavedReplies}); \ No newline at end of file diff --git a/src/ChromeExtension/js/message-receivers.js b/src/ChromeExtension/js/message-receivers.js new file mode 100644 index 0000000..deaebc2 --- /dev/null +++ b/src/ChromeExtension/js/message-receivers.js @@ -0,0 +1,3 @@ +const SERVICE_WORKER = "service-worker"; +const SIDE_PANEL = "side-panel"; + diff --git a/src/ChromeExtension/js/messaging.js b/src/ChromeExtension/js/messaging.js index 1dc9d2e..e6b7ad7 100644 --- a/src/ChromeExtension/js/messaging.js +++ b/src/ChromeExtension/js/messaging.js @@ -45,7 +45,11 @@ const canHandleEvent = async (message, messageName) => { if (isEvent(message) && message.messageName === messageName) { console.log(`handling event`, message); + + return true; } + + return false; } const createCommand = (messageName, target, data) => { @@ -86,6 +90,21 @@ const send = async (command) => { } } +const sendNonAsync = (command) => { + + if (isCommand(command)) { + + console.log(`sent`, command); + + chrome.runtime.sendMessage(command); + + return true; + } + else { + throw new Error(`message: ${command.messageName} is not a command.`); + } +} + const createEvent = (messageName, data) => { if (!messageName) { @@ -117,4 +136,22 @@ const publish = async (event) => { throw new Error(`message: ${event.messageName} is not a event.`); } +} + +const tryPublish = async (event) =>{ + try{ + await publish(event); + } + catch(error){ + console.log(error.message); + } +} + +const trySendMessageToContentScript = async (tabId, event) =>{ + try{ + chrome.tabs.sendMessage(tabId, event); + } + catch(error){ + console.log("Failed to send to message to content script",error.message); + } } \ No newline at end of file diff --git a/src/ChromeExtension/js/modules/tabs.js b/src/ChromeExtension/js/modules/tabs.js new file mode 100644 index 0000000..ed33d8f --- /dev/null +++ b/src/ChromeExtension/js/modules/tabs.js @@ -0,0 +1,8 @@ +const getCurrentTab = async () => { + let queryOptions = { active: true, lastFocusedWindow: true }; + // `tab` will either be a `tabs.Tab` instance or `undefined`. + let [tab] = await chrome.tabs.query(queryOptions); + return tab; + } + +export {getCurrentTab} \ No newline at end of file diff --git a/src/ChromeExtension/js/prism.js b/src/ChromeExtension/js/prism.js new file mode 100644 index 0000000..8c7bcc8 --- /dev/null +++ b/src/ChromeExtension/js/prism.js @@ -0,0 +1,8 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+markdown */ +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 E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.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"+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; +!function(n){function e(n){return n=n.replace(//g,(function(){return"(?:\\\\.|[^\\\\\n\r]|(?:\n|\r\n?)(?![\r\n]))"})),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var t="(?:\\\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\\\|\r\n`])+",a="\\|?__(?:\\|__)+\\|?(?:(?:\n|\r\n?)|(?![^]))".replace(/__/g,(function(){return t})),i="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\n|\r\n?)";n.languages.markdown=n.languages.extend("markup",{}),n.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:n.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+i+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+i+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(t),inside:n.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+i+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(t),alias:"important",inside:n.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:e("\\b__(?:(?!_)|_(?:(?!_))+_)+__\\b|\\*\\*(?:(?!\\*)|\\*(?:(?!\\*))+\\*)+\\*\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:e("\\b_(?:(?!_)|__(?:(?!_))+__)+_\\b|\\*(?:(?!\\*)|\\*\\*(?:(?!\\*))+\\*\\*)+\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:e("(~~?)(?:(?!~))+\\2"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:e('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)|[ \t]?\\[(?:(?!\\]))+\\])'),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(e){["url","bold","italic","strike","code-snippet"].forEach((function(t){e!==t&&(n.languages.markdown[e].inside.content.inside[t]=n.languages.markdown[t])}))})),n.hooks.add("after-tokenize",(function(n){"markdown"!==n.language&&"md"!==n.language||function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t",quot:'"'},l=String.fromCodePoint||String.fromCharCode;n.languages.md=n.languages.markdown}(Prism); diff --git a/src/ChromeExtension/pages/content-script/content-script-storage.js b/src/ChromeExtension/js/saved-replies-storage.js similarity index 55% rename from src/ChromeExtension/pages/content-script/content-script-storage.js rename to src/ChromeExtension/js/saved-replies-storage.js index d5daf89..ac23872 100644 --- a/src/ChromeExtension/pages/content-script/content-script-storage.js +++ b/src/ChromeExtension/js/saved-replies-storage.js @@ -1,5 +1,33 @@ -const getMatchingSavedReplyConfigsFromLocalStorage = async () => { +const getConfigsFromLocalStorage = async () => { + const results = await chrome.storage.local.get(); + + const resultsArray = Object.entries(results); + + let configExpression = new RegExp(".+-config"); + + let configs = []; + + for(let result of resultsArray){ + + if (configExpression.test(result[0])) { + const config = result[1]; + + configs.push(config); + } + } + + return configs; +} + +const getMatchingSavedReplyConfigsFromLocalStorage = async (url) => { + + console.log("getting matchingconfig from storage"); + console.log("url",url); + + if(url === null){ + url = window.location.href; + } const results = await chrome.storage.local.get(); @@ -11,11 +39,17 @@ const getMatchingSavedReplyConfigsFromLocalStorage = async () => { for(let result of resultsArray) { + console.log("results not empty "); + if (configExpression.test(result[0])) { const config = result[1]; - if (canLoadRepliesForUrl(config)) { + console.log("found matching config"); + + if (canLoadRepliesForUrl(config, url)) { + + console.log("can load replies from url"); const configKey = `${config.name}-replies`; @@ -24,6 +58,8 @@ const getMatchingSavedReplyConfigsFromLocalStorage = async () => { let configReplies = repliesResult[configKey]; replies = replies.concat(configReplies) + }else{ + console.log("can not load replies from url"); } } } @@ -42,7 +78,15 @@ const getSavedRepliesLastUpdatedAt = async (name) => { return lastUpdateAt; } -const getSavedRepliesLastUpdatedAtFromLocalStorage = async () => { +const getSavedRepliesLastUpdatedAtFromLocalStorage = async (url) => { + + console.log("getting matchingconfig from storage"); + + console.log("url",url); + + if(url === null){ + url = window.location.href; + } const results = await chrome.storage.local.get(); @@ -54,15 +98,16 @@ const getSavedRepliesLastUpdatedAtFromLocalStorage = async () => { for(let result of resultsArray) { - if (configExpression.test(result[0])) { let config = result[1]; - if (canLoadRepliesForUrl(config)) { + + if (canLoadRepliesForUrl(config, url)) { let nextLastUpdatedAt = await getSavedRepliesLastUpdatedAt(config.name); if (dateIsBefore(currentLastUpdatedAt, nextLastUpdatedAt)) { + currentLastUpdatedAt = nextLastUpdatedAt; } } diff --git a/src/ChromeExtension/js/sidepanel.js b/src/ChromeExtension/js/sidepanel.js new file mode 100644 index 0000000..6aad55c --- /dev/null +++ b/src/ChromeExtension/js/sidepanel.js @@ -0,0 +1,41 @@ +let replies = []; +let repliesExist = false; +let repliesUI = []; + +const closeSavedRepliesPanelMessage = "CloseSharedSavedRepliesPanel"; + +const handleCloseSharedSavedRepliesPanel = (message, handleMessage) =>{ + + if (!canHandleCommand(message, SIDE_PANEL, closeSavedRepliesPanelMessage)) { + return; + } + + handleMessage(); +} + +const closeSharedSavedRepliesPanel = () =>{ + + const closeSharedReplilesPanelCommand = + createCommand(closeSavedRepliesPanelMessage, SIDE_PANEL, {}); + + sendNonAsync(closeSharedReplilesPanelCommand); +} + +const prepareRepliesUI = async(url) => { + + const replies = await getMatchingSavedReplyConfigsFromLocalStorage(url); + + console.log("replies", replies); + + const repliesExist = arrayIsNotEmpty(replies); + + if (repliesExist) { + + const repliesUi = await createSavedRepliesSidePanelDiv(replies); + + return repliesUi; + } + + return []; +} + diff --git a/src/ChromeExtension/js/tabs.js b/src/ChromeExtension/js/tabs.js new file mode 100644 index 0000000..3c0ef9e --- /dev/null +++ b/src/ChromeExtension/js/tabs.js @@ -0,0 +1,6 @@ +const getCurrentTab = async () => { + let queryOptions = { active: true, lastFocusedWindow: true }; + // `tab` will either be a `tabs.Tab` instance or `undefined`. + let [tab] = await chrome.tabs.query(queryOptions); + return tab; + } \ No newline at end of file diff --git a/src/ChromeExtension/js/urls.js b/src/ChromeExtension/js/urls.js new file mode 100644 index 0000000..3acdc7c --- /dev/null +++ b/src/ChromeExtension/js/urls.js @@ -0,0 +1,109 @@ +var currentUrl; + +const setCurrentActiveURL = async (url, callback) =>{ + + await chrome.storage.local.set({ [`currentActiveUrl`]: url }); + + console.log("currentUrl changed", currentUrl); + + if(callback !== undefined){ + + callback(); + } +} + +const getCurrentActiveURL = async () =>{ + + const results = await chrome.storage.local.get([`currentActiveUrl`]); + + return results[`currentActiveUrl`]; +} + +const isGitHubIssueUrl = (url) => { + + if(url === null){ + url = window.location.href; + } + + let pattern = /^(https?:\/\/)github\.com\/.+\/.+\/issues\/\d+/i; + + return pattern.test(url); +} + +const isGitHubPullRequestUrl = (url) => { + + if(url === null){ + url = window.location.href; + } + + let pattern = /^(https?:\/\/)github\.com\/.+\/.+\/pull\/\d+/i; + + return pattern.test(url); +} + +const isLocalhostUrl = (url) => { + + if(url === null){ + url = window.location.href; + } + + let pattern = /^(https?:\/\/)localhost(:\d+).*/i; + + return pattern.test(url); +} + +const getGitHubOwner = (url) => { + + if(url === null){ + url = window.location.href; + } + + const expression = /https:\/\/github.com\/(?[^\/]+)?(.*)/i + + const matches = url.match(expression); + + const owner = matches?.groups['owner']; + + return owner; +} + +const canLoadRepliesForUrl = (config,url) => { + + console.log("evaluating can load replies from url", url); + + if(url === null){ + url = window.location.href; + } + + //this if for unit tests + if(isLocalhostUrl(url)){ + return true; + } + + const gitHubOwner = getGitHubOwner(url); + + if(gitHubOwner === undefined){ + return false; + } + + const validOwner = config.allowEverywhere || gitHubOwner.localeCompare(config.limitToGitHubOwner,undefined,{ sensitivity : `base`}) === 0 ? true : false; + + console.log("validOwner",validOwner); + + const validForIssue = (isGitHubIssueUrl(url) && config.includeIssues); + + console.log("validForIssue",validForIssue); + + const validForPullRequest = (isGitHubPullRequestUrl(url) && config.includePullRequests); + + console.log("validForPullRequest",validForPullRequest); + + if (validOwner + && (validForIssue || validForPullRequest)) { + + return true; + } + + return false; +} + diff --git a/src/ChromeExtension/manifest.json b/src/ChromeExtension/manifest.json index 30e0861..753dadc 100644 --- a/src/ChromeExtension/manifest.json +++ b/src/ChromeExtension/manifest.json @@ -9,18 +9,35 @@ "background": { "service_worker": "pages/service-worker/service-worker.js" }, + "side_panel":{ + "default_path": "pages/sidepanel/saved-replies-sidepanel.html" + }, "content_scripts": [ { "matches": [ "https://github.com/*" ], + "css":[ + "pages/sidepanel/sidepanel-button.css" + ], "js": [ "js/time.js", "js/null.js", + "js/create-element.js", + "js/elements.js", + "js/messaging.js", + "js/message-receivers.js", + "js/tabs.js", + "js/saved-replies-storage.js", + "js/sidepanel.js", + "js/urls.js", + "js/events.js", + "js/can-load-saved-replies.js", "pages/content-script/content-script-copy-saved-reply-element.js", "pages/content-script/content-script-urls.js", - "pages/content-script/content-script-storage.js", - "pages/content-script/content-script-elements.js", + "pages/content-script/content-script-messaging.js", + "pages/sidepanel/saved-replies-sidepanel-toggle-button.js", + "pages/sidepanel/saved-replies-items.js", "pages/content-script/content-script.js" ] } @@ -36,7 +53,17 @@ "page":"pages/options/options.html", "open_in_tab":true }, + "web_accessible_resources":[{ + "resources":[ + "pages/sidepanel/*.svg" + ], + "matches":[ + "" + ] + }], "permissions": [ + "sidePanel", + "activeTab", "tabs", "scripting", "webNavigation", diff --git a/src/ChromeExtension/pages/content-script/content-script-clone-with-events.js b/src/ChromeExtension/pages/content-script/content-script-clone-with-events.js new file mode 100644 index 0000000..17ae595 --- /dev/null +++ b/src/ChromeExtension/pages/content-script/content-script-clone-with-events.js @@ -0,0 +1,65 @@ +//This was super clever to clone events when cloning an element. +//https://stackoverflow.com/a/76089435 + +const _originAddEventListener = HTMLElement.prototype.addEventListener; +const _originRemoveEventListener = HTMLElement.prototype.removeEventListener; +const _originCloneNode = HTMLElement.prototype.cloneNode; +const _eventListeners = []; + +const getEventIndex = (target, targetArgs) => _eventListeners.findIndex(([elem, args]) => { + if(elem !== target) { + return false; + } + + for (let i = 0; i < args.length; i++) { + if(targetArgs[i] !== args[i]) { + return false; + } + } + + return true; +}); + +const getEvents = (target) => _eventListeners.filter(([elem]) => { + return elem === target; +}); + +const cloneEvents = (source, element, deep) => { + for (const [_, args] of getEvents(source)) { + _originAddEventListener.apply(element, args); + } + + if(deep) { + for(const i of source.childNodes.keys()) { + const sourceNode = source.childNodes.item(i); + if(sourceNode instanceof HTMLElement) { + const targetNode = element.childNodes.item(i); + cloneEvents(sourceNode, targetNode, deep); + } + } + } +}; + +HTMLElement.prototype.addEventListener = function() { + _eventListeners.push([this, arguments]); + return _originAddEventListener.apply(this, arguments); +}; + +HTMLElement.prototype.removeEventListener = function() { + + const eventIndex = getEventIndex(this, arguments); + + if(eventIndex !== -1) { + _eventListeners.splice(eventIndex, 1); + } + + return _originRemoveEventListener.apply(this, arguments); +}; + +// HTMLElement.prototype.cloneNode = function(deep) { +// const clonedNode = _originCloneNode.apply(this, arguments); +// if(clonedNode instanceof HTMLElement){ +// cloneEvents(this, clonedNode, deep); +// } +// return clonedNode; +// }; \ No newline at end of file diff --git a/src/ChromeExtension/pages/content-script/content-script-copy-saved-reply-element.js b/src/ChromeExtension/pages/content-script/content-script-copy-saved-reply-element.js index 758d756..97bbb52 100644 --- a/src/ChromeExtension/pages/content-script/content-script-copy-saved-reply-element.js +++ b/src/ChromeExtension/pages/content-script/content-script-copy-saved-reply-element.js @@ -33,33 +33,60 @@ const calculateSavedReplyId = (lastId, savedReplyIndex) => { } } + + +const setElementSavedReplyName = (element, name) =>{ + + element.setAttribute(`saved-reply-name`, name); +} + + const setSavedReplyId = (element,calculatedId) => { element.id = calculatedId; } -const setSavedReplyTitle = (titleSpan, title) => { +const setSavedReplyName = (nameSpan, name) => { - titleSpan.innerText = title; + nameSpan.innerText = name; +} + + +const encodeSavedReplyBody = (body) => { + + let updatedBody = + body.replace(/\r\n/gi, '\\uFFFD') + .replace(/\n/gi,'\\uFFFD'); + + return updatedBody; } const setSavedReplyBody = (bodySpan, body) => { - bodySpan.innerText = body; + let encodedBody = encodeSavedReplyBody(body); + + bodySpan.innerText = encodedBody; } const createNewSavedReplyElementFromExistingElment = (templateElement, savedReply, index) => { + let spans = templateElement.querySelectorAll(`span`); + console.log("bodyspan", spans[1]); + console.log("bodyspanInnerText", spans[1].innerText); + console.log("savereplybody", savedReply.body); + let newSavedReplyElement = templateElement.cloneNode(true); let savedReplySpans = newSavedReplyElement.querySelectorAll(`span`); - let titleSpan = savedReplySpans[0]; + let nameSpan = savedReplySpans[0]; let bodySpan = savedReplySpans[1]; - setSavedReplyTitle(titleSpan, savedReply.name); - + setElementSavedReplyName(newSavedReplyElement, savedReply.name); + + setSavedReplyName(nameSpan, savedReply.name); + setSavedReplyBody(bodySpan, savedReply.body); let id = calculateSavedReplyId(templateElement.id, index); @@ -93,10 +120,64 @@ const addNewSavedReplyElementToContainer = (savedRepliesContainer,savedReplyElem savedRepliesContainer.appendChild(savedReplyElement); } -const addNewSavedRepliesToNestedIssuesContainer = (savedRepliesDivs, savedRepliesContainer) =>{ +const getElementSavedReplyName = (element) =>{ + + return element.getAttribute(`saved-reply-name`); +} + +const getSavedReplyTitleSpan = (savedReplyDiv) => { + +} + +const getSavedReplyTemplateString = (savedReplyDiv) =>{ + let bodySpan = savedReplyDiv.querySelectorAll(`span`)[1] + + let decodedTemplate = bodySpan.innerText + .replace(/(\\uFFFD)+/gi,'
'); + + return decodedTemplate; +} + +const addEventListenerToSavedReplyDiv = (savedReplyDiv, textAreaElement) =>{ + + let templateString = getSavedReplyTemplateString(savedReplyDiv, textAreaElement); + + savedReplyDiv.addEventListener(`click`, function(e){ + + textAreaElement.innerHtml = templateString; + + console.log("text area value", textAreaElement.value); + + }); + +} + +const getCommentTextAreaElement = () => { + + let textAreaElement = + document.querySelector( + `div[data-testid="markdown-editor-comment-composer"] textarea`); + + return textAreaElement; +} + +const addNewSavedRepliesToNestedIssuesContainer = (savedRepliesDivs, savedRepliesContainer) =>{ + + let textAreaElement = getCommentTextAreaElement(); + for (const savedReplyDiv of savedRepliesDivs){ - savedRepliesContainer.appendChild(savedReplyDiv); + + let savedReplyName = savedReplyDiv.getAttribute(`saved-reply-name`); + + let matchingDiv = savedRepliesContainer.querySelector(`div[saved-reply-name="${savedReplyName}"]`); + + if(!matchingDiv){ + + addEventListenerToSavedReplyDiv(savedReplyDiv, textAreaElement); + + savedRepliesContainer.appendChild(savedReplyDiv); + } } } diff --git a/src/ChromeExtension/pages/content-script/content-script-messaging.js b/src/ChromeExtension/pages/content-script/content-script-messaging.js new file mode 100644 index 0000000..1c34802 --- /dev/null +++ b/src/ChromeExtension/pages/content-script/content-script-messaging.js @@ -0,0 +1,9 @@ +const handleCanLoadSavedRepliesChanged = async (event, handleEvent) =>{ + if(!canHandleEvent(event, CAN_LOAD_SAVED_REPLIES_CHANGED)){ + return; + } + + if(handleEvent !== undefined){ + handleEvent(event.data.canLoadSavedReplies); + } +} \ No newline at end of file diff --git a/src/ChromeExtension/pages/content-script/content-script-urls.js b/src/ChromeExtension/pages/content-script/content-script-urls.js index aeff68b..b6ac276 100644 --- a/src/ChromeExtension/pages/content-script/content-script-urls.js +++ b/src/ChromeExtension/pages/content-script/content-script-urls.js @@ -1,67 +1,4 @@ -const isGitHubIssueUrl = () => { - - let url = window.location.href; - - let pattern = /^(https?:\/\/)github\.com\/.+\/.+\/issues\/\d+/i; - - return pattern.test(url); -} - -const isGitHubPullRequestUrl = () => { - - let url = window.location.href; - - let pattern = /^(https?:\/\/)github\.com\/.+\/.+\/pull\/\d+/i; - - return pattern.test(url); -} - -const isLocalhostUrl = () => { - let url = window.location.href; - - let pattern = /^(https?:\/\/)localhost(:\d+).*/i; - - return pattern.test(url); -} - -const shouldLoadContentScript = () => { - - return isLocalhostUrl() || isGitHubIssueUrl() || isGitHubPullRequestUrl(); -} - -const getGitHubOwner = () => { - const url = window.location.href; - - const expression = /https:\/\/github.com\/(?[^\/]+)?(.*)/i - - const matches = url.match(expression); - - const owner = matches?.groups['owner']; - - return owner; -} - -const canLoadRepliesForUrl = (config) => { - - //this if for unit tests - if(isLocalhostUrl()){ - return true; - } - - const gitHubOwner = getGitHubOwner(); - - const validOwner = config.allowEverywhere || gitHubOwner.localeCompare(config.limitToGitHubOwner,undefined,{ sensitivity : `base`}) === 0 ? true : false; - - const validForIssue = (isGitHubIssueUrl() && config.includeIssues); - - const validForPullRequest = (isGitHubPullRequestUrl() && config.includePullRequests); - - if (validOwner - && (validForIssue || validForPullRequest)) { - - return true; - } - - return false; -} +const shouldLoadContentScript = (url) => { + return isLocalhostUrl(url) || isGitHubIssueUrl(url) || isGitHubPullRequestUrl(url); +} \ No newline at end of file diff --git a/src/ChromeExtension/pages/content-script/content-script.js b/src/ChromeExtension/pages/content-script/content-script.js index f2fab81..1857156 100644 --- a/src/ChromeExtension/pages/content-script/content-script.js +++ b/src/ChromeExtension/pages/content-script/content-script.js @@ -1,216 +1,61 @@ -const main = async () => { - - console.log("main called"); - - if (!shouldLoadContentScript()) { - return; - } - - let observer; - let betaObserver; - let repliesUl; - let repliesForNestedIssuesDivs; - let replies; - - const prepareRepliesUl = async () => { - - replies = await getMatchingSavedReplyConfigsFromLocalStorage(); - - const repliesExist = arrayIsNotEmpty(replies); - if (repliesExist) { +const showHideSavedRepliesButton = async (showButton) => { - repliesUl = await createSavedRepliesUl(replies); - } - } - - const tryUpdateSavedRepliesUl = () => { - - let savedRepliesUl = document.querySelector(`.shared-saved-replies`); - - const repliesExist = arrayIsNotEmpty(replies); + const showSavedRepliesButtons = + document.querySelectorAll(".show-saved-replies-button-container"); - if (repliesExist && savedRepliesUl) { + let firstUpdated = false; - savedRepliesUl.replaceWith(repliesUl); + for (button of showSavedRepliesButtons) { - return true; - - } else if (savedRepliesUl) { - - savedRepliesUl.remove(); - - return true; + if (firstUpdated) { + button.remove(); } - } - - const tryUpdateFuzzyList = (node) => { - - let isFuzzyList = node.nodeName === "FUZZY-LIST"; - - let savedReplyFilter; - - let savedRepliesUl; - - const repliesExist = arrayIsNotEmpty(replies); - - if (isFuzzyList) { - let fuzzyList = node; - - const savedReplyMenuFilterSelector = - `div[data-view-component="true"]`; - - savedReplyFilter = - fuzzyList.querySelector(savedReplyMenuFilterSelector); - - savedRepliesUl = savedReplyFilter.querySelector(`.shared-saved-replies`); - } - - if (repliesExist && savedReplyFilter) { - - savedReplyFilter.insertBefore(repliesUl, savedReplyFilter.firstChild); - - return true; - - } else if (savedRepliesUl) { - - savedRepliesUl.remove(); - - return true; - } - - return false; - } - - const onSavedRepliesOpened = async (node) => { - await prepareRepliesUl(); - - if (tryUpdateSavedRepliesUl()) { - return; - } - - if (tryUpdateFuzzyList(node)) { - return; - } - } - - observer = new MutationObserver( - async (mutationList, obs) => { - - console.log(`mutation happened`); - - for (const mutation of mutationList) { - - if (mutation.type === "attributes" && mutation.attributeName == "open") { - - let replyContainer = document.querySelector(`#saved_replies_menu_new_comment_field-dialog`).closest(`.js-saved-reply-container`); - - if (replyContainer.attributes.open) { - console.log(`open`); - - let node = replyContainer.querySelector('FUZZY-LIST'); - - await onSavedRepliesOpened(node); - - } - } + if (button !== undefined) { + if (showButton) { + button?.classList?.remove("hide"); + } else { + button?.classList?.add("hide"); } - }); - - - let savedReplyContainer = document.querySelector(`#saved_replies_menu_new_comment_field-dialog`); - - if(savedReplyContainer){ - - observer.observe(savedReplyContainer, { - attributes: true, - childList: false, - subtree: false - }); - } - - const prepareSavedRepliesForNestedIssues = async () => { - - replies = await getMatchingSavedReplyConfigsFromLocalStorage(); - - const repliesExist = arrayIsNotEmpty(replies); - - if (repliesExist) { - repliesForNestedIssuesDivs = createSavedRepliesUIForNestedIssues(replies); + firstUpdated = true; } } +} - - const tryUpdateSavedRepliesForNestedIssues = () => { - - let saveRepliesContainer = getsSavedRepliesForNestedIssuesContainer(); - - const repliesExist = arrayIsNotEmpty(repliesForNestedIssuesDivs); - - if (repliesExist && saveRepliesContainer) { - - addNewSavedRepliesToNestedIssuesContainer( - repliesForNestedIssuesDivs, - saveRepliesContainer); - - return true; - - } else if (saveRepliesContainer) { - +const main = async () => { - return true; - } - } + console.log("main called"); - const onSavedRepliesDialogIsVisible = async () => { - - await prepareSavedRepliesForNestedIssues(); + const url = window.location.href; - if(tryUpdateSavedRepliesForNestedIssues()){ - - return; - } + if (!shouldLoadContentScript(url)) { + return; } + const showSavedRepliesButton = + document.querySelector(".show-saved-replies-button-container"); - let addSavedReplyButton = document.querySelector(`button[aria-label="Add saved reply (Ctrl + .)"]`); + if (showSavedRepliesButton !== undefined) { - betaObserver = new MutationObserver( - async (mutationList, obs) => { + const showSavedRepliesButton = createShowSavedRepliesButton(); - console.log(`beta mutation happened`); + await addShowSavedRepliesClickHandler(showSavedRepliesButton); - for (const mutation of mutationList) { - - if (mutation.type === "attributes" && mutation.attributeName == "aria-expanded"){ + // addShowSavedRepliesMouseMoveHandlers(showSavedRepliesButton) - let savedRepliesDialogIsVisible = addSavedReplyButton.getAttribute("aria-expanded") === "true"; - - if(savedRepliesDialogIsVisible) - { - console.log("beta open") - await onSavedRepliesDialogIsVisible() - }else{ - console.log("beta closed") - } - } - } - } - ); - - if(addSavedReplyButton){ - - betaObserver.observe(addSavedReplyButton, { - attributes: true, - childList: false, - subtree: false - }); + document.body.appendChild(showSavedRepliesButton); } } +chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { + handleCanLoadSavedRepliesChanged(request, (canLoadSavedReplies) => { + showHideSavedRepliesButton(canLoadSavedReplies); + }); +}); + document.addEventListener("soft-nav:end", main); main().catch((error) => { - console.error("Oh no!", error); + console.error("Oh no!", error); }); \ No newline at end of file diff --git a/src/ChromeExtension/pages/popup/popup.html b/src/ChromeExtension/pages/popup/popup.html index 0036ed1..fadca01 100644 --- a/src/ChromeExtension/pages/popup/popup.html +++ b/src/ChromeExtension/pages/popup/popup.html @@ -22,9 +22,6 @@ - -
-
- +
@@ -65,7 +62,7 @@