Skip to content

Commit

Permalink
add mass unprivate
Browse files Browse the repository at this point in the history
  • Loading branch information
marcustyphoon committed Nov 24, 2024
1 parent 01fe042 commit 977a7e7
Showing 1 changed file with 83 additions and 44 deletions.
127 changes: 83 additions & 44 deletions src/features/mass_privater.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { dom } from '../utils/dom.js';
import { megaEdit } from '../utils/mega_editor.js';
import { showModal, modalCancelButton, modalCompleteButton, hideModal, showErrorModal } from '../utils/modals.js';
import { addSidebarItem, removeSidebarItem } from '../utils/sidebar.js';
import { apiFetch } from '../utils/tumblr_helpers.js';
import { apiFetch, createEditRequestBody } from '../utils/tumblr_helpers.js';
import { userBlogs } from '../utils/user.js';

const getPostsFormId = 'xkit-mass-privater-get-posts';
Expand Down Expand Up @@ -49,8 +49,8 @@ const createNowString = () => {
return `${YYYY}-${MM}-${DD}T${hh}:${mm}`;
};

const showInitialPrompt = async () => {
const initialForm = dom('form', { id: getPostsFormId }, { submit: event => confirmInitialPrompt(event).catch(showErrorModal) }, [
const showInitialPrompt = async (makePrivate) => {
const initialForm = dom('form', { id: getPostsFormId }, { submit: event => confirmInitialPrompt(event, makePrivate).catch(showErrorModal) }, [
dom('label', null, null, [
'Posts on blog:',
dom('select', { name: 'blog', required: true }, null, userBlogs.map(createBlogOption))
Expand All @@ -72,7 +72,7 @@ const showInitialPrompt = async () => {
}

showModal({
title: 'Select posts to make private',
title: `Select posts to make ${makePrivate ? 'private' : 'public'}`,
message: [initialForm],
buttons: [
modalCancelButton,
Expand All @@ -81,7 +81,7 @@ const showInitialPrompt = async () => {
});
};

const confirmInitialPrompt = async event => {
const confirmInitialPrompt = async (event, makePrivate) => {
event.preventDefault();

const { submitter } = event;
Expand All @@ -100,7 +100,7 @@ const confirmInitialPrompt = async event => {
.map(tag => tag.trim().toLowerCase())
.filter(Boolean);

if (tags.length) {
if (makePrivate && tags.length) {
const getTagCount = async tag => {
const { response: { totalPosts } } = await apiFetch(`/v2/blog/${uuid}/posts`, { method: 'GET', queryParams: { tag } });
return totalPosts ?? 0;
Expand All @@ -123,20 +123,20 @@ const confirmInitialPrompt = async event => {

const message = tags.length
? [
'Every published post on ',
`Every ${makePrivate ? 'published' : 'private'} post on `,
createBlogSpan(name),
' from before ',
beforeElement,
' tagged ',
...elementsAsList(tags.map(createTagSpan), 'or'),
' will be set to private.'
` will be set to ${makePrivate ? 'private' : 'public'}.`
]
: [
'Every published post on ',
`Every ${makePrivate ? 'published' : 'private'} post on `,
createBlogSpan(name),
' from before ',
beforeElement,
' will be set to private.'
` will be set to ${makePrivate ? 'private' : 'public'}.`
];

showModal({
Expand All @@ -147,8 +147,8 @@ const confirmInitialPrompt = async event => {
dom(
'button',
{ class: 'red' },
{ click: () => privatePosts({ uuid, name, tags, before }).catch(showErrorModal) },
['Private them!']
{ click: () => editPosts({ makePrivate, uuid, name, tags, before }).catch(showErrorModal) },
[makePrivate ? 'Private them!' : 'Unprivate them!']
)
]
});
Expand Down Expand Up @@ -178,84 +178,116 @@ const showPostsNotFound = ({ name }) =>
buttons: [modalCompleteButton]
});

const privatePosts = async ({ uuid, name, tags, before }) => {
const editPosts = async ({ makePrivate, uuid, name, tags, before }) => {
const gatherStatus = dom('span', null, null, ['Gathering posts...']);
const privateStatus = dom('span');
const editStatus = dom('span');

showModal({
title: 'Making posts private...',
title: `Making posts ${makePrivate ? 'private' : 'public'}...`,
message: [
dom('small', null, null, ['Do not navigate away from this page.']),
'\n\n',
gatherStatus,
privateStatus
editStatus
]
});

let fetchedPosts = 0;
const filteredPostIdsSet = new Set();
const filteredPostsMap = new Map();

const collect = async resource => {
while (resource) {
await Promise.all([
apiFetch(resource).then(({ response }) => {
response.posts
.filter(({ canEdit }) => canEdit === true)
.filter(({ state }) => state === 'published')
.filter(({ state }) => state === (makePrivate ? 'published' : 'private'))
.filter(({ timestamp }) => timestamp < before)
.forEach(({ id }) => filteredPostIdsSet.add(id));
.filter(postData =>
tags.length
? postData.tags.some(tag => tags.includes(tag.toLowerCase()))
: true
)
.forEach((postData) => filteredPostsMap.set(postData.id, postData));

fetchedPosts += response.posts.length;

resource = response.links?.next?.href;

gatherStatus.textContent = `Found ${filteredPostIdsSet.size} posts (checked ${fetchedPosts})${resource ? '...' : '.'}`;
gatherStatus.textContent = `Found ${filteredPostsMap.size} posts (checked ${fetchedPosts})${resource ? '...' : '.'}`;
}),
sleep(1000)
]);
}
};

if (tags.length) {
if (makePrivate && tags.length) {
for (const tag of tags) {
await collect(`/v2/blog/${uuid}/posts?${$.param({ tag, before, limit: 50 })}`);
}
} else {
await collect(`/v2/blog/${uuid}/posts?${$.param({ before, limit: 50 })}`);
}
const filteredPostIds = [...filteredPostIdsSet];

if (filteredPostIds.length === 0) {
if (filteredPostsMap.size === 0) {
showPostsNotFound({ name });
return;
}

let privatedCount = 0;
let privatedFailCount = 0;
const filteredPostIds = [...filteredPostsMap.keys()];
const filteredPosts = [...filteredPostsMap.values()];

while (filteredPostIds.length !== 0) {
const postIds = filteredPostIds.splice(0, 100);
let successCount = 0;
let failCount = 0;

if (privateStatus.textContent === '') privateStatus.textContent = '\nPrivating posts...';
if (makePrivate) {
while (filteredPostIds.length !== 0) {
const postIds = filteredPostIds.splice(0, 100);

await Promise.all([
megaEdit(postIds, { mode: 'private' }).then(() => {
privatedCount += postIds.length;
}).catch(() => {
privatedFailCount += postIds.length;
}).finally(() => {
privateStatus.textContent = `\nPrivated ${privatedCount} posts... ${privatedFailCount ? `(failed: ${privatedFailCount})` : ''}`;
}),
sleep(1000)
]);
if (editStatus.textContent === '') editStatus.textContent = '\nPrivating posts...';

await Promise.all([
megaEdit(postIds, { mode: 'private' }).then(() => {
successCount += postIds.length;
}).catch(() => {
failCount += postIds.length;
}).finally(() => {
editStatus.textContent = `\nPrivated ${successCount} posts... ${failCount ? `(failed: ${failCount})` : ''}`;
}),
sleep(1000)
]);
}
} else {
while (filteredPosts.length !== 0) {
const postData = filteredPosts.shift();

if (editStatus.textContent === '') editStatus.textContent = '\nUnprivating posts...';

await Promise.all([
apiFetch(`/v2/blog/${uuid}/posts/${postData.id}`, {
method: 'PUT',
body: {
...createEditRequestBody(postData),
state: 'published'
}
}).then(() => {
successCount++;
}).catch(() => {
failCount++;
}).finally(() => {
editStatus.textContent = `\nUnprivated ${successCount} posts... ${failCount ? `(failed: ${failCount})` : ''}`;
}),
sleep(1000)
]);
}
}

await sleep(1000);

showModal({
title: 'All done!',
message: [
`Privated ${privatedCount} posts${privatedFailCount ? ` (failed: ${privatedFailCount})` : ''}.\n`,
`${makePrivate ? 'Privated' : 'Unprivated'} ${successCount} posts${failCount ? ` (failed: ${failCount})` : ''}.\n`,
'Refresh the page to see the result.'
],
buttons: [
Expand All @@ -268,11 +300,18 @@ const privatePosts = async ({ uuid, name, tags, before }) => {
const sidebarOptions = {
id: 'mass-privater',
title: 'Mass Privater',
rows: [{
label: 'Make posts private',
onclick: showInitialPrompt,
carrot: true
}],
rows: [
{
label: 'Private posts',
onclick: () => showInitialPrompt(true),
carrot: true
},
{
label: 'Unprivate posts',
onclick: () => showInitialPrompt(false),
carrot: true
}
],
visibility: () => /^\/blog\/[^/]+\/?$/.test(location.pathname)
};

Expand Down

0 comments on commit 977a7e7

Please sign in to comment.