feat: RTL/i18n, AI translate+undo, no-refresh saves, settings perf
- RTL: force dir=rtl on LabelFilter, NotesViewToggle, LabelManagementDialog - i18n: add missing keys (notifications, privacy, edit/preview, AI translate/undo) - Settings pages: convert to Server Components (general, appearance) + loading skeleton - AI menu: add Translate option (10 languages) + Undo AI button in toolbar - Fix: saveInline uses REST API instead of Server Action → eliminates all implicit refreshes in list mode - Fix: NotesTabsView notes sync effect preserves selected note on content changes - Fix: auto-tag suggestions now filter already-assigned labels - Fix: color change in card view uses local state (no refresh) - Fix: nav links use <Link> for prefetching (Settings, Admin) - Fix: suppress duplicate label suggestions already on note - Route: add /api/ai/translate endpoint
This commit is contained in:
@@ -385,9 +385,9 @@ export async function createNote(data: {
|
||||
reminder?: Date | null
|
||||
isMarkdown?: boolean
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
sharedWith?: string[]
|
||||
autoGenerated?: boolean
|
||||
notebookId?: string | undefined // Assign note to a notebook if provided
|
||||
skipRevalidation?: boolean // Option to prevent full page refresh for smooth optimistic UI updates
|
||||
}) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
@@ -421,8 +421,10 @@ export async function createNote(data: {
|
||||
await syncLabels(session.user.id, data.labels, data.notebookId ?? null)
|
||||
}
|
||||
|
||||
// Revalidate main page (handles both inbox and notebook views via query params)
|
||||
revalidatePath('/')
|
||||
if (!data.skipRevalidation) {
|
||||
// Revalidate main page (handles both inbox and notebook views via query params)
|
||||
revalidatePath('/')
|
||||
}
|
||||
|
||||
// Fire-and-forget: run AI operations in background without blocking the response
|
||||
const userId = session.user.id
|
||||
@@ -470,7 +472,9 @@ export async function createNote(data: {
|
||||
data: { labels: JSON.stringify(appliedLabels) }
|
||||
})
|
||||
await syncLabels(userId, appliedLabels, notebookId ?? null)
|
||||
revalidatePath('/')
|
||||
if (!data.skipRevalidation) {
|
||||
revalidatePath('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -503,7 +507,7 @@ export async function updateNote(id: string, data: {
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
autoGenerated?: boolean | null
|
||||
notebookId?: string | null
|
||||
}) {
|
||||
}, options?: { skipContentTimestamp?: boolean; skipRevalidation?: boolean }) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
@@ -556,9 +560,10 @@ export async function updateNote(id: string, data: {
|
||||
|
||||
// Only update contentUpdatedAt for actual content changes, NOT for property changes
|
||||
// (size, color, isPinned, isArchived are properties, not content)
|
||||
// skipContentTimestamp=true is used by the inline editor to avoid bumping "Récent" on every auto-save
|
||||
const contentFields = ['title', 'content', 'checkItems', 'images', 'links']
|
||||
const isContentChange = contentFields.some(field => field in data)
|
||||
if (isContentChange) {
|
||||
if (isContentChange && !options?.skipContentTimestamp) {
|
||||
updateData.contentUpdatedAt = new Date()
|
||||
}
|
||||
|
||||
@@ -582,7 +587,7 @@ export async function updateNote(id: string, data: {
|
||||
const structuralFields = ['isPinned', 'isArchived', 'labels', 'notebookId']
|
||||
const isStructuralChange = structuralFields.some(field => field in data)
|
||||
|
||||
if (isStructuralChange) {
|
||||
if (isStructuralChange && !options?.skipRevalidation) {
|
||||
revalidatePath('/')
|
||||
revalidatePath(`/note/${id}`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user