feat: Add settings page to configure account and preferences

This commit is contained in:
Jesse Wierzbinski 2024-06-18 20:16:28 -10:00
parent 633ff184e3
commit 1691daa000
No known key found for this signature in database
21 changed files with 687 additions and 183 deletions

25
composables/LinkedSSO.ts Normal file
View file

@ -0,0 +1,25 @@
import type { LysandClient } from "@lysand-org/client";
type SSOProvider = {
id: string;
name: string;
icon: string;
};
export const useLinkedSSO = (client: MaybeRef<LysandClient>) => {
if (!client) {
return ref([] as SSOProvider[]);
}
const output = ref([] as SSOProvider[]);
watchEffect(() => {
toValue(client)
?.get<SSOProvider[]>("/api/v1/sso")
.then((res) => {
output.value = res.data;
});
});
return output;
};

View file

@ -1,9 +1,11 @@
import type { LysandClient } from "@lysand-org/client";
import { SettingIds, type Settings } from "~/settings";
import type { Status } from "~/types/mastodon/status";
export const useNoteData = (
noteProp: MaybeRef<Status | undefined>,
client: Ref<LysandClient>,
settings: MaybeRef<Settings>,
) => {
const isReply = computed(() => !!toValue(noteProp)?.in_reply_to_id);
const isQuote = computed(() => !!toValue(noteProp)?.quote);
@ -15,11 +17,13 @@ export const useNoteData = (
? toValue(noteProp)?.reblog ?? toValue(noteProp)
: toValue(noteProp),
);
const showContentWarning = useSetting(SettingIds.ShowContentWarning);
const shouldHide = computed(
() =>
renderedNote.value?.sensitive ||
!!renderedNote.value?.spoiler_text ||
false,
(renderedNote.value?.sensitive ||
!!renderedNote.value?.spoiler_text ||
false) &&
(showContentWarning.value.value as boolean),
);
const mentions = useResolveMentions(
computed(() => renderedNote.value?.mentions ?? []),
@ -29,12 +33,15 @@ export const useNoteData = (
computed(() => renderedNote.value?.content ?? ""),
computed(() => renderedNote.value?.emojis ?? []),
mentions,
settings,
);
const loaded = computed(() => content.value !== null);
const reblogDisplayName = useParsedContent(
toValue(noteProp)?.account.display_name ?? "",
toValue(noteProp)?.account.emojis ?? [],
undefined,
settings,
);
const reblog = computed(() =>
isReblog.value && toValue(noteProp) && !isQuote.value

View file

@ -1,4 +1,5 @@
import { renderToString } from "vue/server-renderer";
import { SettingIds, type Settings, getSettingById } from "~/settings";
import type { Account } from "~/types/mastodon/account";
import type { Emoji } from "~/types/mastodon/emoji";
import MentionComponent from "../components/social-elements/notes/mention.vue";
@ -13,6 +14,7 @@ export const useParsedContent = (
content: MaybeRef<string>,
emojis: MaybeRef<Emoji[]>,
mentions: MaybeRef<Account[]> = ref([]),
settings: MaybeRef<Settings> = ref([]),
): Ref<string | null> => {
const result = ref(null as string | null);
@ -26,28 +28,35 @@ export const useParsedContent = (
const contentHtml = document.createElement("div");
contentHtml.innerHTML = toValue(content);
// Replace emoji shortcodes with images
const paragraphs = contentHtml.querySelectorAll("p");
const shouldRenderEmoji = getSettingById(
toValue(settings),
SettingIds.CustomEmojis,
)?.value;
for (const paragraph of paragraphs) {
paragraph.innerHTML = paragraph.innerHTML.replace(
/:([a-z0-9_-]+):/g,
(match, emoji) => {
const emojiData = toValue(emojis).find(
(e) => e.shortcode === emoji,
);
if (!emojiData) {
return match;
}
const image = document.createElement("img");
image.src = emojiData.url;
image.alt = `:${emoji}:`;
image.title = emojiData.shortcode;
image.className =
"h-6 align-text-bottom inline not-prose hover:scale-110 transition-transform duration-75 ease-in-out";
return image.outerHTML;
},
);
// Replace emoji shortcodes with images
if (shouldRenderEmoji) {
const paragraphs = contentHtml.querySelectorAll("p");
for (const paragraph of paragraphs) {
paragraph.innerHTML = paragraph.innerHTML.replace(
/:([a-z0-9_-]+):/g,
(match, emoji) => {
const emojiData = toValue(emojis).find(
(e) => e.shortcode === emoji,
);
if (!emojiData) {
return match;
}
const image = document.createElement("img");
image.src = emojiData.url;
image.alt = `:${emoji}:`;
image.title = emojiData.shortcode;
image.className =
"h-6 align-text-bottom inline not-prose hover:scale-110 transition-transform duration-75 ease-in-out";
return image.outerHTML;
},
);
}
}
// Replace links containing mentions with interactive mentions
@ -76,3 +85,36 @@ export const useParsedContent = (
return result;
};
export const useParsedAccount = (
account: MaybeRef<Account | undefined | null>,
settings: MaybeRef<Settings>,
) => {
const display_name = computed(() => toValue(account)?.display_name ?? "");
const note = computed(() => toValue(account)?.note ?? "");
const fields = computed(() => toValue(account)?.fields ?? []);
const emojis = computed(() => toValue(account)?.emojis ?? []);
const parsedDisplayName = useParsedContent(
display_name,
emojis,
undefined,
settings,
);
const parsedNote = useParsedContent(note, emojis, undefined, settings);
const parsedFields = computed(() =>
fields.value.map((field) => ({
...field,
value: useParsedContent(field.value, emojis, undefined, settings)
.value,
})),
);
return {
display_name: parsedDisplayName,
note: parsedNote,
fields: parsedFields,
};
};

56
composables/Settings.ts Normal file
View file

@ -0,0 +1,56 @@
import { StorageSerializers } from "@vueuse/core";
import {
type Setting,
type SettingIds,
type Settings,
parseFromJson,
settings,
} from "~/settings";
export const useSettings = () => {
return useLocalStorage<Settings>("lysand:settings", settings, {
serializer: {
read(raw) {
const json = StorageSerializers.object.read(raw);
return parseFromJson(json);
},
write(value) {
// key/value, with key being id and value being the value
const json = value.reduce(
(acc, setting) => {
acc[setting.id] = setting.value;
return acc;
},
{} as Record<string, unknown>,
);
return StorageSerializers.object.write(json);
},
},
});
};
export const useSetting = <T extends Setting = Setting>(id: SettingIds) => {
const settings = useSettings();
const setting: Ref<T> = ref<T>(
settings.value.find((s) => s.id === id) as T,
) as unknown as Ref<T>;
watch(settings, (newSettings) => {
setting.value = newSettings.find((s) => s.id === id) as T;
});
watch(setting, (newSetting) => {
settings.value = settings.value.map((s) =>
s.id === id ? newSetting : s,
) as Settings;
});
return setting;
};
export const getSetting = <T extends Setting = Setting>(id: SettingIds) => {
return settings.find((s) => s.id === id) as T;
};