Merge pull request 'add antenna mark read functionality' (#9358) from supakaity/calckey:ada/antenna-mark-read into develop
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9358
This commit is contained in:
commit
c774a2f6e3
5 changed files with 174 additions and 7 deletions
|
@ -67,6 +67,7 @@ import * as ep___announcements from './endpoints/announcements.js';
|
||||||
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
import * as ep___antennas_create from './endpoints/antennas/create.js';
|
||||||
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
|
import * as ep___antennas_delete from './endpoints/antennas/delete.js';
|
||||||
import * as ep___antennas_list from './endpoints/antennas/list.js';
|
import * as ep___antennas_list from './endpoints/antennas/list.js';
|
||||||
|
import * as ep___antennas_markRead from './endpoints/antennas/markread.js';
|
||||||
import * as ep___antennas_notes from './endpoints/antennas/notes.js';
|
import * as ep___antennas_notes from './endpoints/antennas/notes.js';
|
||||||
import * as ep___antennas_show from './endpoints/antennas/show.js';
|
import * as ep___antennas_show from './endpoints/antennas/show.js';
|
||||||
import * as ep___antennas_update from './endpoints/antennas/update.js';
|
import * as ep___antennas_update from './endpoints/antennas/update.js';
|
||||||
|
@ -397,6 +398,7 @@ const eps = [
|
||||||
['antennas/create', ep___antennas_create],
|
['antennas/create', ep___antennas_create],
|
||||||
['antennas/delete', ep___antennas_delete],
|
['antennas/delete', ep___antennas_delete],
|
||||||
['antennas/list', ep___antennas_list],
|
['antennas/list', ep___antennas_list],
|
||||||
|
['antennas/mark-read', ep___antennas_markRead],
|
||||||
['antennas/notes', ep___antennas_notes],
|
['antennas/notes', ep___antennas_notes],
|
||||||
['antennas/show', ep___antennas_show],
|
['antennas/show', ep___antennas_show],
|
||||||
['antennas/update', ep___antennas_update],
|
['antennas/update', ep___antennas_update],
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import define from '../../define.js';
|
||||||
|
import { Antennas, AntennaNotes } from '@/models/index.js';
|
||||||
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
|
import { AntennaNote } from '@/models/entities/antenna-note.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['antennas', 'account'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:account',
|
||||||
|
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
antennaId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['antennaId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
const antenna = await Antennas.findOneBy({
|
||||||
|
userId: me.id,
|
||||||
|
id: ps.antennaId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!antenna) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
await AntennaNotes.update({
|
||||||
|
antennaId: antenna.id,
|
||||||
|
read: false,
|
||||||
|
}, {
|
||||||
|
read: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,35 @@ const reload = (): void => {
|
||||||
init();
|
init();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refresh = async (): void => {
|
||||||
|
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||||
|
await os.api(props.pagination.endpoint, {
|
||||||
|
...params,
|
||||||
|
limit: items.value.length + 1,
|
||||||
|
offset: 0,
|
||||||
|
}).then(res => {
|
||||||
|
let ids = items.value.reduce((a, b) => {
|
||||||
|
a[b.id] = true;
|
||||||
|
return a;
|
||||||
|
}, {} as { [id: string]: boolean; });
|
||||||
|
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
const item = res[i];
|
||||||
|
if (!updateItem(item.id, old => item)) {
|
||||||
|
append(item);
|
||||||
|
}
|
||||||
|
delete ids[item.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id in ids) {
|
||||||
|
removeItem(i => i.id === id);
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
error.value = true;
|
||||||
|
fetching.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const fetchMore = async (): Promise<void> => {
|
const fetchMore = async (): Promise<void> => {
|
||||||
if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
|
if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
|
||||||
moreFetching.value = true;
|
moreFetching.value = true;
|
||||||
|
@ -257,14 +286,24 @@ const append = (item: Item): void => {
|
||||||
items.value.push(item);
|
items.value.push(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeItem = (finder: (item: Item) => boolean): void => {
|
const removeItem = (finder: (item: Item) => boolean): boolean => {
|
||||||
const i = items.value.findIndex(finder);
|
const i = items.value.findIndex(finder);
|
||||||
|
if (i === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
items.value.splice(i, 1);
|
items.value.splice(i, 1);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
|
const updateItem = (id: Item['id'], replacer: (old: Item) => Item): boolean => {
|
||||||
const i = items.value.findIndex(item => item.id === id);
|
const i = items.value.findIndex(item => item.id === id);
|
||||||
|
if (i === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
items.value[i] = replacer(items.value[i]);
|
items.value[i] = replacer(items.value[i]);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.pagination.params && isRef(props.pagination.params)) {
|
if (props.pagination.params && isRef(props.pagination.params)) {
|
||||||
|
@ -291,6 +330,7 @@ defineExpose({
|
||||||
queue,
|
queue,
|
||||||
backed,
|
backed,
|
||||||
reload,
|
reload,
|
||||||
|
refresh,
|
||||||
prepend,
|
prepend,
|
||||||
append,
|
append,
|
||||||
removeItem,
|
removeItem,
|
||||||
|
|
|
@ -61,6 +61,23 @@ function settings() {
|
||||||
router.push(`/my/antennas/${props.antennaId}`);
|
router.push(`/my/antennas/${props.antennaId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function doMarkRead() {
|
||||||
|
const ret = await os.api('antennas/mark-read', {
|
||||||
|
antennaId: props.antennaId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Failed to mark all as read');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markRead() {
|
||||||
|
await os.promiseDialog(doMarkRead());
|
||||||
|
router.push(`/my/antennas`);
|
||||||
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
tlEl.focus();
|
tlEl.focus();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +96,10 @@ const headerActions = $computed(() => antenna ? [{
|
||||||
icon: 'ph-gear-six-bold ph-lg',
|
icon: 'ph-gear-six-bold ph-lg',
|
||||||
text: i18n.ts.settings,
|
text: i18n.ts.settings,
|
||||||
handler: settings,
|
handler: settings,
|
||||||
|
}, {
|
||||||
|
icon: 'ph-checks-bold ph-lg',
|
||||||
|
text: i18n.ts.markAllAsRead,
|
||||||
|
handler: markRead,
|
||||||
}] : []);
|
}] : []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
|
|
@ -7,9 +7,14 @@
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
|
<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
|
||||||
<MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
|
<div v-for="antenna in items" :key="antenna.id">
|
||||||
<div class="name">{{ antenna.name }}</div>
|
<MkA class="uopelskx" :link="true" :to="`/timeline/antenna/${antenna.id}`">
|
||||||
</MkA>
|
<i class="ph-flying-saucer-bold ph-lg"></i><i :class="`${antenna.hasUnreadNote ? 'ph-circle-fill' : 'ph-check'} ph-xs notify-icon`"></i>
|
||||||
|
</MkA>
|
||||||
|
<MkA class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
|
||||||
|
<div class="name">{{ antenna.name }}</div>
|
||||||
|
</MkA>
|
||||||
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { onActivated, onDeactivated, ref } from 'vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
@ -26,13 +31,38 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'antennas/list' as const,
|
endpoint: 'antennas/list' as const,
|
||||||
limit: 10,
|
limit: 250,
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
|
||||||
|
const list = ref<typeof MkPagination|null>(null);
|
||||||
|
|
||||||
|
let isCached: boolean = false;
|
||||||
|
let refreshTimer: number | null = null;
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
if (isCached) {
|
||||||
|
list.value?.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
isCached = true;
|
||||||
|
refreshTimer = setTimeout(refresh, 15000)
|
||||||
|
};
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
definePageMetadata({
|
definePageMetadata({
|
||||||
title: i18n.ts.manageAntennas,
|
title: i18n.ts.manageAntennas,
|
||||||
icon: 'ph-flying-saucer-bold ph-lg',
|
icon: 'ph-flying-saucer-bold ph-lg',
|
||||||
|
@ -46,6 +76,20 @@ definePageMetadata({
|
||||||
margin: 0 auto 16px auto;
|
margin: 0 auto 16px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uopelskx {
|
||||||
|
float: left;
|
||||||
|
min-width: 25px;
|
||||||
|
padding: 13px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: solid 1px var(--divider);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: solid 1px var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ljoevbzj {
|
.ljoevbzj {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
@ -62,5 +106,20 @@ definePageMetadata({
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notify-icon {
|
||||||
|
position: relative;
|
||||||
|
top: -1em;
|
||||||
|
left: -0.5em;
|
||||||
|
|
||||||
|
&.ph-circle-fill {
|
||||||
|
color: var(--indicator);
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ph-check {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue