Explorar o código

U theme methods; + dev feedback

Piotr Labudda %!s(int64=6) %!d(string=hai) anos
pai
achega
2a7ac598c2
Modificáronse 4 ficheiros con 884 adicións e 21 borrados
  1. 12 0
      SE/se-lib/Theme.php
  2. 42 1
      SE/se-lib/ThemeDefault.php
  3. 600 0
      SE/static/html-screen-capture.js
  4. 230 20
      SE/static/p5UI/feedback.js

+ 12 - 0
SE/se-lib/Theme.php

@@ -36,6 +36,18 @@ class Theme {
 	public static function login($data) {
 		return self::getInstance()->login($data);
 	}
+	public static function remind($data) {
+		return self::getInstance()->remind($data);
+	}
+	public static function remindSent($data) {
+		return self::getInstance()->remindSent($data);
+	}
+	public static function remindSetNewPassword($data) {
+		return self::getInstance()->remindSetNewPassword($data);
+	}
+	public static function remindNewPasswordSet($data) {
+		return self::getInstance()->remindNewPasswordSet($data);
+	}
 	public static function logout($data) {
 		return self::getInstance()->logout($data);
 	}

+ 42 - 1
SE/se-lib/ThemeDefault.php

@@ -1,5 +1,7 @@
 <?php
 
+Lib::loadClass('Request');
+
 class ThemeDefault {
 
 	function __construct() {
@@ -29,8 +31,14 @@ class ThemeDefault {
 			echo '</div>';
 		}
 		if (User::logged() && '1' === V::get('TEST_FEEDBACK', '', $_GET)) {
+			// 1. lib: html2canvas - not everything supported
 			echo UI::h('script', [ 'type' => "text/javascript", 'src' => "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js" ]);
-			UI::inlineJS(APP_PATH_ROOT . '/static/p5UI/feedback.js');
+			// 2. lib: html-screen-capture (MIT)
+			UI::inlineJS(APP_PATH_ROOT . '/static/html-screen-capture.js');
+
+			UI::inlineJS(APP_PATH_ROOT . '/static/p5UI/feedback.js', [
+				'URL_BASE_PATH' => Request::getPathUri(),
+			]);
 		}
 		echo UI::fixFooterPosition('footer_js_tag');
 	}
@@ -42,6 +50,39 @@ class ThemeDefault {
 		include APP_PATH_WWW . '/se-lib/tmpl/login.php';
 	}
 
+	function remind($data) {
+		if (is_array($data) && !empty($data)) {
+			extract($data);
+		}
+		$loginLink = Request::getPathUri();
+		die('TODO: remind password');
+		// include APP_PATH_WWW . '/se-lib/tmpl/remind.php';
+	}
+	function remindSent($data) {
+		if (is_array($data) && !empty($data)) {
+			extract($data);
+		}
+		$loginLink = Request::getPathUri();
+		die('TODO: remind password sent info');
+		// include APP_PATH_WWW . '/se-lib/tmpl/remindSent.php';
+	}
+	function remindSetNewPassword($data) {
+		if (is_array($data) && !empty($data)) {
+			extract($data);
+		}
+		$loginLink = Request::getPathUri();
+		die('TODO: remind set new password');
+		// include APP_PATH_WWW . '/se-lib/tmpl/remindSetNewPass.php';
+	}
+	function remindNewPasswordSet($data) {
+		if (is_array($data) && !empty($data)) {
+			extract($data);
+		}
+		$loginLink = Request::getPathUri();
+		die('TODO: remind new password set');
+		// include APP_PATH_WWW . '/se-lib/tmpl/remindNewPassSetConfirm.php';
+	}
+
 	function logout($data) {
 		if (is_array($data) && !empty($data)) {
 			extract($data);

+ 600 - 0
SE/static/html-screen-capture.js

@@ -0,0 +1,600 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define("htmlScreenCaptureJs", [], factory);
+	else if(typeof exports === 'object')
+		exports["htmlScreenCaptureJs"] = factory();
+	else
+		root["htmlScreenCaptureJs"] = factory();
+})(typeof self !== 'undefined' ? self : this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 1);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var OutputTypeEnum = function OutputTypeEnum() {
+	_classCallCheck(this, OutputTypeEnum);
+
+	this.OBJECT = 'OBJECT';
+	this.STRING = 'STRING';
+	this.URI = 'URI';
+	this.BASE64 = 'BASE64';
+};
+
+exports.default = OutputTypeEnum;
+module.exports = exports['default'];
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+exports.OutputType = undefined;
+exports.capture = capture;
+
+var _outputTypeEnum = __webpack_require__(0);
+
+var _outputTypeEnum2 = _interopRequireDefault(_outputTypeEnum);
+
+var _capturer = __webpack_require__(2);
+
+var _capturer2 = _interopRequireDefault(_capturer);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var OutputType = exports.OutputType = new _outputTypeEnum2.default();
+
+function capture(outputType, htmlDocument, options) {
+	return new _capturer2.default().capture(outputType, htmlDocument, options);
+}
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _logger = __webpack_require__(3);
+
+var _logger2 = _interopRequireDefault(_logger);
+
+var _encoder = __webpack_require__(4);
+
+var _encoder2 = _interopRequireDefault(_encoder);
+
+var _outputTypeEnum = __webpack_require__(0);
+
+var _outputTypeEnum2 = _interopRequireDefault(_outputTypeEnum);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Capturer = function () {
+	function Capturer() {
+		_classCallCheck(this, Capturer);
+
+		this._logger = new _logger2.default();
+		this._isHead = true;
+		this._classMap = {};
+		this._classCount = 0;
+		this._shouldHandleImgDataUrl = true;
+		this._canvas = null;
+		this._ctx = null;
+		this._doc = null;
+		this._options = {
+			tagsOfIgnoredDocHeadElements: [
+				'script',
+				// 'link',
+				// 'style'
+			],
+			tagsOfIgnoredDocBodyElements: ['script'],
+			classesOfIgnoredDocBodyElements: [],
+			attrKeyValuePairsOfIgnoredElements: {},
+			tagsOfSkippedElementsForChildTreeCssHandling: ['svg'],
+			attrKeyForSavingElementOrigClass: '_class',
+			attrKeyForSavingElementOrigStyle: '_style',
+			prefixForNewGeneratedClasses: 'c',
+			imageFormatForDataUrl: 'image/png',
+			imageQualityForDataUrl: 0.92,
+			rulesToAddToDocStyle: [
+				// '*{font-family:"Arial Narrow" !important;}'
+			]
+		};
+	}
+
+	_createClass(Capturer, [{
+		key: '_overrideOptions',
+		value: function _overrideOptions(options) {
+			if (options) {
+				for (var def in options) {
+					if (options.hasOwnProperty(def)) {
+						this._options[def] = options[def];
+					}
+				}
+			}
+		}
+	}, {
+		key: '_getImgDataUrl',
+		value: function _getImgDataUrl(imgElm) {
+			var imgDataUrl = '';
+			try {
+				if (!this._canvas) {
+					this._canvas = this._doc.createElement('canvas');
+					this._ctx = this._canvas.getContext('2d');
+				}
+				this._canvas.width = imgElm.clientWidth;
+				this._canvas.height = imgElm.clientHeight;
+				this._ctx.drawImage(imgElm, 0, 0);
+				imgDataUrl = this._canvas.toDataURL(this._options.imageFormatForDataUrl, this._options.imageQualityForDataUrl);
+			} catch (ex) {
+				this._logger.warn('getImgDataUrl() - ' + ex.message);
+				this._shouldHandleImgDataUrl = false;
+			}
+			return imgDataUrl;
+		}
+	}, {
+		key: '_getClasses',
+		value: function _getClasses(domElm) {
+			var classes = [];
+			var className = domElm.className instanceof SVGAnimatedString ? domElm.className.baseVal : domElm.className;
+			if (className) {
+				var classNames = className.split(' ');
+				classNames.forEach(function (c) {
+					if (c) {
+						classes.push(c);
+					}
+				});
+			}
+			return classes;
+		}
+	}, {
+		key: '_getClassName',
+		value: function _getClassName(domElm) {
+			var classes = domElm.className;
+			return classes instanceof SVGAnimatedString ? classes.baseVal : classes;
+		}
+	}, {
+		key: '_handleElmCss',
+		value: function _handleElmCss(domElm, newElm) {
+			if (this._getClasses(newElm).length > 0) {
+				if (this._options.attrKeyForSavingElementOrigClass) {
+					newElm.setAttribute(this._options.attrKeyForSavingElementOrigClass, this._getClassName(newElm));
+				}
+				newElm.removeAttribute('class');
+			}
+			if (newElm.getAttribute('style')) {
+				if (this._options.attrKeyForSavingElementOrigStyle) {
+					newElm.setAttribute(this._options.attrKeyForSavingElementOrigStyle, newElm.getAttribute('style'));
+				}
+				newElm.removeAttribute('style');
+			}
+			var computedStyle = getComputedStyle(domElm);
+			var classStr = '';
+			for (var i = 0; i < computedStyle.length; i++) {
+				var property = computedStyle.item(i);
+				var value = computedStyle.getPropertyValue(property);
+				var mapKey = property + ':' + value;
+				var className = this._classMap[mapKey];
+				if (!className) {
+					this._classCount++;
+					className = (this._options.prefixForNewGeneratedClasses ? this._options.prefixForNewGeneratedClasses : 'c') + this._classCount;
+					this._classMap[mapKey] = className;
+				}
+				classStr += className + ' ';
+			}
+			if (classStr) {
+				newElm.setAttribute('class', classStr.trim());
+			}
+		}
+	}, {
+		key: '_appendNewStyle',
+		value: function _appendNewStyle(newHtml) {
+			var style = this._doc.createElement('style');
+			style.type = 'text/css';
+			var cssText = this._options.rulesToAddToDocStyle ? this._options.rulesToAddToDocStyle.join('') : '';
+			for (var def in this._classMap) {
+				if (this._classMap.hasOwnProperty(def)) {
+					cssText += '.' + this._classMap[def] + '{' + def + '}';
+				}
+			}
+			if (style.styleSheet) {
+				style.styleSheet.cssText = cssText;
+			} else {
+				style.appendChild(this._doc.createTextNode(cssText));
+			}
+			newHtml.children[0].appendChild(style);
+		}
+	}, {
+		key: '_shouldIgnoreElm',
+		value: function _shouldIgnoreElm(domElm) {
+			var _this = this;
+
+			var shouldRemoveElm = false;
+			if (this._isHead && this._options.tagsOfIgnoredDocHeadElements && this._options.tagsOfIgnoredDocHeadElements.indexOf(domElm.tagName.toLowerCase()) > -1 || !this._isHead && this._options.tagsOfIgnoredDocBodyElements && this._options.tagsOfIgnoredDocBodyElements.indexOf(domElm.tagName.toLowerCase()) > -1) {
+				shouldRemoveElm = true;
+			}
+			if (!shouldRemoveElm && this._options.attrKeyValuePairsOfIgnoredElements) {
+				for (var attrKey in this._options.attrKeyValuePairsOfIgnoredElements) {
+					if (this._options.attrKeyValuePairsOfIgnoredElements.hasOwnProperty(attrKey)) {
+						for (var i = 0; i < domElm.attributes.length; i++) {
+							if (domElm.attributes[i].specified && domElm.attributes[i].value === this._options.attrKeyValuePairsOfIgnoredElements[attrKey]) {
+								shouldRemoveElm = true;
+							}
+						}
+					}
+				}
+			}
+			if (!shouldRemoveElm && !this._isHead && this._options.classesOfIgnoredDocBodyElements) {
+				var domElmClasses = this._getClasses(domElm);
+				domElmClasses.forEach(function (c) {
+					if (!shouldRemoveElm && _this._options.classesOfIgnoredDocBodyElements.indexOf(c) > -1) {
+						shouldRemoveElm = true;
+					}
+				});
+			}
+			return shouldRemoveElm;
+		}
+	}, {
+		key: '_recursiveWalk',
+		value: function _recursiveWalk(domElm, newElm, handleCss) {
+			if (this._shouldHandleImgDataUrl && !this._isHead && domElm.tagName.toLowerCase() === 'img') {
+				var imgDataUrl = this._getImgDataUrl(domElm);
+				if (imgDataUrl) {
+					newElm.setAttribute('src', imgDataUrl);
+				}
+			}
+			if (handleCss) {
+				this._handleElmCss(domElm, newElm);
+				if (this._options.tagsOfSkippedElementsForChildTreeCssHandling && this._options.tagsOfSkippedElementsForChildTreeCssHandling.indexOf(domElm.tagName.toLowerCase()) > -1) {
+					handleCss = false;
+				}
+			}
+			if (domElm.children) {
+				for (var i = domElm.children.length - 1; i >= 0; i--) {
+					if (this._shouldIgnoreElm(domElm.children[i])) {
+						newElm.removeChild(newElm.children[i]);
+					} else {
+						this._recursiveWalk(domElm.children[i], newElm.children[i], handleCss);
+					}
+				}
+			}
+		}
+	}, {
+		key: '_createNewHtml',
+		value: function _createNewHtml() {
+			var newHtml = this._doc.documentElement.cloneNode(false);
+			this._handleElmCss(this._doc.documentElement, newHtml);
+			return newHtml;
+		}
+	}, {
+		key: '_appendNewHead',
+		value: function _appendNewHead(newHtml) {
+			var newHead = this._doc.head.cloneNode(true);
+			this._isHead = true;
+			this._recursiveWalk(this._doc.head, newHead, false);
+			newHtml.appendChild(newHead);
+		}
+	}, {
+		key: '_appendNewBody',
+		value: function _appendNewBody(newHtml) {
+			var newBody = this._doc.body.cloneNode(true);
+			this._isHead = false;
+			this._recursiveWalk(this._doc.body, newBody, true);
+			newHtml.appendChild(newBody);
+		}
+	}, {
+		key: '_getHtmlObject',
+		value: function _getHtmlObject() {
+			var newHtml = this._createNewHtml();
+			this._appendNewHead(newHtml);
+			this._appendNewBody(newHtml);
+			this._appendNewStyle(newHtml);
+			return newHtml;
+		}
+	}, {
+		key: '_prepareOutput',
+		value: function _prepareOutput(newHtmlObject, outputType) {
+			var output = null;
+			var outputTypeEnum = new _outputTypeEnum2.default();
+			if (!outputType || outputType === outputTypeEnum.OBJECT) {
+				output = newHtmlObject;
+			} else {
+				var outerHtml = (newHtmlObject ? newHtmlObject.outerHTML : '') || '';
+				if (outerHtml) {
+					if (outputType === outputTypeEnum.STRING) {
+						output = outerHtml;
+					} else if (outputType === outputTypeEnum.URI) {
+						output = _encoder2.default.uriEncode(outerHtml);
+					} else if (outputType === outputTypeEnum.BASE64) {
+						output = _encoder2.default.base64Encode(outerHtml);
+					}
+				}
+				output = output || '';
+			}
+			if (this._logger.isDebug()) {
+				this._logger.debug('output: ' + (output.outerHTML ? output.outerHTML : output));
+			}
+			return output;
+		}
+	}, {
+		key: 'capture',
+		value: function capture(outputType, htmlDocument, options) {
+			var output = null;
+			var startTime = new Date().getTime();
+			try {
+				this._overrideOptions(options);
+				this._doc = htmlDocument || document;
+				this._logger.setLogLevel(this._options.logLevel);
+				this._logger.info('capture() outputType: ' + outputType + ' - start');
+				var newHtmlObject = this._getHtmlObject();
+				output = this._prepareOutput(newHtmlObject, outputType);
+			} catch (ex) {
+				this._logger.error('capture() - error - ' + ex.message);
+			} finally {
+				this._logger.info('capture() - end - ' + (new Date().getTime() - startTime) + 'ms');
+			}
+			return output;
+		}
+	}]);
+
+	return Capturer;
+}();
+
+exports.default = Capturer;
+module.exports = exports['default'];
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Logger = function () {
+	function Logger() {
+		_classCallCheck(this, Logger);
+
+		this._logLevelNames = ['debug', 'info', 'warn', 'error', 'fatal', 'off'];
+		this.init();
+	}
+
+	_createClass(Logger, [{
+		key: 'init',
+		value: function init() {
+			this._logLevel = this._logLevelNames.indexOf('warn');
+		}
+	}, {
+		key: 'setLogLevel',
+		value: function setLogLevel(levelName) {
+			if (levelName && this._logLevelNames.indexOf(levelName.toLowerCase()) !== -1) {
+				this._logLevel = this._logLevelNames.indexOf(levelName.toLowerCase());
+			}
+		}
+	}, {
+		key: '_log',
+		value: function _log(msg, levelName) {
+			if (this._logLevel <= this._logLevelNames.indexOf(levelName)) {
+				console.log('|html-screen-capture-js|' + levelName + '| ' + msg);
+			}
+		}
+	}, {
+		key: 'isDebug',
+		value: function isDebug() {
+			return this._logLevel === this._logLevelNames.indexOf('debug');
+		}
+	}, {
+		key: 'debug',
+		value: function debug(msg) {
+			this._log(msg, 'debug');
+		}
+	}, {
+		key: 'info',
+		value: function info(msg) {
+			this._log(msg, 'info');
+		}
+	}, {
+		key: 'warn',
+		value: function warn(msg) {
+			this._log(msg, 'warn');
+		}
+	}, {
+		key: 'error',
+		value: function error(msg) {
+			this._log(msg, 'error');
+		}
+	}, {
+		key: 'fatal',
+		value: function fatal(msg) {
+			this._log(msg, 'fatal');
+		}
+	}]);
+
+	return Logger;
+}();
+
+exports.default = Logger;
+module.exports = exports['default'];
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Encoder = function () {
+	function Encoder() {
+		_classCallCheck(this, Encoder);
+	}
+
+	_createClass(Encoder, null, [{
+		key: '_utf8_encode',
+		value: function _utf8_encode(str) {
+			str = str.replace(/\r\n/g, '\n');
+			var utfText = '';
+			for (var n = 0; n < str.length; n++) {
+				var c = str.charCodeAt(n);
+				if (c < 128) {
+					utfText += String.fromCharCode(c);
+				} else if (c > 127 && c < 2048) {
+					utfText += String.fromCharCode(c >> 6 | 192);
+					utfText += String.fromCharCode(c & 63 | 128);
+				} else {
+					utfText += String.fromCharCode(c >> 12 | 224);
+					utfText += String.fromCharCode(c >> 6 & 63 | 128);
+					utfText += String.fromCharCode(c & 63 | 128);
+				}
+			}
+			return utfText;
+		}
+	}, {
+		key: 'base64Encode',
+		value: function base64Encode(str) {
+			var output = '';
+			var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+			var chr1 = void 0,
+			    chr2 = void 0,
+			    chr3 = void 0,
+			    enc1 = void 0,
+			    enc2 = void 0,
+			    enc3 = void 0,
+			    enc4 = void 0;
+			var i = 0;
+			str = Encoder._utf8_encode(str);
+			while (i < str.length) {
+				chr1 = str.charCodeAt(i++);
+				chr2 = str.charCodeAt(i++);
+				chr3 = str.charCodeAt(i++);
+				enc1 = chr1 >> 2;
+				enc2 = (chr1 & 3) << 4 | chr2 >> 4;
+				enc3 = (chr2 & 15) << 2 | chr3 >> 6;
+				enc4 = chr3 & 63;
+				if (isNaN(chr2)) {
+					enc3 = enc4 = 64;
+				} else if (isNaN(chr3)) {
+					enc4 = 64;
+				}
+				output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
+			}
+			return output;
+		}
+	}, {
+		key: 'uriEncode',
+		value: function uriEncode(str) {
+			return (str ? encodeURI(str) : '') || '';
+		}
+	}]);
+
+	return Encoder;
+}();
+
+exports.default = Encoder;
+module.exports = exports['default'];
+
+/***/ })
+/******/ ]);
+});
+//# sourceMappingURL=html-screen-capture.js.map

+ 230 - 20
SE/static/p5UI/feedback.js

@@ -5,6 +5,8 @@ if (!html2canvas) throw "Missing html2canvas!";
 var h = global.p5VendorJs.React.createElement;
 var ReactDOM = global.p5VendorJs.ReactDOM;
 var createReactClass = global.p5VendorJs.createReactClass;
+var DBG = DBG || false;
+var DBG1 = true;
 
 // echo UI:: h('div', [
 //     'style' => "position:fixed; width:20px; height:20px; right:5px; bottom:5px; cursor:pointer; z-index:1041",
@@ -38,11 +40,13 @@ function p5UI__feedback(event) {
     event.stopPropagation()
     event.preventDefault()
     console.log('DBG:p5UI__feedback', { target: event.target });
-
+    
     if (feedbackNode) hideFeedback();
     else {
+
+        TODO__getDomToJson();
+        
         makeScreenshot().then(function (pngDataUrl) {
-            
             showFeedback(pngDataUrl);
         }).catch(function (err) {
             console.log('Error: ', err);
@@ -59,18 +63,18 @@ function showFeedback(pngDataUrl) {
         document.body.scrollHeight, document.body.offsetHeight,
         document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight
     );
-    feedbackNode.style.height = '' + bodyHeight + 'px';
-    feedbackNode.style.zIndex = '2';
-    feedbackNode.style.backgroundColor = '#eee';
-    document.body.appendChild(feedbackNode)
-    ReactDOM.render(
-        h(p5UI__feedbackBox, {
-            pngDataUrl: pngDataUrl,
-            // nameSection: nameSection,
-            // store: globalGraphStore,
-            // actions: globalGraphActions,
-        }),
-        feedbackNode
+feedbackNode.style.height = '' + bodyHeight + 'px';
+feedbackNode.style.zIndex = '2';
+feedbackNode.style.backgroundColor = '#eee';
+document.body.appendChild(feedbackNode)
+ReactDOM.render(
+    h(p5UI__feedbackBox, {
+        pngDataUrl: pngDataUrl,
+        // nameSection: nameSection,
+        // store: globalGraphStore,
+        // actions: globalGraphActions,
+    }),
+    feedbackNode
     );
 }
 function hideFeedback() {
@@ -109,12 +113,12 @@ var p5UI__feedbackBox = createReactClass({
             h('form', { onSubmit: this.sendFeedback }, [
                 h('table', {
                     style: { width: "100%",
-                        backgroundColor: "#eee",
-                        borderBottom: "2px solid #ccc",
-                    }
-                }, [
-                    h('tr', {}, [
-                        // h('td', { style: { padding: "2px 12px" } }, [
+                    backgroundColor: "#eee",
+                    borderBottom: "2px solid #ccc",
+                }
+            }, [
+                h('tr', {}, [
+                    // h('td', { style: { padding: "2px 12px" } }, [
                         //     h('h1', {}, "Test..."),
                         // ]),
                         h('td', { style: { padding: "2px 12px", width: "10%" } }, [
@@ -155,5 +159,211 @@ var p5UI__feedbackBox = createReactClass({
     }
 })
 
+function TODO__getDomToJson() {
+    // var pageStaticHtml = htmlScreenCaptureJs.capture(htmlScreenCaptureJs.OutputType.STRING);
+    // console.log('DBG:TODO__getDomToJson', { pageStaticHtml });
+    
+    // var pageHtml = document.body.innerHTML;
+    // pageHtml.replace(/<script(.*)<\/script>/g, '')
+    // console.log('DBG:TODO__getDomToJson', pageHtml);
+
+    var staticDomClone = cloneDomToStaticPage(document.body);
+    var staticPage = {
+        width: window.document.body.clientWidth,
+        height: window.document.body.clientHeight,
+        body:
+            '<body' + (staticDomClone.hasAttribute('style') ? ' style="' + staticDomClone.getAttribute('style') + '"' : '') + '>' +
+                staticDomClone.innerHTML +
+            '</body>',
+    }
+    DBG1 && console.log('DBG:TODO__getDomToJson', staticPage);
+}
+
+function cloneDomToStaticPage(node) {
+    var staticClone = node.cloneNode(false);
+    DBG && console.log('DBG:cloneDomToStaticPage', { type: node.nodeType, name: node.nodeName, node })
+    cloneChildrensToStatic(staticClone, node, '');
+    return staticClone;
+}
+function cloneDomToStaticPage__rec(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec', { path, type: node.nodeType, name: node.nodeName, node })
+    switch (node.nodeType) {
+        case 1: return cloneDomToStaticPage__rec__tag(parentStaticClone, node, path); // TAG
+        case 3: return cloneDomToStaticPage__rec__text(parentStaticClone, node, path); // #text
+        default: return cloneDomToStaticPage__rec__unknown(parentStaticClone, node, path);
+    }
+    // for (var child = node.firstChild; child !== null; child = child.nextSibling) {
+    //     DBG && console.log('DBG:cloneDomToStaticPageRec child', { type: child.nodeType, name: child.nodeName, child })
+    // }
+}
+function cloneDomToStaticPage__rec__text(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__text', { path, type: node.nodeType, name: node.nodeName, node })
+    parentStaticClone.appendChild(node.cloneNode(false)); // #text cannot have childrens?
+}
+function cloneDomToStaticPage__rec__tag(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__tag', { path, type: node.nodeType, name: node.nodeName, node })
+    switch (node.nodeName) {
+        case 'SCRIPT': return cloneDomToStaticPage__rec__tag_script(parentStaticClone, node, path + '/script');
+        case 'LINK': return cloneDomToStaticPage__rec__tag_link(parentStaticClone, node, path + '/link');
+        case 'BUTTON': return cloneDomToStaticPage__rec__tag_button(parentStaticClone, node, path + '/link');
+
+        // TODO: if (clonedNode.classList.contains('glyphicon'))
+        case 'I': return cloneDomToStaticPage__rec__tag_with_glyphicon(parentStaticClone, node, path + '/i');
+        case 'SPAN': return cloneDomToStaticPage__rec__tag_with_glyphicon(parentStaticClone, node, path + '/span');
+        case 'A': return cloneDomToStaticPage__rec__tag_with_glyphicon(parentStaticClone, node, path + '/a');
+
+        default: return cloneDomToStaticPage__rec__tag_default(parentStaticClone, node, path + '/' + node.nodeName.toLowerCase());
+    }
+}
+function cloneDomToStaticPage__rec__tag_script(parentStaticClone, node, path) {
+    return false; // skip script tags
+}
+function cloneDomToStaticPage__rec__tag_link(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__tag_link', { path, type: node.nodeType, name: node.nodeName, node })
+    var clonedNode = cloneNodeToStatic(node);
+    parentStaticClone.appendChild(clonedNode);
+    cloneChildrensToStatic(clonedNode, node, path);
+}
+function cloneDomToStaticPage__rec__tag_button(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__tag_button', { path, type: node.nodeType, name: node.nodeName, node })
+    var clonedNode = cloneNodeToStatic(node);
+    { // fix button height with only glyphicon inside (electron bug)
+        var btnStyleObj = document.defaultView.getComputedStyle(node)
+        // clonedNode.style.height = btnStyleObj.height + 'px';
+        clonedNode.setAttribute('style',
+            (clonedNode.hasAttribute('style')
+                ? clonedNode.getAttribute('style') + ';'
+                : ''
+            ) + 'height:' + btnStyleObj.height
+        )
+    }
+    parentStaticClone.appendChild(clonedNode);
+    for (var child = node.firstChild; child !== null; child = child.nextSibling) {
+        cloneDomToStaticPage__rec(clonedNode, child, path);
+    }
+}
+function cloneDomToStaticPage__rec__tag_with_glyphicon(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__tag_with_glyphicon', { path, type: node.nodeType, name: node.nodeName, node })
+    var clonedNode = cloneNodeToStatic(node);
+    { // convert glyphicon
+        if (clonedNode.classList.contains('glyphicon')) {
+            var styleObj = document.defaultView.getComputedStyle(node);
+            var width = parseInt(styleObj.width.replace('px', ''));
+            var height = parseInt(styleObj.height.replace('px', ''));
+            if (!height) width = height = parseInt(styleObj.fontSize.replace('px', ''));
+            if (!height) width = height = 12;
+            DBG && console.log('DBG:glyphicon', { classses: clonedNode.classList, width: styleObj.width, height: styleObj.height, fontSize: styleObj.fontSize });
+            if (convertBsIconToSvg['search'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['envelope'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['map-marker'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['remove'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['download'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['pencil'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['folder-open'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['plus-sign'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['plus'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['refresh'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['cog'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['question-sign'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['align-left'](clonedNode, width, height)) {
+            } else if (convertBsIconToSvg['menu-hamburger'](clonedNode, width, height)) {
+            // triangle-top
+            // camera
+            // off
+            // bell
+            // calendar
+            // user
+            // lock
+            } else {
+                DBG1 && console.log('DBG:glyphicon NOT SUPPORTED', { classses: clonedNode.classList, width: styleObj.width, height: styleObj.height, fontSize: styleObj.fontSize });
+            }
+        }
+    }
+    parentStaticClone.appendChild(clonedNode);
+    cloneChildrensToStatic(clonedNode, node, path);
+}
+var convertBsIconToSvg = {};
+convertBsIconToSvg['search'] = function(node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'search', '<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>');
+};
+convertBsIconToSvg['envelope'] = function(node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'envelope', '<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline>');
+};
+convertBsIconToSvg['map-marker'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'map-marker', '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle>');
+};
+convertBsIconToSvg['remove'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'remove', '<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>');
+};
+convertBsIconToSvg['download'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'download', '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line>');
+};
+convertBsIconToSvg['pencil'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'pencil', '<path d="M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34"></path><polygon points="18 2 22 6 12 16 8 16 8 12 18 2"></polygon>');
+};
+convertBsIconToSvg['folder-open'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'folder-open', '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>');
+};
+convertBsIconToSvg['plus-sign'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'plus-sign', '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line>');
+};
+convertBsIconToSvg['plus'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'plus', '<g stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></g>');
+};
+convertBsIconToSvg['refresh'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'refresh', '<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>');
+};
+convertBsIconToSvg['cog'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'cog', '<circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>');
+};
+convertBsIconToSvg['question-sign'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'question-sign', '<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line>');
+};
+convertBsIconToSvg['align-left'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'align-left', '<line x1="17" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="17" y1="18" x2="3" y2="18"></line>');
+};
+convertBsIconToSvg['menu-hamburger'] = function (node, width, height) {
+    return cloneNodeBsIconToSvg(node, width, height, 'menu-hamburger', '<line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>');
+};
+function cloneNodeBsIconToSvg(node, width, height, bsIcon, svgContent) {
+    if (!node.classList.contains('glyphicon-' + bsIcon)) return false;
+    node.classList.remove('glyphicon-' + bsIcon);
+    DBG && console.log('DBG:cloneNodeBsIconToSvg', { bsIcon, width, height });
+    var svgFont = document.createElement('svg');
+    svgFont.setAttribute('viewBox', "0 0 24 24");
+    svgFont.setAttribute('height', height);
+    svgFont.setAttribute('width', width);
+    svgFont.setAttribute('fill', "none");
+    svgFont.setAttribute('stroke', "currentColor");
+    svgFont.setAttribute('stroke-width', "3");
+    svgFont.setAttribute('stroke-linecap', "round");
+    svgFont.setAttribute('stroke-linejoin', "round");
+    node.appendChild(svgFont)
+    svgFont.innerHTML = svgContent;
+    return true;
+}
+
+
+function cloneDomToStaticPage__rec__tag_default(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__tag_default', { path, type: node.nodeType, name: node.nodeName, node })
+    var clonedNode = cloneNodeToStatic(node);
+    parentStaticClone.appendChild(clonedNode);
+    cloneChildrensToStatic(clonedNode, node, path);
+}
+function cloneDomToStaticPage__rec__unknown(parentStaticClone, node, path) {
+    DBG && console.log('DBG:cloneDomToStaticPage__rec__unknown', { path, type: node.nodeType, name: node.nodeName, node })
+}
+function cloneChildrensToStatic(targetNode, sourceNode, path) {
+    for (var child = sourceNode.firstChild; child !== null; child = child.nextSibling) {
+        cloneDomToStaticPage__rec(targetNode, child, path);
+    }
+}
+function cloneNodeToStatic(node) {
+    var clonedNode = node.cloneNode(false);
+    if (clonedNode.hasAttribute('onclick')) clonedNode.removeAttribute('onclick');
+    if (clonedNode.hasAttribute('href')) clonedNode.removeAttribute('href');
+    // if (clonedNode.hasAttribute('href')) clonedNode.href = clonedNode.href.replace(URL_BASE_PATH, './')
+    return clonedNode;
+}
 
 global.p5UI__feedback = p5UI__feedback;