From b5adeaf04874ba243f570b85ea1d4f65a873a97d Mon Sep 17 00:00:00 2001 From: makinzm Date: Sun, 22 Dec 2024 00:51:04 +0900 Subject: [PATCH 1/4] include and exclude domain --- dist/background.js | 2 +- dist/content_script.js | 2 +- dist/options/options.html | 19 +++++ dist/options/options.js | 2 +- dist/options/options.ts | 32 +++++++++ src/content_script.ts | 17 +++-- src/content_script_helper.ts | 15 ++++ src/database.ts | 22 ++++++ src/options/options.html | 19 +++++ src/options/options.ts | 32 +++++++++ tests/e2e/content_script.spec.ts | 89 ++++++++++++++++-------- tests/unit/content_script_helper.spec.ts | 35 ++++++++++ 12 files changed, 248 insertions(+), 38 deletions(-) create mode 100644 src/content_script_helper.ts create mode 100644 tests/unit/content_script_helper.spec.ts diff --git a/dist/background.js b/dist/background.js index 2e518e3..163053c 100644 --- a/dist/background.js +++ b/dist/background.js @@ -1 +1 @@ -(()=>{"use strict";var e={994:function(e,t,o){var n=this&&this.__awaiter||function(e,t,o,n){return new(o||(o=Promise))((function(i,s){function r(e){try{c(n.next(e))}catch(e){s(e)}}function l(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof o?t:new o((function(e){e(t)}))).then(r,l)}c((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(o(379).LocalDatabase);console.log("Background script started."),chrome.runtime.onMessage.addListener(((e,t,o)=>{if(console.log("Message received in background:",e),"save_entry"===e.type)return i.save(e.entry).then((()=>n(void 0,void 0,void 0,(function*(){console.log("Entry saved successfully:",e.entry);const t=yield i.getAll();console.log("All entries after save:",t),o({status:"ok"})})))),!0}))},379:function(e,t){var o=this&&this.__awaiter||function(e,t,o,n){return new(o||(o=Promise))((function(i,s){function r(e){try{c(n.next(e))}catch(e){s(e)}}function l(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof o?t:new o((function(e){e(t)}))).then(r,l)}c((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains"}getAll(){return o(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return o(this,void 0,void 0,(function*(){const t=yield this.getAll(),o=t.findIndex((t=>t.key===e.key));o>-1?t[o]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return o(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return o(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return o(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return o(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}}}},t={};!function o(n){var i=t[n];if(void 0!==i)return i.exports;var s=t[n]={exports:{}};return e[n].call(s.exports,s,s.exports,o),s.exports}(994)})(); \ No newline at end of file +(()=>{"use strict";var e={994:function(e,t,o){var n=this&&this.__awaiter||function(e,t,o,n){return new(o||(o=Promise))((function(i,s){function l(e){try{c(n.next(e))}catch(e){s(e)}}function r(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof o?t:new o((function(e){e(t)}))).then(l,r)}c((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(o(379).LocalDatabase);console.log("Background script started."),chrome.runtime.onMessage.addListener(((e,t,o)=>{if(console.log("Message received in background:",e),"save_entry"===e.type)return i.save(e.entry).then((()=>n(void 0,void 0,void 0,(function*(){console.log("Entry saved successfully:",e.entry);const t=yield i.getAll();console.log("All entries after save:",t),o({status:"ok"})})))),!0}))},379:function(e,t){var o=this&&this.__awaiter||function(e,t,o,n){return new(o||(o=Promise))((function(i,s){function l(e){try{c(n.next(e))}catch(e){s(e)}}function r(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof o?t:new o((function(e){e(t)}))).then(l,r)}c((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains",this.INCLUDED_DOMAIN_KEY="included_domains"}getAll(){return o(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return o(this,void 0,void 0,(function*(){const t=yield this.getAll(),o=t.findIndex((t=>t.key===e.key));o>-1?t[o]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return o(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return o(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return o(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return o(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}getAllIncludedDomains(){return o(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.INCLUDED_DOMAIN_KEY))[this.INCLUDED_DOMAIN_KEY]||[]}))}addIncludedDomain(e){return o(this,void 0,void 0,(function*(){const t=yield this.getAllIncludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t}))}))}removeIncludedDomain(e){return o(this,void 0,void 0,(function*(){const t=(yield this.getAllIncludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t})}))}}}},t={};!function o(n){var i=t[n];if(void 0!==i)return i.exports;var s=t[n]={exports:{}};return e[n].call(s.exports,s,s.exports,o),s.exports}(994)})(); \ No newline at end of file diff --git a/dist/content_script.js b/dist/content_script.js index aee0801..373da1a 100644 --- a/dist/content_script.js +++ b/dist/content_script.js @@ -1 +1 @@ -(()=>{"use strict";({619:function(){var e=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,c){function d(e){try{s(o.next(e))}catch(e){c(e)}}function l(e){try{s(o.throw(e))}catch(e){c(e)}}function s(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(d,l)}s((o=o.apply(e,t||[])).next())}))};e(void 0,void 0,void 0,(function*(){const t=window.location.hostname;console.log(`Current domain: ${t}`),((yield chrome.storage.local.get("excluded_domains")).excluded_domains||[]).some((e=>e.domain===t))?console.log(`This domain (${t}) is excluded from ClipLex.`):(document.addEventListener("scroll",(()=>{const e=document.querySelector('button[data-extension="cliplex"]');e&&document.body.removeChild(e)})),document.addEventListener("click",(e=>{const t=document.querySelector('button[data-extension="cliplex"]');if(t&&!t.contains(e.target)){const n=t.getBoundingClientRect(),o=50;(e.clientXn.right+o||e.clientYn.bottom+o)&&document.body.removeChild(t)}})),document.addEventListener("mouseup",(()=>{const t=window.getSelection();if(!t)return;const n=t.toString().trim();if(!n)return;const o=t.getRangeAt(0).getBoundingClientRect(),i=document.createElement("button");i.style.position="absolute",i.style.top=`${o.bottom+window.scrollY}px`,i.style.left=`${o.left+window.scrollX}px`,i.style.zIndex="9999",i.style.background="transparent",i.style.border="none",i.style.cursor="pointer";const c=document.createElement("img");c.src=chrome.runtime.getURL("ui/icon.png"),c.alt="保存",c.style.width="24px",c.style.height="24px",i.appendChild(c),i.dataset.extension="cliplex",document.body.appendChild(i),i.addEventListener("click",(()=>e(void 0,void 0,void 0,(function*(){const e={key:n,examples:[],note:"",addedDate:(new Date).toISOString(),priority:3},t=yield new Promise((t=>{chrome.runtime.sendMessage({type:"save_entry",entry:e},(e=>{t(e)}))}));t&&"ok"===t.status?console.log("保存しました in cliplex"):alert("保存に失敗しました in cliplex"),document.body.removeChild(i)})))),setTimeout((()=>{document.body.contains(i)&&document.body.removeChild(i)}),5e3)})))}))}})[619]()})(); \ No newline at end of file +(()=>{"use strict";var e={619:function(e,t,n){var o=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,c){function d(e){try{r(o.next(e))}catch(e){c(e)}}function l(e){try{r(o.throw(e))}catch(e){c(e)}}function r(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(d,l)}r((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=n(922);o(void 0,void 0,void 0,(function*(){var e;const t=window.location.hostname;console.log(`Current domain: ${t}`);const n=yield chrome.storage.local.get(["domainFilterMode","excluded_domains","included_domains"]),c=null!==(e=n.domainFilterMode)&&void 0!==e?e:"exclude",d=n.excluded_domains||[],l=n.included_domains||[];(0,i.checkDomainFilterMode)(c,d,l,t)?(document.addEventListener("scroll",(()=>{const e=document.querySelector('button[data-extension="cliplex"]');e&&document.body.removeChild(e)})),document.addEventListener("click",(e=>{const t=document.querySelector('button[data-extension="cliplex"]');if(t&&!t.contains(e.target)){const n=t.getBoundingClientRect(),o=50;(e.clientXn.right+o||e.clientYn.bottom+o)&&document.body.removeChild(t)}})),document.addEventListener("mouseup",(()=>{const e=window.getSelection();if(!e)return;const t=e.toString().trim();if(!t)return;const n=e.getRangeAt(0).getBoundingClientRect(),i=document.createElement("button");i.style.position="absolute",i.style.top=`${n.bottom+window.scrollY}px`,i.style.left=`${n.left+window.scrollX}px`,i.style.zIndex="9999",i.style.background="transparent",i.style.border="none",i.style.cursor="pointer";const c=document.createElement("img");c.src=chrome.runtime.getURL("ui/icon.png"),c.alt="保存",c.style.width="24px",c.style.height="24px",i.appendChild(c),i.dataset.extension="cliplex",document.body.appendChild(i),i.addEventListener("click",(()=>o(void 0,void 0,void 0,(function*(){const e={key:t,examples:[],note:"",addedDate:(new Date).toISOString(),priority:3},n=yield new Promise((t=>{chrome.runtime.sendMessage({type:"save_entry",entry:e},(e=>{t(e)}))}));n&&"ok"===n.status?console.log("保存しました in cliplex"):alert("保存に失敗しました in cliplex"),document.body.removeChild(i)})))),setTimeout((()=>{document.body.contains(i)&&document.body.removeChild(i)}),5e3)}))):console.log("Content script blocked for this domain.")}))},922:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.checkDomainFilterMode=function(e,t,n,o){return"exclude"===e?!t.some((e=>e.domain===o)):n.some((e=>e.domain===o))}}},t={};!function n(o){var i=t[o];if(void 0!==i)return i.exports;var c=t[o]={exports:{}};return e[o].call(c.exports,c,c.exports,n),c.exports}(619)})(); \ No newline at end of file diff --git a/dist/options/options.html b/dist/options/options.html index 7951707..3c8d480 100644 --- a/dist/options/options.html +++ b/dist/options/options.html @@ -84,6 +84,18 @@

Cliplex

+

ドメインフィルターモード

+
+ + +
+

ドメインブロック設定

@@ -92,6 +104,13 @@

ドメインブロック設定

    +

    ドメイン限定設定 (Included)

    +
    + + +
    +
      +
      diff --git a/dist/options/options.js b/dist/options/options.js index 300f08c..07bc8d9 100644 --- a/dist/options/options.js +++ b/dist/options/options.js @@ -1 +1 @@ -(()=>{"use strict";var e={379:function(e,t){var n=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,l){function a(e){try{r(o.next(e))}catch(e){l(e)}}function d(e){try{r(o.throw(e))}catch(e){l(e)}}function r(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,d)}r((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains"}getAll(){return n(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAll(),n=t.findIndex((t=>t.key===e.key));n>-1?t[n]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}}},115:function(e,t,n){var o=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,l){function a(e){try{r(o.next(e))}catch(e){l(e)}}function d(e){try{r(o.throw(e))}catch(e){l(e)}}function r(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,d)}r((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(n(379).LocalDatabase),l=document.getElementById("dateFrom"),a=document.getElementById("dateTo"),d=document.getElementById("filterButton"),r=document.getElementById("prioritySort"),c=document.getElementById("wordsTableBody");let s=[];function u(){return o(this,arguments,void 0,(function*(e={}){let t=yield i.getAll();e.from&&(t=t.filter((t=>new Date(t.addedDate)>=e.from))),e.to&&(t=t.filter((t=>new Date(t.addedDate)<=e.to))),e.prioritySort?t.sort(((t,n)=>"asc"===e.prioritySort?t.priority===n.priority?new Date(t.addedDate).getTime()-new Date(n.addedDate).getTime():t.priority-n.priority:"desc"===e.prioritySort?t.priority===n.priority?new Date(n.addedDate).getTime()-new Date(t.addedDate).getTime():n.priority-t.priority:0)):t.sort(((e,t)=>e.priority===t.priority?new Date(t.addedDate).getTime()-new Date(e.addedDate).getTime():t.priority-e.priority)),s=t,v()}))}let m=1;const p=10,y=document.getElementById("pagination");function h(){y.innerHTML="";const e=Math.ceil(s.length/p),t=document.createElement("button");t.textContent="前へ",t.className="pagination-button",t.disabled=1===m,t.addEventListener("click",(()=>{m>1&&(m--,v(),h())})),y.appendChild(t);const n=document.createElement("button");n.textContent="次へ",n.className="pagination-button",n.disabled=m===e,n.addEventListener("click",(()=>{m${e.key}\n \n \n \n \n \n \n \n \n \n \n
        \n ${e.examples.map(((e,t)=>`\n
      • ${e}
      • \n `)).join("")}\n
      \n \n \n \n \n \n ${new Date(e.addedDate).toLocaleString()}\n \n \n \n \n \n \n \n `,t.querySelector(".add-example").addEventListener("click",(()=>{const t=prompt("新しい例文を入力してください:");t&&(e.examples.push(t),v())})),t.querySelectorAll(".delete-example").forEach((t=>{t.addEventListener("click",(()=>{const n=Number(t.getAttribute("data-index"));e.examples.splice(n,1),v()}))})),t.querySelector(".save-changes").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){const n=t.querySelector(".note-area"),o=t.querySelector(".priority-select");e.note=n.value,e.priority=Number(o.value),yield i.save(e),console.log("保存しました")})))),t.querySelector(".delete-key").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.delete(e.key),u()})))),c.appendChild(t)}h()}d.addEventListener("click",(()=>{u({from:l.value?new Date(l.value):void 0,to:a.value?new Date(a.value):void 0,prioritySort:r.value})}));const f=document.getElementById("excludedDomainInput"),E=document.getElementById("addExcludedDomainButton"),g=document.getElementById("excludedDomainList");function D(){return o(this,void 0,void 0,(function*(){const e=yield i.getAllExcludedDomains();g.innerHTML="";for(const t of e){const e=document.createElement("li");e.textContent=t.domain;const n=document.createElement("button");n.textContent="削除",n.addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.removeExcludedDomain(t.domain),D()})))),e.appendChild(n),g.appendChild(e)}}))}E.addEventListener("click",(()=>o(void 0,void 0,void 0,(function*(){const e=f.value.trim();e&&(yield i.addExcludedDomain(e),f.value="",D())})))),D(),u()}},t={};!function n(o){var i=t[o];if(void 0!==i)return i.exports;var l=t[o]={exports:{}};return e[o].call(l.exports,l,l.exports,n),l.exports}(115)})(); \ No newline at end of file +(()=>{"use strict";var e={379:function(e,t){var n=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains",this.INCLUDED_DOMAIN_KEY="included_domains"}getAll(){return n(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAll(),n=t.findIndex((t=>t.key===e.key));n>-1?t[n]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}getAllIncludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.INCLUDED_DOMAIN_KEY))[this.INCLUDED_DOMAIN_KEY]||[]}))}addIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllIncludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t}))}))}removeIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllIncludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t})}))}}},115:function(e,t,n){var o=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(n(379).LocalDatabase),d=document.getElementById("dateFrom"),l=document.getElementById("dateTo"),a=document.getElementById("filterButton"),c=document.getElementById("prioritySort"),r=document.getElementById("wordsTableBody");let s=[];function u(){return o(this,arguments,void 0,(function*(e={}){let t=yield i.getAll();e.from&&(t=t.filter((t=>new Date(t.addedDate)>=e.from))),e.to&&(t=t.filter((t=>new Date(t.addedDate)<=e.to))),e.prioritySort?t.sort(((t,n)=>"asc"===e.prioritySort?t.priority===n.priority?new Date(t.addedDate).getTime()-new Date(n.addedDate).getTime():t.priority-n.priority:"desc"===e.prioritySort?t.priority===n.priority?new Date(n.addedDate).getTime()-new Date(t.addedDate).getTime():n.priority-t.priority:0)):t.sort(((e,t)=>e.priority===t.priority?new Date(t.addedDate).getTime()-new Date(e.addedDate).getTime():t.priority-e.priority)),s=t,v()}))}let m=1;const h=10,y=document.getElementById("pagination");function p(){y.innerHTML="";const e=Math.ceil(s.length/h),t=document.createElement("button");t.textContent="前へ",t.className="pagination-button",t.disabled=1===m,t.addEventListener("click",(()=>{m>1&&(m--,v(),p())})),y.appendChild(t);const n=document.createElement("button");n.textContent="次へ",n.className="pagination-button",n.disabled=m===e,n.addEventListener("click",(()=>{m${e.key}\n \n \n \n \n \n \n \n \n \n \n
        \n ${e.examples.map(((e,t)=>`\n
      • ${e}
      • \n `)).join("")}\n
      \n \n \n \n \n \n ${new Date(e.addedDate).toLocaleString()}\n \n \n \n \n \n \n \n `,t.querySelector(".add-example").addEventListener("click",(()=>{const t=prompt("新しい例文を入力してください:");t&&(e.examples.push(t),v())})),t.querySelectorAll(".delete-example").forEach((t=>{t.addEventListener("click",(()=>{const n=Number(t.getAttribute("data-index"));e.examples.splice(n,1),v()}))})),t.querySelector(".save-changes").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){const n=t.querySelector(".note-area"),o=t.querySelector(".priority-select");e.note=n.value,e.priority=Number(o.value),yield i.save(e),console.log("保存しました")})))),t.querySelector(".delete-key").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.delete(e.key),u()})))),r.appendChild(t)}p()}a.addEventListener("click",(()=>{u({from:d.value?new Date(d.value):void 0,to:l.value?new Date(l.value):void 0,prioritySort:c.value})}));const E=document.getElementById("excludedDomainInput"),D=document.getElementById("addExcludedDomainButton"),f=document.getElementById("excludedDomainList");function g(){return o(this,void 0,void 0,(function*(){const e=yield i.getAllExcludedDomains();f.innerHTML="";for(const t of e){const e=document.createElement("li");e.textContent=t.domain;const n=document.createElement("button");n.textContent="削除",n.addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.removeExcludedDomain(t.domain),g()})))),e.appendChild(n),f.appendChild(e)}}))}D.addEventListener("click",(()=>o(void 0,void 0,void 0,(function*(){const e=E.value.trim();e&&(yield i.addExcludedDomain(e),E.value="",g())}))));const _="domainFilterMode",x=document.getElementById("radioExclude"),I=document.getElementById("radioInclude");g(),u(),function(){o(this,void 0,void 0,(function*(){var e;"include"===(null!==(e=(yield chrome.storage.local.get(_))[_])&&void 0!==e?e:"exclude")?I.checked=!0:x.checked=!0}))}(),function(){[x,I].forEach((e=>{e.addEventListener("change",(()=>o(this,void 0,void 0,(function*(){e.checked&&(yield chrome.storage.local.set({[_]:e.value}))}))))}))}()}},t={};!function n(o){var i=t[o];if(void 0!==i)return i.exports;var d=t[o]={exports:{}};return e[o].call(d.exports,d,d.exports,n),d.exports}(115)})(); \ No newline at end of file diff --git a/dist/options/options.ts b/dist/options/options.ts index 9f1a4dc..cb86cb2 100644 --- a/dist/options/options.ts +++ b/dist/options/options.ts @@ -270,5 +270,37 @@ addExcludedDomainButton.addEventListener("click", async () => { } }); +// 1) モード保存先キーを決める +const DOMAIN_FILTER_MODE_KEY = "domainFilterMode"; + +// 2) ラジオボタン要素を取得 +const radioExclude = document.getElementById("radioExclude") as HTMLInputElement; +const radioInclude = document.getElementById("radioInclude") as HTMLInputElement; + +// 3) chrome.storage.local からモードを読み込んでUIに反映 +async function loadDomainFilterMode() { + const result = await chrome.storage.local.get(DOMAIN_FILTER_MODE_KEY); + const mode = result[DOMAIN_FILTER_MODE_KEY] ?? "exclude"; // デフォルト exclude + if (mode === "include") { + radioInclude.checked = true; + } else { + radioExclude.checked = true; // exclude + } +} + +// 4) ラジオボタンの変更を検知して保存 +function setupDomainFilterModeListeners() { + [radioExclude, radioInclude].forEach(radio => { + radio.addEventListener("change", async () => { + if (radio.checked) { + await chrome.storage.local.set({ [DOMAIN_FILTER_MODE_KEY]: radio.value }); + } + }); + }); +} + renderExcludedDomains(); loadData(); +loadDomainFilterMode(); +setupDomainFilterModeListeners(); + diff --git a/src/content_script.ts b/src/content_script.ts index 5ba79a3..d4ef4bd 100644 --- a/src/content_script.ts +++ b/src/content_script.ts @@ -1,13 +1,20 @@ +import { checkDomainFilterMode } from "./content_script_helper"; + (async () => { const currentDomain = window.location.hostname; console.log(`Current domain: ${currentDomain}`); - const storage = await chrome.storage.local.get("excluded_domains"); + const storage = await chrome.storage.local.get([ + "domainFilterMode", // "exclude" or "include" + "excluded_domains", + "included_domains" + ]); + const mode = storage["domainFilterMode"] ?? "exclude"; const excludedDomains: DomainEntry[] = storage["excluded_domains"] || []; - const isExcluded = excludedDomains.some((d) => d.domain === currentDomain); + const includedDomains: DomainEntry[] = storage["included_domains"] || []; - if (isExcluded) { - // ブロック対象のドメインなので、このコンテントスクリプトの機能を停止 - console.log(`This domain (${currentDomain}) is excluded from ClipLex.`); + const shouldRun = checkDomainFilterMode(mode, excludedDomains, includedDomains, currentDomain); + if (!shouldRun) { + console.log("Content script blocked for this domain."); return; } diff --git a/src/content_script_helper.ts b/src/content_script_helper.ts new file mode 100644 index 0000000..1522b05 --- /dev/null +++ b/src/content_script_helper.ts @@ -0,0 +1,15 @@ +export function checkDomainFilterMode( + mode: "include" | "exclude", + excludedDomains: DomainEntry[], + includedDomains: DomainEntry[], + currentDomain: string +): boolean { + if (mode === "exclude") { + // もしexcludedに含まれていれば false(ブロック) + return !excludedDomains.some(d => d.domain === currentDomain); + } else { + // includeの場合、含まれていれば true(実行OK) + return includedDomains.some(d => d.domain === currentDomain); + } +} + diff --git a/src/database.ts b/src/database.ts index b67ca62..d767d78 100644 --- a/src/database.ts +++ b/src/database.ts @@ -2,6 +2,7 @@ export class LocalDatabase { private STORAGE_KEY = "word_entries"; private EXCLUDED_DOMAIN_KEY = "excluded_domains"; + private INCLUDED_DOMAIN_KEY = "included_domains"; public async getAll(): Promise { console.log("Getting all entries from storage..."); @@ -46,4 +47,25 @@ export class LocalDatabase { const newAll = all.filter((entry) => entry.domain !== domain); await chrome.storage.local.set({ [this.EXCLUDED_DOMAIN_KEY]: newAll }); } + + // === IncludedDomain 用 === + public async getAllIncludedDomains(): Promise { + const result = await chrome.storage.local.get(this.INCLUDED_DOMAIN_KEY); + return result[this.INCLUDED_DOMAIN_KEY] || []; + } + + public async addIncludedDomain(domain: string): Promise { + const all = await this.getAllIncludedDomains(); + // 重複チェック + if (!all.some((entry) => entry.domain === domain)) { + all.push({ domain }); + await chrome.storage.local.set({ [this.INCLUDED_DOMAIN_KEY]: all }); + } + } + + public async removeIncludedDomain(domain: string): Promise { + const all = await this.getAllIncludedDomains(); + const newAll = all.filter((entry) => entry.domain !== domain); + await chrome.storage.local.set({ [this.INCLUDED_DOMAIN_KEY]: newAll }); + } } diff --git a/src/options/options.html b/src/options/options.html index 7951707..3c8d480 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -84,6 +84,18 @@

      Cliplex

      +

      ドメインフィルターモード

      +
      + + +
      +

      ドメインブロック設定

      @@ -92,6 +104,13 @@

      ドメインブロック設定

        +

        ドメイン限定設定 (Included)

        +
        + + +
        +
          +
          diff --git a/src/options/options.ts b/src/options/options.ts index 9f1a4dc..cb86cb2 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -270,5 +270,37 @@ addExcludedDomainButton.addEventListener("click", async () => { } }); +// 1) モード保存先キーを決める +const DOMAIN_FILTER_MODE_KEY = "domainFilterMode"; + +// 2) ラジオボタン要素を取得 +const radioExclude = document.getElementById("radioExclude") as HTMLInputElement; +const radioInclude = document.getElementById("radioInclude") as HTMLInputElement; + +// 3) chrome.storage.local からモードを読み込んでUIに反映 +async function loadDomainFilterMode() { + const result = await chrome.storage.local.get(DOMAIN_FILTER_MODE_KEY); + const mode = result[DOMAIN_FILTER_MODE_KEY] ?? "exclude"; // デフォルト exclude + if (mode === "include") { + radioInclude.checked = true; + } else { + radioExclude.checked = true; // exclude + } +} + +// 4) ラジオボタンの変更を検知して保存 +function setupDomainFilterModeListeners() { + [radioExclude, radioInclude].forEach(radio => { + radio.addEventListener("change", async () => { + if (radio.checked) { + await chrome.storage.local.set({ [DOMAIN_FILTER_MODE_KEY]: radio.value }); + } + }); + }); +} + renderExcludedDomains(); loadData(); +loadDomainFilterMode(); +setupDomainFilterModeListeners(); + diff --git a/tests/e2e/content_script.spec.ts b/tests/e2e/content_script.spec.ts index c276b5e..9393dac 100644 --- a/tests/e2e/content_script.spec.ts +++ b/tests/e2e/content_script.spec.ts @@ -2,16 +2,15 @@ import { test, expect, chromium } from "@playwright/test"; import * as path from "path"; -test("Content script should create a button when selecting 'World' text", async () => { - // 拡張機能 dist フォルダへのパス (webpack build 後の出力先) +test("Content script should create a button when selecting 'World' text if domein is included, then fail to create if domain is excluded", async () => { + // 拡張機能 dist フォルダへのパス const distPath = path.resolve(__dirname, "../../dist"); - - // 永続化されたユーザーデータディレクトリ (一時的に作ると良い) + // 永続化されたユーザーデータディレクトリ const userDataDir = path.resolve(__dirname, "../../.tmp-user-data"); - // 拡張機能を起動したChromiumブラウザを立ち上げる + // 1) Chromium起動 const context = await chromium.launchPersistentContext(userDataDir, { - headless: false, // 動きを目視確認したい時は false (CIで動かすなら true でもOK) + headless: false, args: [ `--disable-extensions-except=${distPath}`, `--load-extension=${distPath}`, @@ -19,52 +18,82 @@ test("Content script should create a button when selecting 'World' text", async }); const page = await context.newPage(); - // テスト先のURLへアクセス await page.goto("https://makinzm.github.io/rust-wasm-github/"); await page.waitForLoadState("domcontentloaded"); - // もしページ内に「Hello World」のテキストがあればクリックやダブルクリックで選択してみる。 - // ここではダブルクリックだけで選択できるか試す(要素次第で工夫が必要)。 - await page.getByRole('heading', { name: 'Hello World' }).dblclick(); + // ========== (A) 押せるパターン ========== + + // 1) domainFilterMode を "exclude" にし、対象ドメインを included_domains に追加 + // (Service Worker 上で chrome.storage.local.set) + let [bgWorker] = context.serviceWorkers(); + await bgWorker.evaluate(async () => { + chrome.storage.local.set({ + domainFilterMode: "include", + included_domains: [{ domain: "makinzm.github.io" }] + // excluded_domains: ... は関係なければ省略 + }); + }); + + // リロードして Service Worker が再評価されるまで待つ + await page.reload(); + await page.waitForLoadState("domcontentloaded"); - // コンテンツスクリプトの動作(ボタン生成など)を待つ - // (本来は button[data-extension='cliplex'] が見つかるまで待つのがベター) + // 「Hello World」テキストをダブルクリックで選択 + await page.getByRole('heading', { name: 'Hello World' }).dblclick(); + // ボタン生成を(簡易的に)待つ await page.waitForTimeout(1000); - // 生成されたはずのボタン要素を確認 - const button = await page.$("button[data-extension='cliplex']"); - expect(button).not.toBeNull(); + // ボタンがあるか確認 + let button = await page.$("button[data-extension='cliplex']"); + expect(button).not.toBeNull(); // 押せる - // ボタンをクリックして保存処理が走るか確認 (お試し) + // ボタンをクリック if (button) { await button.click(); - // 背景スクリプト or console.log をモニタリングしてレスポンスを確認する手法もあるが、 - // ひとまず「ボタンがクリックできる」ことまで確認する。 } - - // クリック後、保存処理が走って background(script) が chrome.storage に書き込むはず - // 少し待機(本番なら waitForEvent/message などを使うとベター) + // 保存処理の後、少し待つ await page.waitForTimeout(1000); - // MV3拡張機能の service worker(=background) を取得 - const [bgWorker] = context.serviceWorkers(); + // Service Workerを取得し、実際にデータが入ったか確認 + [bgWorker] = context.serviceWorkers(); expect(bgWorker).toBeTruthy(); - // service worker 上下で `chrome.storage.local.get` を実行 - // evaluate() は Service Worker のJS空間で動くので `chrome` が使える - const storedEntries = await bgWorker.evaluate(async () => { + let storedEntries = await bgWorker.evaluate(async () => { return new Promise((resolve) => { chrome.storage.local.get("word_entries", (res) => { const entries = res.word_entries || []; - resolve(entries.map((e: any) => e.key)); // ここでは key だけ取り出す例 + resolve(entries.map((e: any) => e.key)); }); }); }); - - // "Hello World" が保存されているか確認 - // WARN: なぜか World だけが保存される場合があるので注意 + // WARN: なぜか "Hello" が入っていないことがある expect(storedEntries).toContain("World"); + // ========== (B) 押せないパターン ========== + + // 2) domainFilterMode を "exclude" にし、対象ドメインを excluded_domains に追加 + // (同じ Service Worker 上下で chrome.storage.local.set) + [bgWorker] = context.serviceWorkers(); // 取り直してもOK + await bgWorker.evaluate(async () => { + chrome.storage.local.set({ + domainFilterMode: "exclude", + excluded_domains: [{ domain: "makinzm.github.io" }] + // included_domains: ... は関係なければ省略 + }); + }); + + // 3) ページをリロード or 違うタブを開いて戻るなど、content_script が再評価される状況にする + await page.reload(); + await page.waitForLoadState("domcontentloaded"); + + // 4) 再度テキスト選択 + await page.getByRole('heading', { name: 'Hello World' }).dblclick(); + await page.waitForTimeout(1000); + + // 5) 今度はボタンが生成されないはず + button = await page.$("button[data-extension='cliplex']"); + expect(button).toBeNull(); // 押せないことを確認 + await context.close(); }); diff --git a/tests/unit/content_script_helper.spec.ts b/tests/unit/content_script_helper.spec.ts new file mode 100644 index 0000000..c4fc1ee --- /dev/null +++ b/tests/unit/content_script_helper.spec.ts @@ -0,0 +1,35 @@ +import { checkDomainFilterMode } from "../../src/content_script_helper"; + +describe("Domain filter mode logic", () => { + it("exclude mode => block if domain is in excludedDomains", () => { + const mode = "exclude"; + const excluded = [{ domain: "abc.com" }]; + const included = [{ domain: "xyz.com" }]; // irrelevant + const current = "abc.com"; + + const result = checkDomainFilterMode(mode, excluded, included, current); + // result => false means "stop content script" + expect(result).toBe(false); + }); + + it("include mode => block if domain is not in includedDomains", () => { + const mode = "include"; + const excluded = [{ domain: "abc.com" }]; // irrelevant + const included = [{ domain: "xyz.com" }]; + const current = "abc.com"; + + const result = checkDomainFilterMode(mode, excluded, included, current); + expect(result).toBe(false); + }); + + it("include mode => pass if domain is in includedDomains", () => { + const mode = "include"; + const included = [{ domain: "xyz.com" }]; + const current = "xyz.com"; + + const result = checkDomainFilterMode(mode, [], included, current); + // result => true means "let the script run" + expect(result).toBe(true); + }); +}); + From 1bc6f7dd19c8e7fc27ba2d068e1dfcf6f7adf0e3 Mon Sep 17 00:00:00 2001 From: makinzm Date: Sun, 22 Dec 2024 00:59:58 +0900 Subject: [PATCH 2/4] fix: ui --- dist/options/options.html | 31 ++++++++++++++++++------------- dist/options/options.js | 2 +- dist/options/options.ts | 32 ++++++++++++++++++++++++++++++++ src/options/options.html | 31 ++++++++++++++++++------------- src/options/options.ts | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 27 deletions(-) diff --git a/dist/options/options.html b/dist/options/options.html index 3c8d480..c57e87f 100644 --- a/dist/options/options.html +++ b/dist/options/options.html @@ -88,28 +88,33 @@

          ドメインフィルターモード

          - -

          ドメインブロック設定

          -
          - - + + -
            -

            ドメイン限定設定 (Included)

            -
            - - + + -
              diff --git a/dist/options/options.js b/dist/options/options.js index 07bc8d9..af81234 100644 --- a/dist/options/options.js +++ b/dist/options/options.js @@ -1 +1 @@ -(()=>{"use strict";var e={379:function(e,t){var n=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains",this.INCLUDED_DOMAIN_KEY="included_domains"}getAll(){return n(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAll(),n=t.findIndex((t=>t.key===e.key));n>-1?t[n]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}getAllIncludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.INCLUDED_DOMAIN_KEY))[this.INCLUDED_DOMAIN_KEY]||[]}))}addIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllIncludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t}))}))}removeIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllIncludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t})}))}}},115:function(e,t,n){var o=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(n(379).LocalDatabase),d=document.getElementById("dateFrom"),l=document.getElementById("dateTo"),a=document.getElementById("filterButton"),c=document.getElementById("prioritySort"),r=document.getElementById("wordsTableBody");let s=[];function u(){return o(this,arguments,void 0,(function*(e={}){let t=yield i.getAll();e.from&&(t=t.filter((t=>new Date(t.addedDate)>=e.from))),e.to&&(t=t.filter((t=>new Date(t.addedDate)<=e.to))),e.prioritySort?t.sort(((t,n)=>"asc"===e.prioritySort?t.priority===n.priority?new Date(t.addedDate).getTime()-new Date(n.addedDate).getTime():t.priority-n.priority:"desc"===e.prioritySort?t.priority===n.priority?new Date(n.addedDate).getTime()-new Date(t.addedDate).getTime():n.priority-t.priority:0)):t.sort(((e,t)=>e.priority===t.priority?new Date(t.addedDate).getTime()-new Date(e.addedDate).getTime():t.priority-e.priority)),s=t,v()}))}let m=1;const h=10,y=document.getElementById("pagination");function p(){y.innerHTML="";const e=Math.ceil(s.length/h),t=document.createElement("button");t.textContent="前へ",t.className="pagination-button",t.disabled=1===m,t.addEventListener("click",(()=>{m>1&&(m--,v(),p())})),y.appendChild(t);const n=document.createElement("button");n.textContent="次へ",n.className="pagination-button",n.disabled=m===e,n.addEventListener("click",(()=>{m${e.key}\n \n \n \n \n \n \n \n \n \n \n
                \n ${e.examples.map(((e,t)=>`\n
              • ${e}
              • \n `)).join("")}\n
              \n \n \n \n \n \n ${new Date(e.addedDate).toLocaleString()}\n \n \n \n \n \n \n \n `,t.querySelector(".add-example").addEventListener("click",(()=>{const t=prompt("新しい例文を入力してください:");t&&(e.examples.push(t),v())})),t.querySelectorAll(".delete-example").forEach((t=>{t.addEventListener("click",(()=>{const n=Number(t.getAttribute("data-index"));e.examples.splice(n,1),v()}))})),t.querySelector(".save-changes").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){const n=t.querySelector(".note-area"),o=t.querySelector(".priority-select");e.note=n.value,e.priority=Number(o.value),yield i.save(e),console.log("保存しました")})))),t.querySelector(".delete-key").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.delete(e.key),u()})))),r.appendChild(t)}p()}a.addEventListener("click",(()=>{u({from:d.value?new Date(d.value):void 0,to:l.value?new Date(l.value):void 0,prioritySort:c.value})}));const E=document.getElementById("excludedDomainInput"),D=document.getElementById("addExcludedDomainButton"),f=document.getElementById("excludedDomainList");function g(){return o(this,void 0,void 0,(function*(){const e=yield i.getAllExcludedDomains();f.innerHTML="";for(const t of e){const e=document.createElement("li");e.textContent=t.domain;const n=document.createElement("button");n.textContent="削除",n.addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.removeExcludedDomain(t.domain),g()})))),e.appendChild(n),f.appendChild(e)}}))}D.addEventListener("click",(()=>o(void 0,void 0,void 0,(function*(){const e=E.value.trim();e&&(yield i.addExcludedDomain(e),E.value="",g())}))));const _="domainFilterMode",x=document.getElementById("radioExclude"),I=document.getElementById("radioInclude");g(),u(),function(){o(this,void 0,void 0,(function*(){var e;"include"===(null!==(e=(yield chrome.storage.local.get(_))[_])&&void 0!==e?e:"exclude")?I.checked=!0:x.checked=!0}))}(),function(){[x,I].forEach((e=>{e.addEventListener("change",(()=>o(this,void 0,void 0,(function*(){e.checked&&(yield chrome.storage.local.set({[_]:e.value}))}))))}))}()}},t={};!function n(o){var i=t[o];if(void 0!==i)return i.exports;var d=t[o]={exports:{}};return e[o].call(d.exports,d,d.exports,n),d.exports}(115)})(); \ No newline at end of file +(()=>{"use strict";var e={379:function(e,t){var n=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.LocalDatabase=void 0,t.LocalDatabase=class{constructor(){this.STORAGE_KEY="word_entries",this.EXCLUDED_DOMAIN_KEY="excluded_domains",this.INCLUDED_DOMAIN_KEY="included_domains"}getAll(){return n(this,void 0,void 0,(function*(){console.log("Getting all entries from storage...");const e=yield chrome.storage.local.get(this.STORAGE_KEY);return console.log("All entries:",e),e[this.STORAGE_KEY]||[]}))}save(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAll(),n=t.findIndex((t=>t.key===e.key));n>-1?t[n]=e:t.push(e),yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}delete(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAll()).filter((t=>t.key!==e));yield chrome.storage.local.set({[this.STORAGE_KEY]:t})}))}getAllExcludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.EXCLUDED_DOMAIN_KEY))[this.EXCLUDED_DOMAIN_KEY]||[]}))}addExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllExcludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t}))}))}removeExcludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllExcludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.EXCLUDED_DOMAIN_KEY]:t})}))}getAllIncludedDomains(){return n(this,void 0,void 0,(function*(){return(yield chrome.storage.local.get(this.INCLUDED_DOMAIN_KEY))[this.INCLUDED_DOMAIN_KEY]||[]}))}addIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=yield this.getAllIncludedDomains();t.some((t=>t.domain===e))||(t.push({domain:e}),yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t}))}))}removeIncludedDomain(e){return n(this,void 0,void 0,(function*(){const t=(yield this.getAllIncludedDomains()).filter((t=>t.domain!==e));yield chrome.storage.local.set({[this.INCLUDED_DOMAIN_KEY]:t})}))}}},115:function(e,t,n){var o=this&&this.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,d){function l(e){try{c(o.next(e))}catch(e){d(e)}}function a(e){try{c(o.throw(e))}catch(e){d(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(l,a)}c((o=o.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0});const i=new(n(379).LocalDatabase),d=document.getElementById("dateFrom"),l=document.getElementById("dateTo"),a=document.getElementById("filterButton"),c=document.getElementById("prioritySort"),r=document.getElementById("wordsTableBody");let s=[];function u(){return o(this,arguments,void 0,(function*(e={}){let t=yield i.getAll();e.from&&(t=t.filter((t=>new Date(t.addedDate)>=e.from))),e.to&&(t=t.filter((t=>new Date(t.addedDate)<=e.to))),e.prioritySort?t.sort(((t,n)=>"asc"===e.prioritySort?t.priority===n.priority?new Date(t.addedDate).getTime()-new Date(n.addedDate).getTime():t.priority-n.priority:"desc"===e.prioritySort?t.priority===n.priority?new Date(n.addedDate).getTime()-new Date(t.addedDate).getTime():n.priority-t.priority:0)):t.sort(((e,t)=>e.priority===t.priority?new Date(t.addedDate).getTime()-new Date(e.addedDate).getTime():t.priority-e.priority)),s=t,v()}))}let m=1;const h=10,y=document.getElementById("pagination");function p(){y.innerHTML="";const e=Math.ceil(s.length/h),t=document.createElement("button");t.textContent="前へ",t.className="pagination-button",t.disabled=1===m,t.addEventListener("click",(()=>{m>1&&(m--,v(),p())})),y.appendChild(t);const n=document.createElement("button");n.textContent="次へ",n.className="pagination-button",n.disabled=m===e,n.addEventListener("click",(()=>{m${e.key}\n \n \n \n \n \n \n \n \n \n \n
                \n ${e.examples.map(((e,t)=>`\n
              • ${e}
              • \n `)).join("")}\n
              \n \n \n \n \n \n ${new Date(e.addedDate).toLocaleString()}\n \n \n \n \n \n \n \n `,t.querySelector(".add-example").addEventListener("click",(()=>{const t=prompt("新しい例文を入力してください:");t&&(e.examples.push(t),v())})),t.querySelectorAll(".delete-example").forEach((t=>{t.addEventListener("click",(()=>{const n=Number(t.getAttribute("data-index"));e.examples.splice(n,1),v()}))})),t.querySelector(".save-changes").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){const n=t.querySelector(".note-area"),o=t.querySelector(".priority-select");e.note=n.value,e.priority=Number(o.value),yield i.save(e),console.log("保存しました")})))),t.querySelector(".delete-key").addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.delete(e.key),u()})))),r.appendChild(t)}p()}a.addEventListener("click",(()=>{u({from:d.value?new Date(d.value):void 0,to:l.value?new Date(l.value):void 0,prioritySort:c.value})}));const g=document.getElementById("excludedDomainInput"),E=document.getElementById("addExcludedDomainButton"),D=document.getElementById("excludedDomainList");function f(){return o(this,void 0,void 0,(function*(){const e=yield i.getAllExcludedDomains();D.innerHTML="";for(const t of e){const e=document.createElement("li");e.textContent=t.domain;const n=document.createElement("button");n.textContent="削除",n.addEventListener("click",(()=>o(this,void 0,void 0,(function*(){yield i.removeExcludedDomain(t.domain),f()})))),e.appendChild(n),D.appendChild(e)}}))}E.addEventListener("click",(()=>o(void 0,void 0,void 0,(function*(){const e=g.value.trim();e&&(yield i.addExcludedDomain(e),g.value="",f())}))));const x="domainFilterMode",I=document.getElementById("radioExclude"),_=document.getElementById("radioInclude"),k=document.getElementById("excludeSettings"),b=document.getElementById("includeSettings");function L(e){"exclude"===e?(k.style.display="block",b.style.display="none"):(k.style.display="none",b.style.display="block"),localStorage.setItem("domainFilterMode",e)}I.addEventListener("change",(()=>L("exclude"))),_.addEventListener("change",(()=>L("include"))),"exclude"===(localStorage.getItem("domainFilterMode")||"include")?(I.checked=!0,L("exclude")):(_.checked=!0,L("include")),f(),u(),function(){o(this,void 0,void 0,(function*(){var e;"include"===(null!==(e=(yield chrome.storage.local.get(x))[x])&&void 0!==e?e:"exclude")?_.checked=!0:I.checked=!0}))}(),function(){[I,_].forEach((e=>{e.addEventListener("change",(()=>o(this,void 0,void 0,(function*(){e.checked&&(yield chrome.storage.local.set({[x]:e.value}))}))))}))}()}},t={};!function n(o){var i=t[o];if(void 0!==i)return i.exports;var d=t[o]={exports:{}};return e[o].call(d.exports,d,d.exports,n),d.exports}(115)})(); \ No newline at end of file diff --git a/dist/options/options.ts b/dist/options/options.ts index cb86cb2..cdf9804 100644 --- a/dist/options/options.ts +++ b/dist/options/options.ts @@ -299,6 +299,38 @@ function setupDomainFilterModeListeners() { }); } + +function initializeSettings() { + const storedMode = localStorage.getItem("domainFilterMode") || "include"; + if (storedMode === "exclude") { + radioExclude.checked = true; + toggleSettings("exclude"); + } else { + radioInclude.checked = true; + toggleSettings("include"); + } +} + +const excludeSettings = document.getElementById("excludeSettings") as HTMLDivElement; +const includeSettings = document.getElementById("includeSettings") as HTMLDivElement; + +function toggleSettings(mode: "exclude" | "include") { + if (mode === "exclude") { + excludeSettings.style.display = "block"; + includeSettings.style.display = "none"; + } else { + excludeSettings.style.display = "none"; + includeSettings.style.display = "block"; + } + // モードを保存 + localStorage.setItem("domainFilterMode", mode); +} + +// ラジオボタンのイベントリスナー +radioExclude.addEventListener("change", () => toggleSettings("exclude")); +radioInclude.addEventListener("change", () => toggleSettings("include")); + +initializeSettings(); renderExcludedDomains(); loadData(); loadDomainFilterMode(); diff --git a/src/options/options.html b/src/options/options.html index 3c8d480..c57e87f 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -88,28 +88,33 @@

              ドメインフィルターモード

              - -

              ドメインブロック設定

              -
              - - + + -
                -

                ドメイン限定設定 (Included)

                -
                - - + + -
                  diff --git a/src/options/options.ts b/src/options/options.ts index cb86cb2..cdf9804 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -299,6 +299,38 @@ function setupDomainFilterModeListeners() { }); } + +function initializeSettings() { + const storedMode = localStorage.getItem("domainFilterMode") || "include"; + if (storedMode === "exclude") { + radioExclude.checked = true; + toggleSettings("exclude"); + } else { + radioInclude.checked = true; + toggleSettings("include"); + } +} + +const excludeSettings = document.getElementById("excludeSettings") as HTMLDivElement; +const includeSettings = document.getElementById("includeSettings") as HTMLDivElement; + +function toggleSettings(mode: "exclude" | "include") { + if (mode === "exclude") { + excludeSettings.style.display = "block"; + includeSettings.style.display = "none"; + } else { + excludeSettings.style.display = "none"; + includeSettings.style.display = "block"; + } + // モードを保存 + localStorage.setItem("domainFilterMode", mode); +} + +// ラジオボタンのイベントリスナー +radioExclude.addEventListener("change", () => toggleSettings("exclude")); +radioInclude.addEventListener("change", () => toggleSettings("include")); + +initializeSettings(); renderExcludedDomains(); loadData(); loadDomainFilterMode(); From ccfd0922c49ecc73fcd46fc176e6190fc0badd25 Mon Sep 17 00:00:00 2001 From: makinzm Date: Sun, 22 Dec 2024 01:03:38 +0900 Subject: [PATCH 3/4] add ignore --- .gitignore | 1 + dist/options/options.ts | 338 ---------------------------------------- 2 files changed, 1 insertion(+), 338 deletions(-) delete mode 100644 dist/options/options.ts diff --git a/.gitignore b/.gitignore index b912018..6f03c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ coverage/ .tmp-user-data/ test-results/ +dist/*.ts diff --git a/dist/options/options.ts b/dist/options/options.ts deleted file mode 100644 index cdf9804..0000000 --- a/dist/options/options.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { LocalDatabase } from "../database"; - -const db = new LocalDatabase(); - -type FilterOptions = { - from?: Date; - to?: Date; - prioritySort?: "asc" | "desc" | ""; -}; - -const dateFromEl = document.getElementById("dateFrom") as HTMLInputElement; -const dateToEl = document.getElementById("dateTo") as HTMLInputElement; -const filterButton = document.getElementById( - "filterButton", -) as HTMLButtonElement; -const prioritySortEl = document.getElementById( - "prioritySort", -) as HTMLSelectElement; -const tableBody = document.getElementById( - "wordsTableBody", -) as HTMLTableSectionElement; - -let currentData: WordEntry[] = []; - -async function loadData(filter: FilterOptions = {}) { - let data = await db.getAll(); - - // フィルタリング - if (filter.from) { - data = data.filter((d) => new Date(d.addedDate) >= filter.from!); - } - if (filter.to) { - data = data.filter((d) => new Date(d.addedDate) <= filter.to!); - } - - // ソート(フィルタの優先度ソート設定を適用) - if (filter.prioritySort) { - data.sort((a, b) => { - if (filter.prioritySort === "asc") { - if (a.priority === b.priority) { - return ( - new Date(a.addedDate).getTime() - new Date(b.addedDate).getTime() - ); - } - return a.priority - b.priority; // 優先度昇順 - } else if (filter.prioritySort === "desc") { - if (a.priority === b.priority) { - return ( - new Date(b.addedDate).getTime() - new Date(a.addedDate).getTime() - ); - } - return b.priority - a.priority; // 優先度降順 - } - return 0; - }); - } else { - // デフォルトソート: 優先度降順、追加日降順 - data.sort((a, b) => { - if (a.priority === b.priority) { - return ( - new Date(b.addedDate).getTime() - new Date(a.addedDate).getTime() - ); - } - return b.priority - a.priority; - }); - } - - currentData = data; - renderTable(); -} - -let currentPage = 1; -const rowsPerPage = 10; // 1ページあたりの表示数 -const paginationContainer = document.getElementById( - "pagination", -) as HTMLDivElement; - -function renderPagination() { - paginationContainer.innerHTML = ""; - const totalPages = Math.ceil(currentData.length / rowsPerPage); - - // 前へボタン - const prevButton = document.createElement("button"); - prevButton.textContent = "前へ"; - prevButton.className = "pagination-button"; - prevButton.disabled = currentPage === 1; - prevButton.addEventListener("click", () => { - if (currentPage > 1) { - currentPage--; - renderTable(); - renderPagination(); - } - }); - paginationContainer.appendChild(prevButton); - - // 次へボタン - const nextButton = document.createElement("button"); - nextButton.textContent = "次へ"; - nextButton.className = "pagination-button"; - nextButton.disabled = currentPage === totalPages; - nextButton.addEventListener("click", () => { - if (currentPage < totalPages) { - currentPage++; - renderTable(); - renderPagination(); - } - }); - paginationContainer.appendChild(nextButton); -} - -function renderTable() { - tableBody.innerHTML = ""; - const totalPages = Math.ceil(currentData.length / rowsPerPage); - const start = (currentPage - 1) * rowsPerPage; - const end = start + rowsPerPage; - const pageData = currentData.slice(start, end); - - for (const entry of pageData) { - const tr = document.createElement("tr"); - - // リンク - const youglishLink = `https://youglish.com/pronounce/${encodeURIComponent(entry.key)}/english`; - const playPhraseLink = `https://playphrase.me/#/search?q=${encodeURIComponent(entry.key)}`; - const oxfordLink = `https://www.oxfordlearnersdictionaries.com/definition/english/${encodeURIComponent(entry.key)}`; - const weblioLink = `https://ejje.weblio.jp/content/${encodeURIComponent(entry.key)}`; - const yourDictLink = `https://www.yourdictionary.com/${encodeURIComponent(entry.key)}`; - const hyperLink = `https://hypcol.marutank.net/?q=${encodeURIComponent(entry.key)}&d=f`; - - tr.innerHTML = ` - ${entry.key} - - - - - - - - - - -
                    - ${entry.examples - .map( - (ex, idx) => ` -
                  • ${ex}
                  • - `, - ) - .join("")} -
                  - - - - - - ${new Date(entry.addedDate).toLocaleString()} - - - - - - - - `; - - // 追加例文ボタン - const addExBtn = tr.querySelector(".add-example") as HTMLButtonElement; - addExBtn.addEventListener("click", () => { - const newEx = prompt("新しい例文を入力してください:"); - if (newEx) { - entry.examples.push(newEx); - renderTable(); - } - }); - - // 例文削除ボタン - const deleteButtons = tr.querySelectorAll( - ".delete-example", - ) as NodeListOf; - deleteButtons.forEach((btn) => { - btn.addEventListener("click", () => { - const index = Number(btn.getAttribute("data-index")); - entry.examples.splice(index, 1); // 削除 - renderTable(); - }); - }); - - // 保存ボタン - const saveBtn = tr.querySelector(".save-changes") as HTMLButtonElement; - saveBtn.addEventListener("click", async () => { - const noteArea = tr.querySelector(".note-area") as HTMLTextAreaElement; - const prioritySelect = tr.querySelector( - ".priority-select", - ) as HTMLSelectElement; - entry.note = noteArea.value; - entry.priority = Number(prioritySelect.value); - await db.save(entry); - console.log("保存しました"); - }); - - // 削除ボタン - const delBtn = tr.querySelector(".delete-key") as HTMLButtonElement; - delBtn.addEventListener("click", async () => { - await db.delete(entry.key); - loadData(); - }); - - tableBody.appendChild(tr); - } - - renderPagination(); -} - -filterButton.addEventListener("click", () => { - const fromVal = dateFromEl.value ? new Date(dateFromEl.value) : undefined; - const toVal = dateToEl.value ? new Date(dateToEl.value) : undefined; - const priorityVal = prioritySortEl.value as "asc" | "desc" | ""; - - loadData({ - from: fromVal, - to: toVal, - prioritySort: priorityVal, - }); -}); - -const excludedDomainInput = document.getElementById( - "excludedDomainInput", -) as HTMLInputElement; -const addExcludedDomainButton = document.getElementById( - "addExcludedDomainButton", -) as HTMLButtonElement; -const excludedDomainList = document.getElementById( - "excludedDomainList", -) as HTMLUListElement; - -// excludedDomainsリストの表示更新関数 -async function renderExcludedDomains() { - const domains = await db.getAllExcludedDomains(); - excludedDomainList.innerHTML = ""; - for (const d of domains) { - const li = document.createElement("li"); - li.textContent = d.domain; - const removeBtn = document.createElement("button"); - removeBtn.textContent = "削除"; - removeBtn.addEventListener("click", async () => { - await db.removeExcludedDomain(d.domain); - renderExcludedDomains(); - }); - li.appendChild(removeBtn); - excludedDomainList.appendChild(li); - } -} - -addExcludedDomainButton.addEventListener("click", async () => { - const domain = excludedDomainInput.value.trim(); - if (domain) { - await db.addExcludedDomain(domain); - excludedDomainInput.value = ""; - renderExcludedDomains(); - } -}); - -// 1) モード保存先キーを決める -const DOMAIN_FILTER_MODE_KEY = "domainFilterMode"; - -// 2) ラジオボタン要素を取得 -const radioExclude = document.getElementById("radioExclude") as HTMLInputElement; -const radioInclude = document.getElementById("radioInclude") as HTMLInputElement; - -// 3) chrome.storage.local からモードを読み込んでUIに反映 -async function loadDomainFilterMode() { - const result = await chrome.storage.local.get(DOMAIN_FILTER_MODE_KEY); - const mode = result[DOMAIN_FILTER_MODE_KEY] ?? "exclude"; // デフォルト exclude - if (mode === "include") { - radioInclude.checked = true; - } else { - radioExclude.checked = true; // exclude - } -} - -// 4) ラジオボタンの変更を検知して保存 -function setupDomainFilterModeListeners() { - [radioExclude, radioInclude].forEach(radio => { - radio.addEventListener("change", async () => { - if (radio.checked) { - await chrome.storage.local.set({ [DOMAIN_FILTER_MODE_KEY]: radio.value }); - } - }); - }); -} - - -function initializeSettings() { - const storedMode = localStorage.getItem("domainFilterMode") || "include"; - if (storedMode === "exclude") { - radioExclude.checked = true; - toggleSettings("exclude"); - } else { - radioInclude.checked = true; - toggleSettings("include"); - } -} - -const excludeSettings = document.getElementById("excludeSettings") as HTMLDivElement; -const includeSettings = document.getElementById("includeSettings") as HTMLDivElement; - -function toggleSettings(mode: "exclude" | "include") { - if (mode === "exclude") { - excludeSettings.style.display = "block"; - includeSettings.style.display = "none"; - } else { - excludeSettings.style.display = "none"; - includeSettings.style.display = "block"; - } - // モードを保存 - localStorage.setItem("domainFilterMode", mode); -} - -// ラジオボタンのイベントリスナー -radioExclude.addEventListener("change", () => toggleSettings("exclude")); -radioInclude.addEventListener("change", () => toggleSettings("include")); - -initializeSettings(); -renderExcludedDomains(); -loadData(); -loadDomainFilterMode(); -setupDomainFilterModeListeners(); - From 649356979668c3258642e573dc2e8c8e811b6465 Mon Sep 17 00:00:00 2001 From: makinzm Date: Sun, 22 Dec 2024 01:04:06 +0900 Subject: [PATCH 4/4] add ignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6f03c5f..9f3b067 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ node_modules/ coverage/ .tmp-user-data/ test-results/ -dist/*.ts +dist/options/options.ts