@quietsapa/qsl 0.1.0

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/src/index.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * QSL public entry point.
3
+ *
4
+ * Side-effect free: importing this module registers nothing and starts
5
+ * nothing. Compose what you need, then call `init()` yourself. For a
6
+ * batteries-included build see `src/presets/full.js`.
7
+ */
8
+
9
+ export { default as core } from './core.js';
10
+ export { default } from './core.js';
11
+
12
+ export {
13
+ Script,
14
+ Stylesheet,
15
+ InlineScript,
16
+ InlineStyle,
17
+ Pixel,
18
+ Shadow,
19
+ HTML,
20
+ } from './types.js';
21
+
22
+ export {
23
+ default as conditions,
24
+ mediaQueryCondition,
25
+ languageCondition,
26
+ timezoneCondition,
27
+ urlCondition,
28
+ userAgentCondition,
29
+ } from './plugins/conditions.js';
30
+
31
+ export {
32
+ default as triggers,
33
+ loadTrigger,
34
+ idleTrigger,
35
+ domReadyTrigger,
36
+ delayTrigger,
37
+ hoverTrigger,
38
+ visibleTrigger,
39
+ appearsTrigger,
40
+ mediaQueryTrigger,
41
+ } from './plugins/triggers.js';
42
+
43
+ export { default as logger } from './plugins/logger.js';
44
+ export { default as events } from './plugins/events.js';
45
+ export { default as circ } from './plugins/circ.js';
46
+ export { default as dynamic } from './plugins/dynamic.js';
47
+ export { default as simpleEvents } from './plugins/simple-events.js';
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Check for circular dependencies in flows and processes.
3
+ * @param {*} QSL
4
+ */
5
+ export default function(QSL) {
6
+ QSL.loadActions.add(function() {
7
+ /**
8
+ * Helper function to detect circular dependencies
9
+ */
10
+ const detectCircularDependency = (id, getDeps, visited = new Set()) => {
11
+ if (visited.has(id)) return true;
12
+ visited.add(id);
13
+ const deps = getDeps(id) || [];
14
+ for (const depId of deps) {
15
+ if (detectCircularDependency(depId, getDeps, new Set(visited))) return true;
16
+ }
17
+ return false;
18
+ };
19
+
20
+ /**
21
+ * Check for circular dependencies in flows
22
+ */
23
+ const getFlowDeps = id => (this.flowOptions.get(this.normalizeFlowId(id))?.depends) || [];
24
+ for (const [fid, options] of this.flowOptions.entries()) {
25
+ if (Array.isArray(options.depends) && options.depends.length) {
26
+ if (detectCircularDependency(fid, getFlowDeps)) {
27
+ this.log('CIRC_FLOW_DEP_SKIPPED', fid, options.depends);
28
+ this.setFlowOptions({ status: this.FLOW_STATE.COMPLETED }, fid); /* Mark as completed */
29
+ }
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Check for circular dependencies in processes
35
+ */
36
+ const getProcDeps = id => {
37
+ for (const flow of this.flows.values()) {
38
+ const proc = flow.find(p => p.id === id);
39
+ if (proc && Array.isArray(proc.depends)) return proc.depends.map(dep => this.PREFIX + dep);
40
+ }
41
+ return [];
42
+ };
43
+ for (const flow of this.flows.values()) {
44
+ for (const process of flow) {
45
+ if (Array.isArray(process.depends) && process.depends.length) {
46
+ if (detectCircularDependency(process.id, getProcDeps)) {
47
+ this.log('CIRC_PROCESS_DEP_SKIPPED', process.id, process.depends);
48
+ process.condition = false; /* Turn off condition */
49
+ }
50
+ }
51
+ }
52
+ }
53
+ });
54
+
55
+ if ( QSL.logger && QSL.logger.VERSION === 'qsl-logger' ) {
56
+ QSL.logger.LOG.CIRC_FLOW_DEP_SKIPPED = '[QSL] Circular flow dependency skipped:';
57
+ QSL.logger.LOG.CIRC_PROCESS_DEP_SKIPPED = '[QSL] Circular process dependency skipped:';
58
+ }
59
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Media query condition handler
3
+ * @param {*} QSL
4
+ */
5
+ export function mediaQueryCondition(QSL) {
6
+ QSL.conditionHandlers.add(function(opt) {
7
+ if (typeof opt !== 'string' || !opt.startsWith('media:')) {
8
+ return null;
9
+ }
10
+ return !window.matchMedia(opt.slice('media:'.length)).matches;
11
+ });
12
+ }
13
+
14
+ /**
15
+ * Language condition handler
16
+ * @param {*} QSL
17
+ */
18
+ export function languageCondition(QSL) {
19
+ QSL.conditionHandlers.add(function(opt) {
20
+ if (typeof opt !== 'string' || !opt.startsWith('lang:')) {
21
+ return null;
22
+ }
23
+ const p = opt.split(':');
24
+ const t = p[1] || 'equals';
25
+ const v = p.slice(2).join(':');
26
+ const l = navigator.language || navigator.languages?.[0] || '';
27
+
28
+ switch (t) {
29
+ case 'equals':
30
+ case 'is':
31
+ return l !== v;
32
+ case 'contains':
33
+ return !l.includes(v);
34
+ case 'startsWith':
35
+ return !l.startsWith(v);
36
+ case 'in':
37
+ const ls = v.split(',').map(ll => ll.trim());
38
+ return !ls.includes(l);
39
+ default:
40
+ return true; // Unknown type, fail condition
41
+ }
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Timezone condition handler
47
+ * @param {*} QSL
48
+ */
49
+ export function timezoneCondition(QSL) {
50
+ QSL.conditionHandlers.add(function(opt) {
51
+ if (typeof opt !== 'string' ||
52
+ (!opt.startsWith('tz:') && !opt.startsWith('timezone:'))) {
53
+ return null;
54
+ }
55
+ const p = opt.split(':');
56
+ const t = p[1] || 'equals';
57
+ const v = p.slice(2).join(':');
58
+
59
+ try {
60
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
61
+ const o = -new Date().getTimezoneOffset() / 60;
62
+
63
+ switch (t) {
64
+ case 'equals':
65
+ case 'is':
66
+ return tz !== v;
67
+ case 'contains':
68
+ return !tz.includes(v);
69
+ case 'offset':
70
+ return o !== parseInt(v);
71
+ default:
72
+ return true;
73
+ }
74
+ } catch (e) {
75
+ return true;
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * URL condition handler
82
+ * @param {*} QSL
83
+ */
84
+ export function urlCondition(QSL) {
85
+ QSL.conditionHandlers.add(function(opt) {
86
+ if (typeof opt !== 'string' || !opt.startsWith('url:')) {
87
+ return null;
88
+ }
89
+ const lc = window.location;
90
+ const hf = lc.href;
91
+ const pn = lc.pathname;
92
+ const se = lc.search;
93
+ const hn = lc.hostname;
94
+
95
+ const p = opt.split(':');
96
+ const t = p[1];
97
+ const v = p.slice(2).join(':');
98
+
99
+ if (!t) return true;
100
+
101
+ switch (t) {
102
+ case 'contains':
103
+ return !hf.includes(v);
104
+ case 'path':
105
+ return !pn.includes(v);
106
+ case 'pathStartsWith':
107
+ return !pn.startsWith(v);
108
+ case 'pathEndsWith':
109
+ return !pn.endsWith(v);
110
+ case 'query':
111
+ if (se) {
112
+ const q = new URLSearchParams(se);
113
+ if (v.includes('=')) {
114
+ const [key, val] = v.split('=');
115
+ return q.get(key) !== val;
116
+ } else {
117
+ return !q.has(v);
118
+ }
119
+ } else {
120
+ return true;
121
+ }
122
+ case 'hostname':
123
+ return !hn.includes(v);
124
+ case 'matches':
125
+ try {
126
+ return !(new RegExp(v)).test(hf);
127
+ } catch (e) {
128
+ return true;
129
+ }
130
+ case 'pathMatches':
131
+ try {
132
+ return !(new RegExp(v)).test(pn);
133
+ } catch (e) {
134
+ return true; // Error parsing regex, fail condition
135
+ }
136
+ default:
137
+ return true;
138
+ }
139
+ });
140
+ }
141
+
142
+ /**
143
+ * User agent condition handler
144
+ * @param {*} QSL
145
+ */
146
+ export function userAgentCondition(QSL) {
147
+ QSL.conditionHandlers.add(function(opt) {
148
+ if (typeof opt !== 'string' ||
149
+ (!opt.startsWith('ua:') && !opt.startsWith('userAgent:'))) {
150
+ return null;
151
+ }
152
+ const p = opt.split(':');
153
+ const t = p[1] || 'contains';
154
+ const v = p.slice(2).join(':');
155
+ const ua = navigator.userAgent || '';
156
+ const uaLower = ua.toLowerCase();
157
+ const vLower = v.toLowerCase();
158
+
159
+ switch (t) {
160
+ case 'contains':
161
+ if (!uaLower.includes(vLower)) return true;
162
+ break;
163
+
164
+ case 'equals':
165
+ case 'is':
166
+ if (ua !== v) return true;
167
+ break;
168
+
169
+ case 'matches':
170
+ // Regex pattern matching
171
+ try {
172
+ if (!(new RegExp(v, 'i')).test(ua)) return true;
173
+ } catch (e) {
174
+ return true; // Error parsing regex, fail condition
175
+ }
176
+ break;
177
+
178
+ case 'browser':
179
+ // Detect browser
180
+ const bMap = {
181
+ 'chrome': /chrome/i.test(ua) && !/edg|opr/i.test(ua),
182
+ 'firefox': /firefox/i.test(ua),
183
+ 'safari': /safari/i.test(ua) && !/chrome|chromium|edg|opr/i.test(ua),
184
+ 'edge': /edg/i.test(ua),
185
+ 'opera': /opr/i.test(ua),
186
+ 'ie': /msie|trident/i.test(ua),
187
+ 'chromium': /chromium/i.test(ua)
188
+ };
189
+ const bKey = vLower;
190
+ if (!bMap[bKey]) return true;
191
+ break;
192
+
193
+ case 'device':
194
+ // Detect device type
195
+ const isM = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i.test(ua);
196
+ const isT = /tablet|ipad|playbook|silk/i.test(ua) || (isM && /android/i.test(ua) && !/mobile/i.test(ua));
197
+ const isD = !isM && !isT;
198
+
199
+ switch (vLower) {
200
+ case 'mobile': if (!isM) return true; break;
201
+ case 'tablet': if (!isT) return true; break;
202
+ case 'desktop': if (!isD) return true; break;
203
+ default: return true;
204
+ }
205
+ break;
206
+
207
+ case 'os':
208
+ case 'platform':
209
+ // Detect operating system
210
+ const oMap = {
211
+ 'windows': /win/i.test(ua),
212
+ 'mac': /mac/i.test(ua),
213
+ 'ios': /iphone|ipad|ipod/i.test(ua),
214
+ 'android': /android/i.test(ua),
215
+ 'linux': /linux/i.test(ua) && !/android/i.test(ua),
216
+ 'unix': /unix/i.test(ua),
217
+ 'chromeos': /cros/i.test(ua)
218
+ };
219
+ if (!oMap[vLower]) return true;
220
+ break;
221
+
222
+ default:
223
+ return true;
224
+ }
225
+
226
+ // If we reach here, condition passed (none of the cases returned true)
227
+ return false;
228
+ });
229
+ }
230
+
231
+ export default function(QSL) {
232
+ // Media query handler
233
+ mediaQueryCondition(QSL);
234
+
235
+ // Language handler
236
+ languageCondition(QSL);
237
+
238
+ // Timezone handler
239
+ timezoneCondition(QSL);
240
+
241
+ // URL handler
242
+ urlCondition(QSL);
243
+
244
+ // User agent handler
245
+ userAgentCondition(QSL);
246
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Handle dynamic flow assignment when resources are added after load has started.
3
+ * @param {*} QSL
4
+ */
5
+ export default function(QSL) {
6
+ /**
7
+ * Handle dynamic flow assignment when resources are added after load has started.
8
+ */
9
+ QSL.addProcessFilters.add(function(flowId, config) {
10
+ if (this.hasStarted && !flowId) {
11
+ flowId = 'dynamic-' + Math.random().toString(36).slice(2);
12
+ config.paused = true;
13
+ }
14
+ return [flowId, config];
15
+ });
16
+
17
+ /**
18
+ * Filter out dynamic flows from initial processing.
19
+ */
20
+ QSL.flowIdFilters.add(function(flowIds) {
21
+ return flowIds.filter(fid => !fid.includes('dynamic'));
22
+ });
23
+
24
+ /**
25
+ * Process dynamic flows after all regular flows complete.
26
+ * Checks if all non-dynamic flows are completed, then processes dynamic flows.
27
+ */
28
+ QSL.completedFlowsActions.add(function(flowsDone, flows, flowOptions) {
29
+ if (!flowsDone || !flows || !flowOptions) return true;
30
+
31
+ /**
32
+ * Check if all non-dynamic flows are completed
33
+ */
34
+ const nonDynamicFlowsDone = [...flowOptions.entries()]
35
+ .filter(([fid]) => !fid.includes('dynamic'))
36
+ .every(([, opt]) => opt.status === this.FLOW_STATE.COMPLETED);
37
+
38
+ if (!nonDynamicFlowsDone) return false;
39
+
40
+ /**
41
+ * Process dynamic flows
42
+ */
43
+ const dynamicFlowIds = [...flows.keys()].filter(fid => fid.includes('dynamic'));
44
+ if (dynamicFlowIds.length === 0) return true;
45
+
46
+ for (const dynamicFlowId of dynamicFlowIds) {
47
+ const dynamicOptions = flowOptions.get(dynamicFlowId);
48
+
49
+ if (dynamicOptions && dynamicOptions.status !== this.FLOW_STATE.COMPLETED) {
50
+ if (dynamicOptions.status === this.FLOW_STATE.READY) {
51
+ this.setFlowOptions({ status: this.FLOW_STATE.RUNNING }, dynamicFlowId);
52
+ this.runFlow(dynamicFlowId);
53
+ }
54
+ return false;
55
+ }
56
+ }
57
+ return true;
58
+ });
59
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Track events when a process completes.
3
+ * @param {*} QSL
4
+ */
5
+ export default function(QSL) {
6
+ const customEvents = new Map();
7
+ const processElementMap = new WeakMap();
8
+ let documentListener = null;
9
+ let windowListener = null;
10
+ let isIntercepting = false;
11
+
12
+ /**
13
+ * Normalize script URL for comparison (remove protocol, domain, query params, hash).
14
+ * @param {string} url - Script URL or path
15
+ * @returns {string} - Normalized path
16
+ */
17
+ const normalizeScriptPath = (u) => {
18
+ if (!u) return '';
19
+ try {
20
+ return u.includes('://') ? new URL(u).pathname : u.split('?')[0].split('#')[0];
21
+ } catch (e) {
22
+ return u.split('?')[0].split('#')[0];
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Initialize event interception when QSL loads.
28
+ */
29
+ QSL.loadActions.add(function() {
30
+ if (isIntercepting) return;
31
+
32
+ documentListener = document.addEventListener;
33
+ windowListener = window.addEventListener;
34
+ isIntercepting = true;
35
+
36
+ const iterator = (type, eventType, changeEventName) => {
37
+ let currentScriptId = null;
38
+ let targetFlowId = null;
39
+
40
+ /**
41
+ * Priority 1: Check document.currentScript for current process ID and flow ID.
42
+ */
43
+ if (document.currentScript) {
44
+ const processElement = processElementMap.get(document.currentScript);
45
+ if (processElement) {
46
+ currentScriptId = processElement.processId;
47
+ targetFlowId = processElement.flowId;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Priority 2: Check currentProcessPerFlow map for current process ID and flow ID.
53
+ */
54
+ if (!currentScriptId && this.currentProcessPerFlow.size) {
55
+ for (const [flowId, processId] of this.currentProcessPerFlow) {
56
+ if (processId) {
57
+ currentScriptId = processId;
58
+ targetFlowId = flowId;
59
+ break;
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Priority 3: Use Error stack trace only when currentScript is null.
66
+ */
67
+ if (!currentScriptId) {
68
+ const stack = new Error().stack;
69
+ if (stack) {
70
+ const lines = stack.split('\n');
71
+ // Skip first 2 lines (Error and iterator function)
72
+ for (let i = 2; i < lines.length; i++) {
73
+ const match = lines[i].match(/([^()\s]+\.js(?:\?[^:)]*)?):\d+(?::\d+)?/);
74
+ if (match) {
75
+ const file = match[1];
76
+ const qIndex = file.indexOf('?');
77
+ const stackFile = qIndex === -1 ? file.trim() : file.slice(0, qIndex).trim();
78
+
79
+ if (stackFile) {
80
+ // Normalize: remove query/hash, extract pathname from URL
81
+ const qIdx = stackFile.indexOf('?');
82
+ const hIdx = stackFile.indexOf('#');
83
+ const endIdx = qIdx === -1 ? (hIdx === -1 ? stackFile.length : hIdx) : (hIdx === -1 ? qIdx : Math.min(qIdx, hIdx));
84
+ let normalizedFile = stackFile.slice(0, endIdx);
85
+ try {
86
+ if (normalizedFile.includes('://')) {
87
+ normalizedFile = new URL(normalizedFile).pathname;
88
+ }
89
+ } catch (e) {}
90
+ const lastSlash = normalizedFile.lastIndexOf('/');
91
+ const fileName = lastSlash === -1 ? normalizedFile : normalizedFile.slice(lastSlash + 1);
92
+
93
+ for (const [flowId, processes] of this.flows) {
94
+ for (const process of processes) {
95
+ if (process.src && process.type === 'script') {
96
+ const normalizedSrc = normalizeScriptPath(process.src);
97
+ const srcLastSlash = normalizedSrc.lastIndexOf('/');
98
+ const srcFileName = srcLastSlash === -1 ? normalizedSrc : normalizedSrc.slice(srcLastSlash + 1);
99
+
100
+ if (normalizedFile === normalizedSrc || (fileName && fileName === srcFileName)) {
101
+ currentScriptId = process.id;
102
+ targetFlowId = flowId;
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ if (currentScriptId) break;
108
+ }
109
+ if (currentScriptId) break;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * If current script ID and flow ID are found, track event.
118
+ * Only change event name if event already fired (changeEventName = true).
119
+ */
120
+ if (currentScriptId && targetFlowId) {
121
+ const flow = this.flows.get(targetFlowId);
122
+ const process = flow?.find(p => p.id === currentScriptId || p.id === this.PREFIX + currentScriptId);
123
+
124
+ if (process) {
125
+ const shouldFireEvents = process.fireEvents !== false && (this.flowOptions.get(targetFlowId)?.fireEvents !== false);
126
+ if (shouldFireEvents) {
127
+ const eventName = `${type}:${process.id}`;
128
+ if (!customEvents.has(process.id)) customEvents.set(process.id, []);
129
+ const events = customEvents.get(process.id);
130
+ if (!events.some(e => e.name === eventName)) events.push({ type: eventType, name: eventName });
131
+ if (changeEventName) type = eventName;
132
+ }
133
+ }
134
+ }
135
+ return type;
136
+ };
137
+
138
+ document.addEventListener = (type, listener, opts) => {
139
+ if (type === 'DOMContentLoaded' && this.LIFECYCLE.DOMREADY) {
140
+ const trackedType = iterator.call(this, type, this.EVENTS.DOMREADY, true);
141
+ if (trackedType !== type) {
142
+ type = trackedType;
143
+ queueMicrotask(() => document.dispatchEvent(new Event(trackedType)));
144
+ }
145
+ }
146
+ return documentListener.call(document, type, listener, opts);
147
+ };
148
+
149
+ window.addEventListener = (type, listener, opts) => {
150
+ if (type === 'load' && this.LIFECYCLE.LOADED) {
151
+ const trackedType = iterator.call(this, type, this.EVENTS.LOADED, true);
152
+ if (trackedType !== type) {
153
+ type = trackedType;
154
+ queueMicrotask(() => window.dispatchEvent(new Event(trackedType)));
155
+ }
156
+ }
157
+ return windowListener.call(window, type, listener, opts);
158
+ };
159
+ });
160
+
161
+ /**
162
+ * Provide handler callbacks for process execution.
163
+ * Register process elements in processElementMap.
164
+ */
165
+ QSL.handlerCallbacksFilters.add(function(process) {
166
+ return {
167
+ registerProcessElement: (el, config) => {
168
+ processElementMap.set(el, { flowId: config.flowId, processId: config.id });
169
+ }
170
+ };
171
+ });
172
+
173
+ /**
174
+ * Fire tracked events when a process completes.
175
+ * Use processCompleteActions instead of patching execute.
176
+ */
177
+ QSL.processCompleteActions.add(function(process) {
178
+ const events = customEvents.get(process.id);
179
+ if (events && Array.isArray(events)) {
180
+ for (const event of events) {
181
+ if (event.type === this.EVENTS.DOMREADY) {
182
+ if (this.LIFECYCLE.DOMREADY) {
183
+ document.dispatchEvent(new Event(event.name));
184
+ } else {
185
+ document.addEventListener('DOMContentLoaded', () => {
186
+ document.dispatchEvent(new Event(event.name));
187
+ }, { once: true });
188
+ }
189
+ } else if (event.type === this.EVENTS.LOADED) {
190
+ if (this.LIFECYCLE.LOADED) {
191
+ window.dispatchEvent(new Event(event.name));
192
+ } else {
193
+ window.addEventListener('load', () => {
194
+ window.dispatchEvent(new Event(event.name));
195
+ }, { once: true });
196
+ }
197
+ }
198
+ }
199
+ }
200
+ });
201
+
202
+ /**
203
+ * Cleanup: restore original addEventListener functions on reset.
204
+ * Use resetActions instead of patching reset.
205
+ */
206
+ QSL.resetActions.add(function() {
207
+ if (isIntercepting && documentListener && windowListener) {
208
+ document.addEventListener = documentListener;
209
+ window.addEventListener = windowListener;
210
+ isIntercepting = false;
211
+ }
212
+ });
213
+
214
+ /**
215
+ * Store custom events map.
216
+ */
217
+ QSL.customEvents = customEvents;
218
+ }
@@ -0,0 +1,56 @@
1
+ export default function(QSL) {
2
+
3
+ QSL.initActions.add(function() {
4
+
5
+ QSL.logger = {
6
+ VERSION: 'qsl-logger',
7
+ LOG: {
8
+ LOGGER_LOADED: '[QSL] Logger loaded',
9
+ LOGGER_LOAD_ERROR: '[QSL] Logger load error:',
10
+ UNKNOWN_TYPE: '[QSL] Unknown type:',
11
+ PROCESS_STARTED: '[QSL] Process started:',
12
+ PROCESS_COMPLETED: '[QSL] Process completed:',
13
+ PROCESS_FAILED: '[QSL] Process failed:',
14
+ STYLESHEET_STARTED: '[QSL] Stylesheet loading:',
15
+ STYLESHEET_LOADED: '[QSL] Stylesheet loaded:',
16
+ STYLESHEET_FAILED: '[QSL] Stylesheet failed to load:',
17
+ INLINE_SCRIPT_STARTED: '[QSL] Inline script loading:',
18
+ INLINE_SCRIPT_SUCCESS: '[QSL] Inline script loaded:',
19
+ INLINE_SCRIPT_ERROR: '[QSL] Inline script load error:',
20
+ INLINE_STYLE_STARTED: '[QSL] Inline style loading:',
21
+ INLINE_STYLE_SUCCESS: '[QSL] Inline style loaded:',
22
+ INLINE_STYLE_ERROR: '[QSL] Inline style load error:',
23
+ IMAGE_STARTED: '[QSL] Image pixel loading:',
24
+ IMAGE_LOADED: '[QSL] Image pixel loaded:',
25
+ IMAGE_FAILED: '[QSL] Image pixel failed:',
26
+ SHADOW_STARTED: '[QSL] Shadow element loading:',
27
+ SHADOW_SUCCESS: '[QSL] Shadow element loaded:',
28
+ SHADOW_FAILED: '[QSL] Shadow element failed:',
29
+ HTML_STARTED: '[QSL] HTML element loading:',
30
+ HTML_SUCCESS: '[QSL] HTML element loaded:',
31
+ HTML_FAILED: '[QSL] HTML element failed:',
32
+ PRELOAD_ERROR: '[QSL] Preload error:',
33
+ FLOW_DEP_SKIPPED: '[QSL] Flow dependency missed:',
34
+ DEP_NOT_FOUND: '[QSL] Dependency not found:',
35
+ RESET: '[QSL] Global reset',
36
+ ALL_COMPLETED: '[QSL] Loading is completed',
37
+ },
38
+ log(type, ...args) {
39
+ if (this.LOG[type]) {
40
+ console.log(this.LOG[type], ...args, { timestamp: Date.now() });
41
+ } else {
42
+ console.log('[QSL] ' + type, ...args);
43
+ }
44
+ },
45
+ error(type, ...args) {
46
+ if (this.LOG[type]) {
47
+ console.error(this.LOG[type], ...args);
48
+ } else {
49
+ console.error('[QSL] ' + type, ...args);
50
+ }
51
+ }
52
+ };
53
+
54
+ });
55
+
56
+ }
@@ -0,0 +1,6 @@
1
+ export default function(QSL) {
2
+ QSL.allCompleteActions.add(function() {
3
+ document.dispatchEvent(new Event('DOMContentLoaded'));
4
+ window.dispatchEvent(new Event('load'));
5
+ });
6
+ }