4d749b3592
The 论文 menu now carries the papers themselves, read from a pinia store, so a paper opens straight from the rail; every page that changes the list reloads that store, and a filter box appears once there are more than six. PapersView is the library — search, create, edit, open, single and batch delete. PaperDetailView is the writing surface: it renders the server's document verbatim, with an edit button beside every paragraph (written or not, because content only ever enters a paper through the editor), citation markers numbered in reading order to match the 参考文献 list, and a 切换模板 dialog that previews the impact before re-shaping the document. The paragraph editor shows one paragraph as the paper will read it — every sentence on its own line, in sort order, each editable, each able to carry citations — and writes the paragraph back whole. Whitespace-only lines are dropped on save; a citation with an empty 引用内容 is refused; and 所属段落 moves the paragraph to another position, appended after what is already there.
110 lines
3.0 KiB
TypeScript
110 lines
3.0 KiB
TypeScript
import http from './client'
|
|
import type { BatchDeleteResult, PageQuery, PageResult } from './types'
|
|
|
|
/** One placement of a library field inside a template, with its typography. */
|
|
export interface TemplateField {
|
|
id: number
|
|
field_id: number
|
|
/** Display position. The only thing that decides render order. */
|
|
sort: number
|
|
name: string
|
|
level: number
|
|
font_size: number
|
|
font_color: string
|
|
}
|
|
|
|
/** A template as it appears in the list table. */
|
|
export interface TemplateListItem {
|
|
id: number
|
|
name: string
|
|
abstract: string | null
|
|
field_count: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
/** A template with its outline, already ordered by `sort`. */
|
|
export interface TemplateDetail {
|
|
id: number
|
|
name: string
|
|
abstract: string | null
|
|
fields: TemplateField[]
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
/** One entry of the selection sent on create/update. */
|
|
export interface TemplateFieldInput {
|
|
field_id: number
|
|
sort: number
|
|
}
|
|
|
|
export interface TemplatePayload {
|
|
name: string
|
|
abstract: string | null
|
|
fields: TemplateFieldInput[]
|
|
}
|
|
|
|
export async function listTemplates(
|
|
query: PageQuery = {},
|
|
): Promise<PageResult<TemplateListItem>> {
|
|
const params: Record<string, unknown> = {}
|
|
if (query.keyword) params.keyword = query.keyword
|
|
if (query.page) params.page = query.page
|
|
if (query.page_size) params.page_size = query.page_size
|
|
|
|
const { data } = await http.get<PageResult<TemplateListItem>>('/templates', { params })
|
|
return data
|
|
}
|
|
|
|
export async function getTemplate(id: number): Promise<TemplateDetail> {
|
|
const { data } = await http.get<TemplateDetail>(`/templates/${id}`)
|
|
return data
|
|
}
|
|
|
|
/** The API caps `page_size` at 200; a picker wants every template, so it loops. */
|
|
const TEMPLATE_PAGE_SIZE = 200
|
|
|
|
/**
|
|
* Fetch every template, following pagination.
|
|
*
|
|
* A template picker that showed only the first page would silently hide the
|
|
* template the user is looking for, and the paper view needs the whole list
|
|
* before it can offer a switch without a search round trip.
|
|
*/
|
|
export async function fetchAllTemplates(): Promise<TemplateListItem[]> {
|
|
const all: TemplateListItem[] = []
|
|
let page = 1
|
|
|
|
for (;;) {
|
|
const result = await listTemplates({ page, page_size: TEMPLATE_PAGE_SIZE })
|
|
all.push(...result.items)
|
|
if (page >= result.pages || result.items.length === 0) {
|
|
return all
|
|
}
|
|
page += 1
|
|
}
|
|
}
|
|
|
|
export async function createTemplate(payload: TemplatePayload): Promise<TemplateDetail> {
|
|
const { data } = await http.post<TemplateDetail>('/templates', payload)
|
|
return data
|
|
}
|
|
|
|
export async function updateTemplate(
|
|
id: number,
|
|
payload: Partial<TemplatePayload>,
|
|
): Promise<TemplateDetail> {
|
|
const { data } = await http.patch<TemplateDetail>(`/templates/${id}`, payload)
|
|
return data
|
|
}
|
|
|
|
export async function deleteTemplate(id: number): Promise<void> {
|
|
await http.delete(`/templates/${id}`)
|
|
}
|
|
|
|
export async function batchDeleteTemplates(ids: number[]): Promise<BatchDeleteResult> {
|
|
const { data } = await http.post<BatchDeleteResult>('/templates/batch-delete', { ids })
|
|
return data
|
|
}
|