Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Script: Mute #677

Draft
wants to merge 68 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
e53c223
initial commit
marcustyphoon Jul 27, 2022
696808d
fix invisible warning
marcustyphoon Aug 3, 2022
e3f52e4
redesign warning element state logic
marcustyphoon Aug 3, 2022
26560e9
my long state machine rewrite is perfectly constructed
marcustyphoon Aug 4, 2022
cef5fc2
cleanup
marcustyphoon Aug 4, 2022
a5b1a2e
Clear dismissed warnings in clean()
marcustyphoon Aug 4, 2022
7e106e3
Implement dataset-based non css method
marcustyphoon Aug 4, 2022
881f554
Get timeline UUIDs using timelineObject
marcustyphoon Aug 17, 2022
f3c4956
Fix options display with no muted blogs
marcustyphoon Aug 18, 2022
6cae4f2
Revamp storage keys
marcustyphoon Aug 18, 2022
45c8a33
Make styling slightly less terrible
marcustyphoon Aug 18, 2022
0d92caf
Fix async race condition
marcustyphoon Aug 18, 2022
0acce37
Merge branch 'master' into mute
marcustyphoon Sep 2, 2022
66ae4b3
Merge branch 'master' into mute
marcustyphoon Dec 7, 2022
79de138
exclude single post blog view
marcustyphoon Dec 12, 2022
991f4f8
rewrite on-blog warnings (works on all modes now)
marcustyphoon Dec 12, 2022
f545fa4
simplify onstoragechanged
marcustyphoon Dec 12, 2022
9bef00b
remove persistent unmuting
marcustyphoon Dec 12, 2022
2ff9c31
remove test/temporary code
marcustyphoon Dec 12, 2022
50a3098
optionally check the reblog trail
marcustyphoon Dec 12, 2022
9bbabc5
uhhh oops
marcustyphoon Dec 12, 2022
b85bb2e
fix random ads appearing when all posts hidden
marcustyphoon Dec 13, 2022
ea05c44
clarify meatballs menu
marcustyphoon Dec 14, 2022
83e649f
description that I dislike but it's something
marcustyphoon Dec 14, 2022
56f6e67
Merge branch 'master' into mute
marcustyphoon Jan 29, 2023
467eee6
fix warning element removal
marcustyphoon Feb 16, 2023
3530c9b
UI clarity tweaks; refactors
marcustyphoon Feb 16, 2023
a139f78
handle contributed content
marcustyphoon Feb 26, 2023
5e5ee8b
improve warning css
marcustyphoon Mar 7, 2023
562f53a
fix blog view exclusion logic
marcustyphoon Mar 15, 2023
70b852a
Merge remote-tracking branch 'upstream/master' into mute
marcustyphoon Mar 23, 2023
da4565e
customize meatballs menu label
marcustyphoon Mar 23, 2023
22f1b6e
Merge branch 'master' into mute
marcustyphoon Apr 30, 2023
432f846
enable mute menu in blog meatball menus
marcustyphoon May 1, 2023
1cacee5
Merge branch 'master' into mute
marcustyphoon May 5, 2023
fb8e016
combine storage set calls
marcustyphoon Aug 12, 2023
191a195
Merge remote-tracking branch 'upstream/master' into mute
marcustyphoon Aug 17, 2023
792d52d
Update mute.js
marcustyphoon Aug 17, 2023
8462228
Merge branch 'master' into mute
marcustyphoon Oct 27, 2023
6f3d7e7
fix mute dom child
marcustyphoon Feb 11, 2024
0ccb074
fix warning element DOM position with virtual scroller
marcustyphoon Mar 21, 2024
eeab105
Use CSS for placeholder text state
marcustyphoon Apr 29, 2024
5f6a294
refactor list item replacement
marcustyphoon Apr 29, 2024
8337f1e
refactor buttons
marcustyphoon Apr 29, 2024
6f075a3
prettier
marcustyphoon Apr 29, 2024
e0af289
refactor conditional modal
marcustyphoon Apr 29, 2024
1537140
Merge branch 'master' into mute
marcustyphoon Jun 22, 2024
edad23a
correctly parse communities post authors
marcustyphoon Jun 22, 2024
24c6515
partial data-timeline-id implementation
marcustyphoon Jun 22, 2024
0ad3757
Revert "partial data-timeline-id implementation"
marcustyphoon Jun 22, 2024
4baa9d0
Merge branch 'master' into mute
marcustyphoon Jun 23, 2024
6b4fd92
Merge branch 'master' into mute
marcustyphoon Jun 23, 2024
05c4cff
remove missed logging
marcustyphoon Sep 19, 2024
f8e8b8e
Merge branch 'master' into mute
marcustyphoon Sep 19, 2024
8b9a621
fix loop logic bug
marcustyphoon Sep 24, 2024
2586d34
fix (kinda) ui colors on patio
marcustyphoon Sep 24, 2024
87a674b
timeline id util: add likes
marcustyphoon Sep 24, 2024
3d9ced8
timeline id util: add single post view
marcustyphoon Sep 24, 2024
2cb086d
timeline id util: add channelselector
marcustyphoon Sep 24, 2024
0fa274a
implement data-timeline-id
marcustyphoon Sep 24, 2024
d3c48b1
arrow parens
marcustyphoon Sep 25, 2024
87c4626
various clarifying refactors
marcustyphoon Sep 25, 2024
a1e7f76
remove flicker on mute/unmute
marcustyphoon Sep 25, 2024
4710041
timeline id util: fix single post view
marcustyphoon Sep 26, 2024
dcf4be6
fix muted blog timelines with endless scrolling disabled
marcustyphoon Sep 26, 2024
262c76a
technically this will matter in november 2303.
marcustyphoon Sep 26, 2024
15b2f85
Merge branch 'master' into mute
marcustyphoon Jan 7, 2025
1757628
update capitalization of "onclick"
marcustyphoon Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/scripts/_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"mass_privater",
"mass_unliker",
"mirror_posts",
"mute",
"mutual_checker",
"no_recommended",
"notificationblock",
Expand Down
32 changes: 32 additions & 0 deletions src/scripts/mute.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.xkit-mute-active [data-mute-hidden] article,
:is([data-mute-mode="original"], [data-mute-mode="reblogged"]) ~ div [data-mute-hidden-on-blog] article {
display: none;
}

[data-mute-mode="all"] ~ div article {
visibility: hidden !important;
}
[data-mute-mode="all"] ~ div article :is(img, video, canvas) {
display: none;
}

.xkit-mute-lengthened {
min-height: 100vh;
}

.xkit-mute-warning {
padding: 25px 20px;
border-radius: 3px;
margin-bottom: var(--post-padding);

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);
}
260 changes: 260 additions & 0 deletions src/scripts/mute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
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';
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 = ({ blogName, name }) => `Mute options for ${blogName ?? name}`;

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';

const blogNamesStorageKey = 'mute.blogNames';
const mutedBlogsEntriesStorageKey = 'mute.mutedBlogEntries';

let checkTrail;
let contributedContentOriginal;

let blogNames = {};
let mutedBlogs = {};

const lengthenTimeline = timeline => {
if (!timeline.querySelector(keyToCss('manualPaginatorButtons'))) {
timeline.classList.add(lengthenedClass);
}
};

const getNameAndUuid = async (timelineElement, timeline) => {
const uuidOrName = timeline.split('/')?.[3];
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}`);
};

const processBlogSpecificTimeline = async (timelineElement, timeline) => {
const [name, uuid] = await getNameAndUuid(timelineElement, timeline);
const mode = mutedBlogs[uuid];

timelineElement.dataset.muteOnBlogUuid = uuid;

if (mode) {
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'])
]);
warningElement.dataset.muteMode = mode;

timelineElement.querySelector(keyToCss('scrollContainer')).before(warningElement);
}
};

const processTimelines = async (timelineElements) => {
for (const timelineElement of [...new Set(timelineElements)]) {
const { timeline, muteProcessedTimeline } = timelineElement.dataset;

const alreadyProcessed = timeline === muteProcessedTimeline;
if (alreadyProcessed) return;

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);

if (timeline.startsWith('/v2/blog/')) {
await processBlogSpecificTimeline(timelineElement, timeline);
}
}
}
};

const updateStoredName = (uuid, name) => {
blogNames[uuid] = name;
Object.keys(blogNames).forEach(uuid => {
if (!mutedBlogs[uuid]) {
delete blogNames[uuid];
}
});
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: { 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 = contributedContentOriginal
? rebloggedRootUuid && !content.length
: rebloggedRootUuid;

const originalUuid = isRebloggedPost ? rebloggedRootUuid : uuid;
const reblogUuid = isRebloggedPost ? uuid : null;

if (['all', 'original'].includes(mutedBlogs[originalUuid])) {
getTimelineItemWrapper(postElement).setAttribute(
originalUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute,
''
);
}
if (['all', 'reblogged'].includes(mutedBlogs[reblogUuid])) {
getTimelineItemWrapper(postElement).setAttribute(
reblogUuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute,
''
);
}

if (checkTrail) {
for (const { blog } of trail) {
if (['all'].includes(mutedBlogs[blog?.uuid])) {
getTimelineItemWrapper(postElement).setAttribute(
blog?.uuid === currentBlogViewUuid ? onBlogHiddenAttribute : hiddenAttribute,
''
);
}
}
}
});
};

const onMeatballButtonClicked = function ({ currentTarget }) {
const { name, uuid } = currentTarget?.__timelineObjectData?.blog || currentTarget?.__blogData;

const currentMode = mutedBlogs[uuid];

const createRadioElement = value =>
dom('label', null, null, [
`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 }, [
createRadioElement('all'),
createRadioElement('original'),
createRadioElement('reblogged')
]);

form.elements.muteOption.value = currentMode;

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' })
]
marcustyphoon marked this conversation as resolved.
Show resolved Hide resolved
});
};

const muteUser = event => {
event.preventDefault();

const { name, uuid } = event.currentTarget.dataset;
const { value } = event.currentTarget.elements.muteOption;
if (value === '') return;

mutedBlogs[uuid] = value;
blogNames[uuid] = name;

browser.storage.local.set({
[mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs),
[blogNamesStorageKey]: blogNames
});

hideModal();
};

const unmuteUser = uuid => {
delete mutedBlogs[uuid];
browser.storage.local.set({ [mutedBlogsEntriesStorageKey]: Object.entries(mutedBlogs) });

hideModal();
};

export const onStorageChanged = async function (changes, areaName) {
const {
[blogNamesStorageKey]: blogNamesChanges,
[mutedBlogsEntriesStorageKey]: mutedBlogsEntriesChanges
} = changes;

if (
Object.keys(changes).some(key => key.startsWith('mute') && changes[key].oldValue !== undefined) ||
mutedBlogsEntriesChanges
) {
clean().then(main);
return;
}

if (blogNamesChanges) {
({ newValue: blogNames } = blogNamesChanges);
}
};

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);
mutedBlogs = Object.fromEntries(mutedBlogsEntries ?? []);

registerMeatballItem({
id: meatballButtonId,
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);

$(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute);
$(`[${onBlogHiddenAttribute}]`).removeAttr(onBlogHiddenAttribute);
$(`.${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-on-blog-uuid');
};

export const stylesheet = true;
27 changes: 27 additions & 0 deletions src/scripts/mute.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"title": "Mute",
"description": "Hide users' posts all over Tumblr",
"icon": {
"class_name": "ri-volume-mute-line",
"color": "white",
"background_color": "#C20044"
},
"relatedTerms": [ "Blacklist", "Filter", "Savior" ],
"preferences": {
"manageBlockedPosts": {
"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
},
"contributedContentOriginal": {
"type": "checkbox",
"label": "Treat reblogs with contributed content as original",
"default": false
}
}
}
Loading