- // ==UserScript==
- // @name GreasyFork User Dashboard
- // @name:ja GreasyFork User Dashboard
- // @namespace knoa.jp
- // @description It redesigns your own user page.
- // @description:ja 自分用の新しいユーザーページを提供します。
- // @include https://greasyfork.org/*/users/*
- // @version 1.0.2
- // @grant none
- // ==/UserScript==
-
- (function(){
- const SCRIPTNAME = 'GreasyForkUserDashboard';
- const DEBUG = false;/*
- 1.0.2
- small fixes.
- */
- if(window === top && console.time) console.time(SCRIPTNAME);
- const INTERVAL = 1000;/* for fetch */
- const DEFAULTMAX = 10;/* for chart scale */
- const DAYS = 180;/* for chart length */
- const STATSUPDATE = 1000*60*60;/* stats update interval of greasyfork.org */
- const TRANSLATIONEXPIRE = 1000*60*60*24*30;/* cache time for translations */
- let site = {
- targets: {
- userSection: () => $('body > header + div > section:nth-of-type(1)'),
- controlPanel: () => $('#control-panel'),
- newScriptSetLink: () => $('a[href$="/sets/new"]'),
- scriptSets: () => $('body > header + div > section:nth-of-type(2)'),
- scripts: () => $('body > header + div > section:nth-of-type(2) + div'),
- userScriptSets: () => $('#user-script-sets'),
- userScriptList: () => $('#user-script-list'),
- },
- get: {
- language: (d) => d.documentElement.lang,
- firstScript: (list) => list.querySelector('li h2 > a'),
- translation: (d) => {return {
- info: d.querySelector('#script-links > li.current').textContent,
- code: d.querySelector('#script-links > li > a[href$="/code"]').textContent,
- history: d.querySelector('#script-links > li > a[href$="/versions"]').textContent,
- feedback: d.querySelector('#script-links > li > a[href$="/feedback"]').textContent.replace(/\s\(\d+\)/, ''),
- stats: d.querySelector('#script-links > li > a[href$="/stats"]').textContent,
- derivatives: d.querySelector('#script-links > li > a[href$="/derivatives"]').textContent,
- update: d.querySelector('#script-links > li > a[href$="/versions/new"]').textContent,
- delete: d.querySelector('#script-links > li > a[href$="/delete"]').textContent,
- admin: d.querySelector('#script-links > li > a[href$="/admin"]').textContent,
- version: d.querySelector('#script-stats > dt.script-show-version').textContent,
- }},
- props: (li) => {return {
- name: li.querySelector('h2 > a'),
- description: li.querySelector('.description'),
- stats: li.querySelector('dl.inline-script-stats'),
- dailyInstalls: li.querySelector('dd.script-list-daily-installs'),
- totalInstalls: li.querySelector('dd.script-list-total-installs'),
- ratings: li.querySelector('dd.script-list-ratings'),
- createdDate: li.querySelector('dd.script-list-created-date'),
- updatedDate: li.querySelector('dd.script-list-updated-date'),
- scriptVersion: li.dataset.scriptVersion,
- }},
- }
- };
- let translations = {
- 'en': {
- info: 'Info',
- code: 'Code',
- history: 'History',
- feedback: 'Feedback',
- stats: 'Stats',
- derivatives: 'Derivatives',
- update: 'Update',
- delete: 'Delete',
- admin: 'Admin',
- version: 'Version',
- }
- }, translation = translations['en'];
- let elements = {}, shown = {};
- let core = {
- initialize: function(){
- core.getElements();
- if(elements.length < site.targets.length) return log('Not user own page.');
- core.addStyle();
- core.getTranslations();
- core.hideUserSection();
- core.hideControlPanel();
- core.addTabNavigation();
- core.addNewScriptSetLink();
- core.rebuildScriptList();
- },
- getElements: function(){
- if(!site.targets.controlPanel()) return;/* not my own page */
- for(let i = 0, keys = Object.keys(site.targets); keys[i]; i++){
- let element = site.targets[keys[i]]();
- if(!element) return log(`Not found: ${keys[i]}`);
- element.dataset.selector = keys[i];
- elements[keys[i]] = element;
- }
- shown = Storage.read('shown') || shown;
- },
- getTranslations: function(){
- let language = site.get.language(document);
- translations = Storage.read('translations') || translations;
- translation = translations[language] || translation;
- if(site.get.language(document) === 'en' || Object.keys(translations).find((lang) => lang === language)) return;
- let firstScript = site.get.firstScript(elements.userScriptList);
- fetch(firstScript.href, {credentials: 'include'})
- .then(response => response.text())
- .then(text => new DOMParser().parseFromString(text, 'text/html'))
- .then(d => {
- translation = translations[site.get.language(d)] = site.get.translation(d);
- Storage.save('translations', translations, Date.now() + TRANSLATIONEXPIRE);
- });
- },
- hideUserSection: function(){
- let userSection = elements.userSection, more = createElement(core.html.more());
- if(!shown.userSection) userSection.classList.add('hidden');
- more.addEventListener('click', function(e){
- userSection.classList.toggle('hidden');
- shown.userSection = !userSection.classList.contains('hidden');
- Storage.save('shown', shown);
- });
- userSection.appendChild(more);
- },
- hideControlPanel: function(){
- let controlPanel = elements.controlPanel, header = controlPanel.firstElementChild;
- if(!shown.controlPanel) controlPanel.classList.add('hidden');
- elements.userSection.style.minHeight = controlPanel.offsetHeight + controlPanel.offsetTop + 'px';
- header.addEventListener('click', function(e){
- controlPanel.classList.toggle('hidden');
- shown.controlPanel = !controlPanel.classList.contains('hidden');
- Storage.save('shown', shown);
- elements.userSection.style.minHeight = controlPanel.offsetHeight + controlPanel.offsetTop + 'px';
- });
- },
- addTabNavigation: function(){
- const tabs = [
- {label: elements.scriptSets.querySelector('header').textContent, selector: 'scriptSets', list: elements.userScriptSets},
- {label: elements.scripts.querySelector('header').textContent, selector: 'scripts', list: elements.userScriptList, selected: true},
- ];
- let nav = createElement(core.html.tabNavigation()), scriptSets = elements.scriptSets;
- let template = nav.querySelector('li.template');
- scriptSets.parentNode.insertBefore(nav, scriptSets);
- for(let i = 0; tabs[i]; i++){
- let tab = template.cloneNode(true);
- tab.classList.remove('template');
- tab.textContent = tabs[i].label + ` (${tabs[i].list.children.length})`;
- tab.dataset.target = tabs[i].selector;
- tab.addEventListener('click', function(e){
- tab.parentNode.querySelector('[data-selected="true"]').dataset.selected = 'false';
- $('[data-tabified][data-selected="true"]').dataset.selected = 'false';
- tab.dataset.selected = 'true';
- $(`[data-selector="${tab.dataset.target}"]`).dataset.selected = 'true';
- });
- template.parentNode.insertBefore(tab, template);
- /**/
- let target = elements[tabs[i].selector];
- target.dataset.tabified = 'true';
- if(tabs[i].selected) tab.dataset.selected = target.dataset.selected = 'true';
- else tab.dataset.selected = target.dataset.selected = 'false';
- }
- },
- addNewScriptSetLink: function(){
- let link = elements.newScriptSetLink.cloneNode(true), list = elements.userScriptSets, li = document.createElement('li');
- li.appendChild(link);
- list.appendChild(li);
- },
- rebuildScriptList: function(){
- let stats = Storage.read('stats') || {}, promises = [];
- for(let i = 0, list = elements.userScriptList, li; li = list.children[i]; i++){
- let more = createElement(core.html.more()), props = site.get.props(li);
- if(!shown[li.dataset.scriptName]) li.classList.add('hidden');
- more.addEventListener('click', function(e){
- li.classList.toggle('hidden');
- shown[li.dataset.scriptName] = !li.classList.contains('hidden');
- Storage.save('shown', shown);
- });
- li.appendChild(more);
- /* attatch titles */
- props.dailyInstalls.previousElementSibling.title = props.dailyInstalls.previousElementSibling.textContent;
- props.totalInstalls.previousElementSibling.title = props.totalInstalls.previousElementSibling.textContent;
- props.ratings.previousElementSibling.title = props.ratings.previousElementSibling.textContent;
- props.createdDate.previousElementSibling.title = props.createdDate.previousElementSibling.textContent;
- props.updatedDate.previousElementSibling.title = props.updatedDate.previousElementSibling.textContent;
- /* wrap the description to make it an inline element */
- let span = document.createElement('span');
- span.textContent = props.description.textContent.trim();
- props.description.replaceChild(span, props.description.firstChild);
- /* Link to Code from Version */
- let versionLabel = createElement(core.html.dt('script-list-version', translation.version));
- let versionLink = createElement(core.html.ddLink('script-list-version', props.scriptVersion, props.name.href + '/code', translation.code));
- versionLabel.title = versionLabel.textContent;
- props.stats.insertBefore(versionLabel, props.createdDate.previousElementSibling);
- props.stats.insertBefore(versionLink, props.createdDate.previousElementSibling);
- /* Link to Stats from Total installs */
- let statsLink = createElement(core.html.ddLink('script-list-total-installs', props.totalInstalls.textContent, props.name.href + '/stats', translation.stats));
- props.stats.replaceChild(statsLink, props.totalInstalls);
- /* Link to History from Updated date */
- let historyLink = createElement(core.html.ddLink('script-list-updated-date', props.updatedDate.textContent, props.name.href + '/versions', translation.history));
- props.stats.replaceChild(historyLink, props.updatedDate);
- /* Draw chart of daily update checks */
- let chart = createElement(core.html.chart());
- if(stats[li.dataset.scriptName]){
- core.buildChart(chart, stats[li.dataset.scriptName].slice(-DAYS));
- li.appendChild(chart);
- continue;
- }else promises.push(new Promise(function(resolve, reject){
- setTimeout(function(){
- fetch(props.name.href + '/stats.csv')/* less file size than json */
- .then(response => response.text())
- .then(csv => {
- let lines = csv.split('\n');
- lines = lines.slice(1, -1);/* cut the labels + blank line */
- stats[props.name.textContent] = [];
- for(let i = 0; lines[i]; i++){
- let p = lines[i].split(',');
- stats[props.name.textContent][i] = {
- date: p[0],
- installs: parseInt(p[1]),
- updateChecks: parseInt(p[2]),
- };
- }
- core.buildChart(chart, stats[li.dataset.scriptName].slice(-DAYS));
- li.appendChild(chart);
- resolve();
- });
- }, i * INTERVAL);/* server friendly */
- }));
- }
- Promise.all(promises)
- .then(() => {
- let now = Date.now(), past = now % STATSUPDATE, expire = now - past + STATSUPDATE;
- Storage.save('stats', stats, expire);
- });
- },
- buildChart: function(chart, stats){
- let max = DEFAULTMAX;
- for(let i = 0; stats[i]; i++){
- if(stats[i].updateChecks > max) max = stats[i].updateChecks;
- }
- let dl = chart.querySelector('dl'), dt = dl.querySelector('dt'), dd = dl.querySelector('dd');
- for(let i = 0, last = stats.length - 1; stats[i]; i++){
- let date = stats[i].date, installs = stats[i].installs, updateChecks = stats[i].updateChecks;
- let dateDt = dt.cloneNode(), countDd = dd.cloneNode();
- dateDt.classList.remove('template');
- countDd.classList.remove('template');
- dateDt.textContent = date;
- countDd.title = date + ': ' + updateChecks + (updateChecks === 1 ? ' check' : ' checks');
- countDd.style.height = ((updateChecks / max) * 100) + '%';
- if(i === last - 1){
- countDd.classList.add('last');
- let label = document.createElement('span');
- label.textContent = toMetric(updateChecks);
- countDd.appendChild(label);
- }
- dl.insertBefore(dateDt, dt);
- dl.insertBefore(countDd, dt);
- }
- },
- addStyle: function(name = 'style'){
- let style = createElement(core.html[name]());
- document.head.appendChild(style);
- if(elements[name] && elements[name].isConnected) document.head.removeChild(elements[name]);
- elements[name] = style;
- },
- html: {
- more: () => `
- <button class="more"></button>
- `,
- tabNavigation: () => `
- <nav id="tabNavigation">
- <ul>
- <li class="template"></li>
- </ul>
- </nav>
- `,
- dt: (className, textContent) => `
- <dt class="${className}"><span>${textContent}</span></dt>
- `,
- ddLink: (className, textContent, href, title) => `
- <dd class="${className}"><a href="${href}" title="${title}">${textContent}</a></dd>
- `,
- chart: () => `
- <div class="chart">
- <dl>
- <dt class="template date"></dt>
- <dd class="template count"></dd>
- </dl>
- </div>
- `,
- style: () => `
- <style type="text/css">
- /* gray scale: 119-153-187-221 */
- /* coommon */
- h2, h3{
- margin: 0;
- }
- ul, ol{
- margin: 0;
- padding: 0 0 0 2em;
- }
- .template{
- display: none;
- }
- section.text-content{
- position: relative;
- padding: 0;
- }
- section.text-content > *{
- margin: 14px;
- }
- section.text-content h2{
- text-align: left !important;
- margin-bottom: 0;
- }
- section > header + *{
- margin: 0 0 14px !important;
- }
- button.more{
- color: rgb(153,153,153);
- border: 1px solid rgb(187,187,187);
- background: white;
- padding: 0;
- cursor: pointer;
- }
- button.more::-moz-focus-inner{
- border: none;
- }
- button.more::after{
- font-size: medium;
- content: "▴";
- }
- .hidden > button.more{
- background: rgb(221, 221, 221);
- position: absolute;
- }
- .hidden > button.more::after{
- content: "▾";
- }
- /* User panel */
- section[data-selector="userSection"].hidden{
- max-height: 10em;
- overflow: hidden;
- }
- section[data-selector="userSection"] > button.more{
- position: relative;
- bottom: 0;
- width: 100%;
- margin: 0;
- border: none;
- border-top: 1px solid rgba(187, 187, 187);
- border-radius: 0 0 5px 5px;
- }
- section[data-selector="userSection"].hidden > button.more{
- position: absolute;
- }
- /* Control panel */
- section#control-panel{
- font-size: smaller;
- width: 200px;
- position: absolute;
- top: 0;
- right: 0;
- z-index: 1;
- }
- section#control-panel h3{
- font-size: 1em;
- padding: .25em 1em;
- border-radius: 5px 5px 0 0;
- background: rgb(103, 0, 0);
- color: white;
- cursor: pointer;
- }
- section#control-panel.hidden h3{
- border-radius: 5px 5px 5px 5px;
- }
- section#control-panel h3::after{
- content: " ▴";
- margin-left: .25em;
- }
- section#control-panel.hidden h3::after{
- content: " ▾";
- }
- ul#user-control-panel{
- list-style-type: square;
- color: rgb(187, 187, 187);
- width: 100%;
- margin: .5em 0;
- padding: .5em .5em .5em 1.5em;
- background: white;
- border-radius: 0 0 5px 5px;
- border: 1px solid rgb(187, 187, 187);
- border-top: none;
- box-sizing: border-box;
- }
- section#control-panel.hidden > ul#user-control-panel{
- display: none;
- }
- /* Discussions on your scripts */
- #user-discussions-on-scripts-written{
- margin-top: 0;
- }
- /* tabs */
- #tabNavigation > ul{
- list-style-type: none;
- padding: 0;
- display: flex;
- }
- #tabNavigation > ul > li{
- font-weight: bold;
- background: white;
- padding: .25em 1em;
- border: 1px solid rgb(187, 187, 187);
- border-bottom: none;
- border-radius: 5px 5px 0 0;
- box-shadow: 0 0 5px rgb(221, 221, 221);
- cursor: pointer;
- }
- #tabNavigation > ul > li:first-child{
- }
- #tabNavigation > ul > li[data-selected="false"]{
- color: rgb(153,153,153);
- background: rgb(221, 221, 221);
- }
- [data-selector="scriptSets"] > section,
- [data-tabified] #user-script-list{
- border-radius: 0 5px 5px 5px;
- }
- [data-tabified] header{
- display: none;
- }
- [data-tabified][data-selected="false"]{
- display: none;
- }
- /* Scripts */
- #user-script-list li{
- padding: .25em 1em;
- position: relative;
- }
- #user-script-list li:last-child{
- border-bottom: none;/* missing in greasyfork.org */
- }
- #user-script-list li article{
- position: relative;
- z-index: 1;/* over the .chart */
- pointer-events: none;
- }
- #user-script-list li article h2 > a,
- #user-script-list li article h2 > .description/* it's block! */ > span,
- #user-script-list li article dl > dt > *,
- #user-script-list li article dl > dd > *{
- pointer-events: auto;/* apply on inline elements */
- }
- #user-script-list li button.more{
- border-radius: 5px;
- position: absolute;
- top: 0;
- right: 0;
- margin: 5px;
- width: 2em;
- z-index: 1;/* over the .chart */
- }
- #user-script-list li .description{
- font-size: small;
- margin: 0 0 0 .1em;/* ajust first letter position */
- }
- #user-script-list li dl.inline-script-stats{
- margin-top: .25em;
- column-count: 3;
- max-height: 3em;
- }
- #user-script-list li dl.inline-script-stats dt{
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 200px;/* mysterious */
- }
- #user-script-list li dl.inline-script-stats .script-list-author{
- display: none;
- }
- #user-script-list li dl.inline-script-stats dt.script-list-daily-installs,
- #user-script-list li dl.inline-script-stats dt.script-list-total-installs{
- width: 65%;
- }
- #user-script-list li dl.inline-script-stats dd.script-list-daily-installs,
- #user-script-list li dl.inline-script-stats dd.script-list-total-installs{
- width: 35%;
- }
- #user-script-list li.hidden .description,
- #user-script-list li.hidden .inline-script-stats{
- display: none;
- }
- /* chart */
- .chart{
- position: absolute;
- top: 0;
- right: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
- mask-image: linear-gradient(to right, rgba(0,0,0,.5), black);
- -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,.5), black);
- }
- .chart > dl{
- position: absolute;
- bottom: 0;
- right: 2em;
- height: calc(100% - 5px);
- display: flex;
- align-items: flex-end;
- }
- .chart > dl > dt.date{
- display: none;
- }
- .chart > dl > dd.count{
- background: rgb(221,221,221);
- width: 3px;
- border-left: 1px solid white;
- margin: 0;
- }
- .chart > dl > dd.count.last,
- .chart > dl > dd.count:hover{
- background: rgb(187,187,187);
- }
- .chart > dl > dd.count.last:hover{
- background: rgb(153,153,153);
- }
- .chart > dl > dd.count.last > span{
- font-weight: bold;
- color: rgb(153,153,153);
- position: absolute;
- top: 5px;
- right: 10px;
- pointer-events: none;
- }
- .chart > dl > dd.count.last:hover > span{
- color: rgb(119,119,119);
- }
- /* sidebar */
- .sidebar{
- padding-top: 0;
- }
- .ad/* excuse me, it disappears only in my own user page :-) */,
- #script-list-filter{
- display: none !important;
- }
- </style>
- `,
- },
- };
- class Storage{
- static key(key){
- return (SCRIPTNAME) ? (SCRIPTNAME + '-' + key) : key;
- }
- static save(key, value, expire = null){
- key = Storage.key(key);
- localStorage[key] = JSON.stringify({
- value: value,
- saved: Date.now(),
- expire: expire,
- });
- }
- static read(key){
- key = Storage.key(key);
- if(localStorage[key] === undefined) return undefined;
- let data = JSON.parse(localStorage[key]);
- if(data.value === undefined) return data;
- if(data.expire === undefined) return data;
- if(data.expire === null) return data.value;
- if(data.expire < Date.now()) return localStorage.removeItem(key);
- return data.value;
- }
- static delete(key){
- key = Storage.key(key);
- delete localStorage.removeItem(key);
- }
- static saved(key){
- key = Storage.key(key);
- if(localStorage[key] === undefined) return undefined;
- let data = JSON.parse(localStorage[key]);
- if(data.saved) return data.saved;
- else return undefined;
- }
- }
- const $ = function(s){return document.querySelector(s)};
- const $$ = function(s){return document.querySelectorAll(s)};
- const createElement = function(html){
- let outer = document.createElement('div');
- outer.innerHTML = html;
- return outer.firstElementChild;
- };
- const toMetric = function(number, fixed = 1){
- switch(true){
- case(number < 1e3): return (number);
- case(number < 1e6): return (number/ 1e3).toFixed(fixed) + 'K';
- case(number < 1e9): return (number/ 1e6).toFixed(fixed) + 'M';
- case(number < 1e12): return (number/ 1e9).toFixed(fixed) + 'G';
- default: return (number/1e12).toFixed(fixed) + 'T';
- }
- };
- const log = function(){
- if(!DEBUG) return;
- let l = log.last = log.now || new Date(), n = log.now = new Date();
- let error = new Error(), line = log.format.getLine(error), callers = log.format.getCallers(error);
- //console.log(error.stack);
- console.log(
- SCRIPTNAME + ':',
- /* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3),
- /* +0.000s */ '+' + ((n-l)/1000).toFixed(3) + 's',
- /* :00 */ ':' + line,
- /* caller.caller */ (callers[2] ? callers[2] + '() => ' : '') +
- /* caller */ (callers[1] || '') + '()',
- ...arguments
- );
- };
- log.formats = [{
- name: 'Firefox Scratchpad',
- detector: /MARKER@Scratchpad/,
- getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
- getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
- }, {
- name: 'Firefox Console',
- detector: /MARKER@debugger/,
- getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
- getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
- }, {
- name: 'Firefox Greasemonkey 3',
- detector: /\/gm_scripts\//,
- getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1],
- getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
- }, {
- name: 'Firefox Greasemonkey 4+',
- detector: /MARKER@user-script:/,
- getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1] - 500,
- getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
- }, {
- name: 'Firefox Tampermonkey',
- detector: /MARKER@moz-extension:/,
- getLine: (e) => e.stack.split('\n')[1].match(/([0-9]+):[0-9]+$/)[1] - 6,
- getCallers: (e) => e.stack.match(/^[^@]*(?=@)/gm),
- }, {
- name: 'Chrome Console',
- detector: /at MARKER \(<anonymous>/,
- getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)$/)[1],
- getCallers: (e) => e.stack.match(/[^ ]+(?= \(<anonymous>)/gm),
- }, {
- name: 'Chrome Tampermonkey',
- detector: /at MARKER \((userscript\.html|chrome-extension:)/,
- getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+)\)$/)[1] - 6,
- getCallers: (e) => e.stack.match(/[^ ]+(?= \((userscript\.html|chrome-extension:))/gm),
- }, {
- name: 'Edge Console',
- detector: /at MARKER \(eval/,
- getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)$/)[1],
- getCallers: (e) => e.stack.match(/[^ ]+(?= \(eval)/gm),
- }, {
- name: 'Edge Tampermonkey',
- detector: /at MARKER \(Function/,
- getLine: (e) => e.stack.split('\n')[2].match(/([0-9]+):[0-9]+\)$/)[1] - 4,
- getCallers: (e) => e.stack.match(/[^ ]+(?= \(Function)/gm),
- }, {
- name: 'Safari',
- detector: /^MARKER$/m,
- getLine: (e) => 0,/*e.lineが用意されているが最終呼び出し位置のみ*/
- getCallers: (e) => e.stack.split('\n'),
- }, {
- name: 'Default',
- detector: /./,
- getLine: (e) => 0,
- getCallers: (e) => [],
- }];
- log.format = log.formats.find(function MARKER(f){
- if(!f.detector.test(new Error().stack)) return false;
- //console.log('//// ' + f.name + '\n' + new Error().stack);
- return true;
- });
- core.initialize();
- if(window === top && console.timeEnd) console.timeEnd(SCRIPTNAME);
- })();