mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
feat: ✨ Add settings page to configure account and preferences
This commit is contained in:
parent
633ff184e3
commit
1691daa000
21 changed files with 687 additions and 183 deletions
25
composables/LinkedSSO.ts
Normal file
25
composables/LinkedSSO.ts
Normal 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;
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
56
composables/Settings.ts
Normal 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;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue