Merge branch 'main' into permissions-management
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
ci/woodpecker/pr/build-and-publish Pipeline was successful

This commit is contained in:
SauceyRed 2025-08-07 06:39:37 +02:00
commit 9fe067ec5a
Signed by: sauceyred
GPG key ID: 2BF92EB6D8A5CCA7
55 changed files with 1106 additions and 387 deletions

View file

@ -1,21 +0,0 @@
import { render } from "vue";
import ContextMenu from "~/components/UserInterface/ContextMenu.vue";
import type { ContextMenuItem } from "~/types/interfaces";
export default (e: PointerEvent | MouseEvent, menuItems: ContextMenuItem[]) => {
console.log("Rendering new context menu");
const menuContainer = document.createElement("div");
console.log("hello");
menuContainer.id = "context-menu";
document.body.appendChild(menuContainer);
console.log("pointer x:", e.clientX);
console.log("pointer y:", e.clientY);
console.log("menu items:", menuItems);
const contextMenu = h(ContextMenu, {
menuItems,
pointerX: e.clientX,
pointerY: e.clientY
});
render(contextMenu, menuContainer);
console.log("Rendered");
}

View file

@ -1,9 +1,12 @@
import type { MessageProps } from "~/types/props";
const { fetchMe } = useApi()
export default async (element: HTMLDivElement, props: MessageProps) => {
console.log("message:", element);
const me = await fetchWithApi("/me") as any;
if (props.author?.uuid == me.uuid) {
const me = await fetchMe();
if (me && props.author?.uuid == me.uuid) {
const text = element.getElementsByClassName("message-text")[0] as HTMLDivElement;
text.contentEditable = "true";
text.focus();

View file

@ -1,38 +0,0 @@
export default (name: string, seed: string): string => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (canvas && ctx) {
canvas.width = 256;
canvas.height = 256;
// get the first char from every word in the guild name
let previewName = "";
if (name.length > 3) {
let guildName: string[] = name.split(' ')
for (let i = 0; i < 3; i ++) {
if (guildName.length > i) {
previewName += guildName[i].charAt(0)
} else {
break
}
}
} else {
previewName = name
}
// fill background using seeded colour
ctx.fillStyle = generateIrcColor(seed, 50)
ctx.fillRect(0, 0, 256, 256)
ctx.fillStyle = 'white'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.font = `bold 96px Arial, Helvetica, sans-serif`
// 136 isn't actually centered, but it *looks* centered
ctx.fillText(previewName, 128, 136)
return canvas.toDataURL("image/png");
}
return "https://tenor.com/view/dame-da-ne-guy-kiryukazuma-kiryu-yakuza-yakuza-0-gif-14355451116903905918"
}

View file

@ -1,7 +0,0 @@
import type { GuildMemberResponse, UserResponse } from "~/types/interfaces";
export default (user: UserResponse, member?: GuildMemberResponse): string => {
if (member?.nickname) return member.nickname
if (user.display_name) return user.display_name
return user.username
}

View file

@ -1,50 +0,0 @@
//
// Canvas Blocker &
// Firefox privacy.resistFingerprinting Detector.
// (c) 2018 // JOHN OZBAY // CRYPT.EE
// MIT License
//
export default () => {
// create a 1px image data
var blocked = false;
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// some blockers just return an undefined ctx. So let's check that first.
if (ctx) {
var imageData = ctx.createImageData(1,1);
var originalImageData = imageData.data;
// set pixels to RGB 128
originalImageData[0]=128;
originalImageData[1]=128;
originalImageData[2]=128;
originalImageData[3]=255;
// set this to canvas
ctx.putImageData(imageData,1,1);
try {
// now get the data back from canvas.
var checkData = ctx.getImageData(1, 1, 1, 1).data;
// If this is firefox, and privacy.resistFingerprinting is enabled,
// OR a browser extension blocking the canvas,
// This will return RGB all white (255,255,255) instead of the (128,128,128) we put.
// so let's check the R and G to see if they're 255 or 128 (matching what we've initially set)
if (originalImageData[0] !== checkData[0] && originalImageData[1] !== checkData[1]) {
blocked = true;
console.log("Canvas is blocked. Will display warning.");
}
} catch (error) {
// some extensions will return getImageData null. this is to account for that.
blocked = true;
console.log("Canvas is blocked. Will display warning.");
}
} else {
blocked = true;
console.log("Canvas is blocked. Will display warning.");
}
return blocked;
}

View file

@ -1,28 +0,0 @@
let themeLinkElement: HTMLLinkElement | null;
export default function loadPreferredTheme() {
const currentTheme = settingsLoad().selectedThemeId ?? "dark"
if (themeLinkElement) {
themeLinkElement.href = getThemeUrl(currentTheme);
} else {
// create the theme link if one doesn't already exist
useHead({
link: [{
id: "main-theme",
rel: "stylesheet",
href: getThemeUrl(currentTheme)
}]
})
themeLinkElement = document.getElementById('main-theme') as HTMLLinkElement;
}
}
function getThemeUrl(id: string): string {
const runtimeConfig = useRuntimeConfig()
const baseURL = runtimeConfig.app.baseURL;
// this should preferrably use version hash, but that's not implemented yet
return `${baseURL}themes/${id}.css?v=${runtimeConfig.public.buildTimeString}`
}

View file

@ -0,0 +1,52 @@
let styleLinkElement: HTMLLinkElement | null;
let layoutLinkElement: HTMLLinkElement | null;
export default () => {
const runtimeConfig = useRuntimeConfig()
const baseURL = runtimeConfig.app.baseURL;
let currentStyle = settingsLoad().selectedThemeStyle ?? undefined
let currentLayout = settingsLoad().selectedThemeLayout ?? `${baseURL}themes/layout/gorb.css`
if (!currentStyle) {
if (prefersLight()) {
currentStyle = `${baseURL}themes/style/light.css`
} else {
currentStyle = `${baseURL}themes/style/dark.css`
}
}
if (styleLinkElement) {
styleLinkElement.href = currentStyle;
} else {
createStyleHead("style-theme", currentStyle)
styleLinkElement = document.getElementById('style-theme') as HTMLLinkElement;
}
if (layoutLinkElement) {
layoutLinkElement.href = currentLayout;
} else {
createStyleHead("style-layout", currentLayout)
layoutLinkElement = document.getElementById('style-layout') as HTMLLinkElement;
}
}
// create a new theme link if one doesn't already exist
function createStyleHead(id: string, themeUrl: string) {
useHead({
link: [{
id: id,
rel: "stylesheet",
href: themeUrl
}]
})
}
function prefersLight(): boolean {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
return true
}
return false
}

View file

@ -1,6 +1,12 @@
export default () => {
const contextMenu = document.getElementById("context-menu");
if (contextMenu) {
contextMenu.remove();
import type { ContextMenuInterface } from "~/types/interfaces";
export default (contextMenu: Ref<ContextMenuInterface>) => {
console.log("resetting and hiding context menu");
contextMenu.value = {
show: false,
pointerX: 0,
pointerY: 0,
items: []
}
console.log("hidden context menu");
}

View file

@ -2,6 +2,8 @@ import { render } from "vue";
import MessageReply from "~/components/UserInterface/MessageReply.vue";
import type { MessageProps } from "~/types/props";
const { getDisplayName } = useProfile()
export default (element: HTMLDivElement, props: MessageProps) => {
console.log("element:", element);
const messageBox = document.getElementById("message-box") as HTMLDivElement;
@ -10,5 +12,9 @@ export default (element: HTMLDivElement, props: MessageProps) => {
const messageReply = h(MessageReply, { author: getDisplayName(props.author), text: props.text || "", id: props.message.uuid, replyId: props.replyMessage?.uuid || element.dataset.messageId!, maxWidth: "full" });
messageBox.prepend(div);
render(messageReply, div);
const message = document.querySelector(`.message[data-message-id='${props.message.uuid}']`);
if (message) {
message.classList.add("replying-to");
}
}
}

12
utils/showContextMenu.ts Normal file
View file

@ -0,0 +1,12 @@
import { render } from "vue";
import ContextMenu from "~/components/UserInterface/ContextMenu.vue";
import type { ContextMenuInterface, ContextMenuItem } from "~/types/interfaces";
export default (e: MouseEvent | PointerEvent, contextMenu: ContextMenuInterface, menuItems: ContextMenuItem[]) => {
console.log("Showing context menu");
contextMenu.show = true;
contextMenu.pointerX = e.clientX;
contextMenu.pointerY = e.clientY;
contextMenu.items = menuItems;
console.log("Showed");
}

View file

@ -1,7 +1,8 @@
import type { GuildMemberResponse } from "~/types/interfaces";
const { getDisplayName } = useProfile()
export default (members: GuildMemberResponse[]): GuildMemberResponse[] => {
return members.sort((a, b) => {
return getDisplayName(a.user, a).localeCompare(getDisplayName(b.user, b))
return getDisplayName(a).localeCompare(getDisplayName(b))
})
}

View file

@ -1,4 +1,5 @@
import type { UserResponse } from "~/types/interfaces";
const { getDisplayName } = useProfile()
export default (users: UserResponse[]): UserResponse[] => {
return users.sort((a, b) => {

6
utils/uuidToDate.ts Normal file
View file

@ -0,0 +1,6 @@
export default (uuid: string): Date => {
const timeHex = uuid.substring(0, 8) + uuid.substring(9, 13);
const timestamp = parseInt(timeHex, 16);
return new Date(timestamp);
}