<commit_before>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */

console.log('minify.js loaded...');

/* функция сжимает js-скрипты
 * и сохраняет их с именем .min.js
 */

var fs = require('fs');
var path=require('path');

var MinFolder='min/';
/* function clear MinFolder
 * if we could not create
 * directory and it is
 * not exist
 */
var folderExist = function(pError, pStat){
    "use strict";
    /*file found and it's directory */
    if(!pError && pStat.isDirectory())
        console.log('folder exist: ' + MinFolder);
    else MinFolder='/';
};

/*
 * function says thet folder created
 * if everything is OK, or
 * moves to folderExist function
 */
var makeFolder = function(pError){
    "use strict";
    /*folder created successfully*/
    if(!pError)
        console.log('folder created: min');
    else fs.stat(MinFolder,folderExist);
};

/* Trying to create folder min
 * where woud be minifyed versions
 * of files 511(10)=777(8)
 * rwxrwxrwx
 */
fs.mkdir(MinFolder,511,makeFolder);

exports.MinFolder = MinFolder;
exports.Cache    = {};

/* function which minificate js-files
 * @pJSFiles_a              - varible, wich contain array
 *                            of js file names or string, if name
 *                            single
 * @pMoreProcessing_o       - object, thet contain function thet will be executed
 *                            after js-file processed and file name
 * pMoreProcessing_o Example: { Name:'1.js', Func: function(pFinalCode){} }
 * @pCache_b                - if true files do not writes on disk, just saves
 *                              in Minify Cache
 */
exports.jsScripts=function jsScripts(pJSFiles_a, pMoreProcessing_o, pCache_b){
    'use strict';
    /* подключаем модуль uglify-js
     * если его нет - дальнейшая
     * работа функции не имеет смысла
     */
    try{
        var jsp = require("uglify-js").parser;
        var pro = require("uglify-js").uglify;
    }catch(error){
        console.log('can\'n load uglify-js\n'                  +
            'to use js-minification you need to install uglify-js\n'    +
                'npm install uglify-js\n'                               +
                'https://github.com/mishoo/UglifyJS');
        return false;
    }
    /* Константы */
    var CLIENT_JS='client.js';
    var CLOUDFUNC_JS='lib/cloudfunc.js';

    /* if passed string, or object
     * putting it to array
     */
    if(typeof pJSFiles_a !== "array")
        pJSFiles_a=[pJSFiles_a];

    var dataReaded_f=function(pFileName, pData){
        /*
         * if postProcessing function exist
         * getting it from pFileName object
         */
        var lMoreProcessing_f;
        if(typeof pFileName === 'object'){
            var lName;
            for(lName in pFileName){
                break;
            }
            lMoreProcessing_f = pFileName[lName];
            pFileName = lName;
        }
        console.log('file ' + pFileName + ' readed');

        /*********************************/
        /* сжимаем код через uglify-js */
        var uglify_js=function(pDdata){
            var orig_code = pDdata.toString();
            var ast = jsp.parse(orig_code); // parse code and get the initial AST
            ast = pro.ast_mangle(ast); // get a new AST with mangled names
            ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
            var result_code = pro.gen_code(ast); // compressed code here
            return result_code;
        };
        /*********************************/
        var final_code=uglify_js(pData);

        var minFileName=pFileName.replace('.js','.min.js');

        /* если мы сжимаем client.js -
         * меняем строку cloudfunc.js на
         * cloudfunc.min.js и выводим сообщение
         *
         * меняем строку keyBinding.js на
         * keyBinding.min.js
         * если другой файл - ничего не деалем
         */

        /* if lMoreProcessing_f seeted up
         * and function associated with
         * current file name exists -
         * run it
         */
        if(lMoreProcessing_f                    &&
            typeof lMoreProcessing_f === "function"){
                final_code = lMoreProcessing_f(final_code);
        }

        minFileName = path.basename(minFileName);
        /* записываем сжатый js-скрипт
         * в кэш если установлен pCache_b
         * или на диск, если не установлен
         */
        if(pCache_b){
            exports.Cache[minFileName] = final_code;
            console.log('file ' + minFileName + ' saved to cache...');
        }
        else{
            /* minimized file will be in min file
             * if it's possible if not -
             * in root
             */
            minFileName = MinFolder + minFileName;

            fs.writeFile(minFileName, final_code, fileWrited(minFileName));
        }
    };

    /* moving thru all elements of js files array */
    for(var i=0; pJSFiles_a[i]; i++){
        /* if postProcessing function exist
         * getting file name and passet next
         */
        var lName;
        if(typeof pJSFiles_a[i] === 'object'){
            for(lName in pJSFiles_a[i]){
                break;
            }
        }else lName = pJSFiles_a[i];
        console.log('reading file ' + lName + '...');

        fs.readFile(lName, fileReaded(pJSFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция сжимает css-стили
 * и сохраняет их с именем .min.css
 * @pCSSFiles_a           - масив имен css файлов или строка,
 *                          если имя одно
 * @pImgConvertToBase64_b - булевый признак,
 *                          который отвечает за то, что быконвертировать
 *                          картинки в base64 и поместить в выходной css файл
 */
exports.cssStyles=function cssStyles(pCSSFiles_a, pImgConvertToBase64_b){
    'use strict';

     /* connecting cleanCSS,
      * if we can't find it -
      * return false
      */
     var cleanCSS;
     try{
        cleanCSS = require('clean-css');
    }catch(error){
        console.log('can\'n load clean-css \n'                          +
            'to use css-minification you need to install clean-css \n'  +
                'npm install clean-css\n'                               +
                'https://github.com/GoalSmashers/clean-css');
        return false;
    }

    if(typeof pCSSFiles_a === "string")
        pCSSFiles_a=[pCSSFiles_a];
    /* Varible contains information
     * about readed css file
     */
    var lCSSFiles_doneCount=0;

    var lAllStyle='';

    var dataReaded_f=function(pFileName, pData){
        console.log('file ' + pFileName + ' readed');
        /*********************************/
        /* сжимаем код через clean-css */
        var clean_css=function(pData){
            /* Сохраняем весь стиль в одну переменную*/
            return cleanCSS.process(pData);
        };
        /*********************************/
        var final_code=clean_css(pData);

        lAllStyle+=final_code;

        var minFileName=pFileName.replace('.css','.min.css');

        ++lCSSFiles_doneCount;

        /* if all files writed we
         * save all minimized css
         * to one file all.min.css
         */
        if(pCSSFiles_a.length === lCSSFiles_doneCount){
            /* если включена конвертация картинок в base64
             * вызываем её
             */
            if(pImgConvertToBase64_b)
                base64_images(lAllStyle);
            else
                fs.writeFile(MinFolder + 'all.min.css', lAllStyle, fileWrited(MinFolder + 'all.min.css'));
        }
         /* в другом случае - записываем сжатый css файл*/
        else{
            minFileName = MinFolder + path.basename(minFileName);
            fs.writeFile(minFileName, final_code, fileWrited(minFileName));
        }
    };

   /* moving thru all elements of css files array */
    for(var i=0;pCSSFiles_a[i];i++){
        console.log('reading file ' + pCSSFiles_a[i]+'...');
        fs.readFile(pCSSFiles_a[i],fileReaded(pCSSFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция сжимает html файлы
 * и сохраняет их с именем .min.html
 * @pHTMLFiles_a - массим имен html
 * файлов, или строка если имя одно
 */
exports.html=function(pHTMLFiles_a){
    'use strict';

     /* connecting cleanCSS,
      * if we can't find it -
      * return false
      */
     var htmlMinifier;
     try{
        htmlMinifier = require('html-minifier');
    }catch(error){
        console.log('can\'n load html-minifier \n'                 +
            'to use html-minification you need to install html-minifier\n'  +
                'npm install html-minifier\n'                               +
                'https://github.com/kangax/html-minifier');
        return false;
    }

    if(typeof pHTMLFiles_a === "string")
        pHTMLFiles_a=[pHTMLFiles_a];

    var dataReaded_f=function(pFileName, pData){
        console.log('file ' + pFileName + ' readed');
        /*********************************/
        /* сжимаем код через clean-css */
        var html_minify=function(pData){
            /* Сохраняем весь стиль в одну переменную*/

            var lOptions={
                removeComments:                 true,
                removeCommentsFromCDATA:        true,
                removeCDATASectionsFromCDATA:   true,
                collapseWhitespace:             true,
                collapseBooleanAttributes:      true,
                removeAttributeQuotes:          true,
                removeRedundantAttributes:      true,
                useShortDoctype:                true,
                removeEmptyAttributes:          true,
                /* оставляем, поскольку у нас
                 * в элемент fm генерируеться
                 * таблица файлов
                 */
                removeEmptyElements:            false,
                removeOptionalTags:             true,
                removeScriptTypeAttributes:     true,
                removeStyleLinkTypeAttributes:  true
            };

            return htmlMinifier.minify(pData,lOptions);
        };
        /*********************************/
        var final_code=html_minify(pData);

        var minFileName=pFileName.replace('.html','.min.html');

         /* adding Min folder to file path */
         minFileName = MinFolder + minFileName;
         /* записываем сжатый html файл*/
        fs.writeFile(minFileName, final_code, fileWrited(minFileName));
    };

     /* moving thru all elements of css files array */
    for(var i=0;pHTMLFiles_a[i];i++){
        console.log('reading file ' + pHTMLFiles_a[i]+'...');
        fs.readFile(pHTMLFiles_a[i],fileReaded(pHTMLFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция переводит картинки в base64 и записывает в css-файл*/
function base64_images(pFileContent_s){
    'use strict';
     var b64img;
     try{
        b64img = require('css-b64-images');
    }catch(error){
        console.log('can\'n load clean-css \n'                 +
            'to use images to base64 convertation you need to install css-base64-images \n'  +
                'npm install -g css-b64-images\n'                               +
                'https://github.com/Filirom1/css-base64-images');
        return false;
    }
    b64img.fromString(pFileContent_s, '.','', function(err, css){
        console.log('images converted to base64 and saved in css file');
        fs.writeFile(MinFolder + 'all.min.css', css, fileWrited(MinFolder + 'all.min.css'));
    });
}

/* Функция создаёт асинхроную версию
 * для чтения файла
 * @pFileName - имя считываемого файла
 */
function fileReaded(pFileName,pCompressFunc){
    "use strict";
    return function(pError,pData){
        /* функция в которую мы попадаем,
         * если данные считались
         *
         * если ошибка - показываем её
         * иначе если переданная функция -
         * функция запускаем её
         */
        if(!pError)
            if (pCompressFunc && typeof pCompressFunc==="function")
                    pCompressFunc(pFileName,pData.toString());
        else console.log(pError);
    };
}

/*
 * Функция вызываеться после записи файла
 * и выводит ошибку или сообщает,
 * что файл успешно записан
 */
function fileWrited(pFileName){
    "use strict";
    return function(error){
        console.log(error?error:('file '+pFileName+' writed...'));
    };
}<commit_msg>fixed bug with passing not array parameter to function jsScripts<commit_after>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */

console.log('minify.js loaded...');

/* функция сжимает js-скрипты
 * и сохраняет их с именем .min.js
 */

var fs = require('fs');
var path=require('path');

var MinFolder='min/';
/* function clear MinFolder
 * if we could not create
 * directory and it is
 * not exist
 */
var folderExist = function(pError, pStat){
    "use strict";
    /*file found and it's directory */
    if(!pError && pStat.isDirectory())
        console.log('folder exist: ' + MinFolder);
    else MinFolder='/';
};

/*
 * function says thet folder created
 * if everything is OK, or
 * moves to folderExist function
 */
var makeFolder = function(pError){
    "use strict";
    /*folder created successfully*/
    if(!pError)
        console.log('folder created: min');
    else fs.stat(MinFolder,folderExist);
};

/* Trying to create folder min
 * where woud be minifyed versions
 * of files 511(10)=777(8)
 * rwxrwxrwx
 */
fs.mkdir(MinFolder,511,makeFolder);

exports.MinFolder = MinFolder;
exports.Cache    = {};

/* function which minificate js-files
 * @pJSFiles_a              - varible, wich contain array
 *                            of js file names or string, if name
 *                            single
 * @pMoreProcessing_o       - object, thet contain function thet will be executed
 *                            after js-file processed and file name
 * pMoreProcessing_o Example: { Name:'1.js', Func: function(pFinalCode){} }
 * @pCache_b                - if true files do not writes on disk, just saves
 *                              in Minify Cache
 */
exports.jsScripts=function jsScripts(pJSFiles_a, pMoreProcessing_o, pCache_b){
    'use strict';
    /* подключаем модуль uglify-js
     * если его нет - дальнейшая
     * работа функции не имеет смысла
     */
    try{
        var jsp = require("uglify-js").parser;
        var pro = require("uglify-js").uglify;
    }catch(error){
        console.log('can\'n load uglify-js\n'                  +
            'to use js-minification you need to install uglify-js\n'    +
                'npm install uglify-js\n'                               +
                'https://github.com/mishoo/UglifyJS');
        return false;
    }
    /* Константы */
    var CLIENT_JS='client.js';
    var CLOUDFUNC_JS='lib/cloudfunc.js';

    /* if passed string, or object
     * putting it to array
     */
    if(!pJSFiles_a[0])
        pJSFiles_a=[pJSFiles_a];
    var dataReaded_f=function(pFileName, pData){
        /*
         * if postProcessing function exist
         * getting it from pFileName object
         */
        var lMoreProcessing_f;
        if(typeof pFileName === 'object'){
            var lName;
            for(lName in pFileName){
                break;
            }
            lMoreProcessing_f = pFileName[lName];
            pFileName = lName;
        }
        console.log('file ' + pFileName + ' readed');

        /*********************************/
        /* сжимаем код через uglify-js */
        var uglify_js=function(pDdata){
            var orig_code = pDdata.toString();
            var ast = jsp.parse(orig_code); // parse code and get the initial AST
            ast = pro.ast_mangle(ast); // get a new AST with mangled names
            ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
            var result_code = pro.gen_code(ast); // compressed code here
            return result_code;
        };
        /*********************************/
        var final_code=uglify_js(pData);

        var minFileName=pFileName.replace('.js','.min.js');

        /* если мы сжимаем client.js -
         * меняем строку cloudfunc.js на
         * cloudfunc.min.js и выводим сообщение
         *
         * меняем строку keyBinding.js на
         * keyBinding.min.js
         * если другой файл - ничего не деалем
         */

        /* if lMoreProcessing_f seeted up
         * and function associated with
         * current file name exists -
         * run it
         */
        if(lMoreProcessing_f                    &&
            typeof lMoreProcessing_f === "function"){
                final_code = lMoreProcessing_f(final_code);
        }

        minFileName = path.basename(minFileName);
        /* записываем сжатый js-скрипт
         * в кэш если установлен pCache_b
         * или на диск, если не установлен
         */
        if(pCache_b){
            exports.Cache[minFileName] = final_code;
            console.log('file ' + minFileName + ' saved to cache...');
        }
        else{
            /* minimized file will be in min file
             * if it's possible if not -
             * in root
             */
            minFileName = MinFolder + minFileName;

            fs.writeFile(minFileName, final_code, fileWrited(minFileName));
        }
    };

    /* moving thru all elements of js files array */
    for(var i=0; pJSFiles_a[i]; i++){
        /* if postProcessing function exist
         * getting file name and passet next
         */
        var lMoreProcessing_o = pJSFiles_a[i];
        var lName;
        if(typeof lMoreProcessing_o === 'object'){
            for(lName in lMoreProcessing_o){
            }
        }else lName = pJSFiles_a[i];
        console.log('reading file ' + lName + '...');

        fs.readFile(lName, fileReaded(pJSFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция сжимает css-стили
 * и сохраняет их с именем .min.css
 * @pCSSFiles_a           - масив имен css файлов или строка,
 *                          если имя одно
 * @pImgConvertToBase64_b - булевый признак,
 *                          который отвечает за то, что быконвертировать
 *                          картинки в base64 и поместить в выходной css файл
 */
exports.cssStyles=function cssStyles(pCSSFiles_a, pImgConvertToBase64_b){
    'use strict';

     /* connecting cleanCSS,
      * if we can't find it -
      * return false
      */
     var cleanCSS;
     try{
        cleanCSS = require('clean-css');
    }catch(error){
        console.log('can\'n load clean-css \n'                          +
            'to use css-minification you need to install clean-css \n'  +
                'npm install clean-css\n'                               +
                'https://github.com/GoalSmashers/clean-css');
        return false;
    }

    if(typeof pCSSFiles_a === "string")
        pCSSFiles_a=[pCSSFiles_a];
    /* Varible contains information
     * about readed css file
     */
    var lCSSFiles_doneCount=0;

    var lAllStyle='';

    var dataReaded_f=function(pFileName, pData){
        console.log('file ' + pFileName + ' readed');
        /*********************************/
        /* сжимаем код через clean-css */
        var clean_css=function(pData){
            /* Сохраняем весь стиль в одну переменную*/
            return cleanCSS.process(pData);
        };
        /*********************************/
        var final_code=clean_css(pData);

        lAllStyle+=final_code;

        var minFileName=pFileName.replace('.css','.min.css');

        ++lCSSFiles_doneCount;

        /* if all files writed we
         * save all minimized css
         * to one file all.min.css
         */
        if(pCSSFiles_a.length === lCSSFiles_doneCount){
            /* если включена конвертация картинок в base64
             * вызываем её
             */
            if(pImgConvertToBase64_b)
                base64_images(lAllStyle);
            else
                fs.writeFile(MinFolder + 'all.min.css', lAllStyle, fileWrited(MinFolder + 'all.min.css'));
        }
         /* в другом случае - записываем сжатый css файл*/
        else{
            minFileName = MinFolder + path.basename(minFileName);
            fs.writeFile(minFileName, final_code, fileWrited(minFileName));
        }
    };

   /* moving thru all elements of css files array */
    for(var i=0;pCSSFiles_a[i];i++){
        console.log('reading file ' + pCSSFiles_a[i]+'...');
        fs.readFile(pCSSFiles_a[i],fileReaded(pCSSFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция сжимает html файлы
 * и сохраняет их с именем .min.html
 * @pHTMLFiles_a - массим имен html
 * файлов, или строка если имя одно
 */
exports.html=function(pHTMLFiles_a){
    'use strict';

     /* connecting cleanCSS,
      * if we can't find it -
      * return false
      */
     var htmlMinifier;
     try{
        htmlMinifier = require('html-minifier');
    }catch(error){
        console.log('can\'n load html-minifier \n'                 +
            'to use html-minification you need to install html-minifier\n'  +
                'npm install html-minifier\n'                               +
                'https://github.com/kangax/html-minifier');
        return false;
    }

    if(typeof pHTMLFiles_a === "string")
        pHTMLFiles_a=[pHTMLFiles_a];

    var dataReaded_f=function(pFileName, pData){
        console.log('file ' + pFileName + ' readed');
        /*********************************/
        /* сжимаем код через clean-css */
        var html_minify=function(pData){
            /* Сохраняем весь стиль в одну переменную*/

            var lOptions={
                removeComments:                 true,
                removeCommentsFromCDATA:        true,
                removeCDATASectionsFromCDATA:   true,
                collapseWhitespace:             true,
                collapseBooleanAttributes:      true,
                removeAttributeQuotes:          true,
                removeRedundantAttributes:      true,
                useShortDoctype:                true,
                removeEmptyAttributes:          true,
                /* оставляем, поскольку у нас
                 * в элемент fm генерируеться
                 * таблица файлов
                 */
                removeEmptyElements:            false,
                removeOptionalTags:             true,
                removeScriptTypeAttributes:     true,
                removeStyleLinkTypeAttributes:  true
            };

            return htmlMinifier.minify(pData,lOptions);
        };
        /*********************************/
        var final_code=html_minify(pData);

        var minFileName=pFileName.replace('.html','.min.html');

         /* adding Min folder to file path */
         minFileName = MinFolder + minFileName;
         /* записываем сжатый html файл*/
        fs.writeFile(minFileName, final_code, fileWrited(minFileName));
    };

     /* moving thru all elements of css files array */
    for(var i=0;pHTMLFiles_a[i];i++){
        console.log('reading file ' + pHTMLFiles_a[i]+'...');
        fs.readFile(pHTMLFiles_a[i],fileReaded(pHTMLFiles_a[i],dataReaded_f));
    }

    return true;
};

/* функция переводит картинки в base64 и записывает в css-файл*/
function base64_images(pFileContent_s){
    'use strict';
     var b64img;
     try{
        b64img = require('css-b64-images');
    }catch(error){
        console.log('can\'n load clean-css \n'                 +
            'to use images to base64 convertation you need to install css-base64-images \n'  +
                'npm install -g css-b64-images\n'                               +
                'https://github.com/Filirom1/css-base64-images');
        return false;
    }
    b64img.fromString(pFileContent_s, '.','', function(err, css){
        console.log('images converted to base64 and saved in css file');
        fs.writeFile(MinFolder + 'all.min.css', css, fileWrited(MinFolder + 'all.min.css'));
    });
}

/* Функция создаёт асинхроную версию
 * для чтения файла
 * @pFileName - имя считываемого файла
 */
function fileReaded(pFileName,pCompressFunc){
    "use strict";
    return function(pError,pData){
        /* функция в которую мы попадаем,
         * если данные считались
         *
         * если ошибка - показываем её
         * иначе если переданная функция -
         * функция запускаем её
         */
        if(!pError)
            if (pCompressFunc && typeof pCompressFunc==="function")
                    pCompressFunc(pFileName,pData.toString());
        else console.log(pError);
    };
}

/*
 * Функция вызываеться после записи файла
 * и выводит ошибку или сообщает,
 * что файл успешно записан
 */
function fileWrited(pFileName){
    "use strict";
    return function(error){
        console.log(error?error:('file '+pFileName+' writed...'));
    };
}<|endoftext|>


<commit_before>import {Text, BlockQuote, OrderedList, BulletList, ListItem,
        HorizontalRule, Paragraph, Heading, CodeBlock, Image, HardBreak,
        EmMark, StrongMark, LinkMark, CodeMark} from "../model"
import {defineTarget} from "../format"

// :: (Node, ?Object) → string
// Serialize the content of the given node to [CommonMark](http://commonmark.org/).
//
// To define serialization behavior for your own [node
// types](#NodeType), give them a `serializeMarkDown` method. It will
// be called with a `MarkdownSerializer` and a `Node`, and should
// update the serializer's state to add the content of the node.
//
// [Mark types](#MarkType) can define `openMarkdown` and
// `closeMarkdown` properties, which provide the markup text that
// marked content should be wrapped in. They may hold either a string
// or a function from a `MarkdownSerializer` and a `Mark` to a string.
export function toMarkdown(doc, options) {
  let state = new MarkdownSerializer(options)
  state.renderContent(doc)
  return state.out
}

defineTarget("markdown", toMarkdown)

// ;; This is an object used to track state and expose
// methods related to markdown serialization. Instances are passed to
// node and mark serialization methods (see `toMarkdown`).
class MarkdownSerializer {
  constructor(options) {
    this.delim = this.out = ""
    this.closed = false
    this.inTightList = false
    // :: Object
    // The options passed to the serializer. The following are supported:
    //
    // **`hardBreak`**: ?string
    //   : Markdown to use for hard line breaks. Defaults to a backslash
    //     followed by a newline.
    this.options = Object.assign({ hardBreak: "\\\n" }, options)
  }

  flushClose(size) {
    if (this.closed) {
      if (!this.atBlank()) this.out += "\n"
      if (size == null) size = 2
      if (size > 1) {
        let delimMin = this.delim
        let trim = /\s+$/.exec(delimMin)
        if (trim) delimMin = delimMin.slice(0, delimMin.length - trim[0].length)
        for (let i = 1; i < size; i++)
          this.out += delimMin + "\n"
      }
      this.closed = false
    }
  }

  // :: (string, ?string, Node, ())
  // Render a block, prefixing each line with `delim`, and the first
  // line in `firstDelim`. `node` should be the node that is closed at
  // the end of the block, and `f` is a function that renders the
  // content of the block.
  wrapBlock(delim, firstDelim, node, f) {
    let old = this.delim
    this.write(firstDelim || delim)
    this.delim += delim
    f()
    this.delim = old
    this.closeBlock(node)
  }

  atBlank() {
    return /(^|\n)$/.test(this.out)
  }

  // :: ()
  // Ensure the current content ends with a newline.
  ensureNewLine() {
    if (!this.atBlank()) this.out += "\n"
  }

  // :: (?string)
  // Prepare the state for writing output (closing closed paragraphs,
  // adding delimiters, and so on), and then optionally add content
  // (unescaped) to the output.
  write(content) {
    this.flushClose()
    if (this.delim && this.atBlank())
      this.out += this.delim
    if (content) this.out += content
  }

  // :: (Node)
  // Close the block for the given node.
  closeBlock(node) {
    this.closed = node
  }

  // :: (string, ?bool)
  // Add the given text to the document. When escape is not `false`,
  // it will be escaped.
  text(text, escape) {
    let lines = text.split("\n")
    for (let i = 0; i < lines.length; i++) {
      var startOfLine = this.atBlank() || this.closed
      this.write()
      this.out += escape !== false ? this.esc(lines[i], startOfLine) : lines[i]
      if (i != lines.length - 1) this.out += "\n"
    }
  }

  // :: (Node)
  // Render the given node as a block.
  render(node) {
    node.type.serializeMarkdown(this, node)
  }

  // :: (Node)
  // Render the contents of `parent` as block nodes.
  renderContent(parent) {
    parent.forEach(child => this.render(child))
  }

  // :: (Node)
  // Render the contents of `parent` as inline content.
  renderInline(parent) {
    let active = []
    let progress = node => {
      let marks = node ? node.marks : []
      let code = marks.length && marks[marks.length - 1].type.isCode && marks[marks.length - 1]
      let len = marks.length - (code ? 1 : 0)

      // Try to reorder 'mixable' marks, such as em and strong, which
      // in Markdown may be opened and closed in different order, so
      // that order of the marks for the token matches the order in
      // active.
      outer: for (let i = 0; i < len; i++) {
        let mark = marks[i]
        if (!mark.type.markdownMixable) break
        for (let j = 0; j < active.length; j++) {
          let other = active[j]
          if (!other.type.markdownMixable) break
          if (mark.eq(other)) {
            if (i > j)
              marks = marks.slice(0, j).concat(mark).concat(marks.slice(j, i)).concat(marks.slice(i + 1, len))
            else if (j > i)
              marks = marks.slice(0, i).concat(marks.slice(i + 1, j)).concat(mark).concat(marks.slice(j, len))
            continue outer
          }
        }
      }

      // Find the prefix of the mark set that didn't change
      let keep = 0
      while (keep < Math.min(active.length, len) && marks[keep].eq(active[keep])) ++keep

      // Close the marks that need to be closed
      while (keep < active.length)
        this.text(this.markString(active.pop(), false), false)

      // Open the marks that need to be opened
      while (active.length < len) {
        let add = marks[active.length]
        active.push(add)
        this.text(this.markString(add, true), false)
      }

      // Render the node. Special case code marks, since their content
      // may not be escaped.
      if (node) {
        if (code && node.isText)
          this.text(this.markString(code, false) + node.text + this.markString(code, true), false)
        else
          this.render(node)
      }
    }
    parent.forEach(progress)
    progress(null)
  }

  renderList(node, delim, firstDelim) {
    if (this.closed && this.closed.type == node.type)
      this.flushClose(3)
    else if (this.inTightList)
      this.flushClose(1)

    let prevTight = this.inTightList
    this.inTightList = node.attrs.tight
    for (let i = 0; i < node.childCount; i++) {
      if (i && node.attrs.tight) this.flushClose(1)
      this.wrapBlock(delim, firstDelim(i), node, () => this.render(node.child(i)))
    }
    this.inTightList = prevTight
  }

  // :: (string, ?bool) → string
  // Escape the given string so that it can safely appear in Markdown
  // content. If `startOfLine` is true, also escape characters that
  // has special meaning only at the start of the line.
  esc(str, startOfLine) {
    str = str.replace(/[`*\\~+\[\]]/g, "\\$&")
    if (startOfLine) str = str.replace(/^[:#-]/, "\\$&")
    return str
  }

  quote(str) {
    var wrap = str.indexOf('"') == -1 ? '""' : str.indexOf("'") == -1 ? "''" : "()"
    return wrap[0] + str + wrap[1]
  }

  // :: (string, number) → string
  // Repeat the given string `n` times.
  repeat(str, n) {
    let out = ""
    for (let i = 0; i < n; i++) out += str
    return out
  }

  // : (Mark, bool) → string
  // Get the markdown string for a given opening or closing mark.
  markString(mark, open) {
    let value = open ? mark.type.openMarkdown : mark.type.closeMarkdown
    return typeof value == "string" ? value : value(this, mark)
  }
}

function def(cls, method) { cls.prototype.serializeMarkdown = method }

def(BlockQuote, (state, node) => {
  state.wrapBlock("> ", null, node, () => state.renderContent(node))
})

def(CodeBlock, (state, node) => {
  if (node.attrs.params == null) {
    state.wrapBlock("    ", null, node, () => state.text(node.textContent, false))
  } else {
    state.write("```" + node.attrs.params + "\n")
    state.text(node.textContent, false)
    state.ensureNewLine()
    state.write("```")
    state.closeBlock(node)
  }
})

def(Heading, (state, node) => {
  state.write(state.repeat("#", node.attrs.level) + " ")
  state.renderInline(node)
  state.closeBlock(node)
})

def(HorizontalRule, (state, node) => {
  state.write(node.attrs.markup || "---")
  state.closeBlock(node)
})

def(BulletList, (state, node) => {
  state.renderList(node, "  ", () => (node.attrs.bullet || "*") + " ")
})

def(OrderedList, (state, node) => {
  let start = node.attrs.order || 1
  let maxW = String(start + node.childCount - 1).length
  let space = state.repeat(" ", maxW + 2)
  state.renderList(node, space, i => {
    let nStr = String(start + i)
    return state.repeat(" ", maxW - nStr.length) + nStr + ". "
  })
})

def(ListItem, (state, node) => state.renderContent(node))

def(Paragraph, (state, node) => {
  state.renderInline(node)
  state.closeBlock(node)
})

// Inline nodes

def(Image, (state, node) => {
  state.write("![" + state.esc(node.attrs.alt || "") + "](" + state.esc(node.attrs.src) +
              (node.attrs.title ? " " + state.quote(node.attrs.title) : "") + ")")
})

def(HardBreak, state => state.write(state.options.hardBreak))

def(Text, (state, node) => state.text(node.text))

// Marks

EmMark.prototype.openMarkdown = EmMark.prototype.closeMarkdown = "*"
EmMark.prototype.markdownMixable = true

StrongMark.prototype.openMarkdown = StrongMark.prototype.closeMarkdown = "**"
StrongMark.prototype.markdownMixable = true

LinkMark.prototype.openMarkdown = "["
LinkMark.prototype.closeMarkdown = (state, mark) =>
  "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"

CodeMark.prototype.openMarkdown = CodeMark.prototype.closeMarkdown = "`"
<commit_msg>Adjust patch 9b2aad9f39<commit_after>import {Text, BlockQuote, OrderedList, BulletList, ListItem,
        HorizontalRule, Paragraph, Heading, CodeBlock, Image, HardBreak,
        EmMark, StrongMark, LinkMark, CodeMark} from "../model"
import {defineTarget} from "../format"

// :: (Node, ?Object) → string
// Serialize the content of the given node to [CommonMark](http://commonmark.org/).
//
// To define serialization behavior for your own [node
// types](#NodeType), give them a `serializeMarkDown` method. It will
// be called with a `MarkdownSerializer` and a `Node`, and should
// update the serializer's state to add the content of the node.
//
// [Mark types](#MarkType) can define `openMarkdown` and
// `closeMarkdown` properties, which provide the markup text that
// marked content should be wrapped in. They may hold either a string
// or a function from a `MarkdownSerializer` and a `Mark` to a string.
export function toMarkdown(doc, options) {
  let state = new MarkdownSerializer(options)
  state.renderContent(doc)
  return state.out
}

defineTarget("markdown", toMarkdown)

// ;; This is an object used to track state and expose
// methods related to markdown serialization. Instances are passed to
// node and mark serialization methods (see `toMarkdown`).
class MarkdownSerializer {
  constructor(options) {
    this.delim = this.out = ""
    this.closed = false
    this.inTightList = false
    // :: Object
    // The options passed to the serializer. The following are supported:
    //
    // **`hardBreak`**: ?string
    //   : Markdown to use for hard line breaks. Defaults to a backslash
    //     followed by a newline.
    this.options = options || {}
  }

  flushClose(size) {
    if (this.closed) {
      if (!this.atBlank()) this.out += "\n"
      if (size == null) size = 2
      if (size > 1) {
        let delimMin = this.delim
        let trim = /\s+$/.exec(delimMin)
        if (trim) delimMin = delimMin.slice(0, delimMin.length - trim[0].length)
        for (let i = 1; i < size; i++)
          this.out += delimMin + "\n"
      }
      this.closed = false
    }
  }

  // :: (string, ?string, Node, ())
  // Render a block, prefixing each line with `delim`, and the first
  // line in `firstDelim`. `node` should be the node that is closed at
  // the end of the block, and `f` is a function that renders the
  // content of the block.
  wrapBlock(delim, firstDelim, node, f) {
    let old = this.delim
    this.write(firstDelim || delim)
    this.delim += delim
    f()
    this.delim = old
    this.closeBlock(node)
  }

  atBlank() {
    return /(^|\n)$/.test(this.out)
  }

  // :: ()
  // Ensure the current content ends with a newline.
  ensureNewLine() {
    if (!this.atBlank()) this.out += "\n"
  }

  // :: (?string)
  // Prepare the state for writing output (closing closed paragraphs,
  // adding delimiters, and so on), and then optionally add content
  // (unescaped) to the output.
  write(content) {
    this.flushClose()
    if (this.delim && this.atBlank())
      this.out += this.delim
    if (content) this.out += content
  }

  // :: (Node)
  // Close the block for the given node.
  closeBlock(node) {
    this.closed = node
  }

  // :: (string, ?bool)
  // Add the given text to the document. When escape is not `false`,
  // it will be escaped.
  text(text, escape) {
    let lines = text.split("\n")
    for (let i = 0; i < lines.length; i++) {
      var startOfLine = this.atBlank() || this.closed
      this.write()
      this.out += escape !== false ? this.esc(lines[i], startOfLine) : lines[i]
      if (i != lines.length - 1) this.out += "\n"
    }
  }

  // :: (Node)
  // Render the given node as a block.
  render(node) {
    node.type.serializeMarkdown(this, node)
  }

  // :: (Node)
  // Render the contents of `parent` as block nodes.
  renderContent(parent) {
    parent.forEach(child => this.render(child))
  }

  // :: (Node)
  // Render the contents of `parent` as inline content.
  renderInline(parent) {
    let active = []
    let progress = node => {
      let marks = node ? node.marks : []
      let code = marks.length && marks[marks.length - 1].type.isCode && marks[marks.length - 1]
      let len = marks.length - (code ? 1 : 0)

      // Try to reorder 'mixable' marks, such as em and strong, which
      // in Markdown may be opened and closed in different order, so
      // that order of the marks for the token matches the order in
      // active.
      outer: for (let i = 0; i < len; i++) {
        let mark = marks[i]
        if (!mark.type.markdownMixable) break
        for (let j = 0; j < active.length; j++) {
          let other = active[j]
          if (!other.type.markdownMixable) break
          if (mark.eq(other)) {
            if (i > j)
              marks = marks.slice(0, j).concat(mark).concat(marks.slice(j, i)).concat(marks.slice(i + 1, len))
            else if (j > i)
              marks = marks.slice(0, i).concat(marks.slice(i + 1, j)).concat(mark).concat(marks.slice(j, len))
            continue outer
          }
        }
      }

      // Find the prefix of the mark set that didn't change
      let keep = 0
      while (keep < Math.min(active.length, len) && marks[keep].eq(active[keep])) ++keep

      // Close the marks that need to be closed
      while (keep < active.length)
        this.text(this.markString(active.pop(), false), false)

      // Open the marks that need to be opened
      while (active.length < len) {
        let add = marks[active.length]
        active.push(add)
        this.text(this.markString(add, true), false)
      }

      // Render the node. Special case code marks, since their content
      // may not be escaped.
      if (node) {
        if (code && node.isText)
          this.text(this.markString(code, false) + node.text + this.markString(code, true), false)
        else
          this.render(node)
      }
    }
    parent.forEach(progress)
    progress(null)
  }

  renderList(node, delim, firstDelim) {
    if (this.closed && this.closed.type == node.type)
      this.flushClose(3)
    else if (this.inTightList)
      this.flushClose(1)

    let prevTight = this.inTightList
    this.inTightList = node.attrs.tight
    for (let i = 0; i < node.childCount; i++) {
      if (i && node.attrs.tight) this.flushClose(1)
      this.wrapBlock(delim, firstDelim(i), node, () => this.render(node.child(i)))
    }
    this.inTightList = prevTight
  }

  // :: (string, ?bool) → string
  // Escape the given string so that it can safely appear in Markdown
  // content. If `startOfLine` is true, also escape characters that
  // has special meaning only at the start of the line.
  esc(str, startOfLine) {
    str = str.replace(/[`*\\~+\[\]]/g, "\\$&")
    if (startOfLine) str = str.replace(/^[:#-]/, "\\$&")
    return str
  }

  quote(str) {
    var wrap = str.indexOf('"') == -1 ? '""' : str.indexOf("'") == -1 ? "''" : "()"
    return wrap[0] + str + wrap[1]
  }

  // :: (string, number) → string
  // Repeat the given string `n` times.
  repeat(str, n) {
    let out = ""
    for (let i = 0; i < n; i++) out += str
    return out
  }

  // : (Mark, bool) → string
  // Get the markdown string for a given opening or closing mark.
  markString(mark, open) {
    let value = open ? mark.type.openMarkdown : mark.type.closeMarkdown
    return typeof value == "string" ? value : value(this, mark)
  }
}

function def(cls, method) { cls.prototype.serializeMarkdown = method }

def(BlockQuote, (state, node) => {
  state.wrapBlock("> ", null, node, () => state.renderContent(node))
})

def(CodeBlock, (state, node) => {
  if (node.attrs.params == null) {
    state.wrapBlock("    ", null, node, () => state.text(node.textContent, false))
  } else {
    state.write("```" + node.attrs.params + "\n")
    state.text(node.textContent, false)
    state.ensureNewLine()
    state.write("```")
    state.closeBlock(node)
  }
})

def(Heading, (state, node) => {
  state.write(state.repeat("#", node.attrs.level) + " ")
  state.renderInline(node)
  state.closeBlock(node)
})

def(HorizontalRule, (state, node) => {
  state.write(node.attrs.markup || "---")
  state.closeBlock(node)
})

def(BulletList, (state, node) => {
  state.renderList(node, "  ", () => (node.attrs.bullet || "*") + " ")
})

def(OrderedList, (state, node) => {
  let start = node.attrs.order || 1
  let maxW = String(start + node.childCount - 1).length
  let space = state.repeat(" ", maxW + 2)
  state.renderList(node, space, i => {
    let nStr = String(start + i)
    return state.repeat(" ", maxW - nStr.length) + nStr + ". "
  })
})

def(ListItem, (state, node) => state.renderContent(node))

def(Paragraph, (state, node) => {
  state.renderInline(node)
  state.closeBlock(node)
})

// Inline nodes

def(Image, (state, node) => {
  state.write("![" + state.esc(node.attrs.alt || "") + "](" + state.esc(node.attrs.src) +
              (node.attrs.title ? " " + state.quote(node.attrs.title) : "") + ")")
})

const defaultHardBreak = "\\\n"

def(HardBreak, state => state.write(state.options.hardBreak || defaultHardBreak))

def(Text, (state, node) => state.text(node.text))

// Marks

EmMark.prototype.openMarkdown = EmMark.prototype.closeMarkdown = "*"
EmMark.prototype.markdownMixable = true

StrongMark.prototype.openMarkdown = StrongMark.prototype.closeMarkdown = "**"
StrongMark.prototype.markdownMixable = true

LinkMark.prototype.openMarkdown = "["
LinkMark.prototype.closeMarkdown = (state, mark) =>
  "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"

CodeMark.prototype.openMarkdown = CodeMark.prototype.closeMarkdown = "`"
<|endoftext|>


<commit_before>/**
 * CodeMirrorWidget.js
 * Purpose:
 *
 * Description:
 *
 * History:
		Tue Nov 15 14:47:28     2015, Created by peter.liverovsky
 * Copyright (C) 2015 Sinnlabs LTD. All Rights Reserved.
 * This program is distributed under LGPL Version 2.1 in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY.
*/

zk.$package('org.sinnlabs.zk.ui');

(function () {

	function stopOnChanging_(wgt) {
		if (wgt._tChg) {
			clearTimeout(wgt._tChg);
			wgt._tChg = null;
		}
	}

	function startOnChanging_(wgt) {
		stopOnChanging_(wgt);
		wgt._tChg = setTimeout(wgt.proxy(onChanging_), zul.inp.InputWidget.onChangingDelay);
	}

	function onChanging_() {
		this.fireOnChange();
	}

	var SourceWidget =
		/**
		 *
The delay to send the onChanging event is controlled by
		 * {@link #onChangingDelay}, which is default to 350.
		 * To change it, you can specify the following in a ZUL file.
		 *

		   <?script content="zk.afterLoad('zul.inp',function(){zul.inp.InputWidget.onChangingDelay=1000;})"?>

		 */
		org.sinnlabs.zk.ui.CodeMirrorWidget = zk.$extends(zul.inp.Textbox, {

			_codemirror: null,

			_mode: "text/html",

			// For some mysterious reason initial value must not be true,
			// otherwise showing line numbers won't be runtime on/off switchable.
			_lineNumbers: false,

			$init: function () {
				this.$supers('$init', arguments);
				//zWatch.listen({onSize: [this, this.onSize_]});
			},

			bind_: function (dt, skipper, after) {
				this.$supers('bind_', arguments);
				var wgt = this;
				this._codemirror = CodeMirror.fromTextArea(this.$n('codemirror'), {
					lineNumbers: wgt._lineNumbers,
					mode: wgt._mode,
					matchBrackets: true
				});
				this._codemirror.on('blur', function () {
					var val = wgt._codemirror.getValue();
					wgt.setValue(val);
					if (wgt._tChg) {
						clearTimeout(wgt._tChg);
						wgt._tChg = null;
					}
					wgt.fireOnChange();
				});
				this._codemirror.on('keydown', function (evnt) {
					stopOnChanging_(wgt); //wait for key up
				});
				this._codemirror.on('keyup', function (evnt) {
					startOnChanging_(wgt);
				});
				this._multiline = true;
				zWatch.listen({onSize: this});
				this.refresh();
			},

			unbind_: function () {
				this.$supers('unbind_', arguments);
				zWatch.unlisten({onSize: this});
			},

			fireOnChange: function () {
				var val = this._codemirror.getValue();
				this.setValue(val);
				this.fire('onChange', {value: val}, {toServer: true});
			},

			onSize: function () {
				this.refresh();
			},

			setMode: function (val) {
				if (this._mode != val) {
					this._mode = val;
					if (this._codemirror) {
						this._codemirror.setOption("mode", this._mode);
					}
				}
			},

			getMode: function() {
				return this._mode;
			},

			setValue: function(txt) {
				this.$supers('setValue', arguments);
				if (this._codemirror) {
					this._codemirror.setValue(this.getValue());
				}
			},

			setLineNumbers: function (val) {
                if (this._lineNumbers != val) {
                    this._lineNumbers = val;
                    if (this._codemirror) {
                        this._codemirror.setOption("lineNumbers", this._lineNumbers);
                    }
                }
            },

            getLineNumbers: function() {
                return this._lineNumbers;
            },

			refresh: function() {
				$('.CodeMirror').each(function(i, el) {
				    el.CodeMirror.refresh();
				});
			}
		});
})();<commit_msg>Fix issue #4 https://github.com/sinnlabs/zk-codemirror/issues/4<commit_after>/**
 * CodeMirrorWidget.js
 * Purpose:
 *
 * Description:
 *
 * History:
		Tue Nov 15 14:47:28     2015, Created by peter.liverovsky
 * Copyright (C) 2015 Sinnlabs LTD. All Rights Reserved.
 * This program is distributed under LGPL Version 2.1 in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY.
*/

zk.$package('org.sinnlabs.zk.ui');

(function () {

	function stopOnChanging_(wgt) {
		if (wgt._tChg) {
			clearTimeout(wgt._tChg);
			wgt._tChg = null;
		}
	}

	function startOnChanging_(wgt) {
		stopOnChanging_(wgt);
		wgt._tChg = setTimeout(wgt.proxy(onChanging_), zul.inp.InputWidget.onChangingDelay);
	}

	function onChanging_() {
		this.fireOnChange();
	}

	var SourceWidget =
		/**
		 *
The delay to send the onChanging event is controlled by
		 * {@link #onChangingDelay}, which is default to 350.
		 * To change it, you can specify the following in a ZUL file.
		 *

		   <?script content="zk.afterLoad('zul.inp',function(){zul.inp.InputWidget.onChangingDelay=1000;})"?>

		 */
		org.sinnlabs.zk.ui.CodeMirrorWidget = zk.$extends(zul.inp.Textbox, {

			_codemirror: null,

			_mode: "text/html",

			// For some mysterious reason initial value must not be true,
			// otherwise showing line numbers won't be runtime on/off switchable.
			_lineNumbers: false,

			$init: function () {
				this.$supers('$init', arguments);
				//zWatch.listen({onSize: [this, this.onSize_]});
			},

			bind_: function (dt, skipper, after) {
				this.$supers('bind_', arguments);
				var wgt = this;
				this._codemirror = CodeMirror.fromTextArea(this.$n('codemirror'), {
					lineNumbers: wgt._lineNumbers,
					mode: wgt._mode,
					matchBrackets: true
				});
				this._codemirror.on('blur', function () {
					var val = wgt._codemirror.getValue();
					wgt.setValue(val);
					if (wgt._tChg) {
						clearTimeout(wgt._tChg);
						wgt._tChg = null;
					}
					wgt.fireOnChange();
				});
				this._codemirror.on('keydown', function (evnt) {
					stopOnChanging_(wgt); //wait for key up
				});
				this._codemirror.on('keyup', function (evnt) {
					startOnChanging_(wgt);
				});
				this._multiline = true;
				zWatch.listen({onSize: this});
				this.refresh();
			},

			unbind_: function () {
				this.$supers('unbind_', arguments);
				zWatch.unlisten({onSize: this});
			},

			fireOnChange: function () {
				var val = this._codemirror.getValue();
				this.setValue(val);
				this.fire('onChange', {value: val}, {toServer: true});
			},

			onSize: function () {
				this.refresh();
			},

			setMode: function (val) {
				if (this._mode != val) {
					this._mode = val;
					if (this._codemirror) {
						this._codemirror.setOption("mode", this._mode);
					}
				}
			},

			getMode: function() {
				return this._mode;
			},

			setValue: function(txt, fromserver) {
				this.$supers('setValue', arguments);
				if (this._codemirror) {
					if (fromserver) // Fix chrome browser bug
						this._codemirror.setValue(this.getValue());
				}
			},

			setLineNumbers: function (val) {
                if (this._lineNumbers != val) {
                    this._lineNumbers = val;
                    if (this._codemirror) {
                        this._codemirror.setOption("lineNumbers", this._lineNumbers);
                    }
                }
            },

            getLineNumbers: function() {
                return this._lineNumbers;
            },

			refresh: function() {
				$('.CodeMirror').each(function(i, el) {
				    el.CodeMirror.refresh();
				});
			}
		});
})();<|endoftext|>


<commit_before>export { ScaleArray };

import { ArrayInterleaver } from "./ArrayInterleaver.js";

/**
 * Describe an array of scale (i.e. multiplier) values.
 *
 * @member {number[]} scales
 *   The scale (i.e. multiplier) values.
 *
 * @member {number} length
 *   The length of this scale array. Setting it will change the length.
 */
class ScaleArray {

  constructor( length ) {
    this.scales = new Array( length );
  }

  get length() {
    return this.scales.length;
  }

  set length( newLength ) {
    this.scales.length = newLength;
  }

  /**
   * @return {ScaleArray} Return a newly created ScaleArray which is a copy of this ScaleArray.
   */
  clone() {
    let result = new ScaleArray( this.scales.length );
    result.set_all_byScaleArray( this );
    return result;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} N          Set ( this.scales[ thisIndex ] ) by ( N ). Default are ( N = 1 ) (i.e. no scale).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byN( thisIndex, N = 1 ) {
    this.scales[ thisIndex ] = N;
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number[]} Ns       Set ( this.scales[ thisIndex ] ) by ( Ns[ aIndex ] ).
   * @param {number} aIndex     The array index of Ns[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byNs( thisIndex, Ns, aIndex ) {
    return this.set_one_byN( thisIndex, Ns[ aIndex ] );
  }

  /**
   * Set this.scales[ thisIndex ] to a scale value which could let source bounds [ fromLower, fromUpper ] completely insides destination
   * bounds [ toLower, toUpper ].
   *
   * Note: this.scales[ thisIndex ] may become Number.NaN, if it is impossible to let source bounds completely insides destination
   * bounds.
   *
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} fromLower  The source bounds [ fromLower, fromUpper ]
   * @param {number} fromUpper  The source bounds [ fromLower, fromUpper ]
   * @param {number} toLower    The destination bounds [ toLower, toUpper ]
   * @param {number} toUpper    The destination bounds [ toLower, toUpper ]
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_by_fromLowerUpper_toLowerUpper( thisIndex, fromLower, fromUpper, toLower, toUpper ) {
    let aScale = ScaleArray.calc_scale_by_fromLowerUpper_toLowerUpper( fromLower, fromUpper, toLower, toUpper );
    this.scales[ thisIndex ] = aScale;
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} N          Set ( this.scales[ thisIndex ] ) by ( 1 / N ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_N( thisIndex, N ) {
    this.scales[ thisIndex ] = ( 1 / N );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number[]} Ns       Set ( this.scales[ thisIndex ] ) by ( 1 / Ns[ aIndex ] ).
   * @param {number} aIndex     The array index of Ns[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_Ns( thisIndex, Ns, aIndex ) {
    this.scales[ thisIndex ] = ( 1 / Ns[ aIndex ] );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    return this;
  }

  /**
   * @param {number} thisIndex        The array index of this.scales[].
   * @param {ScaleArray} aScaleArray  Set ( this.scales[ thisIndex ] ) by ( 1 / aScaleArray.scales[ aIndex ] ).
   * @param {number} aIndex           The array index of aScaleArray.scales[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_ScaleArray( thisIndex, aScaleArray, aIndex ) {
    return this.set_one_byUndo_Ns( thisIndex, aScaleArray.scales, aIndex );
  }

  /**
   * @param {number} N  Set all ( this.scales[] ) by ( N ). Default are ( N = 1 ) (i.e. no scale).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byN( N = 1 ) {
    this.scales.fill( N );
    return this;
  }

  /**
   * @param {number[]} Ns  Set all ( this.scales[] ) by ( Ns[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byNs( Ns ) {
    for ( let i = 0; i < this.scales.length; ++i ) {
      this.scales[ i ] = Ns[ i ];
    }
    return this;
  }

  /**
   * @param {ScaleArray} aScaleArray  Set all ( this.scales[] ) by ( aScaleArray.scales[] ).
   *
   * @return {ScaleArray} Return this (modified) object whose values are copied from aScaleArray.
   */
  set_all_byScaleArray( aScaleArray ) {
    return this.set_all_byNs( aScaleArray.scales );
  }

  /**
   * @param {number[]} Ns  Set all ( this.scales[] ) by ( 1 / Ns[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byUndo_Ns( Ns ) {
    for ( let i = 0; i < this.scales.length; ++i ) {
      this.scales[ i ] = ( 1 / Ns[ i ] );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    }
    return this;
  }

  /**
   * @param {ScaleArray} aScaleArray  Set all ( this.scales[] ) by ( 1 / aScaleArray.scales[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byUndo_ScaleArray( aScaleArray ) {
    return this.set_all_byUndo_Ns( aScaleArray.scales );
  }

  /**
   * Rearrange scales by interleaving as ( groupCount == 2 ). This element count must be even (i.e. divisible by 2).
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements temporarily. Provide this array could reduce memory re-allocation
   * and improve performance.
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  interleave_asGrouptTwo( arrayTemp ) {
    ArrayInterleaver.interleave_asGrouptTwo( this.scales, 0, this.scales.length, arrayTemp );
    return this;
  }

  /**
   *
   * @param {number} fromLower  The source bounds [ fromLower, fromUpper ]
   * @param {number} fromUpper  The source bounds [ fromLower, fromUpper ]
   * @param {number} toLower    The destination bounds [ toLower, toUpper ]
   * @param {number} toUpper    The destination bounds [ toLower, toUpper ]
   *
   * @return {number}
   *   Return a scale value which could let source bounds [ fromLower, fromUpper ] completely insides destination bounds [ toLower, toUpper ].
   * Return Number.NaN, if it is impossible to do that.
   */
  static calc_scale_by_fromLowerUpper_toLowerUpper( fromLower, fromUpper, toLower, toUpper ) {
    let srcLower = Math.min( fromLower, fromUpper ); // Confirm ( lower <= upper ).
    let srcUpper = Math.max( fromLower, fromUpper );
    let dstLower = Math.min( toLower, toUpper );
    let dstUpper = Math.max( toLower, toUpper );

    let scale;

    // 1. Try lower bound.
    //
    // Note:
    //  ( x / +Infinity ) == +0
    //  ( x / -Infinity ) == -0
    //  (  0 / x ) = 0
    //  ( -1 / 0 ) = -Infinity
    //  ( +1 / 0 ) = +Infinity
    //  (  0 / 0 ) = NaN
    //  ( +-Infinity / +-Infinity ) == NaN
    //
    scale = dstLower / srcLower;

    // 1.2 Verification.
    //   - If scale is zero or -Infinity or +Infinity or NaN, it is always failed.
    //   - If scaled source upper bound is out of destination range, it is also failed.
    let adjustedUpper = srcUpper * scale;
    if ( ( scale == 0 ) || ( !Number.isFinite( scale ) ) || ( adjustedUpper < dstLower ) || ( adjustedUpper > dstUpper ) ) {

      // 2. Try upperer bound, since it is failed to fit [ srcLower, srcUpper ] into [ dstLower, dstUpper ] by scale according to lower bound.
      scale = dstUpper / srcUpper;

      // 2.2 Verification. If scale is zero, it is always failed. Otherwise, check it by lower side.
      //   - If scale is zero or -Infinity or +Infinity or NaN, it is always failed.
      //   - If scaled source lower bound is out of destination range, it is also failed.
      let adjustedLower = srcLower * scale;
      if ( ( scale == 0 ) || ( !Number.isFinite( scale ) ) || ( adjustedLower < dstLower ) || ( adjustedLower > dstUpper ) ) {
        // 3. It is impossible to fit [ srcLower, srcUpper ] into [ dstLower, dstUpper ] only by scale because all cases are tried and failed.
        scale = Number.NaN;
      }
    }

    return scale;
  }

}

<commit_msg>Update ScaleArray.js<commit_after>export { ScaleArray };

import { ArrayInterleaver } from "./ArrayInterleaver.js";

/**
 * Describe an array of scale (i.e. multiplier) values.
 *
 * @member {number[]} scales
 *   The scale (i.e. multiplier) values.
 *
 * @member {number} length
 *   The length of this scale array. Setting it will change the length.
 */
class ScaleArray {

  constructor( length ) {
    this.scales = new Array( length );
  }

  get length() {
    return this.scales.length;
  }

  set length( newLength ) {
    this.scales.length = newLength;
  }

  /**
   * @return {ScaleArray} Return a newly created ScaleArray which is a copy of this ScaleArray.
   */
  clone() {
    let result = new ScaleArray( this.scales.length );
    result.set_all_byScaleArray( this );
    return result;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} N          Set ( this.scales[ thisIndex ] ) by ( N ). Default are ( N = 1 ) (i.e. no scale).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byN( thisIndex, N = 1 ) {
    this.scales[ thisIndex ] = N;
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number[]} Ns       Set ( this.scales[ thisIndex ] ) by ( Ns[ aIndex ] ).
   * @param {number} aIndex     The array index of Ns[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byNs( thisIndex, Ns, aIndex ) {
    return this.set_one_byN( thisIndex, Ns[ aIndex ] );
  }

  /**
   * @param {number} thisIndex        The array index of this.scales[].
   * @param {ScaleArray} aScaleArray  Set ( this.scales[ thisIndex ] ) by ( aScaleArray.scales[ aIndex ] ).
   * @param {number} aIndex           The array index of aScaleArray.scales[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byScaleArray( thisIndex, aScaleArray, aIndex ) {
    return this.set_one_byN( thisIndex, aScaleArray.scales[ aIndex ] );
  }

  /**
   * Set this.scales[ thisIndex ] to a scale value which could let source bounds [ fromLower, fromUpper ] completely insides destination
   * bounds [ toLower, toUpper ].
   *
   * Note: this.scales[ thisIndex ] may become Number.NaN, if it is impossible to let source bounds completely insides destination
   * bounds.
   *
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} fromLower  The source bounds [ fromLower, fromUpper ]
   * @param {number} fromUpper  The source bounds [ fromLower, fromUpper ]
   * @param {number} toLower    The destination bounds [ toLower, toUpper ]
   * @param {number} toUpper    The destination bounds [ toLower, toUpper ]
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_by_fromLowerUpper_toLowerUpper( thisIndex, fromLower, fromUpper, toLower, toUpper ) {
    let aScale = ScaleArray.calc_scale_by_fromLowerUpper_toLowerUpper( fromLower, fromUpper, toLower, toUpper );
    this.scales[ thisIndex ] = aScale;
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number} N          Set ( this.scales[ thisIndex ] ) by ( 1 / N ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_N( thisIndex, N ) {
    this.scales[ thisIndex ] = ( 1 / N );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    return this;
  }

  /**
   * @param {number} thisIndex  The array index of this.scales[].
   * @param {number[]} Ns       Set ( this.scales[ thisIndex ] ) by ( 1 / Ns[ aIndex ] ).
   * @param {number} aIndex     The array index of Ns[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_Ns( thisIndex, Ns, aIndex ) {
    this.scales[ thisIndex ] = ( 1 / Ns[ aIndex ] );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    return this;
  }

  /**
   * @param {number} thisIndex        The array index of this.scales[].
   * @param {ScaleArray} aScaleArray  Set ( this.scales[ thisIndex ] ) by ( 1 / aScaleArray.scales[ aIndex ] ).
   * @param {number} aIndex           The array index of aScaleArray.scales[].
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_one_byUndo_ScaleArray( thisIndex, aScaleArray, aIndex ) {
    return this.set_one_byUndo_Ns( thisIndex, aScaleArray.scales, aIndex );
  }

  /**
   * @param {number} N  Set all ( this.scales[] ) by ( N ). Default are ( N = 1 ) (i.e. no scale).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byN( N = 1 ) {
    this.scales.fill( N );
    return this;
  }

  /**
   * @param {number[]} Ns  Set all ( this.scales[] ) by ( Ns[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byNs( Ns ) {
    for ( let i = 0; i < this.scales.length; ++i ) {
      this.scales[ i ] = Ns[ i ];
    }
    return this;
  }

  /**
   * @param {ScaleArray} aScaleArray  Set all ( this.scales[] ) by ( aScaleArray.scales[] ).
   *
   * @return {ScaleArray} Return this (modified) object whose values are copied from aScaleArray.
   */
  set_all_byScaleArray( aScaleArray ) {
    return this.set_all_byNs( aScaleArray.scales );
  }

  /**
   * @param {number[]} Ns  Set all ( this.scales[] ) by ( 1 / Ns[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byUndo_Ns( Ns ) {
    for ( let i = 0; i < this.scales.length; ++i ) {
      this.scales[ i ] = ( 1 / Ns[ i ] );  // Reciprocal will undo the scale. (Note: Not work for zero.)
    }
    return this;
  }

  /**
   * @param {ScaleArray} aScaleArray  Set all ( this.scales[] ) by ( 1 / aScaleArray.scales[] ).
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byUndo_ScaleArray( aScaleArray ) {
    return this.set_all_byUndo_Ns( aScaleArray.scales );
  }

  /**
   * The this.length will be modified.
   *
   * @param {ScaleArray} inputScaleArray0  The ScaleArray of the 1st input.
   * @param {ScaleArray} inputScaleArray1  The BoundsArray of the 2nd input.
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  set_all_byScaleArray_concat_input0_input1( inputScaleArray0, inputScaleArray1 ) {

    let totalLength = ( inputScaleArray0?.length ?? 0 ) + ( inputScaleArray1?.length ?? 0 );
    this.length = totalLength;

    // Concat value scale array.
    let inChannel = 0;

    if ( inputScaleArray0 ) {
      for ( let inChannel0 = 0; inChannel0 < inputScaleArray0.length; ++inChannel0, ++inChannel ) {
        this.set_one_byScaleArray( inChannel, inputScaleArray0, inChannel0 );
      }
    }

    if ( inputScaleArray1 ) {
      for ( let inChannel1 = 0; inChannel1 < inputScaleArray1.length; ++inChannel1, ++inChannel ) {
        this.set_one_byScaleArray( inChannel, inputScaleArray1, inChannel1 );
      }
    }

    return this;
  }

//!!! ...unfinished... (2022/04/13) split

  /**
   * Rearrange scales by interleaving as ( groupCount == 2 ). This element count must be even (i.e. divisible by 2).
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements temporarily. Provide this array could reduce memory re-allocation
   * and improve performance.
   *
   * @return {ScaleArray} Return this (modified) object.
   */
  interleave_asGrouptTwo( arrayTemp ) {
    ArrayInterleaver.interleave_asGrouptTwo( this.scales, 0, this.scales.length, arrayTemp );
    return this;
  }

  /**
   *
   * @param {number} fromLower  The source bounds [ fromLower, fromUpper ]
   * @param {number} fromUpper  The source bounds [ fromLower, fromUpper ]
   * @param {number} toLower    The destination bounds [ toLower, toUpper ]
   * @param {number} toUpper    The destination bounds [ toLower, toUpper ]
   *
   * @return {number}
   *   Return a scale value which could let source bounds [ fromLower, fromUpper ] completely insides destination bounds [ toLower, toUpper ].
   * Return Number.NaN, if it is impossible to do that.
   */
  static calc_scale_by_fromLowerUpper_toLowerUpper( fromLower, fromUpper, toLower, toUpper ) {
    let srcLower = Math.min( fromLower, fromUpper ); // Confirm ( lower <= upper ).
    let srcUpper = Math.max( fromLower, fromUpper );
    let dstLower = Math.min( toLower, toUpper );
    let dstUpper = Math.max( toLower, toUpper );

    let scale;

    // 1. Try lower bound.
    //
    // Note:
    //  ( x / +Infinity ) == +0
    //  ( x / -Infinity ) == -0
    //  (  0 / x ) = 0
    //  ( -1 / 0 ) = -Infinity
    //  ( +1 / 0 ) = +Infinity
    //  (  0 / 0 ) = NaN
    //  ( +-Infinity / +-Infinity ) == NaN
    //
    scale = dstLower / srcLower;

    // 1.2 Verification.
    //   - If scale is zero or -Infinity or +Infinity or NaN, it is always failed.
    //   - If scaled source upper bound is out of destination range, it is also failed.
    let adjustedUpper = srcUpper * scale;
    if ( ( scale == 0 ) || ( !Number.isFinite( scale ) ) || ( adjustedUpper < dstLower ) || ( adjustedUpper > dstUpper ) ) {

      // 2. Try upperer bound, since it is failed to fit [ srcLower, srcUpper ] into [ dstLower, dstUpper ] by scale according to lower bound.
      scale = dstUpper / srcUpper;

      // 2.2 Verification. If scale is zero, it is always failed. Otherwise, check it by lower side.
      //   - If scale is zero or -Infinity or +Infinity or NaN, it is always failed.
      //   - If scaled source lower bound is out of destination range, it is also failed.
      let adjustedLower = srcLower * scale;
      if ( ( scale == 0 ) || ( !Number.isFinite( scale ) ) || ( adjustedLower < dstLower ) || ( adjustedLower > dstUpper ) ) {
        // 3. It is impossible to fit [ srcLower, srcUpper ] into [ dstLower, dstUpper ] only by scale because all cases are tried and failed.
        scale = Number.NaN;
      }
    }

    return scale;
  }

}

<|endoftext|>


<commit_before>(function(window, document, location) {
  'use strict';

  var HOST = location.host;
  var URI = location.href;
  var KEY_BIND = {
    r: 'reload',
    j: 'next',
    k: 'prev',
    p: 'pinned',
    o: 'open',
    f: 'focus',
    s: 'reblog'
  };

  function SiteScript() {
    this.ap = new AppendPage(this);
    this.form = new Form(this);
    this.ka = new KeyboardAction(this);
    this.add_event();
  }
  (function($) {
    var _page_title_node = document.querySelector('head title');

    Object.defineProperties($, {
      page_title: {
        get: function() {
          return _page_title_node.textContent;
        },
        set: function(title) {
          var text_node = document.createTextNode(title);
          _page_title_node.replaceChild(text_node, _page_title_node.firstChild);
          return title;
        }
      },

      message: {
        get: function() {
          return document.getElementById('message');
        },
        set: function(message) {
          var _message_node = this.message;
          if (!message) {
            if (_message_node)
              _message_node.parentNode.removeChild(_message_node);
            return null;
          }
          if (!_message_node)
            _message_node = this.create_message_node();
          if (message.nodeType) {
            _message_node.parentNode.replaceChild(message, _message_node);
          } else if (typeof message === 'string') {
            var text_node = document.createTextNode(message);
            if (_message_node.firstChild) {
              _message_node.replaceChild(text_node, _message_node.firstChild);
            } else {
              _message_node.appendChild(text_node);
            }
          }
          return _message_node;
        }
      }
    });

    $.add_event = function()  {
      window.addEventListener('keypress', this.onkeypress.bind(this), false);
      window.addEventListener('popstate', this.onpopstate.bind(this), false);
    };

    $.onkeypress = function(event) {
      var target = event.target;
      var press_key = String.fromCharCode(event.keyCode || event.charCode);
      var node_name = target.nodeName.toLowerCase();
      var ignore_nodes = ['input', 'textarea'];
      var keys = Object.keys(KEY_BIND);
      if (ignore_nodes.indexOf(node_name) >= 0 || keys.indexOf(press_key) < 0)
        return;
      event.preventDefault();
      this.ka[KEY_BIND[press_key]]();
    };

    $.onpopstate = function(event) {
      var state = event.state;
      if (!state || this.ap.uri === state.uri)
        return;
      window.scroll(0, 0);
      this.ap.refresh();
      this.ap.uri = URI = state.uri;
      this.ap.request();
      if (this.form.text_field.value !== this.ap.username)
        this.form.text_field.value = this.ap.username;
    };

    $.create_message_node = function() {
      var _message_node = document.createElement('p');
      var selector = [
        'article:last-of-type',
        'body > h1'
      ].join(', ');
      var point = document.querySelector(selector);
      _message_node.setAttribute('id', 'message');
      point.insertAdjacentElement('afterEnd', _message_node);
      return _message_node;
    };
  })(SiteScript.prototype);

  function Form(ss) {
    this.text_field = document.getElementById('tumblr_username');
    this.ap = ss.ap;
    this.init();
  }

  (function($) {
    $.init = function() {
      this.add_event();
    };

    $.add_event = function() {
      window.addEventListener('scroll', this.onscroll.bind(this), false);
      this.text_field.addEventListener('focus', this.onfocus, false);
      this.text_field.form.addEventListener('submit', this.onsubmit.bind(this), false);
      this.onscroll();
    };

    $.onscroll = function() {
      if (this.ap.username !== '_dashboard')
        return;
      var current = this.ap.current_article();
      if (!current)
        return;
      var blog_name = current.querySelector('.blog_name').textContent;
      this.text_field.value = blog_name;
    };

    $.onsubmit = function(event) {
      event.preventDefault();
      if (!this.text_field.value)
        return false;
      this.text_field.blur();
      this.ap.refresh();
      this.ap.username = this.text_field.value;
      this.ap.page = 1;
      this.ap.request();
      return false;
    };

    $.onfocus = function() {
      if (!this.value.length)
        return;
      this.select();
    };
  })(Form.prototype);

  function KeyboardAction(ss) {
    this.ap = ss.ap;
    this.form = ss.form;
  }

  (function($) {
    $.reload = function() {
      this.ap.refresh();
      this.ap.request();
    };

    $.next = function() {
      var current = this.ap.current_article();
      this.ap.next(current);
    };

    $.prev = function() {
      var current = this.ap.current_article();
      this.ap.prev(current);
    };

    $.pinned = function() {
      var current = this.ap.current_article();
      this.ap.set_pin(current);
    };

    $.open = function() {
      var current = this.ap.current_article();
      var pinned = document.getElementsByClassName('pinned');
      var articles = toArray(pinned);
      if (!articles.length) {
        this.ap.open(current);
        return;
      }
      articles.forEach(function(article) {
        this.ap.open(article);
        this.ap.set_pin(article);
      }, this);
    };

    $.focus = function() {
      this.form.text_field.focus();
    };

    $.reblog = function() {
      var current = this.ap.current_article();
      this.ap.reblog(current);
    };
  })(KeyboardAction.prototype);

  function AppendPage(ss) {
    this.ss = ss;
    this.uri = URI;
    this.init();
  }

  (function($) {
    var _expr = /(?:(?:http:\/\/[^/]+)?\/([^\?]+)?(?:\?page=(\d+))?)?/;

    Object.defineProperties($, {
      username: {
        get: function() {
          return _expr.exec(this.uri)[1];
        },
        set: function(username) {
          this.uri = this.get_page_uri({username: username});
          return username;
        }
      },
      page: {
        get: function() {
          return (_expr.exec(this.uri)[2] || 1) * 1;
        },
        set: function(page) {
          this.uri = this.get_page_uri({page: page});
          return page;
        }
      },
      articles: {
        get: function() {
          var articles = document.querySelectorAll('body > article');
          return toArray(articles);
        }
      }
    });

    $.init = function() {
      if (this.username)
        this.add_event();
    };

    $.add_event = function() {
      var self = this;
      window.addEventListener('scroll', function onscroll() {
        self.window_scroll(function() {
          window.removeEventListener('scroll', onscroll);
        });
      }, false);
    };

    $.window_scroll = function(remove_event) {
      var articles = this.articles;
      var len = articles.length;
      if (len === 0) {
        remove_event();
        return;
      }
      if (window.scrollY < articles[len-2].offsetTop)
        return;
      remove_event();
      this.load_next();
    };

    $.load_next = function() {
      if (!this.ss.message)
        return;
      var anchor = this.ss.message.querySelector('a:only-child');
      if (!anchor)
        return;
      this.uri = anchor.getAttribute('href');
      this.request();
    };

    $.request = function() {
      var self = this;
      if (this.uri !== URI) {
        URI = this.uri;
        window.history.pushState({uri: this.uri}, this.ss.page_title, this.uri);
      }
      this.ss.message = 'loading...';
      var req = new XMLHttpRequest();
      req.addEventListener('readystatechange', function() {
        if (req.readyState !== 4)
          return;
        var res = req.responseXML;
        self.append(res);
        self.add_event();
      }, false);
      req.open('GET', this.uri);
      req.send(null);
    };

    $.append = function(res) {
      var df = res.createDocumentFragment();
      var articles = res.querySelectorAll('body > article, #message');
      toArray(articles).forEach(df.appendChild, df);
      this.ss.message = document.importNode(df, true);
      this.ss.page_title = res.querySelector('head title').textContent;
    };

    $.get_page_uri = function(options) {
      return this.uri.replace(_expr, function(_, username, page) {
        if (options.username)
          username = options.username;
        if (options.page)
          page = options.page;
        var path = '/' + username;
        if (page > 1)
          path += '?page=' + page;
        return 'http://' + HOST + path;
      });
    };

    $.refresh = function() {
      var range = document.createRange();
      var articles = this.articles;
      var len = articles.length;
      if (!len)
        return;
      range.setStartBefore(articles[0]);
      range.setEndAfter(articles[len-1]);
      range.deleteContents();
      this.ss.message = null;
    };

    $.open = function(article) {
      var anchor = article.querySelector('a.uri');
      var event = document.createEvent('MouseEvent');
      event.initMouseEvent('click', true, true, window,
        0, 0, 0, 0, 0, false, false, false, false, 1, null);
      anchor.dispatchEvent(event);
    };

    $.set_pin = function(article) {
      article.classList.toggle('pinned');
    };

    $.reblog = function(article) {
      var form = article.querySelector('form');
      if (!form)
        return;
      var uri = form.getAttribute('action');
      var req = new XMLHttpRequest();
      var form_data = new FormData(form);
      req.addEventListener('readystatechange', function() {
        if (req.readyState !== 4 || req.status !== 200)
          return;
        var res = JSON.parse(req.responseText);
        switch (res.meta.status) {
          case 201:
            article.classList.add('reblogged');
            break;
          default:
            var errors = res.response.errors || [];
            alert(errors[0]);
            break;
        }
      });
      req.open('POST', uri);
      req.send(form_data);
    };

    $.current_article = function() {
      var pos = window.scrollY;
      var articles = this.articles;
      if (!articles.length)
        return null;
      return articles.reduceRight(function(prev, current) {
        return prev.offsetTop - pos <= 0 ?
          prev : current;
      });
    };

    $.go = function(article, num) {
      var articles = this.articles;
      var positions = articles.map(function(article) {
        return article.offsetTop;
      });
      var len = articles.length;
      var i = positions.indexOf(article.offsetTop);
      i = i + num;
      if (i < 0 || i > len - 1)
        return -1;
      window.scroll(0, positions[i]);
      return articles[i];
    };

    $.prev = function(article) {
      return this.go(article, -1);
    };

    $.next = function(article) {
      return this.go(article, +1);
    };
  })(AppendPage.prototype);

  document.addEventListener('DOMContentLoaded', function() {
    window.ss = new SiteScript();
  }, false);

  function toArray(obj) {
    return Array.prototype.slice.apply(obj);
  }
})(window, window.document, window.location);
<commit_msg>XMLHttpRequestを呼び出す一つの函数を作成。<commit_after>(function(window, document, location) {
  'use strict';

  var HOST = location.host;
  var URI = location.href;
  var KEY_BIND = {
    r: 'reload',
    j: 'next',
    k: 'prev',
    p: 'pinned',
    o: 'open',
    f: 'focus',
    s: 'reblog'
  };

  function SiteScript() {
    this.ap = new AppendPage(this);
    this.form = new Form(this);
    this.ka = new KeyboardAction(this);
    this.add_event();
  }
  (function($) {
    var _page_title_node = document.querySelector('head title');

    Object.defineProperties($, {
      page_title: {
        get: function() {
          return _page_title_node.textContent;
        },
        set: function(title) {
          var text_node = document.createTextNode(title);
          _page_title_node.replaceChild(text_node, _page_title_node.firstChild);
          return title;
        }
      },

      message: {
        get: function() {
          return document.getElementById('message');
        },
        set: function(message) {
          var _message_node = this.message;
          if (!message) {
            if (_message_node)
              _message_node.parentNode.removeChild(_message_node);
            return null;
          }
          if (!_message_node)
            _message_node = this.create_message_node();
          if (message.nodeType) {
            _message_node.parentNode.replaceChild(message, _message_node);
          } else if (typeof message === 'string') {
            var text_node = document.createTextNode(message);
            if (_message_node.firstChild) {
              _message_node.replaceChild(text_node, _message_node.firstChild);
            } else {
              _message_node.appendChild(text_node);
            }
          }
          return _message_node;
        }
      }
    });

    $.add_event = function()  {
      window.addEventListener('keypress', this.onkeypress.bind(this), false);
      window.addEventListener('popstate', this.onpopstate.bind(this), false);
    };

    $.onkeypress = function(event) {
      var target = event.target;
      var press_key = String.fromCharCode(event.keyCode || event.charCode);
      var node_name = target.nodeName.toLowerCase();
      var ignore_nodes = ['input', 'textarea'];
      var keys = Object.keys(KEY_BIND);
      if (ignore_nodes.indexOf(node_name) >= 0 || keys.indexOf(press_key) < 0)
        return;
      event.preventDefault();
      this.ka[KEY_BIND[press_key]]();
    };

    $.onpopstate = function(event) {
      var state = event.state;
      if (!state || this.ap.uri === state.uri)
        return;
      window.scroll(0, 0);
      this.ap.refresh();
      this.ap.uri = URI = state.uri;
      this.ap.request();
      if (this.form.text_field.value !== this.ap.username)
        this.form.text_field.value = this.ap.username;
    };

    $.create_message_node = function() {
      var _message_node = document.createElement('p');
      var selector = [
        'article:last-of-type',
        'body > h1'
      ].join(', ');
      var point = document.querySelector(selector);
      _message_node.setAttribute('id', 'message');
      point.insertAdjacentElement('afterEnd', _message_node);
      return _message_node;
    };
  })(SiteScript.prototype);

  function Form(ss) {
    this.text_field = document.getElementById('tumblr_username');
    this.ap = ss.ap;
    this.init();
  }

  (function($) {
    $.init = function() {
      this.add_event();
    };

    $.add_event = function() {
      window.addEventListener('scroll', this.onscroll.bind(this), false);
      this.text_field.addEventListener('focus', this.onfocus, false);
      this.text_field.form.addEventListener('submit', this.onsubmit.bind(this), false);
      this.onscroll();
    };

    $.onscroll = function() {
      if (this.ap.username !== '_dashboard')
        return;
      var current = this.ap.current_article();
      if (!current)
        return;
      var blog_name = current.querySelector('.blog_name').textContent;
      this.text_field.value = blog_name;
    };

    $.onsubmit = function(event) {
      event.preventDefault();
      if (!this.text_field.value)
        return false;
      this.text_field.blur();
      this.ap.refresh();
      this.ap.username = this.text_field.value;
      this.ap.page = 1;
      this.ap.request();
      return false;
    };

    $.onfocus = function() {
      if (!this.value.length)
        return;
      this.select();
    };
  })(Form.prototype);

  function KeyboardAction(ss) {
    this.ap = ss.ap;
    this.form = ss.form;
  }

  (function($) {
    $.reload = function() {
      this.ap.refresh();
      this.ap.request();
    };

    $.next = function() {
      var current = this.ap.current_article();
      this.ap.next(current);
    };

    $.prev = function() {
      var current = this.ap.current_article();
      this.ap.prev(current);
    };

    $.pinned = function() {
      var current = this.ap.current_article();
      this.ap.set_pin(current);
    };

    $.open = function() {
      var current = this.ap.current_article();
      var pinned = document.getElementsByClassName('pinned');
      var articles = toArray(pinned);
      if (!articles.length) {
        this.ap.open(current);
        return;
      }
      articles.forEach(function(article) {
        this.ap.open(article);
        this.ap.set_pin(article);
      }, this);
    };

    $.focus = function() {
      this.form.text_field.focus();
    };

    $.reblog = function() {
      var current = this.ap.current_article();
      this.ap.reblog(current);
    };
  })(KeyboardAction.prototype);

  function AppendPage(ss) {
    this.ss = ss;
    this.uri = URI;
    this.init();
  }

  (function($) {
    var _expr = /(?:(?:http:\/\/[^/]+)?\/([^\?]+)?(?:\?page=(\d+))?)?/;

    Object.defineProperties($, {
      username: {
        get: function() {
          return _expr.exec(this.uri)[1];
        },
        set: function(username) {
          this.uri = this.get_page_uri({username: username});
          return username;
        }
      },
      page: {
        get: function() {
          return (_expr.exec(this.uri)[2] || 1) * 1;
        },
        set: function(page) {
          this.uri = this.get_page_uri({page: page});
          return page;
        }
      },
      articles: {
        get: function() {
          var articles = document.querySelectorAll('body > article');
          return toArray(articles);
        }
      }
    });

    $.init = function() {
      if (this.username)
        this.add_event();
    };

    $.add_event = function() {
      var self = this;
      window.addEventListener('scroll', function onscroll() {
        self.window_scroll(function() {
          window.removeEventListener('scroll', onscroll);
        });
      }, false);
    };

    $.window_scroll = function(remove_event) {
      var articles = this.articles;
      var len = articles.length;
      if (len === 0) {
        remove_event();
        return;
      }
      if (window.scrollY < articles[len-2].offsetTop)
        return;
      remove_event();
      this.load_next();
    };

    $.load_next = function() {
      if (!this.ss.message)
        return;
      var anchor = this.ss.message.querySelector('a:only-child');
      if (!anchor)
        return;
      this.uri = anchor.getAttribute('href');
      this.request();
    };

    $.request = function() {
      var self = this;
      if (this.uri !== URI) {
        URI = this.uri;
        window.history.pushState({uri: this.uri}, this.ss.page_title, this.uri);
      }
      this.ss.message = 'loading...';
      xhr({uri: this.uri}, function(res) {
        self.append(res);
        self.add_event();
      });
    };

    $.append = function(res) {
      var df = res.createDocumentFragment();
      var articles = res.querySelectorAll('body > article, #message');
      toArray(articles).forEach(df.appendChild, df);
      this.ss.message = document.importNode(df, true);
      this.ss.page_title = res.querySelector('head title').textContent;
    };

    $.get_page_uri = function(options) {
      return this.uri.replace(_expr, function(_, username, page) {
        if (options.username)
          username = options.username;
        if (options.page)
          page = options.page;
        var path = '/' + username;
        if (page > 1)
          path += '?page=' + page;
        return 'http://' + HOST + path;
      });
    };

    $.refresh = function() {
      var range = document.createRange();
      var articles = this.articles;
      var len = articles.length;
      if (!len)
        return;
      range.setStartBefore(articles[0]);
      range.setEndAfter(articles[len-1]);
      range.deleteContents();
      this.ss.message = null;
    };

    $.open = function(article) {
      var anchor = article.querySelector('a.uri');
      var event = document.createEvent('MouseEvent');
      event.initMouseEvent('click', true, true, window,
        0, 0, 0, 0, 0, false, false, false, false, 1, null);
      anchor.dispatchEvent(event);
    };

    $.set_pin = function(article) {
      article.classList.toggle('pinned');
    };

    $.reblog = function(article) {
      var form = article.querySelector('form');
      if (!form)
        return;
      var options = {
        method: 'POST',
        uri: form.getAttribute('action'),
        form_data: new FormData(form)
      };
      xhr(options, function(res) {
        switch (res.meta.status) {
          case 201:
            article.classList.add('reblogged');
            break;
          default:
            var errors = res.response.errors || [];
            alert(errors[0]);
            break;
        }
      });
    };

    $.current_article = function() {
      var pos = window.scrollY;
      var articles = this.articles;
      if (!articles.length)
        return null;
      return articles.reduceRight(function(prev, current) {
        return prev.offsetTop - pos <= 0 ?
          prev : current;
      });
    };

    $.go = function(article, num) {
      var articles = this.articles;
      var positions = articles.map(function(article) {
        return article.offsetTop;
      });
      var len = articles.length;
      var i = positions.indexOf(article.offsetTop);
      i = i + num;
      if (i < 0 || i > len - 1)
        return -1;
      window.scroll(0, positions[i]);
      return articles[i];
    };

    $.prev = function(article) {
      return this.go(article, -1);
    };

    $.next = function(article) {
      return this.go(article, +1);
    };
  })(AppendPage.prototype);

  document.addEventListener('DOMContentLoaded', function() {
    window.ss = new SiteScript();
  }, false);

  function toArray(obj) {
    return Array.prototype.slice.apply(obj);
  }

  function xhr(options, callback) {
    var req = new XMLHttpRequest();
    req.addEventListener('readystatechange', function() {
      if (req.readyState !== 4)
        return;
      var res = req.responseXML || JSON.parse(req.responseText);
      callback(res);
    }, false);
    req.open(options.method || 'GET', options.uri);
    req.send(options.form_data || null);
    return req;
  }
})(window, window.document, window.location);
<|endoftext|>


<commit_before><commit_msg>chore(version-info): previousVersions should not return `undefined`<commit_after><|endoftext|>


<commit_before>module.exports = (conf, bloggify, ready) => {
    console.log("Loaded plugin a", conf);
    setTimeout(() => {
        ready();
    });
};
<commit_msg>Add page<commit_after>module.exports = (conf, bloggify, ready) => {
    bloggify.server.page.add("/", lien => {
        lien.res.write("hi");
        setTimeout(function () {
            lien.res.write("hi");
            setTimeout(function () {
                lien.res.write("hi");
                setTimeout(function () {
                    lien.res.end("hi");

                }, 1000);

            }, 1000);
        }, 1000);
        //lien.end({
        //    hello: "world"
        //})
    });
    console.log("Loaded plugin a", conf);
    setTimeout(() => {
        ready();
    });
};
<|endoftext|>


<commit_before>const createLayoutHelper = require('./lib/layouts')

exports.up = async function (db) {
  const { addComponents } = createLayoutHelper(db)

  await addComponents('home', [{
    component: 'SearchBox',
    x: 0,
    y: 0,
    w: 16,
    textAlign: 'center',
    colour: 'blue',
    meta: {
      name: 'Your Network Here',
      iconSrc: '/images/banmanager-icon.png',
      showPlayerSearch: true,
      showIpSearch: true
    }
  }])

  const [{ id }] = await db.runSql('SELECT id FROM bm_web_servers LIMIT 1')

  if (!id) return

  await addComponents('home', [{ component: 'ServerNameHeader', x: 0, y: 1, w: 16, meta: { serverId: id, as: 'h2' } }])
  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 4, meta: { serverId: id, type: 'bans' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 4, y: 2, w: 4, meta: { serverId: id, type: 'mutes' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 8, y: 2, w: 4, meta: { serverId: id, type: 'reports' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 12, y: 2, w: 4, meta: { serverId: id, type: 'warnings' }, device: 'desktop' }
  ])

  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 4, meta: { serverId: id, type: 'bans' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 4, y: 2, w: 4, meta: { serverId: id, type: 'mutes' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 8, y: 2, w: 4, meta: { serverId: id, type: 'reports' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 12, y: 2, w: 4, meta: { serverId: id, type: 'warnings' }, device: 'tablet' }
  ])

  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 16, meta: { serverId: id, type: 'bans' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 3, w: 16, meta: { serverId: id, type: 'mutes' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 4, w: 16, meta: { serverId: id, type: 'reports' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 5, w: 16, meta: { serverId: id, type: 'warnings' }, device: 'mobile' }
  ])
}

exports.down = async function (db) {
  await db.runSql('DELETE FROM bm_web_page_layouts WHERE pathname = \'home\'')
}

exports._meta = {
  version: 1
}
<commit_msg>fix: migration server id check<commit_after>const createLayoutHelper = require('./lib/layouts')

exports.up = async function (db) {
  const { addComponents } = createLayoutHelper(db)

  await addComponents('home', [{
    component: 'SearchBox',
    x: 0,
    y: 0,
    w: 16,
    textAlign: 'center',
    colour: 'blue',
    meta: {
      name: 'Your Network Here',
      iconSrc: '/images/banmanager-icon.png',
      showPlayerSearch: true,
      showIpSearch: true
    }
  }])

  const [result] = await db.runSql('SELECT id FROM bm_web_servers LIMIT 1')

  if (!result || !result.id) return

  const id = result.id

  await addComponents('home', [{ component: 'ServerNameHeader', x: 0, y: 1, w: 16, meta: { serverId: id, as: 'h2' } }])
  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 4, meta: { serverId: id, type: 'bans' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 4, y: 2, w: 4, meta: { serverId: id, type: 'mutes' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 8, y: 2, w: 4, meta: { serverId: id, type: 'reports' }, device: 'desktop' },
    { component: 'RecentServerPunishments', x: 12, y: 2, w: 4, meta: { serverId: id, type: 'warnings' }, device: 'desktop' }
  ])

  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 4, meta: { serverId: id, type: 'bans' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 4, y: 2, w: 4, meta: { serverId: id, type: 'mutes' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 8, y: 2, w: 4, meta: { serverId: id, type: 'reports' }, device: 'tablet' },
    { component: 'RecentServerPunishments', x: 12, y: 2, w: 4, meta: { serverId: id, type: 'warnings' }, device: 'tablet' }
  ])

  await addComponents('home', [
    { component: 'RecentServerPunishments', x: 0, y: 2, w: 16, meta: { serverId: id, type: 'bans' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 3, w: 16, meta: { serverId: id, type: 'mutes' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 4, w: 16, meta: { serverId: id, type: 'reports' }, device: 'mobile' },
    { component: 'RecentServerPunishments', x: 0, y: 5, w: 16, meta: { serverId: id, type: 'warnings' }, device: 'mobile' }
  ])
}

exports.down = async function (db) {
  await db.runSql('DELETE FROM bm_web_page_layouts WHERE pathname = \'home\'')
}

exports._meta = {
  version: 1
}
<|endoftext|>