picgo-plugin-gitee 2.1.1 → 2.1.3

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.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/src/index.js +198 -82
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picgo-plugin-gitee",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "picgo uploader for gitee",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -31,7 +31,8 @@
31
31
  "release:patch": "npm run bump:patch && npm publish",
32
32
  "release:minor": "npm run bump:minor && npm publish",
33
33
  "release:major": "npm run bump:major && npm publish",
34
- "prepublishOnly": "node --check src/index.js"
34
+ "prepublishOnly": "node --check src/index.js",
35
+ "publish": "npm publish"
35
36
  },
36
37
  "keywords": [
37
38
  "picgo",
package/src/index.js CHANGED
@@ -22,14 +22,87 @@ module.exports = (ctx) => {
22
22
  const guiMenu = (ctx) => {
23
23
  return [
24
24
  {
25
- label: " Gitee 同步远程文件到相册",
25
+ label: "同步 Gitee 远程文件到相册",
26
26
  async handle(ctx, guiApi) {
27
- await syncRemoteToGallery(ctx, guiApi);
27
+ // picgo GUI 有时会把 async handle 里的异常静默吞掉。
28
+ // 手动加 try/catch 确保任何错误都能反馈到 UI 和日志。
29
+ const started = Date.now();
30
+ log("info", "[同步]按钮被点击");
31
+ log("info", `[同步]ctx 类型: ${typeof ctx}, guiApi 类型: ${typeof guiApi}`);
32
+ log(
33
+ "info",
34
+ `[同步]guiApi.galleryDB: ${guiApi && !!guiApi.galleryDB}, guiApi.showNotification: ${guiApi && !!guiApi.showNotification}`
35
+ );
36
+ try {
37
+ await syncRemoteToGallery(ctx, guiApi);
38
+ log("success", `[同步]完成,耗时 ${Date.now() - started}ms`);
39
+ } catch (err) {
40
+ const msg = err && err.message ? err.message : String(err);
41
+ const stack = err && err.stack ? err.stack : "";
42
+ log("error", "[同步]失败:" + msg);
43
+ if (stack) log("error", stack);
44
+ // 通道 1:ctx.emit('notification') — PicGo 主窗口消息中心(最可靠)
45
+ try {
46
+ if (ctx && ctx.emit) {
47
+ ctx.emit("notification", {
48
+ title: "Gitee 同步失败",
49
+ body: msg,
50
+ });
51
+ }
52
+ } catch (e) {
53
+ log("error", "[同步]ctx.emit 失败:" + e.message);
54
+ }
55
+ // 通道 2:showMessageBox — 系统弹窗,最醒目
56
+ if (guiApi && guiApi.showMessageBox) {
57
+ try {
58
+ guiApi.showMessageBox({
59
+ type: "error",
60
+ title: "Gitee 同步失败",
61
+ message: msg,
62
+ detail: stack ? stack.split("\n").slice(0, 3).join("\n") : "",
63
+ });
64
+ } catch (e) {
65
+ log("error", "[同步]showMessageBox 失败:" + e.message);
66
+ }
67
+ }
68
+ // 通道 3:showNotification — 系统通知(依赖 OS 权限)
69
+ if (guiApi && guiApi.showNotification) {
70
+ try {
71
+ guiApi.showNotification({
72
+ title: "Gitee 同步失败",
73
+ body: msg,
74
+ });
75
+ } catch (e) {
76
+ log("error", "[同步]showNotification 失败:" + e.message);
77
+ }
78
+ }
79
+ }
28
80
  },
29
81
  },
30
82
  ];
31
83
  };
32
84
 
85
+ // 统一的日志输出,兼容 picgo-core (ctx.log.info) 和 picgo GUI (ctx.log 可能行为不同)
86
+ // 同时打 console 兜底,确保 Electron 主进程也能看到
87
+ const log = function (level, msg) {
88
+ try {
89
+ // picgo 的 ctx.log 支持 info / warn / success / error
90
+ const fn = ctx.log && (ctx.log[level] || ctx.log.info);
91
+ if (fn) fn.call(ctx.log, msg);
92
+ } catch (e) {
93
+ // 静吞
94
+ }
95
+ // 兜底:直接打到 stdout,这样无论 picgo GUI 把日志写到哪,这里都至少有一条记录
96
+ // (用户终端如果通过 npm 链接加载插件,能看到;GUI 主进程 console 也能看到)
97
+ if (typeof console !== "undefined") {
98
+ try {
99
+ console.log("[picgo-plugin-gitee]", level.toUpperCase(), msg);
100
+ } catch (e) {
101
+ // 静吞
102
+ }
103
+ }
104
+ };
105
+
33
106
  // 相册里只保留图片文件,避免把 README/配置文件等也拉进来
34
107
  const IMAGE_EXTS = new Set([
35
108
  "jpg", "jpeg", "png", "gif", "webp", "bmp", "svg",
@@ -90,66 +163,54 @@ module.exports = (ctx) => {
90
163
 
91
164
  // 把 Gitee 远程文件写入 PicGo GUI 相册。
92
165
  // 关键点:每条记录带 type="gitee",这样从相册删除时会触发 onRemove 真正删除 gitee 上的文件。
166
+ // 异常统一向上抛出,由 guiMenu.handle 里的 catch 统一弹窗。
93
167
  const syncRemoteToGallery = async function (ctx, guiApi) {
94
168
  if (!guiApi || !guiApi.galleryDB) {
95
- ctx.log.info(
96
- "[同步操作]失败当前:当前不在 PicGo GUI 环境(galleryDB 不可用)"
97
- );
98
- return;
169
+ throw new Error("当前不在 PicGo GUI 环境(galleryDB 不可用)");
99
170
  }
171
+ log("info", "[同步]开始检查配置");
100
172
  let userConfig;
101
173
  try {
102
174
  userConfig = getUserConfig();
103
175
  } catch (err) {
104
- guiApi.showNotification &&
105
- guiApi.showNotification({
106
- title: "Gitee 同步失败",
107
- body: "请先在插件设置中填写 owner / repo / token",
108
- });
109
- return;
176
+ throw new Error("请先在插件设置中填写 owner / repo / token");
110
177
  }
111
- if (
112
- !userConfig.owner ||
113
- !userConfig.repo ||
114
- !userConfig.token
115
- ) {
116
- guiApi.showNotification &&
117
- guiApi.showNotification({
118
- title: "Gitee 同步失败",
119
- body: "请先在插件设置中填写 owner / repo / token",
120
- });
121
- return;
178
+ if (!userConfig.owner || !userConfig.repo || !userConfig.token) {
179
+ throw new Error("请先在插件设置中填写 owner / repo / token");
122
180
  }
181
+ log(
182
+ "info",
183
+ `[同步]配置 OK: owner=${userConfig.owner} repo=${userConfig.repo} path=${userConfig.path || "(root)"}`
184
+ );
123
185
 
124
- guiApi.showNotification &&
125
- guiApi.showNotification({
126
- title: "正在同步",
127
- body: "正在从 Gitee 拉取远程文件...",
128
- });
186
+ notify("success", ctx, guiApi, "正在同步", "正在从 Gitee 拉取远程文件...");
129
187
 
130
- let remoteFiles;
131
- try {
132
- remoteFiles = await listAllRemoteFiles(userConfig);
133
- } catch (err) {
134
- guiApi.showNotification &&
135
- guiApi.showNotification({
136
- title: "Gitee 同步失败",
137
- body: err.message,
138
- });
139
- return;
140
- }
188
+ log("info", "[同步]开始递归拉取 Gitee 文件列表");
189
+ const remoteFiles = await listAllRemoteFiles(userConfig);
190
+ log("info", `[同步]仓库共 ${remoteFiles.length} 个图片文件`);
141
191
 
142
192
  if (remoteFiles.length === 0) {
143
- guiApi.showNotification &&
144
- guiApi.showNotification({
145
- title: "Gitee 同步完成",
146
- body: "仓库里没有任何文件",
147
- });
193
+ log("warn", "[同步]仓库里没有任何图片文件");
194
+ notify(
195
+ "success",
196
+ ctx,
197
+ guiApi,
198
+ "Gitee 同步完成",
199
+ "仓库里没有任何图片文件"
200
+ );
148
201
  return;
149
202
  }
150
203
 
151
- // 相册里已有的就不重复插入
152
- const existing = (await guiApi.galleryDB.get()) || [];
204
+ log("info", "[同步]读取本地相册");
205
+ // galleryDB.get() 返回 { total, data }(picgo/store 格式),
206
+ // 也可能直接返回数组(早期版本兼容)。两种都处理。
207
+ const getResult = await guiApi.galleryDB.get();
208
+ const existing = Array.isArray(getResult)
209
+ ? getResult
210
+ : getResult && Array.isArray(getResult.data)
211
+ ? getResult.data
212
+ : [];
213
+ log("info", `[同步]本地相册已有 ${existing.length} 条`);
153
214
  const existingUrls = new Set(existing.map((x) => x.imgUrl));
154
215
  const newItems = remoteFiles
155
216
  .filter((f) => !existingUrls.has(f.imgUrl))
@@ -158,30 +219,78 @@ module.exports = (ctx) => {
158
219
  imgUrl: f.imgUrl,
159
220
  type: uploadedName,
160
221
  sha: f.sha,
161
- // picgo 相册的可选字段
162
222
  extname: f.fileName.split(".").pop(),
163
223
  }));
224
+ log(
225
+ "info",
226
+ `[同步]需要新增 ${newItems.length} 条(已存在 ${remoteFiles.length - newItems.length} 条)`
227
+ );
164
228
 
165
229
  if (newItems.length === 0) {
166
- guiApi.showNotification &&
167
- guiApi.showNotification({
168
- title: "Gitee 同步完成",
169
- body: "没有新增文件,相册已是最新",
170
- });
230
+ notify(
231
+ "success",
232
+ ctx,
233
+ guiApi,
234
+ "Gitee 同步完成",
235
+ "没有新增文件,相册已是最新"
236
+ );
171
237
  return;
172
238
  }
173
239
 
240
+ log("info", "[同步]写入相册 insertMany");
174
241
  await guiApi.galleryDB.insertMany(newItems);
175
- guiApi.showNotification &&
176
- guiApi.showNotification({
177
- title: "Gitee 同步完成",
178
- body: `新增 ${newItems.length} 个文件到相册(共发现 ${remoteFiles.length} 个)`,
179
- });
180
- ctx.log.info(
181
- `[同步操作]完成:共 ${remoteFiles.length} 个,新增 ${newItems.length} 个`
242
+ log(
243
+ "success",
244
+ `[同步]完成:共 ${remoteFiles.length} 个,新增 ${newItems.length} 个`
245
+ );
246
+ notify(
247
+ "success",
248
+ ctx,
249
+ guiApi,
250
+ "Gitee 同步完成",
251
+ `新增 ${newItems.length} 个文件到相册(共发现 ${remoteFiles.length} 个)`
182
252
  );
183
253
  };
184
254
 
255
+ // 多通道通知:根据场景选用合适的通道。
256
+ // picgo 文档说明:ctx.emit('notification') 是给**失败**提示专用的通道;
257
+ // 成功时 picgo 主进程已经处理(gui 弹窗/消息中心),插件无需重复 emit。
258
+ // 所以 notify 函数有 success / fail 两种模式。
259
+ const notify = function (mode, ctx, guiApi, title, body) {
260
+ log(mode === "fail" ? "error" : "success", `[notify] ${title}: ${body}`);
261
+
262
+ if (mode === "fail") {
263
+ // 失败:ctx.emit(picgo 主窗口消息中心)+ showMessageBox 弹窗(最醒目)+ showNotification
264
+ try {
265
+ if (ctx && ctx.emit) {
266
+ ctx.emit("notification", { title, body });
267
+ }
268
+ } catch (e) {
269
+ log("error", "[notify] ctx.emit 失败:" + e.message);
270
+ }
271
+ try {
272
+ if (guiApi && guiApi.showMessageBox) {
273
+ guiApi.showMessageBox({
274
+ type: "error",
275
+ title: title,
276
+ message: body,
277
+ });
278
+ }
279
+ } catch (e) {
280
+ log("error", "[notify] showMessageBox 失败:" + e.message);
281
+ }
282
+ }
283
+
284
+ // 成功和失败都触发系统通知(依赖 OS 权限)
285
+ try {
286
+ if (guiApi && guiApi.showNotification) {
287
+ log("11111", guiApi.showNotification({ title, body }));
288
+ }
289
+ } catch (e) {
290
+ log("error", "[notify] showNotification 失败:" + e.message);
291
+ }
292
+ };
293
+
185
294
  const getHeaders = function () {
186
295
  return {
187
296
  "Content-Type": "application/json;charset=UTF-8",
@@ -264,13 +373,8 @@ module.exports = (ctx) => {
264
373
  delete imgList[i].buffer;
265
374
  }
266
375
 
267
- // 上传结束后统一给一条汇总通知,避免每次上传都点弹窗打扰用户。
268
- if (successCount > 0) {
269
- ctx.emit("notification", {
270
- title: "上传完成",
271
- body: `成功 ${successCount} 张${failCount > 0 ? `,失败 ${failCount} 张` : ""}`,
272
- });
273
- }
376
+ // 上传成功:让 picgo 主进程自己处理(它会弹"上传成功"提示),
377
+ // 不重复 emit('notification')。这里只写日志 + 显示消息中心(如果失败)。
274
378
  if (fails.length > 0) {
275
379
  ctx.log.info("[上传操作]失败明细:" + JSON.stringify(fails));
276
380
  }
@@ -303,7 +407,9 @@ module.exports = (ctx) => {
303
407
  };
304
408
 
305
409
  // trigger delete file
306
- const onRemove = async function (files) {
410
+ // trigger delete file
411
+ // 从 PicGo GUI 2.3.0 起,remove 事件的第二个参数是 guiApi(Electron 专属)
412
+ const onRemove = async function (files, guiApi) {
307
413
  const rms = files.filter((each) => each.type === uploadedName);
308
414
  if (rms.length === 0) {
309
415
  return;
@@ -326,10 +432,13 @@ module.exports = (ctx) => {
326
432
  "[删除操作]获取 sha 失败,跳过:" + each.imgUrl + " " + err.message
327
433
  );
328
434
  fails.push(`${each.fileName || each.imgUrl}: 获取 sha 失败`);
329
- ctx.emit("notification", {
330
- title: "删除失败",
331
- body: `${each.fileName || each.imgUrl} 获取 sha 失败`,
332
- });
435
+ notify(
436
+ "fail",
437
+ ctx,
438
+ guiApi,
439
+ "删除失败",
440
+ `${each.fileName || each.imgUrl} 获取 sha 失败`
441
+ );
333
442
  continue;
334
443
  }
335
444
 
@@ -366,21 +475,28 @@ module.exports = (ctx) => {
366
475
  err.message
367
476
  );
368
477
  fails.push(`${each.fileName || each.imgUrl}: ${err.message}`);
369
- ctx.emit("notification", {
370
- title: "删除失败",
371
- body: `${each.fileName || each.imgUrl} ${err.message}`,
372
- });
478
+ notify(
479
+ "fail",
480
+ ctx,
481
+ guiApi,
482
+ "删除失败",
483
+ `${each.fileName || each.imgUrl} ${err.message}`
484
+ );
373
485
  }
374
486
  }
375
487
 
376
- // 汇总通知,避免重复打扰。
488
+ // 删除成功:用 guiApi.showNotification 通知(picgo 不自动处理此通知)
377
489
  if (successNames.length > 0) {
378
- ctx.emit("notification", {
379
- title: "删除完成",
380
- body:
381
- `成功同步删除 ${successNames.length} 个` +
382
- (fails.length > 0 ? `,失败 ${fails.length} 个` : ""),
383
- });
490
+ try {
491
+ if (guiApi && guiApi.showNotification) {
492
+ guiApi.showNotification({
493
+ title: "删除完成",
494
+ body: `成功同步删除 ${successNames.length} 个`,
495
+ });
496
+ }
497
+ } catch (e) {
498
+ log("error", "[删除操作]showNotification 失败:" + e.message);
499
+ }
384
500
  }
385
501
  if (fails.length > 0) {
386
502
  ctx.log.info("[删除操作]失败明细:" + JSON.stringify(fails));