update
This commit is contained in:
330
AzA march 2026/_updater_test_install/_internal/webview/js/api.js
Normal file
330
AzA march 2026/_updater_test_install/_internal/webview/js/api.js
Normal file
@@ -0,0 +1,330 @@
|
||||
window.pywebview = {
|
||||
token: '%(token)s',
|
||||
platform: '%(platform)s',
|
||||
api: {},
|
||||
_eventHandlers: {},
|
||||
_returnValuesCallbacks: {},
|
||||
|
||||
_createApi: function (funcList) {
|
||||
function sanitize_params(params) {
|
||||
var reservedWords = [
|
||||
'case',
|
||||
'catch',
|
||||
'const',
|
||||
'debugger',
|
||||
'default',
|
||||
'delete',
|
||||
'do',
|
||||
'export',
|
||||
'extends',
|
||||
'false',
|
||||
'function',
|
||||
'instanceof',
|
||||
'let',
|
||||
'new',
|
||||
'null',
|
||||
'super',
|
||||
'switch',
|
||||
'this',
|
||||
'throw',
|
||||
'true',
|
||||
'typeof',
|
||||
'var',
|
||||
'void',
|
||||
];
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var param = params[i];
|
||||
if (reservedWords.indexOf(param) !== -1) {
|
||||
params[i] = param + '_';
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
for (var i = 0; i < funcList.length; i++) {
|
||||
var element = funcList[i];
|
||||
var funcName = element.func;
|
||||
var params = element.params;
|
||||
|
||||
// Create nested structure and assign function
|
||||
var funcHierarchy = funcName.split('.');
|
||||
var functionName = funcHierarchy.pop();
|
||||
var nestedObject = funcHierarchy.reduce(function (obj, prop) {
|
||||
if (!obj[prop]) {
|
||||
obj[prop] = {};
|
||||
}
|
||||
return obj[prop];
|
||||
}, window.pywebview.api);
|
||||
|
||||
// Define the function body
|
||||
var funcBody =
|
||||
'var __id = (Math.random() + "").substring(2);' +
|
||||
'var promise = new Promise(function(resolve, reject) {' +
|
||||
' window.pywebview._checkValue("' +
|
||||
funcName +
|
||||
'", resolve, reject, __id);' +
|
||||
'});' +
|
||||
'window.pywebview._jsApiCallback("' +
|
||||
funcName +
|
||||
'", Array.prototype.slice.call(arguments), __id);' +
|
||||
'return promise;';
|
||||
|
||||
// Assign the new function
|
||||
nestedObject[functionName] = new Function(
|
||||
sanitize_params(params),
|
||||
funcBody
|
||||
);
|
||||
window.pywebview._returnValuesCallbacks[funcName] = {};
|
||||
}
|
||||
},
|
||||
|
||||
_jsApiCallback: function (funcName, params, id) {
|
||||
switch (window.pywebview.platform) {
|
||||
case 'mshtml':
|
||||
case 'cef':
|
||||
case 'qtwebkit':
|
||||
case 'android-webkit':
|
||||
return window.external.call(funcName, pywebview.stringify(params), id);
|
||||
case 'edgechromium':
|
||||
// Full file path support for WebView2
|
||||
if (
|
||||
params.event instanceof Event &&
|
||||
params.event.type === 'drop' &&
|
||||
params.event.dataTransfer.files
|
||||
) {
|
||||
chrome.webview.postMessageWithAdditionalObjects(
|
||||
'FilesDropped',
|
||||
params.event.dataTransfer.files
|
||||
);
|
||||
}
|
||||
return window.chrome.webview.postMessage([
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id,
|
||||
]);
|
||||
case 'cocoa':
|
||||
case 'gtkwebkit2':
|
||||
return window.webkit.messageHandlers.jsBridge.postMessage(
|
||||
pywebview.stringify(
|
||||
{ funcName: funcName, params: params, id: id }
|
||||
)
|
||||
);
|
||||
case 'qtwebengine':
|
||||
if (!window.pywebview._QWebChannel) {
|
||||
setTimeout(function () {
|
||||
window.pywebview._QWebChannel.objects.external.call(
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id
|
||||
);
|
||||
}, 100);
|
||||
} else {
|
||||
window.pywebview._QWebChannel.objects.external.call(
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_checkValue: function (funcName, resolve, reject, id) {
|
||||
window.pywebview._returnValuesCallbacks[funcName][id] = function(returnObj) {
|
||||
var value = returnObj.value;
|
||||
var isError = returnObj.isError;
|
||||
|
||||
delete window.pywebview._returnValuesCallbacks[funcName][id];
|
||||
|
||||
if (isError) {
|
||||
var pyError = JSON.parse(value);
|
||||
var error = new Error(pyError.message);
|
||||
error.name = pyError.name;
|
||||
error.stack = pyError.stack;
|
||||
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(value));
|
||||
}
|
||||
};
|
||||
},
|
||||
_asyncCallback: function (result, id) {
|
||||
window.pywebview._jsApiCallback('pywebviewAsyncCallback', result, id);
|
||||
},
|
||||
_isPromise: function (obj) {
|
||||
return (
|
||||
!!obj &&
|
||||
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||
typeof obj.then === 'function'
|
||||
);
|
||||
},
|
||||
|
||||
stringify: function stringify(obj, timing) {
|
||||
function tryConvertToArray(obj) {
|
||||
try {
|
||||
return Array.prototype.slice.call(obj);
|
||||
} catch (e) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function isArrayLike(a) {
|
||||
return (
|
||||
a &&
|
||||
typeof a.length === 'number' &&
|
||||
typeof a !== 'string' &&
|
||||
(Array.isArray(a) ||
|
||||
(typeof a === 'object' &&
|
||||
a.length >= 0 &&
|
||||
(a.length === 0 || (a.length - 1) in a)))
|
||||
);
|
||||
}
|
||||
|
||||
function serialize(obj, ancestors) {
|
||||
try {
|
||||
if (obj instanceof Window) return 'Window';
|
||||
if (obj instanceof Node) {
|
||||
obj = pywebview.domJSON.toJSON(obj, {
|
||||
metadata: false,
|
||||
serialProperties: true,
|
||||
});
|
||||
}
|
||||
|
||||
var boundSerialize = serialize.bind(obj);
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
while (
|
||||
ancestors.length > 0 &&
|
||||
ancestors[ancestors.length - 1] !== this
|
||||
) {
|
||||
ancestors.pop();
|
||||
}
|
||||
|
||||
if (ancestors.indexOf(obj) > -1) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
ancestors.push(obj);
|
||||
|
||||
if (isArrayLike(obj)) {
|
||||
obj = tryConvertToArray(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
var arr = obj.map(function (value) {
|
||||
return boundSerialize(value, ancestors);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
var newObj = {};
|
||||
for (var key in obj) {
|
||||
if (typeof obj === 'function') {
|
||||
continue;
|
||||
}
|
||||
newObj[key] = boundSerialize(obj[key], ancestors);
|
||||
}
|
||||
return newObj;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
var startTime = +new Date();
|
||||
|
||||
var _serialize = serialize.bind(null);
|
||||
var finalObj = _serialize(obj, []);
|
||||
var result = JSON.stringify(finalObj);
|
||||
|
||||
var endTime = +new Date();
|
||||
if (timing) {
|
||||
console.log('Serialization time: ' + (endTime - startTime) / 1000 + 's');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_getNodeId: function (element) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
var pywebviewId =
|
||||
element.getAttribute('data-pywebview-id') ||
|
||||
Math.random().toString(36).substr(2, 11);
|
||||
if (!element.hasAttribute('data-pywebview-id')) {
|
||||
element.setAttribute('data-pywebview-id', pywebviewId);
|
||||
}
|
||||
return pywebviewId;
|
||||
},
|
||||
|
||||
_insertNode: function (node, parent, mode) {
|
||||
if (mode === 'LAST_CHILD') {
|
||||
parent.appendChild(node);
|
||||
} else if (mode === 'FIRST_CHILD') {
|
||||
parent.insertBefore(node, parent.firstChild);
|
||||
} else if (mode === 'BEFORE') {
|
||||
parent.parentNode.insertBefore(node, parent);
|
||||
} else if (mode === 'AFTER') {
|
||||
parent.parentNode.insertBefore(node, parent.nextSibling);
|
||||
} else if (mode === 'REPLACE') {
|
||||
parent.parentNode.replaceChild(node, parent);
|
||||
}
|
||||
},
|
||||
|
||||
_loadCss: function (css) {
|
||||
var interval = setInterval(function () {
|
||||
if (document.readyState === 'complete') {
|
||||
clearInterval(interval);
|
||||
|
||||
var cssElement = document.createElement('style');
|
||||
cssElement.type = 'text/css';
|
||||
cssElement.innerHTML = css;
|
||||
document.head.appendChild(cssElement);
|
||||
}
|
||||
}, 10);
|
||||
},
|
||||
|
||||
_processElements: function (elements) {
|
||||
var serializedElements = [];
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var pywebviewId;
|
||||
if (elements[i] === window) {
|
||||
pywebviewId = 'window';
|
||||
} else if (elements[i] === document) {
|
||||
pywebviewId = 'document';
|
||||
} else {
|
||||
pywebviewId = window.pywebview._getNodeId(elements[i]);
|
||||
}
|
||||
|
||||
var node = pywebview.domJSON.toJSON(elements[i], {
|
||||
metadata: false,
|
||||
serialProperties: true,
|
||||
deep: false,
|
||||
});
|
||||
|
||||
node._pywebviewId = pywebviewId;
|
||||
serializedElements.push(node);
|
||||
}
|
||||
|
||||
return serializedElements;
|
||||
},
|
||||
|
||||
_debounce: function (func, delay) {
|
||||
var timeout;
|
||||
return function () {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(function () {
|
||||
func.apply(context, args);
|
||||
}, delay);
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
|
||||
(function() {
|
||||
var platform = window.pywebview.platform;
|
||||
var disableText = '%(text_select)s' === 'False';
|
||||
var disableTextCss = 'body {-webkit-user-select: none; -khtml-user-select: none; -ms-user-select: none; user-select: none; cursor: default;}'
|
||||
|
||||
if (platform == 'mshtml') {
|
||||
window.alert = function(msg) {
|
||||
window.external.alert(msg);
|
||||
}
|
||||
} else if (platform == 'edgechromium') {
|
||||
window.alert = function (message) {
|
||||
window.chrome.webview.postMessage(['_pywebviewAlert', pywebview.stringify(message), 'alert']);
|
||||
}
|
||||
} else if (platform == 'gtkwebkit2') {
|
||||
window.alert = function (message) {
|
||||
window.webkit.messageHandlers.jsBridge.postMessage(pywebview.stringify({funcName: '_pywebviewAlert', params: message, id: 'alert'}));
|
||||
}
|
||||
} else if (platform == 'cocoa') {
|
||||
window.print = function() {
|
||||
window.webkit.messageHandlers.browserDelegate.postMessage('print');
|
||||
}
|
||||
} else if (platform === 'qtwebengine') {
|
||||
window.alert = function (message) {
|
||||
window.pywebview._QWebChannel.objects.external.call('_pywebviewAlert', pywebview.stringify(message), 'alert');
|
||||
}
|
||||
} else if (platform === 'qtwebkit') {
|
||||
window.alert = function (message) {
|
||||
window.external.invoke(JSON.stringify(['_pywebviewAlert', message, 'alert']));
|
||||
}
|
||||
}
|
||||
|
||||
if (disableText) {
|
||||
var css = document.createElement("style");
|
||||
css.type = "text/css";
|
||||
css.innerHTML = disableTextCss;
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
function disableTouchEvents() {
|
||||
var initialX = 0;
|
||||
var initialY = 0;
|
||||
|
||||
function onMouseMove(ev) {
|
||||
var x = ev.screenX - initialX;
|
||||
var y = ev.screenY - initialY;
|
||||
window.pywebview._jsApiCallback('pywebviewMoveWindow', [x, y], 'move');
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
function onMouseDown(ev) {
|
||||
if (
|
||||
'%(drag_region_direct_target_only)s' === 'True' &&
|
||||
!ev.target.matches('%(drag_selector)s')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
initialX = ev.clientX;
|
||||
initialY = ev.clientY;
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
}
|
||||
|
||||
function onBodyMouseDown(event) {
|
||||
var target = event.target;
|
||||
var dragSelectorElements = document.querySelectorAll('%(drag_selector)s');
|
||||
|
||||
while (target && target !== document.body && target !== document.documentElement) {
|
||||
if (target.nodeType === 1) {
|
||||
// Check if target matches the drag selector
|
||||
for (var i = 0; i < dragSelectorElements.length; i++) {
|
||||
if (dragSelectorElements[i] === target) {
|
||||
onMouseDown(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it doesn't match, continue up the DOM tree
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('mousedown', onBodyMouseDown);
|
||||
|
||||
// easy drag for edge chromium
|
||||
if ('%(easy_drag)s' === 'True') {
|
||||
window.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
|
||||
if ('%(zoomable)s' === 'False') {
|
||||
document.body.addEventListener('touchstart', function(e) {
|
||||
if ((e.touches.length > 1) || e.targetTouches.length > 1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}, {passive: false});
|
||||
|
||||
window.addEventListener('wheel', function (e) {
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
}
|
||||
|
||||
// draggable
|
||||
if ('%(draggable)s' === 'False') {
|
||||
document.addEventListener('dragstart', function(e) {
|
||||
if (e.target.tagName === 'IMG' || e.target.tagName === 'A') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disableTouchEvents();
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
window.pywebview._createApi(JSON.parse('%(functions)s'));
|
||||
|
||||
if (window.pywebview.platform == 'qtwebengine') {
|
||||
new QWebChannel(qt.webChannelTransport, function(channel) {
|
||||
window.pywebview._QWebChannel = channel;
|
||||
window.dispatchEvent(new CustomEvent('pywebviewready'));
|
||||
});
|
||||
} else {
|
||||
window.dispatchEvent(new CustomEvent('pywebviewready'));
|
||||
}
|
||||
@@ -0,0 +1,823 @@
|
||||
/**
|
||||
* domJSON.js: A simple framework for converting DOM nodes to special JSON objects, and vice versa
|
||||
*
|
||||
* @fileOverview
|
||||
* @author Alex Zaslavsky
|
||||
* @version 0.1.2
|
||||
* @license The MIT License: Copyright (c) 2013 Alex Zaslavsky
|
||||
*/
|
||||
|
||||
|
||||
|
||||
//Load the library
|
||||
;(function(root, factory) {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define === 'function' && define.amd) { //AMD
|
||||
define(function(){
|
||||
return factory(root);
|
||||
});
|
||||
} else if (typeof exports !== 'undefined') { //CommonJS/node.js
|
||||
var domJSON = factory(root);
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = domJSON;
|
||||
}
|
||||
exports = domJSON;
|
||||
} else { //Browser global
|
||||
window.pywebview.domJSON = factory(root);
|
||||
}
|
||||
})(this, function(win){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* domJSON is a global variable to store two methods: `.toJSON()` to convert a DOM Node into a JSON object, and `.toDOM()` to turn that JSON object back into a DOM Node
|
||||
* @namespace domJSON
|
||||
* @global
|
||||
*/
|
||||
var domJSON = {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An object specifying a list of fields and how to filter it, or an array with the first value being an optional boolean to convey the same information
|
||||
* @typedef {Object|Array} FilterList
|
||||
* @property {boolean} [exclude=false] If this is set to `true`, the `filter` property will specify which fields to exclude from the result (boolean difference), not which ones to include (boolean intersection)
|
||||
* @property {string[]} values An array of strings which specify the fields to include/exclude from some broader list
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default metadata for a JSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var metadata = {
|
||||
href: win.location.href || null,
|
||||
userAgent: window.navigator && window.navigator.userAgent ? window.navigator.userAgent : null,
|
||||
version: '0.1.2'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default options for creating the JSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var defaultsForToJSON = {
|
||||
absolutePaths: ['action', 'data', 'href', 'src'],
|
||||
//absStylePaths: ['attr', 'background', 'background-image', 'border-image', 'border-image-source', 'content', 'list-style-image', 'mask-image'], //http://stackoverflow.com/questions/27790925/what-are-all-the-css3-properties-that-accept-urls-or-uris
|
||||
attributes: true,
|
||||
computedStyle: false,
|
||||
cull: true,
|
||||
deep: true,
|
||||
domProperties: true,
|
||||
filter: false,
|
||||
htmlOnly: false,
|
||||
metadata: true,
|
||||
//parse: false,
|
||||
serialProperties: false,
|
||||
stringify: false,
|
||||
allowDangerousElements: false
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default options for creating a DOM node from a previously generated domJSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var defaultsForToDOM = {
|
||||
noMeta: false,
|
||||
allowDangerousElements: false
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of disallowed HTMLElement tags - there is no flexibility here, these cannot be processed by domJSON for security reasons!
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var banned = [
|
||||
'link',
|
||||
'script'
|
||||
]; //Consider (maybe) adding the following tags: iframe, html, audio, video, object
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of node properties that must be copied if they exist; there is no user option that will remove these
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var required = [
|
||||
'nodeType',
|
||||
'nodeValue',
|
||||
'tagName'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of node properties to specifically avoid simply copying; there is no user option that will allow these to be copied directly
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var ignored = [
|
||||
'attributes',
|
||||
'childNodes',
|
||||
'children',
|
||||
'classList',
|
||||
'dataset',
|
||||
'style'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of serialized read-only nodes to ignore; these can overwritten if the user specifies the "filter" option
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var serials = [
|
||||
'innerHTML',
|
||||
'innerText',
|
||||
'outerHTML',
|
||||
'outerText',
|
||||
'prefix',
|
||||
'text',
|
||||
'textContent',
|
||||
'wholeText'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility function to extend an object - useful for synchronizing user-submitted options with default values; same API as underscore extend
|
||||
* @param {Object} [target] The object that will be extended
|
||||
* @param {...Object} [added] Additional objects that will extend the target
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var extend = function(target) {
|
||||
if (!arguments.length) {
|
||||
return arguments[0] || {};
|
||||
}
|
||||
|
||||
//Overwrite matching properties on the target from the added object
|
||||
for (var p in arguments[1]) {
|
||||
target[p] = arguments[1][p];
|
||||
}
|
||||
|
||||
//If we have more arguments, run the function recursively
|
||||
if (arguments.length > 2) {
|
||||
var moreArgs = [target].concat(Array.prototype.slice.call(arguments, 2));
|
||||
return extend.apply( null, moreArgs);
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the unique values (in the order they first appeared) from one or more arrays
|
||||
* @param {...Array} constituent An array to combine into a larger array of unique values
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var unique = function() {
|
||||
if (!arguments.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var all = Array.prototype.concat.apply([], arguments);
|
||||
for (var a = 0; a < all.length; a++) {
|
||||
if (all.indexOf(all[a]) < a) {
|
||||
all.splice(a, 1);
|
||||
a--;
|
||||
}
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make a shallow copy of an object or array
|
||||
* @param {Object|string[]} item The object/array that will be copied
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var copy = function(item) {
|
||||
if (item instanceof Array) {
|
||||
return item.slice();
|
||||
} else {
|
||||
var output = {};
|
||||
for (var i in item) {
|
||||
output[i] = item[i];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Do a boolean intersection between an array/object and a filter array
|
||||
* @param {Object|string[]} item The object/array that will be intersected with the filter
|
||||
* @param {boolean|string[]} filter Specifies which properties to select from the "item" (or element to keep, if "item is an array")
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolInter = function(item, filter) {
|
||||
var output;
|
||||
if (item instanceof Array) {
|
||||
output = unique(item.filter(function(val) { return filter.indexOf(val) > -1; }));
|
||||
} else {
|
||||
output = {};
|
||||
for (var f in filter) {
|
||||
if (item.hasOwnProperty(filter[f])) {
|
||||
output[filter[f]] = item[filter[f]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Do a boolean difference between an array/object and a filter array
|
||||
* @param {Object|string[]} item The object/array that will be differentiated with the filter
|
||||
* @param {boolean|string[]} filter Specifies which properties to exclude from the "item" (or element to remove, if "item is an array")
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolDiff = function(item, filter) {
|
||||
var output;
|
||||
if (item instanceof Array) {
|
||||
output = unique(item.filter(function(val) { return filter.indexOf(val) === -1; }));
|
||||
} else {
|
||||
output = {};
|
||||
for (var i in item) {
|
||||
output[i] = item[i];
|
||||
}
|
||||
for (var f in filter) {
|
||||
if (output.hasOwnProperty(filter[f])) {
|
||||
delete output[filter[f]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether we want to do a boolean intersection or difference
|
||||
* @param {Object|string[]} item The object/array that will be differentiated with the filter
|
||||
* @param {boolean|Array} filter Specifies which a filter behavior; if it is an array, the first value can be a boolean, indicating whether the filter array is intended for differentiation (true) or intersection (false)
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolFilter = function(item, filter) {
|
||||
//A "false" filter means we return an empty copy of item
|
||||
if (filter === false){
|
||||
return (item instanceof Array) ? [] : {};
|
||||
}
|
||||
|
||||
if (filter instanceof Array && filter.length) {
|
||||
if (typeof filter[0] === 'boolean') {
|
||||
if (filter.length == 1 && typeof(filter[0]) === 'boolean') {
|
||||
//There is a filter array, but its only a single boolean
|
||||
if (filter[0] === true) {
|
||||
return copy(item);
|
||||
} else {
|
||||
return (item instanceof Array) ? [] : {};
|
||||
}
|
||||
} else {
|
||||
//The filter operation has been set explicitly; true = difference
|
||||
if (filter[0] === true) {
|
||||
return boolDiff(item, filter.slice(1));
|
||||
} else {
|
||||
return boolInter(item, filter.slice(1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//There is no explicit operation on the filter, meaning it defaults to an intersection
|
||||
return boolInter(item, filter);
|
||||
}
|
||||
} else {
|
||||
return copy(item);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that a FilterList type input is converted into its shorthand array form
|
||||
* @param {boolean|FilterList} filterList The FilterList, or boolean, that will converted into the shorthand form
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toShorthand = function(filterList) {
|
||||
var outputArray;
|
||||
if (typeof filterList === 'boolean') {
|
||||
return filterList;
|
||||
} else if (typeof filterList === 'object' && filterList !== null) {
|
||||
if (filterList instanceof Array) {
|
||||
return filterList.filter(function(v, i){
|
||||
return typeof v === 'string' || (i === 0 && v === true) ? true : false;
|
||||
});
|
||||
} else {
|
||||
if (!(filterList.values instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outputArray = filterList.values.filter(function(v){
|
||||
return typeof v === 'string' ? true : false;
|
||||
});
|
||||
|
||||
if (!outputArray.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterList.exclude) {
|
||||
outputArray.unshift(filterList.exclude);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
} else if (filterList) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the supplied string value is a relative path, and convert it to an absolute one if necessary; the segment processing paths leading with "../" was inspired by: http://stackoverflow.com/a/14780463/2230156
|
||||
* @param {string} value The value that might be a relative path, and would thus need conversion
|
||||
* @param {Object} origin The origin URL from which to which non-absolute paths are relative
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toAbsolute = function(value, origin) {
|
||||
var protocol, stack, parts;
|
||||
//Sometimes, we get lucky and the DOM Node we're working on already has the absolute URL as a DOM property, so we can just use that
|
||||
/*if (node[name]){
|
||||
//We can just grab the compiled URL directly from the DOM element - easy peasy
|
||||
var sub = node[name].indexOf(value);
|
||||
if (sub !== -1) {
|
||||
return node[name];
|
||||
}
|
||||
}*/
|
||||
|
||||
//Check to make sure we don't already have an absolute path, or even a dataURI
|
||||
if ( value.match(/(?:^data\:|^[\w\-\+\.]*?\:\/\/|^\/\/)/i) ){
|
||||
return value;
|
||||
}
|
||||
|
||||
//If we are using the root URL, start from there
|
||||
if ( value.charAt(0) === '/' ){
|
||||
return origin + value.substr(1);
|
||||
}
|
||||
|
||||
//Uh-oh, the relative path is leading with a single or double dot ("./" or "../"); things get a bit harder...
|
||||
protocol = origin.indexOf('://') > -1 ? origin.substring(0, origin.indexOf('://') + 3) : '';
|
||||
stack = (protocol.length ? origin.substring(protocol.length) : origin).split('/');
|
||||
parts = value.split('/');
|
||||
|
||||
//The value after the last slash is ALWAYS considered a filename, not a directory, so always have trailing slashes on paths ending at directories!
|
||||
stack.pop();
|
||||
|
||||
//Cycle through the relative path, changing the stack as we go
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (parts[i] == '.') {
|
||||
continue;
|
||||
}
|
||||
if (parts[i] == '..') {
|
||||
if (stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
} else {
|
||||
stack.push(parts[i]);
|
||||
}
|
||||
}
|
||||
return (protocol + stack.join('/'));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a copy of a node's properties, ignoring nasty things like event handles and functions
|
||||
* @param {Node} node The DOM Node whose properties will be copied
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var copyJSON = function(node, opts) {
|
||||
var copy = {};
|
||||
//Copy all of the node's properties
|
||||
for (var n in node){
|
||||
//Make sure this property can be accessed
|
||||
try {
|
||||
//accessing `selectionDirection`, `selectionStart`, or `selectionEnd` throws in WebKit-based browsers
|
||||
node[n];
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
//Make sure this is an own property, and isn't a live javascript function for security reasons
|
||||
if (typeof node[n] !== 'undefined' && typeof node[n] !== 'function' && n.charAt(0).toLowerCase() === n.charAt(0)) {
|
||||
//Only allowed objects are arrays
|
||||
if ( typeof node[n] !== 'object' || node[n] instanceof Array ) {
|
||||
//If we are eliminating empty fields, make sure this value is not NULL or UNDEFINED
|
||||
if (opts.cull) {
|
||||
if (node[n] || node[n] === 0 || node[n] === false) {
|
||||
copy[n] = node[n];
|
||||
}
|
||||
} else {
|
||||
copy[n] = node[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copy = boolFilter(copy, opts.domProperties);
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert the attributes property of a DOM Node to a JSON ready object
|
||||
* @param {Node} node The DOM Node whose attributes will be copied
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var attrJSON = function(node, opts) {
|
||||
var attributes = {};
|
||||
var attr = node.attributes;
|
||||
var length = attr.length;
|
||||
var absAttr;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
attributes[attr[i].name] = attr[i].value;
|
||||
}
|
||||
attributes = opts.attributes ? boolFilter(attributes, opts.attributes) : null;
|
||||
|
||||
//Add the attributes object, converting any specified absolute paths along the way
|
||||
absAttr = boolFilter(attributes, opts.absolutePaths);
|
||||
for (var i in absAttr) {
|
||||
attributes[i] = toAbsolute(absAttr[i], opts.absoluteBase);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Grab a DOM Node's computed style
|
||||
* @param {Node} node The DOM Node whose computed style will be calculated
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var styleJSON = function(node, opts) {
|
||||
//Grab the computed style
|
||||
var style, css = {};
|
||||
if (opts.computedStyle && node.style instanceof CSSStyleDeclaration) {
|
||||
style = win.getComputedStyle(node);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Get the relevant properties from the computed style
|
||||
for (var k in style) {
|
||||
if ( k !== 'cssText' && !k.match(/\d/) && typeof style[k] === 'string' && style[k].length ) {
|
||||
//css.push(k+ ': ' +style[k]+ ';');
|
||||
css[k] = style[k];
|
||||
}
|
||||
}
|
||||
|
||||
//Filter the style object
|
||||
return (opts.computedStyle instanceof Array) ? boolFilter(css, opts.computedStyle) : css;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert a single DOM Node into a simple object
|
||||
* @param {Node} node The DOM Node that will be converted
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toJSON = function(node, opts, depth) {
|
||||
var style, kids, kidCount, thisChild, children, copy = copyJSON(node, opts);
|
||||
|
||||
//Per default, some tags are not allowed
|
||||
if (node.nodeType === 1) {
|
||||
if (!opts.allowDangerousElements) {
|
||||
for (var b in banned) {
|
||||
if (node.tagName.toLowerCase() === banned[b]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.nodeType === 3 && !node.nodeValue.trim()) {
|
||||
//Ignore empty buffer text nodes
|
||||
return null;
|
||||
}
|
||||
|
||||
//Copy all attributes and styles, if allowed
|
||||
if (opts.attributes && node.attributes) {
|
||||
copy.attributes = attrJSON(node, opts);
|
||||
}
|
||||
if (opts.computedStyle && (style = styleJSON(node, opts))) {
|
||||
copy.style = style;
|
||||
}
|
||||
|
||||
//Should we continue iterating?
|
||||
if (opts.deep === true || (typeof opts.deep === 'number' && opts.deep > depth)) {
|
||||
//We should!
|
||||
children = [];
|
||||
kids = (opts.htmlOnly) ? node.children : node.childNodes;
|
||||
kidCount = kids.length;
|
||||
for (var c = 0; c < kidCount; c++) {
|
||||
thisChild = toJSON(kids[c], opts, depth + 1);
|
||||
if (thisChild) {
|
||||
children.push(thisChild);
|
||||
}
|
||||
}
|
||||
|
||||
//Append the children in the appropriate place
|
||||
copy.childNodes = children;
|
||||
}
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Take a DOM node and convert it to simple object literal (or JSON string) with no circular references and no functions or events
|
||||
* @param {Node} node The actual DOM Node which will be the starting point for parsing the DOM Tree
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @param {boolean} [opts.allowDangerousElements=`false`] Use `true` to parse the potentially dangerous elements `<link>` and `<script>`
|
||||
* @param {boolean|FilterList} [opts.absolutePaths=`'action', 'data', 'href', 'src'`] Only relevant if `opts.attributes` is not `false`; use `true` to convert all relative paths found in attribute values to absolute paths, or specify a `FilterList` of keys to boolean search
|
||||
* @param {boolean|FilterList} [opts.attributes=`true`] Use `true` to copy all attribute key-value pairs, or specify a `FilterList` of keys to boolean search
|
||||
* @param {boolean|FilterList} [opts.computedStyle=`false`] Use `true` to parse the results of "window.getComputedStyle()" on every node (specify a `FilterList` of CSS properties to be included via boolean search); this operation is VERY costly performance-wise!
|
||||
* @param {boolean} [opts.cull=`false`] Use `true` to ignore empty element properties
|
||||
* @param {boolean|number} [opts.deep=`true`] Use `true` to iterate and copy all childNodes, or an INTEGER indicating how many levels down the DOM tree to iterate
|
||||
* @param {boolean|FilterList} [opts.domProperties=true] 'false' means only 'tagName', 'nodeType', and 'nodeValue' properties will be copied, while a `FilterList` can specify DOM properties to include or exclude in the output (except for ones which serialize the DOM Node, which are handled separately by `opts.serialProperties`)
|
||||
* @param {boolean} [opts.htmlOnly=`false`] Use `true` to only iterate through childNodes where nodeType = 1 (aka, instances of HTMLElement); irrelevant if `opts.deep` is `true`
|
||||
* @param {boolean} [opts.metadata=`false`] Output a special object of the domJSON class, which includes metadata about this operation
|
||||
* @todo {boolean|FilterList} [opts.parse=`false`] a `FilterList` of properties that are DOM nodes, but will still be copied **PLANNED**
|
||||
* @param {boolean|FilterList} [opts.serialProperties=`true`] Use `true` to ignore the properties that store a serialized version of this DOM Node (ex: outerHTML, innerText, etc), or specify a `FilterList` of serial properties (no boolean search!)
|
||||
* @param {boolean} [opts.stringify=`false`] Output a JSON string, or just a JSON-ready javascript object?
|
||||
* @return {Object|string} A JSON-friendly object, or JSON string, of the DOM node -> JSON conversion output
|
||||
* @method
|
||||
* @memberof domJSON
|
||||
*/
|
||||
domJSON.toJSON = function(node, opts) {
|
||||
var copy, keys = [], options = {}, output = {};
|
||||
var timer = new Date().getTime();
|
||||
var requiring = required.slice();
|
||||
var ignoring = ignored.slice();
|
||||
|
||||
//Update the default options w/ the user's custom settings
|
||||
options = extend({}, defaultsForToJSON, opts);
|
||||
|
||||
//Convert all options that accept FilterList type inputs into the shorthand notation
|
||||
options.absolutePaths = toShorthand(options.absolutePaths);
|
||||
options.attributes = toShorthand(options.attributes);
|
||||
options.computedStyle = toShorthand(options.computedStyle);
|
||||
options.domProperties = toShorthand(options.domProperties);
|
||||
options.serialProperties = toShorthand(options.serialProperties);
|
||||
|
||||
//Make sure there is a base URL for absolute path conversions
|
||||
options.absoluteBase = win.location.origin + '/';
|
||||
|
||||
//Make lists of which DOM properties to skip and/or which are absolutely necessary
|
||||
if (options.serialProperties !== true) {
|
||||
if (options.serialProperties instanceof Array && options.serialProperties.length) {
|
||||
if (options.serialProperties[0] === true) {
|
||||
ignoring = ignoring.concat( boolDiff(serials, options.serialProperties) );
|
||||
} else {
|
||||
ignoring = ignoring.concat( boolInter(serials, options.serialProperties) );
|
||||
}
|
||||
} else {
|
||||
ignoring = ignoring.concat( serials );
|
||||
}
|
||||
}
|
||||
if (options.domProperties instanceof Array) {
|
||||
if (options.domProperties[0] === true) {
|
||||
options.domProperties = boolDiff( unique(options.domProperties, ignoring), requiring );
|
||||
} else {
|
||||
options.domProperties = boolDiff( unique(options.domProperties, requiring), ignoring );
|
||||
}
|
||||
} else {
|
||||
if (options.domProperties === false) {
|
||||
options.domProperties = requiring;
|
||||
} else {
|
||||
options.domProperties = [true].concat(ignoring);
|
||||
}
|
||||
}
|
||||
|
||||
//Transform the node into an object literal
|
||||
copy = toJSON(node, options, 0);
|
||||
|
||||
//Wrap our copy object in a nice object of its own to save some metadata
|
||||
if (options.metadata) {
|
||||
output.meta = extend({}, metadata, {
|
||||
clock: new Date().getTime() - timer,
|
||||
date: new Date().toISOString(),
|
||||
dimensions: {
|
||||
inner: {
|
||||
x: window.innerWidth,
|
||||
y: window.innerHeight
|
||||
},
|
||||
outer: {
|
||||
x: window.outerWidth,
|
||||
y: window.outerHeight
|
||||
}
|
||||
},
|
||||
options: options
|
||||
});
|
||||
output.node = copy;
|
||||
} else {
|
||||
output = copy;
|
||||
}
|
||||
|
||||
//If opts.stringify is true, turn the output object into a JSON string
|
||||
if (options.stringify) {
|
||||
return JSON.stringify(output);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a node based on a given nodeType
|
||||
* @param {number} type The type of DOM Node (only the integers 1, 3, 7, 8, 9, 10, 11 are valid, see https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType); currently, only nodeTypes 1,3, and 11 have been tested and are officially supported
|
||||
* @param {DocumentFragment} doc The document fragment to which this newly created DOM Node will be added
|
||||
* @param {Object} data The saved DOM properties that are part of the JSON representation of this DOM Node
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var createNode = function(type, doc, data) {
|
||||
if (doc instanceof DocumentFragment) {
|
||||
doc = doc.ownerDocument;
|
||||
}
|
||||
switch(type) {
|
||||
case 1: //HTMLElement
|
||||
if (typeof data.tagName === 'string') {
|
||||
return doc.createElement(data.tagName);
|
||||
}
|
||||
return false;
|
||||
|
||||
case 3: //Text Node
|
||||
if (typeof data.nodeValue === 'string' && data.nodeValue.length) {
|
||||
return doc.createTextNode(data.nodeValue);
|
||||
}
|
||||
return doc.createTextNode('');
|
||||
|
||||
case 7: //Processing Instruction
|
||||
if (data.hasOwnProperty('target') && data.hasOwnProperty('data')) {
|
||||
return doc.createProcessingInstruction(data.target, data.data);
|
||||
}
|
||||
return false;
|
||||
|
||||
case 8: //Comment Node
|
||||
if (typeof data.nodeValue === 'string') {
|
||||
return doc.createComment(data.nodeValue);
|
||||
}
|
||||
return doc.createComment('');
|
||||
|
||||
case 9: //HTML Document
|
||||
return doc.implementation.createHTMLDocument(data);
|
||||
|
||||
case 11: //Document Fragment
|
||||
return doc;
|
||||
|
||||
default: //Failed
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//Recursively convert a JSON object generated by domJSON to a DOM Node
|
||||
/**
|
||||
* Do the work of converting a JSON object/string generated by domJSON to a DOM Node
|
||||
* @param {Object} obj The JSON representation of the DOM Node we are about to create
|
||||
* @param {HTMLElement} parent The HTML Element to which this DOM Node will be appended
|
||||
* @param {DocumentFragment} doc The document fragment to which this newly created DOM Node will be added
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toDOM = function(obj, parent, doc, opts) {
|
||||
//Create the node, if possible
|
||||
if (obj.nodeType) {
|
||||
//Per default, some tags are not allowed
|
||||
if (obj.nodeType === 1 && !opts.allowDangerousElements) {
|
||||
for (var b in banned) {
|
||||
if (obj.tagName.toLowerCase() === banned[b]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
var node = createNode(obj.nodeType, doc, obj);
|
||||
parent.appendChild(node);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Copy all available properties that are not arrays or objects
|
||||
for (var x in obj) {
|
||||
if (typeof obj[x] !== 'object' && x !== 'isContentEditable' && x !== 'childNodes') {
|
||||
try {
|
||||
node[x] = obj[x];
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If this is an HTMLElement, set the attributes
|
||||
var src;
|
||||
if (obj.nodeType === 1 && obj.tagName) {
|
||||
if (obj.attributes) {
|
||||
//Check for cross-origin
|
||||
/*src = obj.attributes.src ? 'src' : (obj.attributes.href ? 'href' : null);
|
||||
if (src) {
|
||||
obj.attributes[src] += ( (obj.attributes[src].indexOf('?') === -1) ? '?' : '&'+Math.random().toString(36).slice(-2)+'=' ) + Math.random().toString(36).slice(-4);
|
||||
obj.attributes.crossorigin = 'anonymous';
|
||||
//node.setAttribute('crossorigin', 'anonymous');
|
||||
}*/
|
||||
for (var a in obj.attributes) {
|
||||
node.setAttribute(a, obj.attributes[a]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Finally, if we have childNodes, recurse through them
|
||||
if (obj.childNodes && obj.childNodes.length) {
|
||||
for (var c in obj.childNodes) {
|
||||
toDOM(obj.childNodes[c], node, doc, opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Take the JSON-friendly object created by the `.toJSON()` method and rebuild it back into a DOM Node
|
||||
* @param {Object} obj A JSON friendly object, or even JSON string, of some DOM Node
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @param {boolean} [opts.allowDangerousElements=`false`] Use `true` to include the potentially dangerous elements `<link>` and `<script>`
|
||||
* @param {boolean} [opts.noMeta=`false`] `true` means that this object is not wrapped in metadata, which it makes it somewhat more difficult to rebuild properly...
|
||||
* @return {DocumentFragment} A `DocumentFragment` (nodeType 11) containing the result of unpacking the input `obj`
|
||||
* @method
|
||||
* @memberof domJSON
|
||||
*/
|
||||
domJSON.toDOM = function(obj, opts) {
|
||||
var options, node;
|
||||
//Parse the JSON string if necessary
|
||||
if (typeof obj === 'string') {
|
||||
obj = JSON.parse(obj);
|
||||
}
|
||||
//Update the default options w/ the user's custom settings
|
||||
options = extend({}, defaultsForToDOM, opts);
|
||||
|
||||
//Create a document fragment, and away we go!
|
||||
node = document.createDocumentFragment();
|
||||
if (options.noMeta) {
|
||||
toDOM(obj, node, node, options);
|
||||
} else {
|
||||
toDOM(obj.node, node, node, options);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* test-code */
|
||||
//The code below is only included for private API testing, and needs to be removed in distributed builds
|
||||
domJSON.__extend = extend;
|
||||
domJSON.__unique = unique;
|
||||
domJSON.__copy = copy;
|
||||
domJSON.__boolFilter = boolFilter;
|
||||
domJSON.__boolInter = boolInter;
|
||||
domJSON.__boolDiff = boolDiff;
|
||||
domJSON.__toShorthand = toShorthand;
|
||||
/* end-test-code */
|
||||
|
||||
return domJSON;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
pywebview.state = (function() {
|
||||
|
||||
function createEventTargetFromJSON(jsonString) {
|
||||
var data = JSON.parse(jsonString);
|
||||
var eventTarget = new EventTarget();
|
||||
|
||||
for (var key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
eventTarget[key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return eventTarget;
|
||||
}
|
||||
var initialState = '%(state)s'
|
||||
var target = createEventTargetFromJSON(initialState);
|
||||
|
||||
return new Proxy(target, {
|
||||
get: function(obj, key) {
|
||||
var value = obj[key];
|
||||
if (typeof(value) == 'function'){
|
||||
return value.bind(obj);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
set: function(target, key, value) {
|
||||
var haltUpdate = false;
|
||||
if (key.indexOf('__pywebviewHaltUpdate__') == 0) {
|
||||
key = key.replace('__pywebviewHaltUpdate__', '');
|
||||
haltUpdate = true;
|
||||
}
|
||||
var oldValue = target[key];
|
||||
|
||||
if (oldValue === value) {
|
||||
return
|
||||
}
|
||||
|
||||
target[key] = value;
|
||||
target.dispatchEvent(new CustomEvent('change', {detail: {key: key, value: value}}))
|
||||
|
||||
if (!haltUpdate) {
|
||||
pywebview._jsApiCallback('pywebviewStateUpdate', { key: key, value: value}, (Math.random() + "").substring(2));
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteProperty: function(target, key) {
|
||||
var haltUpdate = false;
|
||||
if (key.indexOf('__pywebviewHaltUpdate__') == 0) {
|
||||
key = key.replace('__pywebviewHaltUpdate__', '');
|
||||
haltUpdate = true;
|
||||
}
|
||||
|
||||
if (key in target) {
|
||||
var oldValue = target[key];
|
||||
delete target[key];
|
||||
|
||||
if (!haltUpdate) {
|
||||
pywebview._jsApiCallback('pywebviewStateDelete', key, (Math.random() + "").substring(2));
|
||||
}
|
||||
target.dispatchEvent(new CustomEvent('delete', {detail: {key: key, value: oldValue}}))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})();
|
||||
Reference in New Issue
Block a user