<commit_before>var chai = require('chai')
  , expect = chai.expect
  , sinon = require('sinon')
  , sinonChai = require('sinon-chai')
  , utils = require('../utils.js')
  ;
chai.use(sinonChai);

describe('Page', () => {
  var Page = utils.models.Page,
    User   = utils.models.User,
    conn   = utils.mongoose.connection,
    createdPages,
    createdUsers;

  before(done => {
    Promise.resolve().then(() => {
      var userFixture = [
        {name: 'Anon 0', username: 'anonymous0', email: 'anonymous0@example.com'},
        {name: 'Anon 1', username: 'anonymous1', email: 'anonymous1@example.com'}
      ];

      return testDBUtil.generateFixture(conn, 'User', userFixture);
    }).then(testUsers => {
      createdUsers = testUsers;
      var testUser0 = testUsers[0];

      var fixture = [
        {
          path: '/user/anonymous/memo',
          grant: Page.GRANT_RESTRICTED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/public',
          grant: Page.GRANT_PUBLIC,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/restricted',
          grant: Page.GRANT_RESTRICTED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/specified',
          grant: Page.GRANT_SPECIFIED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/owner',
          grant: Page.GRANT_OWNER,
          grantedUsers: [testUser0],
          creator: testUser0,
        },
        {
          path: '/page/for/extended',
          grant: Page.GRANT_PUBLIC,
          creator: testUser0,
          extended: {hoge: 1}
        },
      ];

      return testDBUtil.generateFixture(conn, 'Page', fixture)
      .then(pages => {
        createdPages = pages;
        done();
      });
    });
  });

  describe('.isPublic', () => {
    context('with a public page', () => {
      it('should return true', done => {
        Page.findOne({path: '/grant/public'}, (err, page) => {
          expect(err).to.be.null;
          expect(page.isPublic()).to.be.equal(true);
          done();
        });
      });
    });

    ['restricted', 'specified', 'owner'].forEach(grant => {
      context('with a ' + grant + ' page', function() {
        it('should return false', done => {
          Page.findOne({path: '/grant/' + grant}, (err, page) => {
            expect(err).to.be.null;
            expect(page.isPublic()).to.be.equal(false);
            done();
          });
        });
      });
    });
  });

  describe('.getDeletedPageName', function() {
    it('should return trash page name', function() {
      expect(Page.getDeletedPageName('/hoge')).to.be.equal('/trash/hoge');
      expect(Page.getDeletedPageName('hoge')).to.be.equal('/trash/hoge');
    });
  });
  describe('.getRevertDeletedPageName', function() {
    it('should return reverted trash page name', function() {
      expect(Page.getRevertDeletedPageName('/hoge')).to.be.equal('/hoge');
      expect(Page.getRevertDeletedPageName('/trash/hoge')).to.be.equal('/hoge');
      expect(Page.getRevertDeletedPageName('/trash/hoge/trash')).to.be.equal('/hoge/trash');
    });
  });

  describe('.isDeletableName', function() {
    it('should decide deletable or not', function() {
      expect(Page.isDeletableName('/hoge')).to.be.true;
      expect(Page.isDeletableName('/user/xxx')).to.be.false;
      expect(Page.isDeletableName('/user/xxx123')).to.be.false;
      expect(Page.isDeletableName('/user/xxx/')).to.be.true;
      expect(Page.isDeletableName('/user/xxx/hoge')).to.be.true;
    });
  });

  describe('.isCreatableName', function() {
    it('should decide creatable or not', function() {
      expect(Page.isCreatableName('/hoge')).to.be.true;

      // edge cases
      expect(Page.isCreatableName('/me')).to.be.false;
      expect(Page.isCreatableName('/me/')).to.be.false;
      expect(Page.isCreatableName('/me/x')).to.be.false;
      expect(Page.isCreatableName('/meeting')).to.be.true;
      expect(Page.isCreatableName('/meeting/x')).to.be.true;

      // end with "edit"
      expect(Page.isCreatableName('/meeting/edit')).to.be.false;

      // under score
      expect(Page.isCreatableName('/_')).to.be.false;
      expect(Page.isCreatableName('/_r/x')).to.be.false;
      expect(Page.isCreatableName('/_api')).to.be.false;
      expect(Page.isCreatableName('/_apix')).to.be.false;
      expect(Page.isCreatableName('/_api/x')).to.be.false;

      expect(Page.isCreatableName('/hoge/xx.md')).to.be.false;

      // start with https?
      expect(Page.isCreatableName('/http://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('/https://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('http://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('https://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;

      expect(Page.isCreatableName('/ the / path / with / space')).to.be.false;

      var forbidden = ['installer', 'register', 'login', 'logout', 'admin', 'files', 'trash', 'paste', 'comments'];
      for (var i = 0; i < forbidden.length ; i++) {
        var pn = forbidden[i];
        expect(Page.isCreatableName('/' + pn + '')).to.be.false;
        expect(Page.isCreatableName('/' + pn + '/')).to.be.false;
        expect(Page.isCreatableName('/' + pn + '/abc')).to.be.false;
      }

      var forbidden = ['bookmarks', 'comments', 'activities', 'pages', 'recent-create', 'recent-edit'];
      for (var i = 0; i < forbidden.length ; i++) {
        var pn = forbidden[i];
        expect(Page.isCreatableName('/user/aoi/' + pn)).to.be.false;
        expect(Page.isCreatableName('/user/aoi/x/' + pn)).to.be.true;
      }
    });
  });

  describe('.isCreator', function() {
    context('with creator', function() {
      it('should return true', done => {
        User.findOne({email: 'anonymous0@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            expect(page.isCreator(user)).to.be.equal(true);
            done();
          })
        });
      });
    });

    context('with non-creator', function() {
      it('should return false', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            expect(page.isCreator(user)).to.be.equal(false);
            done();
          })
        });
      });
    });
  });

  describe('.isGrantedFor', function() {
    context('with a granted user', function() {
      it('should return true', done => {
        User.findOne({email: 'anonymous0@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(true);
            done();
          });
        });
      });
    });

    context('with a public page', function() {
      it('should return true', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/grant/public'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(true);
            done();
          });
        });
      });
    });

    context('with a restricted page and an user who has no grant', function() {
      it('should return false', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/grant/restricted'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(false);
            done();
          });
        });
      });
    });
  });

  describe('Extended field', () => {
    context('Slack Channel.', () => {
      it('should be empty', done => {
        Page.findOne({path: '/page/for/extended'}, (err, page) => {
          expect(page.extended.hoge).to.be.equal(1);
          expect(page.getSlackChannel()).to.be.equal('');
          done();
        })
      });

      it('set slack channel and should get it and should keep hoge ', done => {
        Page.findOne({path: '/page/for/extended'}, (err, page) => {
          page.updateSlackChannel('slack-channel1')
          .then(data => {
            Page.findOne({path: '/page/for/extended'}, (err, page) => {
              expect(page.extended.hoge).to.be.equal(1);
              expect(page.getSlackChannel()).to.be.equal('slack-channel1');
              done();
            });
          })
        });
      });

    });
  });

  describe('Normalize path', () => {
    context('Normalize', function() {
      it('should start with slash', done => {
        expect(Page.normalizePath('hoge/fuga')).to.equal('/hoge/fuga');
        done();
      });

      it('should trim spaces of slash', done => {
        expect(Page.normalizePath('/ hoge / fuga')).to.equal('/hoge/fuga');
        done();
      });
    });
  });

  describe('.findPage', () => {
    context('findPageById', () => {
      it('should find page', done => {
        const pageToFind = createdPages[0];
        Page.findPageById(pageToFind._id)
        .then(pageData => {
          expect(pageData.path).to.equal(pageToFind.path);
          done();
        });
      });
    });

    context('findPageByIdAndGrantedUser', function() {
      it('should find page', done => {
        const pageToFind = createdPages[0];
        const grantedUser = createdUsers[0];
        Page.findPageByIdAndGrantedUser(pageToFind._id, grantedUser)
        .then(pageData => {
          expect(pageData.path).to.equal(pageToFind.path);
          done();
        });
      });

      it('should error by grant', done => {
        const pageToFind = createdPages[0];
        const grantedUser = createdUsers[1];
        Page.findPageByIdAndGrantedUser(pageToFind._id, grantedUser)
        .then(pageData => {
        }).catch(err => {
          expect(err).to.instanceof(Error);
          done();
        });
      });
    });
  });

});
<commit_msg>Replace to arrow function<commit_after>var chai = require('chai')
  , expect = chai.expect
  , sinon = require('sinon')
  , sinonChai = require('sinon-chai')
  , utils = require('../utils.js')
  ;
chai.use(sinonChai);

describe('Page', () => {
  var Page = utils.models.Page,
    User   = utils.models.User,
    conn   = utils.mongoose.connection,
    createdPages,
    createdUsers;

  before(done => {
    Promise.resolve().then(() => {
      var userFixture = [
        {name: 'Anon 0', username: 'anonymous0', email: 'anonymous0@example.com'},
        {name: 'Anon 1', username: 'anonymous1', email: 'anonymous1@example.com'}
      ];

      return testDBUtil.generateFixture(conn, 'User', userFixture);
    }).then(testUsers => {
      createdUsers = testUsers;
      var testUser0 = testUsers[0];

      var fixture = [
        {
          path: '/user/anonymous/memo',
          grant: Page.GRANT_RESTRICTED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/public',
          grant: Page.GRANT_PUBLIC,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/restricted',
          grant: Page.GRANT_RESTRICTED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/specified',
          grant: Page.GRANT_SPECIFIED,
          grantedUsers: [testUser0],
          creator: testUser0
        },
        {
          path: '/grant/owner',
          grant: Page.GRANT_OWNER,
          grantedUsers: [testUser0],
          creator: testUser0,
        },
        {
          path: '/page/for/extended',
          grant: Page.GRANT_PUBLIC,
          creator: testUser0,
          extended: {hoge: 1}
        },
      ];

      return testDBUtil.generateFixture(conn, 'Page', fixture)
      .then(pages => {
        createdPages = pages;
        done();
      });
    });
  });

  describe('.isPublic', () => {
    context('with a public page', () => {
      it('should return true', done => {
        Page.findOne({path: '/grant/public'}, (err, page) => {
          expect(err).to.be.null;
          expect(page.isPublic()).to.be.equal(true);
          done();
        });
      });
    });

    ['restricted', 'specified', 'owner'].forEach(grant => {
      context('with a ' + grant + ' page', () => {
        it('should return false', done => {
          Page.findOne({path: '/grant/' + grant}, (err, page) => {
            expect(err).to.be.null;
            expect(page.isPublic()).to.be.equal(false);
            done();
          });
        });
      });
    });
  });

  describe('.getDeletedPageName', () => {
    it('should return trash page name', () => {
      expect(Page.getDeletedPageName('/hoge')).to.be.equal('/trash/hoge');
      expect(Page.getDeletedPageName('hoge')).to.be.equal('/trash/hoge');
    });
  });
  describe('.getRevertDeletedPageName', () => {
    it('should return reverted trash page name', () => {
      expect(Page.getRevertDeletedPageName('/hoge')).to.be.equal('/hoge');
      expect(Page.getRevertDeletedPageName('/trash/hoge')).to.be.equal('/hoge');
      expect(Page.getRevertDeletedPageName('/trash/hoge/trash')).to.be.equal('/hoge/trash');
    });
  });

  describe('.isDeletableName', () => {
    it('should decide deletable or not', () => {
      expect(Page.isDeletableName('/hoge')).to.be.true;
      expect(Page.isDeletableName('/user/xxx')).to.be.false;
      expect(Page.isDeletableName('/user/xxx123')).to.be.false;
      expect(Page.isDeletableName('/user/xxx/')).to.be.true;
      expect(Page.isDeletableName('/user/xxx/hoge')).to.be.true;
    });
  });

  describe('.isCreatableName', () => {
    it('should decide creatable or not', () => {
      expect(Page.isCreatableName('/hoge')).to.be.true;

      // edge cases
      expect(Page.isCreatableName('/me')).to.be.false;
      expect(Page.isCreatableName('/me/')).to.be.false;
      expect(Page.isCreatableName('/me/x')).to.be.false;
      expect(Page.isCreatableName('/meeting')).to.be.true;
      expect(Page.isCreatableName('/meeting/x')).to.be.true;

      // end with "edit"
      expect(Page.isCreatableName('/meeting/edit')).to.be.false;

      // under score
      expect(Page.isCreatableName('/_')).to.be.false;
      expect(Page.isCreatableName('/_r/x')).to.be.false;
      expect(Page.isCreatableName('/_api')).to.be.false;
      expect(Page.isCreatableName('/_apix')).to.be.false;
      expect(Page.isCreatableName('/_api/x')).to.be.false;

      expect(Page.isCreatableName('/hoge/xx.md')).to.be.false;

      // start with https?
      expect(Page.isCreatableName('/http://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('/https://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('http://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;
      expect(Page.isCreatableName('https://demo.crowi.wiki/user/sotarok/hoge')).to.be.false;

      expect(Page.isCreatableName('/ the / path / with / space')).to.be.false;

      var forbidden = ['installer', 'register', 'login', 'logout', 'admin', 'files', 'trash', 'paste', 'comments'];
      for (var i = 0; i < forbidden.length ; i++) {
        var pn = forbidden[i];
        expect(Page.isCreatableName('/' + pn + '')).to.be.false;
        expect(Page.isCreatableName('/' + pn + '/')).to.be.false;
        expect(Page.isCreatableName('/' + pn + '/abc')).to.be.false;
      }

      var forbidden = ['bookmarks', 'comments', 'activities', 'pages', 'recent-create', 'recent-edit'];
      for (var i = 0; i < forbidden.length ; i++) {
        var pn = forbidden[i];
        expect(Page.isCreatableName('/user/aoi/' + pn)).to.be.false;
        expect(Page.isCreatableName('/user/aoi/x/' + pn)).to.be.true;
      }
    });
  });

  describe('.isCreator', () => {
    context('with creator', () => {
      it('should return true', done => {
        User.findOne({email: 'anonymous0@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            expect(page.isCreator(user)).to.be.equal(true);
            done();
          })
        });
      });
    });

    context('with non-creator', () => {
      it('should return false', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            expect(page.isCreator(user)).to.be.equal(false);
            done();
          })
        });
      });
    });
  });

  describe('.isGrantedFor', () => {
    context('with a granted user', () => {
      it('should return true', done => {
        User.findOne({email: 'anonymous0@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/user/anonymous/memo'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(true);
            done();
          });
        });
      });
    });

    context('with a public page', () => {
      it('should return true', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/grant/public'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(true);
            done();
          });
        });
      });
    });

    context('with a restricted page and an user who has no grant', () => {
      it('should return false', done => {
        User.findOne({email: 'anonymous1@example.com'}, (err, user) => {
          if (err) { done(err); }

          Page.findOne({path: '/grant/restricted'}, (err, page) => {
            if (err) { done(err); }

            expect(page.isGrantedFor(user)).to.be.equal(false);
            done();
          });
        });
      });
    });
  });

  describe('Extended field', () => {
    context('Slack Channel.', () => {
      it('should be empty', done => {
        Page.findOne({path: '/page/for/extended'}, (err, page) => {
          expect(page.extended.hoge).to.be.equal(1);
          expect(page.getSlackChannel()).to.be.equal('');
          done();
        })
      });

      it('set slack channel and should get it and should keep hoge ', done => {
        Page.findOne({path: '/page/for/extended'}, (err, page) => {
          page.updateSlackChannel('slack-channel1')
          .then(data => {
            Page.findOne({path: '/page/for/extended'}, (err, page) => {
              expect(page.extended.hoge).to.be.equal(1);
              expect(page.getSlackChannel()).to.be.equal('slack-channel1');
              done();
            });
          })
        });
      });

    });
  });

  describe('Normalize path', () => {
    context('Normalize', () => {
      it('should start with slash', done => {
        expect(Page.normalizePath('hoge/fuga')).to.equal('/hoge/fuga');
        done();
      });

      it('should trim spaces of slash', done => {
        expect(Page.normalizePath('/ hoge / fuga')).to.equal('/hoge/fuga');
        done();
      });
    });
  });

  describe('.findPage', () => {
    context('findPageById', () => {
      it('should find page', done => {
        const pageToFind = createdPages[0];
        Page.findPageById(pageToFind._id)
        .then(pageData => {
          expect(pageData.path).to.equal(pageToFind.path);
          done();
        });
      });
    });

    context('findPageByIdAndGrantedUser', () => {
      it('should find page', done => {
        const pageToFind = createdPages[0];
        const grantedUser = createdUsers[0];
        Page.findPageByIdAndGrantedUser(pageToFind._id, grantedUser)
        .then(pageData => {
          expect(pageData.path).to.equal(pageToFind.path);
          done();
        });
      });

      it('should error by grant', done => {
        const pageToFind = createdPages[0];
        const grantedUser = createdUsers[1];
        Page.findPageByIdAndGrantedUser(pageToFind._id, grantedUser)
        .then(pageData => {
        }).catch(err => {
          expect(err).to.instanceof(Error);
          done();
        });
      });
    });
  });

});
<|endoftext|>


<commit_before>import React from 'react';
import './EditorForm.scss';
import update from 'immutability-helper';
import includes from 'lodash/includes';
import capitalize from 'lodash/capitalize';
import sortBy from 'lodash/sortBy';

import DropdownInput from '../DropdownInput/DropdownInput';
import { validateConditionalRules } from '../../../utils/validators';

export class EditorForm extends React.Component {
  constructor (props) {
    super(props);
    this.generateAttributeElements = this.generateAttributeElements.bind(this);
    this.getActiveValue = this.getActiveValue.bind(this);
    this.getCheckedState = this.getCheckedState.bind(this);
    this.closeEditorForm = this.closeEditorForm.bind(this);
    this.onChange = this.onChange.bind(this);
    this.onFormInputChange = this.onFormInputChange.bind(this);
    this.state = {
      newAttributes: this.initializeAttributes(this.props.attributeTypes)
    };
  }

  initializeAttributes (attributeTypes) {
    const { attributes } = this.props;
    let initialState = {};
    for (const key in attributeTypes) {
      if (attributeTypes.hasOwnProperty(key)) {
        initialState = Object.assign({}, initialState, {
          [key]: {
            value: attributes[key] ? attributes[key] : null,
            checked: true
          }
        });
      }
    }
    return initialState;
  }

  onChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  onFormInputChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  getActiveValue (key) {
    if (this.state.newAttributes[key].value) {
      return this.state.newAttributes[key].value;
    }
  }

  getCheckedState (key) {
    if (this.props.editorConfig.action === 'edit' && !this.state.newAttributes[key].value) {
      return false;
    } else {
      return this.state.newAttributes[key].checked;
    }
  }

  getAttributesToShow (attributeTypes, attributes) {
    const { newAttributes } = this.state;
    const attributesToShow = [];
    const getAttributeKeys = (attributes) => {
      const attributeKeys = [];
      for (const key in attributes) {
        attributeKeys.push(key);
      }
      return attributeKeys;
    };

    for (const attributeType in attributeTypes) {
      if (includes(attributeTypes[attributeType].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[attributeType].requiredIf.length) {
          if (validateConditionalRules(attributeType, attributeTypes, newAttributes)) {
            attributesToShow.push(attributeType);
            continue;
          }
        }
        if (attributeTypes[attributeType].required ||
          includes(getAttributeKeys(attributes), attributeType)) {
          attributesToShow.push(attributeType);
          continue;
        }
      }
    }

    return this.prepareAttributes(attributesToShow);
  }

  getComplementAttributes (attributeTypes, attributesToShow) {
    const { newAttributes } = this.state;
    const complementAttributes = [];

    for (const key in attributeTypes) {
      if (includes(attributeTypes[key].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[key].requiredIf.length) {
          if (validateConditionalRules(key, attributeTypes, newAttributes)) {
            complementAttributes.push(key);
          }
        } else {
          complementAttributes.push(key);
        }
      }
    }

    return this.prepareAttributes(complementAttributes);
  }

  prepareAttributes (attributesToShow) {
    attributesToShow.splice(attributesToShow.indexOf(`${capitalize(this.props.editorConfig.type)}Type`), 1);
    attributesToShow.splice(attributesToShow.indexOf('TypeSpecifier'), 1);

    const sortedAttributes = sortBy(attributesToShow, (attribute) => (
      this.props.attributeTypes[attribute].index
    ));

    return sortedAttributes;
  }

  mapOptions (array) {
    return array.map(item => ({
      value: item.value,
      label: item.value
    }));
  }

  generateAttributeElements (attributeTypes) {
    let attributesToShow = this.getAttributesToShow(attributeTypes, this.props.attributes);
    if (this.props.editorConfig.action === 'complement') {
      attributesToShow = this.getComplementAttributes(attributeTypes, attributesToShow);
    }
    const attributeElements = [];

    if (attributesToShow.length) {
      for (const key of attributesToShow) {
        if (attributeTypes.hasOwnProperty(key)) {
          if (attributeTypes[key].values.length) {
            const options = this.mapOptions(attributeTypes[key].values);
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}/>

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                <DropdownInput
                  keyValue={key}
                  type={'form'}
                  selectClassName={'form-control editor-form__select'}
                  inputClassName={'form-control edit-attribute__input'}
                  disabled={!this.state.newAttributes[key].checked}
                  valueState={this.getActiveValue(key)}
                  options={options}
                  onChange={this.onChange}
                  onInputChange={this.onFormInputChange}
                  onSubmit={() => null}
                />

            );
          } else if (attributeTypes[key].values.length === 0) {
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}
                />

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                { key === 'AdditionalInformation'
                  ? <textarea
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                  : <input
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                }

            );
          }
        }
      }
    } else {
      attributeElements.push(

Ei täydennettäviä metatietoja

      );
    }

    return attributeElements;
  }

  generateDropdown (elementTypes) {
    const type = `${capitalize(this.props.editorConfig.type)}Type`;

    return (
      <DropdownInput
        keyValue={type}
        type={'form'}
        selectClassName={'form-control col-xs-6'}
        inputClassName={'form-control edit-attribute__input'}
        valueState={this.state.newAttributes[type] ? this.state.newAttributes[type].value : ''}
        options={elementTypes}
        onChange={this.onChange}
        onInputChange={this.onFormInputChange}
        onSubmit={() => null}
      />
    );
  }

  filterAttributes (attributes) {
    const filteredAttributes = Object.assign({}, attributes);
    for (const key in filteredAttributes) {
      if (!filteredAttributes[key].value) {
        delete filteredAttributes[key];
      }
    }
    return filteredAttributes;
  }

  editMetaData (e) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.editMetaDataWithForm(this.filterAttributes(newAttributes));
    this.props.displayMessage({
      title: 'Metatiedot',
      body: 'Tietojen muokkaus onnistui!'
    });
  }

  addRecord (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;

    this.props.elementConfig.createRecord(
      this.filterAttributes(newAttributes),
      targetId
    );
    this.props.displayMessage({
      title: 'Asiakirja',
      body: 'Asiakirjan lisäys onnistui!'
    });
  }

  editElement (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.elementConfig.editWithForm(
      this.filterAttributes(newAttributes),
      targetId
    );
  }

  resolveLabel () {
    const { type, action } = this.props.editorConfig;

    switch (type) {
      case 'function':
        return 'Metatiedot';
      case 'phase':
        if (action === 'add') {
          return 'Uusi käsittelyvaihe';
        }
        if (action === 'edit') {
          return 'Muokkaa käsittelyvaihetta';
        }
        if (action === 'complement') {
          return 'Täydennä käsittelyvaihetta';
        }
        break;
      case 'action':
        if (action === 'add') {
          return 'Uusi toimenpide';
        }
        if (action === 'edit') {
          return 'Muokkaa toimenpidettä';
        }
        if (action === 'complement') {
          return 'Täydennä toimenpidettä';
        }
        break;
      case 'record':
        if (action === 'add') {
          return 'Uusi asiakirja';
        }
        if (action === 'edit') {
          return 'Muokkaa asiakirjaa';
        }
        if (action === 'complement') {
          return 'Täydennä asiakirjaa';
        }
        break;
    }
  }

  resolveOnSubmit (e, targetId) {
    const { action, type } = this.props.editorConfig;
    switch (type) {
      case 'function':
        if (action === 'edit' || action === 'complement') {
          this.editMetaData(e);
        }
        break;
      case 'phase':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Käsittelyvaihe',
            body: 'Käsittelyvaiheen muokkaus onnistui!'
          });
        }
        break;
      case 'action':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Toimenpide',
            body: 'Toimenpiteen muokkaus onnistui!'
          });
        }
        break;
      case 'record':
        if (action === 'add') {
          this.addRecord(e, targetId);
        }
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Asiakirja',
            body: 'Asiakirjan muokkaus onnistui!'
          });
        }
    }
  }

  closeEditorForm (e) {
    e.preventDefault();
    this.props.closeEditorForm();
  }

  renderDescriptions () {
    const { attributeTypes } = this.props;
    const typeName = attributeTypes
      ? attributeTypes[`${capitalize(this.props.editorConfig.type)}Type`].name
      : '';
    const dropdownInput = this.generateDropdown(this.props.elementConfig.elementTypes);

    return (

          {attributeTypes ? attributeTypes.TypeSpecifier.name : ''}

          <input
            className='col-xs-6 form-control edit-attribute__input'
            placeholder='Tarkenne'
            value={this.state.newAttributes.TypeSpecifier.value || ''}
            onChange={(e) => this.onChange(e.target.value, 'TypeSpecifier', 'value')}/>

          {typeName}

          { dropdownInput }

    );
  }

  render () {
    const { attributeTypes, targetId } = this.props;
    const attributeElements = this.generateAttributeElements(attributeTypes);

    return (

{this.resolveLabel()}

        <form onSubmit={(e) => this.resolveOnSubmit(e, targetId)}
              className='editor-form'>
          { this.props.editorConfig.type !== 'function' ? this.renderDescriptions() : null }
          { attributeElements }

            <button
              className='btn btn-primary pull-right editor-form__submit'
              type='submit'>
              Valmis
            </button>
            <button
              className='btn btn-danger pull-right editor-form__cancel'
              onClick={(e) => this.closeEditorForm(e)}>
              Peruuta
            </button>

        </form>

    );
  }
}

EditorForm.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  attributes: React.PropTypes.object.isRequired,
  closeEditorForm: React.PropTypes.func.isRequired,
  displayMessage: React.PropTypes.func.isRequired,
  editMetaDataWithForm: React.PropTypes.func,
  editorConfig: React.PropTypes.shape({
    type: React.PropTypes.string.isRequired,
    action: React.PropTypes.string.isRequired
  }),
  elementConfig: React.PropTypes.shape({
    editWithForm: React.PropTypes.func,
    elementTypes: React.PropTypes.object.isRequired,
    createRecord: React.PropTypes.func // only records created with editorform
  }),
  targetId: React.PropTypes.string
};

export default EditorForm;
<commit_msg>Flip base attributes in EditorForm, hide no-fields text<commit_after>import React from 'react';
import './EditorForm.scss';
import update from 'immutability-helper';
import includes from 'lodash/includes';
import capitalize from 'lodash/capitalize';
import sortBy from 'lodash/sortBy';

import DropdownInput from '../DropdownInput/DropdownInput';
import { validateConditionalRules } from '../../../utils/validators';

export class EditorForm extends React.Component {
  constructor (props) {
    super(props);
    this.generateAttributeElements = this.generateAttributeElements.bind(this);
    this.getActiveValue = this.getActiveValue.bind(this);
    this.getCheckedState = this.getCheckedState.bind(this);
    this.closeEditorForm = this.closeEditorForm.bind(this);
    this.onChange = this.onChange.bind(this);
    this.onFormInputChange = this.onFormInputChange.bind(this);
    this.state = {
      newAttributes: this.initializeAttributes(this.props.attributeTypes)
    };
  }

  initializeAttributes (attributeTypes) {
    const { attributes } = this.props;
    let initialState = {};
    for (const key in attributeTypes) {
      if (attributeTypes.hasOwnProperty(key)) {
        initialState = Object.assign({}, initialState, {
          [key]: {
            value: attributes[key] ? attributes[key] : null,
            checked: true
          }
        });
      }
    }
    return initialState;
  }

  onChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  onFormInputChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  getActiveValue (key) {
    if (this.state.newAttributes[key].value) {
      return this.state.newAttributes[key].value;
    }
  }

  getCheckedState (key) {
    if (this.props.editorConfig.action === 'edit' && !this.state.newAttributes[key].value) {
      return false;
    } else {
      return this.state.newAttributes[key].checked;
    }
  }

  getAttributesToShow (attributeTypes, attributes) {
    const { newAttributes } = this.state;
    const attributesToShow = [];
    const getAttributeKeys = (attributes) => {
      const attributeKeys = [];
      for (const key in attributes) {
        attributeKeys.push(key);
      }
      return attributeKeys;
    };

    for (const attributeType in attributeTypes) {
      if (includes(attributeTypes[attributeType].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[attributeType].requiredIf.length) {
          if (validateConditionalRules(attributeType, attributeTypes, newAttributes)) {
            attributesToShow.push(attributeType);
            continue;
          }
        }
        if (attributeTypes[attributeType].required ||
          includes(getAttributeKeys(attributes), attributeType)) {
          attributesToShow.push(attributeType);
          continue;
        }
      }
    }

    return this.prepareAttributes(attributesToShow);
  }

  getComplementAttributes (attributeTypes, attributesToShow) {
    const { newAttributes } = this.state;
    const complementAttributes = [];

    for (const key in attributeTypes) {
      if (includes(attributeTypes[key].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[key].requiredIf.length) {
          if (validateConditionalRules(key, attributeTypes, newAttributes)) {
            complementAttributes.push(key);
          }
        } else {
          complementAttributes.push(key);
        }
      }
    }

    return this.prepareAttributes(complementAttributes);
  }

  prepareAttributes (attributesToShow) {
    attributesToShow.splice(attributesToShow.indexOf(`${capitalize(this.props.editorConfig.type)}Type`), 1);
    attributesToShow.splice(attributesToShow.indexOf('TypeSpecifier'), 1);

    const sortedAttributes = sortBy(attributesToShow, (attribute) => (
      this.props.attributeTypes[attribute].index
    ));

    return sortedAttributes;
  }

  mapOptions (array) {
    return array.map(item => ({
      value: item.value,
      label: item.value
    }));
  }

  generateAttributeElements (attributeTypes) {
    let attributesToShow = this.getAttributesToShow(attributeTypes, this.props.attributes);
    if (this.props.editorConfig.action === 'complement') {
      attributesToShow = this.getComplementAttributes(attributeTypes, attributesToShow);
    }
    const attributeElements = [];

    if (attributesToShow.length) {
      for (const key of attributesToShow) {
        if (attributeTypes.hasOwnProperty(key)) {
          if (attributeTypes[key].values.length) {
            const options = this.mapOptions(attributeTypes[key].values);
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}/>

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                <DropdownInput
                  keyValue={key}
                  type={'form'}
                  selectClassName={'form-control editor-form__select'}
                  inputClassName={'form-control edit-attribute__input'}
                  disabled={!this.state.newAttributes[key].checked}
                  valueState={this.getActiveValue(key)}
                  options={options}
                  onChange={this.onChange}
                  onInputChange={this.onFormInputChange}
                  onSubmit={() => null}
                />

            );
          } else if (attributeTypes[key].values.length === 0) {
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}
                />

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                { key === 'AdditionalInformation'
                  ? <textarea
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                  : <input
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                }

            );
          }
        }
      }
    } else {
      attributeElements.push(
        //
Ei täydennettäviä metatietoja

      );
    }

    return attributeElements;
  }

  generateDropdown (elementTypes) {
    const type = `${capitalize(this.props.editorConfig.type)}Type`;

    return (
      <DropdownInput
        keyValue={type}
        type={'form'}
        selectClassName={'form-control col-xs-6'}
        inputClassName={'form-control edit-attribute__input'}
        valueState={this.state.newAttributes[type] ? this.state.newAttributes[type].value : ''}
        options={elementTypes}
        onChange={this.onChange}
        onInputChange={this.onFormInputChange}
        onSubmit={() => null}
      />
    );
  }

  filterAttributes (attributes) {
    const filteredAttributes = Object.assign({}, attributes);
    for (const key in filteredAttributes) {
      if (!filteredAttributes[key].value) {
        delete filteredAttributes[key];
      }
    }
    return filteredAttributes;
  }

  editMetaData (e) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.editMetaDataWithForm(this.filterAttributes(newAttributes));
    this.props.displayMessage({
      title: 'Metatiedot',
      body: 'Tietojen muokkaus onnistui!'
    });
  }

  addRecord (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;

    this.props.elementConfig.createRecord(
      this.filterAttributes(newAttributes),
      targetId
    );
    this.props.displayMessage({
      title: 'Asiakirja',
      body: 'Asiakirjan lisäys onnistui!'
    });
  }

  editElement (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.elementConfig.editWithForm(
      this.filterAttributes(newAttributes),
      targetId
    );
  }

  resolveLabel () {
    const { type, action } = this.props.editorConfig;

    switch (type) {
      case 'function':
        return 'Metatiedot';
      case 'phase':
        if (action === 'add') {
          return 'Uusi käsittelyvaihe';
        }
        if (action === 'edit') {
          return 'Muokkaa käsittelyvaihetta';
        }
        if (action === 'complement') {
          return 'Täydennä käsittelyvaihetta';
        }
        break;
      case 'action':
        if (action === 'add') {
          return 'Uusi toimenpide';
        }
        if (action === 'edit') {
          return 'Muokkaa toimenpidettä';
        }
        if (action === 'complement') {
          return 'Täydennä toimenpidettä';
        }
        break;
      case 'record':
        if (action === 'add') {
          return 'Uusi asiakirja';
        }
        if (action === 'edit') {
          return 'Muokkaa asiakirjaa';
        }
        if (action === 'complement') {
          return 'Täydennä asiakirjaa';
        }
        break;
    }
  }

  resolveOnSubmit (e, targetId) {
    const { action, type } = this.props.editorConfig;
    switch (type) {
      case 'function':
        if (action === 'edit' || action === 'complement') {
          this.editMetaData(e);
        }
        break;
      case 'phase':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Käsittelyvaihe',
            body: 'Käsittelyvaiheen muokkaus onnistui!'
          });
        }
        break;
      case 'action':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Toimenpide',
            body: 'Toimenpiteen muokkaus onnistui!'
          });
        }
        break;
      case 'record':
        if (action === 'add') {
          this.addRecord(e, targetId);
        }
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Asiakirja',
            body: 'Asiakirjan muokkaus onnistui!'
          });
        }
    }
  }

  closeEditorForm (e) {
    e.preventDefault();
    this.props.closeEditorForm();
  }

  renderDescriptions () {
    const { attributeTypes } = this.props;
    const typeName = attributeTypes
      ? attributeTypes[`${capitalize(this.props.editorConfig.type)}Type`].name
      : '';
    const dropdownInput = this.generateDropdown(this.props.elementConfig.elementTypes);

    return (

          {typeName}

          { dropdownInput }

          {attributeTypes ? attributeTypes.TypeSpecifier.name : ''}

          <input
            className='col-xs-6 form-control edit-attribute__input'
            placeholder='Tarkenne'
            value={this.state.newAttributes.TypeSpecifier.value || ''}
            onChange={(e) => this.onChange(e.target.value, 'TypeSpecifier', 'value')}/>

    );
  }

  render () {
    const { attributeTypes, targetId } = this.props;
    const attributeElements = this.generateAttributeElements(attributeTypes);

    return (

{this.resolveLabel()}

        <form onSubmit={(e) => this.resolveOnSubmit(e, targetId)}
              className='editor-form'>
          { this.props.editorConfig.type !== 'function' ? this.renderDescriptions() : null }
          { attributeElements }

            <button
              className='btn btn-primary pull-right editor-form__submit'
              type='submit'>
              Valmis
            </button>
            <button
              className='btn btn-danger pull-right editor-form__cancel'
              onClick={(e) => this.closeEditorForm(e)}>
              Peruuta
            </button>

        </form>

    );
  }
}

EditorForm.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  attributes: React.PropTypes.object.isRequired,
  closeEditorForm: React.PropTypes.func.isRequired,
  displayMessage: React.PropTypes.func.isRequired,
  editMetaDataWithForm: React.PropTypes.func,
  editorConfig: React.PropTypes.shape({
    type: React.PropTypes.string.isRequired,
    action: React.PropTypes.string.isRequired
  }),
  elementConfig: React.PropTypes.shape({
    editWithForm: React.PropTypes.func,
    elementTypes: React.PropTypes.object.isRequired,
    createRecord: React.PropTypes.func // only records created with editorform
  }),
  targetId: React.PropTypes.string
};

export default EditorForm;
<|endoftext|>


<commit_before>Ext.define('devilry.extjshelpers.studentsmanager.MultiResultWindow', {
    extend: 'Ext.window.Window',
    layout: 'fit',
    closeAction: 'hide',
    closable: false,
    width: 800,
    height: 600,
    modal: true,
    maximizable: true,

    config: {
        isAdministrator: undefined
    },

    logTpl: Ext.create('Ext.XTemplate',
        '<tpl for="log">',
        '
',
        '
',
        '            <tpl if="assgnmentGroupRecord.data.name">',
        '               {assgnmentGroupRecord.data.name} -',
        '            </tpl>',
        '            <tpl if="parent.isAdministrator">',
        '                <tpl for="assgnmentGroupRecord.data.candidates__student__username">',
        '                   {.}<tpl if="xindex < xcount">, </tpl>',
        '                </tpl>',
        '            </tpl>',
        '            <tpl if="!parent.isAdministrator">',
        '                <tpl for="assgnmentGroupRecord.data.candidates">',
        '                   {identifier}<tpl if="xindex < xcount">, </tpl>',
        '                </tpl>',
        '            </tpl>',
        '
',
        '        {msg}',
        '
',
        '</tpl>'
    ),

    operationErrorTpl: Ext.create('Ext.XTemplate',
        '{msg}. ',
        'Error details: ',
        '<tpl if="status === 0">',
        '    Could not connect to the Devilry server.',
        '</tpl>',
        '<tpl if="status !== 0">',
        '    {status} {statusText}.',
        '</tpl>'
    ),

    constructor: function(config) {
        this.initConfig(config);
        this.callParent([config]);
    },

    initComponent: function() {
        this.errorcontainer = Ext.widget('panel', {
            title: 'Errors',
            bodyPadding: 10
        });
        this.warningcontainer = Ext.widget('panel', {
            title: 'Warnings',
            bodyPadding: 10
        });
        this.successcontainer = Ext.widget('panel', {
            title: 'Successful',
            bodyPadding: 10
        });
        Ext.apply(this, {
            items: {
                xtype: 'panel',
                border: false,
                layout: 'accordion'
            },
            dockedItems: [{
                xtype: 'toolbar',
                dock: 'bottom',
                ui: 'footer',
                items: ['->', {
                    xtype: 'button',
                    text: 'Close this window',
                    scale: 'large',
                    listeners: {
                        scope: this,
                        click: function() {
                            this.close();
                        }
                    }
                }, '->']
            }]
        });
        this.callParent(arguments);
    },

    addToLog: function(level, assgnmentGroupRecord, msg) {

        var logitem = {
            assgnmentGroupRecord: assgnmentGroupRecord,
            msg: msg
        }
        this.log[level].push(logitem);
    },

    addError: function(assgnmentGroupRecord, msg) {
        this.addToLog('error', assgnmentGroupRecord, msg);
    },

    addErrorFromOperation: function(assgnmentGroupRecord, msg, operation) {
        var fullMsg = this.operationErrorTpl.apply({
            msg: msg,
            status: operation.error.status,
            statusText: operation.error.statusText
        });
        this.addToLog('error', assgnmentGroupRecord, fullMsg);
    },

    addWarning: function(assgnmentGroupRecord, msg) {
        this.addToLog('warning', assgnmentGroupRecord, msg);
    },

    addSuccess: function(assgnmentGroupRecord, msg) {
        this.addToLog('success', assgnmentGroupRecord, msg);
    },

    start: function(title) {
        this.log = {
            error: [],
            warning: [],
            success: []
        };
        this.setTitle(title);
        this.down('panel').removeAll();
    },

    /**
     * @private
     */
    _addIfItems: function(log, csscls, title) {
        if(log.length > 0) {
            var container = Ext.widget('panel', {
                title: title,
                bodyPadding: 10,
                flex: 1,
                autoScroll: true,
                html: this.logTpl.apply({
                    csscls: csscls,
                    log: log,
                    isAdministrator: this.isAdministrator
                })
            });
            this.down('panel').add(container);
        }
    },

    finish: function(resultMsg, successMsg) {
        this._addIfItems(this.log.error, 'error', 'Errors');
        if(successMsg && this.log.error.length === 0) {
            this.down('panel').add({
                xtype: 'panel',
                title: successMsg.title,
                html: successMsg.html,
                bodyPadding: 10,
                flex: 1
            });
        }
        if(resultMsg) {
            this.down('panel').add({
                xtype: 'panel',
                title: resultMsg.title,
                html: resultMsg.html,
                bodyPadding: 10,
                flex: 1
            });
        }
        this._addIfItems(this.log.warning, 'warning', 'Warnings');
        this._addIfItems(this.log.success, 'info', 'Details about each successful save');
        this.show();
        this.down('panel').getComponent(0).expand();
    }
});
<commit_msg>apps.extjshelpers...MultiResultWindow: Saner size.<commit_after>Ext.define('devilry.extjshelpers.studentsmanager.MultiResultWindow', {
    extend: 'Ext.window.Window',
    layout: 'fit',
    closeAction: 'hide',
    closable: false,
    width: 600,
    height: 400,
    modal: true,
    maximizable: true,

    config: {
        isAdministrator: undefined
    },

    logTpl: Ext.create('Ext.XTemplate',
        '<tpl for="log">',
        '
',
        '
',
        '            <tpl if="assgnmentGroupRecord.data.name">',
        '               {assgnmentGroupRecord.data.name} -',
        '            </tpl>',
        '            <tpl if="parent.isAdministrator">',
        '                <tpl for="assgnmentGroupRecord.data.candidates__student__username">',
        '                   {.}<tpl if="xindex < xcount">, </tpl>',
        '                </tpl>',
        '            </tpl>',
        '            <tpl if="!parent.isAdministrator">',
        '                <tpl for="assgnmentGroupRecord.data.candidates">',
        '                   {identifier}<tpl if="xindex < xcount">, </tpl>',
        '                </tpl>',
        '            </tpl>',
        '
',
        '        {msg}',
        '
',
        '</tpl>'
    ),

    operationErrorTpl: Ext.create('Ext.XTemplate',
        '{msg}. ',
        'Error details: ',
        '<tpl if="status === 0">',
        '    Could not connect to the Devilry server.',
        '</tpl>',
        '<tpl if="status !== 0">',
        '    {status} {statusText}.',
        '</tpl>'
    ),

    constructor: function(config) {
        this.initConfig(config);
        this.callParent([config]);
    },

    initComponent: function() {
        this.errorcontainer = Ext.widget('panel', {
            title: 'Errors',
            bodyPadding: 10
        });
        this.warningcontainer = Ext.widget('panel', {
            title: 'Warnings',
            bodyPadding: 10
        });
        this.successcontainer = Ext.widget('panel', {
            title: 'Successful',
            bodyPadding: 10
        });
        Ext.apply(this, {
            items: {
                xtype: 'panel',
                border: false,
                layout: 'accordion'
            },
            dockedItems: [{
                xtype: 'toolbar',
                dock: 'bottom',
                ui: 'footer',
                items: ['->', {
                    xtype: 'button',
                    text: 'Close this window',
                    scale: 'large',
                    listeners: {
                        scope: this,
                        click: function() {
                            this.close();
                        }
                    }
                }, '->']
            }]
        });
        this.callParent(arguments);
    },

    addToLog: function(level, assgnmentGroupRecord, msg) {

        var logitem = {
            assgnmentGroupRecord: assgnmentGroupRecord,
            msg: msg
        }
        this.log[level].push(logitem);
    },

    addError: function(assgnmentGroupRecord, msg) {
        this.addToLog('error', assgnmentGroupRecord, msg);
    },

    addErrorFromOperation: function(assgnmentGroupRecord, msg, operation) {
        var fullMsg = this.operationErrorTpl.apply({
            msg: msg,
            status: operation.error.status,
            statusText: operation.error.statusText
        });
        this.addToLog('error', assgnmentGroupRecord, fullMsg);
    },

    addWarning: function(assgnmentGroupRecord, msg) {
        this.addToLog('warning', assgnmentGroupRecord, msg);
    },

    addSuccess: function(assgnmentGroupRecord, msg) {
        this.addToLog('success', assgnmentGroupRecord, msg);
    },

    start: function(title) {
        this.log = {
            error: [],
            warning: [],
            success: []
        };
        this.setTitle(title);
        this.down('panel').removeAll();
    },

    /**
     * @private
     */
    _addIfItems: function(log, csscls, title) {
        if(log.length > 0) {
            var container = Ext.widget('panel', {
                title: title,
                bodyPadding: 10,
                flex: 1,
                autoScroll: true,
                html: this.logTpl.apply({
                    csscls: csscls,
                    log: log,
                    isAdministrator: this.isAdministrator
                })
            });
            this.down('panel').add(container);
        }
    },

    finish: function(resultMsg, successMsg) {
        this._addIfItems(this.log.error, 'error', 'Errors');
        if(successMsg && this.log.error.length === 0) {
            this.down('panel').add({
                xtype: 'panel',
                title: successMsg.title,
                html: successMsg.html,
                bodyPadding: 10,
                flex: 1
            });
        }
        if(resultMsg) {
            this.down('panel').add({
                xtype: 'panel',
                title: resultMsg.title,
                html: resultMsg.html,
                bodyPadding: 10,
                flex: 1
            });
        }
        this._addIfItems(this.log.warning, 'warning', 'Warnings');
        this._addIfItems(this.log.success, 'info', 'Details about each successful save');
        this.show();
        this.down('panel').getComponent(0).expand();
    }
});
<|endoftext|>


<commit_before>$(document).ready(function() {
    URL = 'www.ting.gr:8080';
    var ENTER = 13;
    var ready = false;
    var rex = /^[α-ωa-z0-9]+$/i;
    var wrapper = $('.history-wrapper');
    var myUsername = null;
    var socket = io.connect(URL);

    function scrollDown() {
        setTimeout(function() {
            wrapper.scrollTop(wrapper.get(0).scrollHeight);
        }, 30);
    }

    function username_error_show(error) {
        $('#username-alert').empty();
        var $par = $('

');
        var errors = {
            empty: 'Γράψε ένα ψευδώνυμο.',
            length: 'Το ψευδώνυμο πρέπει να είναι έως 20 γράμματα.',
            chars: 'Το ψευδώνυμο πρέπει να περιλαμβάνει μόνο γραμματα, αριθμούς ή σύμβολα.',
            taken: 'Το ψευδώνυμο το έχει άλλος.'
        };

        $par.append(errors[error]);

        $('#username-alert').append($par);
        $('#username-alert').show();
    }

    function username_error_validation(username) {
        if (username == '') {
            username_error_show('empty');
            return false;

        }
        else if (username.length > 20) {
            username_error_show('length');
            return false;
        }
        else if (!rex.test(username)) {
            username_error_show('chars');
            return false;
        }
        return true;
    }

    $('#username-set-modal').modal('show');
    $('#username-alert').hide()
    setTimeout(function() {
        $('#username').focus();
    }, 300);

    var url = $(location).attr('href');
    parts = url.split('/');
    var channel = parts[1];

    if (channel == 'undefined') {
        channel = 'ting';
    }

    function getAvatar(name) {
        return 'http://www.gravatar.com/avatar/' + md5(name.toLowerCase() + '@gmail.com');
    }

    $.getJSON('/api/messages/' + channel, function(msgs) {
        $.each(msgs, function(index, msg) {
            var src = getAvatar(msg.username);
            var $img = $('');
            var $li = $('

');

            $li.append($img);
            $li.append(document.createTextNode(' '));
            $li.append($('' + msg.username + ''));
            $li[0].innerHTML += '
' + msg.text + '
';

            $('#msg-list').prepend($li);
        });
        scrollDown();
    });

    $('#modal').modal('show');
    $('#username').focus();

    $('#username-set').submit(function(event) {
        event.preventDefault();
        var username = $('#username').val();
        var response = username_error_validation(username);

        if (response != true) {
            username_error_show(response);
            return;
        }
        socket.emit('join', username);
    });

    $('#msg input').keypress(function(e) {
        if (e.which == ENTER) {
            e.preventDefault();
            var msg = $('#msg input').val();
            if (msg != '') {
                data = { ch: channel, msg: msg };
                socket.emit('send', data);
                $('#msg input').val('');
                scrollDown();
            }
        }
    });

    socket.on('join-response', function(success) {
        if (!success) {
            username_error_show('taken');
            return;
        }
        ready = true;
        $('#username-set-modal').modal('hide');
        $('#msg input').focus();
    });

    socket.on('update', function(msg) {
        if (ready) {
            $('#msg-list').append('
' + msg + '
');
            scrollDown();
        }
    });

    socket.on('update-people', function(people) {
        if (ready) {
            $('#online-list').empty();
            $.each(people, function(clientid, name) {
                $('#online-list').append('
' + name + '
');
            });
        }
    });

    socket.on('chat', function(who, msg) {
        if (ready) {
            var avatarHTML = '';
            var className;

            if (who == myUsername) {
                className = 'self';
            }
            else {
                className = 'other';
            }

            var html = '
' + avatarHTML + ' ' + who + '
' + msg + '

';

            $('#msg-list').append(html);
            scrollDown();
        }
    });

    //socket.on('disconnect', function() {
    //    $('#msgs').append('
The server is not available
');
    //    $('#msg').attr('disabled', 'disabled');
    //    $('#send').attr('disabled', 'disabled');
    //});
});
<commit_msg>make username validation function presentation agnostic<commit_after>$(document).ready(function() {
    URL = 'www.ting.gr:8080';
    var ENTER = 13;
    var ready = false;
    var rex = /^[α-ωa-z0-9]+$/i;
    var wrapper = $('.history-wrapper');
    var myUsername = null;
    var socket = io.connect(URL);

    function scrollDown() {
        setTimeout(function() {
            wrapper.scrollTop(wrapper.get(0).scrollHeight);
        }, 30);
    }

    function username_error_show(error) {
        $('#username-alert').empty();
        var $par = $('

');
        var errors = {
            empty: 'Γράψε ένα ψευδώνυμο.',
            length: 'Το ψευδώνυμο πρέπει να είναι έως 20 γράμματα.',
            chars: 'Το ψευδώνυμο πρέπει να περιλαμβάνει μόνο γραμματα, αριθμούς ή σύμβολα.',
            taken: 'Το ψευδώνυμο το έχει άλλος.'
        };

        $par.append(errors[error]);

        $('#username-alert').append($par);
        $('#username-alert').show();
    }

    function username_error_validation(username) {
        if (username == '') {
            return 'empty';
        }
        else if (username.length > 20) {
            return 'length';
        }
        else if (!rex.test(username)) {
            return 'chars';
        }
        return true;
    }

    $('#username-set-modal').modal('show');
    $('#username-alert').hide()
    setTimeout(function() {
        $('#username').focus();
    }, 300);

    var url = $(location).attr('href');
    parts = url.split('/');
    var channel = parts[1];

    if (channel == 'undefined') {
        channel = 'ting';
    }

    function getAvatar(name) {
        return 'http://www.gravatar.com/avatar/' + md5(name.toLowerCase() + '@gmail.com');
    }

    $.getJSON('/api/messages/' + channel, function(msgs) {
        $.each(msgs, function(index, msg) {
            var src = getAvatar(msg.username);
            var $img = $('');
            var $li = $('

');

            $li.append($img);
            $li.append(document.createTextNode(' '));
            $li.append($('' + msg.username + ''));
            $li[0].innerHTML += '
' + msg.text + '
';

            $('#msg-list').prepend($li);
        });
        scrollDown();
    });

    $('#modal').modal('show');
    $('#username').focus();

    $('#username-set').submit(function(event) {
        event.preventDefault();
        var username = $('#username').val();
        var response = username_error_validation(username);

        if (response != true) {
            username_error_show(response);
            return;
        }
        socket.emit('join', username);
    });

    $('#msg input').keypress(function(e) {
        if (e.which == ENTER) {
            e.preventDefault();
            var msg = $('#msg input').val();
            if (msg != '') {
                data = { ch: channel, msg: msg };
                socket.emit('send', data);
                $('#msg input').val('');
                scrollDown();
            }
        }
    });

    socket.on('join-response', function(success) {
        if (!success) {
            username_error_show('taken');
            return;
        }
        ready = true;
        $('#username-set-modal').modal('hide');
        $('#msg input').focus();
    });

    socket.on('update', function(msg) {
        if (ready) {
            $('#msg-list').append('
' + msg + '
');
            scrollDown();
        }
    });

    socket.on('update-people', function(people) {
        if (ready) {
            $('#online-list').empty();
            $.each(people, function(clientid, name) {
                $('#online-list').append('
' + name + '
');
            });
        }
    });

    socket.on('chat', function(who, msg) {
        if (ready) {
            var avatarHTML = '';
            var className;

            if (who == myUsername) {
                className = 'self';
            }
            else {
                className = 'other';
            }

            var html = '
' + avatarHTML + ' ' + who + '
' + msg + '

';

            $('#msg-list').append(html);
            scrollDown();
        }
    });

    //socket.on('disconnect', function() {
    //    $('#msgs').append('
The server is not available
');
    //    $('#msg').attr('disabled', 'disabled');
    //    $('#send').attr('disabled', 'disabled');
    //});
});
<|endoftext|>


<commit_before>(function() {
    function SongPlayer() {
        var SongPlayer = {};

        var currentSong = null;

        /*
        * @desc Buzz object audio file
        * @type {Object}
        */
        var currentBuzzObject = null;

        /*
        * @function setSong
        * @desc Stops currently playing song and loads new audio file as currentBuzzObject
        * @param {Object} song
        */
        var setSong = function(song) {
            if (currentBuzzObject) {
                currentBuzzObject.stop();
                currentSong.playing = null;
            }

            currentBuzzObject = new buzz.sound(song.audioUrl, {
                formats: ['mp3'],
                preload: true
            });

            currentSong = song;
        };

        SongPlayer.play = function(song) {
            if (currentSong !== song) {
                setSong(song);
                currentBuzzObject.play();
                song.playing = true;
            } else if (currentSong === song) {
                if (currentBuzzObject.isPaused()) {
                    currentBuzzObject.play();
                    song.playing = true;
                }
            }
        };

        SongPlayer.pause = function(song) {
            currentBuzzObject.pause();
            song.playing = false;
        };

        return SongPlayer;
    }

    angular
        .module('blocJams')
        .factory('SongPlayer', SongPlayer);
})();
<commit_msg>assignment-7-services-2: complete<commit_after>(function() {
    function SongPlayer() {
        /*
        * @desc SongPlayer object to be returned by service
        * @type {Object}
        */
        var SongPlayer = {};

        /*
        * @desc Currently playing song object
        * @type {Object} song
        */
        var currentSong = null;

        /*
        * @desc Buzz object audio file
        * @type {Object}
        */
        var currentBuzzObject = null;

        /*
        * @function setSong
        * @desc Stops currently playing song and loads new audio file as currentBuzzObject
        * @param {Object} song
        */
        var setSong = function(song) {
            if (currentBuzzObject) {
                currentBuzzObject.stop();
                currentSong.playing = null;
            }

            currentBuzzObject = new buzz.sound(song.audioUrl, {
                formats: ['mp3'],
                preload: true
            });

            currentSong = song;
        };

        /*
        * @function playSong
        * @desc Plays the current Buzz object
        * @param {Object} song
        */
        var playSong = function(song) {
            if (currentBuzzObject) {
                currentBuzzObject.play();
                song.playing = true;
            }
        };

        /*
        * @function play
        * @desc Public method - checks state of currentSong and either calls setSong() and playSong()
        *                       or just playSong()
        * @param {Object} song
        */
        SongPlayer.play = function(song) {
            if (currentSong !== song) {
                setSong(song);
                playSong(song);
            } else if (currentSong === song) {
                if (currentBuzzObject.isPaused()) {
                    playSong(song);
                }
            }
        };

        /*
        * @function pause
        * @desc Public method - pauses current Buzz object
        * @param {Object} song
        */
        SongPlayer.pause = function(song) {
            currentBuzzObject.pause();
            song.playing = false;
        };

        return SongPlayer;
    }

    angular
        .module('blocJams')
        .factory('SongPlayer', SongPlayer);
})();
<|endoftext|>


<commit_before>import Themeable from '../../mixins/themeable'

export default {
  functional: true,

  mixins: [Themeable],

  props: {
    fixed: Boolean
  },

  render (h, { data, children, props }) {
    data.staticClass = data.staticClass ? `toolbar ${data.staticClass}` : 'toolbar'
    if (props.fixed) data.staticClass += ' toolbar--fixed'
    if (props.dark) data.staticClass += ' toolbar--dark'
    if (props.light) data.staticClass += ' toolbar--light'

    return h('nav', data, children)
  }
}
<commit_msg>fix for #673<commit_after>import Themeable from '../../mixins/themeable'

export default {
  functional: true,

  mixins: [Themeable],

  props: {
    fixed: Boolean
  },

  render (h, { data, children, props }) {
    data.staticClass = data.staticClass ? `toolbar ${data.staticClass}` : 'toolbar'
    if (props.fixed) data.staticClass += ' toolbar--fixed'
    data.staticClass += props.light ? ' toolbar--light' : ' toolbar--dark'

    return h('nav', data, children)
  }
}
<|endoftext|>


<commit_before>const gulp = require('gulp');
const gutil = require('gulp-util');
const path = require('path');

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackDevServer = require('webpack-dev-server');
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CleanCss = require('clean-css');
const WriteFilePlugin = require('write-file-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
	.BundleAnalyzerPlugin;
const VueSSRPlugin = require('vue-ssr-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = function(config) {
	let base = path.resolve(config.projectBase);

	let noop = function() {};
	let devNoop = config.production ? undefined : noop;
	let prodNoop = !config.production ? undefined : noop;
	let serverNoop = !config.server ? undefined : noop;

	let externals = {};
	for (let extern of ['nw.gui', 'client-voodoo']) {
		externals[extern] = extern;
	}

	const cleanCssOptions = {
		level: 2,
	};

	let cleanCss = new CleanCss(cleanCssOptions);

	let webpackTarget = 'web';
	if (config.server) {
		webpackTarget = 'node';
	} else if (config.client) {
		webpackTarget = 'node-webkit';
	}

	let libraryTarget = 'var';
	if (config.server) {
		libraryTarget = 'commonjs';
	}

	function stylesLoader(loaders, options) {
		// Note: style-loader doesn't work on the server.

		if (config.production) {
			loaders.push({
				loader: 'clean-css-loader',
				options: cleanCssOptions,
			});

			return ExtractTextPlugin.extract({
				fallback: !config.server ? 'style-loader' : undefined,
				use: loaders,
			});
		}

		if (!config.server) {
			loaders.unshift('style-loader');
		}

		return loaders;
	}

	let webpackSectionConfigs = {};
	let webpackSectionTasks = [];
	config.sections.forEach(function(section) {
		let indexHtml = section === 'app' ? 'index.html' : section + '.html';

		let appEntries = [
			path.resolve(base, 'src/' + section + '/main.styl'),
			path.resolve(base, 'src/' + section + '/main.ts'),
		];

		if (!config.production) {
			appEntries.push(
				'webpack-dev-server/client?http://localhost:' + config.port + '/'
			);
			appEntries.push('webpack/hot/dev-server');
		}

		let entry = {
			app: appEntries,
		};

		if (config.server) {
			entry = {
				server: [path.resolve(base, 'src/' + section + '/server.ts')],
			};
		}

		webpackSectionConfigs[section] = {
			entry,
			target: webpackTarget,
			devServer: {
				outputPath: path.resolve(base, config.buildDir),
			},
			output: {
				publicPath: (config.production ? config.staticCdn : '') + '/',
				path: path.resolve(base, config.buildDir),
				filename: config.production
					? section + '.[name].[chunkhash:6].js'
					: section + '.[name].js',
				chunkFilename: config.production
					? section + '.[name].[id].[chunkhash:6].js'
					: undefined,
				libraryTarget: libraryTarget,
			},
			resolve: {
				extensions: ['.tsx', '.ts', '.js', '.styl'],
				modules: [path.resolve(base, 'src/vendor'), 'node_modules'],
				alias: {
					// Always "app" base img.
					img: path.resolve(base, 'src/app/img'),
					styles: path.resolve(base, 'src/' + section + '/styles'),
				},
			},
			externals: externals,
			resolveLoader: {
				alias: {
					view:
						'vue-template-loader?' +
							JSON.stringify({
								scoped: true,
								transformToRequire: {
									img: 'src',
								},
							}),
				},
			},
			module: {
				rules: [
					{
						test: /\.tsx?$/,
						use: [
							{
								// God save us.
								loader: 'string-replace-loader',
								options: {
									search: /\$import\(/g,
									replace: 'import(',
								},
							},
							{
								loader: 'ts-loader',
								options: {
									transpileOnly: true,
								},
							},
						],
					},
					{
						test: /\.js$/,
						use: [
							{
								// God save us.
								loader: 'string-replace-loader',
								options: {
									search: /\$import\(/g,
									replace: 'import(',
								},
							},
						],
						exclude: /node_modules/,
					},
					{
						enforce: 'pre',
						test: /\.styl$/,
						use: 'stylus-loader?paths[]=src/&resolve url&include css',
					},
					{
						enforce: 'post',
						test: /\.styl$/,
						use: stylesLoader([
							'css-loader?-minimize',
							{ loader: 'postcss-loader', options: { sourceMap: true } },
						]),
					},
					{
						test: /\.md$/,
						use: [
							'file-loader?name=templates/[name].[hash:6].html',
							'extract-loader',
							'html-loader',
							'markdown-loader',
						],
					},
					{
						test: /\.(png|jpe?g|gif|svg|json|ogg|mp4)(\?.*)?$/,
						loader: 'file-loader?name=assets/[name].[hash:6].[ext]',
						exclude: /node_modules/,
					},
					{
						test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
						loader: 'file-loader?name=assets/[name].[hash:6].[ext]',
					},
				],
			},
			// Inline allows us to debug by setting breakpoints.
			// Eval may be faster, but it doesn't allow setting breakpoints.
			devtool: !config.server
				? 'cheap-module-inline-source-map'
				: '#source-map',
			plugins: [
				new webpack.DefinePlugin({
					GJ_ENVIRONMENT: JSON.stringify(
						!config.developmentEnv ? 'production' : 'development'
					),
					GJ_BUILD_TYPE: JSON.stringify(
						config.production ? 'production' : 'development'
					),
					GJ_IS_CLIENT: JSON.stringify(config.client),
					GJ_IS_SSR: JSON.stringify(config.server),
					GJ_VERSION: JSON.stringify(
						require(path.resolve(process.cwd(), 'package.json')).version
					),

					// This sets vue in production mode.
					'process.env': {
						NODE_ENV: JSON.stringify(
							config.production ? 'production' : 'development'
						),
					},
				}),
				new webpack.LoaderOptionsPlugin({
					options: {
						// Fix extract-loader: https://github.com/peerigon/extract-loader/issues/16
						output: {},
						htmlLoader: {
							minimize: config.production,
						},
						stylus: {
							use: [],
							preferPathResolver: 'webpack',
						},
					},
				}),
				// !config.client ? noop : new CopyWebpackPlugin([
				// 	{
				// 		from: path.resolve( base, 'client-package.json' ),
				// 		to: 'package.json',
				// 	},
				// ]),
				devNoop ||
					new webpack.optimize.UglifyJsPlugin({
						compress: {
							warnings: false,
							screw_ie8: true,
						},
					}),
				devNoop || new ImageminPlugin(),
				prodNoop || serverNoop || new webpack.HotModuleReplacementPlugin(),

				// Pull out vendor code from the main entry point.
				devNoop ||
					new webpack.optimize.CommonsChunkPlugin({
						name: 'vendor',
						minChunks: function(module) {
							return (
								module.context &&
								(module.context.indexOf('node_modules') !== -1 ||
									module.context.indexOf('bower-lib') !== -1)
							);
						},
					}),

				// This generates a manifest file that allows our vendor chunk
				// to be cached longer if it doesn't change.
				// More info: https://webpack.js.org/guides/code-splitting-libraries/#implicit-common-vendor-chunk
				devNoop ||
					new webpack.optimize.CommonsChunkPlugin({
						name: 'manifest',
					}),

				serverNoop || new ExtractTextPlugin('[name].[contenthash:6].css'),
				devNoop ||
					new OptimizeCssPlugin({
						cssProcessor: {
							process: function(css) {
								return new Promise(function(resolve, reject) {
									let output = cleanCss.minify(css);
									if (output.errors.length) {
										reject(output.errors);
									} else {
										resolve({
											css: output.styles,
										});
									}
								});
							},
						},
					}),
				serverNoop ||
					new HtmlWebpackPlugin({
						filename: indexHtml,
						template: '!!html-loader?interpolate=require!src/' + indexHtml,
						favicon: path.resolve(base, 'src/app/img/favicon.png'),
						inject: true,
						chunksSortMode: 'dependency',
						excludeChunks: ['server'],
					}),
				prodNoop || new FriendlyErrorsWebpackPlugin(),
				!config.server
					? noop
					: new VueSSRPlugin({
							entry: 'server',
						}),
				config.write ? new WriteFilePlugin() : noop,
				config.analyze ? new BundleAnalyzerPlugin() : noop,
			],
		};

		gulp.task('compile:' + section, function(cb) {
			let compiler = webpack(webpackSectionConfigs[section]);
			compiler.run(function(err, stats) {
				if (err) {
					throw new gutil.PluginError('webpack:build', err);
				}

				gutil.log(
					'[webpack:build]',
					stats.toString({
						chunks: false,
						colors: true,
					})
				);

				cb();
			});
		});

		webpackSectionTasks.push('compile:' + section);
	});

	gulp.task(
		'watch',
		gulp.series('clean:pre', function(cb) {
			let compiler = webpack(webpackSectionConfigs[config.buildSection]);

			let server = new WebpackDevServer(compiler, {
				historyApiFallback: {
					rewrites: [
						{
							from: /./,
							to: config.buildSection === 'app'
								? '/index.html'
								: '/' + config.buildSection + '.html',
						},
					],
				},
				quiet: true,
				hot: !config.server,
				watchOptions: {
					aggregateTimeout: 300,
				},
			});

			if (!config.server) {
				server.listen(config.port, 'localhost');
			}
		})
	);

	gulp.task('compile', gulp.series(webpackSectionTasks));
	gulp.task(
		'default',
		gulp.series('clean:pre', 'translations:compile', 'compile')
	);
};
<commit_msg>remove watch timeout for webpack<commit_after>const gulp = require('gulp');
const gutil = require('gulp-util');
const path = require('path');

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackDevServer = require('webpack-dev-server');
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CleanCss = require('clean-css');
const WriteFilePlugin = require('write-file-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
	.BundleAnalyzerPlugin;
const VueSSRPlugin = require('vue-ssr-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = function(config) {
	let base = path.resolve(config.projectBase);

	let noop = function() {};
	let devNoop = config.production ? undefined : noop;
	let prodNoop = !config.production ? undefined : noop;
	let serverNoop = !config.server ? undefined : noop;

	let externals = {};
	for (let extern of ['nw.gui', 'client-voodoo']) {
		externals[extern] = extern;
	}

	const cleanCssOptions = {
		level: 2,
	};

	let cleanCss = new CleanCss(cleanCssOptions);

	let webpackTarget = 'web';
	if (config.server) {
		webpackTarget = 'node';
	} else if (config.client) {
		webpackTarget = 'node-webkit';
	}

	let libraryTarget = 'var';
	if (config.server) {
		libraryTarget = 'commonjs';
	}

	function stylesLoader(loaders, options) {
		// Note: style-loader doesn't work on the server.

		if (config.production) {
			loaders.push({
				loader: 'clean-css-loader',
				options: cleanCssOptions,
			});

			return ExtractTextPlugin.extract({
				fallback: !config.server ? 'style-loader' : undefined,
				use: loaders,
			});
		}

		if (!config.server) {
			loaders.unshift('style-loader');
		}

		return loaders;
	}

	let webpackSectionConfigs = {};
	let webpackSectionTasks = [];
	config.sections.forEach(function(section) {
		let indexHtml = section === 'app' ? 'index.html' : section + '.html';

		let appEntries = [
			path.resolve(base, 'src/' + section + '/main.styl'),
			path.resolve(base, 'src/' + section + '/main.ts'),
		];

		if (!config.production) {
			appEntries.push(
				'webpack-dev-server/client?http://localhost:' + config.port + '/'
			);
			appEntries.push('webpack/hot/dev-server');
		}

		let entry = {
			app: appEntries,
		};

		if (config.server) {
			entry = {
				server: [path.resolve(base, 'src/' + section + '/server.ts')],
			};
		}

		webpackSectionConfigs[section] = {
			entry,
			target: webpackTarget,
			devServer: {
				outputPath: path.resolve(base, config.buildDir),
			},
			output: {
				publicPath: (config.production ? config.staticCdn : '') + '/',
				path: path.resolve(base, config.buildDir),
				filename: config.production
					? section + '.[name].[chunkhash:6].js'
					: section + '.[name].js',
				chunkFilename: config.production
					? section + '.[name].[id].[chunkhash:6].js'
					: undefined,
				libraryTarget: libraryTarget,
			},
			resolve: {
				extensions: ['.tsx', '.ts', '.js', '.styl'],
				modules: [path.resolve(base, 'src/vendor'), 'node_modules'],
				alias: {
					// Always "app" base img.
					img: path.resolve(base, 'src/app/img'),
					styles: path.resolve(base, 'src/' + section + '/styles'),
				},
			},
			externals: externals,
			resolveLoader: {
				alias: {
					view:
						'vue-template-loader?' +
							JSON.stringify({
								scoped: true,
								transformToRequire: {
									img: 'src',
								},
							}),
				},
			},
			module: {
				rules: [
					{
						test: /\.tsx?$/,
						use: [
							{
								// God save us.
								loader: 'string-replace-loader',
								options: {
									search: /\$import\(/g,
									replace: 'import(',
								},
							},
							{
								loader: 'ts-loader',
								options: {
									transpileOnly: true,
								},
							},
						],
					},
					{
						test: /\.js$/,
						use: [
							{
								// God save us.
								loader: 'string-replace-loader',
								options: {
									search: /\$import\(/g,
									replace: 'import(',
								},
							},
						],
						exclude: /node_modules/,
					},
					{
						enforce: 'pre',
						test: /\.styl$/,
						use: 'stylus-loader?paths[]=src/&resolve url&include css',
					},
					{
						enforce: 'post',
						test: /\.styl$/,
						use: stylesLoader([
							'css-loader?-minimize',
							{ loader: 'postcss-loader', options: { sourceMap: true } },
						]),
					},
					{
						test: /\.md$/,
						use: [
							'file-loader?name=templates/[name].[hash:6].html',
							'extract-loader',
							'html-loader',
							'markdown-loader',
						],
					},
					{
						test: /\.(png|jpe?g|gif|svg|json|ogg|mp4)(\?.*)?$/,
						loader: 'file-loader?name=assets/[name].[hash:6].[ext]',
						exclude: /node_modules/,
					},
					{
						test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
						loader: 'file-loader?name=assets/[name].[hash:6].[ext]',
					},
				],
			},
			// Inline allows us to debug by setting breakpoints.
			// Eval may be faster, but it doesn't allow setting breakpoints.
			devtool: !config.server
				? 'cheap-module-inline-source-map'
				: '#source-map',
			plugins: [
				new webpack.DefinePlugin({
					GJ_ENVIRONMENT: JSON.stringify(
						!config.developmentEnv ? 'production' : 'development'
					),
					GJ_BUILD_TYPE: JSON.stringify(
						config.production ? 'production' : 'development'
					),
					GJ_IS_CLIENT: JSON.stringify(config.client),
					GJ_IS_SSR: JSON.stringify(config.server),
					GJ_VERSION: JSON.stringify(
						require(path.resolve(process.cwd(), 'package.json')).version
					),

					// This sets vue in production mode.
					'process.env': {
						NODE_ENV: JSON.stringify(
							config.production ? 'production' : 'development'
						),
					},
				}),
				new webpack.LoaderOptionsPlugin({
					options: {
						// Fix extract-loader: https://github.com/peerigon/extract-loader/issues/16
						output: {},
						htmlLoader: {
							minimize: config.production,
						},
						stylus: {
							use: [],
							preferPathResolver: 'webpack',
						},
					},
				}),
				// !config.client ? noop : new CopyWebpackPlugin([
				// 	{
				// 		from: path.resolve( base, 'client-package.json' ),
				// 		to: 'package.json',
				// 	},
				// ]),
				devNoop ||
					new webpack.optimize.UglifyJsPlugin({
						compress: {
							warnings: false,
							screw_ie8: true,
						},
					}),
				devNoop || new ImageminPlugin(),
				prodNoop || serverNoop || new webpack.HotModuleReplacementPlugin(),

				// Pull out vendor code from the main entry point.
				devNoop ||
					new webpack.optimize.CommonsChunkPlugin({
						name: 'vendor',
						minChunks: function(module) {
							return (
								module.context &&
								(module.context.indexOf('node_modules') !== -1 ||
									module.context.indexOf('bower-lib') !== -1)
							);
						},
					}),

				// This generates a manifest file that allows our vendor chunk
				// to be cached longer if it doesn't change.
				// More info: https://webpack.js.org/guides/code-splitting-libraries/#implicit-common-vendor-chunk
				devNoop ||
					new webpack.optimize.CommonsChunkPlugin({
						name: 'manifest',
					}),

				serverNoop || new ExtractTextPlugin('[name].[contenthash:6].css'),
				devNoop ||
					new OptimizeCssPlugin({
						cssProcessor: {
							process: function(css) {
								return new Promise(function(resolve, reject) {
									let output = cleanCss.minify(css);
									if (output.errors.length) {
										reject(output.errors);
									} else {
										resolve({
											css: output.styles,
										});
									}
								});
							},
						},
					}),
				serverNoop ||
					new HtmlWebpackPlugin({
						filename: indexHtml,
						template: '!!html-loader?interpolate=require!src/' + indexHtml,
						favicon: path.resolve(base, 'src/app/img/favicon.png'),
						inject: true,
						chunksSortMode: 'dependency',
						excludeChunks: ['server'],
					}),
				prodNoop || new FriendlyErrorsWebpackPlugin(),
				!config.server
					? noop
					: new VueSSRPlugin({
							entry: 'server',
						}),
				config.write ? new WriteFilePlugin() : noop,
				config.analyze ? new BundleAnalyzerPlugin() : noop,
			],
		};

		gulp.task('compile:' + section, function(cb) {
			let compiler = webpack(webpackSectionConfigs[section]);
			compiler.run(function(err, stats) {
				if (err) {
					throw new gutil.PluginError('webpack:build', err);
				}

				gutil.log(
					'[webpack:build]',
					stats.toString({
						chunks: false,
						colors: true,
					})
				);

				cb();
			});
		});

		webpackSectionTasks.push('compile:' + section);
	});

	gulp.task(
		'watch',
		gulp.series('clean:pre', function(cb) {
			let compiler = webpack(webpackSectionConfigs[config.buildSection]);

			let server = new WebpackDevServer(compiler, {
				historyApiFallback: {
					rewrites: [
						{
							from: /./,
							to: config.buildSection === 'app'
								? '/index.html'
								: '/' + config.buildSection + '.html',
						},
					],
				},
				quiet: true,
				hot: !config.server,
			});

			if (!config.server) {
				server.listen(config.port, 'localhost');
			}
		})
	);

	gulp.task('compile', gulp.series(webpackSectionTasks));
	gulp.task(
		'default',
		gulp.series('clean:pre', 'translations:compile', 'compile')
	);
};
<|endoftext|>