Let users download shared notes as Markdown
The shared notes had no way out: everything lived in the SQLite row and the
only reader was the panel itself. Add two export endpoints and buttons for
them at the bottom of the note panel.
GET /api/notes/{page_id}/export returns one page as .md, and
GET /api/notes/export returns every page as a zip laid out as
<section>/<NN>_<title>.md. Pages are not concatenated into a single file on
purpose - the headings inside a page would collide with whatever heading
level we used as a page separator, so each page stays a verbatim file and the
numeric prefix carries the tab order instead.
Titles reach the filesystem here, so strip the Windows-forbidden characters
and control characters, cap the length, and send Content-Disposition with an
RFC 5987 filename* so Japanese titles survive. The front end deliberately
omits the anchor's download attribute to let that name win.
notes store: flush() now returns a promise so the download can wait for
pending edits to be saved before asking the server for the file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -212,10 +212,26 @@ onUnmounted(() => {
|
||||
<button class="create-btn" @click="store.addPage()">+ 最初のページを作成</button>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
:disabled="store.activeId === null"
|
||||
title="開いているページを Markdown ファイルとして保存"
|
||||
@click="store.activeId !== null && store.downloadPage(store.activeId)"
|
||||
>⬇ このページ (.md)</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
:disabled="store.pages.length === 0"
|
||||
title="全ページを ZIP(1ページ1 Markdown、セクションはフォルダ)として保存"
|
||||
@click="store.downloadAll()"
|
||||
>⬇ すべて (.zip)</button>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
ページのタイトル一覧はAIのシステムプロンプトに常に含まれます。本文はAIが必要に応じて読み出します。
|
||||
🏷 でページをセクションにまとめられます(セクション名をクリックすると変更・解除)。
|
||||
タブはドラッグで並べ替え・他のセクションへの移動ができます。
|
||||
⬇ でノートを手元にダウンロードできます(未保存の編集は保存してから書き出します)。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -355,6 +371,26 @@ onUnmounted(() => {
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.actions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #2a2a2a;
|
||||
}
|
||||
.action-btn {
|
||||
background: #1f1f1f;
|
||||
color: #aaa;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.action-btn:hover:not(:disabled) { background: #2a2a2a; color: #93c5fd; border-color: #444; }
|
||||
.action-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.hint {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 10px;
|
||||
|
||||
Reference in New Issue
Block a user