Skip to content
Draft
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
12 changes: 10 additions & 2 deletions src/features/mutual_checker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { onNewPosts, onNewNotifications, pageModifications } from '../../utils/m
import { getPreferences } from '../../utils/preferences.js';
import { blogData, notificationObject, timelineObject } from '../../utils/react_props.js';
import { buildSvg } from '../../utils/remixicon.js';
import { followingTimelineSelector } from '../../utils/timeline_id.js';
import { followingTimelineFilter } from '../../utils/timeline_id.js';
import { timelineTabs } from '../../utils/timeline_tabs.js';
import { apiFetch } from '../../utils/tumblr_helpers.js';
import { primaryBlogName, userBlogNames } from '../../utils/user.js';

Expand Down Expand Up @@ -38,7 +39,7 @@ const styleElement = buildStyle(`
isolation: isolate;
}

${followingTimelineSelector} [${hiddenAttribute}] {
.xkit-timeline-controls[data-mode="mutual-checker-only-mutuals"] ~ div > [${hiddenAttribute}] {
content: linear-gradient(transparent, transparent);
height: 0;
}
Expand Down Expand Up @@ -73,6 +74,8 @@ const alreadyProcessed = postElement =>
postElement.querySelector(`.${mutualIconClass}`);

const addIcons = function (postElements) {
timelineTabs.process();

filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => {
if (alreadyProcessed(postElement)) return;

Expand Down Expand Up @@ -146,6 +149,9 @@ export const main = async function () {
onNewPosts.addListener(addIcons);
pageModifications.register(`${keyToCss('blogCard')} ${keyToCss('blogCardBlogLink')} > a`, addBlogCardIcons);

if (showOnlyMutuals) {
timelineTabs.register({ id: 'mutual-checker-only-mutuals', label: 'From Mutuals', timelineFilter: followingTimelineFilter });
}
if (showOnlyMutualNotifications) {
document.documentElement.append(onlyMutualsStyleElement);
onNewNotifications.addListener(processNotifications);
Expand Down Expand Up @@ -185,4 +191,6 @@ export const clean = async function () {
$(`.${mutualsClass}`).removeClass(mutualsClass);
$(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute);
$(`.${mutualIconClass}`).remove();

timelineTabs.unregister('mutual-checker-only-mutuals');
};
37 changes: 1 addition & 36 deletions src/features/show_originals/index.css
Original file line number Diff line number Diff line change
@@ -1,39 +1,4 @@
[data-show-originals="on"] ~ div > [data-show-originals-hidden] {
.xkit-timeline-controls[data-mode="show-originals"] ~ div > [data-show-originals-hidden] {
content: linear-gradient(transparent, transparent);
height: 0;
}

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

.xkit-show-originals-controls {
color: var(--blog-title-color, rgb(var(--white-on-dark)));
display: flex;
font-weight: 700;
margin-bottom: 20px;
}

.xkit-show-originals-controls > a {
flex: 1;
padding: 14px 16px;
text-align: center;
text-decoration: none;
text-transform: capitalize;
}

.xkit-show-originals-controls > a {
cursor: pointer;
}

.xkit-show-originals-controls > a[data-mode="disabled"] {
cursor: not-allowed;
opacity: 0.65;
}

[data-show-originals="on"].xkit-show-originals-controls > a[data-mode="on"],
[data-show-originals="off"].xkit-show-originals-controls > a[data-mode="off"],
.xkit-show-originals-controls > a[data-mode="disabled"] {
box-shadow: inset 0 -3px 0 var(--blog-link-color, rgb(var(--deprecated-accent)));
color: var(--blog-link-color, rgb(var(--deprecated-accent)));
}
65 changes: 10 additions & 55 deletions src/features/show_originals/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { keyToCss } from '../../utils/css_map.js';
import { a, div } from '../../utils/dom.js';
import { filterPostElements, getTimelineItemWrapper } from '../../utils/interface.js';
import { translate } from '../../utils/language_data.js';
import { onNewPosts } from '../../utils/mutations.js';
Expand All @@ -10,11 +9,11 @@ import {
anyBlogPostsTimelineFilter,
blogPostsTimelineFilter,
blogSubsTimelineFilter,
timelineSelector,
anyCommunityTimelineFilter,
communitiesTimelineFilter,
blogpackTimelineFilter,
} from '../../utils/timeline_id.js';
import { timelineTabs } from '../../utils/timeline_tabs.js';
import { userBlogs } from '../../utils/user.js';

const hiddenAttribute = 'data-show-originals-hidden';
Expand All @@ -23,7 +22,7 @@ const controlsClass = 'xkit-show-originals-controls';

const channelSelector = `${keyToCss('bar')} ~ *`;

const storageKey = 'show_originals.savedModes';
// const storageKey = 'show_originals.savedModes';
const includeFiltered = true;

let showOwnReblogs;
Expand All @@ -32,45 +31,6 @@ let showReblogsOfNotFollowing;
let whitelist;
let disabledBlogs;

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

const createButton = (buttonText, onclick, mode) =>
a({ 'data-mode': mode, click: onclick }, [buttonText]);

const addControls = async (timelineElement, location) => {
const controls = div({ class: controlsClass });
controls.dataset.location = location;

timelineElement.prepend(controls);

const handleClick = async ({ currentTarget: { dataset: { mode } } }) => {
controls.dataset.showOriginals = mode;

const { [storageKey]: savedModes = {} } = await browser.storage.local.get(storageKey);
savedModes[location] = mode;
browser.storage.local.set({ [storageKey]: savedModes });
};

const onButton = createButton(translate('Original Posts'), handleClick, 'on');
const offButton = createButton(translate('All posts'), handleClick, 'off');
const disabledButton = createButton(translate('All posts'), null, 'disabled');

if (location === 'disabled') {
controls.append(disabledButton);
} else {
controls.append(onButton, offButton);

lengthenTimeline(timelineElement);
const { [storageKey]: savedModes = {} } = await browser.storage.local.get(storageKey);
const mode = savedModes[location] ?? 'on';
controls.dataset.showOriginals = mode;
}
};

const getLocation = timelineElement => {
const isBlog =
anyBlogPostsTimelineFilter(timelineElement) && !timelineElement.matches(channelSelector);
Expand All @@ -86,22 +46,14 @@ const getLocation = timelineElement => {
return Object.keys(on).find(location => on[location]);
};

const processTimelines = async () => {
[...document.querySelectorAll(timelineSelector)].forEach(async timelineElement => {
const location = getLocation(timelineElement);

const currentControls = [...timelineElement.children]
.find(element => element.matches(`.${controlsClass}`));

if (currentControls?.dataset?.location !== location) {
currentControls?.remove();
if (location) addControls(timelineElement, location);
}
});
const timelineTabFilter = timelineElement => {
const location = getLocation(timelineElement);
if (location === 'disabled') return 'disabled';
return Boolean(location);
};

const processPosts = async function (postElements) {
processTimelines();
timelineTabs.process();

filterPostElements(postElements, { includeFiltered })
.forEach(async postElement => {
Expand Down Expand Up @@ -135,6 +87,7 @@ export const main = async function () {
disabledBlogs = [...whitelist, ...showOwnReblogs ? nonGroupUserBlogs : []];

onNewPosts.addListener(processPosts);
timelineTabs.register({ id: 'show-originals', label: translate('Original Posts'), timelineFilter: timelineTabFilter });
};

export const clean = async function () {
Expand All @@ -143,6 +96,8 @@ export const clean = async function () {
$(`[${hiddenAttribute}]`).removeAttr(hiddenAttribute);
$(`.${lengthenedClass}`).removeClass(lengthenedClass);
$(`.${controlsClass}`).remove();

timelineTabs.unregister('show-originals');
};

export const stylesheet = true;
135 changes: 135 additions & 0 deletions src/utils/timeline_tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { keyToCss } from './css_map.js';
import { a, div } from './dom.js';
import { buildStyle } from './interface.js';
import { translate } from './language_data.js';
import { timelineSelector } from './timeline_id.js';

const controlsClass = 'xkit-timeline-controls';
const lengthenedClass = 'xkit-timeline-controls-lengthened';

const cachedIdStringAttribute = 'xkit-timeline-controls-cached-id';

// Remove outdated elements when loading module
$(`.${controlsClass}`).remove();

const styleElement = buildStyle(`
.${lengthenedClass} {
min-height: 100vh;
}

.${controlsClass} {
color: var(--blog-title-color, rgb(var(--white-on-dark)));
display: flex;
font-weight: 700;
margin-bottom: 20px;
}

.${controlsClass} > a {
flex: 1;
padding: 14px 16px;
text-align: center;
text-decoration: none;
text-transform: capitalize;
cursor: pointer;
}

.${controlsClass} > a.disabled {
cursor: not-allowed;
opacity: 0.65;
}

.${controlsClass} > a.active {
box-shadow: inset 0 -3px 0 var(--blog-link-color, rgb(var(--deprecated-accent)));
color: var(--blog-link-color, rgb(var(--deprecated-accent)));
}
`);
document.documentElement.append(styleElement);

export const timelineTabs = Object.freeze({
registered: new Map(),

register (options) {
if (this.registered.has(options.id) === false) {
this.registered.set(options.id, options);
this.clean();
this.process();
}
},

unregister (id) {
this.registered.delete(id);
this.clean();
this.process();
},

process () {
[...document.querySelectorAll(timelineSelector)].forEach(async timelineElement => {
const { dataset: { timeline, timelineId } } = timelineElement;
const idString = timelineId
? `timelineId:${timelineId}`
: timeline
? `timeline:${timeline}`
: undefined;
const cachedIdString = timelineElement.getAttribute(cachedIdStringAttribute);

if (idString !== cachedIdString) {
timelineElement.setAttribute(cachedIdStringAttribute, idString);
timelineElement.querySelector(`.${controlsClass}`)?.remove();

const controls = div({ class: controlsClass });

const onclick = async ({ currentTarget }) => {
const { dataset: { mode } } = currentTarget;
if (!currentTarget.classList.contains('disabled')) {
controls.dataset.mode = mode;

buttons.forEach(button => button.classList.toggle('active', button.dataset.mode === mode));

// const { [storageKey]: savedModes = {} } = await browser.storage.local.get(storageKey);
// savedModes[location] = mode;
// browser.storage.local.set({ [storageKey]: savedModes });
}
};

const createButton = ({ id, label, shown }) =>
a({ 'data-mode': id, click: onclick, class: shown === 'disabled' ? 'disabled' : '' }, [label]);

const buttons = [...this.registered.values()]
.map(({ id, timelineFilter, label }) => ({
id,
label,
shown: timelineFilter(timelineElement),
}))
.filter(({ shown }) => shown)
.sort((a, b) => a.id.localeCompare(b.id))
.map(createButton);

if (buttons.length) {
buttons.push(createButton({ id: '', label: translate('All posts'), shown: true }));

// temp
buttons.at(-1).click();

controls.replaceChildren(...buttons);
timelineElement.prepend(controls);

if (!timelineElement.querySelector(keyToCss('manualPaginatorButtons'))) {
timelineElement.classList.add(lengthenedClass);
}

// const { [storageKey]: savedModes = {} } = await browser.storage.local.get(storageKey);
// const mode = savedModes[location] ?? 'on';
// controls.dataset.showOriginals = mode;
}
}
});
},

clean () {
[...document.querySelectorAll(timelineSelector)].forEach(async timelineElement => {
timelineElement.removeAttribute(cachedIdStringAttribute);
timelineElement.querySelector(`.${controlsClass}`)?.remove();
});
},

});