diff --git a/src/features/mass_privater.js b/src/features/mass_privater.js index 1ff85440d..16caf6427 100644 --- a/src/features/mass_privater.js +++ b/src/features/mass_privater.js @@ -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'; @@ -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)) @@ -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, @@ -81,7 +81,7 @@ const showInitialPrompt = async () => { }); }; -const confirmInitialPrompt = async event => { +const confirmInitialPrompt = async (event, makePrivate) => { event.preventDefault(); const { submitter } = event; @@ -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; @@ -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({ @@ -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!'] ) ] }); @@ -178,22 +178,22 @@ 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) { @@ -201,53 +201,85 @@ const privatePosts = async ({ uuid, name, tags, before }) => { 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); @@ -255,7 +287,7 @@ const privatePosts = async ({ uuid, name, tags, before }) => { 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: [ @@ -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) };