frontend: add the paper list, document view, and paragraph editor

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.
This commit is contained in:
2026-09-18 17:29:16 +08:00
parent 06d7e922bd
commit 4d749b3592
13 changed files with 2813 additions and 57 deletions
+24
View File
@@ -62,6 +62,30 @@ export async function getTemplate(id: number): Promise<TemplateDetail> {
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