@qnote/q-ai-note 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/api/images.d.ts +3 -0
- package/dist/server/api/images.d.ts.map +1 -0
- package/dist/server/api/images.js +89 -0
- package/dist/server/api/images.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/web/app.js +295 -26
- package/dist/web/styles.css +62 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/server/api/images.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0FxB,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Router } from 'express';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import { getResolvedDbPath } from '../db.js';
|
|
6
|
+
import { forbid, getAccessContextFromResponse } from './accessHelpers.js';
|
|
7
|
+
const router = Router();
|
|
8
|
+
const MAX_IMAGE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
9
|
+
const IMAGE_EXT_BY_MIME = {
|
|
10
|
+
'image/png': 'png',
|
|
11
|
+
'image/jpeg': 'jpg',
|
|
12
|
+
'image/jpg': 'jpg',
|
|
13
|
+
'image/webp': 'webp',
|
|
14
|
+
'image/gif': 'gif',
|
|
15
|
+
};
|
|
16
|
+
function ensureDir(dirPath) {
|
|
17
|
+
if (!fs.existsSync(dirPath)) {
|
|
18
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function normalizeBase64(input) {
|
|
22
|
+
const raw = String(input || '').trim();
|
|
23
|
+
if (!raw)
|
|
24
|
+
return '';
|
|
25
|
+
const marker = ';base64,';
|
|
26
|
+
const markerIdx = raw.indexOf(marker);
|
|
27
|
+
if (raw.startsWith('data:') && markerIdx >= 0) {
|
|
28
|
+
return raw.slice(markerIdx + marker.length).trim();
|
|
29
|
+
}
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
function pickExtension(mimeType, filename = '') {
|
|
33
|
+
const mime = String(mimeType || '').trim().toLowerCase();
|
|
34
|
+
if (IMAGE_EXT_BY_MIME[mime])
|
|
35
|
+
return IMAGE_EXT_BY_MIME[mime];
|
|
36
|
+
const ext = path.extname(String(filename || '').trim()).replace('.', '').toLowerCase();
|
|
37
|
+
if (ext && ['png', 'jpg', 'jpeg', 'webp', 'gif'].includes(ext)) {
|
|
38
|
+
return ext === 'jpeg' ? 'jpg' : ext;
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
router.post('/', (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
const access = getAccessContextFromResponse(res);
|
|
45
|
+
const hasWritablePage = Boolean(access.full_access
|
|
46
|
+
|| access.page_access?.sandboxes
|
|
47
|
+
|| access.page_access?.diaries
|
|
48
|
+
|| access.page_access?.ai_engineering);
|
|
49
|
+
if (!hasWritablePage) {
|
|
50
|
+
return forbid(res, 'No permission to upload images');
|
|
51
|
+
}
|
|
52
|
+
const mimeType = String(req.body?.mime_type || '').trim().toLowerCase();
|
|
53
|
+
const filename = String(req.body?.filename || '').trim();
|
|
54
|
+
const dataBase64 = normalizeBase64(String(req.body?.data_base64 || ''));
|
|
55
|
+
if (!dataBase64) {
|
|
56
|
+
return res.status(400).json({ success: false, error: 'data_base64 is required' });
|
|
57
|
+
}
|
|
58
|
+
const extension = pickExtension(mimeType, filename);
|
|
59
|
+
if (!extension) {
|
|
60
|
+
return res.status(400).json({ success: false, error: 'Unsupported image type' });
|
|
61
|
+
}
|
|
62
|
+
const buffer = Buffer.from(dataBase64, 'base64');
|
|
63
|
+
if (!buffer.length) {
|
|
64
|
+
return res.status(400).json({ success: false, error: 'Invalid image payload' });
|
|
65
|
+
}
|
|
66
|
+
if (buffer.length > MAX_IMAGE_SIZE_BYTES) {
|
|
67
|
+
return res.status(413).json({ success: false, error: 'Image too large (max 20MB)' });
|
|
68
|
+
}
|
|
69
|
+
const dataDir = getResolvedDbPath();
|
|
70
|
+
const imagesDir = path.join(dataDir, 'images');
|
|
71
|
+
ensureDir(imagesDir);
|
|
72
|
+
const savedFileName = `${Date.now()}-${randomUUID()}.${extension}`;
|
|
73
|
+
const outputPath = path.join(imagesDir, savedFileName);
|
|
74
|
+
fs.writeFileSync(outputPath, buffer);
|
|
75
|
+
return res.status(201).json({
|
|
76
|
+
success: true,
|
|
77
|
+
data: {
|
|
78
|
+
url: `/images/${savedFileName}`,
|
|
79
|
+
file_name: savedFileName,
|
|
80
|
+
size_bytes: buffer.length,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return res.status(500).json({ success: false, error: String(error) });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
export default router;
|
|
89
|
+
//# sourceMappingURL=images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../../src/server/api/images.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AA6BxC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAkBD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,WAAW,GAAE,OAAO,GAAG,YAAoB,EAC3C,UAAU,GAAE,GAAG,CAAC,MAAM,CAAqB,EAC3C,mBAAmB,GAAE,GAAG,CAAC,MAAM,CAAqB,GACnD,OAAO,CAOT;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,MAAM,EACV,WAAW,GAAE,OAAO,GAAG,YAAoB,EAC3C,UAAU,GAAE,GAAG,CAAC,MAAM,CAAqB,GAC1C,OAAO,CAKT;AAYD,wBAAgB,SAAS,CAAC,WAAW,GAAE,OAAO,GAAG,YAAuB,+CA0EvE;AAED,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,SAAO,EACX,WAAW,GAAE,OAAO,GAAG,YAAuE,GAC7F,MAAM,CAmBR;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBhD"}
|
package/dist/server/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
5
|
import { initDb, closeDb } from './db.js';
|
|
6
|
+
import { getResolvedDbPath } from './db.js';
|
|
6
7
|
import sandboxRouter from './api/sandbox.js';
|
|
7
8
|
import workItemRouter from './api/workItem.js';
|
|
8
9
|
import diaryRouter from './api/diary.js';
|
|
@@ -11,6 +12,7 @@ import settingsRouter from './api/settings.js';
|
|
|
11
12
|
import nodeEntitiesRouter from './api/nodeEntities.js';
|
|
12
13
|
import projectSettingsRouter from './api/projectSettings.js';
|
|
13
14
|
import aserRuntimeRouter from './api/aserRuntime.js';
|
|
15
|
+
import imagesRouter from './api/images.js';
|
|
14
16
|
import { getAccessContext, normalizeIpAddress as normalizeIpAddressFromAccess, } from './accessControl.js';
|
|
15
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
18
|
const PORT = Number(process.env.PORT || 3000);
|
|
@@ -72,9 +74,10 @@ export function createApp(inputPolicy = READONLY) {
|
|
|
72
74
|
localIpSet,
|
|
73
75
|
});
|
|
74
76
|
const app = express();
|
|
75
|
-
app.use(express.json());
|
|
77
|
+
app.use(express.json({ limit: '25mb' }));
|
|
76
78
|
app.use('/node_modules', express.static(path.join(__dirname, '../../node_modules')));
|
|
77
79
|
app.use(express.static(path.join(__dirname, '../web')));
|
|
80
|
+
app.use('/images', express.static(path.join(getResolvedDbPath(), 'images')));
|
|
78
81
|
app.get('/api/runtime', (req, res) => {
|
|
79
82
|
const access = getRequestAccessContext(req);
|
|
80
83
|
res.json({
|
|
@@ -119,6 +122,7 @@ export function createApp(inputPolicy = READONLY) {
|
|
|
119
122
|
app.use('/api/items', workItemRouter);
|
|
120
123
|
app.use('/api/diaries', diaryRouter);
|
|
121
124
|
app.use('/api/chats', chatRouter);
|
|
125
|
+
app.use('/api/images', imagesRouter);
|
|
122
126
|
app.use('/api/settings', settingsRouter);
|
|
123
127
|
app.use('/api/project-settings', projectSettingsRouter);
|
|
124
128
|
app.use('/api/sandboxes/:sandboxId/entities', nodeEntitiesRouter);
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/web/app.js
CHANGED
|
@@ -1209,20 +1209,27 @@ function renderWorkTree() {
|
|
|
1209
1209
|
if (fromIdx < 0) return;
|
|
1210
1210
|
const toIdx = fromIdx + delta;
|
|
1211
1211
|
if (toIdx < 0 || toIdx >= siblings.length) return;
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1212
|
+
const reordered = [...siblings];
|
|
1213
|
+
const [movedNode] = reordered.splice(fromIdx, 1);
|
|
1214
|
+
reordered.splice(toIdx, 0, movedNode);
|
|
1215
|
+
const left = reordered[toIdx - 1] || null;
|
|
1216
|
+
const right = reordered[toIdx + 1] || null;
|
|
1215
1217
|
const nextOrderKey = rankBetween(getNodeOrderKey(left, 'order_key'), getNodeOrderKey(right, 'order_key'));
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1218
|
+
const canDirectUpdate = nextOrderKey !== getNodeOrderKey(node, 'order_key')
|
|
1219
|
+
&& isOrderKeyBetween(getNodeOrderKey(left, 'order_key'), getNodeOrderKey(right, 'order_key'), nextOrderKey);
|
|
1220
|
+
if (canDirectUpdate) {
|
|
1221
|
+
await apiRequest(`${API_BASE}/items/${nodeId}`, {
|
|
1222
|
+
method: 'PUT',
|
|
1223
|
+
body: JSON.stringify({
|
|
1224
|
+
extra_data: {
|
|
1225
|
+
...(node.extra_data || {}),
|
|
1226
|
+
order_key: nextOrderKey,
|
|
1227
|
+
},
|
|
1228
|
+
}),
|
|
1229
|
+
});
|
|
1230
|
+
} else {
|
|
1231
|
+
await persistSiblingOrderByList(reordered, parentId);
|
|
1232
|
+
}
|
|
1226
1233
|
await loadSandbox(state.currentSandbox.id);
|
|
1227
1234
|
},
|
|
1228
1235
|
onReorderSiblings: treeReadonly ? undefined : async (dragNodeId, targetNodeId, position) => {
|
|
@@ -1243,19 +1250,30 @@ function renderWorkTree() {
|
|
|
1243
1250
|
const targetIndex = siblings.findIndex((item) => item.id === targetNodeId);
|
|
1244
1251
|
if (targetIndex < 0) return;
|
|
1245
1252
|
const insertIndex = position === 'after' ? targetIndex + 1 : targetIndex;
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1253
|
+
const reordered = [...siblings];
|
|
1254
|
+
reordered.splice(insertIndex, 0, dragNode);
|
|
1255
|
+
const left = reordered[insertIndex - 1] || null;
|
|
1256
|
+
const right = reordered[insertIndex + 1] || null;
|
|
1248
1257
|
const nextOrderKey = rankBetween(getNodeOrderKey(left, 'order_key'), getNodeOrderKey(right, 'order_key'));
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
parent_id
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1258
|
+
const canDirectUpdate = isOrderKeyBetween(getNodeOrderKey(left, 'order_key'), getNodeOrderKey(right, 'order_key'), nextOrderKey)
|
|
1259
|
+
&& (
|
|
1260
|
+
nextOrderKey !== getNodeOrderKey(dragNode, 'order_key')
|
|
1261
|
+
|| (dragNode.parent_id || null) !== nextParentId
|
|
1262
|
+
);
|
|
1263
|
+
if (canDirectUpdate) {
|
|
1264
|
+
await apiRequest(`${API_BASE}/items/${dragNodeId}`, {
|
|
1265
|
+
method: 'PUT',
|
|
1266
|
+
body: JSON.stringify({
|
|
1267
|
+
parent_id: nextParentId,
|
|
1268
|
+
extra_data: {
|
|
1269
|
+
...(dragNode.extra_data || {}),
|
|
1270
|
+
order_key: nextOrderKey,
|
|
1271
|
+
},
|
|
1272
|
+
}),
|
|
1273
|
+
});
|
|
1274
|
+
} else {
|
|
1275
|
+
await persistSiblingOrderByList(reordered, nextParentId, dragNodeId);
|
|
1276
|
+
}
|
|
1259
1277
|
await loadSandbox(state.currentSandbox.id);
|
|
1260
1278
|
},
|
|
1261
1279
|
onReorderLanes: treeReadonly ? undefined : async (dragRootId, targetRootId, position = 'before') => {
|
|
@@ -1409,6 +1427,55 @@ function rankBetween(left, right) {
|
|
|
1409
1427
|
return `${prefix}${ORDER_ALPHABET[Math.floor(ORDER_BASE / 2)]}`;
|
|
1410
1428
|
}
|
|
1411
1429
|
|
|
1430
|
+
function encodeOrderIndex(index, width = 6) {
|
|
1431
|
+
let value = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
|
|
1432
|
+
let encoded = '';
|
|
1433
|
+
do {
|
|
1434
|
+
const digit = value % ORDER_BASE;
|
|
1435
|
+
encoded = `${ORDER_ALPHABET[digit]}${encoded}`;
|
|
1436
|
+
value = Math.floor(value / ORDER_BASE);
|
|
1437
|
+
} while (value > 0);
|
|
1438
|
+
return encoded.padStart(width, ORDER_ALPHABET[0]);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function isOrderKeyBetween(leftKey, rightKey, candidateKey) {
|
|
1442
|
+
const left = String(leftKey || '');
|
|
1443
|
+
const right = String(rightKey || '');
|
|
1444
|
+
const candidate = String(candidateKey || '');
|
|
1445
|
+
if (!candidate) return false;
|
|
1446
|
+
if (left && candidate <= left) return false;
|
|
1447
|
+
if (right && candidate >= right) return false;
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
async function persistSiblingOrderByList(orderedItems, nextParentId = null, movedNodeId = '') {
|
|
1452
|
+
const items = Array.isArray(orderedItems) ? orderedItems : [];
|
|
1453
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
1454
|
+
const item = items[index];
|
|
1455
|
+
if (!item?.id) continue;
|
|
1456
|
+
const targetKey = encodeOrderIndex((index + 1) * 1024);
|
|
1457
|
+
const currentKey = getNodeOrderKey(item, 'order_key');
|
|
1458
|
+
const isMovedNode = Boolean(movedNodeId) && String(item.id) === String(movedNodeId);
|
|
1459
|
+
const currentParentId = item.parent_id || null;
|
|
1460
|
+
const needsParentUpdate = isMovedNode && currentParentId !== nextParentId;
|
|
1461
|
+
const needsOrderUpdate = currentKey !== targetKey;
|
|
1462
|
+
if (!needsParentUpdate && !needsOrderUpdate) continue;
|
|
1463
|
+
const body = {
|
|
1464
|
+
extra_data: {
|
|
1465
|
+
...(item.extra_data || {}),
|
|
1466
|
+
order_key: targetKey,
|
|
1467
|
+
},
|
|
1468
|
+
};
|
|
1469
|
+
if (needsParentUpdate) {
|
|
1470
|
+
body.parent_id = nextParentId;
|
|
1471
|
+
}
|
|
1472
|
+
await apiRequest(`${API_BASE}/items/${item.id}`, {
|
|
1473
|
+
method: 'PUT',
|
|
1474
|
+
body: JSON.stringify(body),
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1412
1479
|
function getNodeOrderKey(item, keyName = 'order_key') {
|
|
1413
1480
|
return String(item?.extra_data?.[keyName] || '').trim();
|
|
1414
1481
|
}
|
|
@@ -1525,10 +1592,15 @@ function renderMarkdownSnippet(text, options = {}) {
|
|
|
1525
1592
|
|
|
1526
1593
|
const renderInline = (raw) => {
|
|
1527
1594
|
let html = safeText(String(raw || ''));
|
|
1595
|
+
html = html.replace(/!\[([^\]]*)\]\(((?:https?:\/\/|\/)[^\s)]+)\)/g, (_m, alt, url) => {
|
|
1596
|
+
const safeUrl = safeText(url);
|
|
1597
|
+
const safeAlt = safeText(alt || 'image');
|
|
1598
|
+
return `<img src="${safeUrl}" alt="${safeAlt}" loading="lazy">`;
|
|
1599
|
+
});
|
|
1528
1600
|
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1529
1601
|
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
1530
1602
|
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
1531
|
-
html = html.replace(/\[([^\]]+)\]\((https
|
|
1603
|
+
html = html.replace(/\[([^\]]+)\]\(((?:https?:\/\/|\/)[^\s)]+)\)/g, (_m, label, url) => {
|
|
1532
1604
|
const safeUrl = safeText(url);
|
|
1533
1605
|
const safeLabel = safeText(label);
|
|
1534
1606
|
return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>`;
|
|
@@ -3667,8 +3739,204 @@ function editWorkItem(id) {
|
|
|
3667
3739
|
document.getElementById('item-dialog').showModal();
|
|
3668
3740
|
}
|
|
3669
3741
|
|
|
3742
|
+
const MARKDOWN_PASTE_TEXTAREA_IDS = [
|
|
3743
|
+
'diary-content',
|
|
3744
|
+
'diary-edit-content',
|
|
3745
|
+
'entity-content-input',
|
|
3746
|
+
'drawer-diary-content',
|
|
3747
|
+
'drawer-work-item-description',
|
|
3748
|
+
'new-item-desc',
|
|
3749
|
+
];
|
|
3750
|
+
|
|
3751
|
+
function readFileAsDataUrl(file) {
|
|
3752
|
+
return new Promise((resolve, reject) => {
|
|
3753
|
+
const reader = new FileReader();
|
|
3754
|
+
reader.onload = () => resolve(String(reader.result || ''));
|
|
3755
|
+
reader.onerror = () => reject(new Error('读取图片失败'));
|
|
3756
|
+
reader.readAsDataURL(file);
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
function isStandaloneHttpUrl(text) {
|
|
3761
|
+
const value = String(text || '').trim();
|
|
3762
|
+
if (!value || /\s/.test(value)) return false;
|
|
3763
|
+
return /^https?:\/\/[^\s]+$/i.test(value);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
function deriveMarkdownLinkName(urlText) {
|
|
3767
|
+
try {
|
|
3768
|
+
const parsed = new URL(String(urlText || '').trim());
|
|
3769
|
+
const segments = String(parsed.pathname || '')
|
|
3770
|
+
.split('/')
|
|
3771
|
+
.map((segment) => segment.trim())
|
|
3772
|
+
.filter((segment) => Boolean(segment));
|
|
3773
|
+
if (!segments.length) return '链接';
|
|
3774
|
+
const lastSegment = decodeURIComponent(segments[segments.length - 1] || '').trim();
|
|
3775
|
+
if (!lastSegment) return '链接';
|
|
3776
|
+
const withoutExt = lastSegment
|
|
3777
|
+
.replace(/\.(?:html?|xhtml|shtml|php|asp|aspx|jsp)$/i, '')
|
|
3778
|
+
.trim();
|
|
3779
|
+
const normalized = withoutExt
|
|
3780
|
+
.replace(/[-_]+/g, ' ')
|
|
3781
|
+
.replace(/\s+/g, ' ')
|
|
3782
|
+
.trim();
|
|
3783
|
+
return normalized || '链接';
|
|
3784
|
+
} catch {
|
|
3785
|
+
return '链接';
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
function insertTextAtCursor(textarea, text) {
|
|
3790
|
+
const start = Number(textarea.selectionStart || 0);
|
|
3791
|
+
const end = Number(textarea.selectionEnd || 0);
|
|
3792
|
+
const value = String(textarea.value || '');
|
|
3793
|
+
const prefix = value.slice(0, start);
|
|
3794
|
+
const suffix = value.slice(end);
|
|
3795
|
+
const prefixBreak = prefix.length > 0 && !prefix.endsWith('\n') ? '\n' : '';
|
|
3796
|
+
const suffixBreak = suffix.length > 0 && !suffix.startsWith('\n') ? '\n' : '';
|
|
3797
|
+
const inserted = `${prefixBreak}${text}${suffixBreak}`;
|
|
3798
|
+
textarea.value = `${prefix}${inserted}${suffix}`;
|
|
3799
|
+
const nextCaret = prefix.length + inserted.length;
|
|
3800
|
+
textarea.selectionStart = nextCaret;
|
|
3801
|
+
textarea.selectionEnd = nextCaret;
|
|
3802
|
+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
async function uploadPastedImage(file) {
|
|
3806
|
+
const dataUrl = await readFileAsDataUrl(file);
|
|
3807
|
+
const marker = ';base64,';
|
|
3808
|
+
const markerIdx = dataUrl.indexOf(marker);
|
|
3809
|
+
if (markerIdx < 0) {
|
|
3810
|
+
throw new Error('无法解析图片数据');
|
|
3811
|
+
}
|
|
3812
|
+
const dataBase64 = dataUrl.slice(markerIdx + marker.length).trim();
|
|
3813
|
+
const response = await fetch(`${API_BASE}/images`, {
|
|
3814
|
+
method: 'POST',
|
|
3815
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3816
|
+
body: JSON.stringify({
|
|
3817
|
+
filename: file.name || '',
|
|
3818
|
+
mime_type: file.type || '',
|
|
3819
|
+
data_base64: dataBase64,
|
|
3820
|
+
}),
|
|
3821
|
+
});
|
|
3822
|
+
const result = await response.json();
|
|
3823
|
+
if (!response.ok || !result?.success || !result?.data?.url) {
|
|
3824
|
+
throw new Error(result?.error || '图片上传失败');
|
|
3825
|
+
}
|
|
3826
|
+
return String(result.data.url);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
function bindMarkdownPasteForTextarea(textarea) {
|
|
3830
|
+
if (!(textarea instanceof HTMLTextAreaElement)) return;
|
|
3831
|
+
if (textarea.dataset.mdPasteImageBound === '1') return;
|
|
3832
|
+
textarea.dataset.mdPasteImageBound = '1';
|
|
3833
|
+
textarea.addEventListener('paste', async (event) => {
|
|
3834
|
+
const items = Array.from(event.clipboardData?.items || []);
|
|
3835
|
+
const imageFiles = items
|
|
3836
|
+
.filter((item) => item.kind === 'file' && String(item.type || '').startsWith('image/'))
|
|
3837
|
+
.map((item) => item.getAsFile())
|
|
3838
|
+
.filter((file) => Boolean(file));
|
|
3839
|
+
if (imageFiles.length) {
|
|
3840
|
+
event.preventDefault();
|
|
3841
|
+
const errors = [];
|
|
3842
|
+
for (const file of imageFiles) {
|
|
3843
|
+
try {
|
|
3844
|
+
const imageUrl = await uploadPastedImage(file);
|
|
3845
|
+
insertTextAtCursor(textarea, ``);
|
|
3846
|
+
} catch (error) {
|
|
3847
|
+
errors.push(error);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
if (errors.length) {
|
|
3851
|
+
alert(`粘贴图片失败:${String(errors[0]?.message || errors[0])}`);
|
|
3852
|
+
}
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
const plainText = String(event.clipboardData?.getData('text/plain') || '').trim();
|
|
3856
|
+
if (!isStandaloneHttpUrl(plainText)) return;
|
|
3857
|
+
const linkName = deriveMarkdownLinkName(plainText);
|
|
3858
|
+
event.preventDefault();
|
|
3859
|
+
insertTextAtCursor(textarea, `[${linkName}](${plainText})`);
|
|
3860
|
+
if (typeof textarea.focus === 'function') {
|
|
3861
|
+
textarea.focus();
|
|
3862
|
+
}
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
function initMarkdownImagePasteSupport() {
|
|
3867
|
+
MARKDOWN_PASTE_TEXTAREA_IDS.forEach((id) => {
|
|
3868
|
+
const el = document.getElementById(id);
|
|
3869
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
3870
|
+
bindMarkdownPasteForTextarea(el);
|
|
3871
|
+
}
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
let markdownImageLightboxEl = null;
|
|
3876
|
+
|
|
3877
|
+
function ensureMarkdownImageLightbox() {
|
|
3878
|
+
if (markdownImageLightboxEl instanceof HTMLElement) {
|
|
3879
|
+
return markdownImageLightboxEl;
|
|
3880
|
+
}
|
|
3881
|
+
const overlay = document.createElement('div');
|
|
3882
|
+
overlay.className = 'image-lightbox hidden';
|
|
3883
|
+
overlay.id = 'image-lightbox';
|
|
3884
|
+
overlay.innerHTML = `
|
|
3885
|
+
<div class="image-lightbox-backdrop" data-action="close"></div>
|
|
3886
|
+
<div class="image-lightbox-content">
|
|
3887
|
+
<img id="image-lightbox-img" alt="preview image">
|
|
3888
|
+
</div>
|
|
3889
|
+
`;
|
|
3890
|
+
document.body.appendChild(overlay);
|
|
3891
|
+
overlay.addEventListener('click', (event) => {
|
|
3892
|
+
const target = event.target;
|
|
3893
|
+
if (target instanceof HTMLElement && target.dataset.action === 'close') {
|
|
3894
|
+
event.preventDefault();
|
|
3895
|
+
event.stopPropagation();
|
|
3896
|
+
overlay.classList.add('hidden');
|
|
3897
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
3898
|
+
}
|
|
3899
|
+
});
|
|
3900
|
+
markdownImageLightboxEl = overlay;
|
|
3901
|
+
return overlay;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
function openMarkdownImageLightbox(src, alt = '') {
|
|
3905
|
+
const overlay = ensureMarkdownImageLightbox();
|
|
3906
|
+
const img = overlay.querySelector('#image-lightbox-img');
|
|
3907
|
+
if (!(img instanceof HTMLImageElement)) return;
|
|
3908
|
+
img.src = String(src || '');
|
|
3909
|
+
img.alt = String(alt || 'preview image');
|
|
3910
|
+
overlay.classList.remove('hidden');
|
|
3911
|
+
overlay.setAttribute('aria-hidden', 'false');
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
function initMarkdownImageLightboxSupport() {
|
|
3915
|
+
document.addEventListener('click', (event) => {
|
|
3916
|
+
const target = event.target;
|
|
3917
|
+
if (!(target instanceof HTMLImageElement)) return;
|
|
3918
|
+
if (!target.closest('.diary-content, .entity-content, .chat-message.assistant')) return;
|
|
3919
|
+
const src = String(target.getAttribute('src') || '').trim();
|
|
3920
|
+
if (!src) return;
|
|
3921
|
+
event.preventDefault();
|
|
3922
|
+
openMarkdownImageLightbox(src, String(target.getAttribute('alt') || ''));
|
|
3923
|
+
});
|
|
3924
|
+
document.addEventListener('keydown', (event) => {
|
|
3925
|
+
if (event.key !== 'Escape') return;
|
|
3926
|
+
const overlay = markdownImageLightboxEl;
|
|
3927
|
+
if (!(overlay instanceof HTMLElement)) return;
|
|
3928
|
+
if (overlay.classList.contains('hidden')) return;
|
|
3929
|
+
event.preventDefault();
|
|
3930
|
+
event.stopPropagation();
|
|
3931
|
+
overlay.classList.add('hidden');
|
|
3932
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3670
3936
|
async function initApp() {
|
|
3671
3937
|
await loadRuntimeMode();
|
|
3938
|
+
initMarkdownImagePasteSupport();
|
|
3939
|
+
initMarkdownImageLightboxSupport();
|
|
3672
3940
|
loadWorkItemAssigneePreference();
|
|
3673
3941
|
loadWorkTreeViewModePreference();
|
|
3674
3942
|
loadSandboxSortModePreference();
|
|
@@ -4198,6 +4466,7 @@ async function initApp() {
|
|
|
4198
4466
|
if (!drawer || drawer.classList.contains('hidden')) return;
|
|
4199
4467
|
const target = event.target;
|
|
4200
4468
|
if (!(target instanceof Node)) return;
|
|
4469
|
+
if (target instanceof HTMLElement && target.closest('#image-lightbox')) return;
|
|
4201
4470
|
if (drawer.contains(target)) return;
|
|
4202
4471
|
if (workTree?.contains(target)) return;
|
|
4203
4472
|
closeNodeEntityDrawer();
|
package/dist/web/styles.css
CHANGED
|
@@ -2900,6 +2900,68 @@ h2 {
|
|
|
2900
2900
|
font-size: 12px;
|
|
2901
2901
|
}
|
|
2902
2902
|
|
|
2903
|
+
.diary-content img,
|
|
2904
|
+
.entity-content img,
|
|
2905
|
+
.chat-message.assistant img {
|
|
2906
|
+
max-width: min(100%, 820px);
|
|
2907
|
+
max-height: min(56vh, 520px);
|
|
2908
|
+
height: auto;
|
|
2909
|
+
width: auto;
|
|
2910
|
+
object-fit: contain;
|
|
2911
|
+
border-radius: 6px;
|
|
2912
|
+
display: block;
|
|
2913
|
+
margin: 10px 0;
|
|
2914
|
+
cursor: zoom-in;
|
|
2915
|
+
border: 1px solid #d8e0ec;
|
|
2916
|
+
background: #f8fafd;
|
|
2917
|
+
box-shadow: 0 6px 16px rgba(18, 42, 76, 0.12);
|
|
2918
|
+
padding: 4px;
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
.image-lightbox {
|
|
2922
|
+
position: fixed;
|
|
2923
|
+
inset: 0;
|
|
2924
|
+
z-index: 2000;
|
|
2925
|
+
display: flex;
|
|
2926
|
+
align-items: center;
|
|
2927
|
+
justify-content: center;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
.image-lightbox.hidden {
|
|
2931
|
+
display: none;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
.image-lightbox-backdrop {
|
|
2935
|
+
position: absolute;
|
|
2936
|
+
inset: 0;
|
|
2937
|
+
background: rgba(0, 0, 0, 0.68);
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
.image-lightbox-content {
|
|
2941
|
+
position: relative;
|
|
2942
|
+
width: auto;
|
|
2943
|
+
max-width: 92vw;
|
|
2944
|
+
max-height: 92vh;
|
|
2945
|
+
padding: 0;
|
|
2946
|
+
border-radius: 0;
|
|
2947
|
+
background: transparent;
|
|
2948
|
+
box-shadow: none;
|
|
2949
|
+
display: flex;
|
|
2950
|
+
align-items: center;
|
|
2951
|
+
justify-content: center;
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
.image-lightbox-content img {
|
|
2955
|
+
display: block;
|
|
2956
|
+
max-width: 92vw;
|
|
2957
|
+
max-height: 92vh;
|
|
2958
|
+
width: auto;
|
|
2959
|
+
height: auto;
|
|
2960
|
+
border-radius: 6px;
|
|
2961
|
+
margin: 0;
|
|
2962
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35);
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2903
2965
|
.diary-actions {
|
|
2904
2966
|
display: flex;
|
|
2905
2967
|
flex-wrap: wrap;
|