From 311cd5aba4e7d1a8ac210fecbfe66b2056a24918 Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 11:15:29 +0900 Subject: [PATCH 01/12] explore-filter --- feature-locales/explore-filter/en.json | 14 + feature-locales/explore-filter/es.json | 14 + feature-locales/explore-filter/ja.json | 14 + feature-locales/explore-filter/tr.json | 14 + features/explore-filter/data.json | 48 ++ .../explore-filter/resources/calendar.svg | 4 + features/explore-filter/resources/filter.svg | 3 + features/explore-filter/resources/title.svg | 3 + features/explore-filter/resources/user.svg | 3 + features/explore-filter/script.js | 446 ++++++++++++++++++ features/explore-filter/style.css | 96 ++++ features/features.json | 5 + 12 files changed, 664 insertions(+) create mode 100644 feature-locales/explore-filter/en.json create mode 100644 feature-locales/explore-filter/es.json create mode 100644 feature-locales/explore-filter/ja.json create mode 100644 feature-locales/explore-filter/tr.json create mode 100644 features/explore-filter/data.json create mode 100644 features/explore-filter/resources/calendar.svg create mode 100644 features/explore-filter/resources/filter.svg create mode 100644 features/explore-filter/resources/title.svg create mode 100644 features/explore-filter/resources/user.svg create mode 100644 features/explore-filter/script.js create mode 100644 features/explore-filter/style.css diff --git a/feature-locales/explore-filter/en.json b/feature-locales/explore-filter/en.json new file mode 100644 index 00000000..0358b58b --- /dev/null +++ b/feature-locales/explore-filter/en.json @@ -0,0 +1,14 @@ +{ + "filter": "filter", + "title": "Title", + "author": "Author", + "period": "Period", + "reset": "Reset", + "save": "Save", + "sharedDate": "Shared Date", + "updateDate": "Update Date", + "startDate": "Start date", + "endDate": "End date", + "including": "including", + "excluding": "excluding" +} \ No newline at end of file diff --git a/feature-locales/explore-filter/es.json b/feature-locales/explore-filter/es.json new file mode 100644 index 00000000..c60b47ca --- /dev/null +++ b/feature-locales/explore-filter/es.json @@ -0,0 +1,14 @@ +{ + "filter": "Filtrar", + "title": "Título", + "author": "Autor", + "period": "Período", + "reset": "Restablecer", + "save": "Guardar", + "sharedDate": "Fecha compartida", + "updateDate": "Fecha de actualización", + "startDate": "Fecha de inicio", + "endDate": "Fecha de fin", + "including": "incluyendo", + "excluding": "excluyendo" +} diff --git a/feature-locales/explore-filter/ja.json b/feature-locales/explore-filter/ja.json new file mode 100644 index 00000000..3800362c --- /dev/null +++ b/feature-locales/explore-filter/ja.json @@ -0,0 +1,14 @@ +{ + "filter": "フィルター", + "title": "タイトル", + "author": "作者", + "period": "期間", + "reset": "リセット", + "save": "保存", + "sharedDate": "共有日", + "updateDate": "更新日", + "startDate": "開始日", + "endDate": "終了日", + "including": "次を含む", + "excluding": "次を除く" +} \ No newline at end of file diff --git a/feature-locales/explore-filter/tr.json b/feature-locales/explore-filter/tr.json new file mode 100644 index 00000000..a624a581 --- /dev/null +++ b/feature-locales/explore-filter/tr.json @@ -0,0 +1,14 @@ +{ + "filter": "Filtre", + "title": "Başlık", + "author": "Yazar", + "period": "Dönem", + "reset": "Sıfırla", + "save": "Kaydet", + "sharedDate": "Paylaşım Tarihi", + "updateDate": "Güncelleme Tarihi", + "startDate": "Başlangıç tarihi", + "endDate": "Bitiş tarihi", + "including": "dahil", + "excluding": "hariç" +} \ No newline at end of file diff --git a/features/explore-filter/data.json b/features/explore-filter/data.json new file mode 100644 index 00000000..121fdb30 --- /dev/null +++ b/features/explore-filter/data.json @@ -0,0 +1,48 @@ +{ + "title": "Explore Filter", + "description": "Customize project and studio search results with filters on the Search, Explore, and Studio pages.", + "credits": [ + { "username": "Masaabu-YT", "url": "https://scratch.mit.edu/users/Masaabu-YT/" } + ], + "type": ["Website"], + "tags": ["New", "Featured"], + "dynamic": true, + "options": [ + { + "id": "filter-operation", + "name": "Filter operation", + "type": 4, + "options": [ + { + "name": "Hide", + "value": "hide" + }, + { + "name": "Blur", + "value": "blur" + } + ] + }, + { + "id": "keep-settings", + "name": "Keep filter setting even if you change pages", + "type": 1 + } + ], + "scripts": [ + { "file": "script.js", "runOn": "/explore/*" }, + { "file": "script.js", "runOn": "/search/*" }, + { "file": "script.js", "runOn": "/studios/*" } + ], + "styles": [ + { "file": "style.css", "runOn": "/explore/*" }, + { "file": "style.css", "runOn": "/search/*" }, + { "file": "style.css", "runOn": "/studios/*" } + ], + "resources": [ + { "name": "filter-icon", "path": "/resources/filter.svg" }, + { "name": "title-icon", "path": "/resources/title.svg" }, + { "name": "user-icon", "path": "/resources/user.svg" }, + { "name": "calendar-icon", "path": "/resources/calendar.svg" } + ] +} \ No newline at end of file diff --git a/features/explore-filter/resources/calendar.svg b/features/explore-filter/resources/calendar.svg new file mode 100644 index 00000000..7381d9c5 --- /dev/null +++ b/features/explore-filter/resources/calendar.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/features/explore-filter/resources/filter.svg b/features/explore-filter/resources/filter.svg new file mode 100644 index 00000000..dec4964c --- /dev/null +++ b/features/explore-filter/resources/filter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/features/explore-filter/resources/title.svg b/features/explore-filter/resources/title.svg new file mode 100644 index 00000000..2ac6bf23 --- /dev/null +++ b/features/explore-filter/resources/title.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/features/explore-filter/resources/user.svg b/features/explore-filter/resources/user.svg new file mode 100644 index 00000000..5515f622 --- /dev/null +++ b/features/explore-filter/resources/user.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js new file mode 100644 index 00000000..9e6f7fc1 --- /dev/null +++ b/features/explore-filter/script.js @@ -0,0 +1,446 @@ +const filterDefault = `{ + "title": {}, + "author": {}, + "period": {} +}` +let options = [ + { + icon: "title-icon", + id: "title" + },{ + icon: "user-icon", + id: "author" + },{ + icon: "calendar-icon", + id: "period" + } +] +let page; +let filterData; +let apiCache = {}; + +async function filterProject(url, element) { + let data = apiCache[url] + if (!data) { + data = await (await fetch(`${url.replace('scratch.mit.edu','api.scratch.mit.edu')}`)).json(); + apiCache[url] = data; + } + if (data.code === "NotFound") {element.classList.add('ste-filter-hide'); return} + if ( + filterData.period.shareStart && filterData.period.shareStart > data.history.shared.split('T')[0] + || filterData.period.shareEnd && filterData.period.shareEnd < data.history.shared.split('T')[0] + || filterData.period.updateStart && filterData.period.updateStart > data.history.modified.split('T')[0] + || filterData.period.updateEnd && filterData.period.updateEnd < data.history.modified.split('T')[0] + || filterData.title.including && !filterData.title.including.every(text => data.title.includes(text)) + || filterData.title.excluding && filterData.title.excluding.some(text => data.title.includes(text)) + || filterData.author.including && !filterData.author.including.some(text => data.author.username.includes(text)) + || filterData.author.excluding && filterData.author.excluding.some(text => data.author.username.includes(text)) + ) element.classList.add('ste-filter-hide') + else if (element.classList.contains('ste-filter-hide')) element.classList.remove('ste-filter-hide') +} + +async function filterStudio(url, element) { + let data = apiCache[url] + if (!data) { + data = await (await fetch(`${url.replace('scratch.mit.edu','api.scratch.mit.edu')}`)).json(); + apiCache[url] = data; + } + if ( + filterData.period.shareStart && filterData.period.shareStart > data.history.created.split('T')[0] + || filterData.period.shareEnd && filterData.period.shareEnd < data.history.created.split('T')[0] + || filterData.period.updateStart && filterData.period.updateStart > data.history.modified.split('T')[0] + || filterData.period.updateEnd && filterData.period.updateEnd < data.history.modified.split('T')[0] + || filterData.title.including && !filterData.title.including.every(text => data.title.includes(text)) + || filterData.title.excluding && filterData.title.excluding.some(text => data.title.includes(text)) + ) element.classList.add('ste-filter-hide') + else if (element.classList.contains('ste-filter-hide')) element.classList.remove('ste-filter-hide') +} + +async function filter() { + switch (page[1]) { + case 'search': + case 'explore':{ + if (page[2]==="projects") + document.querySelectorAll('.thumbnail.project').forEach(element => { + let link = element.querySelector("a.thumbnail-image") + filterProject(link.href, element) + }); + else if (page[2]==="studios") + document.querySelectorAll('.thumbnail.gallery').forEach(element => { + let link = element.querySelector("a.thumbnail-image") + filterStudio(link.href, element) + }); + break; + } + case 'studios':{ + document.querySelectorAll('.studio-project-tile').forEach(element => { + let link = element.querySelector("a.studio-project-title") + filterProject(link.href, element) + }); + break; + } + default: + break; + } +} + +export default async function ({ feature, console }) { + const styleSheet = document.createElement('style'); + styleSheet.appendChild(document.createTextNode(``)); + document.head.appendChild(styleSheet); + async function filterStyleSheet(filterType) { + switch (filterType) { + case 'hide': + styleSheet.textContent = ` + .ste-filter-hide { + display: none + } + ` + break; + case 'blur': + styleSheet.textContent = ` + .ste-filter-hide { + filter: blur(5px); + opacity: 0.6; + transition: all 0.3s 0s ease; + } + .ste-filter-hide:hover { + filter: none; + opacity: 1; + } + ` + break; + + default: + break; + } + } + + filterStyleSheet(feature.settings.get("filter-operation")); + feature.settings.addEventListener("changed", function({ key, value }) { + if (key == "filter-operation") filterStyleSheet(value) + }) + + page = window.location.pathname.split('/'); + if (feature.settings.get("keep-settings")===true) filterData = await ScratchTools.storage.get("project-filter"); + if (!filterData) filterData = JSON.parse(filterDefault); + else switch (page[1]) { + case 'search': + case 'explore':{ + if (page[2]==="projects") + ScratchTools.waitForElements(".thumbnail.project", element => { + let link = element.querySelector("a.thumbnail-image"); + filterProject(link.href, element); + }); + else if (page[2]==="studios") { + ScratchTools.waitForElements('.thumbnail.gallery', element => { + let link = element.querySelector("a.thumbnail-image") + filterStudio(link.href, element) + }); + options = [ + { + icon: "title-icon", + id: "title" + },{ + icon: "calendar-icon", + id: "period" + } + ] + } + break; + } + + case 'studios':{ + ScratchTools.waitForElements(".studio-project-tile", element => { + let link = element.querySelector("a.studio-project-title"); + filterProject(link.href, element); + }); + break; + } + + default: + break; + } + + const filterButton = document.createElement("div"); + filterButton.classList.add('ste-filter-button'); + const filterIcon = document.createElement("img"); + filterIcon.src = feature.self.getResource("filter-icon"); + const filterText = document.createElement("p"); + filterText.textContent = feature.msg("filter"); + filterButton.appendChild(filterIcon); + filterButton.appendChild(filterText); + + function optionButtonClick(id, button) { + function createDetails(label) { + const details = document.createElement('details'); + details.classList.add("ste-project-filter-details"); + details.setAttribute('open', 'open'); + const summary = document.createElement('summary'); + summary.textContent = `${feature.msg(label)}`; + details.appendChild(summary); + return details; + } + + function createTextTag(id, type) { + const content = document.createElement("div"); + const tags = document.createElement("div"); + tags.style.margin = "0" + function addTag(text) { + const tag = document.createElement("span"); + tag.classList.add("ste-filter-text") + tag.textContent = text; + tag.addEventListener("click", function () { + filterData[id][type] = filterData[id][type].filter(function(tagText) { + return tagText !== text; + }); + tag.remove() + filter(); + if (filterData[id][type].length==0) delete filterData[id][type] + if (Object.keys(filterData[id]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } + }) + tags.appendChild(tag) + } + if (filterData[id][type]?.length>=0) filterData[id][type].forEach(addTag); + + const addButton = document.createElement("button"); + addButton.style.cssText = "min-width: 40px !important;" + addButton.textContent = "+"; + addButton.addEventListener("click", function () { + if (!input.value) return; + if (!filterData[id][type]) filterData[id][type]=[] + filterData[id][type].push(input.value); + addTag(input.value) + input.value = ''; + filter(); + button.classList.add('active'); + }); + const input = document.createElement("input"); + + content.appendChild(tags); + content.appendChild(addButton); + content.appendChild(input); + return content; + } + + switch (id) { + case 'reset':{ + filterData = JSON.parse(filterDefault); + document.querySelectorAll(".ste-filter-bar .ste-filter-button.active").forEach(element => { + element.classList.remove("active"); + }); + filter(); + break; + } + + case 'title':{ + const includingDetails = createDetails("including"); + const includingTextTag = createTextTag("title", "including"); + includingDetails.appendChild(includingTextTag); + const excludingDetails = createDetails("excluding"); + const excludingTextTag = createTextTag("title", "excluding"); + excludingDetails.appendChild(excludingTextTag); + let modal = ScratchTools.modals.create({ + title: `${feature.msg("title")}`, + components: [ + { + type: "html", + content: includingDetails + }, + { + type: "html", + content: excludingDetails + } + ] + }); + break; + } + + case 'author':{ + const includingDetails = createDetails("including"); + const includingTextTag = createTextTag("author", "including"); + includingDetails.appendChild(includingTextTag); + const excludingDetails = createDetails("excluding"); + const excludingTextTag = createTextTag("author", "excluding"); + excludingDetails.appendChild(excludingTextTag); + let modal = ScratchTools.modals.create({ + title: `${feature.msg("author")}`, + components: [ + { + type: "html", + content: includingDetails + }, + { + type: "html", + content: excludingDetails + } + ] + }); + break; + } + + case 'period':{ + function createInput(id, label) { + const content = document.createElement("div"); + content.textContent = feature.msg(label); + const input = document.createElement("input"); + input.type = "date"; + input.style.margin = "0 10px"; + if (filterData.period[id]) input.value = filterData.period[id]; + const button = document.createElement("button"); + button.textContent = feature.msg("reset"); + button.addEventListener("click", function () { + input.value = '' + }); + content.appendChild(input); + content.appendChild(button); + return content; + } + + const shareStart = createInput("shareStart", "startDate"); + const shareEnd = createInput("shareEnd", "endDate"); + const updateStart = createInput("updateStart", "startDate"); + const updateEnd = createInput("updateEnd", "endDate"); + + const shareDetails = createDetails("sharedDate"); + shareDetails.appendChild(shareStart); + shareDetails.appendChild(shareEnd); + + const updateDetails = createDetails("updateDate"); + updateDetails.appendChild(updateStart); + updateDetails.appendChild(updateEnd); + + const saveButton = document.createElement("button"); + saveButton.textContent = feature.msg("save"); + saveButton.style.marginRight = "100%"; + + let modal = ScratchTools.modals.create({ + title: `${feature.msg("period")}`, + components: [ + { + type: "html", + content: shareDetails, + }, + { + type: "html", + content: updateDetails, + }, + { + type: "html", + content: saveButton, + } + ] + }); + saveButton.addEventListener("click", function () { + function checkInput(id, element) { + const input = element.querySelector("input"); + if (input.value) filterData["period"][id] = input.value; + else if (filterData.period[id]) delete filterData.period[id]; + } + checkInput("shareStart", shareStart); + checkInput("shareEnd", shareEnd); + checkInput("updateStart", updateStart); + checkInput("updateEnd", updateEnd); + modal.close(); + + filter() + if (Object.keys(filterData[id]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } + else button.classList.add('active'); + }) + break; + } + + default: + break; + } + } + + const controlBar = document.createElement("div"); + controlBar.classList.add('ste-filter-bar'); + if (JSON.stringify(filterData)===filterDefault) controlBar.style.display = 'none'; + else filterButton.classList.add('active'); + const filterSettings = document.createElement("div"); + filterSettings.classList.add('ste-filter-settings'); + options.map((option) => { + const icon = document.createElement("img"); + icon.src = feature.self.getResource(option.icon); + + const text = document.createElement("p"); + text.textContent = feature.msg(option.id); + + const button = document.createElement("div"); + button.classList.add('ste-filter-button'); + if (filterData[option.id]) if (Object.keys(filterData[option.id]).length !== 0) button.classList.add('active'); + button.appendChild(icon); + button.appendChild(text); + + button.addEventListener("click", function() { + optionButtonClick(option.id, button); + }) + + filterSettings.appendChild(button); + }); + const resetButton = document.createElement("div"); + resetButton.style.marginLeft = "20px" + resetButton.classList.add('ste-filter-button'); + const resetButtonText = document.createElement("p"); + resetButtonText.textContent = feature.msg("reset"); + resetButton.appendChild(resetButtonText); + resetButton.addEventListener("click", function() { + optionButtonClick("reset", resetButton); + }) + filterSettings.appendChild(resetButton); + + controlBar.appendChild(filterSettings); + + filterButton.addEventListener("click", function() { + if (controlBar.style.display=='none') { + controlBar.style.display = 'flex'; + filterButton.classList.add('active'); + } else { + controlBar.style.display = 'none'; + if (filterButton.classList.contains('active')) filterButton.classList.remove('active'); + } + }) + + + switch (page[1]) { + case 'search': + case 'explore':{ + const sortElement = await ScratchTools.waitForElement("div.sort-controls"); + const sortForm = await ScratchTools.waitForElement("form.sort-mode"); + controlBar.appendChild(sortForm); + sortElement.after(controlBar); + + sortElement.appendChild(filterButton); + break; + } + + case 'studios':{ + async function setFilterControl() { + const tabTitle = document.querySelector(".studio-header-container h2"); + const headerContainer = document.querySelector(".studio-header-container"); + headerContainer.after(controlBar); + tabTitle.after(filterButton); + } + await ScratchTools.waitForElement(".studio-project-tile"); + if (/^[0-9]+$/.test(window.location.pathname.replace('/studios/','').replaceAll('/',''))) setFilterControl(); + const buttons = document.querySelectorAll(".studio-tab-nav .nav_link"); + buttons.forEach(button => { + if (/^[0-9]+$/.test(button.href.replace('https://scratch.mit.edu/studios/',''))) + button.addEventListener("click", async function() { + await ScratchTools.waitForElement(".studio-project-tile"); + setFilterControl(); + }) + }); + break; + } + + default: + break; + } + + window.addEventListener("beforeunload", async function (event) { + if (feature.settings.get("keep-settings")===true && JSON.stringify(filterData) !== JSON.stringify(await ScratchTools.storage.get("project-filter"))) await ScratchTools.storage.set({ key:"project-filter", value:filterData}) + }); +} diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css new file mode 100644 index 00000000..29fbf3c1 --- /dev/null +++ b/features/explore-filter/style.css @@ -0,0 +1,96 @@ +.ste-filter-button { + margin: 5px 0; + padding: 0 15px; + white-space: nowrap; + display: flex; + border: 1px solid #d9d9d9; + border-radius: 5px; +} +.ste-filter-button p { + margin: auto; + margin-left: 5px; + cursor: pointer; +} +.ste-filter-button img { + width: 1.2rem; +} + +.ste-filter-bar { + display: flex; + margin: 0 auto; + border-bottom: 1px solid #d9d9d9; + padding: 8px 0; + max-width: 58.75rem; + justify-content: space-between; +} +.ste-filter-bar img { + width: 1.5rem; +} +.ste-filter-bar .sort-mode { + margin: auto 0; + height: 32px; +} +.ste-filter-bar .control-label { + display: none; +} + +.ste-filter-settings { + display: flex; +} +.ste-filter-bar .ste-filter-button { + margin: 5px; +} + +.ste-filter-button.active { + border-color: #855cd6; + background-color: #855cd6; +} +.ste-filter-button.active p { + color: white; +} +.ste-filter-button.active img { + filter: brightness(0) invert(1); +} + +.ste-project-filter-details { + margin: 5px 0; + border-radius: 5px; + background-color: #79797929; +} +.ste-project-filter-details summary { + display: list-item; + padding: 6px; + background-color: #ffce7f; + border-radius: 5px; + cursor: pointer; +} +.ste-project-filter-details button { + background-color: white; + margin-top: 5px !important; +} +.ste-project-filter-details div { + margin-left: 20px; + padding-bottom: 5px; + +} +.ste-project-filter-details input { + background-color: white; + margin-bottom: 0; +} + +.ste-filter-text { + display: inline-block; + margin: .9em .2em 0; + padding: .45em .7em; + border: 2px solid #d68b5c; + border-radius: 10px; + background-color: #fff; + color: #d68b5c; + cursor: pointer; + transition: all 0.2s 0s ease; +} +.ste-filter-text:hover { + border: 2px solid #d13636; + color: #d13636; + background-color: #ffe1e1; +} \ No newline at end of file diff --git a/features/features.json b/features/features.json index 5eb894bd..0d2a99ee 100644 --- a/features/features.json +++ b/features/features.json @@ -1,4 +1,9 @@ [ + { + "version": 2, + "id": "explore-filter", + "versionAdded": "v4.0.0" + }, { "version": 2, "id": "align-to-center", From 3d90a54c5f12b7bcb002344de17fab2bb83a9ed5 Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 11:44:13 +0900 Subject: [PATCH 02/12] Remove Save button for period setting --- feature-locales/explore-filter/en.json | 1 - feature-locales/explore-filter/es.json | 1 - feature-locales/explore-filter/ja.json | 1 - feature-locales/explore-filter/tr.json | 1 - features/explore-filter/script.js | 47 ++++++++++---------------- 5 files changed, 17 insertions(+), 34 deletions(-) diff --git a/feature-locales/explore-filter/en.json b/feature-locales/explore-filter/en.json index 0358b58b..0bc2f832 100644 --- a/feature-locales/explore-filter/en.json +++ b/feature-locales/explore-filter/en.json @@ -4,7 +4,6 @@ "author": "Author", "period": "Period", "reset": "Reset", - "save": "Save", "sharedDate": "Shared Date", "updateDate": "Update Date", "startDate": "Start date", diff --git a/feature-locales/explore-filter/es.json b/feature-locales/explore-filter/es.json index c60b47ca..c3fb9364 100644 --- a/feature-locales/explore-filter/es.json +++ b/feature-locales/explore-filter/es.json @@ -4,7 +4,6 @@ "author": "Autor", "period": "Período", "reset": "Restablecer", - "save": "Guardar", "sharedDate": "Fecha compartida", "updateDate": "Fecha de actualización", "startDate": "Fecha de inicio", diff --git a/feature-locales/explore-filter/ja.json b/feature-locales/explore-filter/ja.json index 3800362c..9a5b3aa5 100644 --- a/feature-locales/explore-filter/ja.json +++ b/feature-locales/explore-filter/ja.json @@ -4,7 +4,6 @@ "author": "作者", "period": "期間", "reset": "リセット", - "save": "保存", "sharedDate": "共有日", "updateDate": "更新日", "startDate": "開始日", diff --git a/feature-locales/explore-filter/tr.json b/feature-locales/explore-filter/tr.json index a624a581..6358d89d 100644 --- a/feature-locales/explore-filter/tr.json +++ b/feature-locales/explore-filter/tr.json @@ -4,7 +4,6 @@ "author": "Yazar", "period": "Dönem", "reset": "Sıfırla", - "save": "Kaydet", "sharedDate": "Paylaşım Tarihi", "updateDate": "Güncelleme Tarihi", "startDate": "Başlangıç tarihi", diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js index 9e6f7fc1..fd6db8db 100644 --- a/features/explore-filter/script.js +++ b/features/explore-filter/script.js @@ -287,13 +287,24 @@ export default async function ({ feature, console }) { input.type = "date"; input.style.margin = "0 10px"; if (filterData.period[id]) input.value = filterData.period[id]; - const button = document.createElement("button"); - button.textContent = feature.msg("reset"); - button.addEventListener("click", function () { + input.addEventListener("change", function () { + if (input.value) { + filterData["period"][id] = input.value; + button.classList.add('active'); + } + else if (filterData.period[id]) delete filterData.period[id]; + filter() + }); + const resetButton = document.createElement("button"); + resetButton.textContent = feature.msg("reset"); + resetButton.addEventListener("click", function () { input.value = '' + if (filterData.period[id]) delete filterData.period[id]; + filter() + if (Object.keys(filterData["period"]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } }); content.appendChild(input); - content.appendChild(button); + content.appendChild(resetButton); return content; } @@ -310,11 +321,7 @@ export default async function ({ feature, console }) { updateDetails.appendChild(updateStart); updateDetails.appendChild(updateEnd); - const saveButton = document.createElement("button"); - saveButton.textContent = feature.msg("save"); - saveButton.style.marginRight = "100%"; - - let modal = ScratchTools.modals.create({ + ScratchTools.modals.create({ title: `${feature.msg("period")}`, components: [ { @@ -324,29 +331,9 @@ export default async function ({ feature, console }) { { type: "html", content: updateDetails, - }, - { - type: "html", - content: saveButton, } ] }); - saveButton.addEventListener("click", function () { - function checkInput(id, element) { - const input = element.querySelector("input"); - if (input.value) filterData["period"][id] = input.value; - else if (filterData.period[id]) delete filterData.period[id]; - } - checkInput("shareStart", shareStart); - checkInput("shareEnd", shareEnd); - checkInput("updateStart", updateStart); - checkInput("updateEnd", updateEnd); - modal.close(); - - filter() - if (Object.keys(filterData[id]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } - else button.classList.add('active'); - }) break; } @@ -361,7 +348,7 @@ export default async function ({ feature, console }) { else filterButton.classList.add('active'); const filterSettings = document.createElement("div"); filterSettings.classList.add('ste-filter-settings'); - options.map((option) => { + options.forEach(option => { const icon = document.createElement("img"); icon.src = feature.self.getResource(option.icon); From 0b11acbfd06596654ad034c62b0cb39f94f60743 Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 11:58:58 +0900 Subject: [PATCH 03/12] Fixed a bug that filters did not work correctly when deleting tags --- features/explore-filter/script.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js index fd6db8db..91fe427b 100644 --- a/features/explore-filter/script.js +++ b/features/explore-filter/script.js @@ -195,9 +195,14 @@ export default async function ({ feature, console }) { return tagText !== text; }); tag.remove() + if (filterData[id][type].length == 0) { + delete filterData[id][type] + } + if (Object.keys(filterData[id]).length == 0) { + if (button.classList.contains('active')) button.classList.remove('active') + } + console.log(filterData) filter(); - if (filterData[id][type].length==0) delete filterData[id][type] - if (Object.keys(filterData[id]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } }) tags.appendChild(tag) } @@ -300,8 +305,8 @@ export default async function ({ feature, console }) { resetButton.addEventListener("click", function () { input.value = '' if (filterData.period[id]) delete filterData.period[id]; - filter() if (Object.keys(filterData["period"]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } + filter() }); content.appendChild(input); content.appendChild(resetButton); From 156eb3ba0e86379bf7bb6f8e0a25ab69ae7c8d01 Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 12:00:42 +0900 Subject: [PATCH 04/12] Delete console.log (I forgot) --- features/explore-filter/script.js | 1 - 1 file changed, 1 deletion(-) diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js index 91fe427b..d68542e2 100644 --- a/features/explore-filter/script.js +++ b/features/explore-filter/script.js @@ -201,7 +201,6 @@ export default async function ({ feature, console }) { if (Object.keys(filterData[id]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } - console.log(filterData) filter(); }) tags.appendChild(tag) From 867c0e446c82a54b05bdc0c54d587eb287500c7e Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 12:06:13 +0900 Subject: [PATCH 05/12] Delete localization files omitting English --- feature-locales/explore-filter/es.json | 13 ------------- feature-locales/explore-filter/ja.json | 13 ------------- feature-locales/explore-filter/tr.json | 13 ------------- 3 files changed, 39 deletions(-) delete mode 100644 feature-locales/explore-filter/es.json delete mode 100644 feature-locales/explore-filter/ja.json delete mode 100644 feature-locales/explore-filter/tr.json diff --git a/feature-locales/explore-filter/es.json b/feature-locales/explore-filter/es.json deleted file mode 100644 index c3fb9364..00000000 --- a/feature-locales/explore-filter/es.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "filter": "Filtrar", - "title": "Título", - "author": "Autor", - "period": "Período", - "reset": "Restablecer", - "sharedDate": "Fecha compartida", - "updateDate": "Fecha de actualización", - "startDate": "Fecha de inicio", - "endDate": "Fecha de fin", - "including": "incluyendo", - "excluding": "excluyendo" -} diff --git a/feature-locales/explore-filter/ja.json b/feature-locales/explore-filter/ja.json deleted file mode 100644 index 9a5b3aa5..00000000 --- a/feature-locales/explore-filter/ja.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "filter": "フィルター", - "title": "タイトル", - "author": "作者", - "period": "期間", - "reset": "リセット", - "sharedDate": "共有日", - "updateDate": "更新日", - "startDate": "開始日", - "endDate": "終了日", - "including": "次を含む", - "excluding": "次を除く" -} \ No newline at end of file diff --git a/feature-locales/explore-filter/tr.json b/feature-locales/explore-filter/tr.json deleted file mode 100644 index 6358d89d..00000000 --- a/feature-locales/explore-filter/tr.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "filter": "Filtre", - "title": "Başlık", - "author": "Yazar", - "period": "Dönem", - "reset": "Sıfırla", - "sharedDate": "Paylaşım Tarihi", - "updateDate": "Güncelleme Tarihi", - "startDate": "Başlangıç tarihi", - "endDate": "Bitiş tarihi", - "including": "dahil", - "excluding": "hariç" -} \ No newline at end of file From 9be2e1c3b7983c73b506ed2b650fa136c50cd031 Mon Sep 17 00:00:00 2001 From: Masaabu Date: Thu, 11 Jul 2024 12:16:21 +0900 Subject: [PATCH 06/12] Update user.svg --- features/explore-filter/resources/user.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/explore-filter/resources/user.svg b/features/explore-filter/resources/user.svg index 5515f622..9cf90b10 100644 --- a/features/explore-filter/resources/user.svg +++ b/features/explore-filter/resources/user.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file From 216e574ffbd10005ca393ef663b3a07364536fd0 Mon Sep 17 00:00:00 2001 From: MaterArc <105017592+MaterArc@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:14:31 -0400 Subject: [PATCH 07/12] Fix Reset Button --- features/explore-filter/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css index 29fbf3c1..8be156bb 100644 --- a/features/explore-filter/style.css +++ b/features/explore-filter/style.css @@ -6,7 +6,7 @@ border: 1px solid #d9d9d9; border-radius: 5px; } -.ste-filter-button p { +.ste-filter-button p:not(.ste-reset) { margin: auto; margin-left: 5px; cursor: pointer; @@ -93,4 +93,4 @@ border: 2px solid #d13636; color: #d13636; background-color: #ffe1e1; -} \ No newline at end of file +} From c5b688bb20eddf82af0deae8957ca536db24f464 Mon Sep 17 00:00:00 2001 From: MaterArc <105017592+MaterArc@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:15:16 -0400 Subject: [PATCH 08/12] Fix Reset Button --- features/explore-filter/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css index 8be156bb..0d9a48df 100644 --- a/features/explore-filter/style.css +++ b/features/explore-filter/style.css @@ -11,6 +11,10 @@ margin-left: 5px; cursor: pointer; } +.ste-reset { + margin: auto; + cursor: pointer; +} .ste-filter-button img { width: 1.2rem; } From de47ec3cdc2357991b6741ebac837fcc52829cfc Mon Sep 17 00:00:00 2001 From: MaterArc <105017592+MaterArc@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:23:50 -0400 Subject: [PATCH 09/12] Add class to reset button --- features/explore-filter/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js index d68542e2..60658af6 100644 --- a/features/explore-filter/script.js +++ b/features/explore-filter/script.js @@ -376,6 +376,7 @@ export default async function ({ feature, console }) { resetButton.classList.add('ste-filter-button'); const resetButtonText = document.createElement("p"); resetButtonText.textContent = feature.msg("reset"); + resetButtonText.classList.add('ste-reset'); resetButton.appendChild(resetButtonText); resetButton.addEventListener("click", function() { optionButtonClick("reset", resetButton); From 581d5fc611b92995cc1a97a81031bb622fee42b0 Mon Sep 17 00:00:00 2001 From: MaterArc <105017592+MaterArc@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:37:46 -0400 Subject: [PATCH 10/12] Add Vertical Padding --- features/explore-filter/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css index 0d9a48df..40cedd7d 100644 --- a/features/explore-filter/style.css +++ b/features/explore-filter/style.css @@ -1,6 +1,6 @@ .ste-filter-button { margin: 5px 0; - padding: 0 15px; + padding: 2px 15px; white-space: nowrap; display: flex; border: 1px solid #d9d9d9; From 388b2f9bbb125330451b6c9f9aaafe345fbf70fe Mon Sep 17 00:00:00 2001 From: MaterArc <105017592+MaterArc@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:15:20 -0400 Subject: [PATCH 11/12] Update Padding --- features/explore-filter/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css index 40cedd7d..58a7e8f0 100644 --- a/features/explore-filter/style.css +++ b/features/explore-filter/style.css @@ -1,6 +1,6 @@ .ste-filter-button { margin: 5px 0; - padding: 2px 15px; + padding: 1px 15px; white-space: nowrap; display: flex; border: 1px solid #d9d9d9; From 3eed690daaa4cf973a112e287677d53bde7e5146 Mon Sep 17 00:00:00 2001 From: rgantzos <86856959+rgantzos@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:58:12 -0700 Subject: [PATCH 12/12] Fix a few things --- api/modal.css | 6 +- api/modals.js | 4 - features/explore-filter/data.json | 89 ++-- features/explore-filter/script.js | 814 ++++++++++++++++-------------- features/explore-filter/style.css | 10 + 5 files changed, 492 insertions(+), 431 deletions(-) diff --git a/api/modal.css b/api/modal.css index 97e7cb6d..6bfdf408 100644 --- a/api/modal.css +++ b/api/modal.css @@ -12,14 +12,16 @@ .st-modal { position: fixed; - top: 15rem; + top: 50%; width: calc(40% - 4rem); padding: 2rem; left: 30%; background-color: #fafafa; border-radius: 0.5rem; - padding-top: 3rem; + padding-top: 1rem; z-index: 2147483647; + transform: translateY(-50%); + border-top: 1rem solid #ff9f00; } .st-modal h1 { diff --git a/api/modals.js b/api/modals.js index bd42fa17..9cf8e134 100644 --- a/api/modals.js +++ b/api/modals.js @@ -24,9 +24,6 @@ ScratchTools.modals = { p.textContent = data.description; modal.appendChild(p); - var orangeBar = document.createElement("div"); - orangeBar.className = "st-modal-header"; - data.components?.forEach(function (component) { if (component.type === "code") { var code = document.createElement("code"); @@ -46,7 +43,6 @@ ScratchTools.modals = { modal.appendChild(closeButton); div.appendChild(modal); - modal.prepend(orangeBar); document.body.appendChild(div); return { diff --git a/features/explore-filter/data.json b/features/explore-filter/data.json index 121fdb30..49d76022 100644 --- a/features/explore-filter/data.json +++ b/features/explore-filter/data.json @@ -1,48 +1,51 @@ { - "title": "Explore Filter", - "description": "Customize project and studio search results with filters on the Search, Explore, and Studio pages.", - "credits": [ - { "username": "Masaabu-YT", "url": "https://scratch.mit.edu/users/Masaabu-YT/" } - ], - "type": ["Website"], - "tags": ["New", "Featured"], - "dynamic": true, - "options": [ + "title": "Explore Filter", + "description": "Customize project and studio search results with filters on the Search, Explore, and Studio pages.", + "credits": [ + { + "username": "Masaabu-YT", + "url": "https://scratch.mit.edu/users/Masaabu-YT/" + } + ], + "type": ["Website"], + "tags": ["New", "Featured"], + "dynamic": true, + "options": [ + { + "id": "filter-operation", + "name": "Filter operation", + "type": 4, + "options": [ { - "id": "filter-operation", - "name": "Filter operation", - "type": 4, - "options": [ - { - "name": "Hide", - "value": "hide" - }, - { - "name": "Blur", - "value": "blur" - } - ] + "name": "Blur", + "value": "blur" }, { - "id": "keep-settings", - "name": "Keep filter setting even if you change pages", - "type": 1 + "name": "Hide", + "value": "hide" } - ], - "scripts": [ - { "file": "script.js", "runOn": "/explore/*" }, - { "file": "script.js", "runOn": "/search/*" }, - { "file": "script.js", "runOn": "/studios/*" } - ], - "styles": [ - { "file": "style.css", "runOn": "/explore/*" }, - { "file": "style.css", "runOn": "/search/*" }, - { "file": "style.css", "runOn": "/studios/*" } - ], - "resources": [ - { "name": "filter-icon", "path": "/resources/filter.svg" }, - { "name": "title-icon", "path": "/resources/title.svg" }, - { "name": "user-icon", "path": "/resources/user.svg" }, - { "name": "calendar-icon", "path": "/resources/calendar.svg" } - ] -} \ No newline at end of file + ] + }, + { + "id": "keep-settings", + "name": "Keep filter setting even if you change pages", + "type": 1 + } + ], + "scripts": [ + { "file": "script.js", "runOn": "/explore/*" }, + { "file": "script.js", "runOn": "/search/*" }, + { "file": "script.js", "runOn": "/studios/*" } + ], + "styles": [ + { "file": "style.css", "runOn": "/explore/*" }, + { "file": "style.css", "runOn": "/search/*" }, + { "file": "style.css", "runOn": "/studios/*" } + ], + "resources": [ + { "name": "filter-icon", "path": "/resources/filter.svg" }, + { "name": "title-icon", "path": "/resources/title.svg" }, + { "name": "user-icon", "path": "/resources/user.svg" }, + { "name": "calendar-icon", "path": "/resources/calendar.svg" } + ] +} diff --git a/features/explore-filter/script.js b/features/explore-filter/script.js index 60658af6..f5b19df4 100644 --- a/features/explore-filter/script.js +++ b/features/explore-filter/script.js @@ -1,373 +1,402 @@ -const filterDefault = `{ +export default async function ({ feature, console }) { + filterStyleSheet(feature.settings.get("filter-operation") || "blur"); + const filterDefault = `{ "title": {}, "author": {}, "period": {} -}` -let options = [ +}`; + let options = [ { - icon: "title-icon", - id: "title" - },{ - icon: "user-icon", - id: "author" - },{ - icon: "calendar-icon", - id: "period" - } -] -let page; -let filterData; -let apiCache = {}; + icon: "title-icon", + id: "title", + }, + { + icon: "user-icon", + id: "author", + }, + { + icon: "calendar-icon", + id: "period", + }, + ]; + let page; + let filterData; + let apiCache = {}; -async function filterProject(url, element) { - let data = apiCache[url] + async function filterProject(url, element) { + let data = apiCache[url]; if (!data) { - data = await (await fetch(`${url.replace('scratch.mit.edu','api.scratch.mit.edu')}`)).json(); - apiCache[url] = data; + data = await ( + await fetch(`${url.replace("scratch.mit.edu", "api.scratch.mit.edu")}`) + ).json(); + apiCache[url] = data; + } + if (data.code === "NotFound") { + element.classList.add("ste-filter-hide"); + return; } - if (data.code === "NotFound") {element.classList.add('ste-filter-hide'); return} if ( - filterData.period.shareStart && filterData.period.shareStart > data.history.shared.split('T')[0] - || filterData.period.shareEnd && filterData.period.shareEnd < data.history.shared.split('T')[0] - || filterData.period.updateStart && filterData.period.updateStart > data.history.modified.split('T')[0] - || filterData.period.updateEnd && filterData.period.updateEnd < data.history.modified.split('T')[0] - || filterData.title.including && !filterData.title.including.every(text => data.title.includes(text)) - || filterData.title.excluding && filterData.title.excluding.some(text => data.title.includes(text)) - || filterData.author.including && !filterData.author.including.some(text => data.author.username.includes(text)) - || filterData.author.excluding && filterData.author.excluding.some(text => data.author.username.includes(text)) - ) element.classList.add('ste-filter-hide') - else if (element.classList.contains('ste-filter-hide')) element.classList.remove('ste-filter-hide') -} + (filterData.period.shareStart && + filterData.period.shareStart > data.history.shared.split("T")[0]) || + (filterData.period.shareEnd && + filterData.period.shareEnd < data.history.shared.split("T")[0]) || + (filterData.period.updateStart && + filterData.period.updateStart > data.history.modified.split("T")[0]) || + (filterData.period.updateEnd && + filterData.period.updateEnd < data.history.modified.split("T")[0]) || + (filterData.title.including && + !filterData.title.including.every((text) => + data.title.includes(text) + )) || + (filterData.title.excluding && + filterData.title.excluding.some((text) => data.title.includes(text))) || + (filterData.author.including && + !filterData.author.including.some((text) => + data.author.username.includes(text) + )) || + (filterData.author.excluding && + filterData.author.excluding.some((text) => + data.author.username.includes(text) + )) + ) + element.classList.add("ste-filter-hide"); + else if (element.classList.contains("ste-filter-hide")) + element.classList.remove("ste-filter-hide"); + } -async function filterStudio(url, element) { - let data = apiCache[url] + async function filterStudio(url, element) { + let data = apiCache[url]; if (!data) { - data = await (await fetch(`${url.replace('scratch.mit.edu','api.scratch.mit.edu')}`)).json(); - apiCache[url] = data; + data = await ( + await fetch(`${url.replace("scratch.mit.edu", "api.scratch.mit.edu")}`) + ).json(); + apiCache[url] = data; } if ( - filterData.period.shareStart && filterData.period.shareStart > data.history.created.split('T')[0] - || filterData.period.shareEnd && filterData.period.shareEnd < data.history.created.split('T')[0] - || filterData.period.updateStart && filterData.period.updateStart > data.history.modified.split('T')[0] - || filterData.period.updateEnd && filterData.period.updateEnd < data.history.modified.split('T')[0] - || filterData.title.including && !filterData.title.including.every(text => data.title.includes(text)) - || filterData.title.excluding && filterData.title.excluding.some(text => data.title.includes(text)) - ) element.classList.add('ste-filter-hide') - else if (element.classList.contains('ste-filter-hide')) element.classList.remove('ste-filter-hide') -} + (filterData.period.shareStart && + filterData.period.shareStart > data.history.created.split("T")[0]) || + (filterData.period.shareEnd && + filterData.period.shareEnd < data.history.created.split("T")[0]) || + (filterData.period.updateStart && + filterData.period.updateStart > data.history.modified.split("T")[0]) || + (filterData.period.updateEnd && + filterData.period.updateEnd < data.history.modified.split("T")[0]) || + (filterData.title.including && + !filterData.title.including.every((text) => + data.title.includes(text) + )) || + (filterData.title.excluding && + filterData.title.excluding.some((text) => data.title.includes(text))) + ) + element.classList.add("ste-filter-hide"); + else if (element.classList.contains("ste-filter-hide")) + element.classList.remove("ste-filter-hide"); + } -async function filter() { + async function filter() { switch (page[1]) { - case 'search': - case 'explore':{ - if (page[2]==="projects") - document.querySelectorAll('.thumbnail.project').forEach(element => { - let link = element.querySelector("a.thumbnail-image") - filterProject(link.href, element) - }); - else if (page[2]==="studios") - document.querySelectorAll('.thumbnail.gallery').forEach(element => { - let link = element.querySelector("a.thumbnail-image") - filterStudio(link.href, element) - }); - break; - } - case 'studios':{ - document.querySelectorAll('.studio-project-tile').forEach(element => { - let link = element.querySelector("a.studio-project-title") - filterProject(link.href, element) - }); - break; - } - default: - break; + case "search": + case "explore": { + if (page[2] === "projects") + document.querySelectorAll(".thumbnail.project").forEach((element) => { + let link = element.querySelector("a.thumbnail-image"); + filterProject(link.href, element); + }); + else if (page[2] === "studios") + document.querySelectorAll(".thumbnail.gallery").forEach((element) => { + let link = element.querySelector("a.thumbnail-image"); + filterStudio(link.href, element); + }); + break; + } + case "studios": { + document.querySelectorAll(".studio-project-tile").forEach((element) => { + let link = element.querySelector("a.studio-project-title"); + filterProject(link.href, element); + }); + break; + } + default: + break; } -} + } + async function filterStyleSheet(filterType) { + let app = await ScratchTools.waitForElement("#app"); -export default async function ({ feature, console }) { - const styleSheet = document.createElement('style'); - styleSheet.appendChild(document.createTextNode(``)); - document.head.appendChild(styleSheet); - async function filterStyleSheet(filterType) { - switch (filterType) { - case 'hide': - styleSheet.textContent = ` - .ste-filter-hide { - display: none - } - ` - break; - case 'blur': - styleSheet.textContent = ` - .ste-filter-hide { - filter: blur(5px); - opacity: 0.6; - transition: all 0.3s 0s ease; - } - .ste-filter-hide:hover { - filter: none; - opacity: 1; - } - ` - break; - - default: - break; - } + if (filterType === "hide") { + app.classList.add("ste-filter-mode-hide"); + app.classList.remove("ste-filter-mode-blur"); + } else if (filterType === "blur") { + app.classList.add("ste-filter-mode-blur"); + app.classList.remove("ste-filter-mode-hide"); } + } - filterStyleSheet(feature.settings.get("filter-operation")); - feature.settings.addEventListener("changed", function({ key, value }) { - if (key == "filter-operation") filterStyleSheet(value) - }) + feature.settings.addEventListener("changed", function ({ key, value }) { + if (key == "filter-operation") filterStyleSheet(value); + }); - page = window.location.pathname.split('/'); - if (feature.settings.get("keep-settings")===true) filterData = await ScratchTools.storage.get("project-filter"); - if (!filterData) filterData = JSON.parse(filterDefault); - else switch (page[1]) { - case 'search': - case 'explore':{ - if (page[2]==="projects") - ScratchTools.waitForElements(".thumbnail.project", element => { - let link = element.querySelector("a.thumbnail-image"); - filterProject(link.href, element); - }); - else if (page[2]==="studios") { - ScratchTools.waitForElements('.thumbnail.gallery', element => { - let link = element.querySelector("a.thumbnail-image") - filterStudio(link.href, element) - }); - options = [ - { - icon: "title-icon", - id: "title" - },{ - icon: "calendar-icon", - id: "period" - } - ] - } - break; + page = window.location.pathname.split("/"); + if (feature.settings.get("keep-settings") === true) + filterData = await ScratchTools.storage.get("project-filter"); + if (!filterData) filterData = JSON.parse(filterDefault); + else + switch (page[1]) { + case "search": + case "explore": { + if (page[2] === "projects") + ScratchTools.waitForElements(".thumbnail.project", (element) => { + let link = element.querySelector("a.thumbnail-image"); + filterProject(link.href, element); + }); + else if (page[2] === "studios") { + ScratchTools.waitForElements(".thumbnail.gallery", (element) => { + let link = element.querySelector("a.thumbnail-image"); + filterStudio(link.href, element); + }); + options = [ + { + icon: "title-icon", + id: "title", + }, + { + icon: "calendar-icon", + id: "period", + }, + ]; } + break; + } - case 'studios':{ - ScratchTools.waitForElements(".studio-project-tile", element => { - let link = element.querySelector("a.studio-project-title"); - filterProject(link.href, element); - }); - break; - } + case "studios": { + ScratchTools.waitForElements(".studio-project-tile", (element) => { + let link = element.querySelector("a.studio-project-title"); + filterProject(link.href, element); + }); + break; + } - default: - break; + default: + break; } - const filterButton = document.createElement("div"); - filterButton.classList.add('ste-filter-button'); - const filterIcon = document.createElement("img"); - filterIcon.src = feature.self.getResource("filter-icon"); - const filterText = document.createElement("p"); - filterText.textContent = feature.msg("filter"); - filterButton.appendChild(filterIcon); - filterButton.appendChild(filterText); - - function optionButtonClick(id, button) { - function createDetails(label) { - const details = document.createElement('details'); - details.classList.add("ste-project-filter-details"); - details.setAttribute('open', 'open'); - const summary = document.createElement('summary'); - summary.textContent = `${feature.msg(label)}`; - details.appendChild(summary); - return details; - } + const filterButton = document.createElement("div"); + filterButton.classList.add("ste-filter-button"); + const filterIcon = document.createElement("img"); + filterIcon.src = feature.self.getResource("filter-icon"); + const filterText = document.createElement("p"); + filterText.textContent = feature.msg("filter"); + filterButton.appendChild(filterIcon); + filterButton.appendChild(filterText); - function createTextTag(id, type) { - const content = document.createElement("div"); - const tags = document.createElement("div"); - tags.style.margin = "0" - function addTag(text) { - const tag = document.createElement("span"); - tag.classList.add("ste-filter-text") - tag.textContent = text; - tag.addEventListener("click", function () { - filterData[id][type] = filterData[id][type].filter(function(tagText) { - return tagText !== text; - }); - tag.remove() - if (filterData[id][type].length == 0) { - delete filterData[id][type] - } - if (Object.keys(filterData[id]).length == 0) { - if (button.classList.contains('active')) button.classList.remove('active') - } - filter(); - }) - tags.appendChild(tag) - } - if (filterData[id][type]?.length>=0) filterData[id][type].forEach(addTag); - - const addButton = document.createElement("button"); - addButton.style.cssText = "min-width: 40px !important;" - addButton.textContent = "+"; - addButton.addEventListener("click", function () { - if (!input.value) return; - if (!filterData[id][type]) filterData[id][type]=[] - filterData[id][type].push(input.value); - addTag(input.value) - input.value = ''; - filter(); - button.classList.add('active'); - }); - const input = document.createElement("input"); - - content.appendChild(tags); - content.appendChild(addButton); - content.appendChild(input); - return content; - } + function optionButtonClick(id, button) { + function createDetails(label) { + const details = document.createElement("details"); + details.classList.add("ste-project-filter-details"); + details.setAttribute("open", "open"); + const summary = document.createElement("summary"); + summary.textContent = `${feature.msg(label)}`; + details.appendChild(summary); + return details; + } - switch (id) { - case 'reset':{ - filterData = JSON.parse(filterDefault); - document.querySelectorAll(".ste-filter-bar .ste-filter-button.active").forEach(element => { - element.classList.remove("active"); - }); - filter(); - break; - } + function createTextTag(id, type) { + const content = document.createElement("div"); + const tags = document.createElement("div"); + tags.style.margin = "0"; + function addTag(text) { + const tag = document.createElement("span"); + tag.classList.add("ste-filter-text"); + tag.textContent = text; + tag.addEventListener("click", function () { + filterData[id][type] = filterData[id][type].filter(function ( + tagText + ) { + return tagText !== text; + }); + tag.remove(); + if (filterData[id][type].length == 0) { + delete filterData[id][type]; + } + if (Object.keys(filterData[id]).length == 0) { + if (button.classList.contains("active")) + button.classList.remove("active"); + } + filter(); + }); + tags.appendChild(tag); + } + if (filterData[id][type]?.length >= 0) + filterData[id][type].forEach(addTag); - case 'title':{ - const includingDetails = createDetails("including"); - const includingTextTag = createTextTag("title", "including"); - includingDetails.appendChild(includingTextTag); - const excludingDetails = createDetails("excluding"); - const excludingTextTag = createTextTag("title", "excluding"); - excludingDetails.appendChild(excludingTextTag); - let modal = ScratchTools.modals.create({ - title: `${feature.msg("title")}`, - components: [ - { - type: "html", - content: includingDetails - }, - { - type: "html", - content: excludingDetails - } - ] - }); - break; - } + const addButton = document.createElement("button"); + addButton.style.cssText = "min-width: 40px !important;"; + addButton.textContent = "+"; + addButton.addEventListener("click", function () { + if (!input.value) return; + if (!filterData[id][type]) filterData[id][type] = []; + filterData[id][type].push(input.value); + addTag(input.value); + input.value = ""; + filter(); + button.classList.add("active"); + }); + const input = document.createElement("input"); - case 'author':{ - const includingDetails = createDetails("including"); - const includingTextTag = createTextTag("author", "including"); - includingDetails.appendChild(includingTextTag); - const excludingDetails = createDetails("excluding"); - const excludingTextTag = createTextTag("author", "excluding"); - excludingDetails.appendChild(excludingTextTag); - let modal = ScratchTools.modals.create({ - title: `${feature.msg("author")}`, - components: [ - { - type: "html", - content: includingDetails - }, - { - type: "html", - content: excludingDetails - } - ] - }); - break; - } + content.appendChild(tags); + content.appendChild(addButton); + content.appendChild(input); + return content; + } - case 'period':{ - function createInput(id, label) { - const content = document.createElement("div"); - content.textContent = feature.msg(label); - const input = document.createElement("input"); - input.type = "date"; - input.style.margin = "0 10px"; - if (filterData.period[id]) input.value = filterData.period[id]; - input.addEventListener("change", function () { - if (input.value) { - filterData["period"][id] = input.value; - button.classList.add('active'); - } - else if (filterData.period[id]) delete filterData.period[id]; - filter() - }); - const resetButton = document.createElement("button"); - resetButton.textContent = feature.msg("reset"); - resetButton.addEventListener("click", function () { - input.value = '' - if (filterData.period[id]) delete filterData.period[id]; - if (Object.keys(filterData["period"]).length == 0) { if (button.classList.contains('active')) button.classList.remove('active') } - filter() - }); - content.appendChild(input); - content.appendChild(resetButton); - return content; - } - - const shareStart = createInput("shareStart", "startDate"); - const shareEnd = createInput("shareEnd", "endDate"); - const updateStart = createInput("updateStart", "startDate"); - const updateEnd = createInput("updateEnd", "endDate"); - - const shareDetails = createDetails("sharedDate"); - shareDetails.appendChild(shareStart); - shareDetails.appendChild(shareEnd); - - const updateDetails = createDetails("updateDate"); - updateDetails.appendChild(updateStart); - updateDetails.appendChild(updateEnd); - - ScratchTools.modals.create({ - title: `${feature.msg("period")}`, - components: [ - { - type: "html", - content: shareDetails, - }, - { - type: "html", - content: updateDetails, - } - ] - }); - break; + switch (id) { + case "reset": { + filterData = JSON.parse(filterDefault); + document + .querySelectorAll(".ste-filter-bar .ste-filter-button.active") + .forEach((element) => { + element.classList.remove("active"); + }); + filter(); + break; + } + + case "title": { + const includingDetails = createDetails("including"); + const includingTextTag = createTextTag("title", "including"); + includingDetails.appendChild(includingTextTag); + const excludingDetails = createDetails("excluding"); + const excludingTextTag = createTextTag("title", "excluding"); + excludingDetails.appendChild(excludingTextTag); + let modal = ScratchTools.modals.create({ + title: `${feature.msg("title")}`, + components: [ + { + type: "html", + content: includingDetails, + }, + { + type: "html", + content: excludingDetails, + }, + ], + }); + break; + } + + case "author": { + const includingDetails = createDetails("including"); + const includingTextTag = createTextTag("author", "including"); + includingDetails.appendChild(includingTextTag); + const excludingDetails = createDetails("excluding"); + const excludingTextTag = createTextTag("author", "excluding"); + excludingDetails.appendChild(excludingTextTag); + let modal = ScratchTools.modals.create({ + title: `${feature.msg("author")}`, + components: [ + { + type: "html", + content: includingDetails, + }, + { + type: "html", + content: excludingDetails, + }, + ], + }); + break; + } + + case "period": { + function createInput(id, label) { + const content = document.createElement("div"); + content.textContent = feature.msg(label); + const input = document.createElement("input"); + input.type = "date"; + input.style.margin = "0 10px"; + if (filterData.period[id]) input.value = filterData.period[id]; + input.addEventListener("change", function () { + if (input.value) { + filterData["period"][id] = input.value; + button.classList.add("active"); + } else if (filterData.period[id]) delete filterData.period[id]; + filter(); + }); + const resetButton = document.createElement("button"); + resetButton.textContent = feature.msg("reset"); + resetButton.addEventListener("click", function () { + input.value = ""; + if (filterData.period[id]) delete filterData.period[id]; + if (Object.keys(filterData["period"]).length == 0) { + if (button.classList.contains("active")) + button.classList.remove("active"); } - - default: - break; + filter(); + }); + content.appendChild(input); + content.appendChild(resetButton); + return content; } + + const shareStart = createInput("shareStart", "startDate"); + const shareEnd = createInput("shareEnd", "endDate"); + const updateStart = createInput("updateStart", "startDate"); + const updateEnd = createInput("updateEnd", "endDate"); + + const shareDetails = createDetails("sharedDate"); + shareDetails.appendChild(shareStart); + shareDetails.appendChild(shareEnd); + + const updateDetails = createDetails("updateDate"); + updateDetails.appendChild(updateStart); + updateDetails.appendChild(updateEnd); + + ScratchTools.modals.create({ + title: `${feature.msg("period")}`, + components: [ + { + type: "html", + content: shareDetails, + }, + { + type: "html", + content: updateDetails, + }, + ], + }); + break; + } + + default: + break; } + } + + const controlBar = document.createElement("div"); + controlBar.classList.add("ste-filter-bar"); + if (JSON.stringify(filterData) === filterDefault) + controlBar.style.display = "none"; + else filterButton.classList.add("active"); + const filterSettings = document.createElement("div"); + filterSettings.classList.add("ste-filter-settings"); + options.forEach((option) => { + const icon = document.createElement("img"); + icon.src = feature.self.getResource(option.icon); - const controlBar = document.createElement("div"); - controlBar.classList.add('ste-filter-bar'); - if (JSON.stringify(filterData)===filterDefault) controlBar.style.display = 'none'; - else filterButton.classList.add('active'); - const filterSettings = document.createElement("div"); - filterSettings.classList.add('ste-filter-settings'); - options.forEach(option => { - const icon = document.createElement("img"); - icon.src = feature.self.getResource(option.icon); - - const text = document.createElement("p"); - text.textContent = feature.msg(option.id); - - const button = document.createElement("div"); - button.classList.add('ste-filter-button'); - if (filterData[option.id]) if (Object.keys(filterData[option.id]).length !== 0) button.classList.add('active'); - button.appendChild(icon); - button.appendChild(text); - - button.addEventListener("click", function() { - optionButtonClick(option.id, button); - }) + const text = document.createElement("p"); + text.textContent = feature.msg(option.id); + + const button = document.createElement("div"); + button.classList.add("ste-filter-button"); + if (filterData[option.id]) + if (Object.keys(filterData[option.id]).length !== 0) + button.classList.add("active"); + button.appendChild(icon); + button.appendChild(text); + + button.addEventListener("click", function () { + optionButtonClick(option.id, button); + }); filterSettings.appendChild(button); }); @@ -383,56 +412,77 @@ export default async function ({ feature, console }) { }) filterSettings.appendChild(resetButton); - controlBar.appendChild(filterSettings); + controlBar.appendChild(filterSettings); - filterButton.addEventListener("click", function() { - if (controlBar.style.display=='none') { - controlBar.style.display = 'flex'; - filterButton.classList.add('active'); - } else { - controlBar.style.display = 'none'; - if (filterButton.classList.contains('active')) filterButton.classList.remove('active'); - } - }) + filterButton.addEventListener("click", function () { + if (controlBar.style.display == "none") { + controlBar.style.display = "flex"; + filterButton.classList.add("active"); + } else { + controlBar.style.display = "none"; + if (filterButton.classList.contains("active")) + filterButton.classList.remove("active"); + } + }); + switch (page[1]) { + case "search": + case "explore": { + const sortElement = await ScratchTools.waitForElement( + "div.sort-controls" + ); + const sortForm = await ScratchTools.waitForElement("form.sort-mode"); + controlBar.appendChild(sortForm); + sortElement.after(controlBar); - switch (page[1]) { - case 'search': - case 'explore':{ - const sortElement = await ScratchTools.waitForElement("div.sort-controls"); - const sortForm = await ScratchTools.waitForElement("form.sort-mode"); - controlBar.appendChild(sortForm); - sortElement.after(controlBar); - - sortElement.appendChild(filterButton); - break; - } + sortElement.appendChild(filterButton); + break; + } - case 'studios':{ - async function setFilterControl() { - const tabTitle = document.querySelector(".studio-header-container h2"); - const headerContainer = document.querySelector(".studio-header-container"); - headerContainer.after(controlBar); - tabTitle.after(filterButton); - } + case "studios": { + async function setFilterControl() { + const tabTitle = document.querySelector(".studio-header-container h2"); + const headerContainer = document.querySelector( + ".studio-header-container" + ); + headerContainer.after(controlBar); + tabTitle.after(filterButton); + } + await ScratchTools.waitForElement(".studio-project-tile"); + if ( + /^[0-9]+$/.test( + window.location.pathname.replace("/studios/", "").replaceAll("/", "") + ) + ) + setFilterControl(); + const buttons = document.querySelectorAll(".studio-tab-nav .nav_link"); + buttons.forEach((button) => { + if ( + /^[0-9]+$/.test( + button.href.replace("https://scratch.mit.edu/studios/", "") + ) + ) + button.addEventListener("click", async function () { await ScratchTools.waitForElement(".studio-project-tile"); - if (/^[0-9]+$/.test(window.location.pathname.replace('/studios/','').replaceAll('/',''))) setFilterControl(); - const buttons = document.querySelectorAll(".studio-tab-nav .nav_link"); - buttons.forEach(button => { - if (/^[0-9]+$/.test(button.href.replace('https://scratch.mit.edu/studios/',''))) - button.addEventListener("click", async function() { - await ScratchTools.waitForElement(".studio-project-tile"); - setFilterControl(); - }) - }); - break; - } - - default: - break; + setFilterControl(); + }); + }); + break; } - window.addEventListener("beforeunload", async function (event) { - if (feature.settings.get("keep-settings")===true && JSON.stringify(filterData) !== JSON.stringify(await ScratchTools.storage.get("project-filter"))) await ScratchTools.storage.set({ key:"project-filter", value:filterData}) - }); + default: + break; + } + + window.addEventListener("beforeunload", async function (event) { + if ( + feature.settings.get("keep-settings") === true && + JSON.stringify(filterData) !== + JSON.stringify(await ScratchTools.storage.get("project-filter")) + ) + await ScratchTools.storage.set({ + key: "project-filter", + value: filterData, + }); + }); } diff --git a/features/explore-filter/style.css b/features/explore-filter/style.css index 58a7e8f0..873663d2 100644 --- a/features/explore-filter/style.css +++ b/features/explore-filter/style.css @@ -29,6 +29,8 @@ } .ste-filter-bar img { width: 1.5rem; + transform: scale(.6); + margin-left: -.35rem; } .ste-filter-bar .sort-mode { margin: auto 0; @@ -98,3 +100,11 @@ color: #d13636; background-color: #ffe1e1; } + +#app.ste-filter-mode-blur .project.ste-filter-hide { + filter: opacity(.3) blur(.15rem); +} + +#app.ste-filter-mode-hide .project.ste-filter-hide { + display: none; +}