From e53c2235ff78fe082875ad345fe3e650540f747c Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 27 Jul 2022 12:44:40 -0700 Subject: [PATCH 01/76] initial commit --- src/scripts/_index.json | 1 + src/scripts/mute.css | 25 ++++ src/scripts/mute.js | 195 ++++++++++++++++++++++++++++ src/scripts/mute.json | 16 +++ src/scripts/mute/options/index.css | 65 ++++++++++ src/scripts/mute/options/index.html | 33 +++++ src/scripts/mute/options/index.js | 56 ++++++++ 7 files changed, 391 insertions(+) create mode 100644 src/scripts/mute.css create mode 100644 src/scripts/mute.js create mode 100644 src/scripts/mute.json create mode 100644 src/scripts/mute/options/index.css create mode 100644 src/scripts/mute/options/index.html create mode 100644 src/scripts/mute/options/index.js diff --git a/src/scripts/_index.json b/src/scripts/_index.json index 429b3787e4..8d3dde7dca 100644 --- a/src/scripts/_index.json +++ b/src/scripts/_index.json @@ -9,6 +9,7 @@ "mass_deleter", "mass_unliker", "mirror_posts", + "mute", "mutual_checker", "no_recommended", "notificationblock", diff --git a/src/scripts/mute.css b/src/scripts/mute.css new file mode 100644 index 0000000000..4a501eb856 --- /dev/null +++ b/src/scripts/mute.css @@ -0,0 +1,25 @@ +.xkit-mute-active .xkit-mute-hidden article { + /* display: none; */ + opacity: 0.5; +} + +.xkit-mute-lengthened { + min-height: 100vh; +} + +.xkit-mute-warning { + text-align: center; + background-color: rgba(var(--white-on-dark),.07); + color: rgba(var(--white-on-dark),.65); + padding: 25px 20px; + font-weight: 700; + border-radius: 3px; +} + +.xkit-mute-warning ~ * { + visibility: hidden !important; +} + +.xkit-mute-warning ~ * :is(img, video, canvas) { + display: none; +} diff --git a/src/scripts/mute.js b/src/scripts/mute.js new file mode 100644 index 0000000000..8d135cb1dd --- /dev/null +++ b/src/scripts/mute.js @@ -0,0 +1,195 @@ +import { filterPostElements, blogViewSelector } from '../util/interface.js'; +import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; +import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; +import { timelineObject } from '../util/react_props.js'; +import { onNewPosts, pageModifications } from '../util/mutations.js'; +import { keyToCss } from '../util/css_map.js'; +import { dom } from '../util/dom.js'; + +const meatballButtonId = 'mute'; +const meatballButtonLabel = 'Mute options'; + +const activeClass = 'xkit-mute-active'; +const hiddenClass = 'xkit-mute-hidden'; +const lengthenedClass = 'xkit-mute-lengthened'; +const warningClass = 'xkit-mute-warning'; + +const storageKey = 'mute.mutedblogs'; + +let mutedBlogs = {}; + +const lengthenTimeline = async (timeline) => { + if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { + timeline.classList.add(lengthenedClass); + } +}; + +const updateWarningElement = (timelineElement, timeline) => { + if (timelineElement.dataset.xkitRemovedTimelineWarning === timeline) return; + + const currentWarningElement = timelineElement.previousElementSibling?.classList?.contains(warningClass) + ? timelineElement.previousElementSibling + : null; + + if (currentWarningElement) { + if (currentWarningElement.dataset.cachedTimeline === timeline) { + return; + } else { + currentWarningElement.remove(); + } + } + // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) + const currentBlogUuidOrName = timeline.split('/')[3]; + const mutedBlogEntry = Object.entries(mutedBlogs).find(([uuid, [name]]) => uuid === currentBlogUuidOrName || name === currentBlogUuidOrName); + + if (mutedBlogEntry) { + const [, [name]] = mutedBlogEntry; + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted ${name}!`, + dom('br'), + dom( + 'button', + null, + { click: () => { warningElement.remove(); timelineElement.dataset.xkitRemovedTimelineWarning = timeline; } }, + 'show posts anyway' + ) + ]); + warningElement.dataset.cachedTimeline = timeline; + timelineElement.before(warningElement); + } +}; + +const processTimelines = async () => { + [...document.querySelectorAll('[data-timeline]')].forEach(async timelineElement => { + const timeline = timelineElement.dataset.timeline; + const isSingleBlog = timeline.startsWith('/v2/blog/'); + const isInBlogView = timelineElement.matches(blogViewSelector); + const isChannel = isSingleBlog === true && isInBlogView === false; + + console.log({ timelineElement, timeline, isBlogView: isSingleBlog, isInBlogView, isChannel }); + + if (isChannel) return; + + if (isInBlogView && timelineElement.closest(keyToCss('moreContent')) === null) { + updateWarningElement(timelineElement, timeline); + } + + lengthenTimeline(timelineElement); + timelineElement.classList.add(activeClass); + }); +}; + +const processPosts = async function (postElements) { + processTimelines(); + + filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { + // eslint-disable-next-line prefer-const + let { blog: { name, uuid }, rebloggedRootName, rebloggedRootUuid, content } = await timelineObject(postElement); + + // should there be a setting such that reblogs with contributed content count? + // eslint-disable-next-line no-unused-vars + const contributedContent = content.length > 0; + const isRebloggedPost = Boolean(rebloggedRootUuid); + + // don't hide based on currently viewed blog if warning is dismissed; still mute others + const currentTimeline = postElement.closest('[data-timeline]').dataset.timeline; + if (currentTimeline.startsWith(`/v2/blog/${name}/`) || currentTimeline.startsWith(`/v2/blog/${uuid}/`)) uuid = undefined; + if (currentTimeline.startsWith(`/v2/blog/${rebloggedRootName}/`) || currentTimeline.startsWith(`/v2/blog/${rebloggedRootUuid}/`)) rebloggedRootUuid = undefined; + + const blogMode = mutedBlogs[uuid]?.[1]; + const rootMode = mutedBlogs[rebloggedRootUuid]?.[1]; + + if (isRebloggedPost) { + if (['all', 'reblogged'].includes(blogMode) || ['all', 'original'].includes(rootMode)) { + postElement.classList.add(hiddenClass); + } + } else { + if (['all', 'original'].includes(blogMode)) { + postElement.classList.add(hiddenClass); + } + } + }); +}; + +const onButtonClicked = async function ({ currentTarget }) { + const { blog: { name, uuid } } = currentTarget.__timelineObjectData; + + const currentMode = mutedBlogs[uuid]?.[1]; + + const createRadioElement = (value) => + dom('label', null, null, [ + dom('input', { type: 'radio', name: 'muteOption', value }), + `Hide ${value} posts` + ]); + + const form = dom('form', { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid }, { submit: muteUser }, [ + createRadioElement('all'), + createRadioElement('original'), + createRadioElement('reblogged') + ]); + + form.elements.muteOption.value = currentMode; + + showModal({ + title: `Mute ${name}`, + message: [form], + buttons: [ + modalCancelButton, + ...currentMode ? [dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute'])] : [], + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) + ] + }); +}; + +const muteUser = event => { + event.preventDefault(); + + const { name, uuid } = event.currentTarget.dataset; + const { value } = event.currentTarget.elements.muteOption; + if (value === '') return; + + mutedBlogs[uuid] = [name, value]; + browser.storage.local.set({ [storageKey]: mutedBlogs }); + + hideModal(); +}; + +const unmuteUser = uuid => { + delete mutedBlogs[uuid]; + browser.storage.local.set({ [storageKey]: mutedBlogs }); + + hideModal(); +}; + +export const onStorageChanged = async function (changes, areaName) { + const { [storageKey]: mutedBlogsChanges } = changes; + + if (mutedBlogsChanges) { + ({ newValue: mutedBlogs } = mutedBlogsChanges); + $(`.${hiddenClass}`).removeClass(hiddenClass); + // clean up warnings and data attributes from updateWarningElement + + pageModifications.trigger(processPosts); + } +}; + +export const main = async function () { + ({ [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey)); + + registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, onclick: onButtonClicked }); + onNewPosts.addListener(processPosts); +}; + +export const clean = async function () { + unregisterMeatballItem(meatballButtonId); + onNewPosts.removeListener(processPosts); + + $(`.${activeClass}`).removeClass(activeClass); + $(`.${hiddenClass}`).removeClass(hiddenClass); + $(`.${lengthenedClass}`).removeClass(lengthenedClass); + $(`.${warningClass}`).remove(); + + // clean up data attributes from updateWarningElement +}; + +export const stylesheet = true; diff --git a/src/scripts/mute.json b/src/scripts/mute.json new file mode 100644 index 0000000000..1bccf2c6fe --- /dev/null +++ b/src/scripts/mute.json @@ -0,0 +1,16 @@ +{ + "title": "Mute", + "description": "", + "icon": { + "class_name": "ri-volume-mute-line", + "color": "white", + "background_color": "#C20044" + }, + "preferences": { + "manageBlockedPosts": { + "type": "iframe", + "label": "Manage muted users", + "src": "/scripts/mute/options/index.html" + } + } +} diff --git a/src/scripts/mute/options/index.css b/src/scripts/mute/options/index.css new file mode 100644 index 0000000000..cbb920cb1f --- /dev/null +++ b/src/scripts/mute/options/index.css @@ -0,0 +1,65 @@ +:root { + --black: 21, 20, 25; + --white: 255, 255, 255; + --grey: 207, 207, 216; + --accent: 10, 132, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --black: 251, 251, 254; + --white: 66, 65, 77; + --grey: 91, 91, 102; + --accent: 54, 213, 255; + } +} + +html { + font-size: 14px; + scrollbar-color: rgb(var(--grey)) transparent; + scrollbar-width: thin; + overflow-y: hidden; +} + +body { + background-color: rgb(var(--white)); + color: rgb(var(--black)); + font-family: "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; + font-size: 100%; + -webkit-user-select: none; + user-select: none; +} + +header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-bottom: 1em; +} + +#muted-blogs { + padding: 0; + border-bottom: 1px solid rgb(var(--grey)); + margin: 0; +} + +.muted-blog { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 1ch 0; + border-top: 1px solid rgb(var(--grey)); +} + +.muted-blog button { + padding: 0; + border: none; + + appearance: none; + background-color: transparent; + color: rgb(var(--accent)); + cursor: pointer; + font-weight: bold; +} diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html new file mode 100644 index 0000000000..59e00c6e86 --- /dev/null +++ b/src/scripts/mute/options/index.html @@ -0,0 +1,33 @@ + + + + + + XKit: Manage muted blogs + + + + + + + + + +
+
Your muted blogs:
+ +
+ + diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js new file mode 100644 index 0000000000..e362d027da --- /dev/null +++ b/src/scripts/mute/options/index.js @@ -0,0 +1,56 @@ +const mutedBlogList = document.getElementById('muted-blogs'); +const mutedBlogTemplate = document.getElementById('muted-blog'); + +const storageKey = 'mute.mutedblogs'; + +const unmuteUser = async function ({ currentTarget }) { + const { [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey); + const { uuid } = currentTarget.closest('li').dataset; + + delete mutedBlogs[uuid]; + browser.storage.local.set({ [storageKey]: mutedBlogs }); +}; + +const updateMode = async function (event) { + const { [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey); + const { uuid, name } = event.target.closest('li').dataset; + const { value } = event.target; + + mutedBlogs[uuid] = [name, value]; + browser.storage.local.set({ [storageKey]: mutedBlogs }); +}; + +const renderMutedBlogs = async function () { + const { [storageKey]: mutedblogs = [] } = await browser.storage.local.get(storageKey); + + mutedBlogList.textContent = ''; + + for (const [uuid, [name, mode]] of Object.entries(mutedblogs)) { + const templateClone = mutedBlogTemplate.content.cloneNode(true); + const li = templateClone.querySelector('li'); + const linkElement = templateClone.querySelector('a'); + const modeSelect = templateClone.querySelector('select'); + const unmuteButton = templateClone.querySelector('button'); + + li.dataset.uuid = uuid; + li.dataset.name = name; + + linkElement.textContent = name; + linkElement.href = `https://www.tumblr.com/blog/view/${uuid}`; + + modeSelect.value = mode; + modeSelect.addEventListener('change', updateMode); + + unmuteButton.addEventListener('click', unmuteUser); + + mutedBlogList.append(templateClone); + } +}; + +browser.storage.onChanged.addListener((changes, areaName) => { + if (areaName === 'local' && Object.keys(changes).includes(storageKey)) { + renderMutedBlogs(); + } +}); + +renderMutedBlogs(); From 696808dca33025a2d0bd48f6ddbd708a8deac1a4 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 3 Aug 2022 15:40:06 -0700 Subject: [PATCH 02/76] fix invisible warning this css is intended as temporary anyway --- src/scripts/mute.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index 4a501eb856..abce595eb0 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -9,8 +9,8 @@ .xkit-mute-warning { text-align: center; - background-color: rgba(var(--white-on-dark),.07); - color: rgba(var(--white-on-dark),.65); + background-color: var(--blog-link-color-15); + color: var(--blog-link-color); padding: 25px 20px; font-weight: 700; border-radius: 3px; From e3f52e46eded591e8cb5b7d69b21094df0d63d9b Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 3 Aug 2022 15:40:30 -0700 Subject: [PATCH 03/76] redesign warning element state logic --- src/scripts/mute.js | 51 +++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 8d135cb1dd..46acecc37d 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -18,6 +18,8 @@ const storageKey = 'mute.mutedblogs'; let mutedBlogs = {}; +const dismissedWarnings = new Set(); + const lengthenTimeline = async (timeline) => { if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { timeline.classList.add(lengthenedClass); @@ -25,38 +27,37 @@ const lengthenTimeline = async (timeline) => { }; const updateWarningElement = (timelineElement, timeline) => { - if (timelineElement.dataset.xkitRemovedTimelineWarning === timeline) return; + if (dismissedWarnings.has(timeline)) return; const currentWarningElement = timelineElement.previousElementSibling?.classList?.contains(warningClass) ? timelineElement.previousElementSibling : null; - if (currentWarningElement) { - if (currentWarningElement.dataset.cachedTimeline === timeline) { - return; - } else { - currentWarningElement.remove(); + if (currentWarningElement === null || currentWarningElement.dataset.forTimeline !== timeline) { + currentWarningElement?.remove(); + + // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) + const mutedBlogEntry = Object.entries(mutedBlogs).find( + ([uuid, [name]]) => + timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) + ); + + if (mutedBlogEntry) { + const [, [name]] = mutedBlogEntry; + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted ${name}!`, + dom('br'), + dom( + 'button', + null, + { click: () => { warningElement.remove(); dismissedWarnings.add(timeline); } }, + 'show posts anyway' + ) + ]); + warningElement.dataset.forTimeline = timeline; + timelineElement.before(warningElement); } } - // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) - const currentBlogUuidOrName = timeline.split('/')[3]; - const mutedBlogEntry = Object.entries(mutedBlogs).find(([uuid, [name]]) => uuid === currentBlogUuidOrName || name === currentBlogUuidOrName); - - if (mutedBlogEntry) { - const [, [name]] = mutedBlogEntry; - const warningElement = dom('div', { class: warningClass }, null, [ - `You have muted ${name}!`, - dom('br'), - dom( - 'button', - null, - { click: () => { warningElement.remove(); timelineElement.dataset.xkitRemovedTimelineWarning = timeline; } }, - 'show posts anyway' - ) - ]); - warningElement.dataset.cachedTimeline = timeline; - timelineElement.before(warningElement); - } }; const processTimelines = async () => { From 26560e9ad58c1d21b3988d1f2e1366bbea1c6eab Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 3 Aug 2022 18:57:30 -0700 Subject: [PATCH 04/76] my long state machine rewrite is perfectly constructed moves original/reblogged/all logic to css; moves state logic to control element before timeline --- src/scripts/mute.css | 5 -- src/scripts/mute.js | 191 ++++++++++++++++++++++++++----------------- 2 files changed, 118 insertions(+), 78 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index abce595eb0..9efd972281 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,8 +1,3 @@ -.xkit-mute-active .xkit-mute-hidden article { - /* display: none; */ - opacity: 0.5; -} - .xkit-mute-lengthened { min-height: 100vh; } diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 46acecc37d..0b5b43ecd5 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -1,61 +1,116 @@ -import { filterPostElements, blogViewSelector } from '../util/interface.js'; +import { filterPostElements, blogViewSelector, buildStyle } from '../util/interface.js'; import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; -import { onNewPosts, pageModifications } from '../util/mutations.js'; +import { onNewPosts } from '../util/mutations.js'; import { keyToCss } from '../util/css_map.js'; import { dom } from '../util/dom.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = 'Mute options'; -const activeClass = 'xkit-mute-active'; -const hiddenClass = 'xkit-mute-hidden'; -const lengthenedClass = 'xkit-mute-lengthened'; const warningClass = 'xkit-mute-warning'; +const lengthenedClass = 'xkit-mute-lengthened'; +const controlsClass = 'xkit-mute-controls'; const storageKey = 'mute.mutedblogs'; let mutedBlogs = {}; +const dismissedUuids = new Set(); + +const styleElement = buildStyle(); + +const buildSelectorString = excludedUuid => { + const selectors = []; + Object.entries(mutedBlogs).forEach(([uuid, [name, mode]]) => { + if (uuid === excludedUuid) return; + + if (['all', 'original'].includes(mode)) { + selectors.push(`[data-xkit-mute-original-uuid="${uuid}"]`); + } + if (['all', 'reblogged'].includes(mode)) { + selectors.push(`[data-xkit-mute-reblog-uuid="${uuid}"]`); + } + }); + return `:is(${selectors.join(', ')})`; +}; -const dismissedWarnings = new Set(); +const updateStyleElement = () => { + styleElement.textContent = ''; + + styleElement.textContent += ` + [data-mute="on"] + [data-timeline] ${buildSelectorString()} article { + /* display: none; */ + opacity: 0.5; + } + `; -const lengthenTimeline = async (timeline) => { + dismissedUuids.forEach(uuid => { + styleElement.textContent += ` + [data-mute="except ${uuid}"] + [data-timeline] ${buildSelectorString(uuid)} article { + /* display: none; */ + opacity: 0.5; + } + `; + }); +}; + +const lengthenTimeline = async timeline => { if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { timeline.classList.add(lengthenedClass); } }; -const updateWarningElement = (timelineElement, timeline) => { - if (dismissedWarnings.has(timeline)) return; - - const currentWarningElement = timelineElement.previousElementSibling?.classList?.contains(warningClass) - ? timelineElement.previousElementSibling - : null; - - if (currentWarningElement === null || currentWarningElement.dataset.forTimeline !== timeline) { - currentWarningElement?.remove(); - - // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) - const mutedBlogEntry = Object.entries(mutedBlogs).find( - ([uuid, [name]]) => - timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) - ); - - if (mutedBlogEntry) { - const [, [name]] = mutedBlogEntry; - const warningElement = dom('div', { class: warningClass }, null, [ - `You have muted ${name}!`, - dom('br'), - dom( - 'button', - null, - { click: () => { warningElement.remove(); dismissedWarnings.add(timeline); } }, - 'show posts anyway' - ) - ]); - warningElement.dataset.forTimeline = timeline; - timelineElement.before(warningElement); +const addControls = (timelineElement, timeline) => { + const isSingleBlog = timeline.startsWith('/v2/blog/'); + const isInBlogView = timelineElement.matches(blogViewSelector); + const isChannel = isSingleBlog === true && isInBlogView === false; + + const controls = dom('div', { class: controlsClass }); + controls.dataset.forTimeline = timeline; + controls.dataset.mute = 'on'; + timelineElement.before(controls); + + if (isChannel) { + controls.dataset.mute = 'off'; + return; + } + + // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) + const mutedBlogEntry = Object.entries(mutedBlogs).find( + ([uuid, [name]]) => + timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) + ); + + if (mutedBlogEntry) { + const [uuid, [name, mode]] = mutedBlogEntry; + + if (mode === 'all') { + controls.dataset.mute = `except ${uuid}`; + + if (!dismissedUuids.has(uuid)) { + controls.classList.add(warningClass); + controls.replaceChildren( + ...[ + `You have muted all posts from ${name}!`, + dom('br'), + dom( + 'button', + null, + { + click: () => { + controls.classList.remove(warningClass); + controls.replaceChildren([]); + + dismissedUuids.add(uuid); + updateStyleElement(); + } + }, + 'show posts anyway' + ) + ] + ); + } } } }; @@ -63,20 +118,16 @@ const updateWarningElement = (timelineElement, timeline) => { const processTimelines = async () => { [...document.querySelectorAll('[data-timeline]')].forEach(async timelineElement => { const timeline = timelineElement.dataset.timeline; - const isSingleBlog = timeline.startsWith('/v2/blog/'); - const isInBlogView = timelineElement.matches(blogViewSelector); - const isChannel = isSingleBlog === true && isInBlogView === false; - - console.log({ timelineElement, timeline, isBlogView: isSingleBlog, isInBlogView, isChannel }); - if (isChannel) return; + const currentControls = timelineElement.previousElementSibling?.classList?.contains(controlsClass) + ? timelineElement.previousElementSibling + : null; - if (isInBlogView && timelineElement.closest(keyToCss('moreContent')) === null) { - updateWarningElement(timelineElement, timeline); + if (currentControls?.dataset?.forTimeline !== timeline) { + currentControls?.remove(); + addControls(timelineElement, timeline); + lengthenTimeline(timelineElement); } - - lengthenTimeline(timelineElement); - timelineElement.classList.add(activeClass); }); }; @@ -84,30 +135,18 @@ const processPosts = async function (postElements) { processTimelines(); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - // eslint-disable-next-line prefer-const - let { blog: { name, uuid }, rebloggedRootName, rebloggedRootUuid, content } = await timelineObject(postElement); + const { blog: { uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); // should there be a setting such that reblogs with contributed content count? // eslint-disable-next-line no-unused-vars const contributedContent = content.length > 0; const isRebloggedPost = Boolean(rebloggedRootUuid); - // don't hide based on currently viewed blog if warning is dismissed; still mute others - const currentTimeline = postElement.closest('[data-timeline]').dataset.timeline; - if (currentTimeline.startsWith(`/v2/blog/${name}/`) || currentTimeline.startsWith(`/v2/blog/${uuid}/`)) uuid = undefined; - if (currentTimeline.startsWith(`/v2/blog/${rebloggedRootName}/`) || currentTimeline.startsWith(`/v2/blog/${rebloggedRootUuid}/`)) rebloggedRootUuid = undefined; - - const blogMode = mutedBlogs[uuid]?.[1]; - const rootMode = mutedBlogs[rebloggedRootUuid]?.[1]; - if (isRebloggedPost) { - if (['all', 'reblogged'].includes(blogMode) || ['all', 'original'].includes(rootMode)) { - postElement.classList.add(hiddenClass); - } + postElement.dataset.xkitMuteReblogUuid = uuid; + postElement.dataset.xkitMuteOriginalUuid = rebloggedRootUuid; } else { - if (['all', 'original'].includes(blogMode)) { - postElement.classList.add(hiddenClass); - } + postElement.dataset.xkitMuteOriginalUuid = uuid; } }); }; @@ -117,7 +156,7 @@ const onButtonClicked = async function ({ currentTarget }) { const currentMode = mutedBlogs[uuid]?.[1]; - const createRadioElement = (value) => + const createRadioElement = value => dom('label', null, null, [ dom('input', { type: 'radio', name: 'muteOption', value }), `Hide ${value} posts` @@ -167,30 +206,36 @@ export const onStorageChanged = async function (changes, areaName) { if (mutedBlogsChanges) { ({ newValue: mutedBlogs } = mutedBlogsChanges); - $(`.${hiddenClass}`).removeClass(hiddenClass); - // clean up warnings and data attributes from updateWarningElement - pageModifications.trigger(processPosts); + $(`.${controlsClass}`).remove(); + processTimelines(); + updateStyleElement(); } }; export const main = async function () { ({ [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey)); - registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, onclick: onButtonClicked }); + updateStyleElement(); + document.head.append(styleElement); + registerMeatballItem({ + id: meatballButtonId, + label: meatballButtonLabel, + onclick: onButtonClicked + }); onNewPosts.addListener(processPosts); }; export const clean = async function () { + styleElement.remove(); unregisterMeatballItem(meatballButtonId); onNewPosts.removeListener(processPosts); - $(`.${activeClass}`).removeClass(activeClass); - $(`.${hiddenClass}`).removeClass(hiddenClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); - $(`.${warningClass}`).remove(); + $(`.${controlsClass}`).remove(); - // clean up data attributes from updateWarningElement + $('[data-xkit-mute-original-uuid]').removeAttr('data-xkit-mute-original-uuid'); + $('[data-xkit-mute-reblog-uuid]').removeAttr('data-xkit-mute-reblog-uuid'); }; export const stylesheet = true; From cef5fc278fbfa0f04b6cdfdb16fc6b225e572e6f Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 3 Aug 2022 21:43:29 -0700 Subject: [PATCH 05/76] cleanup --- src/scripts/mute.js | 60 +++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 0b5b43ecd5..e4a3274d7f 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -16,11 +16,11 @@ const controlsClass = 'xkit-mute-controls'; const storageKey = 'mute.mutedblogs'; let mutedBlogs = {}; -const dismissedUuids = new Set(); +const dismissedWarningUuids = new Set(); const styleElement = buildStyle(); -const buildSelectorString = excludedUuid => { +const buildPostSelector = excludedUuid => { const selectors = []; Object.entries(mutedBlogs).forEach(([uuid, [name, mode]]) => { if (uuid === excludedUuid) return; @@ -37,17 +37,15 @@ const buildSelectorString = excludedUuid => { const updateStyleElement = () => { styleElement.textContent = ''; - styleElement.textContent += ` - [data-mute="on"] + [data-timeline] ${buildSelectorString()} article { + [data-mute="on"] + [data-timeline] ${buildPostSelector()} article { /* display: none; */ opacity: 0.5; } `; - - dismissedUuids.forEach(uuid => { + dismissedWarningUuids.forEach(uuid => { styleElement.textContent += ` - [data-mute="except ${uuid}"] + [data-timeline] ${buildSelectorString(uuid)} article { + [data-mute="except ${uuid}"] + [data-timeline] ${buildPostSelector(uuid)} article { /* display: none; */ opacity: 0.5; } @@ -55,7 +53,7 @@ const updateStyleElement = () => { }); }; -const lengthenTimeline = async timeline => { +const lengthenTimeline = timeline => { if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { timeline.classList.add(lengthenedClass); } @@ -88,35 +86,28 @@ const addControls = (timelineElement, timeline) => { if (mode === 'all') { controls.dataset.mute = `except ${uuid}`; - if (!dismissedUuids.has(uuid)) { + if (!dismissedWarningUuids.has(uuid)) { + const dismissWarning = () => { + dismissedWarningUuids.add(uuid); + updateStyleElement(); + + controls.classList.remove(warningClass); + controls.replaceChildren([]); + }; + controls.classList.add(warningClass); - controls.replaceChildren( - ...[ - `You have muted all posts from ${name}!`, - dom('br'), - dom( - 'button', - null, - { - click: () => { - controls.classList.remove(warningClass); - controls.replaceChildren([]); - - dismissedUuids.add(uuid); - updateStyleElement(); - } - }, - 'show posts anyway' - ) - ] - ); + controls.replaceChildren(...[ + `You have muted all posts from ${name}!`, + dom('br'), + dom('button', null, { click: dismissWarning }, 'show posts anyway') + ]); } } } }; -const processTimelines = async () => { - [...document.querySelectorAll('[data-timeline]')].forEach(async timelineElement => { +const processTimelines = () => { + [...document.querySelectorAll('[data-timeline]')].forEach(timelineElement => { const timeline = timelineElement.dataset.timeline; const currentControls = timelineElement.previousElementSibling?.classList?.contains(controlsClass) @@ -131,7 +122,7 @@ const processTimelines = async () => { }); }; -const processPosts = async function (postElements) { +const processPosts = function (postElements) { processTimelines(); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { @@ -151,7 +142,7 @@ const processPosts = async function (postElements) { }); }; -const onButtonClicked = async function ({ currentTarget }) { +const onMeatballButtonClicked = function ({ currentTarget }) { const { blog: { name, uuid } } = currentTarget.__timelineObjectData; const currentMode = mutedBlogs[uuid]?.[1]; @@ -221,7 +212,7 @@ export const main = async function () { registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, - onclick: onButtonClicked + onclick: onMeatballButtonClicked }); onNewPosts.addListener(processPosts); }; @@ -233,7 +224,6 @@ export const clean = async function () { $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${controlsClass}`).remove(); - $('[data-xkit-mute-original-uuid]').removeAttr('data-xkit-mute-original-uuid'); $('[data-xkit-mute-reblog-uuid]').removeAttr('data-xkit-mute-reblog-uuid'); }; From a5b1a2edb96654c8198143648b0094b4c6c23d6c Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 3 Aug 2022 22:21:28 -0700 Subject: [PATCH 06/76] Clear dismissed warnings in clean() --- src/scripts/mute.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index e4a3274d7f..dc4096c4a1 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -226,6 +226,8 @@ export const clean = async function () { $(`.${controlsClass}`).remove(); $('[data-xkit-mute-original-uuid]').removeAttr('data-xkit-mute-original-uuid'); $('[data-xkit-mute-reblog-uuid]').removeAttr('data-xkit-mute-reblog-uuid'); + + dismissedWarningUuids.clear(); }; export const stylesheet = true; From 7e106e3e93fe414cec33b91b8be67f765d6141c5 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 4 Aug 2022 00:51:22 -0700 Subject: [PATCH 07/76] Implement dataset-based non css method --- src/scripts/mute.css | 5 ++ src/scripts/mute.js | 169 ++++++++++++++++--------------------------- 2 files changed, 68 insertions(+), 106 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index 9efd972281..abce595eb0 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,3 +1,8 @@ +.xkit-mute-active .xkit-mute-hidden article { + /* display: none; */ + opacity: 0.5; +} + .xkit-mute-lengthened { min-height: 100vh; } diff --git a/src/scripts/mute.js b/src/scripts/mute.js index dc4096c4a1..c2f1ac9812 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -1,123 +1,75 @@ -import { filterPostElements, blogViewSelector, buildStyle } from '../util/interface.js'; +import { filterPostElements, blogViewSelector } from '../util/interface.js'; import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; -import { onNewPosts } from '../util/mutations.js'; +import { onNewPosts, pageModifications } from '../util/mutations.js'; import { keyToCss } from '../util/css_map.js'; import { dom } from '../util/dom.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = 'Mute options'; +const hiddenClass = 'xkit-mute-hidden'; +const activeClass = 'xkit-mute-active'; const warningClass = 'xkit-mute-warning'; const lengthenedClass = 'xkit-mute-lengthened'; -const controlsClass = 'xkit-mute-controls'; const storageKey = 'mute.mutedblogs'; let mutedBlogs = {}; const dismissedWarningUuids = new Set(); -const styleElement = buildStyle(); - -const buildPostSelector = excludedUuid => { - const selectors = []; - Object.entries(mutedBlogs).forEach(([uuid, [name, mode]]) => { - if (uuid === excludedUuid) return; - - if (['all', 'original'].includes(mode)) { - selectors.push(`[data-xkit-mute-original-uuid="${uuid}"]`); - } - if (['all', 'reblogged'].includes(mode)) { - selectors.push(`[data-xkit-mute-reblog-uuid="${uuid}"]`); - } - }); - return `:is(${selectors.join(', ')})`; -}; - -const updateStyleElement = () => { - styleElement.textContent = ''; - styleElement.textContent += ` - [data-mute="on"] + [data-timeline] ${buildPostSelector()} article { - /* display: none; */ - opacity: 0.5; - } - `; - dismissedWarningUuids.forEach(uuid => { - styleElement.textContent += ` - [data-mute="except ${uuid}"] + [data-timeline] ${buildPostSelector(uuid)} article { - /* display: none; */ - opacity: 0.5; - } - `; - }); -}; - const lengthenTimeline = timeline => { if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { timeline.classList.add(lengthenedClass); } }; -const addControls = (timelineElement, timeline) => { - const isSingleBlog = timeline.startsWith('/v2/blog/'); - const isInBlogView = timelineElement.matches(blogViewSelector); - const isChannel = isSingleBlog === true && isInBlogView === false; - - const controls = dom('div', { class: controlsClass }); - controls.dataset.forTimeline = timeline; - controls.dataset.mute = 'on'; - timelineElement.before(controls); - - if (isChannel) { - controls.dataset.mute = 'off'; - return; - } - - // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) - const mutedBlogEntry = Object.entries(mutedBlogs).find( - ([uuid, [name]]) => - timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) - ); - - if (mutedBlogEntry) { - const [uuid, [name, mode]] = mutedBlogEntry; - - if (mode === 'all') { - controls.dataset.mute = `except ${uuid}`; - - if (!dismissedWarningUuids.has(uuid)) { - const dismissWarning = () => { - dismissedWarningUuids.add(uuid); - updateStyleElement(); - - controls.classList.remove(warningClass); - controls.replaceChildren([]); - }; - - controls.classList.add(warningClass); - controls.replaceChildren(...[ - `You have muted all posts from ${name}!`, - dom('br'), - dom('button', null, { click: dismissWarning }, 'show posts anyway') - ]); - } - } - } -}; - const processTimelines = () => { [...document.querySelectorAll('[data-timeline]')].forEach(timelineElement => { - const timeline = timelineElement.dataset.timeline; + const { timeline, muteProcessedTimeline } = timelineElement.dataset; - const currentControls = timelineElement.previousElementSibling?.classList?.contains(controlsClass) - ? timelineElement.previousElementSibling - : null; + const alreadyProcessed = timeline === muteProcessedTimeline; + const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); - if (currentControls?.dataset?.forTimeline !== timeline) { - currentControls?.remove(); - addControls(timelineElement, timeline); + if (!alreadyProcessed && !isChannel) { + timelineElement.dataset.muteProcessedTimeline = timeline; + + timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); + + if (timelineElement.previousElementSibling?.classList?.contains(warningClass)) { + timelineElement.previousElementSibling.remove(); + } + delete timelineElement.dataset.muteExcludedUuid; + + // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) + const mutedBlogEntry = Object.entries(mutedBlogs).find( + ([uuid, [name]]) => + timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) + ); + + if (mutedBlogEntry) { + const [uuid, [name, mode]] = mutedBlogEntry; + + if (mode === 'all') { + timelineElement.dataset.muteExcludedUuid = uuid; + + if (!dismissedWarningUuids.has(uuid)) { + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted all posts from ${name}!`, + dom('br'), + dom('button', null, { + click: () => { + dismissedWarningUuids.add(uuid); + warningElement.remove(); + } + }, 'show posts anyway') + ]); + timelineElement.before(warningElement); + } + } + } } }); }; @@ -127,17 +79,21 @@ const processPosts = function (postElements) { filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const { blog: { uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); + const { muteExcludedUuid } = postElement.closest('[data-timeline]').dataset; // should there be a setting such that reblogs with contributed content count? // eslint-disable-next-line no-unused-vars const contributedContent = content.length > 0; const isRebloggedPost = Boolean(rebloggedRootUuid); - if (isRebloggedPost) { - postElement.dataset.xkitMuteReblogUuid = uuid; - postElement.dataset.xkitMuteOriginalUuid = rebloggedRootUuid; - } else { - postElement.dataset.xkitMuteOriginalUuid = uuid; + const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid; + const reblogUuid = isRebloggedPost ? uuid : null; + + if (originalUuid !== muteExcludedUuid && ['all', 'original'].includes(mutedBlogs[originalUuid]?.[1])) { + postElement.classList.add(hiddenClass); + } + if (reblogUuid !== muteExcludedUuid && ['all', 'reblogged'].includes(mutedBlogs[reblogUuid]?.[1])) { + postElement.classList.add(hiddenClass); } }); }; @@ -198,17 +154,17 @@ export const onStorageChanged = async function (changes, areaName) { if (mutedBlogsChanges) { ({ newValue: mutedBlogs } = mutedBlogsChanges); - $(`.${controlsClass}`).remove(); - processTimelines(); - updateStyleElement(); + $(`.${hiddenClass}`).removeClass(hiddenClass); + $(`.${warningClass}`).remove(); + $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); + $('[data-mute-excluded-uuid]').removeAttr('data-mute-excluded-uuid'); + pageModifications.trigger(processPosts); } }; export const main = async function () { ({ [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey)); - updateStyleElement(); - document.head.append(styleElement); registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, @@ -218,14 +174,15 @@ export const main = async function () { }; export const clean = async function () { - styleElement.remove(); unregisterMeatballItem(meatballButtonId); onNewPosts.removeListener(processPosts); + $(`.${hiddenClass}`).removeClass(hiddenClass); + $(`.${activeClass}`).removeClass(activeClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); - $(`.${controlsClass}`).remove(); - $('[data-xkit-mute-original-uuid]').removeAttr('data-xkit-mute-original-uuid'); - $('[data-xkit-mute-reblog-uuid]').removeAttr('data-xkit-mute-reblog-uuid'); + $(`.${warningClass}`).remove(); + $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); + $('[data-mute-excluded-uuid]').removeAttr('data-mute-excluded-uuid'); dismissedWarningUuids.clear(); }; From 881f5543159067716e93778d4c2d12dc23280814 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 17 Aug 2022 16:28:44 -0700 Subject: [PATCH 08/76] Get timeline UUIDs using timelineObject --- src/scripts/mute.js | 72 ++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index c2f1ac9812..0b64dc164f 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -1,4 +1,4 @@ -import { filterPostElements, blogViewSelector } from '../util/interface.js'; +import { filterPostElements, blogViewSelector, postSelector } from '../util/interface.js'; import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; @@ -25,6 +25,48 @@ const lengthenTimeline = timeline => { } }; +const getCurrentUuid = async (timelineElement, timeline) => { + const uuidOrName = timeline.split('/')?.[3]; + + if (uuidOrName.startsWith('t:')) return uuidOrName; + + const posts = [...timelineElement.querySelectorAll(postSelector)]; + for (const post of posts) { + const { blog: { name, uuid } } = await timelineObject(post); + if (name === uuidOrName || uuid === uuidOrName) return uuid; + } + + throw new Error(`could not determine UUID of timeline ${timeline}`); +}; + +const processBlogSpecificTimeline = async (timelineElement, timeline) => { + const currentUuid = await getCurrentUuid(timelineElement, timeline); + + const mutedBlogEntry = Object.entries(mutedBlogs).find(([uuid]) => currentUuid === uuid); + + if (mutedBlogEntry) { + const [uuid, [name, mode]] = mutedBlogEntry; + + if (mode === 'all') { + timelineElement.dataset.muteExcludedUuid = uuid; + + if (!dismissedWarningUuids.has(uuid)) { + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted all posts from ${name}!`, + dom('br'), + dom('button', null, { + click: () => { + dismissedWarningUuids.add(uuid); + warningElement.remove(); + } + }, 'show posts anyway') + ]); + timelineElement.before(warningElement); + } + } + } +}; + const processTimelines = () => { [...document.querySelectorAll('[data-timeline]')].forEach(timelineElement => { const { timeline, muteProcessedTimeline } = timelineElement.dataset; @@ -43,32 +85,8 @@ const processTimelines = () => { } delete timelineElement.dataset.muteExcludedUuid; - // TODO: more reliable check if current blog is muted (currently susceptible to stored outdated blognames) - const mutedBlogEntry = Object.entries(mutedBlogs).find( - ([uuid, [name]]) => - timeline.startsWith(`/v2/blog/${uuid}/`) || timeline.startsWith(`/v2/blog/${name}/`) - ); - - if (mutedBlogEntry) { - const [uuid, [name, mode]] = mutedBlogEntry; - - if (mode === 'all') { - timelineElement.dataset.muteExcludedUuid = uuid; - - if (!dismissedWarningUuids.has(uuid)) { - const warningElement = dom('div', { class: warningClass }, null, [ - `You have muted all posts from ${name}!`, - dom('br'), - dom('button', null, { - click: () => { - dismissedWarningUuids.add(uuid); - warningElement.remove(); - } - }, 'show posts anyway') - ]); - timelineElement.before(warningElement); - } - } + if (timeline.startsWith('/v2/blog/')) { + processBlogSpecificTimeline(timelineElement, timeline); } } }); From f3c49560302a94f039e66499c77ae4ddde6a4939 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 17 Aug 2022 18:05:15 -0700 Subject: [PATCH 09/76] Fix options display with no muted blogs --- src/scripts/mute/options/index.css | 5 ++++- src/scripts/mute/options/index.html | 1 + src/scripts/mute/options/index.js | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/scripts/mute/options/index.css b/src/scripts/mute/options/index.css index cbb920cb1f..12e2640724 100644 --- a/src/scripts/mute/options/index.css +++ b/src/scripts/mute/options/index.css @@ -40,10 +40,13 @@ header { #muted-blogs { padding: 0; - border-bottom: 1px solid rgb(var(--grey)); margin: 0; } +#muted-blogs:not(:empty) { + border-bottom: 1px solid rgb(var(--grey)); +} + .muted-blog { display: flex; flex-direction: row; diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html index 59e00c6e86..13c04d82ca 100644 --- a/src/scripts/mute/options/index.html +++ b/src/scripts/mute/options/index.html @@ -27,6 +27,7 @@
Your muted blogs:
+
No muted blogs! Use the meatballs menu on a post to mute a blog.
    diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js index e362d027da..850e4da749 100644 --- a/src/scripts/mute/options/index.js +++ b/src/scripts/mute/options/index.js @@ -1,4 +1,5 @@ const mutedBlogList = document.getElementById('muted-blogs'); +const noMutedBlogText = document.getElementById('no-muted-blogs'); const mutedBlogTemplate = document.getElementById('muted-blog'); const storageKey = 'mute.mutedblogs'; @@ -24,6 +25,7 @@ const renderMutedBlogs = async function () { const { [storageKey]: mutedblogs = [] } = await browser.storage.local.get(storageKey); mutedBlogList.textContent = ''; + noMutedBlogText.style.display = Object.entries(mutedBlogs).length ? 'none' : 'block'; for (const [uuid, [name, mode]] of Object.entries(mutedblogs)) { const templateClone = mutedBlogTemplate.content.cloneNode(true); From 6cae4f2984f57d052bc74b4d0347c61b78073081 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 17 Aug 2022 18:06:07 -0700 Subject: [PATCH 10/76] Revamp storage keys --- src/scripts/mute.js | 104 ++++++++++++++++++------------ src/scripts/mute/options/index.js | 42 ++++++++---- 2 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 0b64dc164f..dc68ac41d3 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -14,9 +14,12 @@ const activeClass = 'xkit-mute-active'; const warningClass = 'xkit-mute-warning'; const lengthenedClass = 'xkit-mute-lengthened'; -const storageKey = 'mute.mutedblogs'; +const namesStorageKey = 'mute.names'; +const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; +let names = {}; let mutedBlogs = {}; + const dismissedWarningUuids = new Set(); const lengthenTimeline = timeline => { @@ -25,44 +28,34 @@ const lengthenTimeline = timeline => { } }; -const getCurrentUuid = async (timelineElement, timeline) => { +const getNameAndUuid = async (timelineElement, timeline) => { const uuidOrName = timeline.split('/')?.[3]; - - if (uuidOrName.startsWith('t:')) return uuidOrName; - const posts = [...timelineElement.querySelectorAll(postSelector)]; for (const post of posts) { const { blog: { name, uuid } } = await timelineObject(post); - if (name === uuidOrName || uuid === uuidOrName) return uuid; + if ([name, uuid].includes(uuidOrName)) return [name, uuid]; } - - throw new Error(`could not determine UUID of timeline ${timeline}`); + throw new Error(`could not determine blog name / UUID for timeline with ${timeline}`); }; const processBlogSpecificTimeline = async (timelineElement, timeline) => { - const currentUuid = await getCurrentUuid(timelineElement, timeline); - - const mutedBlogEntry = Object.entries(mutedBlogs).find(([uuid]) => currentUuid === uuid); - - if (mutedBlogEntry) { - const [uuid, [name, mode]] = mutedBlogEntry; - - if (mode === 'all') { - timelineElement.dataset.muteExcludedUuid = uuid; - - if (!dismissedWarningUuids.has(uuid)) { - const warningElement = dom('div', { class: warningClass }, null, [ - `You have muted all posts from ${name}!`, - dom('br'), - dom('button', null, { - click: () => { - dismissedWarningUuids.add(uuid); - warningElement.remove(); - } - }, 'show posts anyway') - ]); - timelineElement.before(warningElement); - } + const [name, uuid] = await getNameAndUuid(timelineElement, timeline); + + if (mutedBlogs[uuid] === 'all') { + timelineElement.dataset.muteExcludedUuid = uuid; + + if (!dismissedWarningUuids.has(uuid)) { + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted all posts from ${name}!`, + dom('br'), + dom('button', null, { + click: () => { + dismissedWarningUuids.add(uuid); + warningElement.remove(); + } + }, 'show posts anyway') + ]); + timelineElement.before(warningElement); } } }; @@ -92,13 +85,27 @@ const processTimelines = () => { }); }; +const updateNames = () => { + Object.keys(names).forEach(uuid => { + if (!mutedBlogs[uuid]) { + delete names[uuid]; + } + }); + browser.storage.local.set({ [namesStorageKey]: names }); +}; + const processPosts = function (postElements) { processTimelines(); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - const { blog: { uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); + const { blog: { name, uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); const { muteExcludedUuid } = postElement.closest('[data-timeline]').dataset; + if (mutedBlogs[uuid] && names[uuid] !== name) { + names[uuid] = name; + updateNames(); + } + // should there be a setting such that reblogs with contributed content count? // eslint-disable-next-line no-unused-vars const contributedContent = content.length > 0; @@ -107,10 +114,10 @@ const processPosts = function (postElements) { const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid; const reblogUuid = isRebloggedPost ? uuid : null; - if (originalUuid !== muteExcludedUuid && ['all', 'original'].includes(mutedBlogs[originalUuid]?.[1])) { + if (originalUuid !== muteExcludedUuid && ['all', 'original'].includes(mutedBlogs[originalUuid])) { postElement.classList.add(hiddenClass); } - if (reblogUuid !== muteExcludedUuid && ['all', 'reblogged'].includes(mutedBlogs[reblogUuid]?.[1])) { + if (reblogUuid !== muteExcludedUuid && ['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { postElement.classList.add(hiddenClass); } }); @@ -119,7 +126,7 @@ const processPosts = function (postElements) { const onMeatballButtonClicked = function ({ currentTarget }) { const { blog: { name, uuid } } = currentTarget.__timelineObjectData; - const currentMode = mutedBlogs[uuid]?.[1]; + const currentMode = mutedBlogs[uuid]; const createRadioElement = value => dom('label', null, null, [ @@ -153,24 +160,35 @@ const muteUser = event => { const { value } = event.currentTarget.elements.muteOption; if (value === '') return; - mutedBlogs[uuid] = [name, value]; - browser.storage.local.set({ [storageKey]: mutedBlogs }); + mutedBlogs[uuid] = value; + names[uuid] = name; + + browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); + browser.storage.local.set({ [namesStorageKey]: names }); hideModal(); }; const unmuteUser = uuid => { delete mutedBlogs[uuid]; - browser.storage.local.set({ [storageKey]: mutedBlogs }); + browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); hideModal(); }; export const onStorageChanged = async function (changes, areaName) { - const { [storageKey]: mutedBlogsChanges } = changes; + const { + [namesStorageKey]: namesChanges, + [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges + } = changes; + + if (namesChanges) { + ({ newValue: names } = namesChanges); + } - if (mutedBlogsChanges) { - ({ newValue: mutedBlogs } = mutedBlogsChanges); + if (mutedBlogsEntriesChanges) { + const { newValue: mutedBlogsEntries } = mutedBlogsEntriesChanges; + mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); $(`.${hiddenClass}`).removeClass(hiddenClass); $(`.${warningClass}`).remove(); @@ -181,7 +199,9 @@ export const onStorageChanged = async function (changes, areaName) { }; export const main = async function () { - ({ [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey)); + ({ [namesStorageKey]: names = {} } = await browser.storage.local.get(namesStorageKey)); + const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); + mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); registerMeatballItem({ id: meatballButtonId, diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js index 850e4da749..a9d1018ae3 100644 --- a/src/scripts/mute/options/index.js +++ b/src/scripts/mute/options/index.js @@ -2,32 +2,47 @@ const mutedBlogList = document.getElementById('muted-blogs'); const noMutedBlogText = document.getElementById('no-muted-blogs'); const mutedBlogTemplate = document.getElementById('muted-blog'); -const storageKey = 'mute.mutedblogs'; +const namesStorageKey = 'mute.names'; +const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; + +const getNames = async () => { + const { [namesStorageKey]: names = {} } = await browser.storage.local.get(namesStorageKey); + return names; +}; +const getMutedBlogs = async () => { + const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); + return Object.fromEntries(mutedBlogsEntries ?? []); +}; +const setMutedBlogs = mutedBlogs => + browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); const unmuteUser = async function ({ currentTarget }) { - const { [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey); + const mutedBlogs = await getMutedBlogs(); + const { uuid } = currentTarget.closest('li').dataset; delete mutedBlogs[uuid]; - browser.storage.local.set({ [storageKey]: mutedBlogs }); + setMutedBlogs(mutedBlogs); }; const updateMode = async function (event) { - const { [storageKey]: mutedBlogs = {} } = await browser.storage.local.get(storageKey); - const { uuid, name } = event.target.closest('li').dataset; + const mutedBlogs = await getMutedBlogs(); + + const { uuid } = event.target.closest('li').dataset; const { value } = event.target; - mutedBlogs[uuid] = [name, value]; - browser.storage.local.set({ [storageKey]: mutedBlogs }); + mutedBlogs[uuid] = value; + setMutedBlogs(mutedBlogs); }; const renderMutedBlogs = async function () { - const { [storageKey]: mutedblogs = [] } = await browser.storage.local.get(storageKey); + const mutedBlogs = await getMutedBlogs(); + const names = await getNames(); mutedBlogList.textContent = ''; noMutedBlogText.style.display = Object.entries(mutedBlogs).length ? 'none' : 'block'; - for (const [uuid, [name, mode]] of Object.entries(mutedblogs)) { + for (const [uuid, mode] of Object.entries(mutedBlogs)) { const templateClone = mutedBlogTemplate.content.cloneNode(true); const li = templateClone.querySelector('li'); const linkElement = templateClone.querySelector('a'); @@ -35,9 +50,8 @@ const renderMutedBlogs = async function () { const unmuteButton = templateClone.querySelector('button'); li.dataset.uuid = uuid; - li.dataset.name = name; - linkElement.textContent = name; + linkElement.textContent = names[uuid] ?? uuid; linkElement.href = `https://www.tumblr.com/blog/view/${uuid}`; modeSelect.value = mode; @@ -50,7 +64,11 @@ const renderMutedBlogs = async function () { }; browser.storage.onChanged.addListener((changes, areaName) => { - if (areaName === 'local' && Object.keys(changes).includes(storageKey)) { + if ( + areaName === 'local' && + (Object.keys(changes).includes(mutedBlogsEntriesStorageKey) || + Object.keys(changes).includes(namesStorageKey)) + ) { renderMutedBlogs(); } }); From 45c8a33ed8c05229d74af4eacabd9bdd77b17193 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 17 Aug 2022 22:53:07 -0700 Subject: [PATCH 11/76] Make styling slightly less terrible --- src/scripts/mute.css | 15 +++++++++++---- src/scripts/mute/options/index.css | 14 ++++++++------ src/scripts/mute/options/index.html | 8 ++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index abce595eb0..c2b068cb44 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -8,12 +8,19 @@ } .xkit-mute-warning { - text-align: center; - background-color: var(--blog-link-color-15); - color: var(--blog-link-color); padding: 25px 20px; - font-weight: 700; border-radius: 3px; + + background-color: var(--blog-title-color-15); + color: var(--blog-title-color); + + font-weight: 700; + text-align: center; + line-height: 1.5em; +} + +.xkit-mute-warning button { + color: var(--blog-link-color); } .xkit-mute-warning ~ * { diff --git a/src/scripts/mute/options/index.css b/src/scripts/mute/options/index.css index 12e2640724..0be5d631e0 100644 --- a/src/scripts/mute/options/index.css +++ b/src/scripts/mute/options/index.css @@ -31,11 +31,8 @@ body { } header { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; margin-bottom: 1em; + font-weight: bold; } #muted-blogs { @@ -48,12 +45,17 @@ header { } .muted-blog { + padding: 1ch 0; + border-top: 1px solid rgb(var(--grey)); + display: flex; flex-direction: row; justify-content: space-between; align-items: center; - padding: 1ch 0; - border-top: 1px solid rgb(var(--grey)); +} + +.muted-blog > a { + flex: 1; } .muted-blog button { diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html index 13c04d82ca..f5e2045530 100644 --- a/src/scripts/mute/options/index.html +++ b/src/scripts/mute/options/index.html @@ -16,7 +16,7 @@
  • - @@ -27,7 +27,11 @@
    Your muted blogs:
    -
    No muted blogs! Use the meatballs menu on a post to mute a blog.
    +
    + + No muted blogs! Use the meatballs menu on any post to mute the blog that posted it. + +
      From 0d92caf7c5871017e3f15b13d1caebe3f4e499b9 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 17 Aug 2022 23:00:14 -0700 Subject: [PATCH 12/76] Fix async race condition ...yikes. well, this is why I had that weird CSS way before --- src/scripts/mute.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index dc68ac41d3..d51180325e 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -60,8 +60,8 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { } }; -const processTimelines = () => { - [...document.querySelectorAll('[data-timeline]')].forEach(timelineElement => { +const processTimelines = async () => { + for (const timelineElement of [...document.querySelectorAll('[data-timeline]')]) { const { timeline, muteProcessedTimeline } = timelineElement.dataset; const alreadyProcessed = timeline === muteProcessedTimeline; @@ -79,10 +79,10 @@ const processTimelines = () => { delete timelineElement.dataset.muteExcludedUuid; if (timeline.startsWith('/v2/blog/')) { - processBlogSpecificTimeline(timelineElement, timeline); + await processBlogSpecificTimeline(timelineElement, timeline); } } - }); + } }; const updateNames = () => { @@ -94,8 +94,8 @@ const updateNames = () => { browser.storage.local.set({ [namesStorageKey]: names }); }; -const processPosts = function (postElements) { - processTimelines(); +const processPosts = async function (postElements) { + await processTimelines(); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const { blog: { name, uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); From 79de1382b7f2930183c0d99b1dc9e9945ef8d787 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 11 Dec 2022 22:42:52 -0800 Subject: [PATCH 13/76] exclude single post blog view --- src/scripts/mute.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index d51180325e..8507783ca2 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -66,8 +66,9 @@ const processTimelines = async () => { const alreadyProcessed = timeline === muteProcessedTimeline; const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); + const isSinglePostBlogView = timeline.includes('permalink'); - if (!alreadyProcessed && !isChannel) { + if (!alreadyProcessed && !isChannel && !isSinglePostBlogView) { timelineElement.dataset.muteProcessedTimeline = timeline; timelineElement.classList.add(activeClass); From 991f4f8e5041caaf992e66847cf6fecd488bc4f8 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 11 Dec 2022 23:51:01 -0800 Subject: [PATCH 14/76] rewrite on-blog warnings (works on all modes now) also fixes crash when timelines load without posts yet --- src/scripts/mute.css | 19 ++++++------- src/scripts/mute.js | 64 +++++++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index c2b068cb44..395b783724 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,13 +1,22 @@ -.xkit-mute-active .xkit-mute-hidden article { +.xkit-mute-active .xkit-mute-hidden article, +:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ .xkit-mute-hidden-on-blog article { /* display: none; */ opacity: 0.5; } +[data-mute-mode="all"] ~ .xkit-mute-hidden-on-blog article { + visibility: hidden !important; +} +[data-mute-mode="all"] ~ .xkit-mute-hidden-on-blog article :is(img, video, canvas) { + display: none; +} + .xkit-mute-lengthened { min-height: 100vh; } .xkit-mute-warning { + margin-bottom: 20px; padding: 25px 20px; border-radius: 3px; @@ -22,11 +31,3 @@ .xkit-mute-warning button { color: var(--blog-link-color); } - -.xkit-mute-warning ~ * { - visibility: hidden !important; -} - -.xkit-mute-warning ~ * :is(img, video, canvas) { - display: none; -} diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 8507783ca2..db4a511bf4 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -10,6 +10,7 @@ const meatballButtonId = 'mute'; const meatballButtonLabel = 'Mute options'; const hiddenClass = 'xkit-mute-hidden'; +const onBlogHiddenClass = 'xkit-mute-hidden-on-blog'; const activeClass = 'xkit-mute-active'; const warningClass = 'xkit-mute-warning'; const lengthenedClass = 'xkit-mute-lengthened'; @@ -40,28 +41,30 @@ const getNameAndUuid = async (timelineElement, timeline) => { const processBlogSpecificTimeline = async (timelineElement, timeline) => { const [name, uuid] = await getNameAndUuid(timelineElement, timeline); + const mode = mutedBlogs[uuid]; + + timelineElement.dataset.muteOnBlogUuid = uuid; + + if (mode && !dismissedWarningUuids.has(uuid)) { + const warningElement = dom('div', { class: warningClass }, null, [ + `You have muted ${mode} posts from ${name}!`, + dom('br'), + dom('button', null, { + click: () => { + dismissedWarningUuids.add(uuid); + warningElement.remove(); + } + }, 'show posts anyway') + ]); + warningElement.dataset.muteMode = mode; - if (mutedBlogs[uuid] === 'all') { - timelineElement.dataset.muteExcludedUuid = uuid; - - if (!dismissedWarningUuids.has(uuid)) { - const warningElement = dom('div', { class: warningClass }, null, [ - `You have muted all posts from ${name}!`, - dom('br'), - dom('button', null, { - click: () => { - dismissedWarningUuids.add(uuid); - warningElement.remove(); - } - }, 'show posts anyway') - ]); - timelineElement.before(warningElement); - } + const firstPost = timelineElement.querySelector(postSelector); + firstPost?.parentElement?.prepend(warningElement); } }; -const processTimelines = async () => { - for (const timelineElement of [...document.querySelectorAll('[data-timeline]')]) { +const processTimelines = async (timelineElements) => { + for (const timelineElement of [...new Set(timelineElements)]) { const { timeline, muteProcessedTimeline } = timelineElement.dataset; const alreadyProcessed = timeline === muteProcessedTimeline; @@ -77,7 +80,7 @@ const processTimelines = async () => { if (timelineElement.previousElementSibling?.classList?.contains(warningClass)) { timelineElement.previousElementSibling.remove(); } - delete timelineElement.dataset.muteExcludedUuid; + delete timelineElement.dataset.muteOnBlogUuid; if (timeline.startsWith('/v2/blog/')) { await processBlogSpecificTimeline(timelineElement, timeline); @@ -96,11 +99,11 @@ const updateNames = () => { }; const processPosts = async function (postElements) { - await processTimelines(); + await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const { blog: { name, uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); - const { muteExcludedUuid } = postElement.closest('[data-timeline]').dataset; + const { muteOnBlogUuid: onBlogUuid } = postElement.closest('[data-timeline]').dataset; if (mutedBlogs[uuid] && names[uuid] !== name) { names[uuid] = name; @@ -115,11 +118,11 @@ const processPosts = async function (postElements) { const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid; const reblogUuid = isRebloggedPost ? uuid : null; - if (originalUuid !== muteExcludedUuid && ['all', 'original'].includes(mutedBlogs[originalUuid])) { - postElement.classList.add(hiddenClass); + if (['all', 'original'].includes(mutedBlogs[originalUuid])) { + postElement.classList.add(originalUuid === onBlogUuid ? onBlogHiddenClass : hiddenClass); } - if (reblogUuid !== muteExcludedUuid && ['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { - postElement.classList.add(hiddenClass); + if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { + postElement.classList.add(reblogUuid === onBlogUuid ? onBlogHiddenClass : hiddenClass); } }); }; @@ -192,9 +195,14 @@ export const onStorageChanged = async function (changes, areaName) { mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); $(`.${hiddenClass}`).removeClass(hiddenClass); + $(`.${onBlogHiddenClass}`).removeClass(onBlogHiddenClass); + $(`.${activeClass}`).removeClass(activeClass); + $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${warningClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); - $('[data-mute-excluded-uuid]').removeAttr('data-mute-excluded-uuid'); + $('[data-mute-on-blog-uuid]').removeAttr('data-mute-blog-specific-uuid'); + dismissedWarningUuids.clear(); + pageModifications.trigger(processPosts); } }; @@ -217,12 +225,12 @@ export const clean = async function () { onNewPosts.removeListener(processPosts); $(`.${hiddenClass}`).removeClass(hiddenClass); + $(`.${onBlogHiddenClass}`).removeClass(onBlogHiddenClass); $(`.${activeClass}`).removeClass(activeClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${warningClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); - $('[data-mute-excluded-uuid]').removeAttr('data-mute-excluded-uuid'); - + $('[data-mute-on-blog-uuid]').removeAttr('data-mute-blog-specific-uuid'); dismissedWarningUuids.clear(); }; From f545fa43c30e4803016857e7f55289fd12220272 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 11 Dec 2022 23:53:05 -0800 Subject: [PATCH 15/76] simplify onstoragechanged --- src/scripts/mute.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index db4a511bf4..f30eb1ee99 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -2,7 +2,7 @@ import { filterPostElements, blogViewSelector, postSelector } from '../util/inte import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; -import { onNewPosts, pageModifications } from '../util/mutations.js'; +import { onNewPosts } from '../util/mutations.js'; import { keyToCss } from '../util/css_map.js'; import { dom } from '../util/dom.js'; @@ -186,24 +186,13 @@ export const onStorageChanged = async function (changes, areaName) { [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges } = changes; - if (namesChanges) { - ({ newValue: names } = namesChanges); + if (mutedBlogsEntriesChanges) { + clean().then(main); + return; } - if (mutedBlogsEntriesChanges) { - const { newValue: mutedBlogsEntries } = mutedBlogsEntriesChanges; - mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); - - $(`.${hiddenClass}`).removeClass(hiddenClass); - $(`.${onBlogHiddenClass}`).removeClass(onBlogHiddenClass); - $(`.${activeClass}`).removeClass(activeClass); - $(`.${lengthenedClass}`).removeClass(lengthenedClass); - $(`.${warningClass}`).remove(); - $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); - $('[data-mute-on-blog-uuid]').removeAttr('data-mute-blog-specific-uuid'); - dismissedWarningUuids.clear(); - - pageModifications.trigger(processPosts); + if (namesChanges) { + ({ newValue: names } = namesChanges); } }; From 9bef00b9f6991f155c97dd55ad27a51061b3888c Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 11 Dec 2022 23:55:37 -0800 Subject: [PATCH 16/76] remove persistent unmuting I could go either way on this --- src/scripts/mute.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index f30eb1ee99..0b118f1670 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -21,8 +21,6 @@ const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; let names = {}; let mutedBlogs = {}; -const dismissedWarningUuids = new Set(); - const lengthenTimeline = timeline => { if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) { timeline.classList.add(lengthenedClass); @@ -45,16 +43,11 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { timelineElement.dataset.muteOnBlogUuid = uuid; - if (mode && !dismissedWarningUuids.has(uuid)) { + if (mode) { const warningElement = dom('div', { class: warningClass }, null, [ `You have muted ${mode} posts from ${name}!`, dom('br'), - dom('button', null, { - click: () => { - dismissedWarningUuids.add(uuid); - warningElement.remove(); - } - }, 'show posts anyway') + dom('button', null, { click: () => warningElement.remove() }, 'show posts anyway') ]); warningElement.dataset.muteMode = mode; @@ -220,7 +213,6 @@ export const clean = async function () { $(`.${warningClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); $('[data-mute-on-blog-uuid]').removeAttr('data-mute-blog-specific-uuid'); - dismissedWarningUuids.clear(); }; export const stylesheet = true; From 2ff9c3189c9466a5eb006bfd52a695a0afc23462 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 12 Dec 2022 00:12:27 -0800 Subject: [PATCH 17/76] remove test/temporary code --- src/scripts/mute.css | 3 +-- src/scripts/mute.js | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index 395b783724..f3fa31dabf 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,7 +1,6 @@ .xkit-mute-active .xkit-mute-hidden article, :is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ .xkit-mute-hidden-on-blog article { - /* display: none; */ - opacity: 0.5; + display: none; } [data-mute-mode="all"] ~ .xkit-mute-hidden-on-blog article { diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 0b118f1670..8bd36c78f7 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -95,7 +95,7 @@ const processPosts = async function (postElements) { await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - const { blog: { name, uuid }, rebloggedRootUuid, content } = await timelineObject(postElement); + const { blog: { name, uuid }, rebloggedRootUuid } = await timelineObject(postElement); const { muteOnBlogUuid: onBlogUuid } = postElement.closest('[data-timeline]').dataset; if (mutedBlogs[uuid] && names[uuid] !== name) { @@ -103,9 +103,6 @@ const processPosts = async function (postElements) { updateNames(); } - // should there be a setting such that reblogs with contributed content count? - // eslint-disable-next-line no-unused-vars - const contributedContent = content.length > 0; const isRebloggedPost = Boolean(rebloggedRootUuid); const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid; From 50a30981ce00294ab9f4db453ede141b06485f13 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 12 Dec 2022 00:34:12 -0800 Subject: [PATCH 18/76] optionally check the reblog trail --- src/scripts/mute.js | 23 ++++++++++++++++++++--- src/scripts/mute.json | 5 +++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 8bd36c78f7..40e9fed994 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -5,6 +5,7 @@ import { timelineObject } from '../util/react_props.js'; import { onNewPosts } from '../util/mutations.js'; import { keyToCss } from '../util/css_map.js'; import { dom } from '../util/dom.js'; +import { getPreferences } from '../util/preferences.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = 'Mute options'; @@ -18,6 +19,8 @@ const lengthenedClass = 'xkit-mute-lengthened'; const namesStorageKey = 'mute.names'; const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; +let checkTrail; + let names = {}; let mutedBlogs = {}; @@ -95,10 +98,10 @@ const processPosts = async function (postElements) { await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - const { blog: { name, uuid }, rebloggedRootUuid } = await timelineObject(postElement); + const { blog: { name, uuid }, rebloggedRootUuid, trail = [] } = await timelineObject(postElement); const { muteOnBlogUuid: onBlogUuid } = postElement.closest('[data-timeline]').dataset; - if (mutedBlogs[uuid] && names[uuid] !== name) { + if (trail[uuid] && names[uuid] !== name) { names[uuid] = name; updateNames(); } @@ -114,6 +117,16 @@ const processPosts = async function (postElements) { if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { postElement.classList.add(reblogUuid === onBlogUuid ? onBlogHiddenClass : hiddenClass); } + + if (checkTrail) { + for (const { blog } of trail) { + if (['all'].includes(mutedBlogs[blog?.uuid])) { + postElement.classList.add( + blog?.uuid === onBlogUuid ? onBlogHiddenClass : hiddenClass + ); + } + } + } }); }; @@ -176,7 +189,10 @@ export const onStorageChanged = async function (changes, areaName) { [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges } = changes; - if (mutedBlogsEntriesChanges) { + if ( + Object.keys(changes).some(key => key.startsWith('mute') && changes[key].oldValue !== undefined) || + mutedBlogsEntriesChanges + ) { clean().then(main); return; } @@ -187,6 +203,7 @@ export const onStorageChanged = async function (changes, areaName) { }; export const main = async function () { + ({ checkTrail } = await getPreferences('mute')); ({ [namesStorageKey]: names = {} } = await browser.storage.local.get(namesStorageKey)); const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); diff --git a/src/scripts/mute.json b/src/scripts/mute.json index 1bccf2c6fe..5c0f18d694 100644 --- a/src/scripts/mute.json +++ b/src/scripts/mute.json @@ -11,6 +11,11 @@ "type": "iframe", "label": "Manage muted users", "src": "/scripts/mute/options/index.html" + }, + "checkTrail": { + "type": "checkbox", + "label": "Hide posts with a muted user anywhere in the trail", + "default": false } } } From 9bbabc5689f05b0458732a7987a4ab90166bcd76 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 12 Dec 2022 00:36:51 -0800 Subject: [PATCH 19/76] uhhh oops --- src/scripts/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 40e9fed994..f88ce9e881 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -101,7 +101,7 @@ const processPosts = async function (postElements) { const { blog: { name, uuid }, rebloggedRootUuid, trail = [] } = await timelineObject(postElement); const { muteOnBlogUuid: onBlogUuid } = postElement.closest('[data-timeline]').dataset; - if (trail[uuid] && names[uuid] !== name) { + if (mutedBlogs[uuid] && names[uuid] !== name) { names[uuid] = name; updateNames(); } From b85bb2e26134c30d361804ade6517ebfda670315 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 13 Dec 2022 15:14:13 -0800 Subject: [PATCH 20/76] fix random ads appearing when all posts hidden --- src/scripts/mute.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index f3fa31dabf..22dcf30eb9 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -3,10 +3,10 @@ display: none; } -[data-mute-mode="all"] ~ .xkit-mute-hidden-on-blog article { +[data-mute-mode="all"] ~ div article { visibility: hidden !important; } -[data-mute-mode="all"] ~ .xkit-mute-hidden-on-blog article :is(img, video, canvas) { +[data-mute-mode="all"] ~ div article :is(img, video, canvas) { display: none; } From ea05c44aaf685f758f086d82dd0189092ea3bc37 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 13 Dec 2022 16:19:05 -0800 Subject: [PATCH 21/76] clarify meatballs menu --- src/scripts/mute/options/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html index f5e2045530..458b1d8f7d 100644 --- a/src/scripts/mute/options/index.html +++ b/src/scripts/mute/options/index.html @@ -29,7 +29,7 @@
      Your muted blogs:
      - No muted blogs! Use the meatballs menu on any post to mute the blog that posted it. + No muted blogs! Use the ⋯ meatballs menu on any post to mute the blog that posted it.
        From 83e649f1c3c39c682c3059815a90a3d28cf7406f Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 13 Dec 2022 16:23:54 -0800 Subject: [PATCH 22/76] description that I dislike but it's something --- src/scripts/mute.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute.json b/src/scripts/mute.json index 5c0f18d694..2a48466a3a 100644 --- a/src/scripts/mute.json +++ b/src/scripts/mute.json @@ -1,6 +1,6 @@ { "title": "Mute", - "description": "", + "description": "Hide users' posts all over Tumblr", "icon": { "class_name": "ri-volume-mute-line", "color": "white", From 467eee68a75c1f5f981d70d406525f3b8ecff2b0 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 15 Feb 2023 19:08:53 -0800 Subject: [PATCH 23/76] fix warning element removal --- src/scripts/mute.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index f88ce9e881..f797fab036 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -73,9 +73,7 @@ const processTimelines = async (timelineElements) => { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - if (timelineElement.previousElementSibling?.classList?.contains(warningClass)) { - timelineElement.previousElementSibling.remove(); - } + [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); delete timelineElement.dataset.muteOnBlogUuid; if (timeline.startsWith('/v2/blog/')) { From 3530c9bf17c6988054ffa4c4d78c8bc1ae04ee8a Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 15 Feb 2023 20:34:41 -0800 Subject: [PATCH 24/76] UI clarity tweaks; refactors --- src/scripts/mute.js | 65 ++++++++++++++++------------- src/scripts/mute.json | 1 + src/scripts/mute/options/index.html | 4 +- src/scripts/mute/options/index.js | 14 +++---- 4 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index f797fab036..3055e25fb8 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -8,7 +8,7 @@ import { dom } from '../util/dom.js'; import { getPreferences } from '../util/preferences.js'; const meatballButtonId = 'mute'; -const meatballButtonLabel = 'Mute options'; +const meatballButtonLabel = 'User mute options'; const hiddenClass = 'xkit-mute-hidden'; const onBlogHiddenClass = 'xkit-mute-hidden-on-blog'; @@ -16,12 +16,12 @@ const activeClass = 'xkit-mute-active'; const warningClass = 'xkit-mute-warning'; const lengthenedClass = 'xkit-mute-lengthened'; -const namesStorageKey = 'mute.names'; +const blogNamesStorageKey = 'mute.blogNames'; const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; let checkTrail; -let names = {}; +let blogNames = {}; let mutedBlogs = {}; const lengthenTimeline = timeline => { @@ -83,25 +83,25 @@ const processTimelines = async (timelineElements) => { } }; -const updateNames = () => { - Object.keys(names).forEach(uuid => { +const updateStoredName = (uuid, name) => { + blogNames[uuid] = name; + Object.keys(blogNames).forEach(uuid => { if (!mutedBlogs[uuid]) { - delete names[uuid]; + delete blogNames[uuid]; } }); - browser.storage.local.set({ [namesStorageKey]: names }); + browser.storage.local.set({ [blogNamesStorageKey]: blogNames }); }; const processPosts = async function (postElements) { await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - const { blog: { name, uuid }, rebloggedRootUuid, trail = [] } = await timelineObject(postElement); - const { muteOnBlogUuid: onBlogUuid } = postElement.closest('[data-timeline]').dataset; + const { blog: { uuid, name }, rebloggedRootUuid, trail = [] } = await timelineObject(postElement); + const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; - if (mutedBlogs[uuid] && names[uuid] !== name) { - names[uuid] = name; - updateNames(); + if (mutedBlogs[uuid] && blogNames[uuid] !== name) { + updateStoredName(uuid, name); } const isRebloggedPost = Boolean(rebloggedRootUuid); @@ -110,17 +110,17 @@ const processPosts = async function (postElements) { const reblogUuid = isRebloggedPost ? uuid : null; if (['all', 'original'].includes(mutedBlogs[originalUuid])) { - postElement.classList.add(originalUuid === onBlogUuid ? onBlogHiddenClass : hiddenClass); + postElement.classList.add(originalUuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass); } if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { - postElement.classList.add(reblogUuid === onBlogUuid ? onBlogHiddenClass : hiddenClass); + postElement.classList.add(reblogUuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass); } if (checkTrail) { for (const { blog } of trail) { if (['all'].includes(mutedBlogs[blog?.uuid])) { postElement.classList.add( - blog?.uuid === onBlogUuid ? onBlogHiddenClass : hiddenClass + blog?.uuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass ); } } @@ -135,8 +135,8 @@ const onMeatballButtonClicked = function ({ currentTarget }) { const createRadioElement = value => dom('label', null, null, [ - dom('input', { type: 'radio', name: 'muteOption', value }), - `Hide ${value} posts` + `Hide ${value} posts`, + dom('input', { type: 'radio', name: 'muteOption', value }) ]); const form = dom('form', { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid }, { submit: muteUser }, [ @@ -148,13 +148,18 @@ const onMeatballButtonClicked = function ({ currentTarget }) { form.elements.muteOption.value = currentMode; showModal({ - title: `Mute ${name}`, + title: currentMode ? `Mute options for ${name}:` : `Mute ${name}?`, message: [form], - buttons: [ - modalCancelButton, - ...currentMode ? [dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute'])] : [], - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) - ] + buttons: currentMode + ? [ + modalCancelButton, + dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) + ] + : [ + modalCancelButton, + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) + ] }); }; @@ -166,10 +171,10 @@ const muteUser = event => { if (value === '') return; mutedBlogs[uuid] = value; - names[uuid] = name; + blogNames[uuid] = name; browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); - browser.storage.local.set({ [namesStorageKey]: names }); + browser.storage.local.set({ [blogNamesStorageKey]: blogNames }); hideModal(); }; @@ -183,7 +188,7 @@ const unmuteUser = uuid => { export const onStorageChanged = async function (changes, areaName) { const { - [namesStorageKey]: namesChanges, + [blogNamesStorageKey]: blogNamesChanges, [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges } = changes; @@ -195,14 +200,14 @@ export const onStorageChanged = async function (changes, areaName) { return; } - if (namesChanges) { - ({ newValue: names } = namesChanges); + if (blogNamesChanges) { + ({ newValue: blogNames } = blogNamesChanges); } }; export const main = async function () { ({ checkTrail } = await getPreferences('mute')); - ({ [namesStorageKey]: names = {} } = await browser.storage.local.get(namesStorageKey)); + ({ [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey)); const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); @@ -224,7 +229,7 @@ export const clean = async function () { $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${warningClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); - $('[data-mute-on-blog-uuid]').removeAttr('data-mute-blog-specific-uuid'); + $('[data-mute-on-blog-uuid]').removeAttr('data-mute-on-blog-uuid'); }; export const stylesheet = true; diff --git a/src/scripts/mute.json b/src/scripts/mute.json index 2a48466a3a..39362df7a3 100644 --- a/src/scripts/mute.json +++ b/src/scripts/mute.json @@ -6,6 +6,7 @@ "color": "white", "background_color": "#C20044" }, + "relatedTerms": [ "Blacklist", "Filter", "Savior" ], "preferences": { "manageBlockedPosts": { "type": "iframe", diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html index 458b1d8f7d..2114e7b1bf 100644 --- a/src/scripts/mute/options/index.html +++ b/src/scripts/mute/options/index.html @@ -26,10 +26,10 @@
      • -
        Your muted blogs:
        +
        Muted blogs:
        - No muted blogs! Use the ⋯ meatballs menu on any post to mute the blog that posted it. + No muted blogs! Use the meatballs menu on any post to mute the blog that posted it.
          diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js index a9d1018ae3..1b0ff498da 100644 --- a/src/scripts/mute/options/index.js +++ b/src/scripts/mute/options/index.js @@ -2,12 +2,12 @@ const mutedBlogList = document.getElementById('muted-blogs'); const noMutedBlogText = document.getElementById('no-muted-blogs'); const mutedBlogTemplate = document.getElementById('muted-blog'); -const namesStorageKey = 'mute.names'; +const blogNamesStorageKey = 'mute.blogNames'; const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; -const getNames = async () => { - const { [namesStorageKey]: names = {} } = await browser.storage.local.get(namesStorageKey); - return names; +const getBlogNames = async () => { + const { [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey); + return blogNames; }; const getMutedBlogs = async () => { const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); @@ -37,7 +37,7 @@ const updateMode = async function (event) { const renderMutedBlogs = async function () { const mutedBlogs = await getMutedBlogs(); - const names = await getNames(); + const blogNames = await getBlogNames(); mutedBlogList.textContent = ''; noMutedBlogText.style.display = Object.entries(mutedBlogs).length ? 'none' : 'block'; @@ -51,7 +51,7 @@ const renderMutedBlogs = async function () { li.dataset.uuid = uuid; - linkElement.textContent = names[uuid] ?? uuid; + linkElement.textContent = blogNames[uuid] ?? uuid; linkElement.href = `https://www.tumblr.com/blog/view/${uuid}`; modeSelect.value = mode; @@ -67,7 +67,7 @@ browser.storage.onChanged.addListener((changes, areaName) => { if ( areaName === 'local' && (Object.keys(changes).includes(mutedBlogsEntriesStorageKey) || - Object.keys(changes).includes(namesStorageKey)) + Object.keys(changes).includes(blogNamesStorageKey)) ) { renderMutedBlogs(); } From a139f78e76dbae1c42388e7d0f9d679fe0db6edd Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 26 Feb 2023 09:43:44 -0800 Subject: [PATCH 25/76] handle contributed content --- src/scripts/mute.js | 14 +++++++++++--- src/scripts/mute.json | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 3055e25fb8..fe304eb11a 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -20,6 +20,7 @@ const blogNamesStorageKey = 'mute.blogNames'; const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; let checkTrail; +let contributedContentOriginal; let blogNames = {}; let mutedBlogs = {}; @@ -97,14 +98,21 @@ const processPosts = async function (postElements) { await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { - const { blog: { uuid, name }, rebloggedRootUuid, trail = [] } = await timelineObject(postElement); + const { + blog: { uuid, name }, + rebloggedRootUuid, + content = [], + trail = [] + } = await timelineObject(postElement); const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { updateStoredName(uuid, name); } - const isRebloggedPost = Boolean(rebloggedRootUuid); + const isRebloggedPost = contributedContentOriginal + ? rebloggedRootUuid && !content.length + : rebloggedRootUuid; const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid; const reblogUuid = isRebloggedPost ? uuid : null; @@ -206,7 +214,7 @@ export const onStorageChanged = async function (changes, areaName) { }; export const main = async function () { - ({ checkTrail } = await getPreferences('mute')); + ({ checkTrail, contributedContentOriginal } = await getPreferences('mute')); ({ [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey)); const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); diff --git a/src/scripts/mute.json b/src/scripts/mute.json index 39362df7a3..3a6eae72fb 100644 --- a/src/scripts/mute.json +++ b/src/scripts/mute.json @@ -17,6 +17,11 @@ "type": "checkbox", "label": "Hide posts with a muted user anywhere in the trail", "default": false + }, + "contributedContentOriginal": { + "type": "checkbox", + "label": "Treat reblogs with contributed content as original", + "default": false } } } From 5e5ee8b2bb6860221e5b2432c27c0aff0afb9d6f Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 7 Mar 2023 11:54:38 -0800 Subject: [PATCH 26/76] improve warning css --- src/scripts/mute.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index 22dcf30eb9..b801a12ec9 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -15,9 +15,9 @@ } .xkit-mute-warning { - margin-bottom: 20px; padding: 25px 20px; border-radius: 3px; + margin-bottom: var(--post-padding); background-color: var(--blog-title-color-15); color: var(--blog-title-color); From 562f53a7076c4d8758060163db7778027f574c03 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 15 Mar 2023 16:43:16 -0700 Subject: [PATCH 27/76] fix blog view exclusion logic --- src/scripts/mute.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index fe304eb11a..ac7680b552 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -65,18 +65,21 @@ const processTimelines = async (timelineElements) => { const { timeline, muteProcessedTimeline } = timelineElement.dataset; const alreadyProcessed = timeline === muteProcessedTimeline; - const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); - const isSinglePostBlogView = timeline.includes('permalink'); + if (alreadyProcessed) return; + + timelineElement.dataset.muteProcessedTimeline = timeline; - if (!alreadyProcessed && !isChannel && !isSinglePostBlogView) { - timelineElement.dataset.muteProcessedTimeline = timeline; + [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); + delete timelineElement.dataset.muteOnBlogUuid; + const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); + const isSinglePostBlogView = timeline.endsWith('/permalink'); + const isLikes = timeline.endsWith('/likes'); + + if (!isChannel && !isSinglePostBlogView && !isLikes) { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); - delete timelineElement.dataset.muteOnBlogUuid; - if (timeline.startsWith('/v2/blog/')) { await processBlogSpecificTimeline(timelineElement, timeline); } From da4565effa11674f68d8e4a384b1b12f64ccde10 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 22 Mar 2023 20:05:35 -0700 Subject: [PATCH 28/76] customize meatballs menu label --- src/scripts/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index ac7680b552..3110b478d9 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -8,7 +8,7 @@ import { dom } from '../util/dom.js'; import { getPreferences } from '../util/preferences.js'; const meatballButtonId = 'mute'; -const meatballButtonLabel = 'User mute options'; +const meatballButtonLabel = ({ blogName }) => `Mute options for ${blogName}`; const hiddenClass = 'xkit-mute-hidden'; const onBlogHiddenClass = 'xkit-mute-hidden-on-blog'; From 432f8460a9a310733d7a682e7dc953794821eca9 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 30 Apr 2023 17:16:45 -0700 Subject: [PATCH 29/76] enable mute menu in blog meatball menus --- src/scripts/mute.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 3110b478d9..7456d8a562 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -1,5 +1,5 @@ import { filterPostElements, blogViewSelector, postSelector } from '../util/interface.js'; -import { registerMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; +import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; import { onNewPosts } from '../util/mutations.js'; @@ -8,7 +8,7 @@ import { dom } from '../util/dom.js'; import { getPreferences } from '../util/preferences.js'; const meatballButtonId = 'mute'; -const meatballButtonLabel = ({ blogName }) => `Mute options for ${blogName}`; +const meatballButtonLabel = ({ blogName, name }) => `Mute options for ${blogName ?? name}`; const hiddenClass = 'xkit-mute-hidden'; const onBlogHiddenClass = 'xkit-mute-hidden-on-blog'; @@ -140,7 +140,7 @@ const processPosts = async function (postElements) { }; const onMeatballButtonClicked = function ({ currentTarget }) { - const { blog: { name, uuid } } = currentTarget.__timelineObjectData; + const { name, uuid } = currentTarget?.__timelineObjectData?.blog || currentTarget?.__blogData; const currentMode = mutedBlogs[uuid]; @@ -227,11 +227,17 @@ export const main = async function () { label: meatballButtonLabel, onclick: onMeatballButtonClicked }); + registerBlogMeatballItem({ + id: meatballButtonId, + label: meatballButtonLabel, + onClick: onMeatballButtonClicked + }); onNewPosts.addListener(processPosts); }; export const clean = async function () { unregisterMeatballItem(meatballButtonId); + unregisterBlogMeatballItem(meatballButtonId); onNewPosts.removeListener(processPosts); $(`.${hiddenClass}`).removeClass(hiddenClass); From fb8e016853c881793dad03dea7cb682d964f1b30 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sat, 12 Aug 2023 05:46:32 -0700 Subject: [PATCH 30/76] combine storage set calls --- src/scripts/mute.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 7456d8a562..c433d4d9f9 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -184,8 +184,10 @@ const muteUser = event => { mutedBlogs[uuid] = value; blogNames[uuid] = name; - browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); - browser.storage.local.set({ [blogNamesStorageKey]: blogNames }); + browser.storage.local.set({ + [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs), + [blogNamesStorageKey]: blogNames + }); hideModal(); }; From 792d52d61dcc368e007d7ebfa042de4ca387e671 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 17 Aug 2023 16:08:05 -0700 Subject: [PATCH 31/76] Update mute.js --- src/scripts/mute.css | 4 ++-- src/scripts/mute.js | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index b801a12ec9..f631fb62a4 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,5 +1,5 @@ -.xkit-mute-active .xkit-mute-hidden article, -:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ .xkit-mute-hidden-on-blog article { +.xkit-mute-active [data-mute-hidden] article, +:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ [data-mute-hidden-on-blog] article { display: none; } diff --git a/src/scripts/mute.js b/src/scripts/mute.js index c433d4d9f9..798a433c6c 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -1,4 +1,4 @@ -import { filterPostElements, blogViewSelector, postSelector } from '../util/interface.js'; +import { filterPostElements, blogViewSelector, postSelector, getTimelineItemWrapper } from '../util/interface.js'; import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../util/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../util/modals.js'; import { timelineObject } from '../util/react_props.js'; @@ -10,8 +10,8 @@ import { getPreferences } from '../util/preferences.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = ({ blogName, name }) => `Mute options for ${blogName ?? name}`; -const hiddenClass = 'xkit-mute-hidden'; -const onBlogHiddenClass = 'xkit-mute-hidden-on-blog'; +const hiddenAttribute = 'data-mute-hidden'; +const onBlogHiddenAttribute = 'data-mute-hidden-on-blog'; const activeClass = 'xkit-mute-active'; const warningClass = 'xkit-mute-warning'; const lengthenedClass = 'xkit-mute-lengthened'; @@ -121,17 +121,24 @@ const processPosts = async function (postElements) { const reblogUuid = isRebloggedPost ? uuid : null; if (['all', 'original'].includes(mutedBlogs[originalUuid])) { - postElement.classList.add(originalUuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass); + getTimelineItemWrapper(postElement).setAttribute( + originalUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + '' + ); } if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { - postElement.classList.add(reblogUuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass); + getTimelineItemWrapper(postElement).setAttribute( + reblogUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + '' + ); } if (checkTrail) { for (const { blog } of trail) { if (['all'].includes(mutedBlogs[blog?.uuid])) { - postElement.classList.add( - blog?.uuid === currentBlogViewUuid ? onBlogHiddenClass : hiddenClass + getTimelineItemWrapper(postElement).setAttribute( + blog?.uuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + '' ); } } @@ -242,8 +249,8 @@ export const clean = async function () { unregisterBlogMeatballItem(meatballButtonId); onNewPosts.removeListener(processPosts); - $(`.${hiddenClass}`).removeClass(hiddenClass); - $(`.${onBlogHiddenClass}`).removeClass(onBlogHiddenClass); + $(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute); + $(`[${onBlogHiddenAttribute}]`).removeAttr(onBlogHiddenAttribute); $(`.${activeClass}`).removeClass(activeClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${warningClass}`).remove(); From 6f3d7e7986ae421d77b01b13a2f936d334a41b81 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 11 Feb 2024 11:43:26 -0800 Subject: [PATCH 32/76] fix mute dom child --- src/scripts/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 798a433c6c..6811deb841 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -51,7 +51,7 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { const warningElement = dom('div', { class: warningClass }, null, [ `You have muted ${mode} posts from ${name}!`, dom('br'), - dom('button', null, { click: () => warningElement.remove() }, 'show posts anyway') + dom('button', null, { click: () => warningElement.remove() }, ['show posts anyway']) ]); warningElement.dataset.muteMode = mode; From 0ccb074e8a9a50f0e5336fb15b4619fa22bf7bf1 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Mar 2024 16:38:28 -0700 Subject: [PATCH 33/76] fix warning element DOM position with virtual scroller --- src/scripts/mute.css | 2 +- src/scripts/mute.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/scripts/mute.css b/src/scripts/mute.css index f631fb62a4..2a26c3de4b 100644 --- a/src/scripts/mute.css +++ b/src/scripts/mute.css @@ -1,5 +1,5 @@ .xkit-mute-active [data-mute-hidden] article, -:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ [data-mute-hidden-on-blog] article { +:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ div [data-mute-hidden-on-blog] article { display: none; } diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 6811deb841..7503bfac45 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -55,8 +55,7 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { ]); warningElement.dataset.muteMode = mode; - const firstPost = timelineElement.querySelector(postSelector); - firstPost?.parentElement?.prepend(warningElement); + timelineElement.querySelector(keyToCss('scrollContainer')).before(warningElement); } }; From eeab1057f4f017b1a9883bcd90c0e9fdcb0a1fd3 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 29 Apr 2024 10:24:43 -0700 Subject: [PATCH 34/76] Use CSS for placeholder text state Co-Authored-By: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/scripts/mute/options/index.css | 4 ++++ src/scripts/mute/options/index.html | 2 +- src/scripts/mute/options/index.js | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/scripts/mute/options/index.css b/src/scripts/mute/options/index.css index 0be5d631e0..67fe507429 100644 --- a/src/scripts/mute/options/index.css +++ b/src/scripts/mute/options/index.css @@ -44,6 +44,10 @@ header { border-bottom: 1px solid rgb(var(--grey)); } +#muted-blogs:not(:empty) + #no-muted-blogs { + display: none; +} + .muted-blog { padding: 1ch 0; border-top: 1px solid rgb(var(--grey)); diff --git a/src/scripts/mute/options/index.html b/src/scripts/mute/options/index.html index 2114e7b1bf..1043d1adf2 100644 --- a/src/scripts/mute/options/index.html +++ b/src/scripts/mute/options/index.html @@ -27,12 +27,12 @@
          Muted blogs:
          +
            No muted blogs! Use the meatballs menu on any post to mute the blog that posted it.
            -
              diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js index 1b0ff498da..60f8228317 100644 --- a/src/scripts/mute/options/index.js +++ b/src/scripts/mute/options/index.js @@ -1,5 +1,4 @@ const mutedBlogList = document.getElementById('muted-blogs'); -const noMutedBlogText = document.getElementById('no-muted-blogs'); const mutedBlogTemplate = document.getElementById('muted-blog'); const blogNamesStorageKey = 'mute.blogNames'; @@ -40,7 +39,6 @@ const renderMutedBlogs = async function () { const blogNames = await getBlogNames(); mutedBlogList.textContent = ''; - noMutedBlogText.style.display = Object.entries(mutedBlogs).length ? 'none' : 'block'; for (const [uuid, mode] of Object.entries(mutedBlogs)) { const templateClone = mutedBlogTemplate.content.cloneNode(true); From 5f6a294bbea891b2d8d6c4994065cb07b24cb0c1 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 29 Apr 2024 10:33:52 -0700 Subject: [PATCH 35/76] refactor list item replacement Co-Authored-By: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/scripts/mute/options/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/scripts/mute/options/index.js b/src/scripts/mute/options/index.js index 60f8228317..43c42cae67 100644 --- a/src/scripts/mute/options/index.js +++ b/src/scripts/mute/options/index.js @@ -38,9 +38,7 @@ const renderMutedBlogs = async function () { const mutedBlogs = await getMutedBlogs(); const blogNames = await getBlogNames(); - mutedBlogList.textContent = ''; - - for (const [uuid, mode] of Object.entries(mutedBlogs)) { + mutedBlogList.replaceChildren(...Object.entries(mutedBlogs).map(([uuid, mode]) => { const templateClone = mutedBlogTemplate.content.cloneNode(true); const li = templateClone.querySelector('li'); const linkElement = templateClone.querySelector('a'); @@ -57,8 +55,8 @@ const renderMutedBlogs = async function () { unmuteButton.addEventListener('click', unmuteUser); - mutedBlogList.append(templateClone); - } + return templateClone; + })); }; browser.storage.onChanged.addListener((changes, areaName) => { From 8337f1e255efc55f1395feeedad68d579e5513fa Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Mon, 29 Apr 2024 10:34:51 -0700 Subject: [PATCH 36/76] refactor buttons Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/scripts/mute.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 7503bfac45..5f2e3d2b9b 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -167,16 +167,15 @@ const onMeatballButtonClicked = function ({ currentTarget }) { showModal({ title: currentMode ? `Mute options for ${name}:` : `Mute ${name}?`, message: [form], - buttons: currentMode - ? [ - modalCancelButton, - dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) - ] - : [ - modalCancelButton, - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) - ] + buttons: [ + modalCancelButton, + ...(currentMode ? [ + dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) + ] : [ + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) + ]) + ] }); }; From 6f075a3b7b005ecc71d5229e553a687ae822dc43 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 29 Apr 2024 10:36:06 -0700 Subject: [PATCH 37/76] prettier --- src/scripts/mute.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 5f2e3d2b9b..2829329808 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -169,12 +169,12 @@ const onMeatballButtonClicked = function ({ currentTarget }) { message: [form], buttons: [ modalCancelButton, - ...(currentMode ? [ - dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) - ] : [ - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) - ]) + ...(currentMode + ? [ + dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) + ] + : [dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' })]) ] }); }; From e0af28911d46f10da8d80cd631c38c6b3fa70b38 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 29 Apr 2024 14:59:08 -0700 Subject: [PATCH 38/76] refactor conditional modal --- src/scripts/mute.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/scripts/mute.js b/src/scripts/mute.js index 2829329808..7df33afd6d 100644 --- a/src/scripts/mute.js +++ b/src/scripts/mute.js @@ -164,19 +164,24 @@ const onMeatballButtonClicked = function ({ currentTarget }) { form.elements.muteOption.value = currentMode; - showModal({ - title: currentMode ? `Mute options for ${name}:` : `Mute ${name}?`, - message: [form], - buttons: [ - modalCancelButton, - ...(currentMode - ? [ - dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) - ] - : [dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' })]) - ] - }); + currentMode + ? showModal({ + title: `Mute options for ${name}:`, + message: [form], + buttons: [ + modalCancelButton, + dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) + ] + }) + : showModal({ + title: `Mute ${name}?`, + message: [form], + buttons: [ + modalCancelButton, + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) + ] + }); }; const muteUser = event => { From edad23a72e6de81e15f1295941669c7f8c55a200 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sat, 22 Jun 2024 10:09:28 -0700 Subject: [PATCH 39/76] correctly parse communities post authors --- src/features/mute.js | 14 ++++++++++---- src/utils/meatballs.js | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 963f637098..71e04ff4f9 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -8,7 +8,7 @@ import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; const meatballButtonId = 'mute'; -const meatballButtonLabel = ({ blogName, name }) => `Mute options for ${blogName ?? name}`; +const meatballButtonLabel = (data) => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; const hiddenAttribute = 'data-mute-hidden'; const onBlogHiddenAttribute = 'data-mute-hidden-on-blog'; @@ -96,16 +96,20 @@ const updateStoredName = (uuid, name) => { browser.storage.local.set({ [blogNamesStorageKey]: blogNames }); }; +const getVisibleBlog = ({ blog, authorBlog, community }) => (community ? authorBlog : blog); + const processPosts = async function (postElements) { await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { + const timelineObjectData = await timelineObject(postElement); + const { uuid, name } = getVisibleBlog(timelineObjectData); const { - blog: { uuid, name }, rebloggedRootUuid, content = [], trail = [] - } = await timelineObject(postElement); + } = timelineObjectData; + const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { @@ -146,7 +150,9 @@ const processPosts = async function (postElements) { }; const onMeatballButtonClicked = function ({ currentTarget }) { - const { name, uuid } = currentTarget?.__timelineObjectData?.blog || currentTarget?.__blogData; + const { name, uuid } = currentTarget.__timelineObjectData + ? getVisibleBlog(currentTarget.__timelineObjectData) + : currentTarget.__blogData; const currentMode = mutedBlogs[uuid]; diff --git a/src/utils/meatballs.js b/src/utils/meatballs.js index 4a1cefb89c..b4d80eb5e4 100644 --- a/src/utils/meatballs.js +++ b/src/utils/meatballs.js @@ -108,6 +108,8 @@ const addPostMeatballItem = async meatballMenu => { const addBlogMeatballItem = async meatballMenu => { const __blogData = await blogData(meatballMenu); + console.log('__blogData', __blogData); + $(meatballMenu).children('[data-xkit-meatball-button]').remove(); Object.keys(blogMeatballItems).sort().forEach(id => { From 24c6515a39fe449d8d5c5791327d22e99796e557 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sat, 22 Jun 2024 10:09:43 -0700 Subject: [PATCH 40/76] partial data-timeline-id implementation --- src/features/mute.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 71e04ff4f9..15839689fe 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -1,4 +1,4 @@ -import { filterPostElements, blogViewSelector, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; +import { filterPostElements, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../utils/modals.js'; import { timelineObject } from '../utils/react_props.js'; @@ -61,27 +61,21 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { const processTimelines = async (timelineElements) => { for (const timelineElement of [...new Set(timelineElements)]) { - const { timeline, muteProcessedTimeline } = timelineElement.dataset; + const { timeline, timelineId, muteProcessedTimeline } = timelineElement.dataset; - const alreadyProcessed = timeline === muteProcessedTimeline; + const alreadyProcessed = [timeline, timelineId].filter(Boolean).includes(muteProcessedTimeline); if (alreadyProcessed) return; - timelineElement.dataset.muteProcessedTimeline = timeline; + timelineElement.dataset.muteProcessedTimeline = timeline || timelineId; [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); delete timelineElement.dataset.muteOnBlogUuid; - const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); - const isSinglePostBlogView = timeline.endsWith('/permalink'); - const isLikes = timeline.endsWith('/likes'); + timelineElement.classList.add(activeClass); + lengthenTimeline(timelineElement); - if (!isChannel && !isSinglePostBlogView && !isLikes) { - timelineElement.classList.add(activeClass); - lengthenTimeline(timelineElement); - - if (timeline.startsWith('/v2/blog/')) { - await processBlogSpecificTimeline(timelineElement, timeline); - } + if (timeline?.startsWith('/v2/blog/')) { + await processBlogSpecificTimeline(timelineElement, timeline); } } }; @@ -99,7 +93,7 @@ const updateStoredName = (uuid, name) => { const getVisibleBlog = ({ blog, authorBlog, community }) => (community ? authorBlog : blog); const processPosts = async function (postElements) { - await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); + await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline], [data-timeline-id]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const timelineObjectData = await timelineObject(postElement); @@ -110,7 +104,7 @@ const processPosts = async function (postElements) { trail = [] } = timelineObjectData; - const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; + const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline], [data-timeline-id]').dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { updateStoredName(uuid, name); From 0ad375744d3820dbdfb0e0f4e19e23b832841a6b Mon Sep 17 00:00:00 2001 From: Marcus Date: Sat, 22 Jun 2024 10:09:47 -0700 Subject: [PATCH 41/76] Revert "partial data-timeline-id implementation" This reverts commit 24c6515a39fe449d8d5c5791327d22e99796e557. --- src/features/mute.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 15839689fe..71e04ff4f9 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -1,4 +1,4 @@ -import { filterPostElements, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; +import { filterPostElements, blogViewSelector, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../utils/modals.js'; import { timelineObject } from '../utils/react_props.js'; @@ -61,21 +61,27 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { const processTimelines = async (timelineElements) => { for (const timelineElement of [...new Set(timelineElements)]) { - const { timeline, timelineId, muteProcessedTimeline } = timelineElement.dataset; + const { timeline, muteProcessedTimeline } = timelineElement.dataset; - const alreadyProcessed = [timeline, timelineId].filter(Boolean).includes(muteProcessedTimeline); + const alreadyProcessed = timeline === muteProcessedTimeline; if (alreadyProcessed) return; - timelineElement.dataset.muteProcessedTimeline = timeline || timelineId; + timelineElement.dataset.muteProcessedTimeline = timeline; [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); delete timelineElement.dataset.muteOnBlogUuid; - timelineElement.classList.add(activeClass); - lengthenTimeline(timelineElement); + const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); + const isSinglePostBlogView = timeline.endsWith('/permalink'); + const isLikes = timeline.endsWith('/likes'); - if (timeline?.startsWith('/v2/blog/')) { - await processBlogSpecificTimeline(timelineElement, timeline); + if (!isChannel && !isSinglePostBlogView && !isLikes) { + timelineElement.classList.add(activeClass); + lengthenTimeline(timelineElement); + + if (timeline.startsWith('/v2/blog/')) { + await processBlogSpecificTimeline(timelineElement, timeline); + } } } }; @@ -93,7 +99,7 @@ const updateStoredName = (uuid, name) => { const getVisibleBlog = ({ blog, authorBlog, community }) => (community ? authorBlog : blog); const processPosts = async function (postElements) { - await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline], [data-timeline-id]'))); + await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const timelineObjectData = await timelineObject(postElement); @@ -104,7 +110,7 @@ const processPosts = async function (postElements) { trail = [] } = timelineObjectData; - const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline], [data-timeline-id]').dataset; + const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { updateStoredName(uuid, name); From 05c4cff86d8952f2279c36da80857468690a26a8 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 19 Sep 2024 05:05:57 -0700 Subject: [PATCH 42/76] remove missed logging --- src/utils/meatballs.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/meatballs.js b/src/utils/meatballs.js index 8869225dc8..e7315c5a7b 100644 --- a/src/utils/meatballs.js +++ b/src/utils/meatballs.js @@ -108,8 +108,6 @@ const addPostMeatballItem = async meatballMenu => { const addBlogMeatballItem = async meatballMenu => { const __blogData = await blogData(meatballMenu); - console.log('__blogData', __blogData); - $(meatballMenu).children('[data-xkit-meatball-button]').remove(); Object.keys(blogMeatballItems).sort().forEach(id => { From 8b9a62160e694e47df2487f00748f391e728b228 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 18:32:37 -0700 Subject: [PATCH 43/76] fix loop logic bug --- src/features/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/mute.js b/src/features/mute.js index 71e04ff4f9..b573216c07 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -64,7 +64,7 @@ const processTimelines = async (timelineElements) => { const { timeline, muteProcessedTimeline } = timelineElement.dataset; const alreadyProcessed = timeline === muteProcessedTimeline; - if (alreadyProcessed) return; + if (alreadyProcessed) continue; timelineElement.dataset.muteProcessedTimeline = timeline; From 2586d349571dd7f101a129d42bc730f5ca1c4067 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 18:40:43 -0700 Subject: [PATCH 44/76] fix (kinda) ui colors on patio --- src/features/mute.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index 2a26c3de4b..d06ef19c74 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -19,8 +19,8 @@ border-radius: 3px; margin-bottom: var(--post-padding); - background-color: var(--blog-title-color-15); - color: var(--blog-title-color); + background-color: var(--blog-title-color-15, rgba(var(--white-on-dark), .25)); + color: var(--blog-title-color, rgba(var(--white-on-dark))); font-weight: 700; text-align: center; @@ -28,5 +28,5 @@ } .xkit-mute-warning button { - color: var(--blog-link-color); + color: var(--blog-link-color, rgb(var(--deprecated-accent))); } From 87a674b00c48072f59609f0c596de90a57b6f9ed Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 18:59:45 -0700 Subject: [PATCH 45/76] timeline id util: add likes --- src/utils/timeline_id.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index c59aa4566c..9962a43e34 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -46,6 +46,11 @@ export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/queue`)) || timelineId?.match(exactly(`queue-${uuidV4}-${anyBlog}`)); +export const likesTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline === 'v2/user/likes' || + timelineId === 'likes' || + timelineId?.match(exactly(`likes-${uuidV4}`)); + export const tagTimelineFilter = tag => ({ dataset: { timeline, timelineId } }) => timeline === `/v2/hubs/${encodeURIComponent(tag)}/timeline` || From 3d9ced84625f2a739cec98bc83bdfee7811fcf15 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 19:00:04 -0700 Subject: [PATCH 46/76] timeline id util: add single post view --- src/utils/timeline_id.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 9962a43e34..7740486cc7 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -4,6 +4,7 @@ export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; +const anyPostId = '[0-9]{1,18}'; const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => @@ -32,6 +33,10 @@ export const blogTimelineFilter = blog => timelineId === `blog-view-${blog}` || timelineId?.match(exactly(`blog-${uuidV4}-${blog}`)); +export const anyBlogPostTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/${anyPostId}/permalink`)) || + timelineId?.match(exactly(`peepr-posts-${anyBlog}-${anyPostId}-undefined-undefined-undefined-undefined-undefined`)); + export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => timeline === '/v2/timeline?which=blog_subscriptions' || which === 'blog_subscriptions' || From 2cb086d706e942854a175185896c6b02cdb2aa56 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 19:00:28 -0700 Subject: [PATCH 47/76] timeline id util: add channelselector --- src/utils/timeline_id.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 7740486cc7..89311eeb10 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -1,6 +1,9 @@ +import { keyToCss } from './css_map.js'; + const createSelector = (...components) => `:is(${components.filter(Boolean).join(', ')})`; export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; +export const channelSelector = `${keyToCss('bar')} ~ *`; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; From 0fa274aed0d1570947c633c6c2904ad7fe340816 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 23 Sep 2024 19:00:44 -0700 Subject: [PATCH 48/76] implement data-timeline-id --- src/features/mute.js | 61 ++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index b573216c07..9e4dacf8d3 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -1,4 +1,4 @@ -import { filterPostElements, blogViewSelector, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; +import { filterPostElements, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../utils/modals.js'; import { timelineObject } from '../utils/react_props.js'; @@ -6,6 +6,7 @@ import { onNewPosts } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; +import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, channelSelector, likesTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = (data) => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; @@ -31,18 +32,32 @@ const lengthenTimeline = timeline => { } }; -const getNameAndUuid = async (timelineElement, timeline) => { - const uuidOrName = timeline.split('/')?.[3]; +const exactly = (string) => `^${string}$`; +const captureAnyBlog = '(t:[a-zA-Z0-9-_]{22}|[a-z0-9-]{1,32})'; +const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; + +const getNameOrUuid = ({ dataset: { timeline, timelineId } }) => + [ + timeline?.match(exactly(`/v2/blog/${captureAnyBlog}/posts`)), + timelineId?.match(exactly(`peepr-posts-${captureAnyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`)), + timelineId?.match(exactly(`blog-view-${captureAnyBlog}`)), + timelineId?.match(exactly(`blog-${uuidV4}-${captureAnyBlog}`)) + ] + .map((match) => match?.[1]) + .find(Boolean); + +const getNameAndUuid = async (timelineElement) => { + const uuidOrName = getNameOrUuid(timelineElement); const posts = [...timelineElement.querySelectorAll(postSelector)]; for (const post of posts) { const { blog: { name, uuid } } = await timelineObject(post); if ([name, uuid].includes(uuidOrName)) return [name, uuid]; } - throw new Error(`could not determine blog name / UUID for timeline with ${timeline}`); + throw new Error('could not determine blog name / UUID for timeline', timelineElement); }; -const processBlogSpecificTimeline = async (timelineElement, timeline) => { - const [name, uuid] = await getNameAndUuid(timelineElement, timeline); +const processBlogSpecificTimeline = async (timelineElement) => { + const [name, uuid] = await getNameAndUuid(timelineElement); const mode = mutedBlogs[uuid]; timelineElement.dataset.muteOnBlogUuid = uuid; @@ -59,28 +74,41 @@ const processBlogSpecificTimeline = async (timelineElement, timeline) => { } }; +const getLocation = timelineElement => { + const on = { + channel: timelineElement.matches(channelSelector), + singlePostBlogView: anyBlogPostTimelineFilter(timelineElement), + likes: likesTimelineFilter(timelineElement), + + activeBlogView: anyBlogTimelineFilter(timelineElement), + active: true + }; + return Object.keys(on).find(location => on[location]); +}; + const processTimelines = async (timelineElements) => { for (const timelineElement of [...new Set(timelineElements)]) { - const { timeline, muteProcessedTimeline } = timelineElement.dataset; + const { timeline, timelineId, muteProcessedTimeline, muteProcessedTimelineId } = timelineElement.dataset; - const alreadyProcessed = timeline === muteProcessedTimeline; + const alreadyProcessed = + (timeline && timeline === muteProcessedTimeline) || + (timelineId && timelineId === muteProcessedTimelineId); if (alreadyProcessed) continue; timelineElement.dataset.muteProcessedTimeline = timeline; + timelineElement.dataset.muteProcessedTimelineId = timelineId; [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); delete timelineElement.dataset.muteOnBlogUuid; - const isChannel = timeline.startsWith('/v2/blog/') && !timelineElement.matches(blogViewSelector); - const isSinglePostBlogView = timeline.endsWith('/permalink'); - const isLikes = timeline.endsWith('/likes'); + const location = getLocation(timelineElement); - if (!isChannel && !isSinglePostBlogView && !isLikes) { + if (['active', 'activeBlogView'].includes(location)) { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - if (timeline.startsWith('/v2/blog/')) { - await processBlogSpecificTimeline(timelineElement, timeline); + if (location === 'activeBlogView') { + await processBlogSpecificTimeline(timelineElement).catch(console.log); } } } @@ -99,7 +127,7 @@ const updateStoredName = (uuid, name) => { const getVisibleBlog = ({ blog, authorBlog, community }) => (community ? authorBlog : blog); const processPosts = async function (postElements) { - await processTimelines(postElements.map(postElement => postElement.closest('[data-timeline]'))); + await processTimelines(postElements.map(postElement => postElement.closest(timelineSelector))); filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const timelineObjectData = await timelineObject(postElement); @@ -110,7 +138,7 @@ const processPosts = async function (postElements) { trail = [] } = timelineObjectData; - const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest('[data-timeline]').dataset; + const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest(timelineSelector).dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { updateStoredName(uuid, name); @@ -264,6 +292,7 @@ export const clean = async function () { $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${warningClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); + $('[data-mute-processed-timeline-id]').removeAttr('data-mute-processed-timeline-id'); $('[data-mute-on-blog-uuid]').removeAttr('data-mute-on-blog-uuid'); }; From d3c48b1316f424ac4b373de7da3a22d00c98814c Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 25 Sep 2024 00:47:50 -0700 Subject: [PATCH 49/76] arrow parens --- src/features/mute.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 9e4dacf8d3..62c02498be 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -9,7 +9,7 @@ import { getPreferences } from '../utils/preferences.js'; import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, channelSelector, likesTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; const meatballButtonId = 'mute'; -const meatballButtonLabel = (data) => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; +const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; const hiddenAttribute = 'data-mute-hidden'; const onBlogHiddenAttribute = 'data-mute-hidden-on-blog'; @@ -32,7 +32,7 @@ const lengthenTimeline = timeline => { } }; -const exactly = (string) => `^${string}$`; +const exactly = string => `^${string}$`; const captureAnyBlog = '(t:[a-zA-Z0-9-_]{22}|[a-z0-9-]{1,32})'; const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; @@ -43,10 +43,10 @@ const getNameOrUuid = ({ dataset: { timeline, timelineId } }) => timelineId?.match(exactly(`blog-view-${captureAnyBlog}`)), timelineId?.match(exactly(`blog-${uuidV4}-${captureAnyBlog}`)) ] - .map((match) => match?.[1]) + .map(match => match?.[1]) .find(Boolean); -const getNameAndUuid = async (timelineElement) => { +const getNameAndUuid = async timelineElement => { const uuidOrName = getNameOrUuid(timelineElement); const posts = [...timelineElement.querySelectorAll(postSelector)]; for (const post of posts) { @@ -56,7 +56,7 @@ const getNameAndUuid = async (timelineElement) => { throw new Error('could not determine blog name / UUID for timeline', timelineElement); }; -const processBlogSpecificTimeline = async (timelineElement) => { +const processBlogSpecificTimeline = async timelineElement => { const [name, uuid] = await getNameAndUuid(timelineElement); const mode = mutedBlogs[uuid]; @@ -86,7 +86,7 @@ const getLocation = timelineElement => { return Object.keys(on).find(location => on[location]); }; -const processTimelines = async (timelineElements) => { +const processTimelines = async timelineElements => { for (const timelineElement of [...new Set(timelineElements)]) { const { timeline, timelineId, muteProcessedTimeline, muteProcessedTimelineId } = timelineElement.dataset; From 87c462676b6e0459ebc75e05a9ee9b7952cbc31f Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 25 Sep 2024 02:27:02 -0700 Subject: [PATCH 50/76] various clarifying refactors --- src/features/mute.css | 18 +++++----- src/features/mute.js | 81 ++++++++++++++++++++++--------------------- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index d06ef19c74..47b0f9c947 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -1,25 +1,27 @@ -.xkit-mute-active [data-mute-hidden] article, -:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ div [data-mute-hidden-on-blog] article { +.xkit-mute-active [data-mute-hidden] article { display: none; } -[data-mute-mode="all"] ~ div article { +.xkit-muted-blog-controls:is([data-mode='original'], [data-mode='reblogged']) ~ div [data-muted-blog-hidden] article { + display: none; +} +.xkit-muted-blog-controls[data-mode='all'] ~ div article { visibility: hidden !important; } -[data-mute-mode="all"] ~ div article :is(img, video, canvas) { - display: none; +.xkit-muted-blog-controls[data-mode='all'] ~ div article :is(img, video, canvas) { + visibility: hidden !important; } .xkit-mute-lengthened { min-height: 100vh; } -.xkit-mute-warning { +.xkit-muted-blog-controls { padding: 25px 20px; border-radius: 3px; margin-bottom: var(--post-padding); - background-color: var(--blog-title-color-15, rgba(var(--white-on-dark), .25)); + background-color: var(--blog-title-color-15, rgba(var(--white-on-dark), 0.25)); color: var(--blog-title-color, rgba(var(--white-on-dark))); font-weight: 700; @@ -27,6 +29,6 @@ line-height: 1.5em; } -.xkit-mute-warning button { +.xkit-muted-blog-controls button { color: var(--blog-link-color, rgb(var(--deprecated-accent))); } diff --git a/src/features/mute.js b/src/features/mute.js index 62c02498be..81bc6dba32 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -12,9 +12,9 @@ const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; const hiddenAttribute = 'data-mute-hidden'; -const onBlogHiddenAttribute = 'data-mute-hidden-on-blog'; +const mutedBlogHiddenAttribute = 'data-muted-blog-hidden'; const activeClass = 'xkit-mute-active'; -const warningClass = 'xkit-mute-warning'; +const mutedBlogControlsClass = 'xkit-muted-blog-controls'; const lengthenedClass = 'xkit-mute-lengthened'; const blogNamesStorageKey = 'mute.blogNames'; @@ -46,31 +46,32 @@ const getNameOrUuid = ({ dataset: { timeline, timelineId } }) => .map(match => match?.[1]) .find(Boolean); +// Attempts to get the blog name and blog UUID of a timeline element if it contains the posts from a single blog. +// The element itself doesn't contain both values, so a post object must be found with the required data. const getNameAndUuid = async timelineElement => { - const uuidOrName = getNameOrUuid(timelineElement); - const posts = [...timelineElement.querySelectorAll(postSelector)]; - for (const post of posts) { + const nameOrUuid = getNameOrUuid(timelineElement); + for (const post of [...timelineElement.querySelectorAll(postSelector)]) { const { blog: { name, uuid } } = await timelineObject(post); - if ([name, uuid].includes(uuidOrName)) return [name, uuid]; + if ([name, uuid].includes(nameOrUuid)) return { name, uuid }; } - throw new Error('could not determine blog name / UUID for timeline', timelineElement); + throw new Error('could not determine blog name / UUID for timeline element:', timelineElement); }; -const processBlogSpecificTimeline = async timelineElement => { - const [name, uuid] = await getNameAndUuid(timelineElement); +const processBlogTimelineElement = async timelineElement => { + const { name, uuid } = await getNameAndUuid(timelineElement); const mode = mutedBlogs[uuid]; - timelineElement.dataset.muteOnBlogUuid = uuid; - if (mode) { - const warningElement = dom('div', { class: warningClass }, null, [ + timelineElement.dataset.muteBlogUuid = uuid; + + const mutedBlogControls = dom('div', { class: mutedBlogControlsClass }, null, [ `You have muted ${mode} posts from ${name}!`, dom('br'), - dom('button', null, { click: () => warningElement.remove() }, ['show posts anyway']) + dom('button', null, { click: () => mutedBlogControls.remove() }, ['show posts anyway']) ]); - warningElement.dataset.muteMode = mode; + mutedBlogControls.dataset.mode = mode; - timelineElement.querySelector(keyToCss('scrollContainer')).before(warningElement); + timelineElement.querySelector(keyToCss('scrollContainer')).before(mutedBlogControls); } }; @@ -80,7 +81,7 @@ const getLocation = timelineElement => { singlePostBlogView: anyBlogPostTimelineFilter(timelineElement), likes: likesTimelineFilter(timelineElement), - activeBlogView: anyBlogTimelineFilter(timelineElement), + activeBlogTimeline: anyBlogTimelineFilter(timelineElement), active: true }; return Object.keys(on).find(location => on[location]); @@ -98,17 +99,18 @@ const processTimelines = async timelineElements => { timelineElement.dataset.muteProcessedTimeline = timeline; timelineElement.dataset.muteProcessedTimelineId = timelineId; - [...timelineElement.querySelectorAll(`.${warningClass}`)].forEach(el => el.remove()); - delete timelineElement.dataset.muteOnBlogUuid; + [...timelineElement.querySelectorAll(`.${mutedBlogControlsClass}`)].forEach(el => el.remove()); + delete timelineElement.dataset.muteBlogUuid; + timelineElement.classList.remove(activeClass); const location = getLocation(timelineElement); - if (['active', 'activeBlogView'].includes(location)) { + if (['active', 'activeBlogTimeline'].includes(location)) { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - if (location === 'activeBlogView') { - await processBlogSpecificTimeline(timelineElement).catch(console.log); + if (location === 'activeBlogTimeline') { + await processBlogTimelineElement(timelineElement).catch(console.log); } } } @@ -132,13 +134,9 @@ const processPosts = async function (postElements) { filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const timelineObjectData = await timelineObject(postElement); const { uuid, name } = getVisibleBlog(timelineObjectData); - const { - rebloggedRootUuid, - content = [], - trail = [] - } = timelineObjectData; + const { rebloggedRootUuid, content = [], trail = [] } = timelineObjectData; - const { muteOnBlogUuid: currentBlogViewUuid } = postElement.closest(timelineSelector).dataset; + const { muteBlogUuid: timelineBlogUuid } = postElement.closest(timelineSelector).dataset; if (mutedBlogs[uuid] && blogNames[uuid] !== name) { updateStoredName(uuid, name); @@ -153,13 +151,13 @@ const processPosts = async function (postElements) { if (['all', 'original'].includes(mutedBlogs[originalUuid])) { getTimelineItemWrapper(postElement).setAttribute( - originalUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + originalUuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, '' ); } if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { getTimelineItemWrapper(postElement).setAttribute( - reblogUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + reblogUuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, '' ); } @@ -168,7 +166,7 @@ const processPosts = async function (postElements) { for (const { blog } of trail) { if (['all'].includes(mutedBlogs[blog?.uuid])) { getTimelineItemWrapper(postElement).setAttribute( - blog?.uuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute, + blog?.uuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, '' ); } @@ -190,11 +188,16 @@ const onMeatballButtonClicked = function ({ currentTarget }) { dom('input', { type: 'radio', name: 'muteOption', value }) ]); - const form = dom('form', { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid }, { submit: muteUser }, [ - createRadioElement('all'), - createRadioElement('original'), - createRadioElement('reblogged') - ]); + const form = dom( + 'form', + { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid }, + { submit: muteUser }, + [ + createRadioElement('all'), + createRadioElement('original'), + createRadioElement('reblogged') + ] + ); form.elements.muteOption.value = currentMode; @@ -250,7 +253,7 @@ export const onStorageChanged = async function (changes, areaName) { } = changes; if ( - Object.keys(changes).some(key => key.startsWith('mute') && changes[key].oldValue !== undefined) || + Object.keys(changes).some(key => key.startsWith('mute.preferences') && changes[key].oldValue !== undefined) || mutedBlogsEntriesChanges ) { clean().then(main); @@ -287,13 +290,13 @@ export const clean = async function () { onNewPosts.removeListener(processPosts); $(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute); - $(`[${onBlogHiddenAttribute}]`).removeAttr(onBlogHiddenAttribute); + $(`[${mutedBlogHiddenAttribute}]`).removeAttr(mutedBlogHiddenAttribute); $(`.${activeClass}`).removeClass(activeClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); - $(`.${warningClass}`).remove(); + $(`.${mutedBlogControlsClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); $('[data-mute-processed-timeline-id]').removeAttr('data-mute-processed-timeline-id'); - $('[data-mute-on-blog-uuid]').removeAttr('data-mute-on-blog-uuid'); + $('[data-mute-blog-uuid]').removeAttr('data-mute-blog-uuid'); }; export const stylesheet = true; From a1e7f7698a5670a0a007cd8f2d67ac1f535d4157 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 25 Sep 2024 04:11:27 -0700 Subject: [PATCH 51/76] remove flicker on mute/unmute --- src/features/mute.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 81bc6dba32..0a21c83cd6 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -2,7 +2,7 @@ import { filterPostElements, postSelector, getTimelineItemWrapper } from '../uti import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js'; import { showModal, hideModal, modalCancelButton } from '../utils/modals.js'; import { timelineObject } from '../utils/react_props.js'; -import { onNewPosts } from '../utils/mutations.js'; +import { onNewPosts, pageModifications } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; @@ -252,10 +252,7 @@ export const onStorageChanged = async function (changes, areaName) { [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges } = changes; - if ( - Object.keys(changes).some(key => key.startsWith('mute.preferences') && changes[key].oldValue !== undefined) || - mutedBlogsEntriesChanges - ) { + if (Object.keys(changes).some(key => key.startsWith('mute.preferences') && changes[key].oldValue !== undefined)) { clean().then(main); return; } @@ -263,6 +260,14 @@ export const onStorageChanged = async function (changes, areaName) { if (blogNamesChanges) { ({ newValue: blogNames } = blogNamesChanges); } + + if (mutedBlogsEntriesChanges) { + const { newValue: mutedBlogsEntries } = mutedBlogsEntriesChanges; + mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); + + unprocess(); + pageModifications.trigger(processPosts); + } }; export const main = async function () { @@ -284,11 +289,7 @@ export const main = async function () { onNewPosts.addListener(processPosts); }; -export const clean = async function () { - unregisterMeatballItem(meatballButtonId); - unregisterBlogMeatballItem(meatballButtonId); - onNewPosts.removeListener(processPosts); - +const unprocess = () => { $(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute); $(`[${mutedBlogHiddenAttribute}]`).removeAttr(mutedBlogHiddenAttribute); $(`.${activeClass}`).removeClass(activeClass); @@ -299,4 +300,11 @@ export const clean = async function () { $('[data-mute-blog-uuid]').removeAttr('data-mute-blog-uuid'); }; +export const clean = async function () { + unprocess(); + unregisterMeatballItem(meatballButtonId); + unregisterBlogMeatballItem(meatballButtonId); + onNewPosts.removeListener(processPosts); +}; + export const stylesheet = true; From 47100414d603cf65c52a5e9bccb42970f3dbca1c Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 26 Sep 2024 00:15:07 -0700 Subject: [PATCH 52/76] timeline id util: fix single post view turns out you should research things. --- src/utils/timeline_id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 89311eeb10..80b59c879a 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -7,7 +7,7 @@ export const channelSelector = `${keyToCss('bar')} ~ *`; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; -const anyPostId = '[0-9]{1,18}'; +const anyPostId = '[0-9]{1,19}'; const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => From dcf4be65673e8a665753837345c58cc4b8dedd52 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 26 Sep 2024 01:04:41 -0700 Subject: [PATCH 53/76] fix muted blog timelines with endless scrolling disabled --- src/features/mute.js | 4 +++- src/features/show_originals.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 0a21c83cd6..aac26bb5b6 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -7,6 +7,7 @@ import { keyToCss } from '../utils/css_map.js'; import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, channelSelector, likesTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; +import { controlsClass as showOriginalsControlsClass } from './show_originals.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; @@ -71,7 +72,8 @@ const processBlogTimelineElement = async timelineElement => { ]); mutedBlogControls.dataset.mode = mode; - timelineElement.querySelector(keyToCss('scrollContainer')).before(mutedBlogControls); + timelineElement.prepend(mutedBlogControls); + timelineElement.querySelector(`.${showOriginalsControlsClass}`)?.after(mutedBlogControls); } }; diff --git a/src/features/show_originals.js b/src/features/show_originals.js index 6a813298c5..8ec58696cf 100644 --- a/src/features/show_originals.js +++ b/src/features/show_originals.js @@ -17,7 +17,7 @@ import { const hiddenAttribute = 'data-show-originals-hidden'; const lengthenedClass = 'xkit-show-originals-lengthened'; -const controlsClass = 'xkit-show-originals-controls'; +export const controlsClass = 'xkit-show-originals-controls'; const channelSelector = `${keyToCss('bar')} ~ *`; From 262c76a041e1563701fe495199777a6dd7951aec Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 26 Sep 2024 16:10:12 -0700 Subject: [PATCH 54/76] technically this will matter in november 2303. --- src/utils/timeline_id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 80b59c879a..00c2214148 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -7,7 +7,7 @@ export const channelSelector = `${keyToCss('bar')} ~ *`; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; -const anyPostId = '[0-9]{1,19}'; +const anyPostId = '[0-9]{1,20}'; const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => From 1757628ff9ed3ed9611f74276b913e6bcd260f13 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 7 Jan 2025 02:57:15 -0800 Subject: [PATCH 55/76] update capitalization of "onclick" --- src/features/mute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/mute.js b/src/features/mute.js index aac26bb5b6..76ef09e374 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -286,7 +286,7 @@ export const main = async function () { registerBlogMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, - onClick: onMeatballButtonClicked + onclick: onMeatballButtonClicked }); onNewPosts.addListener(processPosts); }; From 82af16ede9ef57a9da51171d37d8767d8564181c Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 5 Feb 2025 23:55:15 -0800 Subject: [PATCH 56/76] various refactors --- src/features/mute.css | 14 ++++------- src/features/mute.js | 54 +++++++++++++++---------------------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index 47b0f9c947..11f3ae873f 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -1,14 +1,10 @@ -.xkit-mute-active [data-mute-hidden] article { +.xkit-mute-active [data-mute-hidden] article, +[data-mute-mode='original'] ~ div [data-muted-blog-hidden] article, +[data-mute-mode='reblogged'] ~ div [data-muted-blog-hidden] article { display: none; } - -.xkit-muted-blog-controls:is([data-mode='original'], [data-mode='reblogged']) ~ div [data-muted-blog-hidden] article { - display: none; -} -.xkit-muted-blog-controls[data-mode='all'] ~ div article { - visibility: hidden !important; -} -.xkit-muted-blog-controls[data-mode='all'] ~ div article :is(img, video, canvas) { +[data-mute-mode='all'] ~ div article, +[data-mute-mode='all'] ~ div article :is(img, video, canvas) { visibility: hidden !important; } diff --git a/src/features/mute.js b/src/features/mute.js index 76ef09e374..0c5a76a2d2 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -6,7 +6,14 @@ import { onNewPosts, pageModifications } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; -import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, channelSelector, likesTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; +import { + anyBlogPostTimelineFilter, + anyBlogTimelineFilter, + blogTimelineFilter, + channelSelector, + likesTimelineFilter, + timelineSelector +} from '../utils/timeline_id.js'; import { controlsClass as showOriginalsControlsClass } from './show_originals.js'; const meatballButtonId = 'mute'; @@ -33,27 +40,14 @@ const lengthenTimeline = timeline => { } }; -const exactly = string => `^${string}$`; -const captureAnyBlog = '(t:[a-zA-Z0-9-_]{22}|[a-z0-9-]{1,32})'; -const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; - -const getNameOrUuid = ({ dataset: { timeline, timelineId } }) => - [ - timeline?.match(exactly(`/v2/blog/${captureAnyBlog}/posts`)), - timelineId?.match(exactly(`peepr-posts-${captureAnyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`)), - timelineId?.match(exactly(`blog-view-${captureAnyBlog}`)), - timelineId?.match(exactly(`blog-${uuidV4}-${captureAnyBlog}`)) - ] - .map(match => match?.[1]) - .find(Boolean); - // Attempts to get the blog name and blog UUID of a timeline element if it contains the posts from a single blog. // The element itself doesn't contain both values, so a post object must be found with the required data. const getNameAndUuid = async timelineElement => { - const nameOrUuid = getNameOrUuid(timelineElement); for (const post of [...timelineElement.querySelectorAll(postSelector)]) { const { blog: { name, uuid } } = await timelineObject(post); - if ([name, uuid].includes(nameOrUuid)) return { name, uuid }; + if (blogTimelineFilter(name)(timelineElement) || blogTimelineFilter(uuid)(timelineElement)) { + return { name, uuid }; + } } throw new Error('could not determine blog name / UUID for timeline element:', timelineElement); }; @@ -65,29 +59,21 @@ const processBlogTimelineElement = async timelineElement => { if (mode) { timelineElement.dataset.muteBlogUuid = uuid; - const mutedBlogControls = dom('div', { class: mutedBlogControlsClass }, null, [ + const mutedBlogControls = dom('div', { class: mutedBlogControlsClass, 'data-mute-mode': mode }, null, [ `You have muted ${mode} posts from ${name}!`, dom('br'), dom('button', null, { click: () => mutedBlogControls.remove() }, ['show posts anyway']) ]); - mutedBlogControls.dataset.mode = mode; - timelineElement.prepend(mutedBlogControls); timelineElement.querySelector(`.${showOriginalsControlsClass}`)?.after(mutedBlogControls); } }; -const getLocation = timelineElement => { - const on = { - channel: timelineElement.matches(channelSelector), - singlePostBlogView: anyBlogPostTimelineFilter(timelineElement), - likes: likesTimelineFilter(timelineElement), - - activeBlogTimeline: anyBlogTimelineFilter(timelineElement), - active: true - }; - return Object.keys(on).find(location => on[location]); -}; +const shouldDisable = timelineElement => Boolean( + (anyBlogTimelineFilter(timelineElement) && timelineElement.matches(channelSelector)) || + anyBlogPostTimelineFilter(timelineElement) || + likesTimelineFilter(timelineElement) +); const processTimelines = async timelineElements => { for (const timelineElement of [...new Set(timelineElements)]) { @@ -105,13 +91,11 @@ const processTimelines = async timelineElements => { delete timelineElement.dataset.muteBlogUuid; timelineElement.classList.remove(activeClass); - const location = getLocation(timelineElement); - - if (['active', 'activeBlogTimeline'].includes(location)) { + if (shouldDisable(timelineElement) === false) { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - if (location === 'activeBlogTimeline') { + if (anyBlogTimelineFilter(timelineElement)) { await processBlogTimelineElement(timelineElement).catch(console.log); } } From 3193e1d4efd3308d9e9b197f278d1aa4dbb78807 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 00:55:54 -0800 Subject: [PATCH 57/76] refactors for clarification --- src/features/mute.css | 10 +++++---- src/features/mute.js | 49 ++++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index 11f3ae873f..36abd1fe69 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -1,10 +1,12 @@ .xkit-mute-active [data-mute-hidden] article, -[data-mute-mode='original'] ~ div [data-muted-blog-hidden] article, -[data-mute-mode='reblogged'] ~ div [data-muted-blog-hidden] article { +[data-muted-blog-controls-mode='original'] ~ div [data-muted-blog-controls-hidden] article, +[data-muted-blog-controls-mode='reblogged'] ~ div [data-muted-blog-controls-hidden] article { display: none; } -[data-mute-mode='all'] ~ div article, -[data-mute-mode='all'] ~ div article :is(img, video, canvas) { + +/* prevents endless post loading by preserving post height */ +[data-muted-blog-controls-mode='all'] ~ div article, +[data-muted-blog-controls-mode='all'] ~ div article :is(img, video, canvas) { visibility: hidden !important; } diff --git a/src/features/mute.js b/src/features/mute.js index 0c5a76a2d2..07f00ac7de 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -20,13 +20,13 @@ const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; const hiddenAttribute = 'data-mute-hidden'; -const mutedBlogHiddenAttribute = 'data-muted-blog-hidden'; +const mutedBlogControlsHiddenAttribute = 'data-muted-blog-controls-hidden'; const activeClass = 'xkit-mute-active'; const mutedBlogControlsClass = 'xkit-muted-blog-controls'; const lengthenedClass = 'xkit-mute-lengthened'; const blogNamesStorageKey = 'mute.blogNames'; -const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; +const mutedBlogEntriesStorageKey = 'mute.mutedBlogEntries'; let checkTrail; let contributedContentOriginal; @@ -54,13 +54,13 @@ const getNameAndUuid = async timelineElement => { const processBlogTimelineElement = async timelineElement => { const { name, uuid } = await getNameAndUuid(timelineElement); - const mode = mutedBlogs[uuid]; + const mutedBlogMode = mutedBlogs[uuid]; - if (mode) { + if (mutedBlogMode) { timelineElement.dataset.muteBlogUuid = uuid; - const mutedBlogControls = dom('div', { class: mutedBlogControlsClass, 'data-mute-mode': mode }, null, [ - `You have muted ${mode} posts from ${name}!`, + const mutedBlogControls = dom('div', { class: mutedBlogControlsClass, 'data-muted-blog-controls-mode': mutedBlogMode }, null, [ + `You have muted ${mutedBlogMode} posts from ${name}!`, dom('br'), dom('button', null, { click: () => mutedBlogControls.remove() }, ['show posts anyway']) ]); @@ -128,6 +128,16 @@ const processPosts = async function (postElements) { updateStoredName(uuid, name); } + const hidePost = relevantBlogUuid => + getTimelineItemWrapper(postElement).setAttribute( + // Posts hidden on blog timelines can be revealed by muted blog timeline controls + // if and only if they are hidden because the current blog is muted. + relevantBlogUuid === timelineBlogUuid + ? mutedBlogControlsHiddenAttribute + : hiddenAttribute, + '' + ); + const isRebloggedPost = contributedContentOriginal ? rebloggedRootUuid && !content.length : rebloggedRootUuid; @@ -136,25 +146,16 @@ const processPosts = async function (postElements) { const reblogUuid = isRebloggedPost ? uuid : null; if (['all', 'original'].includes(mutedBlogs[originalUuid])) { - getTimelineItemWrapper(postElement).setAttribute( - originalUuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, - '' - ); + hidePost(originalUuid); } if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) { - getTimelineItemWrapper(postElement).setAttribute( - reblogUuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, - '' - ); + hidePost(reblogUuid); } if (checkTrail) { for (const { blog } of trail) { if (['all'].includes(mutedBlogs[blog?.uuid])) { - getTimelineItemWrapper(postElement).setAttribute( - blog?.uuid === timelineBlogUuid ? mutedBlogHiddenAttribute : hiddenAttribute, - '' - ); + hidePost(blog.uuid); } } } @@ -194,7 +195,7 @@ const onMeatballButtonClicked = function ({ currentTarget }) { buttons: [ modalCancelButton, dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mute' }) + dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mode' }) ] }) : showModal({ @@ -218,7 +219,7 @@ const muteUser = event => { blogNames[uuid] = name; browser.storage.local.set({ - [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs), + [mutedBlogEntriesStorageKey]: Object.entries(mutedBlogs), [blogNamesStorageKey]: blogNames }); @@ -227,7 +228,7 @@ const muteUser = event => { const unmuteUser = uuid => { delete mutedBlogs[uuid]; - browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); + browser.storage.local.set({ [mutedBlogEntriesStorageKey]: Object.entries(mutedBlogs) }); hideModal(); }; @@ -235,7 +236,7 @@ const unmuteUser = uuid => { export const onStorageChanged = async function (changes, areaName) { const { [blogNamesStorageKey]: blogNamesChanges, - [mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges + [mutedBlogEntriesStorageKey]: mutedBlogsEntriesChanges } = changes; if (Object.keys(changes).some(key => key.startsWith('mute.preferences') && changes[key].oldValue !== undefined)) { @@ -259,7 +260,7 @@ export const onStorageChanged = async function (changes, areaName) { export const main = async function () { ({ checkTrail, contributedContentOriginal } = await getPreferences('mute')); ({ [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey)); - const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); + const { [mutedBlogEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogEntriesStorageKey); mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []); registerMeatballItem({ @@ -277,7 +278,7 @@ export const main = async function () { const unprocess = () => { $(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute); - $(`[${mutedBlogHiddenAttribute}]`).removeAttr(mutedBlogHiddenAttribute); + $(`[${mutedBlogControlsHiddenAttribute}]`).removeAttr(mutedBlogControlsHiddenAttribute); $(`.${activeClass}`).removeClass(activeClass); $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${mutedBlogControlsClass}`).remove(); From 85c4e8a0d16324c33258a2780394e7f90f2166ae Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 01:19:16 -0800 Subject: [PATCH 58/76] disable on more timelines --- src/features/mute.js | 16 ++++++++++++---- src/utils/timeline_id.js | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 07f00ac7de..8782fe8503 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -9,12 +9,16 @@ import { getPreferences } from '../utils/preferences.js'; import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, + anyDraftsTimelineFilter, + anyFlaggedReviewTimelineFilter, + anyQueueTimelineFilter, blogTimelineFilter, - channelSelector, + inboxTimelineFilter, likesTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; import { controlsClass as showOriginalsControlsClass } from './show_originals.js'; +import { userBlogNames } from '../utils/user.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; @@ -70,9 +74,13 @@ const processBlogTimelineElement = async timelineElement => { }; const shouldDisable = timelineElement => Boolean( - (anyBlogTimelineFilter(timelineElement) && timelineElement.matches(channelSelector)) || - anyBlogPostTimelineFilter(timelineElement) || - likesTimelineFilter(timelineElement) + userBlogNames.some(name => blogTimelineFilter(name)(timelineElement)) || + anyDraftsTimelineFilter(timelineElement) || + anyQueueTimelineFilter(timelineElement) || + anyFlaggedReviewTimelineFilter(timelineElement) || + likesTimelineFilter(timelineElement) || + inboxTimelineFilter(timelineElement) || + anyBlogPostTimelineFilter(timelineElement) ); const processTimelines = async timelineElements => { diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index efda0a3bda..300ef0206e 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -54,11 +54,17 @@ export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/queue`)) || timelineId?.match(exactly(`queue-${uuidV4}-${anyBlog}`)); +export const anyFlaggedReviewTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/review`)); + export const likesTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline === 'v2/user/likes' || timelineId === 'likes' || timelineId?.match(exactly(`likes-${uuidV4}`)); +export const inboxTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.startsWith('/v2/user/inbox'); + export const tagTimelineFilter = tag => ({ dataset: { timeline, timelineId } }) => timeline === `/v2/hubs/${encodeURIComponent(tag)}/timeline` || From 3a22cd26d0e79f298142c2acb442f0c83308215c Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 01:51:12 -0800 Subject: [PATCH 59/76] process peepr search/tagged correctly --- src/features/mute.js | 12 ++++++++++-- src/utils/timeline_id.js | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/features/mute.js b/src/features/mute.js index 8782fe8503..0249ae4885 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -11,10 +11,12 @@ import { anyBlogTimelineFilter, anyDraftsTimelineFilter, anyFlaggedReviewTimelineFilter, + anyPeeprTimelineFilter, anyQueueTimelineFilter, blogTimelineFilter, inboxTimelineFilter, likesTimelineFilter, + peeprTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; import { controlsClass as showOriginalsControlsClass } from './show_originals.js'; @@ -49,7 +51,12 @@ const lengthenTimeline = timeline => { const getNameAndUuid = async timelineElement => { for (const post of [...timelineElement.querySelectorAll(postSelector)]) { const { blog: { name, uuid } } = await timelineObject(post); - if (blogTimelineFilter(name)(timelineElement) || blogTimelineFilter(uuid)(timelineElement)) { + if ( + blogTimelineFilter(name)(timelineElement) || + peeprTimelineFilter(name)(timelineElement) || + blogTimelineFilter(uuid)(timelineElement) || + peeprTimelineFilter(uuid)(timelineElement) + ) { return { name, uuid }; } } @@ -75,6 +82,7 @@ const processBlogTimelineElement = async timelineElement => { const shouldDisable = timelineElement => Boolean( userBlogNames.some(name => blogTimelineFilter(name)(timelineElement)) || + userBlogNames.some(name => peeprTimelineFilter(name)(timelineElement)) || anyDraftsTimelineFilter(timelineElement) || anyQueueTimelineFilter(timelineElement) || anyFlaggedReviewTimelineFilter(timelineElement) || @@ -103,7 +111,7 @@ const processTimelines = async timelineElements => { timelineElement.classList.add(activeClass); lengthenTimeline(timelineElement); - if (anyBlogTimelineFilter(timelineElement)) { + if (anyBlogTimelineFilter(timelineElement) || anyPeeprTimelineFilter(timelineElement)) { await processBlogTimelineElement(timelineElement).catch(console.log); } } diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 300ef0206e..cd9f6cb89e 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -40,6 +40,15 @@ export const anyBlogPostTimelineFilter = ({ dataset: { timeline, timelineId } }) timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/${anyPostId}/permalink`)) || timelineId?.match(exactly(`peepr-posts-${anyBlog}-${anyPostId}-undefined-undefined-undefined-undefined-undefined`)); +// includes viewing a blog on and searching/tag searching a blog on peepr +export const anyPeeprTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timelineId?.startsWith('peepr-posts-'); + +// includes viewing a blog on and searching/tag searching a blog on peepr +export const peeprTimelineFilter = blog => + ({ dataset: { timeline, timelineId } }) => + timelineId?.startsWith(`peepr-posts-${blog}-`); + export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => timeline === '/v2/timeline?which=blog_subscriptions' || which === 'blog_subscriptions' || From 19185f27697f7567cb55f62a6522605783aa25af Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 01:56:51 -0800 Subject: [PATCH 60/76] disable on own public likes --- src/features/mute.js | 2 ++ src/utils/timeline_id.js | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/features/mute.js b/src/features/mute.js index 0249ae4885..6eda1b52ac 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -16,6 +16,7 @@ import { blogTimelineFilter, inboxTimelineFilter, likesTimelineFilter, + peeprLikesTimelineFilter, peeprTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; @@ -87,6 +88,7 @@ const shouldDisable = timelineElement => Boolean( anyQueueTimelineFilter(timelineElement) || anyFlaggedReviewTimelineFilter(timelineElement) || likesTimelineFilter(timelineElement) || + userBlogNames.some(name => peeprLikesTimelineFilter(name)(timelineElement)) || inboxTimelineFilter(timelineElement) || anyBlogPostTimelineFilter(timelineElement) ); diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index cd9f6cb89e..35c21bb05d 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -71,6 +71,13 @@ export const likesTimelineFilter = ({ dataset: { timeline, timelineId } }) => timelineId === 'likes' || timelineId?.match(exactly(`likes-${uuidV4}`)); +export const peeprLikesTimelineFilter = blog => + ({ dataset: { timeline, timelineId } }) => + timelineId === `peepr-likes-${blog}`; + +export const anyPeeprLikesTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timelineId?.match(exactly(`peepr-likes-${anyBlog}`)); + export const inboxTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.startsWith('/v2/user/inbox'); From 77a3d6734c33373e9c0fafb37c677fcf963febcf Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 02:15:10 -0800 Subject: [PATCH 61/76] fix list/masonry switch removing class --- src/features/mute.css | 2 +- src/features/mute.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index 36abd1fe69..b63e09b832 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -1,4 +1,4 @@ -.xkit-mute-active [data-mute-hidden] article, +[data-mute-active] [data-mute-hidden] article, [data-muted-blog-controls-mode='original'] ~ div [data-muted-blog-controls-hidden] article, [data-muted-blog-controls-mode='reblogged'] ~ div [data-muted-blog-controls-hidden] article { display: none; diff --git a/src/features/mute.js b/src/features/mute.js index 6eda1b52ac..892816b73b 100644 --- a/src/features/mute.js +++ b/src/features/mute.js @@ -28,7 +28,7 @@ const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleB const hiddenAttribute = 'data-mute-hidden'; const mutedBlogControlsHiddenAttribute = 'data-muted-blog-controls-hidden'; -const activeClass = 'xkit-mute-active'; +const activeAttribute = 'data-mute-active'; const mutedBlogControlsClass = 'xkit-muted-blog-controls'; const lengthenedClass = 'xkit-mute-lengthened'; @@ -107,10 +107,10 @@ const processTimelines = async timelineElements => { [...timelineElement.querySelectorAll(`.${mutedBlogControlsClass}`)].forEach(el => el.remove()); delete timelineElement.dataset.muteBlogUuid; - timelineElement.classList.remove(activeClass); + timelineElement.removeAttribute(activeAttribute); if (shouldDisable(timelineElement) === false) { - timelineElement.classList.add(activeClass); + timelineElement.setAttribute(activeAttribute, ''); lengthenTimeline(timelineElement); if (anyBlogTimelineFilter(timelineElement) || anyPeeprTimelineFilter(timelineElement)) { @@ -297,7 +297,7 @@ export const main = async function () { const unprocess = () => { $(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute); $(`[${mutedBlogControlsHiddenAttribute}]`).removeAttr(mutedBlogControlsHiddenAttribute); - $(`.${activeClass}`).removeClass(activeClass); + $(`[${activeAttribute}]`).removeAttr(activeAttribute); $(`.${lengthenedClass}`).removeClass(lengthenedClass); $(`.${mutedBlogControlsClass}`).remove(); $('[data-mute-processed-timeline]').removeAttr('data-mute-processed-timeline'); From 9a5c52d3ef7bc13ffc25f160f797c8d773a1ca39 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 6 Feb 2025 02:17:35 -0800 Subject: [PATCH 62/76] remove unused export --- src/utils/timeline_id.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 35c21bb05d..3c7950a45d 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -1,9 +1,6 @@ -import { keyToCss } from './css_map.js'; - const createSelector = (...components) => `:is(${components.filter(Boolean).join(', ')})`; export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; -export const channelSelector = `${keyToCss('bar')} ~ *`; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; From 5c32fd33a3aba33461f259a13d5f98e1288ad5a4 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 30 Mar 2025 16:17:41 -0400 Subject: [PATCH 63/76] apply #1544 --- src/features/mute.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/features/mute.css b/src/features/mute.css index b63e09b832..4046a0545c 100644 --- a/src/features/mute.css +++ b/src/features/mute.css @@ -1,7 +1,8 @@ -[data-mute-active] [data-mute-hidden] article, -[data-muted-blog-controls-mode='original'] ~ div [data-muted-blog-controls-hidden] article, -[data-muted-blog-controls-mode='reblogged'] ~ div [data-muted-blog-controls-hidden] article { - display: none; +[data-mute-active] [data-mute-hidden], +[data-muted-blog-controls-mode='original'] ~ div [data-muted-blog-controls-hidden], +[data-muted-blog-controls-mode='reblogged'] ~ div [data-muted-blog-controls-hidden] { + content: linear-gradient(transparent, transparent); + height: 0; } /* prevents endless post loading by preserving post height */ From 4cb7286eaf24058029f9cbaccf7ec4d32731b2fa Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 30 Jun 2025 12:00:54 -0700 Subject: [PATCH 64/76] update directory structure --- src/features/{mute.json => mute/feature.json} | 0 src/features/{mute.css => mute/index.css} | 0 src/features/{mute.js => mute/index.js} | 22 +++++++++---------- 3 files changed, 11 insertions(+), 11 deletions(-) rename src/features/{mute.json => mute/feature.json} (100%) rename src/features/{mute.css => mute/index.css} (100%) rename src/features/{mute.js => mute/index.js} (93%) diff --git a/src/features/mute.json b/src/features/mute/feature.json similarity index 100% rename from src/features/mute.json rename to src/features/mute/feature.json diff --git a/src/features/mute.css b/src/features/mute/index.css similarity index 100% rename from src/features/mute.css rename to src/features/mute/index.css diff --git a/src/features/mute.js b/src/features/mute/index.js similarity index 93% rename from src/features/mute.js rename to src/features/mute/index.js index 892816b73b..8aba5d277b 100644 --- a/src/features/mute.js +++ b/src/features/mute/index.js @@ -1,11 +1,11 @@ -import { filterPostElements, postSelector, getTimelineItemWrapper } from '../utils/interface.js'; -import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js'; -import { showModal, hideModal, modalCancelButton } from '../utils/modals.js'; -import { timelineObject } from '../utils/react_props.js'; -import { onNewPosts, pageModifications } from '../utils/mutations.js'; -import { keyToCss } from '../utils/css_map.js'; -import { dom } from '../utils/dom.js'; -import { getPreferences } from '../utils/preferences.js'; +import { filterPostElements, postSelector, getTimelineItemWrapper } from '../../utils/interface.js'; +import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../../utils/meatballs.js'; +import { showModal, hideModal, modalCancelButton } from '../../utils/modals.js'; +import { timelineObject } from '../../utils/react_props.js'; +import { onNewPosts, pageModifications } from '../../utils/mutations.js'; +import { keyToCss } from '../../utils/css_map.js'; +import { dom } from '../../utils/dom.js'; +import { getPreferences } from '../../utils/preferences.js'; import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, @@ -19,9 +19,9 @@ import { peeprLikesTimelineFilter, peeprTimelineFilter, timelineSelector -} from '../utils/timeline_id.js'; -import { controlsClass as showOriginalsControlsClass } from './show_originals.js'; -import { userBlogNames } from '../utils/user.js'; +} from '../../utils/timeline_id.js'; +import { controlsClass as showOriginalsControlsClass } from '../show_originals/index.js'; +import { userBlogNames } from '../../utils/user.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; From 823bf29efa8073f30e0b20a5ca795950ae42e7d3 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Fri, 17 Oct 2025 14:31:55 -0700 Subject: [PATCH 65/76] use new dom utils --- src/features/mute/index.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index 8aba5d277b..6e87c0cf7e 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -4,7 +4,7 @@ import { showModal, hideModal, modalCancelButton } from '../../utils/modals.js'; import { timelineObject } from '../../utils/react_props.js'; import { onNewPosts, pageModifications } from '../../utils/mutations.js'; import { keyToCss } from '../../utils/css_map.js'; -import { dom } from '../../utils/dom.js'; +import { button, div, form, input, label, dom } from '../../utils/dom.js'; import { getPreferences } from '../../utils/preferences.js'; import { anyBlogPostTimelineFilter, @@ -71,10 +71,10 @@ const processBlogTimelineElement = async timelineElement => { if (mutedBlogMode) { timelineElement.dataset.muteBlogUuid = uuid; - const mutedBlogControls = dom('div', { class: mutedBlogControlsClass, 'data-muted-blog-controls-mode': mutedBlogMode }, null, [ + const mutedBlogControls = div({ class: mutedBlogControlsClass, 'data-muted-blog-controls-mode': mutedBlogMode }, [ `You have muted ${mutedBlogMode} posts from ${name}!`, dom('br'), - dom('button', null, { click: () => mutedBlogControls.remove() }, ['show posts anyway']) + button({ click: () => mutedBlogControls.remove() }, ['show posts anyway']) ]); timelineElement.prepend(mutedBlogControls); timelineElement.querySelector(`.${showOriginalsControlsClass}`)?.after(mutedBlogControls); @@ -188,15 +188,13 @@ const onMeatballButtonClicked = function ({ currentTarget }) { const currentMode = mutedBlogs[uuid]; const createRadioElement = value => - dom('label', null, null, [ + label({}, [ `Hide ${value} posts`, - dom('input', { type: 'radio', name: 'muteOption', value }) + input({ type: 'radio', name: 'muteOption', value }) ]); - const form = dom( - 'form', - { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid }, - { submit: muteUser }, + const formElement = form( + { id: 'xkit-mute-form', 'data-name': name, 'data-uuid': uuid, submit: muteUser }, [ createRadioElement('all'), createRadioElement('original'), @@ -204,24 +202,24 @@ const onMeatballButtonClicked = function ({ currentTarget }) { ] ); - form.elements.muteOption.value = currentMode; + formElement.elements.muteOption.value = currentMode; currentMode ? showModal({ title: `Mute options for ${name}:`, - message: [form], + message: [formElement], buttons: [ modalCancelButton, - dom('button', { class: 'blue' }, { click: () => unmuteUser(uuid) }, ['Unmute']), - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Update Mode' }) + button({ class: 'blue', click: () => unmuteUser(uuid) }, ['Unmute']), + input({ type: 'submit', form: formElement.id, class: 'red', value: 'Update Mode' }) ] }) : showModal({ title: `Mute ${name}?`, - message: [form], + message: [formElement], buttons: [ modalCancelButton, - dom('input', { type: 'submit', form: form.id, class: 'red', value: 'Mute' }) + input({ type: 'submit', form: formElement.id, class: 'red', value: 'Mute' }) ] }); }; From 8ed8b4cc9cbd80043b144c572fa3b2fd549b34f7 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Wed, 22 Oct 2025 02:39:29 -0700 Subject: [PATCH 66/76] use more new dom utils --- src/features/mute/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index 6e87c0cf7e..806ea355d4 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -4,7 +4,7 @@ import { showModal, hideModal, modalCancelButton } from '../../utils/modals.js'; import { timelineObject } from '../../utils/react_props.js'; import { onNewPosts, pageModifications } from '../../utils/mutations.js'; import { keyToCss } from '../../utils/css_map.js'; -import { button, div, form, input, label, dom } from '../../utils/dom.js'; +import { br, button, div, form, input, label } from '../../utils/dom.js'; import { getPreferences } from '../../utils/preferences.js'; import { anyBlogPostTimelineFilter, @@ -73,7 +73,7 @@ const processBlogTimelineElement = async timelineElement => { const mutedBlogControls = div({ class: mutedBlogControlsClass, 'data-muted-blog-controls-mode': mutedBlogMode }, [ `You have muted ${mutedBlogMode} posts from ${name}!`, - dom('br'), + br(), button({ click: () => mutedBlogControls.remove() }, ['show posts anyway']) ]); timelineElement.prepend(mutedBlogControls); From bb6240f713359fc1b49dd4b917c8a90a0804ea65 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Wed, 12 Nov 2025 04:26:05 -0800 Subject: [PATCH 67/76] consolidate duplicates of updated `blogTimelineFilter` --- src/features/mute/index.js | 13 ++----------- src/utils/timeline_id.js | 12 ------------ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index 806ea355d4..bfe03b0ff2 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -9,15 +9,11 @@ import { getPreferences } from '../../utils/preferences.js'; import { anyBlogPostTimelineFilter, anyBlogTimelineFilter, - anyDraftsTimelineFilter, anyFlaggedReviewTimelineFilter, - anyPeeprTimelineFilter, - anyQueueTimelineFilter, blogTimelineFilter, inboxTimelineFilter, likesTimelineFilter, peeprLikesTimelineFilter, - peeprTimelineFilter, timelineSelector } from '../../utils/timeline_id.js'; import { controlsClass as showOriginalsControlsClass } from '../show_originals/index.js'; @@ -54,9 +50,7 @@ const getNameAndUuid = async timelineElement => { const { blog: { name, uuid } } = await timelineObject(post); if ( blogTimelineFilter(name)(timelineElement) || - peeprTimelineFilter(name)(timelineElement) || - blogTimelineFilter(uuid)(timelineElement) || - peeprTimelineFilter(uuid)(timelineElement) + blogTimelineFilter(uuid)(timelineElement) ) { return { name, uuid }; } @@ -83,9 +77,6 @@ const processBlogTimelineElement = async timelineElement => { const shouldDisable = timelineElement => Boolean( userBlogNames.some(name => blogTimelineFilter(name)(timelineElement)) || - userBlogNames.some(name => peeprTimelineFilter(name)(timelineElement)) || - anyDraftsTimelineFilter(timelineElement) || - anyQueueTimelineFilter(timelineElement) || anyFlaggedReviewTimelineFilter(timelineElement) || likesTimelineFilter(timelineElement) || userBlogNames.some(name => peeprLikesTimelineFilter(name)(timelineElement)) || @@ -113,7 +104,7 @@ const processTimelines = async timelineElements => { timelineElement.setAttribute(activeAttribute, ''); lengthenTimeline(timelineElement); - if (anyBlogTimelineFilter(timelineElement) || anyPeeprTimelineFilter(timelineElement)) { + if (anyBlogTimelineFilter(timelineElement)) { await processBlogTimelineElement(timelineElement).catch(console.log); } } diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 8b02727740..be4f551be0 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -57,15 +57,6 @@ export const anyBlogPostTimelineFilter = ({ dataset: { timeline, timelineId } }) timeline?.match(exactly(`/v2/blog/${anyBlogName}/posts/${anyPostId}/permalink`)) || timelineId?.match(exactly(`peepr-posts-${anyBlogName}-${anyPostId}-undefined-undefined-undefined-undefined-undefined`)); -// includes viewing a blog on and searching/tag searching a blog on peepr -export const anyPeeprTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timelineId?.startsWith('peepr-posts-'); - -// includes viewing a blog on and searching/tag searching a blog on peepr -export const peeprTimelineFilter = blogName => - ({ dataset: { timeline, timelineId } }) => - timelineId?.startsWith(`peepr-posts-${blogName}-`); - export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => timeline === '/v2/timeline?which=blog_subscriptions' || which === 'blog_subscriptions' || @@ -92,9 +83,6 @@ export const peeprLikesTimelineFilter = blogName => ({ dataset: { timeline, timelineId } }) => timelineId === `peepr-likes-${blogName}`; -export const anyPeeprLikesTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timelineId?.match(exactly(`peepr-likes-${anyBlogName}`)); - export const inboxTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.startsWith('/v2/user/inbox'); From 9469fa24c7f9e6ae7a0b06f7c7ad01ca7c7b3e2c Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Wed, 12 Nov 2025 04:35:42 -0800 Subject: [PATCH 68/76] update anyPostPermalinkTimelineFilter --- src/features/mute/index.js | 4 ++-- src/utils/timeline_id.js | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index bfe03b0ff2..a251130461 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -7,9 +7,9 @@ import { keyToCss } from '../../utils/css_map.js'; import { br, button, div, form, input, label } from '../../utils/dom.js'; import { getPreferences } from '../../utils/preferences.js'; import { - anyBlogPostTimelineFilter, anyBlogTimelineFilter, anyFlaggedReviewTimelineFilter, + anyPostPermalinkTimelineFilter, blogTimelineFilter, inboxTimelineFilter, likesTimelineFilter, @@ -81,7 +81,7 @@ const shouldDisable = timelineElement => Boolean( likesTimelineFilter(timelineElement) || userBlogNames.some(name => peeprLikesTimelineFilter(name)(timelineElement)) || inboxTimelineFilter(timelineElement) || - anyBlogPostTimelineFilter(timelineElement) + anyPostPermalinkTimelineFilter(timelineElement) ); const processTimelines = async timelineElements => { diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index be4f551be0..664cfe723c 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -3,6 +3,7 @@ const createSelector = (...components) => `:is(${components.filter(Boolean).join export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; const startsWith = string => `^${string}`; +const endsWith = string => `${string}$`; const exactly = string => `^${string}$`; const anyBlogName = '[a-z0-9-]{1,32}'; const anyPostId = '[0-9]{1,20}'; @@ -53,9 +54,12 @@ export const blogPostsTimelineFilter = blogName => // Matches any blog's main posts timeline, not including subpages such as drafts or in-blog searches. export const anyBlogPostsTimelineFilter = blogPostsTimelineFilter(anyBlogName); -export const anyBlogPostTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(exactly(`/v2/blog/${anyBlogName}/posts/${anyPostId}/permalink`)) || - timelineId?.match(exactly(`peepr-posts-${anyBlogName}-${anyPostId}-undefined-undefined-undefined-undefined-undefined`)); +export const postPermalinkTimelineFilter = postId => + ({ dataset: { timeline, timelineId } }) => + timeline?.match(endsWith(`posts/${postId}/permalink`)) || + timelineId?.match(exactly(peeprPostsTimelineId({ blog: anyBlogName, postId }))); + +export const anyPostPermalinkTimelineFilter = postPermalinkTimelineFilter(anyPostId); export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => timeline === '/v2/timeline?which=blog_subscriptions' || From 48b9cf81bee74fb1d5d2cbad53f6b0255d94b506 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Tue, 3 Feb 2026 11:43:09 -0800 Subject: [PATCH 69/76] format --- src/features/mute/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index a251130461..74eaec8b29 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -14,7 +14,7 @@ import { inboxTimelineFilter, likesTimelineFilter, peeprLikesTimelineFilter, - timelineSelector + timelineSelector, } from '../../utils/timeline_id.js'; import { controlsClass as showOriginalsControlsClass } from '../show_originals/index.js'; import { userBlogNames } from '../../utils/user.js'; @@ -68,7 +68,7 @@ const processBlogTimelineElement = async timelineElement => { const mutedBlogControls = div({ class: mutedBlogControlsClass, 'data-muted-blog-controls-mode': mutedBlogMode }, [ `You have muted ${mutedBlogMode} posts from ${name}!`, br(), - button({ click: () => mutedBlogControls.remove() }, ['show posts anyway']) + button({ click: () => mutedBlogControls.remove() }, ['show posts anyway']), ]); timelineElement.prepend(mutedBlogControls); timelineElement.querySelector(`.${showOriginalsControlsClass}`)?.after(mutedBlogControls); @@ -81,7 +81,7 @@ const shouldDisable = timelineElement => Boolean( likesTimelineFilter(timelineElement) || userBlogNames.some(name => peeprLikesTimelineFilter(name)(timelineElement)) || inboxTimelineFilter(timelineElement) || - anyPostPermalinkTimelineFilter(timelineElement) + anyPostPermalinkTimelineFilter(timelineElement), ); const processTimelines = async timelineElements => { @@ -144,7 +144,7 @@ const processPosts = async function (postElements) { relevantBlogUuid === timelineBlogUuid ? mutedBlogControlsHiddenAttribute : hiddenAttribute, - '' + '', ); const isRebloggedPost = contributedContentOriginal @@ -181,7 +181,7 @@ const onMeatballButtonClicked = function ({ currentTarget }) { const createRadioElement = value => label({}, [ `Hide ${value} posts`, - input({ type: 'radio', name: 'muteOption', value }) + input({ type: 'radio', name: 'muteOption', value }), ]); const formElement = form( @@ -189,8 +189,8 @@ const onMeatballButtonClicked = function ({ currentTarget }) { [ createRadioElement('all'), createRadioElement('original'), - createRadioElement('reblogged') - ] + createRadioElement('reblogged'), + ], ); formElement.elements.muteOption.value = currentMode; @@ -202,16 +202,16 @@ const onMeatballButtonClicked = function ({ currentTarget }) { buttons: [ modalCancelButton, button({ class: 'blue', click: () => unmuteUser(uuid) }, ['Unmute']), - input({ type: 'submit', form: formElement.id, class: 'red', value: 'Update Mode' }) - ] + input({ type: 'submit', form: formElement.id, class: 'red', value: 'Update Mode' }), + ], }) : showModal({ title: `Mute ${name}?`, message: [formElement], buttons: [ modalCancelButton, - input({ type: 'submit', form: formElement.id, class: 'red', value: 'Mute' }) - ] + input({ type: 'submit', form: formElement.id, class: 'red', value: 'Mute' }), + ], }); }; @@ -227,7 +227,7 @@ const muteUser = event => { browser.storage.local.set({ [mutedBlogEntriesStorageKey]: Object.entries(mutedBlogs), - [blogNamesStorageKey]: blogNames + [blogNamesStorageKey]: blogNames, }); hideModal(); @@ -243,7 +243,7 @@ const unmuteUser = uuid => { export const onStorageChanged = async function (changes, areaName) { const { [blogNamesStorageKey]: blogNamesChanges, - [mutedBlogEntriesStorageKey]: mutedBlogsEntriesChanges + [mutedBlogEntriesStorageKey]: mutedBlogsEntriesChanges, } = changes; if (Object.keys(changes).some(key => key.startsWith('mute.preferences') && changes[key].oldValue !== undefined)) { @@ -273,12 +273,12 @@ export const main = async function () { registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, - onclick: onMeatballButtonClicked + onclick: onMeatballButtonClicked, }); registerBlogMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, - onclick: onMeatballButtonClicked + onclick: onMeatballButtonClicked, }); onNewPosts.addListener(processPosts); }; From 867117377da59340f0952179590eed8bf66f3362 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 14 Feb 2026 12:45:59 -0800 Subject: [PATCH 70/76] organize imports --- src/features/mute/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/features/mute/index.js b/src/features/mute/index.js index 74eaec8b29..ca9e172587 100644 --- a/src/features/mute/index.js +++ b/src/features/mute/index.js @@ -1,11 +1,11 @@ -import { filterPostElements, postSelector, getTimelineItemWrapper } from '../../utils/interface.js'; -import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../../utils/meatballs.js'; -import { showModal, hideModal, modalCancelButton } from '../../utils/modals.js'; -import { timelineObject } from '../../utils/react_props.js'; -import { onNewPosts, pageModifications } from '../../utils/mutations.js'; import { keyToCss } from '../../utils/css_map.js'; import { br, button, div, form, input, label } from '../../utils/dom.js'; +import { filterPostElements, getTimelineItemWrapper, postSelector } from '../../utils/interface.js'; +import { registerBlogMeatballItem, registerMeatballItem, unregisterBlogMeatballItem, unregisterMeatballItem } from '../../utils/meatballs.js'; +import { hideModal, modalCancelButton, showModal } from '../../utils/modals.js'; +import { onNewPosts, pageModifications } from '../../utils/mutations.js'; import { getPreferences } from '../../utils/preferences.js'; +import { timelineObject } from '../../utils/react_props.js'; import { anyBlogTimelineFilter, anyFlaggedReviewTimelineFilter, @@ -16,8 +16,8 @@ import { peeprLikesTimelineFilter, timelineSelector, } from '../../utils/timeline_id.js'; -import { controlsClass as showOriginalsControlsClass } from '../show_originals/index.js'; import { userBlogNames } from '../../utils/user.js'; +import { controlsClass as showOriginalsControlsClass } from '../show_originals/index.js'; const meatballButtonId = 'mute'; const meatballButtonLabel = data => `Mute options for ${data.name ?? getVisibleBlog(data).name}`; From 8bc94556acfab687ab00b5cd706fc9753e2dda3d Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Fri, 6 Mar 2026 08:42:16 -0800 Subject: [PATCH 71/76] fix timeline id util --- src/utils/timeline_id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index f3bb5c034e..2a52d30df9 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -57,7 +57,7 @@ export const anyBlogPostsTimelineFilter = blogPostsTimelineFilter(anyBlogName); export const postPermalinkTimelineFilter = postId => ({ dataset: { timeline, timelineId } }) => timeline?.match(endsWith(`posts/${postId}/permalink`)) || - timelineId?.match(exactly(peeprPostsTimelineId({ blog: anyBlogName, postId }))); + timelineId?.match(exactly(peeprPostsTimelineId({ blogName: anyBlogName, postId }))); export const anyPostPermalinkTimelineFilter = postPermalinkTimelineFilter(anyPostId); From 90ccc431b46cffcf083ce428ad6967a9352ed8f7 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Wed, 29 Apr 2026 20:15:21 -0400 Subject: [PATCH 72/76] update timeline id util --- src/utils/timeline_id.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 01990650e6..ad577f17c5 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -85,11 +85,15 @@ export const anyFlaggedReviewTimelineFilter = ({ dataset: { timeline, timelineId export const likesTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline === 'v2/user/likes' || timelineId === 'likes' || + timelineId === 'likes-asc' || + timelineId === 'likes-desc' || timelineId?.match(exactly(`likes-${uuidV4}`)); export const peeprLikesTimelineFilter = blogName => ({ dataset: { timeline, timelineId } }) => - timelineId === `peepr-likes-${blogName}`; + timelineId === `peepr-likes-${blogName}` || + timelineId === `peepr-likes-${blogName}-asc` || + timelineId === `peepr-likes-${blogName}-desc`; export const inboxTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.startsWith('/v2/user/inbox'); From 1b1ac5864274d047e0fa3b8637c586760b24fe65 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Mon, 4 May 2026 12:45:53 -0700 Subject: [PATCH 73/76] basic component preference migration Co-Authored-By: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/mute/feature.json | 5 +- src/features/mute/options/index.css | 81 +++++-------- src/features/mute/options/index.html | 38 ------- src/features/mute/options/index.js | 164 ++++++++++++++++++--------- 4 files changed, 141 insertions(+), 147 deletions(-) delete mode 100644 src/features/mute/options/index.html diff --git a/src/features/mute/feature.json b/src/features/mute/feature.json index 4cb51eba1c..562d35ff6f 100644 --- a/src/features/mute/feature.json +++ b/src/features/mute/feature.json @@ -9,9 +9,8 @@ "relatedTerms": [ "Blacklist", "Filter", "Savior" ], "preferences": { "manageBlockedPosts": { - "type": "iframe", - "label": "Manage muted users", - "src": "/features/mute/options/index.html" + "type": "component", + "src": "/features/mute/options/index.js" }, "checkTrail": { "type": "checkbox", diff --git a/src/features/mute/options/index.css b/src/features/mute/options/index.css index 67fe507429..5dbcb2d639 100644 --- a/src/features/mute/options/index.css +++ b/src/features/mute/options/index.css @@ -1,74 +1,47 @@ -:root { - --black: 21, 20, 25; - --white: 255, 255, 255; - --grey: 207, 207, 216; - --accent: 10, 132, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --black: 251, 251, 254; - --white: 66, 65, 77; - --grey: 91, 91, 102; - --accent: 54, 213, 255; - } -} - -html { - font-size: 14px; - scrollbar-color: rgb(var(--grey)) transparent; - scrollbar-width: thin; - overflow-y: hidden; -} - -body { - background-color: rgb(var(--white)); - color: rgb(var(--black)); - font-family: "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; - font-size: 100%; - -webkit-user-select: none; - user-select: none; -} - -header { - margin-bottom: 1em; - font-weight: bold; +:host { + display: block; } #muted-blogs { + --border-width: 1px; + + display: flex; + flex-direction: column; + row-gap: var(--border-width); padding: 0; + border-radius: var(--border-radius-medium); margin: 0; -} + overflow: hidden; -#muted-blogs:not(:empty) { - border-bottom: 1px solid rgb(var(--grey)); + background-color: var(--border-color); + list-style-type: none; + outline: var(--border-width) solid var(--border-color); + outline-offset: 0; } -#muted-blogs:not(:empty) + #no-muted-blogs { +#muted-blogs:empty, #muted-blogs:not(:empty) + #no-muted-blogs { display: none; } -.muted-blog { - padding: 1ch 0; - border-top: 1px solid rgb(var(--grey)); +h3 + #muted-blogs { + margin-block-start: var(--space-medium); +} +.muted-blog { display: flex; flex-direction: row; justify-content: space-between; align-items: center; -} + column-gap: var(--space-small); + padding-block: var(--space-small); + padding-inline-start: var(--space-medium); + padding-inline-end: var(--space-small); + margin: 0; -.muted-blog > a { - flex: 1; + background-color: var(--background-color-canvas); } -.muted-blog button { - padding: 0; - border: none; - - appearance: none; - background-color: transparent; - color: rgb(var(--accent)); - cursor: pointer; - font-weight: bold; +.muted-blog > a { + margin-inline-end: auto; + min-width: 0; } diff --git a/src/features/mute/options/index.html b/src/features/mute/options/index.html deleted file mode 100644 index fc3708e428..0000000000 --- a/src/features/mute/options/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - XKit: Manage muted blogs - - - - - - - - - -
              -
              Muted blogs:
              -
                -
                - - No muted blogs! Use the meatballs menu on any post to mute the blog that posted it. - -
                -
                - - diff --git a/src/features/mute/options/index.js b/src/features/mute/options/index.js index 43c42cae67..987c8a9dfe 100644 --- a/src/features/mute/options/index.js +++ b/src/features/mute/options/index.js @@ -1,72 +1,132 @@ -const mutedBlogList = document.getElementById('muted-blogs'); -const mutedBlogTemplate = document.getElementById('muted-blog'); +import { CustomElement, fetchStyleSheets } from '../../../action/components/index.js'; + +const localName = 'mute-muted-users-management'; + +const templateDocument = new DOMParser().parseFromString(` + +`, 'text/html'); + +const adoptedStyleSheets = await fetchStyleSheets([ + '/lib/modern-normalize.css', + '/action/acorn.css', + './index.css', +].map(import.meta.resolve)); const blogNamesStorageKey = 'mute.blogNames'; const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; -const getBlogNames = async () => { - const { [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey); - return blogNames; -}; -const getMutedBlogs = async () => { - const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); - return Object.fromEntries(mutedBlogsEntries ?? []); -}; -const setMutedBlogs = mutedBlogs => - browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); +class MuteMutedUsersElement extends CustomElement { + /** @type {HTMLUListElement} */ #mutedBlogList; + /** @type {HTMLTemplateElement} */ #mutedBlogTemplate; -const unmuteUser = async function ({ currentTarget }) { - const mutedBlogs = await getMutedBlogs(); + constructor () { + super(templateDocument, adoptedStyleSheets); - const { uuid } = currentTarget.closest('li').dataset; + this.#mutedBlogList = this.shadowRoot.getElementById('muted-blogs'); + this.#mutedBlogTemplate = this.shadowRoot.getElementById('muted-blog'); + } + + getBlogNames = async () => { + const { [blogNamesStorageKey]: blogNames = {} } = await browser.storage.local.get(blogNamesStorageKey); + return blogNames; + }; + + getMutedBlogs = async () => { + const { [mutedBlogsEntriesStorageKey]: mutedBlogsEntries } = await browser.storage.local.get(mutedBlogsEntriesStorageKey); + return Object.fromEntries(mutedBlogsEntries ?? []); + }; + + setMutedBlogs = mutedBlogs => + browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); + + unmuteUser = async ({ currentTarget }) => { + const mutedBlogs = await this.getMutedBlogs(); - delete mutedBlogs[uuid]; - setMutedBlogs(mutedBlogs); -}; + const { uuid } = currentTarget.closest('li').dataset; -const updateMode = async function (event) { - const mutedBlogs = await getMutedBlogs(); + delete mutedBlogs[uuid]; + this.setMutedBlogs(mutedBlogs); + }; - const { uuid } = event.target.closest('li').dataset; - const { value } = event.target; + updateMode = async event => { + const mutedBlogs = await this.getMutedBlogs(); - mutedBlogs[uuid] = value; - setMutedBlogs(mutedBlogs); -}; + const { uuid } = event.target.closest('li').dataset; + const { value } = event.target; -const renderMutedBlogs = async function () { - const mutedBlogs = await getMutedBlogs(); - const blogNames = await getBlogNames(); + mutedBlogs[uuid] = value; + this.setMutedBlogs(mutedBlogs); + }; - mutedBlogList.replaceChildren(...Object.entries(mutedBlogs).map(([uuid, mode]) => { - const templateClone = mutedBlogTemplate.content.cloneNode(true); - const li = templateClone.querySelector('li'); - const linkElement = templateClone.querySelector('a'); - const modeSelect = templateClone.querySelector('select'); - const unmuteButton = templateClone.querySelector('button'); + renderMutedBlogs = async () => { + const mutedBlogs = await this.getMutedBlogs(); + const blogNames = await this.getBlogNames(); - li.dataset.uuid = uuid; + this.#mutedBlogList.replaceChildren(...Object.entries(mutedBlogs).map(([uuid, mode]) => { + const templateClone = this.#mutedBlogTemplate.content.cloneNode(true); + const li = templateClone.querySelector('li'); + const linkElement = templateClone.querySelector('a'); + const modeSelect = templateClone.querySelector('select'); + const unmuteButton = templateClone.querySelector('button'); - linkElement.textContent = blogNames[uuid] ?? uuid; - linkElement.href = `https://www.tumblr.com/blog/view/${uuid}`; + li.dataset.uuid = uuid; - modeSelect.value = mode; - modeSelect.addEventListener('change', updateMode); + linkElement.textContent = blogNames[uuid] ?? uuid; + linkElement.href = `https://www.tumblr.com/blog/view/${uuid}`; - unmuteButton.addEventListener('click', unmuteUser); + modeSelect.value = mode; + modeSelect.addEventListener('change', this.updateMode); - return templateClone; - })); -}; + unmuteButton.addEventListener('click', this.unmuteUser); -browser.storage.onChanged.addListener((changes, areaName) => { - if ( - areaName === 'local' && - (Object.keys(changes).includes(mutedBlogsEntriesStorageKey) || - Object.keys(changes).includes(blogNamesStorageKey)) - ) { - renderMutedBlogs(); + return templateClone; + })); + }; + + onStorageChanged = changes => { + if ( + Object.keys(changes).includes(mutedBlogsEntriesStorageKey) || + Object.keys(changes).includes(blogNamesStorageKey) + ) { + this.renderMutedBlogs(); + } + }; + + connectedCallback () { + this.ariaLabel ||= 'Manage muted users'; + this.role ||= 'listitem'; + this.slot ||= 'preferences'; + + browser.storage.local.onChanged.addListener(this.onStorageChanged); + this.renderMutedBlogs(); } -}); +} + +customElements.define(localName, MuteMutedUsersElement); -renderMutedBlogs(); +export default () => document.createElement(localName); From acbec2e16f8198dd22c2f4294a43b0a1c0d9ac1e Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 17 May 2026 03:33:44 -0700 Subject: [PATCH 74/76] add unmute confirmation dialogue Co-Authored-By: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/mute/options/index.js | 39 +++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/features/mute/options/index.js b/src/features/mute/options/index.js index 987c8a9dfe..143d6ff6e6 100644 --- a/src/features/mute/options/index.js +++ b/src/features/mute/options/index.js @@ -28,6 +28,16 @@ const templateDocument = new DOMParser().parseFromString(` + `, 'text/html'); @@ -43,12 +53,14 @@ const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries'; class MuteMutedUsersElement extends CustomElement { /** @type {HTMLUListElement} */ #mutedBlogList; /** @type {HTMLTemplateElement} */ #mutedBlogTemplate; + /** @type {HTMLTemplateElement} */ #unmuteTemplate; constructor () { super(templateDocument, adoptedStyleSheets); this.#mutedBlogList = this.shadowRoot.getElementById('muted-blogs'); this.#mutedBlogTemplate = this.shadowRoot.getElementById('muted-blog'); + this.#unmuteTemplate = this.shadowRoot.getElementById('unmute-template'); } getBlogNames = async () => { @@ -64,13 +76,32 @@ class MuteMutedUsersElement extends CustomElement { setMutedBlogs = mutedBlogs => browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) }); - unmuteUser = async ({ currentTarget }) => { + /** @type {(event: PointerEvent) => Promise} */ + onUnmuteButtonClick = async ({ currentTarget }) => { const mutedBlogs = await this.getMutedBlogs(); + const blogNames = await this.getBlogNames(); const { uuid } = currentTarget.closest('li').dataset; - delete mutedBlogs[uuid]; - this.setMutedBlogs(mutedBlogs); + const unmuteTemplateClone = this.#unmuteTemplate.content.cloneNode(true); + + const unmuteDialog = unmuteTemplateClone.getElementById('unmute-dialog'); + const unmuteBlognameDisplay = unmuteTemplateClone.getElementById('unmute-blogname'); + const unmuteCancelButton = unmuteTemplateClone.getElementById('unmute-cancel'); + const unmuteConfirmButton = unmuteTemplateClone.getElementById('unmute-confirm'); + + unmuteBlognameDisplay.textContent = blogNames[uuid]; + + unmuteDialog.addEventListener('close', () => unmuteDialog.remove()); + unmuteCancelButton.addEventListener('click', () => unmuteDialog.close()); + unmuteConfirmButton.addEventListener('click', async () => { + delete mutedBlogs[uuid]; + this.setMutedBlogs(mutedBlogs); + unmuteDialog.close(); + }); + + this.shadowRoot.append(unmuteDialog); + unmuteDialog.showModal(); }; updateMode = async event => { @@ -102,7 +133,7 @@ class MuteMutedUsersElement extends CustomElement { modeSelect.value = mode; modeSelect.addEventListener('change', this.updateMode); - unmuteButton.addEventListener('click', this.unmuteUser); + unmuteButton.addEventListener('click', this.onUnmuteButtonClick); return templateClone; })); From b87f3b0f85c3cd9aa7c73f8806df26fa228f1e14 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 17 May 2026 03:40:59 -0700 Subject: [PATCH 75/76] fix mode selector can't reference event after await statement --- src/features/mute/options/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/mute/options/index.js b/src/features/mute/options/index.js index 143d6ff6e6..642176c323 100644 --- a/src/features/mute/options/index.js +++ b/src/features/mute/options/index.js @@ -104,11 +104,11 @@ class MuteMutedUsersElement extends CustomElement { unmuteDialog.showModal(); }; - updateMode = async event => { + updateMode = async ({ currentTarget }) => { const mutedBlogs = await this.getMutedBlogs(); - const { uuid } = event.target.closest('li').dataset; - const { value } = event.target; + const { uuid } = currentTarget.closest('li').dataset; + const { value } = currentTarget; mutedBlogs[uuid] = value; this.setMutedBlogs(mutedBlogs); From aeb21b172565d763fe78def65b06e83d937309b6 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 17 May 2026 03:42:46 -0700 Subject: [PATCH 76/76] tweak unmute confirmation wording? idk --- src/features/mute/options/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/mute/options/index.js b/src/features/mute/options/index.js index 642176c323..1d07e38ec8 100644 --- a/src/features/mute/options/index.js +++ b/src/features/mute/options/index.js @@ -30,8 +30,8 @@ const templateDocument = new DOMParser().parseFromString(`