文書の表示以前のリビジョンバックリンク文書の先頭へ この文書は読取専用です。文書のソースを閲覧することは可能ですが、変更はできません。もし変更したい場合は管理者に連絡してください。 <html> <div id="app"> <!--[head-start]--> <!--<script src='/_media/javascript/jquery/jquery-3.4.1.js'></script> <script> jQuery.noConflict(); </script>--> <script> //self.DEBUG = true; // DukuWiki cookie backup const dokujQuery = jQuery; </script> <script src="/_media/javascript/requirejs/require-2.3.6.min.js?cache=recache"></script> <script src="/_media/javascript/requirejs/settings.js?cache=nocache"></script> <style> /*#region [CSS Style]*/ #dw__toc { display: none; } #dokuwiki__content h1 { text-shadow: 0 0 5px rgba(0, 0, 0, .3); } #container { background-image: url(/_media/mg_chara.svg?cache=recache); background-repeat: no-repeat; background-position: center; background-color: #000; position: relative; z-index: 0; margin-bottom: 1.5em; } #container::before { content: ''; background: inherit; filter: blur(3px); position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: -1; overflow: hidden; } #_selfCheck { opacity: 0.8; background-color: white; } #_selfCheckConsole { padding: 5px; background-color: #002240; border-style: solid; border-width: 1px; border-color: white; } .terminal { /*--color: rgba(0, 128, 0, 0.99);*/ --color: white; --background: #002240; border-style: solid; border-width: 1px; border-color: white; } #loadingPanel { opacity: 0.8; background-color: white; } #ideMain { opacity: 0.8; background-color: white; } #ideHeader { padding: 5px; background-color: #002240; border-style: solid; border-width: 1px; border-color: white; } /* タブスタイル */ .ui-helper-reset { line-height: 12px; } .ui-tabs { overflow: hidden; padding: 0; } .ui-tabs .ui-tabs-nav li a { padding: 8px 3px 2px 8px; } .ui-tabs-nav .ui-tabs-active { background: -moz-linear-gradient(top, #3399ff 0%, #fff 10%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0.01, #3399ff), color-stop(0.1, #fff)); } .ui-widget-header { background-color: #002240; } /*.ui-tabs .ui-tabs-panel { background: initial; } .ui-widget-content { background: initial; } .ui-tabs-panel { background: initial; }*/ .ui-tab-title { float: left; } .ui-icon-close { float: right; margin: -2px 0 0 2px; cursor: pointer; } .ui-tabs .ui-tabs-panel { padding: 0; width: 100%; height: 100%; } /* タブヘッダーの丸みを解除 */ .ui-corner-all, ul.ui-corner-all { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-top-left-radius: 0; border-top-right-radius: 0; } /* タブヘッダーのボーダーを削除 */ .ui-widget-header { border: 0; } #circularG { top: 30%; } #ideHeader p { margin: 0; } #aceEditor { border-width: 1px; border-style: solid; border-color: lightgrey; } #circularG{ position:relative; width:128px; height:128px; margin: auto; } .circularG { position:absolute; background-color:rgb(0,0,255); width:30px; height:30px; border-radius:19px; -o-border-radius:19px; -ms-border-radius:19px; -webkit-border-radius:19px; -moz-border-radius:19px; animation-name:bounce_circularG; -o-animation-name:bounce_circularG; -ms-animation-name:bounce_circularG; -webkit-animation-name:bounce_circularG; -moz-animation-name:bounce_circularG; animation-duration:0.478s; -o-animation-duration:0.478s; -ms-animation-duration:0.478s; -webkit-animation-duration:0.478s; -moz-animation-duration:0.478s; animation-iteration-count:infinite; -o-animation-iteration-count:infinite; -ms-animation-iteration-count:infinite; -webkit-animation-iteration-count:infinite; -moz-animation-iteration-count:infinite; animation-direction:normal; -o-animation-direction:normal; -ms-animation-direction:normal; -webkit-animation-direction:normal; -moz-animation-direction:normal; } #circularG_1 { left:0; top:51px; animation-delay:0.178s; -o-animation-delay:0.178s; -ms-animation-delay:0.178s; -webkit-animation-delay:0.178s; -moz-animation-delay:0.178s; } #circularG_2 { left:13px; top:13px; animation-delay:0.244s; -o-animation-delay:0.244s; -ms-animation-delay:0.244s; -webkit-animation-delay:0.244s; -moz-animation-delay:0.244s; } #circularG_3 { top:0; left:51px; animation-delay:0.3s; -o-animation-delay:0.3s; -ms-animation-delay:0.3s; -webkit-animation-delay:0.3s; -moz-animation-delay:0.3s; } #circularG_4 { right:13px; top:13px; animation-delay:0.356s; -o-animation-delay:0.356s; -ms-animation-delay:0.356s; -webkit-animation-delay:0.356s; -moz-animation-delay:0.356s; } #circularG_5 { right:0; top:51px; animation-delay:0.422s; -o-animation-delay:0.422s; -ms-animation-delay:0.422s; -webkit-animation-delay:0.422s; -moz-animation-delay:0.422s; } #circularG_6 { right:13px; bottom:13px; animation-delay:0.478s; -o-animation-delay:0.478s; -ms-animation-delay:0.478s; -webkit-animation-delay:0.478s; -moz-animation-delay:0.478s; } #circularG_7 { left:51px; bottom:0; animation-delay:0.544s; -o-animation-delay:0.544s; -ms-animation-delay:0.544s; -webkit-animation-delay:0.544s; -moz-animation-delay:0.544s; } #circularG_8 { left:13px; bottom:13px; animation-delay:0.6s; -o-animation-delay:0.6s; -ms-animation-delay:0.6s; -webkit-animation-delay:0.6s; -moz-animation-delay:0.6s; } @keyframes bounce_circularG { 0%{ transform:scale(1); } 100%{ transform:scale(.3); } } @-o-keyframes bounce_circularG { 0%{ -o-transform:scale(1); } 100%{ -o-transform:scale(.3); } } @-ms-keyframes bounce_circularG { 0%{ -ms-transform:scale(1); } 100%{ -ms-transform:scale(.3); } } @-webkit-keyframes bounce_circularG { 0%{ -webkit-transform:scale(1); } 100%{ -webkit-transform:scale(.3); } } @-moz-keyframes bounce_circularG { 0%{ -moz-transform:scale(1); } 100%{ -moz-transform:scale(.3); } } /*#endregion*/ </style> <script> 'use strict'; self.DEBUG = !!self.DEBUG; if (!!!$) self.$ = jQuery; class Counter { static increment() { this._count = this._count || (new WeakMap()).set(this, 0) || this._count.set(this, this._count.get(this) + 1); return this._count.get(this); } } self.Counter = Counter; //#region [String extends] // String.bytelength - 文字列のバイトサイズを取得 Object.defineProperty(String.prototype, 'bytelength', { get: function () { console.log(encodeURIComponent(this).replace(/%../g, ' ')); return encodeURIComponent(this).replace(/%../g, ' ').length; }}); // String.linelength - 文字列の行数を取得 Object.defineProperty(String.prototype, 'linelength', { get: function () { return this.split('\n').length; }}); // String.hashCode - 文字列のハッシュコードを取得 String.prototype.hashCode = function() { let value = this; let hash = 0, len = value.length, char; if (len === 0) return hash; for(let i = 0; i < len; ++i) { char = value.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } hash = hash.toString(16); hash = hash.substring(0, 1) == '-' ? '1' + hash.substring(1) : '0' + hash; return hash; }; // String.suppressZero - 少数部の 0 サプレス処理 String.prototype.suppressZero = function() { let value = this; var mt, result = // 小数点以降に0が続く場合は小数点以降を除去 (mt = value.match(/(-?[\d,]+)\.0+$/)) ? mt[1] : // 小数部の最後の0以外の数字以降に続く0を除去 (mt = value.match(/(-?[\d,]+\.\d+[1-9])0+$/)) ? mt[1] : this; return result; }; // String.suppressZero - 数値のカンマ編集 String.prototype.formatComma = function() { let result = this, value = this; if (value !== '') { if (!isNaN(value)) { //value = new BigDecimal(unformat_comma(value)); result = value.replace(/^(-?\d+)(?=\.|$)/, (str) => { return str.replace(/(\d+?)(?=(?:\d{3})+$)/g, '$1,'); }); } } return result; }; //#endregion //#region [Show loadingPanel] jQuery(($) => { const $container = $('#container'); $container.append( `<article id="loadingPanel" style="width: 100%; height: 870px;"> <section id="circularG"> <div id="circularG_1" class="circularG"></div> <div id="circularG_2" class="circularG"></div> <div id="circularG_3" class="circularG"></div> <div id="circularG_4" class="circularG"></div> <div id="circularG_5" class="circularG"></div> <div id="circularG_6" class="circularG"></div> <div id="circularG_7" class="circularG"></div> <div id="circularG_8" class="circularG"></div> </section> </article>`); }); //#endregion console.log(`[_selfCheck] script(DEBUG: ${DEBUG}) - start`); //#region [isMobile] self.isMobile = () => { var agent = navigator.userAgent; return (agent.search(/iPhone/) != -1 || agent.search(/iPad/) != -1 || agent.search(/iPod/) != -1 || agent.search(/Android/) != -1); }; //#endregion //#region [_selfCheck config] const config = { editorDefault: { theme: 'ace/theme/cobalt', options: { maxLines: isMobile() ? 20 : 30, enableBasicAutocompletion: true, enableSnippets: true, enableLiveAutocompletion: true, }, }, }; //#endregion /*const tomoyanSetup = () => { alert('tomoyanSetup!'); };*/ //#region [ProcessTime class] class ProcessTime { constructor() { this.timeTable = {}; } start(procName) { this.timeTable[procName] = [new Date(), null]; } end(procName) { const table = this.timeTable[procName]; table[1] = new Date(); return table[1] - table[0]; } } self.procTime = new ProcessTime(); //#endregion //#region [DokuWiki class] define('DokuWiki', ['axios'], (axios) => { console.log(`axios: ${axios}`); return class DokuWiki { //#region [DokuWiki class constants] static get HEAD_START() { return '<!--[head-start]-->'; } static get HEAD_END() { return '<!--[head-end]-->'; } static get CODE_START() { return '<!--[code-start]-->'; } static get CODE_END() { return '<!--[code-end]-->'; } static get WIKI_URL() { return location.origin + location.pathname; } //#endregion constructor() { this.MEDIA_AJAX_URL = '/lib/exe/ajax.php'; $('#wiki_hash').hide(); $('#wiki_hash + div').hide(); $('#wiki_hash + div + div').hide(); } //#region [getMediaList function] async getMediaList(ns, grep_) { const grep = !!grep_ ? grep_ : ''; procTime.start('DokuWiki.getMediaList'); // Create Ajax Post Form let $form = $('<form>'); $form.append($('<input>').attr({name: 'call', value: 'medialist'})); $form.append($('<input>').attr({name: 'ns', value: ns})); $form.append($('<input>').attr({name: 'tab_files', value: 'files'})); $form.append($('<input>').attr({name: 'do', value: 'media'})); const url = this.MEDIA_AJAX_URL; let htmlData = ''; console.log(`[DokuWiki.getMediaList] - ajax(${url}, POST)`); await $.ajax({ url: url, type: 'POST', data: $form.serialize(), dataType: 'html', }).done((data, textStatus, jqXHR) => { const ms = procTime.end('DokuWiki.getMediaList'); console.log(`[DokuWiki.getMadiaList] - ajax done! (${ms}ms)`); htmlData = data; }).fail((jqXHR, textStatus, errorThrown) => { console.log('[DokuWiki.getMadiaList] - ajax fail!'); console.log(`[DokuWiki.getMadiaList] ${errorThrown}`); }); /*htmlData = `<!DOCTYPE html> <html`+` lang="ja"> <head> <meta charset="utf-8" /> </head> <body> <div> ${htmlData} </div> </body> </`+`html>`;*/ let $html = $($.parseHTML(htmlData)); //console.log(htmlData); let $list = $html.find('ul.rows li'); if ($list.length === 0) $list = $html.find('ul.thumbs li'); let fileList = []; //console.log($list.length); for (let i = 0; i < $list.length; i++) { let name = $($list[i].innerHTML).find('dd.name a')[0].innerHTML; let size = $($list[i].innerHTML).find('dd.filesize')[0].innerHTML; //console.log(`name: ${name}, size: ${size}`); if (grep !== '' ? name.match(grep) !== null : true) { //console.log({name: name, size: size}); fileList.push({name: name, size: size}); } } // fileList sort fileList.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1); //console.log(fileList); return [fileList, htmlData]; } //#endregion //region [getWikiText function] async getWikiText() { const url = DokuWiki.WIKI_URL + '?do=edit'; let wikiText = ''; procTime.start('DokuWiki.getWikiText'); console.log(`[DokuWiki.getWikiText] - ajax(${url}, GET)`); await $.ajax({ url: url, type: 'GET', dataType: 'html', }).done((htmlData, textStatus, jqXHR) => { const ms = procTime.end('DokuWiki.getWikiText'); console.log(`[DokuWiki.getWikiText] - ajax done! (${ms}ms)`); const $html = $($.parseHTML(htmlData)); const editForm = $html.find('#dw__editform'); wikiText = editForm.find('#wiki__text').text(); console.log(`[DokuWiki.getWikiText] Wiki text lines: ${wikiText.linelength} line!`); }).fail((jqXHR, textStatus, errorThrown) => { console.log('[DokuWiki.getWikiText] - ajax fail!'); console.log(`[DokuWiki.getWikiText] ${errorThrown}`); }); // ajax end return wikiText; } //#endregion //#region [getAppCode function] getAppCode(wikiText) { const wikiTextLines = wikiText.split('\n'); const appCodeLines = []; let codeIn = 0; let codeOut = 0; for (let i = 0; i < wikiTextLines.length; i++) { if (wikiTextLines[i] === DokuWiki.CODE_START) codeIn = i + 2; else if (wikiTextLines[i] === DokuWiki.CODE_END) codeOut = i; if (codeIn !== 0 && i >= codeIn && codeOut === 0) appCodeLines.push(wikiTextLines[i]); } codeIn += 1; codeOut += 1; appCodeLines.pop(); console.log(`[DokuWiki.getAppCode] App code lines: ${appCodeLines.length} line!`); console.log(`[DokuWiki.getAppCode] EditWikiText codeIn: ${codeIn}, codeOut: ${codeOut}`); return appCodeLines.join('\n'); } //#endregion //#region [getWikiRevisions function] async getWikiRevisions(grep) { grep = !!grep ? grep : '^((?!(:\\d{2} |[^ ]\\d{4} lines)).)*$'; console.log(grep); const url = DokuWiki.WIKI_URL + '?do=revisions'; let $noDiv; let result = ''; let first = 0; let maxNo = 0; let formatLine; do { const firstParam = first !== 0 ? `&first=${first}` : ''; procTime.start('DokuWiki.getWikiRevisions'); console.log(`[DokuWiki.getWikiRevisions] - ajax(${url}${firstParam}, GET)`); await $.ajax({ url: `${url}${firstParam}`, type: 'GET', dataType: 'html', }).done((htmlData, textStatus, jqXHR) => { const ms = procTime.end('DokuWiki.getWikiRevisions'); console.log(`[DokuWiki.getWikiRevisions] - ajax done! (${ms}ms)`); const $html = $($.parseHTML(htmlData)); $noDiv = $html.find('.button.btn_older .no'); const $pageRevisions = $html.find('#page__revisions ul li'); for (let i = 0; i < $pageRevisions.length; i++) { const $item = $($pageRevisions[i]); const no = `000000${first === 0 ? i + 1 : Number(first) + i + 2}`.slice(-6); maxNo = Number(no); let revNo = $item.find('input[name="rev2[]"]').val(); revNo = revNo === 'current' ? ` ${revNo} ` : `${revNo}`; const date = $item.find('.date').text().trim(); const sum = $item.find('.sum bdi').text().trim(); const user = $item.find('.user bdi:first').text().trim(); const ip = $item.find('.user bdo[dir="ltr"]').text().trim(); const sizechange = $item.find('.sizechange').text().trim(); formatLine = `${no} ${revNo} ${date} ${sum} ${user} ${ip} ${sizechange}`; if (grep !== '' ? formatLine.match(grep) !== null : true) { result += `${formatLine}\n`; console.log(formatLine); } } if ($noDiv.length !== 0) { first = $noDiv.find('input[name="first"]').val(); } }).fail((jqXHR, textStatus, errorThrown) => { console.log('[DokuWiki.getWikiRevisions] - ajax fail!'); console.log(`[DokuWiki.getWikiRevisions] ${errorThrown}`); }); // ajax end } while($noDiv.length !== 0) if (maxNo !== 0) { formatLine = `Total commit count: ${maxNo}`; console.log(formatLine); result += `${formatLine}\n`; } return result; } } //#endregion }); //#endregion class V8Native { // Mobile is static variables not supported //static #checkFunction; static _wrapEval(script) { return Function(`"use strict"; return ${script};`).apply(this); } static isAllowNativeSyntax(func) { let allowNative = false; const messages = []; try { this._wrapEval('%GetOptimizationStatus();'); allowNative = true; messages.push('Native v8 functions are available!!'); } catch (err) { messages.push('v8 engine is not running with --js-flags="--allow-natives-syntax";'); messages.push('Native v8 functions are not available.'); allowNative = false; } if (typeof func === 'function') func(messages); return allowNative; } static GetFunctionName(func) { if (this.isAllowNativeSyntax() && typeof func === 'function') { this._checkFunction = func; return this._wrapEval( '%GetFunctionName(this._checkFunction);'); } else { if (typeof func === 'undefined') console.warn('func is undefined.'); else console.warn(`'${func.toString()}' is not function.`); return ''; } } static GetOptimizationStatus(func) { if (this.isAllowNativeSyntax() && typeof func === 'function') { this._checkFunction = func; return this._wrapEval( '%GetOptimizationStatus(this._checkFunction);'); } else { if (typeof func === 'undefined') console.warn('func is undefined.'); else console.warn(`'${func.toString()}' is not function.`); return 0; } } static printOptimizationStatus(func) { console.log(`func: ${func}`); console.log(`typeof func: ${typeof func}`); const optStatusString = []; let funcName = ''; const checkAllowNativeSyntax = this.isAllowNativeSyntax(); console.log(`checkAllowNativeSyntax: ${checkAllowNativeSyntax}`); if (checkAllowNativeSyntax) { const checkBitmap = (value, bit) => { return ((value & bit) === bit); }; const optStatus = this.GetOptimizationStatus(func); funcName = this.GetFunctionName(func); if (checkBitmap(optStatus,1)) { optStatusString.push("is Function"); } if (checkBitmap(optStatus,2)) { optStatusString.push("Never Optimized"); } if (checkBitmap(optStatus,4)) { optStatusString.push("Always Optimized"); } if (checkBitmap(optStatus,8)) { optStatusString.push("Maybe Deopted"); } if (checkBitmap(optStatus,16)) { optStatusString.push("Optimized"); } if (checkBitmap(optStatus,32)) { optStatusString.push("TurboFanned"); } if (checkBitmap(optStatus,64)) { optStatusString.push("Interpreted"); } if (checkBitmap(optStatus,128)) { optStatusString.push("Marked for Optimization"); } if (checkBitmap(optStatus,256)) { optStatusString.push("Marked for Concurrent Optimization"); } if (checkBitmap(optStatus,512)) { optStatusString.push("Conccurently Optimizating"); } if (checkBitmap(optStatus,1024)) { optStatusString.push("Is Executing"); } if (checkBitmap(optStatus,2048)) { optStatusString.push("Topmost frame is Turbo Fanned"); } if (checkBitmap(optStatus,4096)) { optStatusString.push("Lite Mode"); } if (checkBitmap(optStatus,8192)) { optStatusString.push("Marked for Deoptimization"); } console.log(`${funcName}: ${optStatusString.join(", ")}`); } return { [funcName]: optStatusString.join(", "), }; } } //#region [dokujQueryMerge] // DokuWiki jQuery に存在する jQuery Plugin 類を // 新しい jQuery をロードした際に移植する。 // ※これを行わないと新しい jQuery をロードした際に // DokuWiki がエラーを起こす。 self.dokujQueryMerge = () => { if (!!dokujQuery) { procTime.start('dokujQueryMerge'); let dokuKeys = Object.keys(dokujQuery); let dokuDiffKeys = new Set(dokuKeys); let jqKeys = Object.keys(jQuery); //console.dir(dokuKeys); //console.dir(dokuDiffKeys); //console.dir(jqKeys); for (let i = 0; i < jqKeys.length; i++) { dokuDiffKeys.delete(jqKeys[i]); } console.log(`merge count: ${dokuDiffKeys.size}`); for (let key of dokuDiffKeys) { //console.log(`merge: ${key}`); jQuery[key] = dokujQuery[key]; } dokuKeys = Object.keys(dokujQuery.fn); dokuDiffKeys = new Set(dokuKeys); jqKeys = Object.keys(jQuery.fn); //console.dir(dokuKeys); //console.dir(dokuDiffKeys); //console.dir(jqKeys); for (let i = 0; i < jqKeys.length; i++) { dokuDiffKeys.delete(jqKeys[i]); } console.log(`fn merge count: ${dokuDiffKeys.size}`); for (let key of dokuDiffKeys) { //console.log(`fn merge: ${key}`); jQuery.fn[key] = dokujQuery.fn[key]; } let ms = procTime.end('dokujQueryMerge'); console.log(`Doku jQuery Merge time: ${ms}ms`); } }; //#endregion //#region [RequireJS version info] if (!!requirejs) { console.log(`RequireJS Version: ${requirejs.version} loaded.`); } else { console.log('[_selfCheck] RequireJS not loaded!'); throw new Error('RequireJS not loaded!'); } //#endregion { const __version__ = '0.0.4'; const wrapEval = (script) => { return (new Function(`"use strict"; return ${script};`))(); }; console.log('[_selfCheck] selfCheck - start!!'); require(['DokuWiki'], (DokuWiki) => { const dokuWiki = new DokuWiki(); //#region [ConsoleEx class] class ConsoleEx { constructor() { this._logBuffer = []; this.memory = new MemoryInfo(); this._console = self.console; } assert() { this._console.assert().apply(this, arguments); } clear () { this._console.clear().apply(this, arguments); } context() { this._console.context().apply(this, arguments); } count() { this._console.count().apply(this, arguments); } countReset() { this._console.countReset().apply(this, arguments); } debug() { this._console.debug().apply(this, arguments); } dir() { this._console.dir().apply(this, arguments); } dirxml() { this._console.dirxml().apply(this, arguments); } error() { this._console.error().apply(this, arguments); } group() { this._console.group().apply(this, arguments); } groupCollapsed() { this._console.groupCollapsed().apply(this, arguments); } groupEnd() { this._console.groupEnd().apply(this, arguments); } info() { this._console.info().apply(this, arguments); } log() { this._console.log().apply(this, arguments); } profile() { this._console.profile().apply(this, arguments); } profileEnd() { this._console.profileEnd().apply(this, arguments); } table() { this._console.table().apply(this, arguments); } time() { this._console.time().apply(this, arguments); } timeEnd() { this._console.timeEnd().apply(this, arguments); } timeLog() { this._console.timeLog().apply(this, arguments); } timeStamp() { this._console.timeStamp().apply(this, arguments); } trace() { this._console.trace().apply(this, arguments); } warn() { this._console.warn().apply(this, arguments); } } //#endregion console.log('[_selfCheck] Main - start!!'); let processingRuntimeException; //#region [Set All Runtime Code Error Trap] let flgRuntimeCodeErrorFound = false; let exceptionList = []; console.log('[_selfCheck] Set all exception trap handler'); window.onerror = (msg, url, lineNo, columnNo, err) => { if (!flgRuntimeCodeErrorFound) { flgRuntimeCodeErrorFound = true; console.log('[_selfCheck] Runtime Exception Trap !!'); } console.log(`[_selfCheck] Error occured: ${msg}, url: ${url}, line: ${lineNo}`); exceptionList.push({ msg: msg, url: url, lineNo: lineNo, columnNo: columnNo, err: err}); if (!!self.trapExceptionSetting) { let tExs = self.trapExceptionSetting; if (!!tExs.trap) { if (!!tExs.callback && typeof tExs.callback === 'function') { tExs.callback(exceptionList); exceptionList.length = 0; // Clear List } } } else if (!!processingRuntimeException) { processingRuntimeException(); exceptionList.length = 0; // Clear List } return false; }; //#endregion procTime.start('_selfCheck.jQueryLoad'); console.log('[_selfCheck] jQuery loading...'); require(['jquery'], ($) => { console.log('[_selfCheck] Doku jQuery Merge...'); dokujQueryMerge(); //#region [jQuery version info] let ms = procTime.end('_selfCheck.jQueryLoad'); if (!!$) { console.log(`[_selfCheck] jQuery Version: ${$.fn.jquery} loaded! ${ms}ms`); } else { console.log(`[_selfCheck] jQuery not loaded! ${ms}ms`); return; } //#endregion //#region [selfCheck DOM build] procTime.start('_selfCheck.DOMBuild'); console.log('[_selfCheck] DOM Build start !'); const $container = $('#container'); $container.show(); $container.append($('<article>').attr({id: '_selfCheck'})); const $selfCheck = $('#_selfCheck'); $selfCheck .hide() .append($('<section>').attr({id: '_selfCheckEditor', style: 'width: 100%;'})) .append($('<section>').attr({id: '_selfCheckToolBar', style: 'width: 100%;'})) .append($('<section>').attr({id: '_selfCheckConsole', style: 'overflow: scroll; overflow-x: hidden; ' + (isMobile() ? 'height: 100px' : 'height: 200px') + '; ' + 'font-family: monospace; font-size: 12px;', })); const $selfCheckEditor = $('#_selfCheckEditor'); const $selfCheckToolBar = $('#_selfCheckToolBar'); $selfCheckToolBar .append($('<input>').attr({ id: '_selfCheckSaveServer', type: 'button', value: 'Save Server'})) .append($('<span>説明:</span>')) .append($('<input>').attr({ id: '_selfCheckSaveDescription', type: 'text', size: isMobile() ? '30' : '50'})); const $saveServer = $('#_selfCheckSaveServer'); const $saveDesc = $('#_selfCheckSaveDescription'); const $selfCheckConsole = $('#_selfCheckConsole'); const showInfo = (message) => { $selfCheckConsole.append( $(`<p style="margin: 0;"><span style="color: white;">${message}</span></p>`)); $selfCheckConsole.animate({scrollTop:$selfCheckConsole.find(':last-child').offset().top}); } const showError = (message) => { $selfCheckConsole.append( $(`<p style="margin: 0;"><span style="color: red;">${message}</span></p>`)); $selfCheckConsole.animate({scrollTop:$selfCheckConsole.find(':last-child').offset().top}); } ms = procTime.end('_selfCheck.DOMBuild'); console.log(`[_selfCheck] DOM Build end! ${ms}ms`); //#endregion procTime.start('_selfCheck.ACEEditorLoad'); console.log('[_selfCheck] Ace Editor loading...'); require(['jquery', 'ace', 'ace.language_tools'], ($, ace) => { //#region [Ace Editor version info] let ms = procTime.end('_selfCheck.ACEEditorLoad'); if (!!ace) { console.log(`[_selfCheck] Ace Editor Version: ${ace.version} loaded! ${ms}ms`); } else { console.log(`[_selfCheck] Ace Editor not loaded! ${ms}ms`); return; } //#endregion $(() => { console.log('[_selfCheck] $() start!'); // DOM を構築後に非同期処理に入る // ※async function 内では DOM を操作出来ない。 // しかし、Ace Editor は動作するようである。 (async () => { //#region [最新の wiki page (表示ページ) を取得] let rawWikiPageLines = []; let url = location.origin + location.pathname; console.log(`[_selfCheck] WikiPage - ajax(${url}, GET)`); await $.ajax({ url: url, type: 'GET', dataType: 'html', }).done((htmlData) => { console.log('[_selfCheck] WikiPage - ajax done!'); rawWikiPageLines = htmlData.split('\n'); console.log(`[_selfCheck] Raw wiki page lines: ${rawWikiPageLines.length} line!`); }).fail((jqXHR, textStatus, errorThrown) => { console.log('[_selfCheck] WikiPage - ajax fail!'); console.log(errorThrown); }); // ajax end //#endregion // 最新の wiki text (編集ページ) を取得 const wikiText = await dokuWiki.getWikiText(); //#region [Ace Editor 初期化処理] ace.require("ace/ext/language_tools"); ace.require('ace/multi_select'); ace.require("ace/ext/emmet"); //#region [Ace Editor fold all region] isCommentFold = (line) => { var isAFold = false; if (/^\s*(\/\*|\/\/)#?region\b/.test(line)) { isAFold = true; } if (/\/\/(.*)\{/.test(line)) { isAFold = true; } return isAFold; } ace.EditSession.prototype.foldAllRegion = function(startRow, endRow, depth) { if (depth == undefined) { //depth = 100000; // JSON.stringify doesn't hanle Infinity depth = 1; } var foldWidgets = this.foldWidgets; console.log(`[_selfCheck] foldWidgets length: ${typeof foldWidgets === 'undefined' ? 'undefined' : foldWidgets.length}`); console.dir(foldWidgets); if (!foldWidgets) return; // mode doesn't support folding endRow = endRow || this.getLength(); startRow = startRow || 0; for (var row = startRow; row < endRow; row++) { if (foldWidgets[row] == null) foldWidgets[row] = this.getFoldWidget(row); if (foldWidgets[row] != "start") continue; if (!isCommentFold(this.getLine(row))) continue var range = this.getFoldWidgetRange(row); // sometimes range can be incompatible with existing fold // TODO change addFold to return null istead of throwing if (range && range.isMultiLine() && range.end.row <= endRow && range.start.row >= startRow) { row = range.end.row; try { // addFold can change the range var fold = this.addFold("...", range); if (fold) fold.collapseChildren = depth; } catch(ex) {} } } }; //endregion const editor = ace.edit($selfCheckEditor[0].id); self._selfCheckEditor = editor; //editor.session.setMode("ace/mode/html"); editor.setTheme(config.editorDefault.theme); editor.session.setOptions({ mode: 'ace/mode/html', tabSize: 2, useSoftTabs: true, }); console.log('[_selfCheck] Ace Editor set wikiText!'); editor.setValue(wikiText, -1); editor.setOptions(config.editorDefault.options); //#endregion //#region [Load editor state from LocalStorage] if (!!window.localStorage) { let state; try { state = JSON.parse(localStorage['_selfCheckEditorState']); } catch (ex) { } if (!!state) { const session = editor.session; console.log('[_selfCheck] Load editor state <- LocalStorage'); showInfo('Load editor state <- LocalStorage'); //session.setValue(state.value); session.selection.fromJSON(state.selection); session.setOptions(state.options); session.setMode(state.mode !== 'ace/mode/text' ? state.mode : 'ace/mode/html'); // 折り畳み状態を復元 if (state.codeHash === editor.getValue().hashCode()) { try { state.folds.forEach((fold) => { fold.collapseChildren = 1; session.addFold(fold.placeholder, ace.Range.fromPoints(fold.start, fold.end)); }); } catch(ex) { console.log('[_selfCheck] Ace Editor Set fold fail!!'); console.log(ex); } } session.setScrollTop(state.scrollTop); session.setScrollLeft(state.scrollLeft); } } //#endregion //#region [Ace Editor app code validation...] console.log('[_selfCheck] Ace Editor app code validation...'); const appCode = dokuWiki.getAppCode(wikiText); const valEditor = ace.edit(); valEditor.session.setMode("ace/mode/javascript"); valEditor.setValue(appCode, -1); valEditor.session.on('changeAnnotation', () => { const annot_list = valEditor.session.getAnnotations(); let flgCodeErrorFound = false; if (annot_list.length == 0) return; console.log(`[_selfCheck] Ace Editor Annotation count: ${annot_list.length}`); for (let i = 0; i < annot_list.length; i++) { let row = annot_list[i].row; let col = annot_list[i].column; let text = annot_list[i].text; let type = annot_list[i].type; let raw = annot_list[i].raw; if (!flgCodeErrorFound && type == 'error') { console.log('[_selfCheck] app code error found!!'); showError('app code error found!!'); flgCodeErrorFound = true; //editor.session.setScrollTop(row); //editor.moveCursorTo(row, col); console.log(`[_selfCheck] Annotation editor gotoLine(row: ${row + codeIn}, col: ${col})`); editor.gotoLine(row + codeIn, col, true); } if (flgCodeErrorFound) showError( `row: ${row}, col: ${col}, type: ${type}, text: ${text}`); console.log(`[_selfCheck] row: ${row}, col: ${col}, type: ${type}, text: ${text}`); } }); // changeAnnotation function end //#endregion //#region [eval app cpde validation...] try { wrapEval(`function selfCheckFunction() { ${appCode} }`); console.log('[_selfCheck] app code self check ok!!'); console.log(`[_selfCheck] selfCheck panel hide!`); $selfCheck.hide(); valEditor.session.removeAllListeners('changeAnnotation'); } catch (ex) { $container.show(); $selfCheck.show(); console.log('[_selfCheck] app code error found!!'); console.log(ex); showError('app code error found!!'); showError(ex); } //#endregion //#region [processingRuntimeException function] processingRuntimeException = () => { // Runtime Code Error Processing if (flgRuntimeCodeErrorFound) { console.log(`[_selfCheck] Runtime Exception count: ${exceptionList.length}`); $container.show(); $selfCheck.show(); for (let i = 0; i < exceptionList.length; i++) { let ex = exceptionList[i]; //msg, url, lineNo, columnNo, err showError(`msg: ${ex.msg}`); showError(`url: ${ex.url}, lineNo: ${ex.lineNo}, columnNo: ${ex.columnNo}`); let errorRow = 0; let errorCol = 0; if (url.includes(document.location.href)) { //let matchs = stackLines[i].match(/(?<=:)[0-9]*/g); // ios not supported let matchs = url.match(/:[0-9]*/g).map(s => s.substring(1)); if (matchs.length >= 2) { errorRow = Number(matchs[1]); // エラー行番号 if (matchs.length >= 3) errorCol = Number(matchs[2]); // エラー列番号 console.log(`[_selfCheck] errorRow: ${errorRow}, errorCol: ${errorCol}`); } } if (errorRow === 0 && errorCol === 0 && !!ex.err.stack) { console.log(`[_selfCheck] Stack: ${ex.err.stack}`); showError(`Stack: ${ex.err.stack}`); if (ex.err.stack.includes(document.location.href)) { // エラーが発生した行と列を取得 const stackLines = ex.err.stack.split('\n'); for (let i = 0; i < stackLines.length; i++) { if (stackLines[i].includes(document.location.href)) { //let matchs = stackLines[i].match(/(?<=:)[0-9]*/g); // ios not supported let matchs = stackLines[i].match(/:[0-9]*/g).map(s => s.substring(1)); if (matchs.length >= 2) { errorRow = Number(matchs[1]); // エラー行番号 if (matchs.length >= 3) errorCol = Number(matchs[2]); // エラー列番号 console.log(`[_selfCheck] errorRow: ${errorRow}, errorCol: ${errorCol}`); } break; } // if stackLines[i].includes end } // for stackLines end } // if ex.err.stack.includes end } // if !!err.stack end if (errorRow !== 0 || errorCol !== 0) { console.log(`[_selfCheck] errorCode: '${rawWikiPageLines[errorRow - 1]}'`) showInfo(`errorCode: '${rawWikiPageLines[errorRow - 1]}'`) const findWikiErrorRow = () => { let wikiErrorRow = -1; const getRange = (index, count) => { let range = []; for (let i = index <= Math.floor(count / 2) - 1 ? 0 : index - Math.floor(count / 2); i < (index <= Math.floor(count / 2) - 1 ? 0 : index - Math.floor(count / 2)) + count; i++) { range.push(i); } return range; }; for (let i = 0; i < wikiTextLines.length; i++) { if (wikiTextLines[i] === rawWikiPageLines[errorRow - 1]) { let chkDistLines = getRange(i, 10); let chkSrcLines = getRange(errorRow - 1, 10); console.log(`[_selfCheck] getRange(${i}) - chkDistLines: [${chkDistLines}]`); console.log(`[_selfCheck] getRange(${errorRow - 1}) - chkSrcLines: [${chkSrcLines}]`); let flgMatch = true; for (let i = 0; i < chkSrcLines.length; i++) { if (flgMatch) { flgMatch = wikiTextLines[chkDistLines[i]] === rawWikiPageLines[chkSrcLines[i]]; } else break; } if (flgMatch) { wikiErrorRow = i; break; } } } return wikiErrorRow + 1; } // function findWikiErrorRow end const wikiErrorRow = findWikiErrorRow(); console.log(`[_selfCheck] findWikiErrorRow: ${wikiErrorRow}`) if (wikiErrorRow >= 0) { console.log(`[_selfCheck] StackTrace editor gotoLine(row: ${wikiErrorRow}, col: ${errorCol})`); showInfo(`StackTrace findWikiError row: ${wikiErrorRow}, col: ${errorCol}`); editor.resize(true); // ace gotoLine not working bug fix editor.gotoLine(wikiErrorRow, errorCol, true); } break; } // if errorRow !== 0 || errorCol !== 0 end } // for exceptionList end } // if flgRuntimeCodeErrorFound end } // function processingRuntimeException end processingRuntimeException(); //#endregion //#region [saveServer] const saveServer = async (e) => { console.log('[_selfCheck] saveServer - start') showInfo('Save Server - Start'); /*if (!validateCode(editor)) { showInfo('Code error found!\nSave is canceled!!'); return; }*/ // Save Start procTime.start('_selfCheck.saveServer'); // 最新の wiki code を取得 let url = location.origin + location.pathname + '?do=edit'; let $editForm; console.log(`[_selfCheck] saveServer - ajax(${url}, GET)`); await $.ajax({ url: url, type: 'GET', dataType: 'html', }).done((htmlData, textStatus, jqXHR) => { console.log('[_selfCheck] saveServer - ajax done!'); const $html = $($.parseHTML(htmlData)); $editForm = $html.find('#dw__editform'); }).fail((jqXHR, textStatus, errorThrown) => { console.log('[_selfCheck] saveServer - ajax fail!'); console.log(errorThrown); }); const $wikiText = $editForm.find('#wiki__text'); const nowWikiText = $wikiText.text(); const wikiLines = editor.getValue().split('\n'); const matchs = nowWikiText.match(/#_wikihash>[0-9a-f]*</g); const beforeWikiHash = (!!matchs ? matchs[0].substring(matchs[0].indexOf('>') + 1, matchs[0].indexOf('<')) : ''); // Wiki hash code の付加 const WIKI_HASH_HEADER = '===== Wiki Hash ====='; let index; for (index = 0; index < wikiLines.length; index++) { if (wikiLines[wikiLines.length - index - 1] === WIKI_HASH_HEADER) { break; } } if (index !== wikiLines.length - 1) { for (let i = 0; i <= index; i++) { wikiLines.pop(); } } let wikiText = wikiLines.join('\n'); wikiLines.push(WIKI_HASH_HEADER); wikiLines.push("Don't edit!!"); wikiLines.push(`<WRAP #_wikihash>${wikiText.hashCode()}</WRAP>`); wikiText = wikiLines.join('\n'); // Form の wiki text を更新する $wikiText.text(wikiText); // Form の summary を更新する const lineCount = editor.session.getLength(); let editSummary = $saveDesc.val(); editSummary = editSummary !== '' ? `[${editSummary} - ${lineCount} lines]` : `[${lineCount} lines]`; const $summary = $editForm.find('input[name="summary"]'); if ($summary.length !== 0) { $summary.val(editSummary); } else { $editForm.append($('<input>').attr({type: 'hidden', name: 'summary', value: editSummary})); } $editForm.append($('<input>').attr({type: 'hidden', name: 'do[save]'})); // Save editor state to LocalStorage if (!!window.localStorage) { const filterHistory = (deltas) => { return deltas.filter((d) => { return d.group != "fold"; }); }; const session = editor.session; let state = {}; //state.value = session.getValue(); state.selection = session.selection.toJSON(); state.options = session.getOptions(); state.mode = session.getMode().$id; state.history = { undo: session.getUndoManager().$undoStack.map(filterHistory), redo: session.getUndoManager().$undoStack.map(filterHistory) }, state.folds = session.getAllFolds().map((fold) => { return { start: fold.start, end: fold.end, placeholder: fold.placeholder }; }); state.codeHash = wikiText.hashCode(); // WikiText のハッシュ state.scrollTop = session.getScrollTop(); state.scrollLeft = session.getScrollLeft(); console.log('[_selfCheck] Save editor state -> LocalStorage'); showInfo('Save editor state -> LocalStorage'); try { JSON.stringify(state.history); } catch (ex) { console.log('[_selfCheck] Truncate state.history !!'); showInfo('Truncate state.history!!'); state.history = {}; } localStorage['_selfCheckEditorState'] = JSON.stringify(state); } // Dokuwiki Ajax Form Post console.log(`[_selfCheck] saveServer - ajax(${url}, POST)`); await $.ajax({ url: url, type: $editForm.attr('method'), // POST data: $editForm.serialize(), }).done((htmlData, textStatus, jqXHR) => { console.log('[_selfCheck] saveServer - ajax done!'); const $html = $($.parseHTML(htmlData)); const afterWikiHash = $html.find('#_wikihash').text().trim(); // Save End ms = procTime.end('_selfCheck.saveServer'); console.log(`[_selfCheck] saveServer - WikiHash > before: ${beforeWikiHash} after: ${afterWikiHash}`); showInfo(`Save Server - WikiHash > before: ${beforeWikiHash} after: ${afterWikiHash}`); // Check save with wikiHash if (beforeWikiHash !== afterWikiHash) { console.log(`[_selfCheck] saveServer - Edit Summary: '${editSummary}'`); showInfo(`Save Server - Edit Summary: '${editSummary}'`); console.log(`[_selfCheck] saveServer - Saved wiki text! > ${lineCount} line ${ms}ms`); showInfo(`<span style="color: yellow">Saved wiki text! > ${lineCount} line ${ms}ms</span>`); } else { console.log(`[_selfCheck] saveServer - end > ${lineCount} line ${ms}ms`); showInfo('Wiki text is not modified!'); showInfo(`Save Server - End > ${lineCount} line ${ms}ms`); } }).fail((jqXHR, textStatus, errorThrown) => { console.log('[_selfCheck] saveServer - ajax fail!'); console.log(errorThrown); }); } // function saveServer end //#endregion $saveServer.click(saveServer); setTimeout(() => { console.log('[_selfCheck] Ace Editor foldAllRegion!'); editor.session.foldAllRegion(); }, 1600); })(); // async function end console.log('[_selfCheck] $() end!'); }); // jQuery $() end }); // require $ ace end }); // require $ end }); } </script> <!--[head-end]--> <!--[code-start]--> <script> { 'use strict'; //self.DEBUG = true; self.DEBUG = !!self.DEBUG; const __version__ = '0.0.5'; const wrapEval = (script) => { return (new Function(`"use strict"; return ${script};`))(); }; console.log(`[Main] script(DEBUG: ${DEBUG}) - start`); require(['DokuWiki'], (DokuWiki) => { const dokuWiki = new DokuWiki(); let loadPackages = []; //#region [Main config] const config = { editorDefault: { theme: 'ace/theme/cobalt', options: { maxLines: isMobile() ? 20: 30, enableBasicAutocompletion: true, enableSnippets: true, enableLiveAutocompletion: true, }, }, }; //#endregion /*window.tomoyanSetup = () => { alert('tomoyan!'); };*/ require(['Vue', 'ace', 'ace.language_tools'], (Vue, ace) => { Vue.component('ace'); }); procTime.start('Main.jQueryLoad'); console.log('[Main] jQuery & jQuery UI loading...'); require(['jquery', 'jquery.ui'], ($) => { console.log('[Main] Doku jQuery Merge...'); dokujQueryMerge(); let ms = procTime.end('Main.jQueryLoad'); //#region [jQuery version info] if (!!$) { console.log(`[Main] jQuery Version: ${$.fn.jquery} loaded! ${ms}ms`); console.log(`[Main] jQuery UI Version: ${$.ui.version} loaded! ${ms}ms`); } else { console.log(`[Main] jQuery not loaded! ${ms}ms`); return; } //#endregion //#region [Layout class] class Layout { constructor(elementId) { const $element = this.$element = $(`#${elementId}`); const TEMPLATE_LAYOUT = //'<header id="outer-north">' + // '<div id="header" class="header">' + // '<span id="resolution"></span>' + // '</div>' + // '<nav></nav>' + //'</header>' + `<article id="${elementId}">` +//'<article id="outer-center">' + '<section id="inner-north">' + // '<div id="menu" class="ui-widget-header"></div>' + // '<div id="toolbar" class="ui-widget-header"></div>' + '</section>' + '<section id="inner-west"></section>' + '<section id="inner-center"></section>' + '<section id="inner-east"></section>' + '<section id="inner-south"></section>' + '</article>'; //+ //'<footer id="outer-south">' + // "<p>© 2013-2019 TomoYan.NET</p>" + //'</footer>'; } } //#endregion //#region [Tabs class] class Tabs { constructor(elementId) { const $element = this.$element = $(`#${elementId}`); const TEMPLATE_TABS = `<ul id="${elementId}-tabs"></ul>`; $element.append(TEMPLATE_TABS); this.$tabs = $(`#${elementId}-tabs`); this.$element.tabs(); } // Create and append tab createTab(tabId, tabTitle='Tab', cssClass='') { const href = `#${tabId}`; const title = tabTitle; const TEMPLATE_TAB = `<li><a href="${href}">` + `<span class="ui-tab-title">${title}</span>` + '<span class="ui-icon ui-icon-close" role="presentation">Remove Tab</span>' + '</a></li>'; const TEMPLATE_TAB_PANEL = `<div id="${tabId}"${cssClass ? 'class="' + cssClass + '"' : ''}></div>`; let $tab = $(TEMPLATE_TAB); this.$tabs.append($tab); let $tabPanel = $(TEMPLATE_TAB_PANEL); this.$element.append($tabPanel); this.$element.tabs('refresh'); return $tabPanel; } // Tab active activeTab(tabId) { this.$element.tabs('option', 'active', tabId); } } //#endregion //#region [Jip - JavaScript Installs Packages] class Jip { constructor() { this.req = require; this.config = this.req.s.contexts._.config; } jip(cmd) { cmd = cmd.toLowerCase(); if (cmd === 'freeze') { return this.freeze(); } else if (cmd === 'list') { return this.list(); } else if (!!!cmd || cmd === 'help') { return `RequireJS version ${this.req.version}\n` + '\n' + 'Usage:\n' + ' jip <command > [option]\n' + '\n' + 'Commands:\n' + ' install\n' + ' freeze\n' + ' list\n' + ' help\n'; } } _getPackageVersion(packagePath) { const ver = packagePath.match(/.*([0-9\.]*)/g); return ver; } freeze() { let result = ''; const paths = this.config.paths; const keys = Object.keys(paths); keys.sort(); keys.forEach((key) => { result += `${key}==${this._getPackageVersion(paths[key])}\n`; }); return result; } list() { let resuly = ''; const paths = this.config.paths; } } //#endregion //#region [setDoubleTapEvent] self.setDoubleTapEvent = ($) => { $.event.special.dblTap = { setup: (() => { var tapStartFlag = false; return () => { $(this).click(() => { if(tapStartFlag){ $(this).trigger("dblTap"); tapStartFlag = false; } else { tapStartFlag = true; } setTimeout(() => { tapStartFlag = false; }, $.event.special.dblTap.delay ); // setTimeout }); // click }; // return function })(), // setup delay : 500, }; // dblTap }; //#endregion $(() => { console.log('[Main] $() start!'); procTime.start('Main'); if (isMobile()) { setDoubleTapEvent($); } // iframe 内の場合は処理を中断 if (window !== window.parent) return; const BASE_URL = '/_media/javascript/'; const LINK = '<link>'; let url; //#region [Main DOM build] procTime.start('Main.DOMBuild'); console.log('[Main] DOM Build start!'); const $HEAD = $('head'); const $container = $('#container'); $container.append($('<article>').attr({id: 'ideMain'})); const $ideMain = $('#ideMain'); $ideMain .hide() .append($('<section>').attr({id: 'ideHeader'})) .append($('<section>').attr({id: 'ideCenter'})) .append($('<section>').attr({id: 'ideToolbar'})) .append($('<section>').attr({id: 'terminal'})); const $ideHeader = $ideMain.find('#ideHeader'); const $ideCenter = $ideMain.find('#ideCenter'); const $ideToolBar = $ideMain.find('#ideToolbar'); const $terminal = $ideMain.find('#terminal'); const ideCenterTabs = new Tabs($ideCenter[0].id); const $editorTab = ideCenterTabs.createTab('aceEditorTabPanel', 'Ace Editor'); $editorTab.append($('<div>').attr({id: 'aceEditor'})); ideCenterTabs.createTab('aceEditor1', 'Ace Editor1'); ideCenterTabs.activeTab(0); const $aceEditor = $ideCenter.find('#aceEditor'); $ideToolBar .append($('<input>').attr({id: 'saveServer', type: 'button',value: 'Save Server'})) .append($('<span>説明:</span>')) .append($('<input>').attr({ id: 'saveDescription', type: 'text', size: isMobile() ? '30' : '50'})) .append($('<input>').attr({id: 'saveLocal', type: 'button',value: 'Save Local'})) .append($('<input>').attr({id: 'loadServer', type: 'button',value: 'Load Server'})) .append($('<input>').attr({id: 'loadLocal', type: 'button',value: 'Load Local'})) .append($('<input>').attr({id: 'runCode', type: 'button',value: 'Run Code'})) .append($('<select>').attr({id: 'editorTheme'})); const $saveServer = $('#saveServer'); const $saveDesc = $('#saveDescription'); const $saveLocal = $('#saveLocal'); const $loadServer = $('#loadServer'); const $loadLocal = $('#loadLocal'); const $runCode = $('#runCode'); const $editorTheme = $ideToolBar.find('#editorTheme'); //#endregion ms = procTime.end('Main.DOMBuild'); console.log(`[Main] DOM Build end! ${ms}ms`); //#region [setEditorTheme] const setEditorTheme = async ($select, theme) => { const doku = new DokuWiki(); const [mediaList, htmlData] = await doku.getMediaList('javascript:ace:1.4.5:src-noconflict', /theme-/); for (let i = 0; i < mediaList.length; i++) { const mediaTheme = mediaList[i].name.substring('theme-'.length).replace('.js', ''); // ambiance is not working on mobile if (isMobile() && ['ambiance'].includes(mediaTheme)) continue; // Create option const $option = $('<option>') .val(mediaTheme) .text(mediaTheme) .prop('selected', mediaTheme === theme); $select.append($option); } }; //#endregion //#region [MemoryInfo class] class MemoryInfo { } const getMemoryInfo = () => { if (!!!window.performance.memory) { $ideHeader.hide(); return; } $ideHeader.append($('<div>').attr({ id: 'memoryInfo', style: "font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', " + "'Consolas', 'source-code-pro', monospace; " + 'color: white;' })); const $element = $ideHeader.find('#memoryInfo'); $element.append('<p><b>UsedHeap:</b> <span id="usedHeap"></span></p>'); $element.append('<p><b>TotalHeap:</b> <span id="totalHeap"></span></p>'); setInterval(() => { const memInfo = performance.memory; $('#usedHeap').text( String(memInfo.usedJSHeapSize).formatComma() + 'Byte(' + String(Math.floor(memInfo.usedJSHeapSize / 1024)).formatComma() + 'MB)'); $('#totalHeap').text( String(memInfo.totalJSHeapSize).formatComma() + 'Byte(' + String(Math.floor(memInfo.totalJSHeapSize / 1024)).formatComma() + 'MB)'); }, 1000); }; getMemoryInfo(); //#endregion const loadJavascript = () => { procTime.start('loadJavascript'); // RequireJS define() と jQuery Terminal define() の衝突回避策 try { require(['require']); } catch (ex) { } // RequireJS Dummy load //if (!!jQuery.terminal) delete jQuery.terminal; //if (!!$.terminal) delete $.terminal; const loadPackages = ['jquery', 'ace', 'ace.language_tools', 'jquery.terminal']; //if (isMobile()) loadPackages.push('jquery.mobile'); require(loadPackages, function($, ace) { if (!!!self.termcss_loaded) { self.termcss_loaded = true; //----- Load jQuery Terminal -----// url = BASE_URL + 'jquery/terminal/jquery.terminal-2.7.1' + (!DEBUG ? '.min' : '') + '.css?cache=recache'; $HEAD.append($(LINK).attr({href: url, rel: 'stylesheet'})); } //self.term = $('#terminal'); //term.replaceWith('<div id="terminal"></div>'); //self.term = $('#terminal'); if (!!self.term && !!term.destroy) term.destroy(); self.term = $('#' + $terminal[0].id); term.terminal((command, term) => { //#region [createAndViewAceEditor] const createAndViewAceEditor = (value) => { if ($('#aceViewSrc').length === 0) { term.echo('create ace src view div...'); $container.append($('<div>').attr({id: 'aceViewSrc'})); } const $aceViewSrc = $('#aceViewSrc'); term.echo('loading ace editor...'); let viewSrcEditor = ace.edit($aceViewSrc[0].id); viewSrcEditor.setTheme('ace/theme/chrome'); viewSrcEditor.session.setMode('ace/mode/html'); term.echo('set ace editor text...'); viewSrcEditor.setValue(value, -1); viewSrcEditor.setOptions({maxLines: 35}); return viewSrcEditor; }; //#endregion //#region [ajaxGet] const ajaxGet = (url, dataType, doneFunc) => { url = !!url ? url : location.href; $.ajax({ url: url, type: 'GET', dataType: dataType, }).done((data, textStatus, jqXHR) => { term.echo('ajax done!'); console.log('[Main] ajax done!'); doneFunc(data, textStatus, jqXHR); }).fail((jqXHR, textStatus, errorThrown) => { term.echo('ajax fail!'); console.log('[Main] ajax fail!'); console.log(`[Main] ${errorThrown}`); }); }; //#endregion //#region [cmd help] if (command === 'help') { term.echo('load py|python - Python Interpreter'); term.echo('cls|clear - Clear Terminal'); term.echo('code valid - editor code validation'); if (isMobile()) term.echo('debug - mobile console log to terminal'); term.echo('throw rtex - throw RunTimeException'); term.echo('view src - view-source: ide.html to terminal'); term.echo('view src ace - view-source: ide.html to Ace Editor'); term.echo('chk selfchk - selfCheck script try eval test'); term.echo('self chk - Show selfCheck panel'); term.echo('run cmd ace - run command result to Ace Editor'); term.echo('scripts - DOM scripts listup to Ace Editor'); term.echo('dir cookie - dir cookie list to terminal'); term.echo('dir header - dir http response header to terminal'); term.echo('cls ls - Clear localStorage'); term.echo('jip <cmd> [opt] - JavaScript Installs Packages'); term.echo('revs|revisions - DokuWiki revisions list'); } //#endregion //#region [cmd cls] else if (command === 'cls' || command == 'clear') { term.clear(); } //#endregion //#region [cmd py] else if (command === 'load py' || command === 'load python') { term.echo('Loading Pyodide terminal...'); loadPython(); } //#endregion //#region [cmd code valid] else if (command === 'code valid') { if (self.validateCode(editor, (ex) => { term.error('Code is error found!!'); term.error(String(ex)); })) { term.echo('Code is no error!'); } } //#endregion else if (command === 'opt chk') { const checkAllowNativeSyntax = V8Native.isAllowNativeSyntax((msgs) => { msgs.forEach((msg) => { console.log(msg); term.echo(msg); }); }); console.log(`checkAllowNativeSyntax: ${checkAllowNativeSyntax}`); term.echo(`checkAllowNativeSyntax: ${checkAllowNativeSyntax}`); let func = new Function(editor.getValue()); func = String.prototype.hashCode; return V8Native.printOptimizationStatus(func); } //#region [cmd debug] else if (command === 'debug') { if((/android|iphone/gi).test(navigator.appVersion)) { self.console = { log: (msg) => { term.echo(msg); }, show: (msg) => { term.echo(msg); }, warn: (msg) => { term.echo(msg); }, dir: (obj) => { term.echo(dir(obj)); }, }; } window.onerror = function(msg, url, line) { console.log("ERROR: '" + msg + "' at '" + "', line " + line); }; window.addEventListener('touchstart', (e) => { if(e.touches.length === 3) { console.show(); } }); } //#endregion //#region [cmd throw rtex] else if (command === 'throw rtex') { class RuntimeException extends Error { constructor(message, fileName, lineNumber) { message = !!message ? message : 'RuntimeException'; super(message, fileName, lineNumber); } } throw new RuntimeException(); } //#endregion //#region [cmd view src] else if (command === 'view src') { ajaxGet(null, 'html', (htmlData) => { term.echo(htmlData); }); } //#endregion //#region [cmd view src ace] else if (command === 'view src ace') { term.echo('ajax loading src...'); ajaxGet(null, 'html', (htmlData) => { createAndViewAceEditor(htmlData); term.echo('view src ace - end'); }); } //#endregion //#region [cmd chk selfchk] else if (command === 'chk selfchk') { for (let i = 0; i < $('script').length; i++) { let script = $('script')[i].innerHTML; if (script.includes('_selfCheck')) { term.echo('found _selfCheck script!'); try { term.echo('Try test eval script.'); let result = wrapEval(script); term.echo('Script test ok!'); } catch (ex) { term.echo('error!!'); term.echo(ex); } break; } } } //#endregion //#region [cmd self chk] else if (command === 'self chk') { const $selfCheck = $('#_selfCheck'); console.log(`[Main] selfCheck panel show!`); $selfCheck.show(); } //#endregion //#region [cmd run cmd ace] else if (command.startsWith('run cmd ace')) { const cmd = command.substring('run cmd ace'.length).trim(); console.log(`[Main] run cmd ace - cmd: '${cmd}'`); createAndViewAceEditor(wrapEval(cmd)); } //#endregion //#region [cmd scripts] else if (command === 'scripts') { let scripts = ''; for (let i = 0; i < $('script').length; i++) { scripts += `script[${i}]: ${$('script')[i].innerHTML}\n`; } createAndViewAceEditor(scripts); } //#endregion //#region [cmd dir cookie] else if (command === 'dir cookie') { let cookies = document.cookie.split(';'); if (cookies.length !== 0) { term.echo('----- cookie list -----'); cookies.forEach((cookie) => { let [key, value] = cookie.split('='); term.echo(`key: %{key}, value: ${value}`); }); } } //#endregion //#region [cmd dir header] else if (command === 'dir header') { ajaxGet(null, 'html', (data, textStatus, jqXHR) => { term.echo(jqXHR.getAllResponseHeaders()); }); } //#endregion //#region [cmd media list] else if (command.startsWith('media list')) { //const ns = command.substring('media list'.length).trim(); const ns = 'javascript:ace:1.4.5:src-noconflict'; (async () => { const [mediaList, htmlData] = await dokuWiki.getMediaList(ns); console.log(mediaList); for (let i = 0; i < mediaList.length; i++) { term.echo(`name: ${mediaList[i].name}, size: ${mediaList[i].size}`); } self.result = mediaList; self.htmlData = htmlData; self.ViewAceEditor = createAndViewAceEditor(htmlData); })(); //return mediaList; } //#endregion //#region [cmd revs] else if (command.startsWith('revs') || command.startsWith('revisions')) { const grep = command.startsWith('revs') ? command.substring(5).trim() : command.startsWith('revisions') ? command.substring(10).trim() : ''; (async () => { term.echo(await dokuWiki.getWikiRevisions(grep)); })(); } //#endregion //#region [cmd cls ls] else if (command === 'cls ls') { if (!!window.localStorage) { localStorage.clear(); } } //#endregion //#region [cmd jip] else if (command.startsWith('jip')) { const jip = new Jip() const cmd = command.substring('jip'.length).trim(); term.echo(jip.jip(cmd)); } //#endregion //#region [cmd load] else if (command.startsWith('load')) { const url = command.substring(4).trim(); const newScript = document.createElement('script'); newScript.setAttribute('src', url); document.head.appendChild(newScript); } //#endregion //#region [cmd ace trap] else if (command === 'ace trap') { term.echo('Ace Editor All Events Trap Start!!'); editor.on('blur', function() { console.log('editor.onBlur'); term.echo('editor.onBlur'); console.dir(arguments); }); editor.on('change', function() { console.log('editor.onChange'); term.echo('editor.onChange'); console.dir(arguments); }); editor.on('changeSelectionStyle', function() { console.log('editor.onChange'); term.echo('editor.onChange'); console.dir(arguments); }); editor.on('changeSession', function() { console.log('editor.onChangeSession'); term.echo('editor.onChangeSession'); console.dir(arguments); }); editor.on('copy', function() { console.log('editor.onCopy'); term.echo('editor.onCopy'); console.dir(arguments); }); editor.on('focus', function() { console.log('editor.onFocus'); term.echo('editor.onFocus'); console.dir(arguments); }); editor.on('paste', function() { console.log('editor.onPaste'); term.echo('editor.onPaste'); console.dir(arguments); }); editor.session.on('blur', function() { console.log('session.onBlur'); term.echo('session.onBlur'); console.dir(arguments); }); editor.session.on('changeAnnotation', function() { console.log('session.onChangeAnnotation'); term.echo('session.onChangeAnnotation'); console.dir(arguments); }); editor.session.on('changBackMarker', function() { console.log('session.onBlur'); term.echo('session.onChangeBackMarker'); console.dir(arguments); }); editor.session.on('changeBreakpoint', function() { console.log('session.onChangeBreakpoint'); term.echo('session.onChangeBreakpoint'); console.dir(arguments); }); editor.session.on('changeFold', function() { console.log('session.onChangeFold'); term.echo('session.onChangeFold'); console.dir(arguments); }); editor.session.on('changeFrontMarker', function() { console.log('session.onChangeFrontMarker'); term.echo('session.onChangeFrontMarker'); console.dir(arguments); }); editor.session.on('changeMode', function() { console.log('session.onChangeMode'); term.echo('session.onChangeMode'); console.dir(arguments); }); editor.session.on('changeWrapLimit', function() { console.log('session.onChangeWrapLimit'); term.echo('session.onChangeWrapLimit'); console.dir(arguments); }); editor.session.on('changeWrapMode', function() { console.log('session.onChangeWrapMode'); term.echo('session.onChangeWrapMode'); console.dir(arguments); }); editor.session.on('commandKey', function() { console.log('session.onCommandKey'); term.echo('session.onCommandKey'); console.dir(arguments); }); editor.session.on('compositionEnd', function() { console.log('session.onCompositionEnd'); term.echo('session.onCompositionEnd'); console.dir(arguments); }); editor.session.on('compositionStart', function() { console.log('session.onCompositionStart'); term.echo('session.onCompositionStart'); console.dir(arguments); }); editor.session.on('compositionUpdate', function() { console.log('session.onCompositionUpdate'); term.echo('session.onCompositionUpdate'); console.dir(arguments); }); editor.session.on('copy', function() { console.log('session.onCopy'); term.echo('session.onCopy'); console.dir(arguments); }); editor.session.on('cursorChange', function() { console.log('session.onCursorChange'); term.echo('session.onCursorChange'); console.dir(arguments); }); editor.session.on('cut', function() { console.log('session.onCut'); term.echo('session.onCut'); console.dir(arguments); }); editor.session.on('documentChange', function() { console.log('session.onDocumentChange'); term.echo('session.onDocumentChange'); console.dir(arguments); }); editor.session.on('focus', function() { console.log('session.onFocus'); term.echo('session.onFocus'); console.dir(arguments); }); editor.session.on('paste', function() { console.log('session.onPaste(String text)'); term.echo('session.onPaste(String text)'); console.dir(arguments); }); editor.session.on('scrollLeftChange', function() { console.log('session.onScrollLeftChange'); term.echo('session.onScrollLeftChange'); console.dir(arguments); }); editor.session.on('scrollTopChange', function() { console.log('session.onScrollTopChange'); term.echo('session.onScrollTopChange'); console.dir(arguments); }); editor.session.on('selectionChange', function() { console.log('session.onSelectionChange'); term.echo('session.onSelectionChange'); console.dir(arguments); }); editor.session.on('textInput', function() { console.log('session.onTextInput'); term.echo('session.onTextInput'); console.dir(arguments); }); editor.session.on('tokenizerUpdate', function() { console.log('session.onTokenizerUpdate'); term.echo('session.onTokenizerUpdate'); console.dir(arguments); }); } //#endregion //#region [cmd eval] else if (command !== '') { try { let result = wrapEval(command); if (result !== undefined) { term.echo(String(result)); } } catch(ex) { term.error(String(ex)); } } //#endregion }, //#region [terminal - opsions] { greetings: 'JavaScript Interpreter Version ' + $.terminal.version + (!!navigator.hardwareConcurrency ? ' (CPU Cores: ' + navigator.hardwareConcurrency + ' core)' : '') + '\n' + navigator.userAgent, name: 'interpreter', height: 200, prompt: 'js > ', onInit: (term) => { console.log('[Main] terminal on init!!'); const ms = procTime.end('Main.DOMBuild'); console.log(`[Main] script(DEBUG: ${DEBUG}) load end! ${ms}ms`); term.echo(`[Main] script(DEBUG: ${DEBUG}) load end! ${ms}ms`); setTimeout(() => { console.log('[Main] setTimeout!!'); const _$loadingPanel = $('#loadingPanel'); const _$ideMain = $('#ideMain'); if (_$loadingPanel.length !== 0) { _$loadingPanel.fadeOut('slow', () => { _$ideMain.fadeIn('slow'); }); } else { _$ideMain.fadeIn('slow'); } term.focus(false); console.log('[Main] Ace Editor foldAllRegion!'); editor.session.foldAllRegion(); editor.focus(); }, 400); }, //onBlur: () => { return false; }, }); //#endregion //if (isMobile()) { $('<input>').attr({id: 'up', type: 'button', value: '↑'}).appendTo($ideToolBar); $('<input>').attr({id: 'down', type: 'button', value: '↓'}).appendTo($ideToolBar); $('#up').click(() => { term.focus(); term.invoke_key('CTRL+P'); }); $('#down').click(() => { term.focus(); term.invoke_key('CTRL+N'); }); //} //#region [Set Exception Callback] self.trapExceptionSetting = { trap: true, callback: (exceptionList) => { for (let i = 0; i < exceptionList.length; i++) { let ex = exceptionList[i]; term.error(ex.msg); if (!!ex.err) { if (!!ex.err.stack) { term.echo(ex.err.stack); } } } }, }; //#endregion }); // require end ms = procTime.end('loadJavascript'); console.log(`[Main] Load Javascript process time: ${ms}ms`); }; // loadJavascript() //#region [loadPython] const loadPython = () => { procTime.start('loadPython'); // RequireJS define() と jQuery Terminal define() の衝突回避策 try { require(['require']); } catch (ex) { } // RequireJS Dummy load //if (!!jQuery.terminal) delete jQuery.terminal; //if (!!$.terminal) delete $.terminal; self.languagePluginUrl = '/_media/python/pyodide/'; const loadPackages = ['jquery', 'pyodide', 'jquery.terminal']; //if (isMobile()) loadPackages.push('jquery.mobile'); require(loadPackages, function($, pyodide) { self.startTime = new Date(); if (!!!self.pyodide) { /*self.languagePluginUrl = '/_media/python/pyodide/'; url = self.languagePluginUrl + 'pyodide_dev.js'; $HEAD.append($(SCRIPT).attr({src: url}));*/ $HEAD.append($(LINK).attr({ href: self.languagePluginUrl + 'renderedhtml.css?cache=recache', rel: 'stylesheet'})); } languagePluginLoader.then(() => { const handleResult = (result) => { if (result) { term.set_prompt('[[;gray;]... ]'); } else { term.set_prompt('[[;red;]>>> ]'); var stderr = pyodide.runPython("sys.stderr.getvalue()").trim(); if (stderr) { term.echo(`[[;red;]${stderr}]`); } else { var stdout = pyodide.runPython("sys.stdout.getvalue()"); if (stdout) { term.echo(stdout.trim()); } } } }; const pushCode = (command) => { if (command == 'help') { term.echo('load js|javascript - JavaScript Interpreter'); term.echo('cls|clear - Clear Terminal'); } else if (command === 'cls' || command == 'clear') { term.clear(); } else if (command === 'load javascript' || command == 'load js') { loadJavascript(); } else { handleResult(c.push(command)); } }; self.endTime = new Date(); var ms = self.endTime.getTime() - self.startTime.getTime(); //self.term = $('#terminal'); if (!!self.term && !!term.destroy) term.destroy(); self.term = $('#' + $terminal[0].id); term.terminal( pushCode, { greetings: 'Interpreter Loading time: ' + ms + 'ms\n' + 'Welcome to the Pyodide terminal emulator 🐍\n' + navigator.userAgent, name: 'interpreter', height: 200, prompt: "[[;red;]>>> ]", } ); //try { pyodide.runPython(` import io, code, sys from js import term, pyodide class Console(code.InteractiveConsole): def runcode(self, code): sys.stdout = io.StringIO() sys.stderr = io.StringIO() term.runPython("\\n".join(self.buffer)) _c = Console(locals=globals()) `); var c = pyodide.pyimport('_c'); /*} catch (ex) { term.error(String(ex)); }*/ term.runPython = (code) => { pyodide.runPythonAsync(code).then( term.handlePythonResult, term.handlePythonError ); }; term.handlePythonResult = (result) => { if (result === undefined) { return; } else if (result._repr_html_ !== undefined) { term.echo(result._repr_html_, {raw: true}); } else { term.echo(result.toString()); } }; term.handlePythonError = (result) => { term.error(result.toString()); }; }); }); ms = procTime.end('loadPython'); console.log(`[Main] Load Python process time: ${ms}ms`); }; // loadPython() //#endregion loadPackages = ['jquery', 'ace', 'ace.language_tools']; //if (isMobile()) loadPackages.push('jquery.mobile'); require(loadPackages, function($, ace) { //#region [getServerCode] const getServerCode = () => { let app_code = $('#app').html(); let lines = app_code.split('\n'); let buf = []; let startLineNo = 0, endLineNo = 0; let fastLineIndent = 0; for (let i = 0; i < lines.length; i++) { if (lines[i].trim() == DokuWiki.CODE_START) { startLineNo = i; } else if(lines[i].trim() == DokuWiki.CODE_END) { endLineNo = i; } if (i == startLineNo + 2) { fastLineIndent = lines[i].match(/^ */)[0].length; } if (startLineNo > 0 && i >= startLineNo + 2 && endLineNo === 0) { buf.push(lines[i].substring(fastLineIndent)); } } buf.pop(); app_code = buf.join('\n'); return [app_code, fastLineIndent]; }; //#endregion loadJavascript(); ace.require("ace/ext/language_tools"); ace.require('ace/multi_select'); ace.require("ace/ext/emmet"); //#region [Ace Editor fold all region] isCommentFold = (line) => { var isAFold = false; if (/^\s*(\/\*|\/\/)#?region\b/.test(line)) { isAFold = true; } if (/\/\/(.*)\{/.test(line)) { isAFold = true; } return isAFold; } ace.EditSession.prototype.foldAllRegion = function(startRow, endRow, depth) { if (depth == undefined) { //depth = 100000; // JSON.stringify doesn't hanle Infinity depth = 1; } var foldWidgets = this.foldWidgets; console.log(`[Main] foldWidgets length: ${!!foldWidgets ? foldWidgets.length : 0}`); console.dir(foldWidgets); if (!foldWidgets) return; // mode doesn't support folding endRow = endRow || this.getLength(); startRow = startRow || 0; for (var row = startRow; row < endRow; row++) { if (foldWidgets[row] == null) foldWidgets[row] = this.getFoldWidget(row); if (foldWidgets[row] != "start") continue; if (!isCommentFold(this.getLine(row))) continue var range = this.getFoldWidgetRange(row); // sometimes range can be incompatible with existing fold // TODO change addFold to return null istead of throwing if (range && range.isMultiLine() && range.end.row <= endRow && range.start.row >= startRow) { row = range.end.row; try { // addFold can change the range var fold = this.addFold("...", range); if (fold) fold.collapseChildren = depth; } catch(ex) {} } } }; //#endregion self.editor = ace.edit($aceEditor[0].id); editor.setTheme(config.editorDefault.theme); editor.session.setOptions({ mode: 'ace/mode/javascript', tabSize: 2, useSoftTabs: true }); //#region [_getFileName - pathよりファイル名を取得] const _getFileName = (path) => { // pathよりファイル名を取得 let tmp = path.split('/'); return tmp[tmp.length - 1]; }; //#endregion //#region [_getEditorIdByPath - パスより editorId を取得] const _getEditorIdByPath = (path) => { // pathよりファイル名を取得 let name = _getFileName(path); // ファイル名の英数字_-以外を除去(require.js -> requirejs) name = name.replace(/[^a-z0-9_\-]/gi, ''); // 名前の先頭8文字を取得 name = name.substring(0, 8); // pathのハッシュコード + _ + ファイル名(8文字) return 'editor_' + path.hashCode() + '_' + name; }; //#endregion self.editorList = {}; self.editorList[_getEditorIdByPath(document.location.origin + document.location.pathname)] = editor; let [app_code, fastLineIndent] = getServerCode(); // Run Code が実行された場合は backupEditorCode からリロードする if (!!self.backupEditorCode) { app_code = self.backupEditorCode; delete self.backupEditorCode; } //#region [setEditorText - エディタのテキストを設定] const setEditorText = (editor, text) => { if (!!editor && !!text) { console.log('[Main] setEditorText(setValue) - editor:', editor, 'text.length:', text.length); editor.setValue(text, -1); //editor.gotoLine(1); } }; //#endregion //#region [loadEditor - エディタをロード] const loadEditor = (editorId, editorTitle, options, localLoad, setFocus) => { options = !!options ? options : {}; options.path = !!options.path ? options.path : document.location.origin + document.location.pathname; editorId = _getEditorIdByPath(options.path); editorTitle = !!editorTitle ? editorTitle : _getFileName(options.path); localLoad = !!!localLoad; setFocus = !!setFocus; console.log(`[Main] loadEditor - editorId: {${editorId}, editorTitle: ${editorTitle}, ` + `options: ${dir(options)}, localLoad: ${localLoad}}`); // エディタを生成 //let editor = this.createEditor(editorId, editorTitle, this._defaultTheme, options.editorMode); //console.log('[Main] _loadEditor - editor:', editor); let editor = self.editorList[editorId]; options.scrollTop = !!options.scrollTop ? options.scrollTop : 0; options.curPos = !!options.curPos ? options.curPos : [0, 0]; options.folds = !!options.folds ? options.folds : []; options.codeHash = !!options.codeHash ? options.codeHash : ''; options.theme = !!options.theme ? options.theme : config.editorDefault.theme; options.sessionOptions = !!options.sessionOptions ? options.sessionOptions : { mode: 'ace/mode/javascript', tabSize: 2, useSoftTabs: true }; options.size = !!options.size ? options.size : 0; options.mtime = !!options.mtime ? options.mtime : null; // エディタ情報設定 let ideInfo = editor.ideInfo = { editorId: editorId, // エディタIDを保持 editorTitle: editorTitle, // タイトルを保持 scrollTop: options.scrollTop, // 縦スクロール位置を保持 curPos: options.curPos, // エディタカーソル位置を保持 folds: options.folds, // 折り畳み状態を保持 path: options.path, // パスを保持 theme: options.theme, // テーマを保持 sessionOptions: options.sessionOptions, // エディタセッションオプションを保持 size: options.size, mtime: options.mtime, is_text_changed: false, is_save_local: false, }; // テキストを設定 //if (!!options.text) setEditorText(editor, options.text); // エディタの縦スクロール位置を設定 editor.session.setScrollTop(ideInfo.scrollTop); // カーソル位置を復元 if (!!ideInfo.curPos) { var curPos = ideInfo.curPos; editor.moveCursorTo(curPos[0], curPos[1]); } // 折り畳み状態を復元 if (options.codeHash === editor.getValue().hashCode()) { try { ideInfo.folds.forEach((fold) => { fold.collapseChildren = 1; editor.session.addFold(fold.placeholder, ace.Range.fromPoints(fold.start, fold.end)); }); } catch(ex) { console.log('[Main] loadEditor - Ace Editor Set fold fail!!'); console.log(ex); } } // エディタテーマを設定 editor.setTheme(ideInfo.theme); // エディタオプションを設定 editor.session.setOptions(ideInfo.sessionOptions); editor.setOptions(config.editorDefault.options); // ローカルからのロード以外の場合 if (!localLoad) { // ローカルストレージにエディタ内容を保存 //_compressSaveEditorToLocalStorage(editor); } if (setFocus) editor.focus(); // エディタをリストに保持 self.editorList[editorId] = editor; }; //#endregion //#region [isAvailableLocalStorage - LocalStorage が利用可能かどうかを取得] const isAvailableLocalStorage = () => { var isAvailable = !!window.localStorage; console.log(`[Main] isAvailableLocalStorage - isAvailable: ${isAvailable}`); return isAvailable; }; //#endregion //#region [_isEditorId - editorId かどうかをチェック] const _isEditorId = (editorId) => { return editorId.startsWith('editor_'); }; //#endregion //#region [saveEditorState - ローカルストレージにエディタ状態を保存] const saveEditorState = () => { console.log('[Main] saveEditorState'); if (!isAvailableLocalStorage()) return; // editorStateList を生成 let editorStateList = { length: 0 }; for (let editorId in self.editorList) { let editor = self.editorList[editorId]; if (!!editor.ideInfo) { let info = editor.ideInfo; // スクロールトップ取得 let scrollTop = editor.session.getScrollTop(); // カーソル位置取得 let curPos = editor.getCursorPosition(); // テーマ取得 let theme = editor.getTheme(); // エディタ状態を格納 editorStateList[editorId] = { editorId: editorId, // エディタId editorTitle: info.editorTitle, // エディタタイトル scrollTop: scrollTop, // 縦スクロール位置 curPos: [curPos.row, curPos.column], // カーソル位置 folds: editor.session.getAllFolds().map((fold) => { // 折り畳み状態 return { start: fold.start, end: fold.end, placeholder: fold.placeholder }; }), codeHash: editor.getValue().hashCode(), // エディタコードのハッシュ theme: theme, // テーマ path: info.path, // ファイルパス sessionOptions: info.sessionOptions, // エディタセッションオプション size: info.size, // サイズ(byte) mtime: info.mtime, // 更新日時 }; editorStateList.length++; } } // editorId のデータで editorStateList に存在しないデータを削除 for (let key in localStorage) { if (_isEditorId(key) && !(key in editorStateList)) { delete localStorage[key]; } } // ローカルストレージに editorStateList を保存 localStorage.editorStateList = JSON.stringify(editorStateList); }; //#endregion //#region [loadEditorState - ローカルストレージよりエディタ状態をロード] const loadEditorState = () => { console.log('[Main] loadEditorState'); if (!isAvailableLocalStorage()) return; if (!localStorage.editorStateList) return; let editorStateList = JSON.parse(localStorage.editorStateList); if (!!editorStateList && editorStateList.length !== 0) { let idx = 0, len = editorStateList.length; for (var key in editorStateList) { if (!_isEditorId(key)) continue; let editorId = key; idx++; let editorState = editorStateList[editorId]; console.log('[Main] loadEditorState - editorTitle:', editorState.editorTitle); loadEditor(editorId, editorState.editorTitle, { scrollTop: editorState.scrollTop, curPos: editorState.curPos, folds: editorState.folds, codeHash: editorState.codeHash, path: editorState.path, theme: editorState.theme, sessionOptions: editorState.sessionOptions, size: editorState.size, mtime: editorState.mtime, }); } } }; //#endregion setEditorText(editor, app_code); loadEditor(undefined, undefined, undefined, undefined, true); loadEditorState(); setEditorTheme($editorTheme, editor.getTheme().replace('ace/theme/', '')); $editorTheme.change((e) => { editor.setTheme('ace/theme/' + $editorTheme.val()); saveEditorState(); }); //#region [validateCode] const validateCode = (editor, error_func) => { let anot_list = editor.getSession().getAnnotations(); let no_error = false; for (let i = 0; i < anot_list.length; i++) { let anot = anot_list[i]; /*term.echo('row: ' + anot.row + 'column: ' + anot.column + '\n' + 'text: ' + anot.text + '\n' + 'type: ' + anot.type + '\n' + 'raw: ' + anot.raw);*/ } try { let run_code = 'function dummy() {\n' + editor.getValue() + '\n' + '}'; wrapEval(run_code); console.log('Validate Code - Code validate is no error.'); if (!!term) term.echo('Validate Code - Code validate is no error.'); no_error = true; } catch (ex) { if (!!error_func) error_func(ex); if (!!term) term.echo(ex); console.log(`[Main] validateCode: ${ex}`); } return no_error; }; self.validateCode = validateCode; //#endregion //#region [saveServer] const saveServer = async (e) => { console.log('[Main] saveServer - start'); procTime.start('Main.saveServer'); term.echo('Save Server - Save editor state.'); saveEditorState(); if (!validateCode(editor)) { console.log('[Main] saveServer - Code error found!'); console.log('[Main] saveServer - Save is canceled!!'); term.echo('Save Server - Code error found!'); term.echo('Save Server - Save is canceled!!'); return; } // 最新の wiki code を取得 url = location.pathname + '?do=edit'; let $editForm; let $wikiText; let beforeWikiHash; let afterWikiHash; console.log(`[Main] saveServer - ajax(${url}, GET)`); procTime.start('Main.saveServer-ajaxGET'); await $.ajax({ url: url, type: 'GET', dataType: 'html', }).done((htmlData) => { ms = procTime.end('Main.saveServer-ajaxGET'); console.log(`[Main] saveServer - ajax done! ${ms}ms`); let $html = $($.parseHTML(htmlData)); $editForm = $html.find('#dw__editform'); $editForm.attr('action', url); $wikiText = $editForm.find('#wiki__text'); }).fail((jqXHR, textStatus, errorThrown) => { console.log('[Main] saveServer - ajax fail!!'); console.log(errorThrown); }); let nowWikiText = $wikiText.text(); let editorCode = editor.getValue(); let indentStr = ' '.repeat(fastLineIndent); let srvLines = nowWikiText.split('\n'); let edtLines = editorCode.split('\n'); let wikiLines = []; let startLineNo = 0, endLineNo = 0; let flgFirst = true; const matchs = nowWikiText.match(/#_wikihash>[0-9a-f]*</g); beforeWikiHash = !!matchs ? matchs[0].substring(matchs[0].indexOf('>') + 1, matchs[0].indexOf('<')) : ''; for (let i = 0; i < srvLines.length; i++) { if (srvLines[i].trim() == DokuWiki.CODE_START) { startLineNo = i; } else if(srvLines[i].trim() == DokuWiki.CODE_END) { endLineNo = i; } if (startLineNo === 0 || endLineNo !== 0 && i > endLineNo) { wikiLines.push(srvLines[i]); } else if (flgFirst) { flgFirst = false; wikiLines.push(DokuWiki.CODE_START); wikiLines.push(indentStr + '<' + 'script>'); for (let j = 0; j < edtLines.length; j++) { wikiLines.push(indentStr + edtLines[j]); } wikiLines.push(indentStr + '<' + '/script>'); wikiLines.push(DokuWiki.CODE_END); } } // for end // Wiki hash code の付加 const WIKI_HASH_HEADER = '===== Wiki Hash ====='; if (wikiLines[wikiLines.length - 3] === WIKI_HASH_HEADER) { wikiLines.pop(); wikiLines.pop(); wikiLines.pop(); } let wikiText = wikiLines.join('\n'); wikiLines.push(WIKI_HASH_HEADER); wikiLines.push("Don't edit!!"); wikiLines.push(`<WRAP #_wikihash>${wikiText.hashCode()}</WRAP>`); wikiText = wikiLines.join('\n'); // Form の wiki text を更新する $wikiText.text(wikiText); // Form の summary を更新する const lineCount = wikiLines.length; let editSummary = $saveDesc.val(); editSummary = editSummary !== '' ? `[${editSummary} - ${lineCount} lines]` : `[${lineCount} lines]`; const $summary = $editForm.find('input[name="summary"]'); if ($summary.length !== 0) { $summary.val(editSummary); } else { $editForm.append($('<input>').attr({type: 'hidden', name: 'summary', value: editSummary})); } $editForm.append($('<input>').attr({type: 'hidden', name: 'do[save]'})); // Dokuwiki Ajax Form Post url = $editForm.attr('action'); console.log(`[Main] saveServer - ajax(${url}, POST)`); procTime.start('Main.saveServer-ajaxPOST'); await $.ajax({ url: url, type: $editForm.attr('method'), // POST data: $editForm.serialize(), }).done((htmlData) => { let ms = procTime.end('Main.saveServer-ajaxPOST'); console.log(`[Main] saveServer - ajax done! ${ms}ms`); const $html = $($.parseHTML(htmlData)); afterWikiHash = $html.find('#_wikihash').text().trim(); console.log(`[Main] saveServer - beforeWikiHash: ${beforeWikiHash}`) console.log(`[Main] saveServer - afterWikiHash: ${afterWikiHash}`) // Save End ms = procTime.end('Main.saveServer'); // Check save with wikiHash if (beforeWikiHash !== afterWikiHash) { console.log(`[Main] saveServer - Edit Summary: '${editSummary}'`); term.echo(`Save Server - Edit Summary: '${editSummary}'`); console.log(`[Main] saveServer - Saved editor code! ${lineCount} lines ${ms}ms`); term.echo(`[[;yellow;]Save Server - Saved editor code! ${lineCount} lines ${ms}ms]`); } else { console.log(`[Main] saveServer - end ${lineCount} line ${ms}ms`); term.echo('Save Server - Code is not modified!'); term.echo(`Save Server - End ${lineCount} line ${ms}ms`); } }).fail((jqXHR, textStatus, errorThrown) => { console.log('[Main] saveServer - ajax fail!!'); console.log(errorThrown); }); }; //#endregion //#region [saveLocal] const saveLocal = (e) => { alert('saveLocal:' + e); }; //#endregion //#region [loadServer] const loadServer = (e) => { alert('loadServer:' + e); }; //#endregion //#region [loadLocal] const loadLocal = (e) => { alert('loadLocal:' + e); }; //#endregion //#region [runCode] const runCode = (e) => { if (!validateCode(editor)) { term.echo('Code error found!\nRun code is canceled!!'); return; } let editorCode = editor.getValue(); // editor のリロードに備えてコードを退避する self.backupEditorCode = editorCode; // コード実行 wrapEval(`function __dummy_function__() { ${editorCode} }; __dummy_function__();`); }; //#endregion $saveServer.click(saveServer); $saveLocal.click(saveLocal); $loadServer.click(loadServer); $loadLocal.click(loadLocal); $runCode.click(runCode); }); console.log('[Main] $() end!'); }); // jQuery $() end // jQuery 3 から $() の 外で on('load') する必要がある $(window).on('load', () => { console.log('[Main] window.onLoad!!'); //setTimeout(() => { //console.log('[Main] setTimeout!!'); //}, 1000); }); //#region [dir] self.dir = (object, sort, newLine, recursive, indent, grep) => { sort = !!sort; newLine = !!newLine; recursive = !!recursive ? recursive : 0; indent = !!indent ? indent : ''; grep = !!grep ? grep : ''; const dumpObj = (key, value) => { let result = ''; //console.log('key: %s, val: %s', key, new String(value)); if (typeof value === 'function') { result += (!!key ? key : '') + '() as ' + typeof value; } else if (['string', 'number', 'boolean', 'undefined'].includes(typeof value)) { result += (!!key ? key + ': ' : '') + '"' + value + '" as ' + typeof value; } else if (Array.isArray(value)) { result += (!!key ? key + ': ' : '') + '[' + value.join(', ') + ']'; } else if (value === null) { result += (!!key ? key + ': ' : '') + 'null'; } else if (typeof value === 'object') { result += (!!key ? key + ': ' : '') ; if (recursive > 0) { result += dir(value, sort, newLine, recursive - 1, indent.repeat(recursive), grep); } result += ' as object'; } else { result += key + ' as ' + typeof value; } result += ', '; if (newLine) result += '\n'; return result; }; if (object === undefined) return dumpObj('', object); if (object === null) return dumpObj('', object); let result = ''; const properties = Object.entries(object); if (sort) properties.sort((a, b) => a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1); let count = 0; for (let [key, value] of properties) { /*if (['xtime', 'XRegExp', 'xor', 'winElt', 'window', 'webkitStorageInfo', 'webkitResolveLocalFileSystemURL', 'webkitRequestFileSystem', 'webkitRequestAnimationFrame', 'webkitCancelAnimationFrame', 'wcwidth', 'vsprintf', 'visualViewport', 'verifyDecrypt'].includes(key)) continue;*/ if (object.hasOwnProperty(key) && (grep !== '' ? key.match(grep) !== null : true)) { result += dumpObj(key, value); count++; } } if (result === '') { result = dumpObj('', object); count++; } result += `${count} items`; return result; }; //#endregion /*$(document).ajaxStop(() => { const ideLoadingTime = procTime.end('Main'); console.log(`[Main] IDE total loading time: ${ideLoadingTime}ms`); }); // ajaxStop end*/ }); // require $ end }); // require dokuWiki end } </script> <!--[code-end]--> </div> </html> ====== IDE Labo ====== <WRAP prewrap 100% #container> </WRAP> **TODO:** 以下の内容のうち DokuWiki に関する内容は整理して [[:linux:dokuwiki|DokuWiki]] に移動して、Apache に関する内容は整理して [[:linux:httpd|Apache HTTP Server]] に移動しよう。 ===== Chrome で V8 ネイティブ シンタックスを有効にする方法 ===== ネイティブ シンタックスについては、[[web:chrome#v8_ネイティブ_シンタックスを有効化する方法|Chrome V8 ネイティブ シンタックスを有効化する方法]] を参照のこと。\\ **Linux**\\ <code> $ /opt/google/chrome/chrome --js-flags="--allow-natives-syntax" & </code> chrome-beta の場合\\ <code> $ /opt/google/chrome-beta/chrome --js-flags="--allow-natives-syntax" & </code> ===== IDE ロード時間の短縮 ===== ==== クライアントサイドキャッシュの利用 ==== IDE のソースコードの大半は DokuWiki の Media によって配信されるため、**<script>** の **src** URL に **?cache=recache**を付加するように変更した。\\ <WRAP prewrap 100%> <file html ide.html> <script src="/_media/javascript/requirejs/require-2.3.6.min.js?cache=recache"></script> <script src="/_media/javascript/requirejs/settings.js?cache=nocache"></script> </file> ※**settings.js** だけは即反映して欲しいので **nocache** とした。\\ <file javascript requirejs/settings.js> require.config({ // DokuWiki media cache setting (cache=recache = cachetime setting value) urlArgs: 'cache=recache', </file> </WRAP> これにより、クライアントサイドでは、DokuWiki の [管理] - [サイト設定] の **cachetime** の時間までキャッシュされるようになる。しかし、**php.ini** のデフォルト設定で **Pragma: no-cache** が付加されるため、更に以下のような工夫が必要である。\\ ==== PHP アプリが Pragma: no-cache を吐かないように設定 ==== [[linux:fedora:install_lamp|Fedora による LAMP(Apache, MySQL, PHP) 環境構築]]\\ \\ **session.cache_limiter** を **public** に設定することでも、**Pragma: no-cache** を吐かなくなるが、DokuWiki の **?do=edit (編集ページ)** までキャッシュされて動作不良を起こす。(**__この方法は上手く行かない__**)\\ <WRAP prewrap 100%> <file autoconf /etc/php.ini> ;session.cache_limiter = nocache session.cache_limiter = public </file> </WRAP> 以下のように **.htaccess** を編集して、メディア配信要求に限定して **Pragma: cache** を **set** する。\\ <WRAP prewrap 100%> <file autoconf dokuwiki/.htaccess> ... RewriteRule lib/exe/fetch\.php - [E=X_CACHE_HEADER] RewriteRule ^_media/(.*) lib/exe/fetch.php?media=$1 [QSA,L] RewriteRule ^_detail/(.*) lib/exe/detail.php?media=$1 [QSA,L] RewriteRule ^_export/([^/]+)/(.*) doku.php?do=export_$1&id=$2 [QSA,L] RewriteRule ^$ doku.php [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) doku.php?id=$1 [QSA,L] RewriteRule ^index.php$ doku.php Header always set Pragma cache env=X_CACHE_HEADER ... </file> </WRAP> **lib/exe/fetch.php** への要求に対して環境変数 **X_CACHE_HEADER** をセットして、メディア配信要求に限定して **Pragma** ヘッダの **cache** を **set** する。\\ ==== FireFox が確実にキャッシュを使うようする ==== FireFox だけ上記の設定を行っても、ファイルによってはキャッシュを利用しない事がある。\\ いろいろ試した結果、**ETag** を除去するとキャッシュの利用割合が増加するようだ。\\ <file autoconf dokuwiki/.htaccess> Header unset ETag FileETag None ... </file> **ETag** があると FireFox は余計なキャッシュ確認を行う??\\ [[https://developer.mozilla.org/ja/docs/Web/HTTP/Caching|HTTP キャッシュ - HTTP | MDN]]\\ \\ キャッシュを無効化した際の転送量は **4.48MB** であるが、**ETag** を除去してキャッシュを有効化すると転送量は **1.88MB** まで削減できた。\\ ==== Apache の HTTP 圧縮 ==== Apache の HTTP 圧縮を有効化して html, txt, csv, css, js, json, py, wasm, data, svg などのファイルを Brotli で圧縮して配信時間を短縮している。\\ <WRAP prewrap 100%> <file autoconf /etc/httpd/conf/httpd.conf> # Compress Filter Settings # AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/csv text/css application/javascript application/json application/x-python application/wasm application/octet-stream image/svg+xml </file> </WRAP> ==== HTTP プロトコルを HTTP/2.0 に設定 ==== 少ないセッションで大量ファイルを転送するために、Web サーバーのプロトコルを HTTP/2.0 に設定する。\\ <WRAP prewrap 100%> <file autoconf /etc/httpd/conf.d/domain-name.conf> Protocols h2 h2c http/1.1 </file> </WRAP> 以上のチューニングにより IDE のロード時間はモバイル回線で以下のようになる。(DEBUG: false - js/css Minify)\\ * Chrome で最速 **161(cache on)~1500(cache off)ms** である。 * FireFox で最速 **213(cache on)~1349(cache off)ms** である。 ===== Wiki Hash ===== Don't edit!! <WRAP #_wikihash>12ebdfd80</WRAP> javascript/ide.txt 最終更新: 2024/02/04 14:26by 非ログインユーザー