update
This commit is contained in:
134
favorites.js
Normal file
134
favorites.js
Normal file
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
const Database = require('better-sqlite3');
|
||||
const { checkAgainstCap } = require('./lib/favorites-cap');
|
||||
const { likePatterns } = require('./lib/search-plan');
|
||||
|
||||
function open(dbPath) {
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('synchronous = NORMAL');
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS favorites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT NOT NULL,
|
||||
text TEXT,
|
||||
image BLOB,
|
||||
preview TEXT,
|
||||
source_clip_id INTEGER,
|
||||
created_at REAL NOT NULL,
|
||||
favorited_at REAL NOT NULL,
|
||||
UNIQUE(source_clip_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_fav_favorited_at ON favorites(favorited_at DESC);
|
||||
`);
|
||||
return db;
|
||||
}
|
||||
|
||||
function close(db) {
|
||||
if (db) db.close();
|
||||
}
|
||||
|
||||
// 列表不取 image BLOB —— 图片走 getImage() 按需拉。
|
||||
const LIST_COLS = `id, type, text, preview, source_clip_id, created_at, favorited_at,
|
||||
(image IS NOT NULL) AS has_image`;
|
||||
|
||||
// 每个连接一套预编译语句,避免每次调用都重新 prepare
|
||||
function stmts(db) {
|
||||
if (!db._stmt) {
|
||||
db._stmt = {
|
||||
insert: db.prepare(`
|
||||
INSERT INTO favorites(type, text, image, preview, source_clip_id, created_at, favorited_at)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?)
|
||||
`),
|
||||
getMeta: db.prepare(`SELECT ${LIST_COLS} FROM favorites WHERE id = ?`),
|
||||
getImage: db.prepare('SELECT image FROM favorites WHERE id = ?'),
|
||||
getRaw: db.prepare('SELECT * FROM favorites WHERE id = ?'),
|
||||
bySource: db.prepare('SELECT id FROM favorites WHERE source_clip_id = ?'),
|
||||
remove: db.prepare('DELETE FROM favorites WHERE id = ?'),
|
||||
count: db.prepare('SELECT COUNT(*) AS n FROM favorites'),
|
||||
list: db.prepare(`SELECT ${LIST_COLS} FROM favorites ORDER BY favorited_at DESC LIMIT ?`),
|
||||
};
|
||||
}
|
||||
return db._stmt;
|
||||
}
|
||||
|
||||
function rowToClip(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
id: row.id,
|
||||
type: row.type,
|
||||
text: row.text || '',
|
||||
hasImage: row.has_image != null ? row.has_image === 1 : row.image != null,
|
||||
preview: row.preview || '',
|
||||
sourceClipId: row.source_clip_id,
|
||||
createdAt: row.created_at,
|
||||
favoritedAt: row.favorited_at,
|
||||
};
|
||||
}
|
||||
|
||||
function add(db, sourceClip) {
|
||||
const cap = checkAgainstCap(db);
|
||||
if (!cap.ok) {
|
||||
const err = new Error(cap.message);
|
||||
err.ok = false; err.code = 'FAV_LIMIT';
|
||||
throw err;
|
||||
}
|
||||
const now = Date.now() / 1000;
|
||||
const info = stmts(db).insert.run(
|
||||
sourceClip.type,
|
||||
sourceClip.type === 'text' ? (sourceClip.text || '') : null,
|
||||
sourceClip.type === 'image' ? (sourceClip.image || null) : null,
|
||||
sourceClip.preview || '',
|
||||
sourceClip.id,
|
||||
sourceClip.createdAt || now,
|
||||
now,
|
||||
);
|
||||
return rowToClip(stmts(db).getMeta.get(info.lastInsertRowid));
|
||||
}
|
||||
|
||||
function isFavorited(db, clipId) {
|
||||
const row = stmts(db).bySource.get(clipId);
|
||||
return row ? row.id : null;
|
||||
}
|
||||
|
||||
function remove(db, favId) {
|
||||
return stmts(db).remove.run(favId).changes > 0;
|
||||
}
|
||||
|
||||
function get(db, favId) {
|
||||
return rowToClip(stmts(db).getMeta.get(favId));
|
||||
}
|
||||
|
||||
function getImage(db, favId) {
|
||||
const row = stmts(db).getImage.get(favId);
|
||||
if (!row || !row.image) return null;
|
||||
return Buffer.from(row.image).toString('base64');
|
||||
}
|
||||
|
||||
// 原始行(含 image BLOB),仅供主进程"复制收藏到系统剪贴板"用。
|
||||
// 字段形状(type / text / image)与 clips 表一致,可直接喂 lib/clip-payload。
|
||||
// 渲染进程不要用它 —— 图片走 getImage() 按需拉。
|
||||
function getRaw(db, favId) {
|
||||
return stmts(db).getRaw.get(favId) || null;
|
||||
}
|
||||
|
||||
function count(db) {
|
||||
return stmts(db).count.get().n;
|
||||
}
|
||||
|
||||
function list(db, query = '', limit = 5000) {
|
||||
const s = stmts(db);
|
||||
// 与历史搜索同语义:每个 token 独立子串 AND,% _ \ 已转义(lib/search-plan)。
|
||||
// 不转义时搜 "100%" 里的 % 会当通配符命中一片无关记录。
|
||||
const patterns = likePatterns(query);
|
||||
if (!patterns.length) return s.list.all(limit).map(rowToClip);
|
||||
const where = patterns.map(() => `text LIKE ? ESCAPE '\\'`).join(' AND ');
|
||||
const rows = db.prepare(
|
||||
`SELECT ${LIST_COLS} FROM favorites WHERE ${where} ORDER BY favorited_at DESC LIMIT ?`
|
||||
).all(...patterns, limit);
|
||||
return rows.map(rowToClip);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
open, close, rowToClip, add, isFavorited, remove, get, getImage, getRaw, count, list,
|
||||
};
|
||||
Reference in New Issue
Block a user