Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
93 changes: 86 additions & 7 deletions src/features/notificationblock.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
import { buildStyle } from '../utils/interface.js';
import { registerMeatballItem, unregisterMeatballItem } from '../utils/meatballs.js';
import { onNewNotifications } from '../utils/mutations.js';
import { showModal, hideModal, modalCancelButton } from '../utils/modals.js';
import { showModal, hideModal, modalCancelButton, modalCompleteButton } from '../utils/modals.js';
import { dom } from '../utils/dom.js';
import { userBlogNames } from '../utils/user.js';
import { apiFetch } from '../utils/tumblr_helpers.js';
import { userBlogNames, userBlogs } from '../utils/user.js';
import { apiFetch, navigate } from '../utils/tumblr_helpers.js';
import { notificationObject } from '../utils/react_props.js';

const storageKey = 'notificationblock.blockedPostTargetIDs';
const uuidsStorageKey = 'notificationblock.uuids';
const toOpenStorageKey = 'notificationblock.toOpen';
const meatballButtonBlockId = 'notificationblock-block';
const meatballButtonBlockLabel = 'Block notifications';
const meatballButtonUnblockId = 'notificationblock-unblock';
const meatballButtonUnblockLabel = 'Unblock notifications';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

let blockedPostTargetIDs;
let uuids = {};

const userBlogsToSearch = userBlogs
.filter(({ posts }) => posts)
.sort((a, b) => b.posts - a.posts);

const findUuid = async id => {
if (uuids[id]) return;
uuids[id] = false;
for (const { uuid } of userBlogsToSearch) {
const delay = sleep(500);
try {
await apiFetch(`/v2/blog/${uuid}/posts/${id}`);
uuids[id] = uuid;
break;
} catch (e) {
await delay;
}
}
await browser.storage.local.set({ [uuidsStorageKey]: uuids });
};

const backgroundFindUuids = async () => {
const idsMissingUuids = blockedPostTargetIDs.filter(id => uuids[id] === undefined).reverse();
for (const id of idsMissingUuids) {
await findUuid(id);
}
};

const styleElement = buildStyle();
const buildCss = () => `:is(${blockedPostTargetIDs.map(rootId => `[data-target-root-post-id="${rootId}"]`).join(', ')
Expand All @@ -38,10 +70,11 @@ const muteNotificationsMessage = [
];

const onButtonClicked = async function ({ currentTarget }) {
const { id, rebloggedRootId, blog: { uuid } } = currentTarget.__timelineObjectData;
const { id, rebloggedRootId, blog: { uuid }, rebloggedRootUuid } = currentTarget.__timelineObjectData;
const { response: { muted } } = await apiFetch(`/v2/blog/${uuid}/posts/${id}`);

const rootId = rebloggedRootId || id;
const rootUuid = rebloggedRootUuid || uuid;
const shouldBlockNotifications = blockedPostTargetIDs.includes(rootId) === false;

const title = shouldBlockNotifications
Expand All @@ -61,8 +94,18 @@ const onButtonClicked = async function ({ currentTarget }) {
? 'red'
: 'blue';
const saveNotificationPreference = shouldBlockNotifications
? () => { blockedPostTargetIDs.push(rootId); browser.storage.local.set({ [storageKey]: blockedPostTargetIDs }); }
: () => browser.storage.local.set({ [storageKey]: blockedPostTargetIDs.filter(blockedId => blockedId !== rootId) });
? () => {
blockedPostTargetIDs.push(rootId);
browser.storage.local.set({ [storageKey]: blockedPostTargetIDs });
uuids[rootId] = rootUuid;
browser.storage.local.set({ [uuidsStorageKey]: uuids });
}
: () => {
blockedPostTargetIDs = blockedPostTargetIDs.filter(blockedId => blockedId !== rootId);
browser.storage.local.set({ [storageKey]: blockedPostTargetIDs });
delete uuids[rootId];
browser.storage.local.set({ [uuidsStorageKey]: uuids });
};

showModal({
title,
Expand Down Expand Up @@ -94,20 +137,56 @@ const unblockPostFilter = async ({ id, rebloggedRootId }) => {
};

export const onStorageChanged = (changes, areaName) => {
if (Object.keys(changes).includes(storageKey)) {
const { [storageKey]: blockedPostChanges, [uuidsStorageKey]: uuidsChanges } = changes;

if (uuidsChanges) {
({ newValue: uuids } = uuidsChanges);
}

if (blockedPostChanges) {
blockedPostTargetIDs = changes[storageKey].newValue;
styleElement.textContent = buildCss();
}
};

export const main = async function () {
({ [storageKey]: blockedPostTargetIDs = [] } = await browser.storage.local.get(storageKey));
({ [uuidsStorageKey]: uuids = {} } = await browser.storage.local.get(uuidsStorageKey));

backgroundFindUuids();

styleElement.textContent = buildCss();
document.documentElement.append(styleElement);
onNewNotifications.addListener(processNotifications);

registerMeatballItem({ id: meatballButtonBlockId, label: meatballButtonBlockLabel, onclick: onButtonClicked, postFilter: blockPostFilter });
registerMeatballItem({ id: meatballButtonUnblockId, label: meatballButtonUnblockLabel, onclick: onButtonClicked, postFilter: unblockPostFilter });

const { [toOpenStorageKey]: toOpen } = await browser.storage.local.get(toOpenStorageKey);
if (toOpen) {
browser.storage.local.remove(toOpenStorageKey);

const { blockedPostID } = toOpen;
try {
showModal({
title: 'NotificationBlock',
message: [`Searching for post ${blockedPostID} on your blogs. Please wait...`]
});
await findUuid(blockedPostID);
if (!uuids[blockedPostID]) {
throw new Error();
}
const { response: { blog: { name } } } = await apiFetch(`/v2/blog/${uuids[blockedPostID]}/info`);
hideModal();
navigate(`/${name}/${blockedPostID}`);
} catch (e) {
showModal({
title: 'NotificationBlock',
message: [`Failed to find and open post ${blockedPostID}! It may not be one of your original posts.`],
buttons: [modalCompleteButton]
});
}
}
};

export const clean = async function () {
Expand Down
9 changes: 8 additions & 1 deletion src/features/notificationblock.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@
"background_color": "#f5223c"
},
"help": "https://github.com/AprilSylph/XKit-Rewritten/wiki/Features#notificationblock",
"relatedTerms": [ "activity", "mute notifications", "notes" ]
"relatedTerms": [ "activity", "mute notifications", "notes" ],
"preferences": {
"manageBlockedPosts": {
"type": "iframe",
"label": "Manage posts with blocked notifications",
"src": "/features/notificationblock/options/index.html"
}
}
}
69 changes: 69 additions & 0 deletions src/features/notificationblock/options/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
: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;
}

#notification-blocked-count {
font-weight: bold;
}

#notification-blocked-posts {
padding: 0;
border-bottom: 1px solid rgb(var(--grey));
margin: 0;
}

.notification-blocked-post {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 1ch 0;
border-top: 1px solid rgb(var(--grey));
}

.notification-blocked-post button {
padding: 0;
border: none;

appearance: none;
background-color: transparent;
color: rgb(var(--accent));
cursor: pointer;
font-weight: bold;
}
28 changes: 28 additions & 0 deletions src/features/notificationblock/options/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XKit: Manage posts with blocked notifications</title>
<link rel="icon" href="/icons/128.png" type="image/png">
<link rel="stylesheet" href="/lib/normalize.min.css">
<link rel="stylesheet" href="index.css">
<script src="/action/resize_frames.js" defer></script>
<script src="/lib/browser-polyfill.min.js" defer></script>
<script src="index.js" defer></script>
</head>
<body>
<template id="notification-blocked-post">
<li class="notification-blocked-post">
<a href="javascript:void(0);"><span></span></a>
<button data-post-id="">Remove</button>
</li>
</template>
<main>
<header>
<span id="notification-blocked-count"></span>
</header>
<ul id="notification-blocked-posts"></ul>
</main>
</body>
</html>
50 changes: 50 additions & 0 deletions src/features/notificationblock/options/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const postsBlockedCount = document.getElementById('notification-blocked-count');
const blockedPostList = document.getElementById('notification-blocked-posts');
const blockedPostTemplate = document.getElementById('notification-blocked-post');

const storageKey = 'notificationblock.blockedPostTargetIDs';
const toOpenStorageKey = 'notificationblock.toOpen';

const unblockPost = async function ({ currentTarget }) {
let { [storageKey]: blockedPostRootIDs = [] } = await browser.storage.local.get(storageKey);

blockedPostRootIDs = blockedPostRootIDs.filter(id => id !== currentTarget.dataset.postId);
await browser.storage.local.set({ [storageKey]: blockedPostRootIDs });

currentTarget.remove();
};

const renderBlocked = async function () {
const { [storageKey]: blockedPostRootIDs = [] } = await browser.storage.local.get(storageKey);

postsBlockedCount.textContent = `${blockedPostRootIDs.length} ${blockedPostRootIDs.length === 1 ? 'post' : 'posts'} with blocked notifications`;
blockedPostList.textContent = '';

for (const blockedPostID of blockedPostRootIDs) {
const templateClone = blockedPostTemplate.content.cloneNode(true);
const anchorElement = templateClone.querySelector('a');
const spanElement = templateClone.querySelector('span');
const unblockButton = templateClone.querySelector('button');

spanElement.textContent = blockedPostID;
unblockButton.dataset.postId = blockedPostID;
unblockButton.addEventListener('click', unblockPost);

anchorElement.addEventListener('click', async () => {
await browser.storage.local.set({
[toOpenStorageKey]: { blockedPostID }
});
window.open('https://www.tumblr.com/');
});

blockedPostList.append(templateClone);
}
};

browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'local' && Object.keys(changes).includes(storageKey)) {
renderBlocked();
}
});

renderBlocked();