mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(collab): a shared link can be edited, and its glyph opens it
A link in the Collab tab could only be deleted and added again once its title or address needed a correction; the update route has taken both all along. The add dialog edits a link handed to it now, behind a pencil on each row. The external-link glyph next to a row that was already a link read as a button doing nothing; it is a link to the address itself now. Closes #2414
This commit is contained in:
@@ -118,6 +118,45 @@ describe('CollabLinks', () => {
|
||||
expect(screen.getByRole('button', { name: /delete link|collab\.links\.delete/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-COMP-LINKS-009: a link can be edited in place, title and address alike', async () => {
|
||||
// #2414: a link used to be delete-and-add once it needed a correction.
|
||||
const user = userEvent.setup();
|
||||
let put: Record<string, unknown> | null = null;
|
||||
server.use(
|
||||
http.get('/api/trips/1/collab/links', () => HttpResponse.json({ links: [buildLink()] })),
|
||||
http.put('/api/trips/1/collab/links/1', async ({ request }) => {
|
||||
put = (await request.json()) as Record<string, unknown>;
|
||||
return HttpResponse.json({ link: buildLink({ title: 'Ferry timetable 2026', url: 'https://ferries.example/2026' }) });
|
||||
}),
|
||||
);
|
||||
render(<CollabLinks tripId={1} />);
|
||||
await user.click(await screen.findByRole('button', { name: /edit link|collab\.links\.edit/i }));
|
||||
const title = await screen.findByLabelText(/link title|collab\.links\.titlePlaceholder/i);
|
||||
expect(title).toHaveValue('Ferry timetable');
|
||||
await user.clear(title);
|
||||
await user.type(title, 'Ferry timetable 2026');
|
||||
const url = screen.getByLabelText(/https|collab\.links\.urlPlaceholder/i);
|
||||
expect(url).toHaveValue('https://ferries.example/timetable');
|
||||
await user.clear(url);
|
||||
await user.type(url, 'https://ferries.example/2026');
|
||||
await user.click(screen.getByRole('button', { name: /save link|collab\.links\.save/i }));
|
||||
|
||||
await waitFor(() => expect(put).toEqual({ title: 'Ferry timetable 2026', url: 'https://ferries.example/2026' }));
|
||||
await waitFor(() => expect(screen.queryByLabelText(/link title|collab\.links\.titlePlaceholder/i)).not.toBeInTheDocument());
|
||||
expect(await screen.findByText('Ferry timetable 2026')).toBeInTheDocument();
|
||||
expect(screen.getByText('https://ferries.example/2026')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-COMP-LINKS-010: the external-link glyph opens the address in a new tab', async () => {
|
||||
server.use(
|
||||
http.get('/api/trips/1/collab/links', () => HttpResponse.json({ links: [buildLink()] })),
|
||||
);
|
||||
render(<CollabLinks tripId={1} />);
|
||||
const open = await screen.findByRole('link', { name: /open link|collab\.links\.open/i });
|
||||
expect(open).toHaveAttribute('href', 'https://ferries.example/timetable');
|
||||
expect(open).toHaveAttribute('target', '_blank');
|
||||
});
|
||||
|
||||
it('FE-COMP-LINKS-008: a viewer without edit rights gets no add button', async () => {
|
||||
// collab_edit reserved for the owner, on somebody else's trip.
|
||||
seedStore(usePermissionsStore, { permissions: { collab_edit: 'trip_owner' } });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CSSProperties, FormEvent, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { ExternalLink, Link2, Pin, Plus, Trash2, X } from 'lucide-react'
|
||||
import { ExternalLink, Link2, Pencil, Pin, Plus, Trash2, X } from 'lucide-react'
|
||||
import { collabApi } from '../../api/client'
|
||||
import { addListener, removeListener } from '../../api/websocket'
|
||||
import { useTranslation } from '../../i18n'
|
||||
@@ -27,14 +27,16 @@ function LinkIcon({ url, title }: { url: string; title: string }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The add form as a modal, like the poll and note panels next to it. Inline it
|
||||
* pushed the list down and gave the panel a second header, which is what made
|
||||
* this tab read as a different app from its four siblings.
|
||||
* The add and edit form as a modal, like the poll and note panels next to it.
|
||||
* Inline it pushed the list down and gave the panel a second header, which is
|
||||
* what made this tab read as a different app from its four siblings. With a
|
||||
* link handed in it edits that link (#2414): a link used to be delete-and-add
|
||||
* once its title or address needed a correction.
|
||||
*/
|
||||
function AddLinkModal({ onClose, onCreate }: { onClose: () => void; onCreate: (link: { title: string; url: string }) => Promise<void> }) {
|
||||
function LinkModal({ link, onClose, onSave }: { link?: CollabLink; onClose: () => void; onSave: (link: { title: string; url: string }) => Promise<void> }) {
|
||||
const { t } = useTranslation()
|
||||
const [title, setTitle] = useState('')
|
||||
const [url, setUrl] = useState('')
|
||||
const [title, setTitle] = useState(link?.title ?? '')
|
||||
const [url, setUrl] = useState(link?.url ?? '')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const valid = title.trim().length > 0 && url.trim().length > 0
|
||||
|
||||
@@ -43,10 +45,10 @@ function AddLinkModal({ onClose, onCreate }: { onClose: () => void; onCreate: (l
|
||||
if (!valid || busy) return
|
||||
setBusy(true)
|
||||
try {
|
||||
await onCreate({ title: title.trim(), url: url.trim() })
|
||||
await onSave({ title: title.trim(), url: url.trim() })
|
||||
onClose()
|
||||
} catch {
|
||||
// add() already said what went wrong; the form stays open with the input in it.
|
||||
// The caller already said what went wrong; the form stays open with the input in it.
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
@@ -66,7 +68,7 @@ function AddLinkModal({ onClose, onCreate }: { onClose: () => void; onCreate: (l
|
||||
<div role="presentation" style={{ position: 'fixed', inset: 0, background: 'var(--overlay-bg, rgba(0,0,0,0.35))', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999, padding: 16, fontFamily: FONT }} onClick={e => { if (e.target === e.currentTarget) onClose() }}>
|
||||
<form style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 400, maxHeight: '90vh', overflow: 'auto', border: '1px solid var(--border-faint)' }} onSubmit={submit}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}>
|
||||
<h3 style={{ fontSize: 'calc(14px * var(--fs-scale-body, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>{t('collab.links.add')}</h3>
|
||||
<h3 style={{ fontSize: 'calc(14px * var(--fs-scale-body, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>{t(link ? 'collab.links.edit' : 'collab.links.add')}</h3>
|
||||
<button type="button" onClick={onClose} aria-label={t('collab.links.cancel')} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}><X size={16} /></button>
|
||||
</div>
|
||||
<div style={{ padding: '14px 16px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
@@ -99,6 +101,7 @@ export default function CollabLinks({ tripId }: { tripId: number }) {
|
||||
const toast = useToast()
|
||||
const [links, setLinks] = useState<CollabLink[]>([])
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
const [editing, setEditing] = useState<CollabLink | null>(null)
|
||||
|
||||
// Both are new objects on every render, so depending on them would reload the
|
||||
// list after each keystroke and drop a link that was just added back out of it.
|
||||
@@ -168,6 +171,17 @@ export default function CollabLinks({ tripId }: { tripId: number }) {
|
||||
}
|
||||
}
|
||||
|
||||
const edit = async (link: CollabLink, data: { title: string; url: string }) => {
|
||||
try {
|
||||
const d = await collabApi.updateLink(tripId, link.id, data)
|
||||
setLinks(v => v.map(x => x.id === link.id ? d.link : x))
|
||||
} catch (err) {
|
||||
console.error('Failed to edit collab link:', err)
|
||||
toast.error(t('common.error'))
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const remove = async (id: number) => {
|
||||
try {
|
||||
await collabApi.deleteLink(tripId, id)
|
||||
@@ -210,8 +224,15 @@ export default function CollabLinks({ tripId }: { tripId: number }) {
|
||||
<div style={{ fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{link.title}</div>
|
||||
<div style={{ fontSize: 'calc(11px * var(--fs-scale-caption, 1))', color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{link.url}</div>
|
||||
</a>
|
||||
<ExternalLink size={15} color="var(--text-faint)" aria-hidden="true" />
|
||||
{/* The glyph used to be decoration next to a row that was already a link,
|
||||
which read as a button that did nothing (#2414). */}
|
||||
<a href={link.url} target="_blank" rel="noreferrer" aria-label={t('collab.links.open')} title={t('collab.links.open')} style={{ display: 'flex', color: 'var(--text-faint)' }}>
|
||||
<ExternalLink size={15} aria-hidden="true" />
|
||||
</a>
|
||||
{canEdit && <>
|
||||
<button type="button" onClick={() => setEditing(link)} aria-label={t('collab.links.edit')} style={{ border: 0, background: 'transparent', color: 'var(--text-faint)', cursor: 'pointer' }}>
|
||||
<Pencil size={15} aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" onClick={() => toggle(link)} aria-label={link.pinned ? t('collab.links.unpin') : t('collab.links.pin')} style={{ border: 0, background: 'transparent', color: link.pinned ? 'var(--accent)' : 'var(--text-faint)', cursor: 'pointer' }}>
|
||||
<Pin size={15} fill={link.pinned ? 'currentColor' : 'none'} aria-hidden="true" />
|
||||
</button>
|
||||
@@ -225,8 +246,9 @@ export default function CollabLinks({ tripId }: { tripId: number }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Create Modal */}
|
||||
{showForm && <AddLinkModal onClose={() => setShowForm(false)} onCreate={add} />}
|
||||
{/* Create and edit modals */}
|
||||
{showForm && <LinkModal onClose={() => setShowForm(false)} onSave={add} />}
|
||||
{editing && <LinkModal key={editing.id} link={editing} onClose={() => setEditing(null)} onSave={data => edit(editing, data)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'تثبيت الرابط',
|
||||
'collab.links.unpin': 'إلغاء تثبيت الرابط',
|
||||
'collab.links.delete': 'حذف الرابط',
|
||||
'collab.links.edit': 'تعديل الرابط',
|
||||
'collab.links.open': 'فتح الرابط',
|
||||
'collab.whatsNext.title': 'ما التالي',
|
||||
'collab.whatsNext.today': 'اليوم',
|
||||
'collab.whatsNext.tomorrow': 'غدًا',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Fixar link',
|
||||
'collab.links.unpin': 'Desafixar link',
|
||||
'collab.links.delete': 'Excluir link',
|
||||
'collab.links.edit': 'Editar link',
|
||||
'collab.links.open': 'Abrir link',
|
||||
'collab.whatsNext.title': 'Próximos passos',
|
||||
'collab.whatsNext.today': 'Hoje',
|
||||
'collab.whatsNext.tomorrow': 'Amanhã',
|
||||
|
||||
@@ -15,6 +15,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Fixa l\'enllaç',
|
||||
'collab.links.unpin': 'Desfixa l\'enllaç',
|
||||
'collab.links.delete': 'Suprimeix l\'enllaç',
|
||||
'collab.links.edit': 'Edita l\'enllaç',
|
||||
'collab.links.open': 'Obre l\'enllaç',
|
||||
'collab.whatsNext.title': 'Què ve ara',
|
||||
'collab.whatsNext.today': 'Avui',
|
||||
'collab.whatsNext.tomorrow': 'Demà',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Připnout odkaz',
|
||||
'collab.links.unpin': 'Odepnout odkaz',
|
||||
'collab.links.delete': 'Smazat odkaz',
|
||||
'collab.links.edit': 'Upravit odkaz',
|
||||
'collab.links.open': 'Otevřít odkaz',
|
||||
'collab.whatsNext.title': 'Co následuje',
|
||||
'collab.whatsNext.today': 'Dnes',
|
||||
'collab.whatsNext.tomorrow': 'Zítra',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Link anheften',
|
||||
'collab.links.unpin': 'Link lösen',
|
||||
'collab.links.delete': 'Link löschen',
|
||||
'collab.links.edit': 'Link bearbeiten',
|
||||
'collab.links.open': 'Link öffnen',
|
||||
'collab.whatsNext.title': 'Nächste',
|
||||
'collab.whatsNext.today': 'Heute',
|
||||
'collab.whatsNext.tomorrow': 'Morgen',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Pin link',
|
||||
'collab.links.unpin': 'Unpin link',
|
||||
'collab.links.delete': 'Delete link',
|
||||
'collab.links.edit': 'Edit link',
|
||||
'collab.links.open': 'Open link',
|
||||
'collab.whatsNext.title': "What's Next",
|
||||
'collab.whatsNext.today': 'Today',
|
||||
'collab.whatsNext.tomorrow': 'Tomorrow',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Fijar enlace',
|
||||
'collab.links.unpin': 'Desfijar enlace',
|
||||
'collab.links.delete': 'Eliminar enlace',
|
||||
'collab.links.edit': 'Editar enlace',
|
||||
'collab.links.open': 'Abrir enlace',
|
||||
'collab.whatsNext.title': 'Qué viene ahora',
|
||||
'collab.whatsNext.today': 'Hoy',
|
||||
'collab.whatsNext.tomorrow': 'Mañana',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Épingler le lien',
|
||||
'collab.links.unpin': 'Désépingler le lien',
|
||||
'collab.links.delete': 'Supprimer le lien',
|
||||
'collab.links.edit': 'Modifier le lien',
|
||||
'collab.links.open': 'Ouvrir le lien',
|
||||
'collab.whatsNext.title': 'À venir',
|
||||
'collab.whatsNext.today': "Aujourd'hui",
|
||||
'collab.whatsNext.tomorrow': 'Demain',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Καρφίτσωμα συνδέσμου',
|
||||
'collab.links.unpin': 'Ξεκαρφίτσωμα συνδέσμου',
|
||||
'collab.links.delete': 'Διαγραφή συνδέσμου',
|
||||
'collab.links.edit': 'Επεξεργασία συνδέσμου',
|
||||
'collab.links.open': 'Άνοιγμα συνδέσμου',
|
||||
'collab.whatsNext.title': 'Τι Επόμενο',
|
||||
'collab.whatsNext.today': 'Σήμερα',
|
||||
'collab.whatsNext.tomorrow': 'Αύριο',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Link rögzítése',
|
||||
'collab.links.unpin': 'Link feloldása',
|
||||
'collab.links.delete': 'Link törlése',
|
||||
'collab.links.edit': 'Link szerkesztése',
|
||||
'collab.links.open': 'Link megnyitása',
|
||||
'collab.whatsNext.title': 'Mi következik',
|
||||
'collab.whatsNext.today': 'Ma',
|
||||
'collab.whatsNext.tomorrow': 'Holnap',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Sematkan tautan',
|
||||
'collab.links.unpin': 'Lepas sematan tautan',
|
||||
'collab.links.delete': 'Hapus tautan',
|
||||
'collab.links.edit': 'Edit tautan',
|
||||
'collab.links.open': 'Buka tautan',
|
||||
'collab.whatsNext.title': 'Berikutnya',
|
||||
'collab.whatsNext.today': 'Hari ini',
|
||||
'collab.whatsNext.tomorrow': 'Besok',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Fissa link',
|
||||
'collab.links.unpin': 'Sblocca link',
|
||||
'collab.links.delete': 'Elimina link',
|
||||
'collab.links.edit': 'Modifica link',
|
||||
'collab.links.open': 'Apri link',
|
||||
'collab.whatsNext.title': "Cosa c'è dopo",
|
||||
'collab.whatsNext.today': 'Oggi',
|
||||
'collab.whatsNext.tomorrow': 'Domani',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'リンクを固定',
|
||||
'collab.links.unpin': 'リンクの固定を解除',
|
||||
'collab.links.delete': 'リンクを削除',
|
||||
'collab.links.edit': 'リンクを編集',
|
||||
'collab.links.open': 'リンクを開く',
|
||||
'collab.whatsNext.title': '次にすること',
|
||||
'collab.whatsNext.today': '今日',
|
||||
'collab.whatsNext.tomorrow': '明日',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': '링크 고정',
|
||||
'collab.links.unpin': '링크 고정 해제',
|
||||
'collab.links.delete': '링크 삭제',
|
||||
'collab.links.edit': '링크 편집',
|
||||
'collab.links.open': '링크 열기',
|
||||
'collab.whatsNext.title': '다음 할 일',
|
||||
'collab.whatsNext.today': '오늘',
|
||||
'collab.whatsNext.tomorrow': '내일',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Link vastmaken',
|
||||
'collab.links.unpin': 'Link losmaken',
|
||||
'collab.links.delete': 'Link verwijderen',
|
||||
'collab.links.edit': 'Link bewerken',
|
||||
'collab.links.open': 'Link openen',
|
||||
'collab.whatsNext.title': 'Wat komt er',
|
||||
'collab.whatsNext.today': 'Vandaag',
|
||||
'collab.whatsNext.tomorrow': 'Morgen',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Przypnij link',
|
||||
'collab.links.unpin': 'Odepnij link',
|
||||
'collab.links.delete': 'Usuń link',
|
||||
'collab.links.edit': 'Edytuj link',
|
||||
'collab.links.open': 'Otwórz link',
|
||||
'collab.whatsNext.title': 'Co dalej',
|
||||
'collab.whatsNext.today': 'Dzisiaj',
|
||||
'collab.whatsNext.tomorrow': 'Jutro',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Закрепить ссылку',
|
||||
'collab.links.unpin': 'Открепить ссылку',
|
||||
'collab.links.delete': 'Удалить ссылку',
|
||||
'collab.links.edit': 'Изменить ссылку',
|
||||
'collab.links.open': 'Открыть ссылку',
|
||||
'collab.whatsNext.title': 'Что дальше',
|
||||
'collab.whatsNext.today': 'Сегодня',
|
||||
'collab.whatsNext.tomorrow': 'Завтра',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Fäst länk',
|
||||
'collab.links.unpin': 'Lossa länk',
|
||||
'collab.links.delete': 'Ta bort länk',
|
||||
'collab.links.edit': 'Redigera länk',
|
||||
'collab.links.open': 'Öppna länk',
|
||||
'collab.whatsNext.title': 'Vad händer härnäst?',
|
||||
'collab.whatsNext.today': 'Idag',
|
||||
'collab.whatsNext.tomorrow': 'I morgon',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Bağlantıyı sabitle',
|
||||
'collab.links.unpin': 'Sabitlemeyi kaldır',
|
||||
'collab.links.delete': 'Bağlantıyı sil',
|
||||
'collab.links.edit': 'Bağlantıyı düzenle',
|
||||
'collab.links.open': 'Bağlantıyı aç',
|
||||
'collab.whatsNext.title': "What's Next",
|
||||
'collab.whatsNext.today': 'Bugün',
|
||||
'collab.whatsNext.tomorrow': 'Yarın',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Закріпити посилання',
|
||||
'collab.links.unpin': 'Відкріпити посилання',
|
||||
'collab.links.delete': 'Видалити посилання',
|
||||
'collab.links.edit': 'Редагувати посилання',
|
||||
'collab.links.open': 'Відкрити посилання',
|
||||
'collab.whatsNext.title': 'Що далі',
|
||||
'collab.whatsNext.today': 'Сьогодні',
|
||||
'collab.whatsNext.tomorrow': 'Завтра',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': 'Ghim liên kết',
|
||||
'collab.links.unpin': 'Bỏ ghim liên kết',
|
||||
'collab.links.delete': 'Xóa liên kết',
|
||||
'collab.links.edit': 'Chỉnh sửa liên kết',
|
||||
'collab.links.open': 'Mở liên kết',
|
||||
'collab.whatsNext.title': 'Tiếp theo là gì',
|
||||
'collab.whatsNext.today': 'Hôm nay',
|
||||
'collab.whatsNext.tomorrow': 'Ngày mai',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': '釘選連結',
|
||||
'collab.links.unpin': '取消釘選連結',
|
||||
'collab.links.delete': '刪除連結',
|
||||
'collab.links.edit': '編輯連結',
|
||||
'collab.links.open': '開啟連結',
|
||||
'collab.whatsNext.title': '接下來',
|
||||
'collab.whatsNext.today': '今天',
|
||||
'collab.whatsNext.tomorrow': '明天',
|
||||
|
||||
@@ -23,6 +23,8 @@ const collab: TranslationStrings = {
|
||||
'collab.links.pin': '固定链接',
|
||||
'collab.links.unpin': '取消固定链接',
|
||||
'collab.links.delete': '删除链接',
|
||||
'collab.links.edit': '编辑链接',
|
||||
'collab.links.open': '打开链接',
|
||||
'collab.whatsNext.title': '接下来',
|
||||
'collab.whatsNext.today': '今天',
|
||||
'collab.whatsNext.tomorrow': '明天',
|
||||
|
||||
Reference in New Issue
Block a user