2023-02-16 12:51:54 +03:00
|
|
|
import { proxy, subscribe } from 'valtio';
|
2023-02-02 12:29:57 +03:00
|
|
|
import { subscribeKey } from 'valtio/utils';
|
2023-01-14 14:42:04 +03:00
|
|
|
|
2023-02-05 19:17:19 +03:00
|
|
|
import { api } from './api';
|
2023-12-30 13:13:56 +03:00
|
|
|
import isMastodonLinkMaybe from './isMastodonLinkMaybe';
|
2023-10-14 15:33:40 +03:00
|
|
|
import pmem from './pmem';
|
2023-12-22 04:54:50 +03:00
|
|
|
import rateLimit from './ratelimit';
|
2023-01-14 14:42:04 +03:00
|
|
|
import store from './store';
|
2023-12-30 13:13:56 +03:00
|
|
|
import unfurlMastodonLink from './unfurl-link';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-01-09 14:11:34 +03:00
|
|
|
const states = proxy({
|
2023-02-28 10:27:42 +03:00
|
|
|
appVersion: {},
|
2023-01-27 15:54:18 +03:00
|
|
|
// history: [],
|
|
|
|
prevLocation: null,
|
|
|
|
currentLocation: null,
|
2023-01-07 15:26:23 +03:00
|
|
|
statuses: {},
|
2023-01-10 14:59:02 +03:00
|
|
|
statusThreadNumber: {},
|
2022-12-10 12:14:48 +03:00
|
|
|
home: [],
|
2023-01-27 15:54:18 +03:00
|
|
|
// specialHome: [],
|
2022-12-10 12:14:48 +03:00
|
|
|
homeNew: [],
|
2023-01-27 06:47:30 +03:00
|
|
|
homeLast: null, // Last item in 'home' list
|
2022-12-10 12:14:48 +03:00
|
|
|
homeLastFetchTime: null,
|
|
|
|
notifications: [],
|
2023-10-11 14:07:36 +03:00
|
|
|
notificationsLast: null, // Last read notification
|
2022-12-10 12:14:48 +03:00
|
|
|
notificationsNew: [],
|
2023-02-12 12:38:50 +03:00
|
|
|
notificationsShowNew: false,
|
2022-12-10 12:14:48 +03:00
|
|
|
notificationsLastFetchTime: null,
|
|
|
|
reloadStatusPage: 0,
|
2023-10-02 12:51:36 +03:00
|
|
|
reloadGenericAccounts: {
|
|
|
|
id: null,
|
|
|
|
counter: 0,
|
|
|
|
},
|
2023-01-07 15:26:23 +03:00
|
|
|
spoilers: {},
|
2023-12-24 16:07:46 +03:00
|
|
|
spoilersMedia: {},
|
2023-01-07 15:26:23 +03:00
|
|
|
scrollPositions: {},
|
2023-02-23 11:45:53 +03:00
|
|
|
unfurledLinks: {},
|
2023-04-22 19:55:47 +03:00
|
|
|
statusQuotes: {},
|
2023-12-14 20:58:29 +03:00
|
|
|
statusFollowedTags: {},
|
2023-03-11 16:33:55 +03:00
|
|
|
accounts: {},
|
2023-09-01 10:40:00 +03:00
|
|
|
routeNotification: null,
|
2022-12-10 12:14:48 +03:00
|
|
|
// Modals
|
|
|
|
showCompose: false,
|
|
|
|
showSettings: false,
|
|
|
|
showAccount: false,
|
2023-03-07 19:32:33 +03:00
|
|
|
showAccounts: false,
|
2023-01-13 10:30:09 +03:00
|
|
|
showDrafts: false,
|
2023-01-29 10:23:53 +03:00
|
|
|
showMediaModal: false,
|
2023-02-16 12:51:54 +03:00
|
|
|
showShortcutsSettings: false,
|
2023-09-06 17:54:05 +03:00
|
|
|
showKeyboardShortcutsHelp: false,
|
2023-09-28 06:22:05 +03:00
|
|
|
showGenericAccounts: false,
|
2023-09-28 10:48:32 +03:00
|
|
|
showMediaAlt: false,
|
2023-02-16 12:51:54 +03:00
|
|
|
// Shortcuts
|
2023-10-11 14:07:36 +03:00
|
|
|
shortcuts: [],
|
2023-02-02 12:29:57 +03:00
|
|
|
// Settings
|
2023-01-14 14:42:04 +03:00
|
|
|
settings: {
|
2023-10-11 14:07:36 +03:00
|
|
|
autoRefresh: false,
|
|
|
|
shortcutsViewMode: null,
|
|
|
|
shortcutsColumnsMode: false,
|
|
|
|
boostsCarousel: true,
|
|
|
|
contentTranslation: true,
|
|
|
|
contentTranslationTargetLanguage: null,
|
|
|
|
contentTranslationHideLanguages: [],
|
|
|
|
contentTranslationAutoInline: false,
|
2023-12-27 18:33:59 +03:00
|
|
|
mediaAltGenerator: false,
|
2023-10-11 14:07:36 +03:00
|
|
|
cloakMode: false,
|
2023-01-14 14:42:04 +03:00
|
|
|
},
|
2022-12-10 12:14:48 +03:00
|
|
|
});
|
2023-02-02 12:29:57 +03:00
|
|
|
|
2023-01-09 14:11:34 +03:00
|
|
|
export default states;
|
|
|
|
|
2023-08-30 12:42:33 +03:00
|
|
|
export function initStates() {
|
|
|
|
// init all account based states
|
|
|
|
// all keys that uses store.account.get() should be initialized here
|
|
|
|
states.notificationsLast = store.account.get('notificationsLast') || null;
|
|
|
|
states.shortcuts = store.account.get('shortcuts') ?? [];
|
|
|
|
states.settings.autoRefresh =
|
|
|
|
store.account.get('settings-autoRefresh') ?? false;
|
|
|
|
states.settings.shortcutsViewMode =
|
|
|
|
store.account.get('settings-shortcutsViewMode') ?? null;
|
2023-11-02 07:59:52 +03:00
|
|
|
if (store.account.get('settings-shortcutsColumnsMode')) {
|
|
|
|
states.settings.shortcutsColumnsMode = true;
|
|
|
|
}
|
2023-08-30 12:42:33 +03:00
|
|
|
states.settings.boostsCarousel =
|
|
|
|
store.account.get('settings-boostsCarousel') ?? true;
|
|
|
|
states.settings.contentTranslation =
|
|
|
|
store.account.get('settings-contentTranslation') ?? true;
|
|
|
|
states.settings.contentTranslationTargetLanguage =
|
|
|
|
store.account.get('settings-contentTranslationTargetLanguage') || null;
|
|
|
|
states.settings.contentTranslationHideLanguages =
|
|
|
|
store.account.get('settings-contentTranslationHideLanguages') || [];
|
|
|
|
states.settings.contentTranslationAutoInline =
|
|
|
|
store.account.get('settings-contentTranslationAutoInline') ?? false;
|
2023-12-27 18:33:59 +03:00
|
|
|
states.settings.mediaAltGenerator =
|
|
|
|
store.account.get('settings-mediaAltGenerator') ?? false;
|
2023-08-30 12:42:33 +03:00
|
|
|
states.settings.cloakMode = store.account.get('settings-cloakMode') ?? false;
|
|
|
|
}
|
|
|
|
|
2023-02-02 12:29:57 +03:00
|
|
|
subscribeKey(states, 'notificationsLast', (v) => {
|
|
|
|
console.log('CHANGE', v);
|
|
|
|
store.account.set('notificationsLast', states.notificationsLast);
|
|
|
|
});
|
2023-03-09 08:20:01 +03:00
|
|
|
subscribe(states, (changes) => {
|
|
|
|
console.debug('STATES change', changes);
|
|
|
|
for (const [action, path, value, prevValue] of changes) {
|
2023-05-05 12:53:16 +03:00
|
|
|
if (path.join('.') === 'settings.autoRefresh') {
|
|
|
|
store.account.set('settings-autoRefresh', !!value);
|
|
|
|
}
|
2023-03-09 08:20:01 +03:00
|
|
|
if (path.join('.') === 'settings.boostsCarousel') {
|
|
|
|
store.account.set('settings-boostsCarousel', !!value);
|
|
|
|
}
|
|
|
|
if (path.join('.') === 'settings.shortcutsViewMode') {
|
|
|
|
store.account.set('settings-shortcutsViewMode', value);
|
|
|
|
}
|
|
|
|
if (path.join('.') === 'settings.contentTranslation') {
|
|
|
|
store.account.set('settings-contentTranslation', !!value);
|
|
|
|
}
|
2023-07-22 15:59:07 +03:00
|
|
|
if (path.join('.') === 'settings.contentTranslationAutoInline') {
|
|
|
|
store.account.set('settings-contentTranslationAutoInline', !!value);
|
|
|
|
}
|
2023-03-09 08:20:01 +03:00
|
|
|
if (path.join('.') === 'settings.contentTranslationTargetLanguage') {
|
|
|
|
console.log('SET', value);
|
|
|
|
store.account.set('settings-contentTranslationTargetLanguage', value);
|
|
|
|
}
|
2023-03-28 14:04:52 +03:00
|
|
|
if (/^settings\.contentTranslationHideLanguages/i.test(path.join('.'))) {
|
|
|
|
store.account.set(
|
|
|
|
'settings-contentTranslationHideLanguages',
|
|
|
|
states.settings.contentTranslationHideLanguages,
|
|
|
|
);
|
|
|
|
}
|
2023-12-27 18:33:59 +03:00
|
|
|
if (path.join('.') === 'settings.mediaAltGenerator') {
|
|
|
|
store.account.set('settings-mediaAltGenerator', !!value);
|
|
|
|
}
|
2023-03-09 08:20:01 +03:00
|
|
|
if (path?.[0] === 'shortcuts') {
|
|
|
|
store.account.set('shortcuts', states.shortcuts);
|
|
|
|
}
|
2023-04-23 07:08:41 +03:00
|
|
|
if (path.join('.') === 'settings.cloakMode') {
|
|
|
|
store.account.set('settings-cloakMode', !!value);
|
|
|
|
}
|
2023-02-16 12:51:54 +03:00
|
|
|
}
|
|
|
|
});
|
2023-01-14 14:42:04 +03:00
|
|
|
|
2023-02-02 05:30:16 +03:00
|
|
|
export function hideAllModals() {
|
|
|
|
states.showCompose = false;
|
|
|
|
states.showSettings = false;
|
|
|
|
states.showAccount = false;
|
2023-03-07 19:32:33 +03:00
|
|
|
states.showAccounts = false;
|
2023-02-02 05:30:16 +03:00
|
|
|
states.showDrafts = false;
|
|
|
|
states.showMediaModal = false;
|
2023-02-17 06:29:53 +03:00
|
|
|
states.showShortcutsSettings = false;
|
2023-09-06 17:54:05 +03:00
|
|
|
states.showKeyboardShortcutsHelp = false;
|
2023-09-17 07:54:48 +03:00
|
|
|
states.showGenericAccounts = false;
|
2023-09-28 10:48:32 +03:00
|
|
|
states.showMediaAlt = false;
|
2023-02-02 05:30:16 +03:00
|
|
|
}
|
|
|
|
|
2023-02-06 11:35:03 +03:00
|
|
|
export function statusKey(id, instance) {
|
2023-05-19 20:06:16 +03:00
|
|
|
if (!id) return;
|
2023-02-06 11:35:03 +03:00
|
|
|
return instance ? `${instance}/${id}` : id;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getStatus(statusID, instance) {
|
|
|
|
if (instance) {
|
|
|
|
const key = statusKey(statusID, instance);
|
|
|
|
return states.statuses[key];
|
|
|
|
}
|
|
|
|
return states.statuses[statusID];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function saveStatus(status, instance, opts) {
|
|
|
|
if (typeof instance === 'object') {
|
|
|
|
opts = instance;
|
|
|
|
instance = null;
|
|
|
|
}
|
2023-12-30 13:13:56 +03:00
|
|
|
const {
|
|
|
|
override = true,
|
|
|
|
skipThreading = false,
|
|
|
|
skipUnfurling = false,
|
|
|
|
} = opts || {};
|
2023-01-09 14:11:34 +03:00
|
|
|
if (!status) return;
|
2023-03-21 19:09:36 +03:00
|
|
|
const oldStatus = getStatus(status.id, instance);
|
|
|
|
if (!override && oldStatus) return;
|
2023-12-29 03:25:58 +03:00
|
|
|
queueMicrotask(() => {
|
|
|
|
const key = statusKey(status.id, instance);
|
|
|
|
if (oldStatus?._pinned) status._pinned = oldStatus._pinned;
|
|
|
|
// if (oldStatus?._filtered) status._filtered = oldStatus._filtered;
|
|
|
|
states.statuses[key] = status;
|
|
|
|
if (status.reblog) {
|
|
|
|
const key = statusKey(status.reblog.id, instance);
|
|
|
|
states.statuses[key] = status.reblog;
|
|
|
|
}
|
|
|
|
});
|
2023-01-10 14:59:02 +03:00
|
|
|
|
|
|
|
// THREAD TRAVERSER
|
|
|
|
if (!skipThreading) {
|
2023-12-27 18:32:52 +03:00
|
|
|
queueMicrotask(() => {
|
2023-02-06 11:35:03 +03:00
|
|
|
threadifyStatus(status, instance);
|
2023-01-10 17:58:54 +03:00
|
|
|
if (status.reblog) {
|
2023-12-29 03:25:58 +03:00
|
|
|
queueMicrotask(() => {
|
|
|
|
threadifyStatus(status.reblog, instance);
|
|
|
|
});
|
2023-01-10 17:58:54 +03:00
|
|
|
}
|
2023-01-10 14:59:02 +03:00
|
|
|
});
|
|
|
|
}
|
2023-12-30 13:13:56 +03:00
|
|
|
|
|
|
|
// UNFURLER
|
|
|
|
if (!skipUnfurling) {
|
|
|
|
queueMicrotask(() => {
|
|
|
|
unfurlStatus(status, instance);
|
|
|
|
});
|
|
|
|
}
|
2023-01-10 14:59:02 +03:00
|
|
|
}
|
|
|
|
|
2023-12-22 04:54:50 +03:00
|
|
|
function _threadifyStatus(status, propInstance) {
|
2023-02-06 11:35:03 +03:00
|
|
|
const { masto, instance } = api({ instance: propInstance });
|
2023-01-10 14:59:02 +03:00
|
|
|
// Return all statuses in the thread, via inReplyToId, if inReplyToAccountId === account.id
|
|
|
|
let fetchIndex = 0;
|
|
|
|
async function traverse(status, index = 0) {
|
|
|
|
const { inReplyToId, inReplyToAccountId } = status;
|
|
|
|
if (!inReplyToId || inReplyToAccountId !== status.account.id) {
|
|
|
|
return [status];
|
|
|
|
}
|
|
|
|
if (inReplyToId && inReplyToAccountId !== status.account.id) {
|
|
|
|
throw 'Not a thread';
|
|
|
|
// Possibly thread of replies by multiple people?
|
|
|
|
}
|
2023-02-19 16:16:23 +03:00
|
|
|
const key = statusKey(inReplyToId, instance);
|
|
|
|
let prevStatus = states.statuses[key];
|
2023-01-10 14:59:02 +03:00
|
|
|
if (!prevStatus) {
|
|
|
|
if (fetchIndex++ > 3) throw 'Too many fetches for thread'; // Some people revive old threads
|
2023-12-22 05:19:06 +03:00
|
|
|
await new Promise((r) => setTimeout(r, 500 * fetchIndex)); // Be nice to rate limits
|
2023-10-12 07:48:09 +03:00
|
|
|
// prevStatus = await masto.v1.statuses.$.select(inReplyToId).fetch();
|
2023-02-23 17:53:12 +03:00
|
|
|
prevStatus = await fetchStatus(inReplyToId, masto);
|
2023-02-06 11:35:03 +03:00
|
|
|
saveStatus(prevStatus, instance, { skipThreading: true });
|
2023-01-10 14:59:02 +03:00
|
|
|
}
|
|
|
|
// Prepend so that first status in thread will be index 0
|
|
|
|
return [...(await traverse(prevStatus, ++index)), status];
|
|
|
|
}
|
|
|
|
return traverse(status)
|
|
|
|
.then((statuses) => {
|
|
|
|
if (statuses.length > 1) {
|
|
|
|
console.debug('THREAD', statuses);
|
|
|
|
statuses.forEach((status, index) => {
|
2023-02-19 16:16:23 +03:00
|
|
|
const key = statusKey(status.id, instance);
|
|
|
|
states.statusThreadNumber[key] = index + 1;
|
2023-01-10 14:59:02 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.error(e, status);
|
|
|
|
});
|
2023-01-09 14:11:34 +03:00
|
|
|
}
|
2023-12-22 05:19:06 +03:00
|
|
|
export const threadifyStatus = rateLimit(_threadifyStatus, 100);
|
2023-02-23 17:53:12 +03:00
|
|
|
|
2023-12-30 13:13:56 +03:00
|
|
|
const fauxDiv = document.createElement('div');
|
|
|
|
export function unfurlStatus(status, instance) {
|
|
|
|
const { instance: currentInstance } = api();
|
|
|
|
const content = status.reblog?.content || status.content;
|
|
|
|
const hasLink = /<a/i.test(content);
|
|
|
|
if (hasLink) {
|
|
|
|
const sKey = statusKey(status?.reblog?.id || status?.id, instance);
|
|
|
|
fauxDiv.innerHTML = content;
|
|
|
|
const links = fauxDiv.querySelectorAll(
|
|
|
|
'a[href]:not(.u-url):not(.mention):not(.hashtag)',
|
|
|
|
);
|
|
|
|
[...links]
|
|
|
|
.filter((a) => {
|
|
|
|
const url = a.href;
|
|
|
|
const isPostItself = url === status.url || url === status.uri;
|
|
|
|
return !isPostItself && isMastodonLinkMaybe(url);
|
|
|
|
})
|
|
|
|
.forEach((a, i) => {
|
|
|
|
unfurlMastodonLink(currentInstance, a.href).then((result) => {
|
|
|
|
if (!result) return;
|
|
|
|
if (!sKey) return;
|
|
|
|
if (!Array.isArray(states.statusQuotes[sKey])) {
|
|
|
|
states.statusQuotes[sKey] = [];
|
|
|
|
}
|
|
|
|
if (!states.statusQuotes[sKey][i]) {
|
|
|
|
states.statusQuotes[sKey].splice(i, 0, result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-14 15:33:40 +03:00
|
|
|
const fetchStatus = pmem((statusID, masto) => {
|
2023-10-12 07:48:09 +03:00
|
|
|
return masto.v1.statuses.$select(statusID).fetch();
|
2023-02-23 17:53:12 +03:00
|
|
|
});
|