Feat: メッセージの送受信 / New: ユーザーのiconプロパティ / New: logエンティティ・リポジトリ / Chg: コミュニティリポジトリのスキーマのiconをoptionalに / Del: 不要なimport / Fix: Vue起動前のindex.htmlの背景色をVueと同期 / Enhance: Service Workerを改善 / Fix: 最初に開いたページが動作しない問題 / Feat: 上部の通知モーダル / Feat: 閉じることができないエラーのモーダルに再読み込みボタンを追加 / Fix: はみ出す挙動などのCSSを修正

This commit is contained in:
2026-05-31 13:54:55 +09:00
parent beb0e25ad9
commit cbf18aec8f
37 changed files with 1174 additions and 83 deletions
+42 -23
View File
@@ -1,37 +1,46 @@
import TopNotice from "@/components/Modal/TopNotice.vue";
import { createApp, h, ref, type Component } from "vue";
const layer = ref<number>(0);
const layer = ref<number>(1);
export const createModal = <T extends Component>(data: {
component: T,
isTopNotice?: boolean;
style?: Record<string, string>,
props?: Record<string, ((...args: any[]) => any) | any>,
onClose?: () => void
onClose?: (...args: any) => any
}) => {
layer.value++
const newContainer = document.createElement("div");
for (const [key, value] of Object.entries({
...data.style,
transform: `translateZ(${layer.value})`,
position: "fixed",
inset: "0",
display: "flex",
"justify-content": "center",
"align-items": "center",
width: "100dvw",
height: "100dvh",
"background-color": "#00000080",
})) {
newContainer.style.setProperty(key, value);
}
layer.value++;
const container = document.querySelector(".modals-container")!
.appendChild(newContainer);
let container: HTMLDivElement;
if (!data.isTopNotice) {
const newContainer = document.createElement("div");
for (const [key, value] of Object.entries({
...data.style,
"transform": `translateZ(${layer.value})`,
"position": "fixed",
"inset": "0",
"display": "flex",
"justify-content": "center",
"align-items": "center",
"width": "100dvw",
"height": "100dvh",
"background-color": "#00000080",
})) {
newContainer.style.setProperty(key, value);
}
container = document.querySelector(".modals-container")!
.appendChild(newContainer);
} else {
container = document.querySelector(".top-notice-container")!
.appendChild(document.createElement("div"));
}
let app: ReturnType<typeof createApp>;
const close = () => {
data.onClose?.();
const close = (closeData?: any) => {
data.onClose?.(closeData);
layer.value--;
app.unmount();
container.remove();
@@ -49,4 +58,14 @@ export const createModal = <T extends Component>(data: {
app.mount(container);
return close;
}
}
export const createTopNotice = (data?: {
style?: Record<string, string>,
props?: Record<string, ((...args: any[]) => any) | any>,
onClose?: (...args: any) => any
}) => createModal({
...data,
component: TopNotice,
isTopNotice: true,
});
+28
View File
@@ -0,0 +1,28 @@
export function DateParse(date: string | Date, startDate: Date = new Date()) {
const diffMs = startDate.getTime() - new Date(date).getTime();
const diffSec = Math.abs(Math.floor(diffMs / 1000));
const diffMin = Math.abs(Math.floor(diffSec / 60));
const diffHour = Math.abs(Math.floor(diffMin / 60));
const diffDay = Math.abs(Math.floor(diffHour / 24));
const diffMonth = Math.abs(Math.floor(diffDay / 30));
const diffYear = Math.abs(Math.floor(diffMonth / 12));
const diffStr = diffMs < 0
? "後"
: "前";
switch (true) {
case diffSec < 60:
return `${diffSec}${diffStr}`;
case diffMin < 60:
return `${diffMin}${diffStr}`;
case diffHour < 24:
return `${diffHour}時間${diffStr}`;
case diffDay < 30:
return `${diffDay}${diffStr}`;
case diffMonth < 12:
return `${diffMonth}ヶ月${diffStr}`;
default:
return `${diffYear}${diffStr}`;
}
}
+46 -10
View File
@@ -1,27 +1,63 @@
const swSelf = globalThis as unknown as ServiceWorkerGlobalScope;
const resources = {
"general": [
"/assets/lynqchat.svg",
],
};
// @ts-expect-error
const manifest = self.__WB_MANIFEST || [];
manifest.forEach((entry: any) => {
const url = typeof entry === "string"
? entry
: entry.url;
resources.general.push(url);
});
swSelf.addEventListener("install", (event) => {
event.waitUntil(swSelf.skipWaiting());
swSelf.skipWaiting();
event.waitUntil((async () => {
await Promise.all(Object.entries(resources).map(async ([name, assets]) => {
const cache = await caches.open(name);
for (const asset of assets) {
try {
await cache.add(asset);
} catch (error) {
console.error(`Failed to cache asset: ${asset}`, error);
}
}
}));
})());
});
swSelf.addEventListener("activate", (event) => {
event.waitUntil(swSelf.clients.claim());
event.waitUntil((async () => {
await swSelf.clients.claim();
})());
});
swSelf.addEventListener("fetch", (event) => {
const request = event.request;
if (request.url.indexOf("http") !== 0)
if (request.method !== "GET")
return;
const url = new URL(request.url);
if (url.origin !== location.origin)
return;
event.respondWith((async () => {
const cached = await caches.match(request);
if (cached)
return cached;
try {
const res = await fetch(request);
return res;
} catch (err) {
return new Response("Network error", {
status: 504,
});
return await fetch(request);
} catch (error) {
return new Response("Network error", { status: 504 });
}
})());
});