<commit_before>'use strict';
import marked from "marked";
import Base from './base.js';
import request from 'request';
import markToc from "marked-toc";
import {PasswordHash} from 'phpass';
import highlight from 'highlight.js';

request.defaults({
  strictSSL: false,
  rejectUnauthorized: false
});

export default class extends Base {
  modelInstance = this.modelInstance.where({type: 0});
  /**
   * get
   * @return {[type]} [description]
   */
  async getAction(self){
    // this.modelInstance.field('id,user_id,type,status,title,pathname,create_time,update_time');
    let data;
    if( this.id ) {
      if( this.id === 'lastest' ) {
        return this.lastest();
      }
      data = await this.modelInstance.where({id: this.id}).find();
      //文章选项
      if(data.options){
        data.options = JSON.parse(data.options) || {};
      }else{
        data.options = {};
      }
    } else {
      let where = {};
      //不是管理员，只显示个人的文章
      if(this.userInfo.type !== 1){
        where.user_id = this.userInfo.id;
      }

      if(this.get('status')) {
        where.status = this.get('status');
      }

      if(this.get('keyword')) {
        let keywords = this.get('keyword').split(/\s+/g);
        if( keywords.indexOf(':public') > -1 || keywords.indexOf(':private') > -1 ) {
          where.is_public = Number(keywords.indexOf(':public') > -1);
          keywords = keywords.filter(word => word !== ':public' && word !== ':private');
        }
        if(keywords.length > 0) {
          where.title = ["like", keywords.map(word => `%${word}%`)];
        }
      }

      let field = ['id', 'title', 'user_id', 'create_time', 'update_time', 'status', 'pathname'];
      data = await this.modelInstance.where(where).field(field).order('id DESC').page( this.get('page'), 15 ).countSelect();
    }
    return this.success(data);
  }

  getBaseAction(self) {
    return super.getAction(self);
  }
  /**
   * add user
   * @return {[type]} [description]
   */
  async postAction(){
    let data = this.post();
    //check pathname
    let post = await this.modelInstance.where({pathname: data.pathname}).select();
    if( post.length > 0 ) {
      return this.fail('PATHNAME_EXIST');
    }

    /** 如果是编辑发布文章的话默认状态改为审核中 **/
    if( data.status == 3 && this.userInfo.type != 1 ) {
      data.status = 1;
    }

    /** 推送文章 **/
    this.pushPost(data);

    data.tag = await this.getTagIds(data.tag);
    data = this.getContentAndSummary(data);
    data.user_id = this.userInfo.id;
    data = this.getPostTime(data);
    data.options = data.options ? JSON.stringify(data.options) : '';

    let insertId = await this.modelInstance.addPost(data);
    return this.success({id: insertId});
  }
  /**
   * update user info
   * @return {[type]} [description]
   */
  async putAction(){
    if (!this.id) {
      return this.fail('PARAMS_ERROR');
    }
    let data = this.post();

    /** 推送文章 **/
    this.pushPost(data);

    data.id = this.id;
    data = this.getPostTime(data);
    data = this.getContentAndSummary(data);
    data.options = data.options ? JSON.stringify(data.options) : '';
    if(data.tag) {
      data.tag = await this.getTagIds(data.tag);
    }
    let rows = await this.modelInstance.savePost(data);
    return this.success({affectedRows: rows});
  }

  async deleteAction() {
    if(!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    /** 如果不是管理员且不是本文作者则无权限删除文章 **/
    if(this.userInfo.type !== 1) {
      let post = await this.modelInstance.where({id: this.id}).find();
      if( post.user_id != this.userInfo.id ) {
        return this.fail('USER_NO_PERMISSION');
      }
    }

    await this.modelInstance.deletePost(this.id);
    return this.success();
  }

  async pushPost(post) {
    let postOpt = JSON.parse(post.options);
    let canPush = Array.isArray(postOpt.push_sites) && postOpt.push_sites.length > 0;
    if( post.status != 3 && post.is_public != 1 && !canPush ) {
      return;
    }
    post = think.extend({}, post);

    let options = await this.model('options').getOptions();
    let push_sites = options.push_sites;
    let push_sites_keys = postOpt.push_sites;
    let passwordHash = new PasswordHash();

    if( post.markdown_content.slice(0, 5) !== '> 原文：') {
      let options = await this.model('options').getOptions();
      let site_url = options.hasOwnProperty('site_url') ? options.site_url : `http://${this.http.host}`;
      post.markdown_content = `> 原文：${site_url}/post/${post.pathname}

${post.markdown_content}`;
    }

    async function push(post, {appKey, appSecret, url}) {
      let auth_key = passwordHash.hashPassword(`${appSecret}${post.markdown_content}`);
      Object.assign(post, {app_key:appKey, auth_key});
      request.post({url: url + '/admin/post_push', form: post}, (err, res, body) => console.log(err, res, body));
    }

    delete post.cate;
    delete post.options;
    if(!Array.isArray(push_sites_keys)) { push_sites_keys = [push_sites_keys]; }
    let pushes = push_sites_keys.map(key => push(post, push_sites[key]));
    await Promise.all(pushes);
  }

  async lastest() {
    let userId = this.userInfo.type !== 1 ? this.userInfo.id : null;
    let data = await this.modelInstance.getLatest(userId, 6);
    return this.success(data);
  }

  getPostTime(data) {
    data.update_time = think.datetime();
    /**草稿可以没有创建时间**/
    if( !data.create_time ) {
      data.create_time = data.status != 0 ? data.update_time : null;
    }else{
      data.create_time = think.datetime(data.create_time);
    }
    return data;
  }

  getContentAndSummary(data) {
    data.content = this.markdownToHtml(data.markdown_content);
    data.summary = data.content.split('')[0].replace(/<[>]*>/g, '');
    return data;
  }

  async getTagIds(tags) {
    if(!tags){
      return [];
    }
    if(!think.isArray(tags)){
      tags = [tags];
    }
    let modelInstance = this.model('tag').setRelation(false), tagIds = [];
    let promises = tags.map(name =>
      modelInstance.where({name}).thenAdd({name, pathname: encodeURIComponent(name)}).then(data => tagIds.push({tag_id: data.id, name: name}))
    );
    await Promise.all(promises);
    return tagIds;
  }

  /**
   * generate toc name
   * @param  {String} name []
   * @return {String}      []
   */
  generateTocName(name){
    name = name.trim().replace(/\s+/g, '').replace(/\)/g, '').replace(/[\(\,]/g, '-').toLowerCase();
    if(/^[\w\-]+$/.test(name)){
      return name;
    }
    return `toc-${think.md5(name).slice(0, 3)}`;
  }
  /**
   * markdown to html
   * @return {} []
   */
  markdownToHtml(content){
    let tocContent = marked(markToc(content)).replace(/([^<>]+)<\/a>/g, (a, b, c) => {
      return `<a href="#${this.generateTocName(c)}">${c}</a>`;
    });

    let markedContent = marked(content).replace(/<h(\d)[^<>]*>(.*?)<\/h\1>/g, (a, b, c) => {
      if(b == 2){
        return `<h${b} id="${this.generateTocName(c)}">${c}</h${b}>`;
      }
      return `<h${b} id="${this.generateTocName(c)}"><a class="anchor" href="#${this.generateTocName(c)}"></a>${c}</h${b}>`;
    });
    // markedContent = markedContent.replace(/<h(\d)[^<>]*>([^<>]+)<\/h\1>/, (a, b, c) => {
    //   return `${a}<div class="toc">${tocContent}</div>`;
    // });

    let highlightContent = markedContent.replace(/
([\s\S]+?)<\/code><\/pre>/mg, (a, language, text) => {
      text = text.replace(/'/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/\"/g, '"').replace(/\&/g, "&");
      var result = highlight.highlightAuto(text, language ? [language] : undefined);
      return `<pre><code class="hljs lang-${result.language}">${result.value}</code></pre>`;
    });

    return highlightContent;
  }

}
<commit_msg>Add .html for push article<commit_after>'use strict';
import marked from "marked";
import Base from './base.js';
import request from 'request';
import markToc from "marked-toc";
import {PasswordHash} from 'phpass';
import highlight from 'highlight.js';

request.defaults({
  strictSSL: false,
  rejectUnauthorized: false
});

export default class extends Base {
  modelInstance = this.modelInstance.where({type: 0});
  /**
   * get
   * @return {[type]} [description]
   */
  async getAction(self){
    // this.modelInstance.field('id,user_id,type,status,title,pathname,create_time,update_time');
    let data;
    if( this.id ) {
      if( this.id === 'lastest' ) {
        return this.lastest();
      }
      data = await this.modelInstance.where({id: this.id}).find();
      //文章选项
      if(data.options){
        data.options = JSON.parse(data.options) || {};
      }else{
        data.options = {};
      }
    } else {
      let where = {};
      //不是管理员，只显示个人的文章
      if(this.userInfo.type !== 1){
        where.user_id = this.userInfo.id;
      }

      if(this.get('status')) {
        where.status = this.get('status');
      }

      if(this.get('keyword')) {
        let keywords = this.get('keyword').split(/\s+/g);
        if( keywords.indexOf(':public') > -1 || keywords.indexOf(':private') > -1 ) {
          where.is_public = Number(keywords.indexOf(':public') > -1);
          keywords = keywords.filter(word => word !== ':public' && word !== ':private');
        }
        if(keywords.length > 0) {
          where.title = ["like", keywords.map(word => `%${word}%`)];
        }
      }

      let field = ['id', 'title', 'user_id', 'create_time', 'update_time', 'status', 'pathname'];
      data = await this.modelInstance.where(where).field(field).order('id DESC').page( this.get('page'), 15 ).countSelect();
    }
    return this.success(data);
  }

  getBaseAction(self) {
    return super.getAction(self);
  }
  /**
   * add user
   * @return {[type]} [description]
   */
  async postAction(){
    let data = this.post();
    //check pathname
    let post = await this.modelInstance.where({pathname: data.pathname}).select();
    if( post.length > 0 ) {
      return this.fail('PATHNAME_EXIST');
    }

    /** 如果是编辑发布文章的话默认状态改为审核中 **/
    if( data.status == 3 && this.userInfo.type != 1 ) {
      data.status = 1;
    }

    /** 推送文章 **/
    this.pushPost(data);

    data.tag = await this.getTagIds(data.tag);
    data = this.getContentAndSummary(data);
    data.user_id = this.userInfo.id;
    data = this.getPostTime(data);
    data.options = data.options ? JSON.stringify(data.options) : '';

    let insertId = await this.modelInstance.addPost(data);
    return this.success({id: insertId});
  }
  /**
   * update user info
   * @return {[type]} [description]
   */
  async putAction(){
    if (!this.id) {
      return this.fail('PARAMS_ERROR');
    }
    let data = this.post();

    /** 推送文章 **/
    this.pushPost(data);

    data.id = this.id;
    data = this.getPostTime(data);
    data = this.getContentAndSummary(data);
    data.options = data.options ? JSON.stringify(data.options) : '';
    if(data.tag) {
      data.tag = await this.getTagIds(data.tag);
    }
    let rows = await this.modelInstance.savePost(data);
    return this.success({affectedRows: rows});
  }

  async deleteAction() {
    if(!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    /** 如果不是管理员且不是本文作者则无权限删除文章 **/
    if(this.userInfo.type !== 1) {
      let post = await this.modelInstance.where({id: this.id}).find();
      if( post.user_id != this.userInfo.id ) {
        return this.fail('USER_NO_PERMISSION');
      }
    }

    await this.modelInstance.deletePost(this.id);
    return this.success();
  }

  async pushPost(post) {
    let postOpt = JSON.parse(post.options);
    let canPush = Array.isArray(postOpt.push_sites) && postOpt.push_sites.length > 0;
    if( post.status != 3 && post.is_public != 1 && !canPush ) {
      return;
    }
    post = think.extend({}, post);

    let options = await this.model('options').getOptions();
    let push_sites = options.push_sites;
    let push_sites_keys = postOpt.push_sites;
    let passwordHash = new PasswordHash();

    if( post.markdown_content.slice(0, 5) !== '> 原文：') {
      let options = await this.model('options').getOptions();
      let site_url = options.hasOwnProperty('site_url') ? options.site_url : `http://${this.http.host}`;
      post.markdown_content = `> 原文：${site_url}/post/${post.pathname}.html

${post.markdown_content}`;
    }

    async function push(post, {appKey, appSecret, url}) {
      let auth_key = passwordHash.hashPassword(`${appSecret}${post.markdown_content}`);
      Object.assign(post, {app_key:appKey, auth_key});
      request.post({url: url + '/admin/post_push', form: post}, (err, res, body) => console.log(err, res, body));
    }

    delete post.cate;
    delete post.options;
    if(!Array.isArray(push_sites_keys)) { push_sites_keys = [push_sites_keys]; }
    let pushes = push_sites_keys.map(key => push(post, push_sites[key]));
    await Promise.all(pushes);
  }

  async lastest() {
    let userId = this.userInfo.type !== 1 ? this.userInfo.id : null;
    let data = await this.modelInstance.getLatest(userId, 6);
    return this.success(data);
  }

  getPostTime(data) {
    data.update_time = think.datetime();
    /**草稿可以没有创建时间**/
    if( !data.create_time ) {
      data.create_time = data.status != 0 ? data.update_time : null;
    }else{
      data.create_time = think.datetime(data.create_time);
    }
    return data;
  }

  getContentAndSummary(data) {
    data.content = this.markdownToHtml(data.markdown_content);
    data.summary = data.content.split('')[0].replace(/<[>]*>/g, '');
    return data;
  }

  async getTagIds(tags) {
    if(!tags){
      return [];
    }
    if(!think.isArray(tags)){
      tags = [tags];
    }
    let modelInstance = this.model('tag').setRelation(false), tagIds = [];
    let promises = tags.map(name =>
      modelInstance.where({name}).thenAdd({name, pathname: encodeURIComponent(name)}).then(data => tagIds.push({tag_id: data.id, name: name}))
    );
    await Promise.all(promises);
    return tagIds;
  }

  /**
   * generate toc name
   * @param  {String} name []
   * @return {String}      []
   */
  generateTocName(name){
    name = name.trim().replace(/\s+/g, '').replace(/\)/g, '').replace(/[\(\,]/g, '-').toLowerCase();
    if(/^[\w\-]+$/.test(name)){
      return name;
    }
    return `toc-${think.md5(name).slice(0, 3)}`;
  }
  /**
   * markdown to html
   * @return {} []
   */
  markdownToHtml(content){
    let tocContent = marked(markToc(content)).replace(/([^<>]+)<\/a>/g, (a, b, c) => {
      return `<a href="#${this.generateTocName(c)}">${c}</a>`;
    });

    let markedContent = marked(content).replace(/<h(\d)[^<>]*>(.*?)<\/h\1>/g, (a, b, c) => {
      if(b == 2){
        return `<h${b} id="${this.generateTocName(c)}">${c}</h${b}>`;
      }
      return `<h${b} id="${this.generateTocName(c)}"><a class="anchor" href="#${this.generateTocName(c)}"></a>${c}</h${b}>`;
    });
    // markedContent = markedContent.replace(/<h(\d)[^<>]*>([^<>]+)<\/h\1>/, (a, b, c) => {
    //   return `${a}<div class="toc">${tocContent}</div>`;
    // });

    let highlightContent = markedContent.replace(/
([\s\S]+?)<\/code><\/pre>/mg, (a, language, text) => {
      text = text.replace(/'/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/\"/g, '"').replace(/\&/g, "&");
      var result = highlight.highlightAuto(text, language ? [language] : undefined);
      return `<pre><code class="hljs lang-${result.language}">${result.value}</code></pre>`;
    });

    return highlightContent;
  }

}
<|endoftext|>


<commit_before>import React from 'react';
import { Component, PropTypes } from '../utils/';
import Transition from '../transition';
import Button from '../button';
import Icon from '../icon';

const ButtonGroup = Button.Group;

export default class Modal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: props.visible,
      isMount: false,
    };
  }
  componentWillMount() {
    if (this.props.visible) {
      this.setState({ isMount: true });
    }
  }
  componentWillReceiveProps(nextProps) {
    if (this.props.visible !== nextProps.visible) {
      document.body.style.overflow = nextProps.visible ? 'hidden' : 'inherit';
      if (nextProps.visible) {
        this.setState({ isMount: true });
      }
      this.setState({
        visible: nextProps.visible,
      });
    }
  }
  onExited(props) {
    const { onCancel, onExited } = this.props;
    this.setState({ isMount: false }, () => {
      // 动画事件不同步，带来的闪烁问题
      const timer = setTimeout(() => {
        if (!this.state.isMount) {
          onExited(props);
        }
        clearTimeout(timer);
      }, 100);
      if (!this.state.isMount) {
        onCancel();
      }
    });
  }
  onCancel = (ismask) => {
    // 禁止遮罩层关闭
    if (ismask === 'mask' && !this.props.maskClosable) return;
    this.setState({ visible: false });
  }
  handleOk = (e) => {
    const { onOk } = this.props;
    onOk && onOk(e);
  }
  render() {
    const { prefixCls, className, title, footer, horizontal, styleMask, children, confirmLoading, onCancel, cancelText, okText, width, onEntered, ...other } = this.props;
    const { visible, isMount } = this.state;
    let defaultFooter = !footer ? (
      <ButtonGroup>
        <Button key="cancel" size="small" onClick={this.onCancel}>
          {cancelText || '取消'}
        </Button>
        <Button key="confirm" size="small" loading={confirmLoading} onClick={this.handleOk}>
          {okText || '确定'}
        </Button>
      </ButtonGroup>
    ) : footer;
    const cls = this.classNames(prefixCls, {
      [`${prefixCls}-wrap`]: isMount,
      [`${prefixCls}-horizontal-left`]: horizontal === 'left' && isMount,
      [`${prefixCls}-horizontal-right`]: horizontal === 'right' && isMount,
      [className]: className,
    });

    let AnimateType = '';
    switch (horizontal) {
      case 'left': AnimateType = 'fadeIn left'; break;
      case 'right': AnimateType = 'fadeIn right'; break;
      default: AnimateType = 'fadeIn down'; break;
    }
    defaultFooter = (footer === null ? null : <div className={`${prefixCls}-footer`}>{defaultFooter}
);
    return (

        <Transition in={visible} sequence="fadeIn">
          <div className={`${prefixCls}-mask`} style={styleMask} onClick={() => this.onCancel('mask')} />
        </Transition>
        <Transition onExited={this.onExited.bind(this)} onEntered={onEntered} in={visible} sequence={AnimateType}>
          <div className={`${prefixCls}-content`} style={{ width, ...other.style }}>
            {title && (
              <div className={`${prefixCls}-header`}>
                <div className={`${prefixCls}-title`}>{title}

                 this.onCancel()} className={`${prefixCls}-close-icon`}><Icon type="close" />

            )}
            <div className={`${prefixCls}-body`}>{children}

            {defaultFooter}

        </Transition>

    );
  }
}

Modal.defaultProps = {
  prefixCls: 'w-modal',
  width: 520,
  title: '',
  visible: false,
  maskClosable: true,
  confirmLoading: false,
  onCancel: v => v,
  onExited: v => v,
  onEntered: v => v,
};
Modal.propTypes = {
  visible: PropTypes.bool,
  horizontal: PropTypes.oneOf(['left', 'right']),
  maskClosable: PropTypes.bool,
  styleMask: PropTypes.object,
  style: PropTypes.object,
  confirmLoading: PropTypes.bool,
  title: PropTypes.node,
  footer: PropTypes.node,
  onCancel: PropTypes.func,
  onExited: PropTypes.func,
  onEntered: PropTypes.func,
  width: PropTypes.oneOfType([
    PropTypes.number, PropTypes.string,
  ]),
};
<commit_msg>Fix(Modal): Fix scrolling issue.<commit_after>import React from 'react';
import { Component, PropTypes } from '../utils/';
import Transition from '../transition';
import Button from '../button';
import Icon from '../icon';

const ButtonGroup = Button.Group;

export default class Modal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: props.visible,
      isMount: false,
    };
  }
  componentWillMount() {
    if (this.props.visible) {
      this.setState({ isMount: true });
    }
  }
  componentWillUnmount() {
    document.body.style.overflow = 'inherit';
  }
  componentWillReceiveProps(nextProps) {
    if (this.props.visible !== nextProps.visible) {
      document.body.style.overflow = nextProps.visible ? 'hidden' : 'inherit';
      const props = { visible: nextProps.visible };
      if (nextProps.visible) {
        props.isMount = true;
      }
      this.setState({ ...props });
    }
  }
  onExited(props) {
    const { onCancel, onExited } = this.props;
    this.setState({ isMount: false }, () => {
      // 动画事件不同步，带来的闪烁问题
      const timer = setTimeout(() => {
        if (!this.state.isMount) {
          onExited(props);
        }
        clearTimeout(timer);
      }, 100);
      if (!this.state.isMount) {
        onCancel();
      }
    });
  }
  onCancel = (ismask) => {
    // 禁止遮罩层关闭
    if (ismask === 'mask' && !this.props.maskClosable) return;
    this.setState({ visible: false });
  }
  handleOk = (e) => {
    const { onOk } = this.props;
    onOk && onOk(e);
  }
  render() {
    const { prefixCls, className, title, footer, horizontal, styleMask, children, confirmLoading, onCancel, cancelText, okText, width, onEntered, ...other } = this.props;
    const { visible, isMount } = this.state;
    let defaultFooter = !footer ? (
      <ButtonGroup>
        <Button key="cancel" size="small" onClick={this.onCancel}>
          {cancelText || '取消'}
        </Button>
        <Button key="confirm" size="small" loading={confirmLoading} onClick={this.handleOk}>
          {okText || '确定'}
        </Button>
      </ButtonGroup>
    ) : footer;
    const cls = this.classNames(prefixCls, {
      [`${prefixCls}-wrap`]: isMount,
      [`${prefixCls}-horizontal-left`]: horizontal === 'left' && isMount,
      [`${prefixCls}-horizontal-right`]: horizontal === 'right' && isMount,
      [className]: className,
    });

    let AnimateType = '';
    switch (horizontal) {
      case 'left': AnimateType = 'fadeIn left'; break;
      case 'right': AnimateType = 'fadeIn right'; break;
      default: AnimateType = 'fadeIn down'; break;
    }
    defaultFooter = (footer === null ? null : <div className={`${prefixCls}-footer`}>{defaultFooter}
);
    return (

        <Transition in={visible} sequence="fadeIn">
          <div className={`${prefixCls}-mask`} style={styleMask} onClick={() => this.onCancel('mask')} />
        </Transition>
        <Transition onExited={this.onExited.bind(this)} onEntered={onEntered} in={visible} sequence={AnimateType}>
          <div className={`${prefixCls}-content`} style={{ width, ...other.style }}>
            {title && (
              <div className={`${prefixCls}-header`}>
                <div className={`${prefixCls}-title`}>{title}

                 this.onCancel()} className={`${prefixCls}-close-icon`}><Icon type="close" />

            )}
            <div className={`${prefixCls}-body`}>{children}

            {defaultFooter}

        </Transition>

    );
  }
}

Modal.defaultProps = {
  prefixCls: 'w-modal',
  width: 520,
  title: '',
  visible: false,
  maskClosable: true,
  confirmLoading: false,
  onCancel: v => v,
  onExited: v => v,
  onEntered: v => v,
};
Modal.propTypes = {
  visible: PropTypes.bool,
  horizontal: PropTypes.oneOf(['left', 'right']),
  maskClosable: PropTypes.bool,
  styleMask: PropTypes.object,
  style: PropTypes.object,
  confirmLoading: PropTypes.bool,
  title: PropTypes.node,
  footer: PropTypes.node,
  onCancel: PropTypes.func,
  onExited: PropTypes.func,
  onEntered: PropTypes.func,
  width: PropTypes.oneOfType([
    PropTypes.number, PropTypes.string,
  ]),
};
<|endoftext|>


<commit_before>/**
 * The default testnet node
 *
 * @type {string}
 */
let defaultTestnet = 'http://bigalice2.nem.ninja';

/**
 * The default mainnet node
 *
 * @type {string}
 */
let defaultMainnet = 'http://alice6.nem.ninja';

/**
 * The default mijin node
 *
 * @type {string}
 */
let defaultMijin = '';

/**
 * The default mainnet block explorer
 *
 * @type {string}
 */
let mainnetExplorer = 'http://chain.nem.ninja/#/transfer/';

/**
 * The default testnet block explorer
 *
 * @type {string}
 */
let testnetExplorer = 'http://bob.nem.ninja:8765/#/transfer/';

/**
 * The default mijin block explorer
 *
 * @type {string}
 */
let mijinExplorer = '';

/**
 * The nodes allowing search by transaction hash on testnet
 *
 * @type {array}
 */
let searchOnTestnet = [
	{
	    'uri': 'http://bigalice2.nem.ninja',
	    'location': 'America / New_York'
	},
	{
	    'uri': 'http://192.3.61.243',
	    'location': 'America / Los_Angeles'
	},
	{
	    'uri': 'http://23.228.67.85',
	    'location': 'America / Los_Angeles'
	}
];

/**
 * The nodes allowing search by transaction hash on mainnet
 *
 * @type {array}
 */
let searchOnMainnet = [
	{
	    'uri': 'http://62.75.171.41',
	    'location': 'Germany'
	}, {
	    'uri': 'http://104.251.212.131',
	    'location': 'USA'
	}, {
	    'uri': 'http://45.124.65.125',
	    'location': 'Hong Kong'
	}, {
	    'uri': 'http://185.53.131.101',
	    'location': 'Netherlands'
	}, {
	    'uri': 'http://sz.nemchina.com',
	    'location': 'China'
	}
];

/**
 * The nodes allowing search by transaction hash on mijin
 *
 * @type {array}
 */
let searchOnMijin = [
	{
	    'uri': '',
	    'location': ''
	}
];

/**
 * The testnet nodes
 *
 * @type {array}
 */
let testnet = [
	{
	        uri: 'http://104.128.226.60'
	}, {
	        uri: 'http://23.228.67.85'
	}, {
	        uri: 'http://192.3.61.243'
	}, {
	        uri: 'http://50.3.87.123'
	}, {
	    uri: 'http://localhost'
	}
];

/**
 * The mainnet nodes
 *
 * @type {array}
 */
let mainnet = [
    {
        uri: 'http://62.75.171.41'
    }, {
        uri: 'http://san.nem.ninja'
    }, {
        uri: 'http://go.nem.ninja'
    }, {
        uri: 'http://hachi.nem.ninja'
    }, {
        uri: 'http://jusan.nem.ninja'
    }, {
        uri: 'http://nijuichi.nem.ninja'
    }, {
        uri: 'http://alice2.nem.ninja'
    }, {
        uri: 'http://alice3.nem.ninja'
    }, {
        uri: 'http://alice4.nem.ninja'
    }, {
        uri: 'http://alice5.nem.ninja'
    }, {
        uri: 'http://alice6.nem.ninja'
    }, {
        uri: 'http://alice7.nem.ninja'
    }, {
        uri: 'http://localhost'
    }
];

/**
 * The mijin nodes
 *
 * @type {array}
 */
let mijin = [
	{
	    uri: ''
	}
];

/**
 * The server verifying signed apostilles
 *
 * @type {string}
 */
let apostilleAuditServer = 'http://185.117.22.58:4567/verify';

/**
 * The API to get all supernodes
 *
 * @type {string}
 */
let supernodes = 'https://supernodes.nem.io/nodes';

/**
 * The API to get XEM/BTC market data
 *
 * @type {string}
 */
let marketInfo = 'https://poloniex.com/public';

/**
 * The API to get BTC/USD market data
 *
 * @type {string}
 */
let btcPrice = 'https://blockchain.info/ticker';

/**
 * The default endpoint port
 *
 * @type {number}
 */
let defaultPort = 7890;

/**
 * The Mijin endpoint port
 *
 * @type {number}
 */
let mijinPort = 7895;

/**
 * The websocket port
 *
 * @type {number}
 */
let websocketPort = 7778;

module.exports = {
    defaultTestnet,
    defaultMainnet,
    defaultMijin,
    mainnetExplorer,
    testnetExplorer,
    mijinExplorer,
    searchOnTestnet,
    searchOnMainnet,
    searchOnMijin,
    testnet,
    mainnet,
    mijin,
    apostilleAuditServer,
    supernodes,
    defaultPort,
    mijinPort,
    websocketPort,
    marketInfo,
    btcPrice
}<commit_msg>Update nodes<commit_after>/**
 * The default testnet node
 *
 * @type {string}
 */
let defaultTestnet = 'http://23.228.67.85';

/**
 * The default mainnet node
 *
 * @type {string}
 */
let defaultMainnet = 'http://hugealice3.nem.ninja';

/**
 * The default mijin node
 *
 * @type {string}
 */
let defaultMijin = '';

/**
 * The default mainnet block explorer
 *
 * @type {string}
 */
let mainnetExplorer = 'http://chain.nem.ninja/#/transfer/';

/**
 * The default testnet block explorer
 *
 * @type {string}
 */
let testnetExplorer = 'http://bob.nem.ninja:8765/#/transfer/';

/**
 * The default mijin block explorer
 *
 * @type {string}
 */
let mijinExplorer = '';

/**
 * The nodes allowing search by transaction hash on testnet
 *
 * @type {array}
 */
let searchOnTestnet = [
	{
	    'uri': 'http://bigalice2.nem.ninja',
	    'location': 'America / New_York'
	},
	{
	    'uri': 'http://192.3.61.243',
	    'location': 'America / Los_Angeles'
	},
	{
	    'uri': 'http://23.228.67.85',
	    'location': 'America / Los_Angeles'
	}
];

/**
 * The nodes allowing search by transaction hash on mainnet
 *
 * @type {array}
 */
let searchOnMainnet = [
	{
	    'uri': 'http://62.75.171.41',
	    'location': 'Germany'
	}, {
	    'uri': 'http://104.251.212.131',
	    'location': 'USA'
	}, {
	    'uri': 'http://45.124.65.125',
	    'location': 'Hong Kong'
	}, {
	    'uri': 'http://185.53.131.101',
	    'location': 'Netherlands'
	}, {
	    'uri': 'http://sz.nemchina.com',
	    'location': 'China'
	}
];

/**
 * The nodes allowing search by transaction hash on mijin
 *
 * @type {array}
 */
let searchOnMijin = [
	{
	    'uri': '',
	    'location': ''
	}
];

/**
 * The testnet nodes
 *
 * @type {array}
 */
let testnet = [
	{
	        uri: 'http://104.128.226.60'
	}, {
	        uri: 'http://23.228.67.85'
	}, {
	        uri: 'http://192.3.61.243'
	}, {
	        uri: 'http://50.3.87.123'
	}, {
	    uri: 'http://localhost'
	}
];

/**
 * The mainnet nodes
 *
 * @type {array}
 */
let mainnet = [
    {
        uri: 'http://hugealice.nem.ninja'
    }, {
        uri: 'http://hugealice2.nem.ninja'
    }, {
        uri: 'http://hugealice3.nem.ninja'
    }, {
        uri: 'http://hugealice4.nem.ninja'
    }, {
        uri: 'http://62.75.171.41'
    }, {
        uri: 'http://san.nem.ninja'
    }, {
        uri: 'http://go.nem.ninja'
    }, {
        uri: 'http://hachi.nem.ninja'
    }, {
        uri: 'http://jusan.nem.ninja'
    }, {
        uri: 'http://nijuichi.nem.ninja'
    }, {
        uri: 'http://alice2.nem.ninja'
    }, {
        uri: 'http://alice3.nem.ninja'
    }, {
        uri: 'http://alice4.nem.ninja'
    }, {
        uri: 'http://alice5.nem.ninja'
    }, {
        uri: 'http://alice6.nem.ninja'
    }, {
        uri: 'http://alice7.nem.ninja'
    }, {
        uri: 'http://localhost'
    }
];

/**
 * The mijin nodes
 *
 * @type {array}
 */
let mijin = [
	{
	    uri: ''
	}
];

/**
 * The server verifying signed apostilles
 *
 * @type {string}
 */
let apostilleAuditServer = 'http://185.117.22.58:4567/verify';

/**
 * The API to get all supernodes
 *
 * @type {string}
 */
let supernodes = 'https://supernodes.nem.io/nodes';

/**
 * The API to get XEM/BTC market data
 *
 * @type {string}
 */
let marketInfo = 'https://poloniex.com/public';

/**
 * The API to get BTC/USD market data
 *
 * @type {string}
 */
let btcPrice = 'https://blockchain.info/ticker';

/**
 * The default endpoint port
 *
 * @type {number}
 */
let defaultPort = 7890;

/**
 * The Mijin endpoint port
 *
 * @type {number}
 */
let mijinPort = 7895;

/**
 * The websocket port
 *
 * @type {number}
 */
let websocketPort = 7778;

module.exports = {
    defaultTestnet,
    defaultMainnet,
    defaultMijin,
    mainnetExplorer,
    testnetExplorer,
    mijinExplorer,
    searchOnTestnet,
    searchOnMainnet,
    searchOnMijin,
    testnet,
    mainnet,
    mijin,
    apostilleAuditServer,
    supernodes,
    defaultPort,
    mijinPort,
    websocketPort,
    marketInfo,
    btcPrice
}<|endoftext|>


<commit_before>// Source: https://github.com/amitava82/angular-multiselect
angular.module('ui.multiselect', [])

  //from bootstrap-ui typeahead parser
  .factory('optionParser', ['$parse', function ($parse) {

    //                      00000111000000000000022200000000000000003333333333333330000000000044000
    var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;

    return {
      parse: function (input) {

        var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
        if (!match) {
          throw new Error(
            "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
              " but got '" + input + "'.");
        }

        return {
          itemName: match[3],
          source: $parse(match[4]),
          viewMapper: $parse(match[2] || match[1]),
          modelMapper: $parse(match[1])
        };
      }
    };
  }])

  .directive('multiselect', ['$parse', '$document', '$compile', '$interpolate', 'optionParser',

    function ($parse, $document, $compile, $interpolate, optionParser) {
      return {
        restrict: 'E',
        require: 'ngModel',
        link: function (originalScope, element, attrs, modelCtrl) {

          var exp = attrs.options,
            parsedResult = optionParser.parse(exp),
            isMultiple = attrs.multiple ? true : false,
            required = false,
            scope = originalScope.$new(),
            changeHandler = attrs.change || angular.noop;

          scope.items = [];
          scope.header = 'Select';
          scope.multiple = isMultiple;
          scope.disabled = false;

          originalScope.$on('$destroy', function () {
            scope.$destroy();
          });

          var popUpEl = angular.element('<multiselect-popup' +
                        (attrs.templateUrl ? (' template-url="' + attrs.templateUrl + '"'): '' ) +
                        '></multiselect-popup>');

          //required validator
          if (attrs.required || attrs.ngRequired) {
            required = true;
          }
          attrs.$observe('required', function(newVal) {
            required = newVal;
          });

          //watch disabled state
          scope.$watch(function () {
            return $parse(attrs.disabled)(originalScope);
          }, function (newVal) {
            scope.disabled = newVal;
          });

          //watch single/multiple state for dynamically change single to multiple
          scope.$watch(function () {
            return $parse(attrs.multiple)(originalScope);
          }, function (newVal) {
            isMultiple = newVal || false;
          });

          //watch option changes for options that are populated dynamically
          scope.$watch(function () {
            return parsedResult.source(originalScope);
          }, function (newVal) {
            if (angular.isDefined(newVal))
              parseModel();
          }, true);

          //watch model change
          scope.$watch(function () {
            return modelCtrl.$modelValue;
          }, function (newVal, oldVal) {
            //when directive initialize, newVal usually undefined. Also, if model value already set in the controller
            //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
            //model changes. We need to do this only if it is done outside directive scope, from controller, for example.
            if (angular.isDefined(newVal)) {
              markChecked(newVal);
              scope.$eval(changeHandler);
            }
            getHeaderText();
            modelCtrl.$setValidity('required', scope.valid());
          }, true);

          function parseModel() {
            scope.items.length = 0;
            var model = parsedResult.source(originalScope);
            if(!angular.isDefined(model)) return;
            for (var i = 0; i < model.length; i++) {
              var local = {};
              local[parsedResult.itemName] = model[i];
              scope.items.push({
                label: parsedResult.viewMapper(local),
                model: parsedResult.modelMapper(local),
                checked: false
              });
            }
          }

          parseModel();

          element.append($compile(popUpEl)(scope));

          function getHeaderText() {
            if (is_empty(modelCtrl.$modelValue)) return scope.header = attrs.msHeader || 'Select';

              if (isMultiple) {
                  if (attrs.msSelected) {
                      scope.header = $interpolate(attrs.msSelected)(scope);
                  } else {
                     if (modelCtrl.$modelValue.length == 1) {
                          for(var i = 0; i < scope.items.length; i++) {
                              if(scope.items[i].model === modelCtrl.$modelValue[0]) {
                                  scope.header = scope.items[i].label;
                              }
                          }
                      } else {
                          scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
                      }
                  }

            } else {
              var local = {};
              local[parsedResult.itemName] = modelCtrl.$modelValue;
              scope.header = parsedResult.viewMapper(local) || scope.items[modelCtrl.$modelValue].label;
            }
          }

          function is_empty(obj) {
            if (angular.isNumber(obj)) return false;
            if (obj && obj.length && obj.length > 0) return false;
            for (var prop in obj) if (obj[prop]) return false;
            return true;
          };

          scope.valid = function validModel() {
            if(!required) return true;
            var value = modelCtrl.$modelValue;
            return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
          };

          function selectSingle(item) {
            if (item.checked) {
              scope.uncheckAll();
            } else {
              scope.uncheckAll();
              item.checked = !item.checked;
            }
            setModelValue(false);
          }

          function selectMultiple(item) {
            item.checked = !item.checked;
            setModelValue(true);
          }

          function setModelValue(isMultiple) {
            var value = null;

            if (isMultiple) {
              value = [];
              angular.forEach(scope.items, function (item) {
                if (item.checked) value.push(item.model);
              })
            } else {
              angular.forEach(scope.items, function (item) {
                if (item.checked) {
                  value = item.model;
                  return false;
                }
              })
            }
            modelCtrl.$setViewValue(value);
          }

          function markChecked(newVal) {
            if (!angular.isArray(newVal)) {
              angular.forEach(scope.items, function (item) {
                if (angular.equals(item.model, newVal)) {
                  scope.uncheckAll();
                  item.checked = true;
                  setModelValue(false);
                  return false;
                }
              });
            } else {
              angular.forEach(scope.items, function (item) {
                item.checked = false;
                angular.forEach(newVal, function (i) {
                  if (angular.equals(item.model, i)) {
                    item.checked = true;
                  }
                });
              });
            }
          }

          scope.checkAll = function () {
            if (!isMultiple) return;
            angular.forEach(scope.items, function (item) {
              item.checked = true;
            });
            setModelValue(true);
          };

          scope.uncheckAll = function () {
            angular.forEach(scope.items, function (item) {
              item.checked = false;
            });
            setModelValue(true);
          };

          scope.select = function (item) {
            if (isMultiple === false) {
              selectSingle(item);
              scope.toggleSelect();
            } else {
              selectMultiple(item);
            }
          }
        }
      };
    }])

  .directive('multiselectPopup', ['$document', function ($document) {
    return {
      restrict: 'E',
      scope: false,
      replace: true,
      templateUrl: function (element, attr) {
                return attr.templateUrl || 'multiselect.tmpl.html';
        },
      link: function (scope, element, attrs) {

        scope.isVisible = false;

        scope.toggleSelect = function () {
          if (element.hasClass('open')) {
            element.removeClass('open');
            $document.unbind('click', clickHandler);
          } else {
            element.addClass('open');
            $document.bind('click', clickHandler);
            scope.focus();
          }
        };

        function clickHandler(event) {
          if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
            return;
          element.removeClass('open');
          $document.unbind('click', clickHandler);
          scope.$apply();
        }

        scope.focus = function focus(){
          var searchBox = element.find('input')[0];
          searchBox.focus();
        }

        var elementMatchesAnyInArray = function (element, elementArray) {
          for (var i = 0; i < elementArray.length; i++)
            if (element == elementArray[i])
              return true;
          return false;
        }
      }
    }
  }]);
<commit_msg>lets make sure we can focus on the filter search input as the template may not contain filter at all<commit_after>// Source: https://github.com/amitava82/angular-multiselect
angular.module('ui.multiselect', [])

  //from bootstrap-ui typeahead parser
  .factory('optionParser', ['$parse', function ($parse) {

    //                      00000111000000000000022200000000000000003333333333333330000000000044000
    var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;

    return {
      parse: function (input) {

        var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
        if (!match) {
          throw new Error(
            "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
              " but got '" + input + "'.");
        }

        return {
          itemName: match[3],
          source: $parse(match[4]),
          viewMapper: $parse(match[2] || match[1]),
          modelMapper: $parse(match[1])
        };
      }
    };
  }])

  .directive('multiselect', ['$parse', '$document', '$compile', '$interpolate', 'optionParser',

    function ($parse, $document, $compile, $interpolate, optionParser) {
      return {
        restrict: 'E',
        require: 'ngModel',
        link: function (originalScope, element, attrs, modelCtrl) {

          var exp = attrs.options,
            parsedResult = optionParser.parse(exp),
            isMultiple = attrs.multiple ? true : false,
            required = false,
            scope = originalScope.$new(),
            changeHandler = attrs.change || angular.noop;

          scope.items = [];
          scope.header = 'Select';
          scope.multiple = isMultiple;
          scope.disabled = false;

          originalScope.$on('$destroy', function () {
            scope.$destroy();
          });

          var popUpEl = angular.element('<multiselect-popup' +
                        (attrs.templateUrl ? (' template-url="' + attrs.templateUrl + '"'): '' ) +
                        '></multiselect-popup>');

          //required validator
          if (attrs.required || attrs.ngRequired) {
            required = true;
          }
          attrs.$observe('required', function(newVal) {
            required = newVal;
          });

          //watch disabled state
          scope.$watch(function () {
            return $parse(attrs.disabled)(originalScope);
          }, function (newVal) {
            scope.disabled = newVal;
          });

          //watch single/multiple state for dynamically change single to multiple
          scope.$watch(function () {
            return $parse(attrs.multiple)(originalScope);
          }, function (newVal) {
            isMultiple = newVal || false;
          });

          //watch option changes for options that are populated dynamically
          scope.$watch(function () {
            return parsedResult.source(originalScope);
          }, function (newVal) {
            if (angular.isDefined(newVal))
              parseModel();
          }, true);

          //watch model change
          scope.$watch(function () {
            return modelCtrl.$modelValue;
          }, function (newVal, oldVal) {
            //when directive initialize, newVal usually undefined. Also, if model value already set in the controller
            //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
            //model changes. We need to do this only if it is done outside directive scope, from controller, for example.
            if (angular.isDefined(newVal)) {
              markChecked(newVal);
              scope.$eval(changeHandler);
            }
            getHeaderText();
            modelCtrl.$setValidity('required', scope.valid());
          }, true);

          function parseModel() {
            scope.items.length = 0;
            var model = parsedResult.source(originalScope);
            if(!angular.isDefined(model)) return;
            for (var i = 0; i < model.length; i++) {
              var local = {};
              local[parsedResult.itemName] = model[i];
              scope.items.push({
                label: parsedResult.viewMapper(local),
                model: parsedResult.modelMapper(local),
                checked: false
              });
            }
          }

          parseModel();

          element.append($compile(popUpEl)(scope));

          function getHeaderText() {
            if (is_empty(modelCtrl.$modelValue)) return scope.header = attrs.msHeader || 'Select';

              if (isMultiple) {
                  if (attrs.msSelected) {
                      scope.header = $interpolate(attrs.msSelected)(scope);
                  } else {
                     if (modelCtrl.$modelValue.length == 1) {
                          for(var i = 0; i < scope.items.length; i++) {
                              if(scope.items[i].model === modelCtrl.$modelValue[0]) {
                                  scope.header = scope.items[i].label;
                              }
                          }
                      } else {
                          scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
                      }
                  }

            } else {
              var local = {};
              local[parsedResult.itemName] = modelCtrl.$modelValue;
              scope.header = parsedResult.viewMapper(local) || scope.items[modelCtrl.$modelValue].label;
            }
          }

          function is_empty(obj) {
            if (angular.isNumber(obj)) return false;
            if (obj && obj.length && obj.length > 0) return false;
            for (var prop in obj) if (obj[prop]) return false;
            return true;
          };

          scope.valid = function validModel() {
            if(!required) return true;
            var value = modelCtrl.$modelValue;
            return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
          };

          function selectSingle(item) {
            if (item.checked) {
              scope.uncheckAll();
            } else {
              scope.uncheckAll();
              item.checked = !item.checked;
            }
            setModelValue(false);
          }

          function selectMultiple(item) {
            item.checked = !item.checked;
            setModelValue(true);
          }

          function setModelValue(isMultiple) {
            var value = null;

            if (isMultiple) {
              value = [];
              angular.forEach(scope.items, function (item) {
                if (item.checked) value.push(item.model);
              })
            } else {
              angular.forEach(scope.items, function (item) {
                if (item.checked) {
                  value = item.model;
                  return false;
                }
              })
            }
            modelCtrl.$setViewValue(value);
          }

          function markChecked(newVal) {
            if (!angular.isArray(newVal)) {
              angular.forEach(scope.items, function (item) {
                if (angular.equals(item.model, newVal)) {
                  scope.uncheckAll();
                  item.checked = true;
                  setModelValue(false);
                  return false;
                }
              });
            } else {
              angular.forEach(scope.items, function (item) {
                item.checked = false;
                angular.forEach(newVal, function (i) {
                  if (angular.equals(item.model, i)) {
                    item.checked = true;
                  }
                });
              });
            }
          }

          scope.checkAll = function () {
            if (!isMultiple) return;
            angular.forEach(scope.items, function (item) {
              item.checked = true;
            });
            setModelValue(true);
          };

          scope.uncheckAll = function () {
            angular.forEach(scope.items, function (item) {
              item.checked = false;
            });
            setModelValue(true);
          };

          scope.select = function (item) {
            if (isMultiple === false) {
              selectSingle(item);
              scope.toggleSelect();
            } else {
              selectMultiple(item);
            }
          }
        }
      };
    }])

  .directive('multiselectPopup', ['$document', function ($document) {
    return {
      restrict: 'E',
      scope: false,
      replace: true,
      templateUrl: function (element, attr) {
                return attr.templateUrl || 'multiselect.tmpl.html';
        },
      link: function (scope, element, attrs) {

        scope.isVisible = false;

        scope.toggleSelect = function () {
          if (element.hasClass('open')) {
            element.removeClass('open');
            $document.unbind('click', clickHandler);
          } else {
            element.addClass('open');
            $document.bind('click', clickHandler);
            scope.focus();
          }
        };

        function clickHandler(event) {
          if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
            return;
          element.removeClass('open');
          $document.unbind('click', clickHandler);
          scope.$apply();
        }

        scope.focus = function focus(){
          var searchBox = element.find('input')[0];
          if (searchBox) {
            searchBox.focus();
          }
        }

        var elementMatchesAnyInArray = function (element, elementArray) {
          for (var i = 0; i < elementArray.length; i++)
            if (element == elementArray[i])
              return true;
          return false;
        }
      }
    }
  }]);
<|endoftext|>


<commit_before>const { NakoIndentError } = require('./nako_errors')
const NakoPrepare = require('./nako_prepare')

/**
 * インデント構文指定があればコードを変換する
 * @param {string} code
 * @param {string} filename
 * @returns {{ code: string, insertedLines: number[], deletedLines: { lineNumber: number, len: number }[] }}
 */
function convert(code, filename) {
    // 最初の30行をチェック
    if (isIndentSyntaxEnabled(code)) {
        return convertGo(code, filename)
    }
    return { code, insertedLines: [], deletedLines: [] }
}

// ありえない改行マークを定義
const SpecialRetMark = '🌟🌟改行🌟🌟s4j#WjcSb😀/FcX3🌟🌟'

/**
 * @param {string} code
 * @returns {boolean}
 */
function isIndentSyntaxEnabled(code) {
    // プログラム冒頭に「!インデント構文」があればインデント構文が有効
    const keywords = ['!インデント構文', '!ここまでだるい']
    const lines = code.split('\n', 30)
    for (const line of lines) {
        const s9 = line.substr(0, 8).replace('！', '!')
        if (keywords.indexOf(s9) >= 0) {
            return true
        }
    }
    return false
}

/**
 * ソースコードのある1行の中のコメントを全て取り除く。
 * 事前にreplaceRetMarkによって文字列や範囲コメント内の改行文字が置換されている必要がある。
 * @param {string} src
 * @return {string}
 */
function removeCommentsFromLine(src) {
    const prepare = new NakoPrepare()  // `※`, `／/`, `／＊` といったパターン全てに対応するために必要
    const len = src.length
    let result = ''
    let eos = ''
    let i = 0
    let isComment = false
    while (i < len) {
        const c = src.charAt(i)
        const ch2 = src.substr(i, 2)
        const cPrepared = prepare.convert1ch(c)
        const ch2Prepared = ch2.split('').map((c) => prepare.convert1ch(c)).join("")

        // eosか?
        if (eos != '') {
            // srcのi文字目以降がeosで始まるなら文字列を終了、そうでなければ1文字進める
            if (eos === (eos.length === 1 ? cPrepared : ch2Prepared)) {
                if (!isComment) {
                    result += src.substr(i, eos.length)
                }
                i += eos.length
                isComment = false
                eos = ''
            } else {
                if (!isComment) {
                    result += c
                }
                i++
            }
            continue
        }
        // 文字列の改行も無視する
        switch (cPrepared) {
            case '"':
            case '\'':
                eos = c
                result += c
                i++
                continue
            case '「':
                eos = '」'
                result += c
                i++
                continue
            case '『':
                eos = '』'
                result += c
                i++
                continue
            case '“':
                eos = '”'
                result += c
                i++
                continue
            case '{':
                eos = '}'
                result += c
                i++
                continue
            case '[':
                eos = ']'
                result += c
                i++
                continue
        }

        switch (ch2) {
            case '🌴':
                eos = '🌴'
                result += ch2
                i += 2
                continue
            case '🌿':
                eos = '🌿'
                result += ch2
                i += 2
                continue
        }

        // 行コメント
        if (cPrepared === '#') {
            eos = '\n'
            isComment = true
            i++
            continue
        }
        if (ch2Prepared === '//') {
            eos = '\n'
            isComment = true
            i += 2
            continue
        }

        // 範囲コメント
        if (ch2Prepared === '/*') {
            eos = '*/'
            isComment = true
            i += 2
            continue
        }

        result += c
        i++
    }
    return result
}

/**
 * @param {string} code
 * @param {string} filename
 * @returns {{ code: string, insertedLines: number[], deletedLines: { lineNumber: number, len: number }[] }}
 */
function convertGo(code, filename) {
    /** @type {number[]} */
    const insertedLines = []
    /** @type {{ lineNumber: number, len: number }[]} */
    const deletedLines = []

    const END = 'ここまで‰'
    const code2 = replaceRetMark(code) // 文字列の中などの改行を置換
    const lines = code2.split('\n')
    /** @type {string[]} */
    const lines2 = []
    /** @type {number[]} */
    const indentStack = []
    let lastIndent = 0

    let lineCount = -1
    lines.forEach((line) => {
        lineCount += line.split(SpecialRetMark).length
        // trim line
        if (/^[ 　・\t]*$/.test(line)) {
            deletedLines.push({ lineNumber: lines2.length, len: line.length })
            return
        }
        const lineTrimed = removeCommentsFromLine(line).replace(/^[ 　・\t]+/, '').replace(/\s+$/, '')
        if (lineTrimed === '') {
            lines2.push(line)
            return
        }
        if (lineTrimed === 'ここまで') {
            throw new NakoIndentError(`インデント構文が有効化されているときに『ここまで』を使うことはできません。`, lineCount, filename)
        }

        // check indent
        const indent = countIndent(line)
        if (lastIndent == indent) {
            lines2.push(line)
            return
        }

        // indent
        if (lastIndent < indent) {
            indentStack.push(lastIndent)
            lastIndent = indent
            lines2.push(line)
            return
        }
        // unindent
        if (lastIndent > indent) {
            // 5回
            //   3回
            //     1を表示
            //   |
            // |
            lastIndent = indent
            while (indentStack.length > 0) {
                const n = indentStack.pop()
                if (n == indent) {
                    if (lineTrimed != '違えば') {
                        insertedLines.push(lines2.length)
                        lines2.push(makeIndent(n) + END)
                    }
                    lines2.push(line)
                    return
                }
                if (indent < n) {
                    insertedLines.push(lines2.length)
                    lines2.push(makeIndent(n) + END)
                    continue
                }
            }
        }
    })
    // 残りのインデントを処理
    while (indentStack.length > 0) {
        const n = indentStack.pop()
        insertedLines.push(lines2.length)
        lines2.push(makeIndent(n) + END)
    }
    // 特別マーカーを改行に置換
    /** @type {string[]} */
    const lines3 = []
    for (let i = 0; i < lines2.length; i++) {
        if (lines2[i].includes(SpecialRetMark)) {
            const lines4 = lines2[i].split(SpecialRetMark)

            // 置換されたマーカーの数だけ、それ以降の行数をずらす。
            // unindentによって挿入された行がSpecialRetMarkを含むことはない。
            for (let j = 0; j < insertedLines.length; j++) {
                if (lines3.length < insertedLines[j]) {
                    insertedLines[j] += lines4.length - 1
                }
            }
            for (let j = 0; j < deletedLines.length; j++) {
                if (lines3.length < deletedLines[j].lineNumber) {
                    deletedLines[j].lineNumber += lines4.length - 1
                }
            }

            lines3.push(...lines4)
        } else {
            lines3.push(lines2[i])
        }
    }

    return { code: lines3.join("\n"), insertedLines, deletedLines }
}

function makeIndent(count) {
    let s = ''
    for (let i = 0; i < count; i++) {
        s += ' '
    }
    return s
}

/**
 * インデント部分を取り出す
 * @param {string} line
 * @returns {string}
 */
function getIndent(line) {
    //@ts-ignore
    return /^([ 　・\t]*)/.exec(removeCommentsFromLine(line))[1]
}

/**
 * インデントの個数を数える
 * @param {string} line
 */
function countIndent(line) {
    let cnt = 0
    for (let i = 0; i < line.length; i++) {
        const ch = line.charAt(i)
        if (ch == ' ') {
            cnt++
            continue
        }
        if (ch == '　') {
            cnt += 2
            continue
        }
        if (ch == '・') {
            cnt += 2
            continue
        }
        if (ch == '\t') {
            cnt += 4
            continue
        }
        break
    }
    return cnt
}

function replaceRetMark(src) {
    const prepare = new NakoPrepare()  // `※`, `／/`, `／＊` といったパターン全てに対応するために必要
    const len = src.length
    let result = ''
    let eos = ''
    let i = 0
    while (i < len) {
        const c = src.charAt(i)
        const ch2 = src.substr(i, 2)
        const cPrepared = prepare.convert1ch(c)
        const ch2Prepared = ch2.split('').map((c) => prepare.convert1ch(c)).join("")

        // eosか?
        if (eos != '') {
            // srcのi文字目以降がeosで始まるなら文字列を終了、そうでなければ1文字進める
            if (eos === (eos.length === 1 ? cPrepared : ch2Prepared)) {
                result += src.substr(i, eos.length)
                i += eos.length
                eos = ''
            } else {
                if (c == '\n') {
                    result += SpecialRetMark
                } else {
                    result += c
                }
                i++
            }
            continue
        }
        // 文字列の改行も無視する
        switch (cPrepared) {
            case '"':
            case '\'':
                eos = c
                result += c
                i++
                continue
            case '「':
                eos = '」'
                result += c
                i++
                continue
            case '『':
                eos = '』'
                result += c
                i++
                continue
            case '“':
                eos = '”'
                result += c
                i++
                continue
            case '{':
                eos = '}'
                result += c
                i++
                continue
            case '[':
                eos = ']'
                result += c
                i++
                continue
        }

        switch (ch2) {
            case '🌴':
                eos = '🌴'
                result += ch2
                i += 2
                continue
            case '🌿':
                eos = '🌿'
                result += ch2
                i += 2
                continue
        }

        // 行コメント
        if (cPrepared === '#') {
            eos = '\n'
            result += c
            i++
            continue
        }
        if (ch2Prepared === '//') {
            eos = '\n'
            result += ch2
            i += 2
            continue
        }

        // 範囲コメント
        if (ch2Prepared === '/*') {
            eos = '*/'
            result += ch2
            i += 2
            continue
        }

        result += c
        i++
    }
    return result
}

/**
 * コードのインデントの構造を取得する。
 * 空白行や複数行にまたがる構文を考慮する。
 * インデント構文が有効化されていない場合にも使われる。
 * @param {string} code
 */
function getBlockStructure(code) {
    /** @type {{ lines: number[], pairs: [number, number][], parents: (number | null)[], spaces: string[] }} */
    const result = {
        lines: [],  // 各行のインデント量
        pairs: [],
        parents: [],  // 各行の親の行
        spaces: [],  // 各行のインデントの文字列
    }

    const lines = replaceRetMark(code).split('\n')

    /** @type {number[]} */
    const stack = []
    let lineCount = 0
    let prev = countIndent(lines[0])
    for (const line of lines) {
        const numLines = line.split(SpecialRetMark).length
        const line2 = removeCommentsFromLine(line)
        const current = (line2.replace(/^[ 　・\t]+/, '') === '')
            ? prev
            : countIndent(line2)
        result.lines.push(...Array(numLines).fill(current))
        //@ts-ignore
        result.spaces.push(...Array(numLines).fill(getIndent(line2)))

        if (prev < current) {
            stack.push(lineCount - 1)
        } else if (prev > current) {
            const last = stack.pop()
            if (last !== undefined) {
                result.pairs.push([last, lineCount])
            }
        }

        const parent = stack[stack.length - 1] !== undefined ? stack[stack.length - 1] : null
        result.parents.push(...Array(numLines).fill(parent))

        prev = current
        lineCount += numLines
    }

    // スタックが余ったらコードの末尾とペアにする。
    for (const item of stack) {
        result.pairs.push([item, lineCount])
    }

    return result
}

module.exports = {
    convert,
    getBlockStructure,
    getIndent,
    countIndent,
    isIndentSyntaxEnabled,
}

<commit_msg>インデント構文で「違えばもし」がエラーになる #940<commit_after>const { NakoIndentError } = require('./nako_errors')
const NakoPrepare = require('./nako_prepare')

/**
 * インデント構文指定があればコードを変換する
 * @param {string} code
 * @param {string} filename
 * @returns {{ code: string, insertedLines: number[], deletedLines: { lineNumber: number, len: number }[] }}
 */
function convert(code, filename) {
    // 最初の30行をチェック
    if (isIndentSyntaxEnabled(code)) {
        return convertGo(code, filename)
    }
    return { code, insertedLines: [], deletedLines: [] }
}

// ありえない改行マークを定義
const SpecialRetMark = '🌟🌟改行🌟🌟s4j#WjcSb😀/FcX3🌟🌟'

/**
 * @param {string} code
 * @returns {boolean}
 */
function isIndentSyntaxEnabled(code) {
    // プログラム冒頭に「!インデント構文」があればインデント構文が有効
    const keywords = ['!インデント構文', '!ここまでだるい']
    const lines = code.split('\n', 30)
    for (const line of lines) {
        const s9 = line.substr(0, 8).replace('！', '!')
        if (keywords.indexOf(s9) >= 0) {
            return true
        }
    }
    return false
}

/**
 * ソースコードのある1行の中のコメントを全て取り除く。
 * 事前にreplaceRetMarkによって文字列や範囲コメント内の改行文字が置換されている必要がある。
 * @param {string} src
 * @return {string}
 */
function removeCommentsFromLine(src) {
    const prepare = new NakoPrepare()  // `※`, `／/`, `／＊` といったパターン全てに対応するために必要
    const len = src.length
    let result = ''
    let eos = ''
    let i = 0
    let isComment = false
    while (i < len) {
        const c = src.charAt(i)
        const ch2 = src.substr(i, 2)
        const cPrepared = prepare.convert1ch(c)
        const ch2Prepared = ch2.split('').map((c) => prepare.convert1ch(c)).join("")

        // eosか?
        if (eos != '') {
            // srcのi文字目以降がeosで始まるなら文字列を終了、そうでなければ1文字進める
            if (eos === (eos.length === 1 ? cPrepared : ch2Prepared)) {
                if (!isComment) {
                    result += src.substr(i, eos.length)
                }
                i += eos.length
                isComment = false
                eos = ''
            } else {
                if (!isComment) {
                    result += c
                }
                i++
            }
            continue
        }
        // 文字列の改行も無視する
        switch (cPrepared) {
            case '"':
            case '\'':
                eos = c
                result += c
                i++
                continue
            case '「':
                eos = '」'
                result += c
                i++
                continue
            case '『':
                eos = '』'
                result += c
                i++
                continue
            case '“':
                eos = '”'
                result += c
                i++
                continue
            case '{':
                eos = '}'
                result += c
                i++
                continue
            case '[':
                eos = ']'
                result += c
                i++
                continue
        }

        switch (ch2) {
            case '🌴':
                eos = '🌴'
                result += ch2
                i += 2
                continue
            case '🌿':
                eos = '🌿'
                result += ch2
                i += 2
                continue
        }

        // 行コメント
        if (cPrepared === '#') {
            eos = '\n'
            isComment = true
            i++
            continue
        }
        if (ch2Prepared === '//') {
            eos = '\n'
            isComment = true
            i += 2
            continue
        }

        // 範囲コメント
        if (ch2Prepared === '/*') {
            eos = '*/'
            isComment = true
            i += 2
            continue
        }

        result += c
        i++
    }
    return result
}

/**
 * @param {string} code
 * @param {string} filename
 * @returns {{ code: string, insertedLines: number[], deletedLines: { lineNumber: number, len: number }[] }}
 */
function convertGo(code, filename) {
    /** @type {number[]} */
    const insertedLines = []
    /** @type {{ lineNumber: number, len: number }[]} */
    const deletedLines = []

    const END = 'ここまで‰'
    const code2 = replaceRetMark(code) // 文字列の中などの改行を置換
    const lines = code2.split('\n')
    /** @type {string[]} */
    const lines2 = []
    /** @type {number[]} */
    const indentStack = []
    let lastIndent = 0

    let lineCount = -1
    lines.forEach((line) => {
        lineCount += line.split(SpecialRetMark).length
        // trim line
        if (/^[ 　・\t]*$/.test(line)) {
            deletedLines.push({ lineNumber: lines2.length, len: line.length })
            return
        }
        const lineTrimed = removeCommentsFromLine(line).replace(/^[ 　・\t]+/, '').replace(/\s+$/, '')
        if (lineTrimed === '') {
            lines2.push(line)
            return
        }
        if (lineTrimed === 'ここまで') {
            throw new NakoIndentError(`インデント構文が有効化されているときに『ここまで』を使うことはできません。`, lineCount, filename)
        }

        // check indent
        const indent = countIndent(line)
        if (lastIndent == indent) {
            lines2.push(line)
            return
        }

        // indent
        if (lastIndent < indent) {
            indentStack.push(lastIndent)
            lastIndent = indent
            lines2.push(line)
            return
        }
        // unindent
        if (lastIndent > indent) {
            // 5回
            //   3回
            //     1を表示
            //   |
            // |
            lastIndent = indent
            while (indentStack.length > 0) {
                const n = indentStack.pop()
                if (n == indent) {
                    if (lineTrimed.substr(0, 3) != '違えば') {
                        insertedLines.push(lines2.length)
                        lines2.push(makeIndent(n) + END)
                    }
                    lines2.push(line)
                    return
                }
                if (indent < n) {
                    insertedLines.push(lines2.length)
                    lines2.push(makeIndent(n) + END)
                    continue
                }
            }
        }
    })
    // 残りのインデントを処理
    while (indentStack.length > 0) {
        const n = indentStack.pop()
        insertedLines.push(lines2.length)
        lines2.push(makeIndent(n) + END)
    }
    // 特別マーカーを改行に置換
    /** @type {string[]} */
    const lines3 = []
    for (let i = 0; i < lines2.length; i++) {
        if (lines2[i].includes(SpecialRetMark)) {
            const lines4 = lines2[i].split(SpecialRetMark)

            // 置換されたマーカーの数だけ、それ以降の行数をずらす。
            // unindentによって挿入された行がSpecialRetMarkを含むことはない。
            for (let j = 0; j < insertedLines.length; j++) {
                if (lines3.length < insertedLines[j]) {
                    insertedLines[j] += lines4.length - 1
                }
            }
            for (let j = 0; j < deletedLines.length; j++) {
                if (lines3.length < deletedLines[j].lineNumber) {
                    deletedLines[j].lineNumber += lines4.length - 1
                }
            }

            lines3.push(...lines4)
        } else {
            lines3.push(lines2[i])
        }
    }

    return { code: lines3.join("\n"), insertedLines, deletedLines }
}

function makeIndent(count) {
    let s = ''
    for (let i = 0; i < count; i++) {
        s += ' '
    }
    return s
}

/**
 * インデント部分を取り出す
 * @param {string} line
 * @returns {string}
 */
function getIndent(line) {
    //@ts-ignore
    return /^([ 　・\t]*)/.exec(removeCommentsFromLine(line))[1]
}

/**
 * インデントの個数を数える
 * @param {string} line
 */
function countIndent(line) {
    let cnt = 0
    for (let i = 0; i < line.length; i++) {
        const ch = line.charAt(i)
        if (ch == ' ') {
            cnt++
            continue
        }
        if (ch == '　') {
            cnt += 2
            continue
        }
        if (ch == '・') {
            cnt += 2
            continue
        }
        if (ch == '\t') {
            cnt += 4
            continue
        }
        break
    }
    return cnt
}

function replaceRetMark(src) {
    const prepare = new NakoPrepare()  // `※`, `／/`, `／＊` といったパターン全てに対応するために必要
    const len = src.length
    let result = ''
    let eos = ''
    let i = 0
    while (i < len) {
        const c = src.charAt(i)
        const ch2 = src.substr(i, 2)
        const cPrepared = prepare.convert1ch(c)
        const ch2Prepared = ch2.split('').map((c) => prepare.convert1ch(c)).join("")

        // eosか?
        if (eos != '') {
            // srcのi文字目以降がeosで始まるなら文字列を終了、そうでなければ1文字進める
            if (eos === (eos.length === 1 ? cPrepared : ch2Prepared)) {
                result += src.substr(i, eos.length)
                i += eos.length
                eos = ''
            } else {
                if (c == '\n') {
                    result += SpecialRetMark
                } else {
                    result += c
                }
                i++
            }
            continue
        }
        // 文字列の改行も無視する
        switch (cPrepared) {
            case '"':
            case '\'':
                eos = c
                result += c
                i++
                continue
            case '「':
                eos = '」'
                result += c
                i++
                continue
            case '『':
                eos = '』'
                result += c
                i++
                continue
            case '“':
                eos = '”'
                result += c
                i++
                continue
            case '{':
                eos = '}'
                result += c
                i++
                continue
            case '[':
                eos = ']'
                result += c
                i++
                continue
        }

        switch (ch2) {
            case '🌴':
                eos = '🌴'
                result += ch2
                i += 2
                continue
            case '🌿':
                eos = '🌿'
                result += ch2
                i += 2
                continue
        }

        // 行コメント
        if (cPrepared === '#') {
            eos = '\n'
            result += c
            i++
            continue
        }
        if (ch2Prepared === '//') {
            eos = '\n'
            result += ch2
            i += 2
            continue
        }

        // 範囲コメント
        if (ch2Prepared === '/*') {
            eos = '*/'
            result += ch2
            i += 2
            continue
        }

        result += c
        i++
    }
    return result
}

/**
 * コードのインデントの構造を取得する。
 * 空白行や複数行にまたがる構文を考慮する。
 * インデント構文が有効化されていない場合にも使われる。
 * @param {string} code
 */
function getBlockStructure(code) {
    /** @type {{ lines: number[], pairs: [number, number][], parents: (number | null)[], spaces: string[] }} */
    const result = {
        lines: [],  // 各行のインデント量
        pairs: [],
        parents: [],  // 各行の親の行
        spaces: [],  // 各行のインデントの文字列
    }

    const lines = replaceRetMark(code).split('\n')

    /** @type {number[]} */
    const stack = []
    let lineCount = 0
    let prev = countIndent(lines[0])
    for (const line of lines) {
        const numLines = line.split(SpecialRetMark).length
        const line2 = removeCommentsFromLine(line)
        const current = (line2.replace(/^[ 　・\t]+/, '') === '')
            ? prev
            : countIndent(line2)
        result.lines.push(...Array(numLines).fill(current))
        //@ts-ignore
        result.spaces.push(...Array(numLines).fill(getIndent(line2)))

        if (prev < current) {
            stack.push(lineCount - 1)
        } else if (prev > current) {
            const last = stack.pop()
            if (last !== undefined) {
                result.pairs.push([last, lineCount])
            }
        }

        const parent = stack[stack.length - 1] !== undefined ? stack[stack.length - 1] : null
        result.parents.push(...Array(numLines).fill(parent))

        prev = current
        lineCount += numLines
    }

    // スタックが余ったらコードの末尾とペアにする。
    for (const item of stack) {
        result.pairs.push([item, lineCount])
    }

    return result
}

module.exports = {
    convert,
    getBlockStructure,
    getIndent,
    countIndent,
    isIndentSyntaxEnabled,
}

<|endoftext|>