pinokiod 8.0.7 → 8.0.8
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/kernel/api/hf/index.js
CHANGED
|
@@ -118,6 +118,8 @@ class HF {
|
|
|
118
118
|
href: login.verification_uri_complete,
|
|
119
119
|
target: "_blank",
|
|
120
120
|
features: "browser",
|
|
121
|
+
copyText: login.user_code,
|
|
122
|
+
copyFeedbackSelector: ".hf-login-modal-copy",
|
|
121
123
|
primary: true,
|
|
122
124
|
close: false,
|
|
123
125
|
icon: "fa-solid fa-arrow-up-right-from-square"
|
package/package.json
CHANGED
|
@@ -286,17 +286,59 @@
|
|
|
286
286
|
return classes.join(' ')
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
async copyActionText(action) {
|
|
290
|
+
if (!action || typeof action.copyText !== 'string') {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
const feedback = action.copyFeedbackSelector
|
|
294
|
+
? this.body.querySelector(action.copyFeedbackSelector)
|
|
295
|
+
: null
|
|
296
|
+
try {
|
|
297
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
298
|
+
await navigator.clipboard.writeText(action.copyText)
|
|
299
|
+
} else {
|
|
300
|
+
const input = createElement('textarea')
|
|
301
|
+
try {
|
|
302
|
+
input.value = action.copyText
|
|
303
|
+
input.setAttribute('readonly', '')
|
|
304
|
+
input.style.position = 'fixed'
|
|
305
|
+
input.style.opacity = '0'
|
|
306
|
+
document.body.appendChild(input)
|
|
307
|
+
input.select()
|
|
308
|
+
if (!document.execCommand('copy')) {
|
|
309
|
+
throw new Error('Clipboard copy was rejected')
|
|
310
|
+
}
|
|
311
|
+
} finally {
|
|
312
|
+
input.remove()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (feedback) {
|
|
316
|
+
feedback.textContent = 'The code has been copied to your clipboard.'
|
|
317
|
+
feedback.classList.remove('warning')
|
|
318
|
+
feedback.classList.add('success')
|
|
319
|
+
}
|
|
320
|
+
} catch (_) {
|
|
321
|
+
if (feedback) {
|
|
322
|
+
feedback.textContent = 'Clipboard copy failed. Copy the displayed code manually.'
|
|
323
|
+
feedback.classList.remove('success')
|
|
324
|
+
feedback.classList.add('warning')
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
289
329
|
handleAction(action) {
|
|
290
330
|
if (!action) {
|
|
291
331
|
return
|
|
292
332
|
}
|
|
333
|
+
if (typeof action.copyText === 'string') {
|
|
334
|
+
this.copyActionText(action)
|
|
335
|
+
}
|
|
293
336
|
if (action.type === 'link' && action.href) {
|
|
294
|
-
|
|
295
|
-
window.open(action.href, target, action.features || 'noopener')
|
|
337
|
+
this.openActionUrl(action)
|
|
296
338
|
return
|
|
297
339
|
}
|
|
298
340
|
if (action.type === 'submit' && action.href) {
|
|
299
|
-
|
|
341
|
+
this.openActionUrl(action)
|
|
300
342
|
}
|
|
301
343
|
if (action.type === 'submit' && this.current.awaiting && this.current.socket) {
|
|
302
344
|
this.emitResponse({ action: action.id || 'submit', payload: action.payload || null })
|
|
@@ -306,8 +348,21 @@
|
|
|
306
348
|
} else if (action.type === 'button' && this.current.socket && this.current.awaiting) {
|
|
307
349
|
this.emitResponse({ action: action.id || 'button', payload: action.payload || null })
|
|
308
350
|
} else if (action.type === 'button' && action.href) {
|
|
309
|
-
|
|
351
|
+
this.openActionUrl(action)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
openActionUrl(action) {
|
|
356
|
+
if (action.features && action.features.includes('browser')) {
|
|
357
|
+
const agent = document.body.getAttribute('data-agent')
|
|
358
|
+
if (agent === 'electron') {
|
|
359
|
+
window.open(action.href, action.target || '_blank', 'browser')
|
|
360
|
+
} else {
|
|
361
|
+
window.open(action.href, action.target || '_blank')
|
|
362
|
+
}
|
|
363
|
+
return
|
|
310
364
|
}
|
|
365
|
+
window.open(action.href, action.target || '_blank', action.features || 'noopener')
|
|
311
366
|
}
|
|
312
367
|
|
|
313
368
|
emitResponse(data) {
|
package/test/hf-api.test.js
CHANGED
|
@@ -108,6 +108,8 @@ test('hf.login shows a modal, waits for the user to open Hugging Face, then clos
|
|
|
108
108
|
href: 'https://huggingface.co/oauth/device?user_code=ABCD-EFGH',
|
|
109
109
|
target: '_blank',
|
|
110
110
|
features: 'browser',
|
|
111
|
+
copyText: 'ABCD-EFGH',
|
|
112
|
+
copyFeedbackSelector: '.hf-login-modal-copy',
|
|
111
113
|
primary: true,
|
|
112
114
|
close: false,
|
|
113
115
|
icon: 'fa-solid fa-arrow-up-right-from-square'
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs/promises')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const { JSDOM } = require('jsdom')
|
|
6
|
+
|
|
7
|
+
const htmlModalPath = path.resolve(__dirname, '../server/public/htmlmodal.js')
|
|
8
|
+
|
|
9
|
+
async function createModalDom(options = {}) {
|
|
10
|
+
const script = await fs.readFile(htmlModalPath, 'utf8')
|
|
11
|
+
const opened = []
|
|
12
|
+
const dom = new JSDOM(`<!doctype html><html><head></head><body><script>${script}</script></body></html>`, {
|
|
13
|
+
url: 'http://127.0.0.1:42000/',
|
|
14
|
+
runScripts: 'dangerously',
|
|
15
|
+
pretendToBeVisual: true,
|
|
16
|
+
beforeParse(window) {
|
|
17
|
+
if (options.clipboard) {
|
|
18
|
+
Object.defineProperty(window.navigator, 'clipboard', {
|
|
19
|
+
configurable: true,
|
|
20
|
+
value: options.clipboard
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
if (options.execCommand) {
|
|
24
|
+
window.document.execCommand = options.execCommand
|
|
25
|
+
}
|
|
26
|
+
window.open = (...args) => {
|
|
27
|
+
opened.push(args)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (!dom.window.HtmlModal) {
|
|
33
|
+
await new Promise((resolve) => dom.window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
|
34
|
+
}
|
|
35
|
+
if (options.agent) {
|
|
36
|
+
dom.window.document.body.setAttribute('data-agent', options.agent)
|
|
37
|
+
}
|
|
38
|
+
return { dom, manager: dom.window.HtmlModal, opened }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function openCopyModal(manager) {
|
|
42
|
+
manager.handle({
|
|
43
|
+
id: 'packet',
|
|
44
|
+
data: {
|
|
45
|
+
action: 'open',
|
|
46
|
+
id: 'hf-login:test',
|
|
47
|
+
html: '<div class="hf-login-modal-copy custom-class">Copy this code.</div>',
|
|
48
|
+
actions: [{
|
|
49
|
+
id: 'open',
|
|
50
|
+
label: 'Open Hugging Face',
|
|
51
|
+
type: 'submit',
|
|
52
|
+
href: 'https://huggingface.co/oauth/device?user_code=ABCD-EFGH',
|
|
53
|
+
target: '_blank',
|
|
54
|
+
features: 'browser',
|
|
55
|
+
copyText: 'ABCD-EFGH',
|
|
56
|
+
copyFeedbackSelector: '.hf-login-modal-copy'
|
|
57
|
+
}]
|
|
58
|
+
}
|
|
59
|
+
}, null)
|
|
60
|
+
manager.actions.querySelector('button').click()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test('html modal copies action text from the user click and opens a client-side web tab', async () => {
|
|
64
|
+
const copied = []
|
|
65
|
+
const { manager, opened } = await createModalDom({
|
|
66
|
+
clipboard: {
|
|
67
|
+
async writeText(value) {
|
|
68
|
+
copied.push(value)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
openCopyModal(manager)
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(copied, ['ABCD-EFGH'])
|
|
76
|
+
assert.deepEqual(opened, [[
|
|
77
|
+
'https://huggingface.co/oauth/device?user_code=ABCD-EFGH',
|
|
78
|
+
'_blank'
|
|
79
|
+
]])
|
|
80
|
+
const feedback = manager.body.querySelector('.hf-login-modal-copy')
|
|
81
|
+
assert.equal(feedback.textContent, 'The code has been copied to your clipboard.')
|
|
82
|
+
assert.equal(feedback.classList.contains('custom-class'), true)
|
|
83
|
+
assert.equal(feedback.classList.contains('success'), true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('html modal keeps the Electron external-browser hint', async () => {
|
|
87
|
+
const { manager, opened } = await createModalDom({
|
|
88
|
+
agent: 'electron',
|
|
89
|
+
clipboard: { async writeText() {} }
|
|
90
|
+
})
|
|
91
|
+
openCopyModal(manager)
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
93
|
+
|
|
94
|
+
assert.deepEqual(opened, [[
|
|
95
|
+
'https://huggingface.co/oauth/device?user_code=ABCD-EFGH',
|
|
96
|
+
'_blank',
|
|
97
|
+
'browser'
|
|
98
|
+
]])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('html modal reports fallback copy failure and removes its temporary textarea', async () => {
|
|
102
|
+
const { dom, manager } = await createModalDom({
|
|
103
|
+
execCommand() {
|
|
104
|
+
throw new Error('copy denied')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
openCopyModal(manager)
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
109
|
+
|
|
110
|
+
const feedback = manager.body.querySelector('.hf-login-modal-copy')
|
|
111
|
+
assert.equal(feedback.textContent, 'Clipboard copy failed. Copy the displayed code manually.')
|
|
112
|
+
assert.equal(feedback.classList.contains('custom-class'), true)
|
|
113
|
+
assert.equal(feedback.classList.contains('warning'), true)
|
|
114
|
+
assert.equal(dom.window.document.querySelectorAll('body > textarea').length, 0)
|
|
115
|
+
})
|