@qbix/q 1.0.5

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +678 -0
  3. package/dist/Metrics.js +2873 -0
  4. package/dist/Metrics.min.js +95 -0
  5. package/dist/Q.js +15677 -0
  6. package/dist/Q.min.js +385 -0
  7. package/dist/Q.minimal.js +11594 -0
  8. package/dist/Q.minimal.min.js +286 -0
  9. package/dist/handlebars-v4.0.10.min.js +29 -0
  10. package/dist/handlebars.minimal.min.js +1 -0
  11. package/dist/img/hints/rotate-left.gif +0 -0
  12. package/dist/img/hints/swipe-down.gif +0 -0
  13. package/dist/img/hints/swipe-up.gif +0 -0
  14. package/dist/img/hints/tap.gif +0 -0
  15. package/dist/img/throbbers/loading.gif +0 -0
  16. package/dist/jquery.minimal.min.js +18 -0
  17. package/dist/methods/Q/Audio/load.js +29 -0
  18. package/dist/methods/Q/Audio/loadVoices.js +36 -0
  19. package/dist/methods/Q/Audio/play.js +47 -0
  20. package/dist/methods/Q/Audio/speak.js +149 -0
  21. package/dist/methods/Q/Crypto/delegate.js +186 -0
  22. package/dist/methods/Q/Crypto/internalKeypair.js +170 -0
  23. package/dist/methods/Q/Crypto/sign.js +200 -0
  24. package/dist/methods/Q/Crypto/verify.js +212 -0
  25. package/dist/methods/Q/Crypto/verifyDelegated.js +214 -0
  26. package/dist/methods/Q/Data/Bloom/_internal.js +163 -0
  27. package/dist/methods/Q/Data/Bloom/create.js +23 -0
  28. package/dist/methods/Q/Data/Bloom/fromBase64.js +21 -0
  29. package/dist/methods/Q/Data/Bloom/fromBytes.js +15 -0
  30. package/dist/methods/Q/Data/Bloom/fromElements.js +33 -0
  31. package/dist/methods/Q/Data/Merkle/_internal.js +68 -0
  32. package/dist/methods/Q/Data/Merkle/build.js +29 -0
  33. package/dist/methods/Q/Data/Merkle/proof.js +50 -0
  34. package/dist/methods/Q/Data/Merkle/verify.js +32 -0
  35. package/dist/methods/Q/Data/Prolly/_internal.js +190 -0
  36. package/dist/methods/Q/Data/Prolly/build.js +28 -0
  37. package/dist/methods/Q/Data/Prolly/delete.js +28 -0
  38. package/dist/methods/Q/Data/Prolly/diff.js +70 -0
  39. package/dist/methods/Q/Data/Prolly/get.js +38 -0
  40. package/dist/methods/Q/Data/Prolly/set.js +32 -0
  41. package/dist/methods/Q/Data/compress.js +45 -0
  42. package/dist/methods/Q/Data/decompress.js +35 -0
  43. package/dist/methods/Q/Data/decrypt.js +55 -0
  44. package/dist/methods/Q/Data/derive.js +76 -0
  45. package/dist/methods/Q/Data/digest.js +29 -0
  46. package/dist/methods/Q/Data/encrypt.js +61 -0
  47. package/dist/methods/Q/Data/generateKey.js +40 -0
  48. package/dist/methods/Q/Data/hkdf.js +44 -0
  49. package/dist/methods/Q/Data/importKey.js +34 -0
  50. package/dist/methods/Q/Data/sign.js +42 -0
  51. package/dist/methods/Q/Data/verify.js +45 -0
  52. package/dist/methods/Q/Onboarding/handle.js +50 -0
  53. package/dist/methods/Q/Onboarding/start.js +165 -0
  54. package/dist/methods/Q/Onboarding/stop.js +21 -0
  55. package/dist/methods/Q/Sandbox/run.js +392 -0
  56. package/dist/methods/Q/Tool/define/component.js +218 -0
  57. package/dist/methods/Q/globalMemoryWalk.js +82 -0
  58. package/dist/methods/Q/leaves.js +34 -0
  59. package/dist/methods/Q/registerWebComponent.js +0 -0
  60. package/dist/methods/Q/sanitize.js +142 -0
  61. package/dist/test.html +6 -0
  62. package/dist/tools/Q/lazyload.js +433 -0
  63. package/package.json +26 -0
@@ -0,0 +1,55 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Decrypts AES-256-GCM ciphertext.
5
+ *
6
+ * @module Q
7
+ * @class Q.Data
8
+ */
9
+
10
+ /**
11
+ * @static
12
+ * @method decrypt
13
+ * @param {CryptoKey} key AES-GCM key
14
+ * @param {String} ivBase64 Base64-encoded IV (12 bytes)
15
+ * @param {String} ciphertextBase64 Base64-encoded ciphertext + tag
16
+ * @param {Object} [options]
17
+ * @param {Uint8Array} [options.additional] Additional authenticated data (AAD).
18
+ * Must match exactly what was passed to encrypt(), otherwise decryption fails.
19
+ * @param {String} [options.tag] Base64-encoded authentication tag (16 bytes).
20
+ * If provided, the tag is appended to ciphertext before decryption,
21
+ * matching the WebCrypto AES-GCM expectation of ciphertext || tag.
22
+ * If omitted, ciphertextBase64 is assumed to already include the tag.
23
+ * @return {Q.Promise} Resolves Uint8Array plaintext
24
+ */
25
+ return function Q_Data_decrypt(key, ivBase64, ciphertextBase64, options) {
26
+ options = options || {};
27
+
28
+ var iv = Q.Data.fromBase64(ivBase64);
29
+ var ciphertext = Q.Data.fromBase64(ciphertextBase64);
30
+
31
+ // If a separate tag is provided, concatenate ciphertext || tag
32
+ // to match the WebCrypto AES-GCM wire format
33
+ if (options.tag) {
34
+ var tag = Q.Data.fromBase64(options.tag);
35
+ var combined = new Uint8Array(ciphertext.length + tag.length);
36
+ combined.set(ciphertext, 0);
37
+ combined.set(tag, ciphertext.length);
38
+ ciphertext = combined;
39
+ }
40
+
41
+ var params = {
42
+ name: 'AES-GCM',
43
+ iv: iv
44
+ };
45
+ if (options.additional) {
46
+ params.additionalData = options.additional;
47
+ }
48
+
49
+ return crypto.subtle.decrypt(params, key, ciphertext)
50
+ .then(function (plaintext) {
51
+ return new Uint8Array(plaintext);
52
+ });
53
+ };
54
+
55
+ });
@@ -0,0 +1,76 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Deterministically derives key material from a seed
5
+ * using HKDF with explicit domain separation.
6
+ *
7
+ * PURE FUNCTION:
8
+ * - no randomness
9
+ * - no storage
10
+ * - no side effects
11
+ *
12
+ * Seed must be binary — pass Uint8Array, ArrayBuffer, or a raw
13
+ * binary-compatible type handled by Q.Data.toUint8Array().
14
+ * If you have a hex or base64 string, decode it first with
15
+ * Q.Data.fromHex() or Q.Data.fromBase64() before calling derive().
16
+ *
17
+ * @module Q
18
+ * @class Q.Data
19
+ */
20
+
21
+ /**
22
+ * @static
23
+ * @method derive
24
+ *
25
+ * @param {Uint8Array|ArrayBuffer} seed
26
+ * Raw binary seed material. Must be a typed binary type —
27
+ * not a string. Decode hex/base64 strings before calling.
28
+ * @param {String} label
29
+ * Domain separation label (used as HKDF info). Must be non-empty UTF-8.
30
+ * @param {Object} [options]
31
+ * @param {Number} [options.size=32] Output length in bytes.
32
+ * @param {String} [options.context=""] Context string used to derive salt
33
+ * via SHA-256(context). Empty string is valid and produces a fixed salt.
34
+ *
35
+ * @return {Q.Promise<Uint8Array>} Resolves derived key material.
36
+ */
37
+ return function Q_Data_derive(seed, label, options) {
38
+
39
+ options = options || {};
40
+ var size = options.size || 32;
41
+ var context = options.context || "";
42
+
43
+ // ---------------------------------------------
44
+ // Normalize seed — strict binary types only
45
+ // ---------------------------------------------
46
+ var seedBytes;
47
+ try {
48
+ seedBytes = Q.Data.toUint8Array(seed);
49
+ } catch (e) {
50
+ return Q.reject(new Error(
51
+ "derive: seed must be a binary type (Uint8Array, ArrayBuffer). " +
52
+ "Decode hex or base64 strings before calling derive(). " +
53
+ "Original error: " + e.message
54
+ ));
55
+ }
56
+
57
+ if (!label || typeof label !== "string") {
58
+ return Q.reject(new Error("derive: label must be a non-empty string"));
59
+ }
60
+
61
+ // ---------------------------------------------
62
+ // salt = SHA-256(context)
63
+ // ---------------------------------------------
64
+ var contextBytes = new TextEncoder().encode(context);
65
+
66
+ return Q.Data.digest("SHA-256", contextBytes)
67
+ .then(function (salt) {
68
+ return Q.Data.hkdf(
69
+ seedBytes, // IKM
70
+ salt, // salt = SHA-256(context)
71
+ label, // info = label
72
+ size
73
+ );
74
+ });
75
+ };
76
+ });
@@ -0,0 +1,29 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Data
7
+ */
8
+
9
+ /**
10
+ * Generate a digest
11
+ * @static
12
+ * @method digest
13
+ * @param {String} algorithm
14
+ * @param {String} payload
15
+ * @param {Function} callback receives (err, result)
16
+ * @return {Q.Promise} Returns a Uint8Array
17
+ */
18
+ return function Q_Data_digest(algorithm, payload, callback) {
19
+ var encoded = new TextEncoder().encode(payload);
20
+
21
+ return crypto.subtle.digest(algorithm, encoded)
22
+ .then(function (buffer) {
23
+ var result = new Uint8Array(buffer);
24
+ callback && callback(null, result);
25
+ return result;
26
+ });
27
+ };
28
+
29
+ });
@@ -0,0 +1,61 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * Encrypts plaintext using AES-256-GCM.
5
+ *
6
+ * @module Q
7
+ * @class Q.Data
8
+ */
9
+
10
+ /**
11
+ * @static
12
+ * @method encrypt
13
+ * @param {CryptoKey} key AES-GCM key
14
+ * @param {Uint8Array} plaintext
15
+ * @param {Object} [options]
16
+ * @param {Uint8Array|String} [options.iv] Optional IV (12 bytes).
17
+ * If omitted, a random IV is generated via crypto.getRandomValues.
18
+ * Pass a derived IV (e.g. from Q.Data.derive) for deterministic /
19
+ * convergent encryption, such as Safecloud chunk encryption where
20
+ * the same content should always produce the same ciphertext and CID.
21
+ * WARNING: never reuse the same (key, iv) pair for different plaintexts.
22
+ * @param {Uint8Array} [options.additional] Additional authenticated data (AAD).
23
+ * Authenticated but not encrypted. Pass e.g. a chunk index or stream ID
24
+ * to bind the ciphertext to its position and prevent reordering attacks.
25
+ * @return {Q.Promise} Resolves { iv: String, ciphertext: String, tag: String }
26
+ * All values are base64-encoded.
27
+ */
28
+ return function Q_Data_encrypt(key, plaintext, options) {
29
+ options = options || {};
30
+
31
+ var iv;
32
+ if (options.iv) {
33
+ iv = (typeof options.iv === 'string')
34
+ ? Q.Data.fromBase64(options.iv)
35
+ : Q.Data.toUint8Array(options.iv);
36
+ } else {
37
+ iv = crypto.getRandomValues(new Uint8Array(12));
38
+ }
39
+
40
+ var params = {
41
+ name: 'AES-GCM',
42
+ iv: iv
43
+ };
44
+ if (options.additional) {
45
+ params.additionalData = options.additional;
46
+ }
47
+
48
+ return crypto.subtle.encrypt(params, key, plaintext)
49
+ .then(function (ciphertext) {
50
+ var ct = new Uint8Array(ciphertext);
51
+ var tag = ct.slice(ct.length - 16);
52
+ var data = ct.slice(0, ct.length - 16);
53
+ return {
54
+ iv: Q.Data.toBase64(iv),
55
+ ciphertext: Q.Data.toBase64(data),
56
+ tag: Q.Data.toBase64(tag)
57
+ };
58
+ });
59
+ };
60
+
61
+ });
@@ -0,0 +1,40 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Generates a public/private key pair and exports them as base64 strings.
4
+ * Compatible with Q.Data.verify and Q.Data.sign using SubtleCrypto.
5
+ *
6
+ * @module Q
7
+ * @class Q.Data
8
+ */
9
+
10
+ /**
11
+ * @static
12
+ * @method generateKey
13
+ * @param {Object} [algo] optional algorithm override
14
+ * @param {String} [algo.name="ECDSA"]
15
+ * @param {String} [algo.namedCurve="P-256"]
16
+ * @param {String} [algo.hash="SHA-256"]
17
+ * @return {Q.Promise} Resolves with { publicKey, privateKey, algorithm },
18
+ * where each key is a base64 string and algorithm is the resolved object.
19
+ */
20
+ return function Q_Data_generateKey(algo) {
21
+ algo = Q.extend({
22
+ name: 'ECDSA',
23
+ namedCurve: 'P-256',
24
+ hash: { name: "SHA-256" }
25
+ }, algo);
26
+
27
+ return crypto.subtle.generateKey(algo, true, ['sign', 'verify']).then(function (keyPair) {
28
+ return Q.Promise.all([
29
+ crypto.subtle.exportKey('raw', keyPair.publicKey),
30
+ crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
31
+ ]).then(function ([pubKeyBuf, privKeyBuf]) {
32
+ return {
33
+ publicKey: Q.Data.toBase64(pubKeyBuf),
34
+ privateKey: Q.Data.toBase64(privKeyBuf),
35
+ algorithm: algo
36
+ };
37
+ });
38
+ });
39
+ };
40
+ });
@@ -0,0 +1,44 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * HKDF using SHA-256 (RFC 5869).
5
+ *
6
+ * @module Q
7
+ * @class Q.Data
8
+ */
9
+
10
+ /**
11
+ * @static
12
+ * @method hkdf
13
+ * @param {Uint8Array} ikm Input key material
14
+ * @param {Uint8Array} salt Salt
15
+ * @param {String} info Context string
16
+ * @param {Number} [length=32] Output length in bytes
17
+ * @return {Q.Promise} Resolves Uint8Array
18
+ */
19
+ return function Q_Data_hkdf(ikm, salt, info, length) {
20
+ length = length || 32;
21
+ info = info || "";
22
+
23
+ return crypto.subtle.importKey(
24
+ "raw",
25
+ ikm,
26
+ { name: "HKDF" },
27
+ false,
28
+ ["deriveBits"]
29
+ ).then(function (key) {
30
+ return crypto.subtle.deriveBits(
31
+ {
32
+ name: "HKDF",
33
+ hash: "SHA-256",
34
+ salt: salt,
35
+ info: new TextEncoder().encode(info)
36
+ },
37
+ key,
38
+ length * 8
39
+ );
40
+ }).then(function (bits) {
41
+ return new Uint8Array(bits);
42
+ });
43
+ };
44
+ });
@@ -0,0 +1,34 @@
1
+ Q.exports(function (Q) {
2
+
3
+ /**
4
+ * @module Q
5
+ * @class Q.Data
6
+ */
7
+
8
+ /**
9
+ * Imports raw key bytes into a CryptoKey for use with SubtleCrypto.
10
+ * @static
11
+ * @method importKey
12
+ * @param {Uint8Array} keyBytes Raw key material
13
+ * @param {Object} [algo] optional algorithm override
14
+ * @param {String} [algo.name="AES-GCM"]
15
+ * @param {Number} [algo.length=256]
16
+ * @param {Array} [algo.usages=["encrypt", "decrypt"]]
17
+ * @return {Q.Promise} Resolves CryptoKey
18
+ */
19
+ return function Q_Data_importKey(keyBytes, algo) {
20
+ algo = Q.extend({
21
+ name: "AES-GCM",
22
+ length: 256,
23
+ usages: ["encrypt", "decrypt"]
24
+ }, algo);
25
+
26
+ return crypto.subtle.importKey(
27
+ "raw",
28
+ keyBytes,
29
+ { name: algo.name, length: algo.length },
30
+ false,
31
+ algo.usages
32
+ );
33
+ };
34
+ });
@@ -0,0 +1,42 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Data
7
+ */
8
+
9
+ /**
10
+ * Encodes text data with TextEncoder, then signs it with
11
+ * each of the private keys. Returns a promise that is rejected
12
+ * if any of the keys fail to be imported.
13
+ * @static
14
+ * @method sign
15
+ * @param {String} data the data to sign
16
+ * @param {Array} privateKeyPKCS8Strings array of strings containing
17
+ * PKCS8 encodings of private keys
18
+ * @param {Object} [algo] you can specify a different algorithm
19
+ * @param {String} [algo.name="ECDSA"]
20
+ * @param {String} [algo.namedCurve="P-256"]
21
+ * @param {String} [algo.hash="SHA-256"]
22
+ * @param {Function} callback receives (err, result)
23
+ * @return {Q.Promise} Resolves an array of ArrayBuffers containing
24
+ * data signed with each of the corresponding public keys.
25
+ * (You can use Q.Data.toBase64 to convert them into strings).
26
+ * Or rejects if any of the keys fail to be imported.
27
+ */
28
+ return function Q_Data_sign(data, privateKeyPKCS8Strings, algo) {
29
+ algo = Q.extend({
30
+ name: 'ECDSA',
31
+ namedCurve: 'P-256',
32
+ hash: { name: "SHA-256" }
33
+ }, algo);
34
+ return Q.Promise.all(
35
+ privateKeyPKCS8Strings.map(pks =>
36
+ crypto.subtle.importKey('pkcs8', Q.Data.fromBase64(pks), algo, false, ['sign'])
37
+ .then(privateKey => crypto.subtle.sign(algo, privateKey, new TextEncoder().encode(data)))
38
+ )
39
+ );
40
+ };
41
+
42
+ });
@@ -0,0 +1,45 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Data
7
+ */
8
+
9
+ /**
10
+ * Encodes text data with TextEncoder, then verifies it with
11
+ * each of the public keys.
12
+ * @static
13
+ * @method verify
14
+ * @param {String} data the signed payload to verify
15
+ * @param {Array} publicKeyRawStrings array of exported public keys
16
+ * @param {Array} signatures array of ArrayBuffer signatures
17
+ * previously generated with the corresponding public keys.
18
+ * @param {Object} [algo] you can specify a different algorithm
19
+ * @param {String} [algo.name="ECDSA"]
20
+ * @param {String} [algo.namedCurve="P-256"]
21
+ * @param {String} [algo.hash="SHA-256"]
22
+ * @param {Function} callback receives (err, result)
23
+ * @return {Q.Promise} Resolves with array of booleans corresponding
24
+ * to each public key, indicating whether the verifications succeeded.
25
+ * Or rejects if any of the keys fail to be imported.
26
+ */
27
+ return function Q_Data_verify(data, publicKeyRawStrings, signatures, algo) {
28
+ const sigs = [];
29
+ for (var sig of signatures) {
30
+ sigs.push(typeof sig === 'string' ? Q.Data.fromBase64(sig) : sig);
31
+ }
32
+ algo = Q.extend({
33
+ name: 'ECDSA',
34
+ namedCurve: 'P-256',
35
+ hash: { name: "SHA-256" }
36
+ }, algo);
37
+ return Q.Promise.all(
38
+ publicKeyRawStrings.map((pks, i) =>
39
+ crypto.subtle.importKey('raw', Q.Data.fromBase64(pks), algo, false, ['verify']
40
+ ).then(publicKey => crypto.subtle.verify(algo, publicKey, sigs[i], new TextEncoder().encode(data)))
41
+ )
42
+ );
43
+ };
44
+
45
+ });
@@ -0,0 +1,50 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Onboarding
7
+ */
8
+
9
+ /**
10
+ * Handles a specific hint event, if it hasn't already occurred.
11
+ * Ignores the "after" instruction of the event, if any.
12
+ * But checks whether elements are visible, otherwise returns false.
13
+ * @static
14
+ * @method handle
15
+ * @param {String} key
16
+ * @param {Function} [callback]
17
+ */
18
+ return function _Q_Onboarding_handle(key, callback) {
19
+ // Added: support returning a Promise in addition to callback
20
+ var promise = new Promise(function (resolve) {
21
+ var lsk = Q.Onboarding.prefix + key;
22
+ var event = Q.Onboarding.events[key];
23
+ if (!localStorage[lsk] || !event || event.occurred || event.stopped) {
24
+ if (callback) callback(false);
25
+ return resolve(false);
26
+ }
27
+ var appeared = [];
28
+ var visible = false;
29
+ var selectorToAppear = Q.Onboarding.selectors[key].appear;
30
+ var elementsToAppear = document.querySelectorAll(selectorToAppear);
31
+ for (var i=0; i<elementsToAppear.length; ++i) {
32
+ var r = elementsToAppear[i].getBoundingClientRect();
33
+ if (r.width && r.height) {
34
+ visible = true;
35
+ appeared.push(elementsToAppear[i]);
36
+ }
37
+ }
38
+ if (!visible) {
39
+ if (callback) callback(false);
40
+ return resolve(false);
41
+ }
42
+ Q.Onboarding.waitToDisappear[key] = false;
43
+ Q.handle(Q.Onboarding.events[key], {}, [appeared, key]);
44
+ if (callback) callback(true);
45
+ resolve(true);
46
+ });
47
+
48
+ return promise;
49
+ };
50
+ });
@@ -0,0 +1,165 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Onboarding
7
+ */
8
+
9
+ /**
10
+ * Starts an onboarding process. You can also trigger
11
+ * Q.Onboarding.event manually with Q.handle().
12
+ * @static
13
+ * @method start
14
+ * @param {Object} [instructions] An object of key: instructionObject pairs
15
+ * where instructionObject has at least "appear" key, with value = CSS selector.
16
+ * It can also have optional:
17
+ * "targets" (selector to override what to hint),
18
+ * "disappear" (selector to override what should disappear before hint event stops)
19
+ * "after" (key of a hint event that has to stop before this hint starts)
20
+ * "options" (for options passed to Q.Visual.hint, merged over Q.Onboarding.options.hint)
21
+ *
22
+ */
23
+ return function _Q_Onboarding_start(instructions) {
24
+ // Added: track whether onboarding actually started
25
+ var started = false;
26
+ var resolveStarted;
27
+ var startedPromise = new Promise(function (resolve) {
28
+ resolveStarted = resolve;
29
+ });
30
+
31
+ if (!instructions) {
32
+ resolveStarted(false);
33
+ return startedPromise;
34
+ }
35
+ if (typeof Q.Onboarding.textName === 'string') {
36
+ Q.Text.get(Q.Onboarding.textName).then(function (text) {
37
+ Q.Onboarding.text = text;
38
+ if (!Q.isEmpty(Q.Onboarding.textPath)) {
39
+ Q.Onboarding.text = Q.getObject(
40
+ Q.Onboarding.textPath, Q.Onboarding.text
41
+ );
42
+ }
43
+ });
44
+ }
45
+ var selectors = Q.Onboarding.selectors;
46
+ var events = Q.Onboarding.events;
47
+ var waitToDisappear = Q.Onboarding.waitToDisappear = {};
48
+ Q.each(instructions, function (k) {
49
+ var instr = instructions[k];
50
+ events[k] = new Q.Event(function (targets, k) {
51
+ var hintTargets = (selectors[k].targets)
52
+ ? document.querySelectorAll(selectors[k].targets)
53
+ : targets;
54
+ var lsk = Q.Onboarding.prefix + k;
55
+ if (localStorage[lsk]) {
56
+ events[k].stop(); // it already occurred before
57
+ } else {
58
+ var o = Q.extend({}, Q.Onboarding.options.hint, instr.options);
59
+ if (Q.Onboarding.text[k]) {
60
+ o.tooltip = Q.extend({
61
+ text: Q.Onboarding.text && Q.Onboarding.text[k]
62
+ }, o.tooltip);
63
+ }
64
+ o.onHide = Q.extend(o.onHide, {
65
+ 'Q.Onboarding': function () {
66
+ setTimeout(function () {
67
+ if (!waitToDisappear[k]) {
68
+ // They never appeared, or weren't specified
69
+ // So go to the next hint.
70
+ localStorage[Q.Onboarding.prefix + k] = 'stopped';
71
+ events[k].stop();
72
+ return;
73
+ }
74
+ }, Q.Onboarding.interval.afterHintHide || 3000);
75
+ }
76
+ });
77
+ Q.Visual.stopHints(); // stop previous hints
78
+ Q.Visual.hint(hintTargets, o);
79
+ localStorage[lsk] = 'occurred';
80
+ }
81
+ }, 'Q.hint');
82
+ selectors[k] = { appear: instr.appear };
83
+ Q.take(instr, ['targets', 'disappear'], selectors[k]);
84
+ });
85
+ Q.Onboarding.intervalId && clearInterval(Q.Onboarding.intervalId);
86
+ Q.Onboarding.intervalId = setInterval(function () {
87
+ for (var k in selectors) {
88
+ var selectorToAppear = selectors[k].appear;
89
+ var selectorToDisappear = selectors[k].disappear || selectors[k].appear;
90
+ var elementsToAppear = document.querySelectorAll(selectorToAppear);
91
+ var appeared = [];
92
+ var visible = false;
93
+ for (var i=0; i<elementsToAppear.length; ++i) {
94
+ var r = elementsToAppear[i].getBoundingClientRect();
95
+ var s = Q.Onboarding.treatAsVisible;
96
+ var elements = Array.prototype.slice.call(document.querySelectorAll(s));
97
+ var treatAsVisible = elements.includes(elementsToAppear[i]);
98
+ if (treatAsVisible || (r.width && r.height)) {
99
+ visible = true;
100
+ appeared.push(elementsToAppear[i]);
101
+ }
102
+ }
103
+ if (!events[k].occurred && visible) {
104
+ var after = instructions[k]?.after;
105
+ if (typeof after === 'string') {
106
+ after = [after];
107
+ }
108
+ var proceed = false;
109
+ Q.each(after, function (i, s) {
110
+ if (Q.Onboarding.events[s]?.stopped
111
+ || localStorage[Q.Onboarding.prefix + s] === 'stopped') {
112
+ proceed = true;
113
+ return false;
114
+ }
115
+ });
116
+ if (!after || proceed) {
117
+ Q.Onboarding.waitToDisappear[k] = false;
118
+ Q.handle(events[k], instructions, [appeared, k]);
119
+ // Added: resolve when onboarding first starts
120
+ if (!started) {
121
+ started = true;
122
+ resolveStarted(true);
123
+ }
124
+ }
125
+ }
126
+ if (!events[k].occurred || events[k].stopped) {
127
+ continue;
128
+ }
129
+ if (selectorToDisappear === selectorToAppear) {
130
+ // event will stop onHide of the hints
131
+ continue; // nothing to wait for
132
+ }
133
+ visible = false;
134
+ var elementsToDisappear = document.querySelectorAll(selectorToDisappear);
135
+ for (var i=0; i<elementsToDisappear.length; ++i) {
136
+ var r = elementsToDisappear[i].getBoundingClientRect();
137
+ var s = Q.Onboarding.treatAsVisible;
138
+ var elements = Array.prototype.slice.call(document.querySelectorAll(s));
139
+ var treatAsVisible = elements.includes(elementsToDisappear[i]);
140
+ if (treatAsVisible || (r.width && r.height)) {
141
+ visible = true;
142
+ break;
143
+ }
144
+ }
145
+ if (visible) {
146
+ waitToDisappear[k] = true;
147
+ }
148
+ if (waitToDisappear[k] && !visible) {
149
+ localStorage[Q.Onboarding.prefix + k] = 'stopped';
150
+ events[k].stop();
151
+ waitToDisappear[k] = false;
152
+ }
153
+ }
154
+ }, 100);
155
+
156
+ // Added: if nothing ever started after a grace period, resolve false
157
+ setTimeout(function () {
158
+ if (!started) {
159
+ resolveStarted(false);
160
+ }
161
+ }, 1000);
162
+
163
+ return startedPromise;
164
+ };
165
+ });
@@ -0,0 +1,21 @@
1
+ Q.exports(function (Q) {
2
+ /**
3
+ * Q plugin's front end code
4
+ *
5
+ * @module Q
6
+ * @class Q.Onboarding
7
+ */
8
+
9
+ /**
10
+ * Starts an onboarding process
11
+ * @static
12
+ * @method start
13
+ * @param {Object} [options={}] Can be one of the following options
14
+ */
15
+ return function _Q_Onboarding_stop(key) {
16
+ var instructions = Q.Onboarding.processes[key];
17
+ if (instructions) {
18
+ clearInterval(instructions.intervalId);
19
+ }
20
+ };
21
+ });