Compare commits

...

9 Commits

Author SHA1 Message Date
Linca fcc4783406 Merge branch 'feat/pwa-use-site-ico' into 'develop'
Draft: feat: Generate PWA icons from the server logo (Catodon issue 7)

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10783
2024-05-08 20:59:05 +00:00
naskya 3af8f86924
chore: lint 2024-05-09 02:06:10 +09:00
naskya 276cabbbe3
ci: fix clippy task 2024-05-09 01:15:09 +09:00
naskya af14bee31f
docs: update changelog 2024-05-09 00:41:49 +09:00
naskya b3d1be457b Merge branch 'fix/MkTime' into 'develop'
refactor MkTime: replace the awful ?: chain with if-else; fix: force update ticker when props.time changed

Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10797
2024-05-08 12:11:31 +00:00
Hosted Weblate 347851d6bb
Merge branch 'origin/develop' into Weblate 2024-05-08 10:21:46 +02:00
jolupa abec71074b
locale: update translations (Catalan)
Currently translated at 100.0% (1930 of 1930 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
2024-05-08 10:21:45 +02:00
Lhcfl 272e30be0c refactor: replace the awful ?: chain with if-else; fix: force update ticker when props.time changed
related: ed6f866a4f

Co-authored-by: kakkokari-gtyih <kakkokari-gtyih@users.noreply.github.com>
2024-05-08 10:52:32 +08:00
Lhcfl bfa8bc5f7d feat: use instance iconUrl as PWA icon 2024-04-30 11:35:06 +08:00
11 changed files with 146 additions and 64 deletions

View File

@ -185,6 +185,7 @@ cargo_clippy:
when: never
services: []
before_script:
- apt-get update && apt-get -y upgrade
- apt-get install -y --no-install-recommends build-essential clang mold perl
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
- rustup component add clippy

View File

@ -5,6 +5,11 @@ Critical security updates are indicated by the :warning: icon.
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
## Unreleased
- Improve timeline UX
- Fix bugs
## [v20240504](https://firefish.dev/firefish/firefish/-/merge_requests/10790/commits)
- Fix bugs

View File

@ -2301,3 +2301,6 @@ getQrCode: Mostrar el codi QR
copyRemoteFollowUrl: Còpia la adreça URL del seguidor remot
foldNotification: Agrupar les notificacions similars
slashQuote: Cita encadenada
i18nServerInfo: Els nous clients els trobares en {language} per defecte.
i18nServerChange: Fes servir {language} en comptes.
i18nServerSet: Fes servir {language} per els nous clients.

View File

@ -3,19 +3,71 @@ import { fetchMeta } from "backend-rs";
import { config } from "@/config.js";
import manifest from "./manifest.json" assert { type: "json" };
interface Manifest {
short_name: string;
name: string;
description: string;
start_url: string;
scope: string;
display: string;
background_color: string;
theme_color: string;
orientation: string;
icons: {
src: string;
sizes?: string;
type?: string;
purpose?: "any" | "maskable" | "monochrome";
}[];
share_target: {
action: "/share/";
params: {
title: "title";
text: "text";
url: "url";
};
};
screenshots: {
src: string;
sizes: string;
type: string;
platform: string;
label: string;
}[];
shortcuts: {
name: string;
short_name?: string;
url: string;
}[];
categories: string[];
}
export const manifestHandler = async (ctx: Koa.Context) => {
// TODO
//const res = structuredClone(manifest);
const res = JSON.parse(JSON.stringify(manifest));
const res: Manifest = JSON.parse(JSON.stringify(manifest));
const instance = await fetchMeta(false);
res.short_name = instance.name || "Firefish";
res.name = instance.name || "Firefish";
if (instance.themeColor) res.theme_color = instance.themeColor;
for (const icon of res.icons) {
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;
if (instance.iconUrl) {
res.icons = [
{
src: instance.iconUrl,
sizes: "48x48 72x72 96x96 128x128 192x192 256x256",
purpose: "any",
},
];
} else {
for (const icon of res.icons) {
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;
}
}
if (instance.themeColor) res.theme_color = instance.themeColor;
for (const screenshot of res.screenshots) {
screenshot.src = `${screenshot.src}?v=${config.version.replace(
/[^0-9]/g,

View File

@ -18,8 +18,8 @@
class="reply-to"
/>
<MkNoteSub
v-else-if="!detailedView && !collapsedReply && parents"
v-for="n of parents"
v-else-if="!detailedView && !collapsedReply && parents"
:key="n.id"
:note="n"
class="reply-to"
@ -354,7 +354,7 @@ import { deepClone } from "@/scripts/clone";
import { getNoteSummary } from "@/scripts/get-note-summary";
import icon from "@/scripts/icon";
import type { NoteTranslation, NoteType } from "@/types/note";
import { isRenote as _isRenote, isDeleted as _isDeleted } from "@/scripts/note";
import { isDeleted as _isDeleted, isRenote as _isRenote } from "@/scripts/note";
const props = defineProps<{
note: NoteType;
@ -368,7 +368,7 @@ const props = defineProps<{
isLongJudger?: (note: entities.Note) => boolean;
}>();
//#region Constants
// #region Constants
const router = useRouter();
const inChannel = inject("inChannel", null);
const keymap = {
@ -398,9 +398,9 @@ const currentClipPage = inject<Ref<entities.Clip> | null>(
"currentClipPage",
null,
);
//#endregion
// #endregion
//#region Variables bound to Notes
// #region Variables bound to Notes
let capture: ReturnType<typeof useNoteCapture> | undefined;
const note = ref(deepClone(props.note));
const postIsExpanded = ref(false);
@ -408,9 +408,9 @@ const translation = ref<NoteTranslation | null>(null);
const translating = ref(false);
const isDeleted = ref(false);
const renotes = ref(props.renotes?.filter((rn) => !_isDeleted(rn.id)));
//#endregion
// #endregion
//#region computed
// #region computed
const renotesSliced = computed(() => renotes.value?.slice(0, 5));
@ -470,7 +470,7 @@ const accessibleLabel = computed(() => {
label += `${date.toLocaleTimeString()}`;
return label;
});
//#endregion
// #endregion
async function pluginInit(newNote: NoteType) {
// plugin

View File

@ -1,9 +1,9 @@
<template>
<div
v-show="!deleted"
ref="el"
v-size="{ min: [350, 500] }"
class="yohlumlk"
ref="el"
>
<MkAvatar class="avatar" :user="note.user" />
<div class="main">
@ -17,9 +17,9 @@
<script lang="ts" setup>
import type { entities } from "firefish-js";
import { computed, ref, watch } from "vue";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
import { computed, ref, watch } from "vue";
import { deepClone } from "@/scripts/clone";
import { useNoteCapture } from "@/scripts/use-note-capture";
import { isDeleted } from "@/scripts/note";

View File

@ -28,9 +28,9 @@
ref="tlComponent"
:no-gap="!defaultStore.state.showGapBetweenNotesInTimeline"
:pagination="pagination"
:folder
@queue="(x) => (queue = x)"
@status="pullToRefreshComponent?.setDisabled($event)"
:folder
/>
</MkPullToRefresh>
<XNotes
@ -38,9 +38,9 @@
ref="tlComponent"
:no-gap="!defaultStore.state.showGapBetweenNotesInTimeline"
:pagination="pagination"
:folder
@queue="(x) => (queue = x)"
@status="pullToRefreshComponent?.setDisabled($event)"
:folder
/>
</template>

View File

@ -25,15 +25,21 @@ const props = withDefaults(
},
);
function getDateSafe(n: Date | string | number) {
try {
if (n instanceof Date) {
return n;
}
return new Date(n);
} catch (err) {
return {
getTime: () => Number.NaN,
};
}
}
const _time = computed(() =>
props.time == null
? Number.NaN
: typeof props.time === "number"
? props.time
: (props.time instanceof Date
? props.time
: new Date(props.time)
).getTime(),
props.time == null ? Number.NaN : getDateSafe(props.time).getTime(),
);
const invalid = computed(() => Number.isNaN(_time.value));
const absolute = computed(() =>
@ -41,45 +47,57 @@ const absolute = computed(() =>
);
const now = ref(props.origin?.getTime() ?? Date.now());
const relative = computed<string>(() => {
if (props.mode === "absolute") return ""; // absoluterelative使
if (invalid.value) return i18n.ts._ago.invalid;
const ago = (now.value - _time.value) / 1000; /* ms */
return ago >= 31536000
? i18n.t("_ago.yearsAgo", { n: Math.floor(ago / 31536000).toString() })
: ago >= 2592000
? i18n.t("_ago.monthsAgo", {
n: Math.floor(ago / 2592000).toString(),
})
: ago >= 604800
? i18n.t("_ago.weeksAgo", {
n: Math.floor(ago / 604800).toString(),
})
: ago >= 86400
? i18n.t("_ago.daysAgo", {
n: Math.floor(ago / 86400).toString(),
})
: ago >= 3600
? i18n.t("_ago.hoursAgo", {
n: Math.floor(ago / 3600).toString(),
})
: ago >= 60
? i18n.t("_ago.minutesAgo", {
n: (~~(ago / 60)).toString(),
})
: ago >= 10
? i18n.t("_ago.secondsAgo", {
n: (~~(ago % 60)).toString(),
})
: ago >= -1
? i18n.ts._ago.justNow
: i18n.ts._ago.future;
if (ago >= 31536000) {
return i18n.t("_ago.yearsAgo", {
n: Math.floor(ago / 31536000).toString(),
});
}
if (ago >= 2592000) {
return i18n.t("_ago.monthsAgo", {
n: Math.floor(ago / 2592000).toString(),
});
}
if (ago >= 604800) {
return i18n.t("_ago.weeksAgo", {
n: Math.floor(ago / 604800).toString(),
});
}
if (ago >= 86400) {
return i18n.t("_ago.daysAgo", {
n: Math.floor(ago / 86400).toString(),
});
}
if (ago >= 3600) {
return i18n.t("_ago.hoursAgo", {
n: Math.floor(ago / 3600).toString(),
});
}
if (ago >= 60) {
return i18n.t("_ago.minutesAgo", {
n: (~~(ago / 60)).toString(),
});
}
if (ago >= 10) {
return i18n.t("_ago.secondsAgo", {
n: (~~(ago % 60)).toString(),
});
}
if (ago >= -1) {
return i18n.ts._ago.justNow;
}
return i18n.ts._ago.future;
});
let tickId: number | undefined;
function tick() {
function tick(forceUpdateTicker = false) {
if (
invalid.value ||
props.origin ||
@ -101,13 +119,16 @@ function tick() {
if (!tickId) {
tickId = window.setInterval(tick, next);
} else if (prev < next) {
} else if (prev < next || forceUpdateTicker) {
window.clearInterval(tickId);
tickId = window.setInterval(tick, next);
}
}
watch(() => props.time, tick);
watch(
() => props.time,
() => tick(true),
);
onMounted(() => {
tick();

View File

@ -14,10 +14,10 @@
>
</template>
</I18n>
<I18n :src="i18n.ts.i18nServerInfo" v-if="serverLang" tag="span">
<I18n v-if="serverLang" :src="i18n.ts.i18nServerInfo" tag="span">
<template #language><strong>{{ langs.find(a => a[0] === serverLang)?.[1] ?? serverLang }}</strong></template>
</I18n>
<button class="_textButton" @click="updateServerLang" v-if="lang && lang !== serverLang">
<button v-if="lang && lang !== serverLang" class="_textButton" @click="updateServerLang">
{{i18n.t(serverLang ? "i18nServerChange" : "i18nServerSet", { language: langs.find(a => a[0] === lang)?.[1] ?? lang })}}
</button>
</template>

View File

@ -1,11 +1,11 @@
import type { entities } from "firefish-js";
import { isDeleted, isRenote } from "./note";
import type {
FoldableNotification,
NotificationFolded,
} from "@/types/notification";
import type { NoteType, NoteThread, NoteFolded } from "@/types/note";
import type { NoteFolded, NoteThread, NoteType } from "@/types/note";
import { me } from "@/me";
import { isDeleted, isRenote } from "./note";
interface FoldOption {
/** If items length is 1, skip aggregation */

View File

@ -12,19 +12,19 @@ export type NoteType = entities.Note & {
_prId_?: string;
};
export type NoteFolded = {
export interface NoteFolded {
id: string;
key: string;
createdAt: entities.Note["createdAt"];
folded: "renote";
note: entities.Note;
renotesArr: entities.Note[];
};
}
export type NoteThread = {
export interface NoteThread {
id: string;
createdAt: entities.Note["createdAt"];
folded: "thread";
note: entities.Note;
parents: entities.Note[];
};
}