@lancc/cmark 1.1.0 → 1.2.1
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/CHANGELOG.md +27 -0
- package/LICENSE +21 -21
- package/README.md +54 -82
- package/assets/abnfDiagram-WEYOIA3C-CKV_7Upb.js +1 -0
- package/assets/architectureDiagram-ZHVYKT53-BvLudDvk.js +1 -0
- package/assets/blockDiagram-RJAE7QA3--gzkMGi2.js +1 -0
- package/assets/c4Diagram-XDPPPE54-ByfRiJGR.js +1 -0
- package/assets/chunk-2E7WHVMD-TfbC_dmh.js +1 -0
- package/assets/chunk-2Q5K7J3B-0YJgChVo.js +1 -0
- package/assets/chunk-4EMNKR2T-DHoYW7g5.js +1 -0
- package/assets/chunk-55XCU76K-Cq25Yw_G.js +1 -0
- package/assets/chunk-5VM5RSS4-CJaRXFHK.js +1 -0
- package/assets/chunk-JWPE2WC7-BP2iXBhe.js +1 -0
- package/assets/chunk-VQ5W62QI-B95zur4F.js +1 -0
- package/assets/chunk-XXDRQBXY-Ct0MhvQo.js +1 -0
- package/assets/chunk-ZCHMQSOR-C5VhW-wL.js +2 -0
- package/assets/classDiagram-v2-4MYWR3UA-C1G-Vkj0.js +1 -0
- package/assets/cose-bilkent-JH36ORCC-qIQoIBv0.js +1 -0
- package/assets/cynefin-EF2NZ3EQ-CR1E3YiZ.js +1 -0
- package/assets/cynefinDiagram-WF24UDRP-DaJFvZ3b.js +1 -0
- package/assets/cytoscape.esm-SjxgSg6I.js +9 -0
- package/assets/dagre-NK4GIZVX-BFRQhpii.js +1 -0
- package/assets/diagram-CBK3MWTC-DlueL6XZ.js +1 -0
- package/assets/diagram-I5XIIV2D-bITyafYX.js +1 -0
- package/assets/diagram-ODIL2UCW-Dlb4hg_-.js +1 -0
- package/assets/diagram-PFBEJUXE-CRfen0O7.js +1 -0
- package/assets/diagram-RJA3I4VV-CK-FlhGV.js +1 -0
- package/assets/diagram-TYUNFSNR-DmgVsxqa.js +1 -0
- package/assets/ebnfDiagram-V4P3TTPT-B9KGI9eQ.js +1 -0
- package/assets/echarts-CytsvCdp.js +25 -0
- package/assets/elk-QONTBCTQ-VgDftvsU.js +1 -0
- package/assets/erDiagram-KYQIGQAA-DkpP7sS4.js +1 -0
- package/assets/flowDiagram-IUGLLWBU-CRXa9Kxy.js +1 -0
- package/assets/ganttDiagram-R7GPNTJY-Cu2I55Wb.js +1 -0
- package/assets/gitGraphDiagram-PDYL7XHY-CwSo663I.js +1 -0
- package/assets/graph-Dz7AM1Q5.js +1 -0
- package/assets/index-BJ-NdQc4.js +100 -0
- package/assets/index-eB_q_VA3.css +1 -0
- package/assets/infoDiagram-MVDRKAX5-D3Qknzuo.js +1 -0
- package/assets/ishikawaDiagram-HPEHV2GE-9qYT75tk.js +1 -0
- package/assets/journeyDiagram-BOSHW7O7-DeSrJg9o.js +1 -0
- package/assets/kanban-definition-FNSA4HC5-DINkB6G7.js +1 -0
- package/assets/katex-DN8cTsI3.js +1 -0
- package/assets/markdown-S2mWvoE-.js +5 -0
- package/assets/mermaid-T_6Ze_cT.js +7 -0
- package/assets/mindmap-definition-6OY57UG3-DW5hqiAe.js +1 -0
- package/assets/pegDiagram-ECIUJNBX-BWy_tkzy.js +1 -0
- package/assets/pieDiagram-GNMPY74T-CniJbr6l.js +1 -0
- package/assets/quadrantDiagram-D57K4MC3-BVnDJkCv.js +1 -0
- package/assets/railroadDiagram-Y72NRBL7-BNKZpJJb.js +1 -0
- package/assets/requirementDiagram-EQYSW5RA-BETipHj0.js +1 -0
- package/assets/sankeyDiagram-SY24JHO5-ByEYqT7T.js +1 -0
- package/assets/sequenceDiagram-BLEHNHQU-BqCJ2tKG.js +1 -0
- package/assets/sizeCapture-INFHLROL-DlhrWFj-.js +1 -0
- package/assets/stateDiagram-v2-A6IESKWZ-DJUBWLTP.js +1 -0
- package/assets/swimlanes-B3ENRKFQ-D7k7tF5U.js +1 -0
- package/assets/swimlanesDiagram-AYQRV4AK-DoQBgB9o.js +1 -0
- package/assets/timeline-definition-JXYKGLGB-BCNhwnn6.js +1 -0
- package/assets/usecaseDiagram-WPOLBNNX-DRa_Rgl3.js +1 -0
- package/assets/vennDiagram-Q3Y3IDCW-Ba2eaO68.js +1 -0
- package/assets/wardleyDiagram-IVAIDKWA-W_i9vzY-.js +1 -0
- package/assets/xychartDiagram-4URNHDU4-ByfaGjvc.js +1 -0
- package/export-file/github-markdown.css +549 -549
- package/export-file/index.html +32 -32
- package/index.html +4 -4
- package/logo.png +0 -0
- package/package.json +1 -1
- package/plugin.json +8 -218
- package/preload/branch-bar.js +44 -2
- package/preload/export-css.js +59 -0
- package/preload/git-panel.js +102 -23
- package/preload/git-service.js +99 -6
- package/preload/host.js +130 -0
- package/preload/remote-setup.js +425 -0
- package/preload/remote-wizard.js +330 -0
- package/preload/rename-note.js +4 -5
- package/preload/services.js +6 -15
- package/preload/settings-panel.js +468 -356
- package/preload/ws-prefs.js +55 -0
- package/preload.js +291 -6
- package/assets/abnfDiagram-WEYOIA3C-BgXsTF7a.js +0 -1
- package/assets/architectureDiagram-ZHVYKT53-BsCWa_qC.js +0 -36
- package/assets/blockDiagram-RJAE7QA3-wbIxEiYN.js +0 -140
- package/assets/c4Diagram-XDPPPE54-5zdyxFvA.js +0 -38
- package/assets/chunk-2E7WHVMD-F85Lca-i.js +0 -88
- package/assets/chunk-2Q5K7J3B-CjpAZ0OI.js +0 -1
- package/assets/chunk-4EMNKR2T-C8Kbh83c.js +0 -1
- package/assets/chunk-55XCU76K-BINcFiLN.js +0 -1
- package/assets/chunk-5VM5RSS4-Diy5h2LS.js +0 -15
- package/assets/chunk-JWPE2WC7-BXnjDdh1.js +0 -1
- package/assets/chunk-VQ5W62QI-BM7ZZo1I.js +0 -1
- package/assets/chunk-XXDRQBXY-CW8CjB1w.js +0 -1
- package/assets/chunk-ZCHMQSOR-C7ATz1L1.js +0 -2
- package/assets/classDiagram-v2-4MYWR3UA-CMoqNk-b.js +0 -217
- package/assets/cose-bilkent-JH36ORCC-6rqR5iJk.js +0 -1
- package/assets/cynefin-EF2NZ3EQ-DypLIJxl.js +0 -141
- package/assets/cynefinDiagram-WF24UDRP-8b2CEBlR.js +0 -62
- package/assets/cytoscape.esm-CjX6Hyah.js +0 -331
- package/assets/dagre-NK4GIZVX-Dv6vuUbR.js +0 -4
- package/assets/diagram-CBK3MWTC-DJgMDeYd.js +0 -24
- package/assets/diagram-I5XIIV2D-BB9cMoRW.js +0 -24
- package/assets/diagram-ODIL2UCW-BVcRtsdy.js +0 -30
- package/assets/diagram-PFBEJUXE-BbGSOtJx.js +0 -3
- package/assets/diagram-RJA3I4VV-CzkY_8Fo.js +0 -41
- package/assets/diagram-TYUNFSNR-YTl5QdfL.js +0 -200
- package/assets/ebnfDiagram-V4P3TTPT-COVjPAjE.js +0 -1
- package/assets/echarts-BAPDkhe3.js +0 -61
- package/assets/elk-QONTBCTQ-DHsjY8Z_.js +0 -27
- package/assets/erDiagram-KYQIGQAA-CUAbTUl1.js +0 -99
- package/assets/flowDiagram-IUGLLWBU-CWsiqTQC.js +0 -213
- package/assets/ganttDiagram-R7GPNTJY-DetBW0Wm.js +0 -292
- package/assets/gitGraphDiagram-PDYL7XHY-ByrdgJmw.js +0 -106
- package/assets/graph-D8UmR1vK.js +0 -1
- package/assets/index-BLONSItV.css +0 -1
- package/assets/index-Cz6qaZbV.js +0 -744
- package/assets/infoDiagram-MVDRKAX5-BM48fjhU.js +0 -2
- package/assets/ishikawaDiagram-HPEHV2GE-DtHYywHZ.js +0 -70
- package/assets/journeyDiagram-BOSHW7O7-DJ1LiRh3.js +0 -139
- package/assets/kanban-definition-FNSA4HC5-DPPt8pwJ.js +0 -89
- package/assets/katex-CQL9J12G.js +0 -257
- package/assets/markdown-DVehI1Sg.js +0 -314
- package/assets/mermaid-C_q9pug0.js +0 -312
- package/assets/mindmap-definition-6OY57UG3-zbqYOEY6.js +0 -98
- package/assets/pegDiagram-ECIUJNBX-e7QG7TMq.js +0 -1
- package/assets/pieDiagram-GNMPY74T-Duf6YP0N.js +0 -39
- package/assets/quadrantDiagram-D57K4MC3-B2eYoUUy.js +0 -7
- package/assets/railroadDiagram-Y72NRBL7-BWCzPcI_.js +0 -1
- package/assets/requirementDiagram-EQYSW5RA-HhPMEqB1.js +0 -84
- package/assets/sankeyDiagram-SY24JHO5-CMUTwFBB.js +0 -40
- package/assets/sequenceDiagram-BLEHNHQU-C9wR2ll7.js +0 -169
- package/assets/sizeCapture-INFHLROL-DXfz37S6.js +0 -1
- package/assets/stateDiagram-v2-A6IESKWZ-D_gtonhE.js +0 -291
- package/assets/swimlanes-B3ENRKFQ-DdJOuuMV.js +0 -1
- package/assets/swimlanesDiagram-AYQRV4AK-BWt3HxHV.js +0 -8
- package/assets/timeline-definition-JXYKGLGB-DQsdwJe-.js +0 -120
- package/assets/usecaseDiagram-WPOLBNNX-DTRyFq7d.js +0 -370
- package/assets/vennDiagram-Q3Y3IDCW-dihYszm0.js +0 -34
- package/assets/wardleyDiagram-IVAIDKWA-BoPpgYfK.js +0 -78
- package/assets/xychartDiagram-4URNHDU4-DoLjYxeB.js +0 -7
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remote-setup.js —— 一键授权建仓 / 绑定远程笔记仓库
|
|
3
|
+
*
|
|
4
|
+
* 为什么要这个模块:
|
|
5
|
+
* 原来绑定远程要用户自己开网页建仓、复制仓库地址、再配 SSH 或凭据管理器,
|
|
6
|
+
* 对不熟 git 的人门槛太高。这里把「授权 → 建仓(或选已有仓库)→ 绑定 remote → 首次推送」串起来。
|
|
7
|
+
*
|
|
8
|
+
* 两个平台的能力差异(查过官方文档):
|
|
9
|
+
* GitHub:OAuth Device Flow,只需 client_id,不需要 client_secret,
|
|
10
|
+
* 用户在 github.com/login/device 输入 8 位码即可,桌面端的安全做法。
|
|
11
|
+
* Gitee :OAuth2 强制 client_secret(嵌进安装包会被提取),所以改用「私人令牌」,
|
|
12
|
+
* 用户在 gitee.com/profile/personal_access_tokens 生成后粘进来。
|
|
13
|
+
*
|
|
14
|
+
* token 存哪:
|
|
15
|
+
* ~/.cmark-config.json 的 remoteToken 字段(明文,与 git 自身的凭据存储同级别)。
|
|
16
|
+
* git 运行时通过内联 credential helper + 环境变量传入,不写进 .git/config。
|
|
17
|
+
*/
|
|
18
|
+
const fs = require('fs')
|
|
19
|
+
const os = require('os')
|
|
20
|
+
const path = require('path')
|
|
21
|
+
const https = require('https')
|
|
22
|
+
const { execFile } = require('child_process')
|
|
23
|
+
const wsPrefs = require('./ws-prefs.js')
|
|
24
|
+
|
|
25
|
+
const CFG_PATH = path.join(os.homedir(), '.cmark-config.json')
|
|
26
|
+
const GITHUB_CLIENT_ID = 'Ov23liDQDiefOfwzHHLA'
|
|
27
|
+
const GITHUB_SCOPE = 'repo'
|
|
28
|
+
const UA = 'CMark-ZTools-Plugin'
|
|
29
|
+
|
|
30
|
+
/* ---------------- 配置读写 ---------------- */
|
|
31
|
+
function readJSON (p) {
|
|
32
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')) } catch (e) { return null }
|
|
33
|
+
}
|
|
34
|
+
function readCfg () {
|
|
35
|
+
return readJSON(CFG_PATH) || {}
|
|
36
|
+
}
|
|
37
|
+
function patchCfg (patch) {
|
|
38
|
+
const next = Object.assign({}, readCfg(), patch || {})
|
|
39
|
+
try {
|
|
40
|
+
fs.mkdirSync(path.dirname(CFG_PATH), { recursive: true })
|
|
41
|
+
fs.writeFileSync(CFG_PATH, JSON.stringify(next, null, 2), 'utf8')
|
|
42
|
+
return true
|
|
43
|
+
} catch (e) { return false }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ---------------- HTTP ---------------- */
|
|
47
|
+
function request (opts) {
|
|
48
|
+
const o = opts || {}
|
|
49
|
+
return new Promise(function (resolve) {
|
|
50
|
+
let body = o.body || ''
|
|
51
|
+
const headers = Object.assign({
|
|
52
|
+
'User-Agent': UA,
|
|
53
|
+
Accept: 'application/json'
|
|
54
|
+
}, o.headers || {})
|
|
55
|
+
if (body && !headers['Content-Type']) headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
56
|
+
if (body) headers['Content-Length'] = Buffer.byteLength(body)
|
|
57
|
+
const req = https.request({
|
|
58
|
+
hostname: o.hostname,
|
|
59
|
+
path: o.path,
|
|
60
|
+
method: o.method || 'GET',
|
|
61
|
+
headers: headers,
|
|
62
|
+
timeout: o.timeout || 20000
|
|
63
|
+
}, function (res) {
|
|
64
|
+
let buf = ''
|
|
65
|
+
res.setEncoding('utf8')
|
|
66
|
+
res.on('data', function (c) { buf += c })
|
|
67
|
+
res.on('end', function () {
|
|
68
|
+
let data = null
|
|
69
|
+
try { data = JSON.parse(buf) } catch (e) { data = { raw: buf.slice(0, 500) } }
|
|
70
|
+
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, data: data })
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
req.on('timeout', function () { req.destroy(new Error('请求超时')) })
|
|
74
|
+
req.on('error', function (e) { resolve({ ok: false, status: 0, data: { message: (e && e.message) || String(e) } }) })
|
|
75
|
+
if (body) req.write(body)
|
|
76
|
+
req.end()
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
function form (obj) {
|
|
80
|
+
return Object.keys(obj).map(function (k) {
|
|
81
|
+
return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k] == null ? '' : obj[k])
|
|
82
|
+
}).join('&')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ---------------- GitHub Device Flow ---------------- */
|
|
86
|
+
function ghStart () {
|
|
87
|
+
return request({
|
|
88
|
+
hostname: 'github.com',
|
|
89
|
+
path: '/login/device/code',
|
|
90
|
+
method: 'POST',
|
|
91
|
+
body: form({ client_id: GITHUB_CLIENT_ID, scope: GITHUB_SCOPE })
|
|
92
|
+
}).then(function (r) {
|
|
93
|
+
const d = r.data || {}
|
|
94
|
+
if (!r.ok || !d.device_code) {
|
|
95
|
+
return { ok: false, message: d.error_description || d.message || '申请设备码失败(检查 OAuth App 是否开启 Device Flow)' }
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
deviceCode: d.device_code,
|
|
100
|
+
userCode: d.user_code,
|
|
101
|
+
verificationUri: d.verification_uri || 'https://github.com/login/device',
|
|
102
|
+
expiresIn: d.expires_in || 900,
|
|
103
|
+
interval: d.interval || 5
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 单次轮询:pending = 用户还没在浏览器里确认
|
|
109
|
+
function ghPoll (deviceCode) {
|
|
110
|
+
return request({
|
|
111
|
+
hostname: 'github.com',
|
|
112
|
+
path: '/login/oauth/access_token',
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: form({
|
|
115
|
+
client_id: GITHUB_CLIENT_ID,
|
|
116
|
+
device_code: deviceCode,
|
|
117
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
118
|
+
})
|
|
119
|
+
}).then(function (r) {
|
|
120
|
+
const d = r.data || {}
|
|
121
|
+
if (d.access_token) return { ok: true, token: d.access_token }
|
|
122
|
+
if (d.error === 'authorization_pending') return { ok: false, pending: true }
|
|
123
|
+
if (d.error === 'slow_down') return { ok: false, pending: true, slowDown: true }
|
|
124
|
+
if (d.error === 'expired_token') return { ok: false, message: '设备码已过期,请重新发起授权' }
|
|
125
|
+
if (d.error === 'access_denied') return { ok: false, message: '你取消了授权' }
|
|
126
|
+
return { ok: false, message: d.error_description || d.error || ('轮询失败 HTTP ' + r.status) }
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ghApi (token, method, apiPath, bodyObj) {
|
|
131
|
+
const body = bodyObj ? JSON.stringify(bodyObj) : ''
|
|
132
|
+
return request({
|
|
133
|
+
hostname: 'api.github.com',
|
|
134
|
+
path: apiPath,
|
|
135
|
+
method: method,
|
|
136
|
+
headers: Object.assign({
|
|
137
|
+
Authorization: 'Bearer ' + token,
|
|
138
|
+
Accept: 'application/vnd.github+json'
|
|
139
|
+
}, body ? { 'Content-Type': 'application/json' } : {}),
|
|
140
|
+
body: body
|
|
141
|
+
}).then(function (r) { return { ok: r.ok, status: r.status, data: r.data || {} } })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function ghUser (token) {
|
|
145
|
+
return ghApi(token, 'GET', '/user').then(function (r) {
|
|
146
|
+
if (!r.ok) return { ok: false, message: (r.data && r.data.message) || '无法读取 GitHub 账号' }
|
|
147
|
+
return { ok: true, login: r.data.login, name: r.data.name || r.data.login }
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function ghListRepos (token) {
|
|
152
|
+
return ghApi(token, 'GET', '/user/repos?per_page=100&sort=updated&affiliation=owner').then(function (r) {
|
|
153
|
+
if (!r.ok) return { ok: false, message: (r.data && r.data.message) || '读取仓库列表失败' }
|
|
154
|
+
const list = (Array.isArray(r.data) ? r.data : []).map(function (x) {
|
|
155
|
+
return {
|
|
156
|
+
name: x.name,
|
|
157
|
+
fullName: x.full_name,
|
|
158
|
+
private: !!x.private,
|
|
159
|
+
url: x.clone_url,
|
|
160
|
+
updatedAt: x.updated_at,
|
|
161
|
+
empty: x.size === 0
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
return { ok: true, repos: list }
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ghCreateRepo (token, name, isPrivate) {
|
|
169
|
+
return ghApi(token, 'POST', '/user/repos', {
|
|
170
|
+
name: name,
|
|
171
|
+
private: isPrivate !== false,
|
|
172
|
+
auto_init: false,
|
|
173
|
+
description: 'CMark 笔记'
|
|
174
|
+
}).then(function (r) {
|
|
175
|
+
if (!r.ok) {
|
|
176
|
+
const msg = (r.data && r.data.message) || '建仓失败'
|
|
177
|
+
const detail = (r.data && r.data.errors && r.data.errors[0] && r.data.errors[0].message) || ''
|
|
178
|
+
return { ok: false, message: msg + (detail ? ':' + detail : '') }
|
|
179
|
+
}
|
|
180
|
+
return { ok: true, url: r.data.clone_url, fullName: r.data.full_name }
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ---------------- Gitee(私人令牌) ---------------- */
|
|
185
|
+
function giteeApi (method, apiPath, params, token) {
|
|
186
|
+
const query = Object.assign({}, params || {}, { access_token: token })
|
|
187
|
+
const qs = form(query)
|
|
188
|
+
const isGet = method === 'GET'
|
|
189
|
+
return request({
|
|
190
|
+
hostname: 'gitee.com',
|
|
191
|
+
path: '/api/v5' + apiPath + (isGet ? '?' + qs : ''),
|
|
192
|
+
method: method,
|
|
193
|
+
body: isGet ? '' : qs
|
|
194
|
+
}).then(function (r) { return { ok: r.ok, status: r.status, data: r.data || {} } })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function giteeVerify (token) {
|
|
198
|
+
return giteeApi('GET', '/user', {}, token).then(function (r) {
|
|
199
|
+
if (!r.ok) return { ok: false, message: (r.data && (r.data.message || r.data.error)) || '令牌无效或已过期' }
|
|
200
|
+
return { ok: true, login: r.data.login || r.data.name, name: r.data.name || r.data.login }
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function giteeListRepos (token) {
|
|
205
|
+
return giteeApi('GET', '/user/repos', { per_page: 100, sort: 'updated' }, token).then(function (r) {
|
|
206
|
+
if (!r.ok) return { ok: false, message: (r.data && (r.data.message || r.data.error)) || '读取仓库列表失败' }
|
|
207
|
+
const list = (Array.isArray(r.data) ? r.data : []).map(function (x) {
|
|
208
|
+
return {
|
|
209
|
+
name: x.name,
|
|
210
|
+
fullName: x.full_name || (x.namespace && x.namespace.path ? x.namespace.path + '/' + x.name : x.name),
|
|
211
|
+
private: !!x.private,
|
|
212
|
+
url: x.html_url ? x.html_url.replace(/\.git$/, '') + '.git' : '',
|
|
213
|
+
updatedAt: x.updated_at
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
return { ok: true, repos: list }
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function giteeCreateRepo (token, name, isPrivate) {
|
|
221
|
+
return giteeApi('POST', '/user/repos', {
|
|
222
|
+
name: name,
|
|
223
|
+
private: isPrivate !== false,
|
|
224
|
+
auto_init: false,
|
|
225
|
+
description: 'CMark 笔记'
|
|
226
|
+
}, token).then(function (r) {
|
|
227
|
+
if (!r.ok) return { ok: false, message: (r.data && (r.data.message || r.data.error)) || '建仓失败' }
|
|
228
|
+
const full = r.data.full_name || r.data.path
|
|
229
|
+
return { ok: true, url: 'https://gitee.com/' + full + '.git', fullName: full }
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ---------------- git 绑定 ---------------- */
|
|
234
|
+
// Gitee 的 HTTPS 用户名是强校验的:必须与令牌所有者一致,否则回 403
|
|
235
|
+
// 「The token username invalid」(x-access-token 只有 GitHub 认)。
|
|
236
|
+
// 用户名优先级:调用方给的 login > 从仓库地址推断的 owner > x-access-token
|
|
237
|
+
const SAFE_USER = /^[A-Za-z0-9._-]{1,64}$/
|
|
238
|
+
function userForUrl (url) {
|
|
239
|
+
const m = /^https?:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/([^/]+)\//.exec(String(url || ''))
|
|
240
|
+
if (m && /(^|\.)gitee\.com$/i.test(m[1]) && SAFE_USER.test(m[2])) return m[2]
|
|
241
|
+
return 'x-access-token'
|
|
242
|
+
}
|
|
243
|
+
function tokenHelper (username) {
|
|
244
|
+
const u = SAFE_USER.test(String(username || '')) ? String(username) : 'x-access-token'
|
|
245
|
+
return '!f() { echo username=' + u + '; echo password=$CMARK_GIT_TOKEN; }; f'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function git (args, cwd, token, timeout, username) {
|
|
249
|
+
return new Promise(function (resolve) {
|
|
250
|
+
const pre = token
|
|
251
|
+
? ['-c', 'core.quotepath=false', '-c', 'credential.helper=', '-c', 'credential.helper=' + tokenHelper(username)]
|
|
252
|
+
: ['-c', 'core.quotepath=false', '-c', 'credential.helper=', '-c', 'credential.helper=manager']
|
|
253
|
+
execFile('git', pre.concat(args), {
|
|
254
|
+
cwd: cwd,
|
|
255
|
+
encoding: 'utf8',
|
|
256
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
257
|
+
windowsHide: true,
|
|
258
|
+
timeout: timeout || 120000,
|
|
259
|
+
env: Object.assign({}, process.env, {
|
|
260
|
+
GCM_INTERACTIVE: 'never',
|
|
261
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
262
|
+
LC_ALL: 'C',
|
|
263
|
+
GIT_LITERAL_PATHSPECS: '1',
|
|
264
|
+
CMARK_GIT_TOKEN: token || ''
|
|
265
|
+
})
|
|
266
|
+
}, function (err, stdout, stderr) {
|
|
267
|
+
if (err) {
|
|
268
|
+
resolve({ ok: false, message: String(stderr || stdout || err.message || '').trim().slice(0, 600) })
|
|
269
|
+
} else {
|
|
270
|
+
resolve({ ok: true, data: String(stdout || '').trim() })
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isRepo (dir) {
|
|
277
|
+
try { return fs.existsSync(path.join(dir, '.git')) } catch (e) { return false }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 绑定远程笔记仓库
|
|
282
|
+
* @param {object} o
|
|
283
|
+
* provider: 'github' | 'gitee'
|
|
284
|
+
* url: https 仓库地址(不带 token)
|
|
285
|
+
* token: 访问令牌
|
|
286
|
+
* dir: 本地笔记目录
|
|
287
|
+
* existing: true 表示远程已有内容,需要先拉下来(否则会把本地空历史推上去)
|
|
288
|
+
*/
|
|
289
|
+
async function bind (o) {
|
|
290
|
+
const opt = o || {}
|
|
291
|
+
const dir = opt.dir
|
|
292
|
+
const url = opt.url
|
|
293
|
+
const token = opt.token
|
|
294
|
+
// Gitee 必须用令牌所有者的用户名校验;调用方(向导)拿到的是 login,
|
|
295
|
+
// 老路径/手填地址没有 login 时用仓库地址里的 owner 兜底
|
|
296
|
+
const username = opt.username || userForUrl(url)
|
|
297
|
+
if (!dir || !url || !token) return { ok: false, message: '参数不完整' }
|
|
298
|
+
if (!fs.existsSync(dir)) {
|
|
299
|
+
try { fs.mkdirSync(dir, { recursive: true }) } catch (e) {
|
|
300
|
+
return { ok: false, message: '无法创建目录:' + dir }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const steps = []
|
|
305
|
+
// 首推:本地还没有提交就先提交,然后 push -u origin main。
|
|
306
|
+
// 全新仓库和「选了已有仓库但远端是空的」两种情况共用。
|
|
307
|
+
async function firstPush () {
|
|
308
|
+
const hasCommit = await git(['rev-parse', '--verify', 'HEAD'], dir)
|
|
309
|
+
if (!hasCommit.ok) {
|
|
310
|
+
const addAll = await git(['add', '-A', '--', '.'], dir)
|
|
311
|
+
if (!addAll.ok) return { ok: false, message: '暂存失败:' + addAll.message, steps: steps }
|
|
312
|
+
const commit = await git(['commit', '-m', '初始化笔记仓库'], dir)
|
|
313
|
+
if (!commit.ok && !/nothing to commit/i.test(commit.message)) {
|
|
314
|
+
return { ok: false, message: '提交失败:' + commit.message, steps: steps }
|
|
315
|
+
}
|
|
316
|
+
steps.push('commit')
|
|
317
|
+
}
|
|
318
|
+
const push = await git(['push', '-u', 'origin', 'main'], dir, token, 180000, username)
|
|
319
|
+
if (!push.ok) return { ok: false, message: '首次推送失败:' + push.message, steps: steps }
|
|
320
|
+
steps.push('push')
|
|
321
|
+
return { ok: true }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!isRepo(dir)) {
|
|
325
|
+
const init = await git(['init', '-b', 'main'], dir)
|
|
326
|
+
if (!init.ok) return { ok: false, message: 'git init 失败:' + init.message }
|
|
327
|
+
steps.push('init')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const getRemote = await git(['remote', 'get-url', 'origin'], dir)
|
|
331
|
+
if (getRemote.ok) {
|
|
332
|
+
const setUrl = await git(['remote', 'set-url', 'origin', url], dir)
|
|
333
|
+
if (!setUrl.ok) return { ok: false, message: '更新远端地址失败:' + setUrl.message }
|
|
334
|
+
steps.push('remote-set')
|
|
335
|
+
} else {
|
|
336
|
+
const add = await git(['remote', 'add', 'origin', url], dir)
|
|
337
|
+
if (!add.ok) return { ok: false, message: '添加 remote 失败:' + add.message }
|
|
338
|
+
steps.push('remote-add')
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (opt.existing) {
|
|
342
|
+
// 远程已有仓库:把内容拉下来(只能 fast-forward,避免覆盖本地笔记)
|
|
343
|
+
const fetch = await git(['fetch', 'origin'], dir, token, 180000, username)
|
|
344
|
+
if (!fetch.ok) return { ok: false, message: '拉取失败:' + fetch.message, steps: steps }
|
|
345
|
+
steps.push('fetch')
|
|
346
|
+
|
|
347
|
+
// 「使用已有仓库」也可能选到刚建好、还没推过东西的空仓库(auto_init:false 没有任何分支):
|
|
348
|
+
// 这种情况下 fetch 会成功但拉不到任何引用,接着 merge origin/main 必然失败。
|
|
349
|
+
// 所以要显式分叉到首推,而不是让用户看到「合并失败」这种莫名报错。
|
|
350
|
+
const heads = await git(['ls-remote', '--heads', 'origin'], dir, token, 60000, username)
|
|
351
|
+
const hasRemoteHeads = heads.ok && String(heads.data || '').trim() !== ''
|
|
352
|
+
if (!hasRemoteHeads) {
|
|
353
|
+
const fp = await firstPush()
|
|
354
|
+
if (!fp.ok) return fp
|
|
355
|
+
} else {
|
|
356
|
+
// 远端默认分支不一定是 main(Gitee 新建仓库默认 master),按远端 HEAD 指向来合并
|
|
357
|
+
let remoteBranch = 'main'
|
|
358
|
+
try {
|
|
359
|
+
const sym = await git(['ls-remote', '--symref', 'origin', 'HEAD'], dir, token, 60000, username)
|
|
360
|
+
const m = sym.ok ? /ref:\s+refs\/heads\/(\S+)\s+HEAD/.exec(sym.data || '') : null
|
|
361
|
+
if (m && m[1]) remoteBranch = m[1]
|
|
362
|
+
} catch (e) {}
|
|
363
|
+
|
|
364
|
+
if (remoteBranch !== 'main') {
|
|
365
|
+
// 本地是 main、远程是别的名字:把本地主干指到远程分支,避免两个平行根
|
|
366
|
+
await git(['branch', '-M', remoteBranch], dir)
|
|
367
|
+
}
|
|
368
|
+
const merge = await git(['merge', '--ff-only', 'origin/' + remoteBranch], dir, token, 60000, username)
|
|
369
|
+
if (!merge.ok) {
|
|
370
|
+
return { ok: false, message: '合并远端分支 ' + remoteBranch + ' 失败:' + merge.message, steps: steps }
|
|
371
|
+
}
|
|
372
|
+
steps.push('merge')
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
// 全新仓库:提交本地内容后首次推送
|
|
376
|
+
const fp = await firstPush()
|
|
377
|
+
if (!fp.ok) return fp
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// dir 是工作区(仓库根)。笔记目录若已经在它之内就保持不动,
|
|
381
|
+
// 这样「工作区 D:\Proj + 笔记 D:\Proj\Notes」这种布局不会被建仓流程压平
|
|
382
|
+
const cur = readCfg() || {}
|
|
383
|
+
const curWs = cur.workspace || ''
|
|
384
|
+
let keepWs = false
|
|
385
|
+
try {
|
|
386
|
+
const rel = path.relative(dir, curWs)
|
|
387
|
+
keepWs = !!curWs && !rel.startsWith('..') && !path.isAbsolute(rel)
|
|
388
|
+
} catch (e) { keepWs = false }
|
|
389
|
+
patchCfg(Object.assign({ repoDir: dir, initialized: true }, keepWs ? {} : { workspace: dir }))
|
|
390
|
+
const savedRes = wsPrefs.patch(dir, { remoteToken: { provider: opt.provider || '', token: token, url: url, username: username } })
|
|
391
|
+
const saved = !!(savedRes && savedRes.ok)
|
|
392
|
+
return { ok: saved, steps: steps, message: saved ? '' : ((savedRes && savedRes.message) || '写入配置失败') }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function attach (win) {
|
|
396
|
+
win.__CMARK_REMOTE = {
|
|
397
|
+
githubClientId: GITHUB_CLIENT_ID,
|
|
398
|
+
ghStart: ghStart,
|
|
399
|
+
ghPoll: ghPoll,
|
|
400
|
+
ghUser: ghUser,
|
|
401
|
+
ghListRepos: ghListRepos,
|
|
402
|
+
ghCreateRepo: ghCreateRepo,
|
|
403
|
+
giteeVerify: giteeVerify,
|
|
404
|
+
giteeListRepos: giteeListRepos,
|
|
405
|
+
giteeCreateRepo: giteeCreateRepo,
|
|
406
|
+
bind: bind,
|
|
407
|
+
// 当前是否已存过凭据
|
|
408
|
+
current: function () {
|
|
409
|
+
const c = readCfg() || {}
|
|
410
|
+
const p = c.workspace ? wsPrefs.read(c.workspace) : {}
|
|
411
|
+
const t = p.remoteToken || c.remoteToken
|
|
412
|
+
return t ? { provider: t.provider, url: t.url } : null
|
|
413
|
+
},
|
|
414
|
+
clear: function () {
|
|
415
|
+
const c = readCfg() || {}
|
|
416
|
+
if (c.workspace) wsPrefs.patch(c.workspace, { remoteToken: null })
|
|
417
|
+
patchCfg({ remoteToken: null })
|
|
418
|
+
return { ok: true }
|
|
419
|
+
},
|
|
420
|
+
openUrl: function (u) { try { win.ztools && win.ztools.shellOpenExternal && win.ztools.shellOpenExternal(u) } catch (e) {} }
|
|
421
|
+
}
|
|
422
|
+
return win.__CMARK_REMOTE
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
module.exports = attach
|