diff --git a/xinquan/app.py b/xinquan/app.py
index e3aac8f..f4836ee 100644
--- a/xinquan/app.py
+++ b/xinquan/app.py
@@ -132,7 +132,10 @@ INDEX_HTML = r"""
button:hover,.btn:hover{opacity:.9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}
.tile{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:16px;
- text-decoration:none;color:var(--text);display:block;transition:.16s;box-shadow:0 2px 10px rgba(20,30,70,.05)}
+ text-decoration:none;color:var(--text);display:block;transition:.16s;box-shadow:0 2px 10px rgba(20,30,70,.05);
+ cursor:grab}
+ .tile:active{cursor:grabbing}
+ .tile.drag-over{border-color:var(--brand);box-shadow:0 0 0 2px rgba(99,102,241,.25)}
.tile:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(20,30,70,.1)}
.tile h3{font-size:16px;margin-bottom:6px}
.tile p{font-size:13px;color:var(--muted);line-height:1.5;
@@ -218,13 +221,32 @@ function renderList(){
`;
const a=$('#app');
if(!state.pages.length){ a.innerHTML=`
还没有内容。
`; return; }
- a.innerHTML = `${state.pages.map(p=>`
-
+ a.innerHTML = ``;
}
+let dragId=null;
+function dragStart(id){ dragId=id; }
+function dropEv(ev){
+ ev.preventDefault();
+ if(!dragId) return;
+ const tile=ev.target.closest('.tile');
+ if(!tile) return;
+ const tid=tile.getAttribute('data-id');
+ if(!tid || tid===dragId){ dragId=null; return; }
+ const from=state.pages.findIndex(p=>p.id===dragId);
+ const to=state.pages.findIndex(p=>p.id===tid);
+ if(from<0 || to<0){ dragId=null; return; }
+ const [moved]=state.pages.splice(from,1);
+ state.pages.splice(to,0,moved);
+ dragId=null;
+ renderList();
+ api('/api/pages/order',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({order:state.pages.map(p=>p.id)})})
+ .catch(e=>toast('排序保存失败: '+e.message));
+}
async function openPage(id){
try{
const p = await api('/api/pages/'+encodeURIComponent(id));
@@ -475,6 +497,8 @@ class Handler(BaseHTTPRequestHandler):
return self._logout()
if not self._user():
return self._auth_denied()
+ if path == "/api/pages/order":
+ return self._reorder()
if path == "/api/pages":
return self._create_page()
if path == "/api/upload":
@@ -514,9 +538,10 @@ class Handler(BaseHTTPRequestHandler):
pages = _read_pages()
out = [{"id": pid, "title": p.get("title", ""),
"summary": _summary(p.get("content", "")),
- "updated": p.get("updated")}
+ "updated": p.get("updated"),
+ "order": p.get("order", 1 << 30)}
for pid, p in pages.items()]
- out.sort(key=lambda x: x["updated"] or "", reverse=True)
+ out.sort(key=lambda x: x.get("order", 1 << 30))
self._json(out)
def _get_page(self, pid):
@@ -535,9 +560,10 @@ class Handler(BaseHTTPRequestHandler):
pages = _read_pages()
pid = uuid.uuid4().hex[:12]
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ nxt = max((p.get("order", 0) for p in pages.values()), default=-1) + 1
pages[pid] = {"title": data["title"].strip(),
"content": data.get("content", ""),
- "created": now, "updated": now}
+ "created": now, "updated": now, "order": nxt}
_write_pages(pages)
self._json({"id": pid}, 201)
@@ -566,6 +592,25 @@ class Handler(BaseHTTPRequestHandler):
_write_pages(pages)
self._json({"ok": True})
+ def _reorder(self):
+ data = self._read_json()
+ order = data.get("order") if isinstance(data, dict) else None
+ if not isinstance(order, list):
+ return self._json({"error": "bad order"}, 400)
+ with _lock:
+ pages = _read_pages()
+ for i, pid in enumerate(order):
+ if pid in pages:
+ pages[pid]["order"] = i
+ # 未在列表中的页面补一个靠后的 order
+ have = set(order)
+ big = len(order)
+ for pid in pages:
+ if pid not in have:
+ pages[pid]["order"] = pages[pid].get("order", big)
+ _write_pages(pages)
+ self._json({"ok": True})
+
# ---- 图片 ----
def _upload(self):
data = self._read_json()