<commit_before>var inquirer = require('inquirer')
var semver = require('semver')
var shell = require('shelljs')
var fs = require('fs')

var pkg = require('./package.json')

console.log('')
console.log('Wellcome to servable release utility!')
console.log('----------------------------------------------------------------')
console.log('')

var questions = [
  {
    type: 'list',
    name: 'version',
    message: 'Which version will it be? (current is ' + pkg.version + ')',
    choices: [
      semver.inc(pkg.version, 'patch'),
      semver.inc(pkg.version, 'minor'),
      semver.inc(pkg.version, 'major'),
      semver.inc(pkg.version, 'premajor', 'rc'),
    ],
  },
  {
    type: 'list',
    name: 'dryRun',
    message: 'Do you want to release, or just see what would happen if you do?',
    choices: ['Just see', 'Release!'],
  },
]

inquirer.prompt(questions).then(function(answers) {
  var newVerison = answers.version
  var dryRun = answers.dryRun === 'Just see'

  pkg.version = newVerison

  console.log('')
  if (dryRun) {
    console.log('Ok, here is what would happen:')
  } else {
    console.log('Doing actual release:')
  }
  console.log('')

  // run('npm test', dryRun) &&
    run('yarn build', dryRun) &&
    run(`npm version ${newVerison}`, dryRun) &&
    run('git push origin --tags', dryRun) &&
    run('npm publish', dryRun)
});

function run(cmd, dry) {
  console.log('Running `' + cmd + '`')
  if (!dry) {
    if (shell.exec(cmd, {silent: false}).code === 0) {
      console.log('... ok')
    } else {
      console.error('... fail!')
      return false
    }
  }
  return true
}
<commit_msg>Fix up release script spelling<commit_after>const inquirer = require('inquirer')
const semver = require('semver')
const shell = require('shelljs')
const pkg = require('./package.json')

console.log('');
console.log('Welcome to servable release utility!');
console.log('----------------------------------------------------------------');
console.log('');

const questions = [
  {
    type: 'list',
    name: 'version',
    message: 'Which version will it be? (current is ' + pkg.version + ')',
    choices: [
      semver.inc(pkg.version, 'patch'),
      semver.inc(pkg.version, 'minor'),
      semver.inc(pkg.version, 'major'),
      semver.inc(pkg.version, 'premajor', 'rc'),
    ],
  },
  {
    type: 'list',
    name: 'dryRun',
    message: 'Do you want to release, or just see what would happen if you do?',
    choices: ['Just see', 'Release!'],
  },
];

inquirer.prompt(questions).then(function(answers) {
  const newVerison = answers.version
  const dryRun = answers.dryRun === 'Just see';

  pkg.version = newVerison;

  console.log('');
  if (dryRun) {
    console.log('Ok, here is what would happen:');
  } else {
    console.log('Doing actual release:');
  }
  console.log('');

  // run('npm test', dryRun) &&
  run('yarn build', dryRun) &&
  run(`npm version ${newVerison}`, dryRun) &&
  run('git push origin --tags', dryRun) &&
  run('npm publish', dryRun);
});

function run(cmd, dry) {
  console.log('Running `' + cmd + '`');
  if (!dry) {
    if (shell.exec(cmd, {silent: false}).code === 0) {
      console.log('... ok');
    } else {
      console.error('... fail!');
      return false;
    }
  }
  return true;
}
<|endoftext|>


<commit_before>const { execSync } = require('child_process');
const readline = require('readline');
const semver = require('semver');
const { getLatestVersion } = require('./version');

function generatePatchVersion() {
  let version = getLatestVersion();
  version = semver.inc(version, 'patch');
  return version;
}
const currentVersion = getLatestVersion();
const version = generatePatchVersion();

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function question(theQuestion) {
  return new Promise((resolve) => rl.question(theQuestion, (answ) => resolve(answ.toLowerCase().includes('y'))));
}

async function main() {
  let answer = false;
  answer = await question(`Creating an excalibur release at version [v${version}]? (y/n):`);
  if (answer) {
    console.log(`Cleaning dist 🧹`);
    console.log(execSync(`npm run clean`).toString());
    console.log(`Cloning dist repo 🤖`);
    console.log(execSync(`git clone https://github.com/excaliburjs/excalibur-dist build`).toString());

    console.log(`Rebuilding with version v${version} 🔨`);
    process.env.GH_TOKEN = `v${version}`;
    process.env.TRAVIS_TAG = `v${version}`;
    process.env.TRAVIS_BUILD_NUMBER = `v${version}`;
    console.log(execSync(`npm run core && npm run core:bundle-prod`).toString());
    console.log(`Zipping dists 🤐`);
    console.log(execSync(`powershell -noprofile "Compress-Archive -Force ./build/dist ./build/dist.${version}.zip"`).toString());
    console.log(`Building nuget package 📦`);
    console.log(execSync(`npm run nuget ${version}`).toString());

    console.log(`Preparing the release notes for Excalibur 📝⚔`);
    console.log(execSync(`git log v${currentVersion}..HEAD --oneline --pretty=format:%s --graph > changes.txt`));
    console.log(execSync(`echo Excalibur v${version} Release > title.txt`));
    console.log(execSync(`type title.txt .\\assets\\release.md changes.txt > pending-release.md`));
    console.log(execSync(`type .\\pending-release.md`).toString());
    console.log(`Updating package.json to ${version} 😘`);
    console.log(execSync(`npm --no-git-tag-version version ${version}`).toString());
    console.log('Committing updates ✅');
    console.log(execSync(`git add package.json`).toString());
    console.log(execSync(`git commit -nm "[chore] Release excalibur@v${version}"`).toString());

    console.log('Creating releases 🤞');
    const releaseUrl = execSync(
      [
        'hub release create -d',
        '--file ./pending-release.md',
        '--attach ./build/dist/excalibur.js',
        '--attach ./build/dist/excalibur.d.ts',
        '--attach ./build/dist/excalibur.min.js',
        '--attach ./build/dist/excalibur.min.js.map',
        `--attach ./build/dist/Excalibur.${version}.nupkg`,
        `--attach ./build/dist.${version}.zip`,
        `v${version}`
      ].join(' ')
    ).toString();
    console.log(releaseUrl);
    console.log(execSync(`"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" ${releaseUrl}`));

    const releaseUrlDist = execSync(
      [
        'cd ./build && ',
        'hub release create -d',
        '--file ../pending-release.md',
        '--attach ./dist/excalibur.js',
        '--attach ./dist/excalibur.d.ts',
        '--attach ./dist/excalibur.min.js',
        '--attach ./dist/excalibur.min.js.map',
        `--attach ./dist/Excalibur.${version}.nupkg`,
        `--attach ./dist.${version}.zip`,
        `v${version}`
      ].join(' ')
    ).toString();
    console.log(releaseUrlDist);
    console.log(execSync(`"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" ${releaseUrlDist}`));
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Push the updated package.json? (y/n)');
  if (answer) {
    console.log(execSync(`git push`).toString());
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Publish the nuget package? (y/n)');
  if (answer) {
    console.log(execSync(`nuget push ./dist/Excalibur.${version}.nupkg -Source https://api.nuget.org/v3/index.json`).toString());
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Clean working directory? (y/n)');
  if (answer) {
    console.log(execSync(`git clean -f`).toString());
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }
}

main();
<commit_msg>[chore] Fix nuget publish<commit_after>const { execSync } = require('child_process');
const readline = require('readline');
const semver = require('semver');
const { getLatestVersion } = require('./version');

function generatePatchVersion() {
  let version = getLatestVersion();
  version = semver.inc(version, 'patch');
  return version;
}
const currentVersion = getLatestVersion();
const version = generatePatchVersion();

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function question(theQuestion) {
  return new Promise((resolve) => rl.question(theQuestion, (answ) => resolve(answ.toLowerCase().includes('y'))));
}

async function main() {
  let answer = false;
  answer = await question(`Creating an excalibur release at version [v${version}]? (y/n):`);
  if (answer) {
    console.log(`Cleaning dist 🧹`);
    console.log(execSync(`npm run clean`).toString());
    console.log(`Cloning dist repo 🤖`);
    console.log(execSync(`git clone https://github.com/excaliburjs/excalibur-dist build`).toString());

    console.log(`Rebuilding with version v${version} 🔨`);
    process.env.GH_TOKEN = `v${version}`;
    process.env.TRAVIS_TAG = `v${version}`;
    process.env.TRAVIS_BUILD_NUMBER = `v${version}`;
    console.log(execSync(`npm run core && npm run core:bundle-prod`).toString());
    console.log(`Zipping dists 🤐`);
    console.log(execSync(`powershell -noprofile "Compress-Archive -Force ./build/dist ./build/dist.${version}.zip"`).toString());
    console.log(`Building nuget package 📦`);
    console.log(execSync(`npm run nuget ${version}`).toString());

    console.log(`Preparing the release notes for Excalibur 📝⚔`);
    console.log(execSync(`git log v${currentVersion}..HEAD --oneline --pretty=format:%s --graph > changes.txt`));
    console.log(execSync(`echo Excalibur v${version} Release > title.txt`));
    console.log(execSync(`type title.txt .\\assets\\release.md changes.txt > pending-release.md`));
    console.log(execSync(`type .\\pending-release.md`).toString());
    console.log(`Updating package.json to ${version} 😘`);
    console.log(execSync(`npm --no-git-tag-version version ${version}`).toString());
    console.log('Committing updates ✅');
    console.log(execSync(`git add package.json`).toString());
    console.log(execSync(`git commit -nm "[chore] Release excalibur@v${version}"`).toString());

    console.log('Creating releases 🤞');
    const releaseUrl = execSync(
      [
        'hub release create -d',
        '--file ./pending-release.md',
        '--attach ./build/dist/excalibur.js',
        '--attach ./build/dist/excalibur.d.ts',
        '--attach ./build/dist/excalibur.min.js',
        '--attach ./build/dist/excalibur.min.js.map',
        `--attach ./build/dist/Excalibur.${version}.nupkg`,
        `--attach ./build/dist.${version}.zip`,
        `v${version}`
      ].join(' ')
    ).toString();
    console.log(releaseUrl);
    console.log(execSync(`"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" ${releaseUrl}`));

    const releaseUrlDist = execSync(
      [
        'cd ./build && ',
        'hub release create -d',
        '--file ../pending-release.md',
        '--attach ./dist/excalibur.js',
        '--attach ./dist/excalibur.d.ts',
        '--attach ./dist/excalibur.min.js',
        '--attach ./dist/excalibur.min.js.map',
        `--attach ./dist/Excalibur.${version}.nupkg`,
        `--attach ./dist.${version}.zip`,
        `v${version}`
      ].join(' ')
    ).toString();
    console.log(releaseUrlDist);
    console.log(execSync(`"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" ${releaseUrlDist}`));
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Push the updated package.json? (y/n)');
  if (answer) {
    console.log(execSync(`git push`).toString());
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Publish the nuget package? (y/n)');
  if (answer) {
    console.log(
      execSync(
        `.\\src\\tools\\NuGet.exe push ./build/dist/Excalibur.${version}.nupkg -Source https://api.nuget.org/v3/index.json`
      ).toString()
    );
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }

  answer = await question('Clean working directory? (y/n)');
  if (answer) {
    console.log(execSync(`git clean -f`).toString());
  } else {
    console.log('Abort!');
    rl.close();
    return;
  }
}

main();
<|endoftext|>


<commit_before>/* @flow */

import React from 'react'

import {
  Image,
  Text,
  View,
} from 'react-native'

import {
  Cell,
  Grid,
  Row,
} from 'react-native-tabular-grid-markdown-view'

import type {
  EmptyNode,
  HeadingNode,
  ImageNode,
  InlineContentNode,
  LinkNode,
  ListNode,
  TableNode,
  OutputFunction,
  RenderState,
  RenderStyles,
} from './types'

function renderImage(node: ImageNode, output: OutputFunction, state: RenderState, styles: RenderStyles) {
  const {imageWrapper: wrapperStyle, image: imageStyle} = styles
  return (
    <View key={state.key} style={node.width && node.height ? [wrapperStyle, paddedSize(node, wrapperStyle)] : wrapperStyle}>
      <Image source={{uri: node.target}} style={imageStyle}/>
    </View>
  )
}

function renderTableCell(cell, row, column, rowCount, columnCount, output, state, styles) {
  const cellStyle = [styles.tableCell]
  const contentStyle = [styles.tableCellContent]

  if (row % 2 == 0) {
    cellStyle.push(styles.tableCellEvenRow)
    contentStyle.push(styles.tableCellContentEvenRow)
  } else {
    cellStyle.push(styles.tableCellOddRow)
    contentStyle.push(styles.tableCellContentOddRow)
  }

  if (column % 2 == 0) {
    cellStyle.push(styles.tableCellEvenColumn)
    contentStyle.push(styles.tableCellContentEvenColumn)
  } else {
    cellStyle.push(styles.tableCellOddColumn)
    contentStyle.push(styles.tableCellContentOddColumn)
  }

  if (row == 1) {
    cellStyle.push(styles.tableHeaderCell)
    contentStyle.push(styles.tableHeaderCellContent)
  } else if (row == rowCount) {
    cellStyle.push(styles.tableCellLastRow)
    contentStyle.push(styles.tableCellContentLastRow)
  }

  if (column == columnCount) {
    cellStyle.push(styles.tableCellLastColumn)
    contentStyle.push(styles.tableCellContentLastColumn)
  }

  return <Cell rowId={row} id={column} key={column} style={cellStyle}>
    <Text style={contentStyle}>
      {output(cell, state)}
    </Text>
  </Cell>
}

function paragraphRenderer() {
  var renderText = textContentRenderer('paragraph')

  return (node: InlineContentNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => {
    if (node.content instanceof Array && node.content.length === 1 && node.content[0].type === 'image') {
      const imageNode : ImageNode = node.content[0]
      return renderImage(imageNode, output, state, styles)
    } else {
      return renderText(node, output, state, styles)
    }
  }
}

function textBlockRenderer(styleName, styleName2) {
  return (node: InlineContentNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key}>
      <Text style={styleName2 ? [styles[styleName], styles[styleName2]] : styles[styleName]}>
        {typeof node.content === 'string' ? node.content : output(node.content, state)}
      </Text>
    </Text>
  )
}

function textContentRenderer(styleName) {
  return (node: InlineContentNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styles[styleName]}>
      {typeof node.content === 'string' ? node.content : output(node.content, state)}
    </Text>
  )
}

function paddedSize(size, style) {
  function either(a, b) {
    return a === undefined ? b : a
  }

  const {
    padding = 0,
    paddingLeft,
    paddingRight,
    paddingTop,
    paddingBottom,
  } = style

  return {
    width: size.width + either(paddingLeft, padding) + either(paddingRight, padding),
    height: size.height + either(paddingTop, padding) + either(paddingBottom, padding)
  }
}

export default Object.freeze({
  blockQuote: textBlockRenderer('blockQuote'),
  br: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styles.br}>
      {'\n\n'}
    </Text>
  ),
  codeBlock: textBlockRenderer('codeBlock'),
  del: textContentRenderer('del'),
  em: textContentRenderer('em'),
  heading: (node: HeadingNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    textBlockRenderer('heading', 'heading' + node.level)(node, output, state, styles)
  ),
  hr: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <View key={state.key} style={styles.hr}/>
  ),
  image: renderImage,
  inlineCode: textContentRenderer('inlineCode'),
  link: (node: LinkNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => {
    const onPress = state.onLinkPress
    return <Text key={state.key} style={styles.link} onPress={onPress ? () => onPress(node.target) : null}>
      {typeof node.content === 'string' ? node.content : output(node.content, state)}
    </Text>
  },
  list: (node: ListNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <View key={state.key} style={styles.list}>
      {node.items.map((item, i) => (
        <View key={i} style={styles.listItem}>
          {
            node.ordered ?
              <Text style={styles.listItemNumber}>{`${i + 1}.`}</Text>
              :
              <Text style={styles.listItemBullet}>
                {styles.listItemBullet && styles.listItemBullet.content ? styles.listItemBullet.content : '\u2022'}
              </Text>
          }
          <Text style={node.ordered ? styles.listItemOrderedContent : styles.listItemUnorderedContent}>
            {output(item, state)}
          </Text>
        </View>
      ))}
    </View>
  ),
  newline: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styles.newline}>
      {'\n'}
    </Text>
  ),
  paragraph: paragraphRenderer(),
  strong: textContentRenderer('strong'),
  table: (node: TableNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Grid key={state.key} style={styles.table}>
      {[<Row id={1} key={1}>
        {node.header.map((cell, column) => renderTableCell(cell, 1, column + 1, node.cells.length + 1, node.header.length, output, state, styles))}
      </Row>].concat(node.cells.map((cells, row) => (
        <Row id={row + 2} key={row + 2}>
          {cells.map((cell, column) => renderTableCell(cell, row + 2, column + 1, node.cells.length + 1, cells.length, output, state, styles))}
        </Row>
      )))}
    </Grid>
  ),
  text: textContentRenderer('text'),
  u: textContentRenderer('u')
})
<commit_msg>Fixed heading style<commit_after>/* @flow */

import React from 'react'

import {
  Image,
  Text,
  View,
} from 'react-native'

import {
  Cell,
  Grid,
  Row,
} from 'react-native-tabular-grid-markdown-view'

import type {
  EmptyNode,
  HeadingNode,
  ImageNode,
  InlineContentNode,
  LinkNode,
  ListNode,
  TableNode,
  OutputFunction,
  RenderState,
  RenderStyles,
} from './types'

function renderImage(node: ImageNode, output: OutputFunction, state: RenderState, styles: RenderStyles) {
  const {imageWrapper: wrapperStyle, image: imageStyle} = styles
  return (
    <View key={state.key} style={node.width && node.height ? [wrapperStyle, paddedSize(node, wrapperStyle)] : wrapperStyle}>
      <Image source={{uri: node.target}} style={imageStyle}/>
    </View>
  )
}

function renderTableCell(cell, row, column, rowCount, columnCount, output, state, styles) {
  const cellStyle = [styles.tableCell]
  const contentStyle = [styles.tableCellContent]

  if (row % 2 == 0) {
    cellStyle.push(styles.tableCellEvenRow)
    contentStyle.push(styles.tableCellContentEvenRow)
  } else {
    cellStyle.push(styles.tableCellOddRow)
    contentStyle.push(styles.tableCellContentOddRow)
  }

  if (column % 2 == 0) {
    cellStyle.push(styles.tableCellEvenColumn)
    contentStyle.push(styles.tableCellContentEvenColumn)
  } else {
    cellStyle.push(styles.tableCellOddColumn)
    contentStyle.push(styles.tableCellContentOddColumn)
  }

  if (row == 1) {
    cellStyle.push(styles.tableHeaderCell)
    contentStyle.push(styles.tableHeaderCellContent)
  } else if (row == rowCount) {
    cellStyle.push(styles.tableCellLastRow)
    contentStyle.push(styles.tableCellContentLastRow)
  }

  if (column == columnCount) {
    cellStyle.push(styles.tableCellLastColumn)
    contentStyle.push(styles.tableCellContentLastColumn)
  }

  return <Cell rowId={row} id={column} key={column} style={cellStyle}>
    <Text style={contentStyle}>
      {output(cell, state)}
    </Text>
  </Cell>
}

function paragraphRenderer() {
  var renderText = textContentRenderer('paragraph')

  return (node: InlineContentNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => {
    if (node.content instanceof Array && node.content.length === 1 && node.content[0].type === 'image') {
      const imageNode : ImageNode = node.content[0]
      return renderImage(imageNode, output, state, styles)
    } else {
      return renderText(node, output, state, styles)
    }
  }
}

function textContentRenderer(styleName, styleName2) {
  return (node: InlineContentNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styleName2 ? [styles[styleName], styles[styleName2]] : styles[styleName]}>
      {typeof node.content === 'string' ? node.content : output(node.content, state)}
    </Text>
  )
}

function paddedSize(size, style) {
  function either(a, b) {
    return a === undefined ? b : a
  }

  const {
    padding = 0,
    paddingLeft,
    paddingRight,
    paddingTop,
    paddingBottom,
  } = style

  return {
    width: size.width + either(paddingLeft, padding) + either(paddingRight, padding),
    height: size.height + either(paddingTop, padding) + either(paddingBottom, padding)
  }
}

export default Object.freeze({
  blockQuote: textContentRenderer('blockQuote'),
  br: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styles.br}>
      {'\n\n'}
    </Text>
  ),
  codeBlock: textContentRenderer('codeBlock'),
  del: textContentRenderer('del'),
  em: textContentRenderer('em'),
  heading: (node: HeadingNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    textContentRenderer('heading', 'heading' + node.level)(node, output, state, styles)
  ),
  hr: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <View key={state.key} style={styles.hr}/>
  ),
  image: renderImage,
  inlineCode: textContentRenderer('inlineCode'),
  link: (node: LinkNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => {
    const onPress = state.onLinkPress
    return <Text key={state.key} style={styles.link} onPress={onPress ? () => onPress(node.target) : null}>
      {typeof node.content === 'string' ? node.content : output(node.content, state)}
    </Text>
  },
  list: (node: ListNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <View key={state.key} style={styles.list}>
      {node.items.map((item, i) => (
        <View key={i} style={styles.listItem}>
          {
            node.ordered ?
              <Text style={styles.listItemNumber}>{`${i + 1}.`}</Text>
              :
              <Text style={styles.listItemBullet}>
                {styles.listItemBullet && styles.listItemBullet.content ? styles.listItemBullet.content : '\u2022'}
              </Text>
          }
          <Text style={node.ordered ? styles.listItemOrderedContent : styles.listItemUnorderedContent}>
            {output(item, state)}
          </Text>
        </View>
      ))}
    </View>
  ),
  newline: (node: EmptyNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Text key={state.key} style={styles.newline}>
      {'\n'}
    </Text>
  ),
  paragraph: paragraphRenderer(),
  strong: textContentRenderer('strong'),
  table: (node: TableNode, output: OutputFunction, state: RenderState, styles: RenderStyles) => (
    <Grid key={state.key} style={styles.table}>
      {[<Row id={1} key={1}>
        {node.header.map((cell, column) => renderTableCell(cell, 1, column + 1, node.cells.length + 1, node.header.length, output, state, styles))}
      </Row>].concat(node.cells.map((cells, row) => (
        <Row id={row + 2} key={row + 2}>
          {cells.map((cell, column) => renderTableCell(cell, row + 2, column + 1, node.cells.length + 1, cells.length, output, state, styles))}
        </Row>
      )))}
    </Grid>
  ),
  text: textContentRenderer('text'),
  u: textContentRenderer('u')
})
<|endoftext|>


<commit_before>/***************************************************************************
 * This is the pubsub driver that should tell caspa about the melted status
 *
 * this code is more than slightly coupled with caspa's section that deals
 * with the messages. It should probably be refactored a little, things like
 * creating constants in and handling db collections in mbc-common
 ***************************************************************************/
var events = require('events');
var util = require('util');
var _ = require('underscore');
var mbc = require('mbc-common');

defaults = { // copied from caspa/models.App.Status
    _id: 2,
    piece: {
        previous: {name: ''},
        current:  {name: '', progress: '0%'},
        next:     {name: ''},
    },
    show: {
        previous: {_id: -1},
        current:  {_id: -1},
        next:     {_id: -1},
    },
    source: null,
    on_air: false,
};

function MostoMessage(value, description, message) {
    this.value = value;
    this.description = description;
    this.message = message;
}

function CaspaDriver() {
    events.EventEmitter.call(this);
    var self = this;
    this.status = _.clone(defaults);
    this.db = mbc.db();
    this.publisher = mbc.pubsub();

    CaspaDriver.prototype.setupAll = function() {
        var setups = [
            this.setupStatus,
            this.setupMessages,
        ];
        var sendReady = _.after(setups.length, function() {
            self.emit('ready');
        });
        setups.forEach(function(setup){
            setup(sendReady);
        });
    };

    CaspaDriver.prototype.setupStatus = function(callback) {
        var col = this.db.collection('status');
        col.findOne({_id: 2}, function(err, res) {
            if( err )
                // err.. do something?
                return;
            if( !res ) {
                // the status doesn't exist, create it
                col.save(self.status, function(err, itm) {
                    callback();
                });
            } else {
                // res existed, just signal as ready
                callback();
            }
        });
    };

    CaspaDriver.prototype.setupMessages = function(callback) {
        // I think we should assume at init there's no sticky errors?
        this.db.collection('mostomessages').remove(callback);
    };

    CaspaDriver.prototype.setStatus = function(meltedStatus) {
        // this overrides this.status with the values passed by status
        function makePiece(clip) {
            return {
                name: clip.name,
                _id: clip.id
            }
        }
        var clips = meltedStatus.clips;
        var status = {
            piece: {
                previous: makePiece(clips[meltedStatus.previous]),
                current:  makePiece(clips[meltedStatus.current]),
                next:     makePiece(clips[meltedStatus.next]),
            },
            show: {
                previous: { _id: meltedStatus.show.previous.id },
                current: { _id: meltedStatus.show.current.id },
                next: { _id: meltedStatus.show.next.id },
            },
            on_air: true,
        };
        status.piece.current.progress = meltedStatus.position * 100 + "%";

        this.status = _.extend(this.status, status);
        // except next. If that's undefined, I just don't know!
        this.status.show.next = status.show.next;
        this.publishStatus(status);
    };
    CaspaDriver.prototype.publishStatus = function(status) {
        this.publisher.publish({backend: "mostoStatus", model: status})
    };

    CaspaDriver.prototype.publishMessage = function(code, description, message, sticky) {
        message = new MostoMessage(code, description, message);
        var method = 'emit';
        if( sticky ) {
            // I create an id with the timestamp to be able to cancel the error afterwards
            message.stickId = (new moment()).valueOf();
            method = 'create';
        }
        this.publisher.publish({backend: "mostoMessage", method: method,
                                model: message});
        return message;
    };
    CaspaDriver.prototype.dropMessage = function(message) {
        this.publisher.publish({backend: "mostoMessage", method: 'delete',
                                model: message});
    };
}

util.inherits(CaspaDriver, events.EventEmitter);

exports = module.exports = function() {
    driver = new CaspaDriver();
    driver.setupAll();

    return driver;
};
<commit_msg>checking undefined clip and added undefined var...<commit_after>/***************************************************************************
 * This is the pubsub driver that should tell caspa about the melted status
 *
 * this code is more than slightly coupled with caspa's section that deals
 * with the messages. It should probably be refactored a little, things like
 * creating constants in and handling db collections in mbc-common
 ***************************************************************************/
var events = require('events');
var util = require('util');
var _ = require('underscore');
var mbc = require('mbc-common');

defaults = { // copied from caspa/models.App.Status
    _id: 2,
    piece: {
        previous: {name: ''},
        current:  {name: '', progress: '0%'},
        next:     {name: ''},
    },
    show: {
        previous: {_id: -1},
        current:  {_id: -1},
        next:     {_id: -1},
    },
    source: null,
    on_air: false,
};

function MostoMessage(value, description, message) {
    this.value = value;
    this.description = description;
    this.message = message;
}

function CaspaDriver() {
    events.EventEmitter.call(this);
    var self = this;
    this.status = _.clone(defaults);
    this.db = mbc.db();
    this.publisher = mbc.pubsub();

    CaspaDriver.prototype.setupAll = function() {
        var setups = [
            this.setupStatus,
            this.setupMessages,
        ];
        var sendReady = _.after(setups.length, function() {
            self.emit('ready');
        });
        setups.forEach(function(setup){
            setup(sendReady);
        });
    };

    CaspaDriver.prototype.setupStatus = function(callback) {
        var col = this.db.collection('status');
        col.findOne({_id: 2}, function(err, res) {
            if( err )
                // err.. do something?
                return;
            if( !res ) {
                // the status doesn't exist, create it
                col.save(self.status, function(err, itm) {
                    callback();
                });
            } else {
                // res existed, just signal as ready
                callback();
            }
        });
    };

    CaspaDriver.prototype.setupMessages = function(callback) {
        // I think we should assume at init there's no sticky errors?
        this.db.collection('mostomessages').remove(callback);
    };

    CaspaDriver.prototype.setStatus = function(meltedStatus) {
        // this overrides this.status with the values passed by status
        function makePiece(clip) {
            if (clip===undefined) return { name: '', _id: '' };
            return {
                name: clip.name,
                _id: clip.id
            }
        }

        var clips = meltedStatus.clips;
        var status = {
            piece: {
                previous: makePiece(clips[meltedStatus.clip.previous]),
                current:  makePiece(clips[meltedStatus.clip.current]),
                next:     makePiece(clips[meltedStatus.clip.next]),
            },
            show: {
                previous: { _id: meltedStatus.show.previous },
                current: { _id: meltedStatus.show.current },
                next: { _id: meltedStatus.show.next },
            },
            on_air: true,
        };
        status.piece.current.progress = meltedStatus.position * 100 + "%";

        this.status = _.extend(this.status, status);
        // except next. If that's undefined, I just don't know!
        this.status.show.next = status.show.next;
        this.publishStatus(status);
    };
    CaspaDriver.prototype.publishStatus = function(status) {
        this.publisher.publish({backend: "mostoStatus", model: status})
    };

    CaspaDriver.prototype.publishMessage = function(code, description, message, sticky) {
        message = new MostoMessage(code, description, message);
        var method = 'emit';
        if( sticky ) {
            // I create an id with the timestamp to be able to cancel the error afterwards
            message.stickId = (new moment()).valueOf();
            method = 'create';
        }
        this.publisher.publish({backend: "mostoMessage", method: method,
                                model: message});
        return message;
    };
    CaspaDriver.prototype.dropMessage = function(message) {
        this.publisher.publish({backend: "mostoMessage", method: 'delete',
                                model: message});
    };
}

util.inherits(CaspaDriver, events.EventEmitter);

exports = module.exports = function() {
    var driver = new CaspaDriver();
    driver.setupAll();

    return driver;
};
<|endoftext|>


<commit_before>String.prototype.repeat = function( num )
{
    return new Array( num + 1 ).join( this );
}

BubbleMenu = {

    width: undefined,
    height: undefined,
    element: undefined,
    raphael: undefined,
    animationTimeout: undefined,
    solutions: [],
    data: undefined,
    zoomCount: 0,
    lifeSavingString: "_",

    init: function () {

        if (BubbleMenu.raphael) {
            this.solutions = [];
            BubbleMenu.raphael.remove();
            clearTimeout(this.animationTimeout);
        }
        this.data = $.parseJSON(window.data).solutions;
        this.element = $("#bubble-container");
        this.width = this.element.width();
        this.height = this.element.height();
        this.raphael = new Raphael(this.element.get(0), this.width, this.height);
        this.zoomCount += 1
        this.createSolutions();
        this.animate();
    },

    createSolutions: function () {
        var period = this.width / (this.data.length + 1);
        for (var i = 0; i < this.data.length; i++) {
            var pos = (period * (i + 1));
            var uniqueIdGenerator = this.lifeSavingString.repeat(this.zoomCount) + i

            var solution = new Solution(pos, this.height / 2, uniqueIdGenerator, this.raphael);
            solution.addEventListeners()
            this.solutions.push(solution);
        }
    },

    animate: function () {
        if (this.solutions) {
            for (var i = 0; i < this.solutions.length; i++) {
                this.solutions[i].animate();
            }
            this.animationTimeout = setTimeout(this.animate, 1000);
        }
    }
};

MenuNode = function (raphael) {
    this.raphael = raphael;
    this.sprite = raphael.circle(0, 0, 20).attr({fill: BubbleGraph.SOLUTION_COLOR, stroke: "none"});
    this.direction = Math.round(Math.random()) == 0 ? -1 : 1;
    this.centerPos = BubbleMenu.height / 2;
};

MenuNode.prototype.animate = function () {
    var targetY = this.centerPos + (Math.random() * 25 * this.direction);
    this.sprite.animate({cy: targetY}, 1000);
    this.direction *= -1;
};
<commit_msg>add test code for appending image to bubble slots(commented out)<commit_after>String.prototype.repeat = function( num )
{
    return new Array( num + 1 ).join( this );
}

BubbleMenu = {

    width: undefined,
    height: undefined,
    element: undefined,
    raphael: undefined,
    animationTimeout: undefined,
    solutions: [],
    data: undefined,
    zoomCount: 0,
    lifeSavingString: "_",

    init: function () {

        if (BubbleMenu.raphael) {
            this.solutions = [];
            BubbleMenu.raphael.remove();
            clearTimeout(this.animationTimeout);
        }
        this.data = $.parseJSON(window.data).solutions;
        this.element = $("#bubble-container");
        this.width = this.element.width();
        this.height = this.element.height();
        this.raphael = new Raphael(this.element.get(0), this.width, this.height);
        this.zoomCount += 1
        this.createSolutions();
        this.animate();
    },

    createSolutions: function () {
        var period = this.width / (this.data.length + 1);
        for (var i = 0; i < this.data.length; i++) {
            var pos = (period * (i + 1));
            var uniqueIdGenerator = this.lifeSavingString.repeat(this.zoomCount) + i

            var solution = new Solution(pos, this.height / 2, uniqueIdGenerator, this.raphael);
            // var imageRadius = (solution.radius + (solution.radius * .1)) * 2
            // this.raphael.image("../assets/bubble.png", pos - 35, this.height / 2, imageRadius, imageRadius)
            solution.addEventListeners()
            this.solutions.push(solution);
        }
    },

    animate: function () {
        if (this.solutions) {
            for (var i = 0; i < this.solutions.length; i++) {
                this.solutions[i].animate();
            }
            this.animationTimeout = setTimeout(this.animate, 1000);
        }
    }
};

MenuNode = function (raphael) {
    this.raphael = raphael;
    this.sprite = raphael.circle(0, 0, 20).attr({fill: BubbleGraph.SOLUTION_COLOR, stroke: "none"});
    this.direction = Math.round(Math.random()) == 0 ? -1 : 1;
    this.centerPos = BubbleMenu.height / 2;
};

MenuNode.prototype.animate = function () {
    var targetY = this.centerPos + (Math.random() * 25 * this.direction);
    this.sprite.animate({cy: targetY}, 1000);
    this.direction *= -1;
};
<|endoftext|>


<commit_before>app.factory('crud', ['$http', '$compile', '$animate', '$q', function($http, $compile, $animate, $q) {
  'use strict';

  return {
    extend: function(scope, element) {
      document.title = '';
      scope.title = '';
      scope.primaryKey = '';

      scope.beforeRead = [];
      scope.afterRead = [];
      scope.beforeCreate = [];
      scope.afterCreated = [];
      scope.beforeUpdate = [];
      scope.afterUpdated = [];
      scope.beforeDelete = [];
      scope.afterDeleted= [];

      scope.list = {};
      scope.paginator = {};
      scope.paginator.count = 0;
      scope.paginator.page = 0;
      scope.paginator.perPage = 0;
      scope.paginator.lastPage = 0;

      // read data from server
      scope.read = function(c, m, params) {
        var beforeResult = true;
        for (var i = 0; i < scope.beforeRead.length; i++) {
          beforeResult = beforeResult && (scope.beforeRead[i])();
        }

        if (beforeResult === true) {
          if (!params) {
            params = '';
          }

          var p = $http({
            method: 'GET',
            url: c + '/' + m + params,
          });
          p.success(function(response){
            scope.title = response.title;
            document.title = scope.title;
            // must exclude relation fields when saving
            scope.relation = response.relation;

            for (var i = 0; i < scope.afterRead.length; i++) {
              (scope.afterRead[i])(response);
            }
          });
          p.error(function(response, status) {
            Util.hideIosNotify();
            Util.notify('操作失败', 'error');
          });
        }
      };

      // write data to server
      scope.write = function(c, m, data, item) {
        var url = '';
        var success = {};
        var method = '';

        data['authenticity_token'] = $("meta[name='csrf-token']").attr('content');

        switch (m) {
          case 'create':
            url = c;
            method = 'POST';
            success = function(response, status) {
              Util.hideIosNotify();
              if (response.code === 1) {
                // update primary key into new item
                item[scope.primaryKey] = response[scope.primaryKey];

                for (var i = 0; i < scope.afterCreated.length; i++) {
                  (scope.afterCreated[i])(item);
                }
                Util.notify(response.desc);
                element.find('#panel-list').removeClass('dn').addClass('db');
                element.find('#panel-edit').removeClass('db').addClass('dn');
              } else {
                Util.notify(response.desc, 'error');
              }
            };
            break;
        case 'edit':
          url = c + '/' + item[scope.primaryKey];
          method = 'PUT';
          data[scope.primaryKey] = item[scope.primaryKey];
          success = function(response, status) {
            Util.hideIosNotify();
            if (response.code === 1) {
              for (var i = 0; i < scope.afterUpdated.length; i++) {
                (scope.afterUpdated[i])(response);
              }
              Util.notify(response.desc);
              element.find('#panel-list').removeClass('dn').addClass('db');
              element.find('#panel-edit').removeClass('db').addClass('dn');
            } else {
              Util.notify(response.desc, 'error');
            }
          };
          break;
        case 'destroy':
          url = c + '/' + item[scope.primaryKey];
          method = 'DELETE';
          data[scope.primaryKey] = item[scope.primaryKey];
          success = function(response, status) {
            Util.hideIosNotify();
            if (response.code === 1) {
              // remove item from list, and reindex the list
              var index = scope.list.data.indexOf(item);
              scope.list.data.splice(index, 1);

              for (var i = 0; i < scope.afterDeleted.length; i++) {
                (scope.afterDeleted[i])(response);
              }
              Util.notify(response.desc);
              element.find('#panel-list').removeClass('dn').addClass('db');
              element.find('#panel-edit').removeClass('db').addClass('dn');
            } else {
              Util.notify(response.desc, 'error');
            }
          }
          break;
        default:
          Util.hideIosNotify();
          Util.notify('操作失败', 'error');
          return;
        }

        var p = $http({
          method: method,
          url: url,
          headers: {'Content-Type': 'application/json'},
          data: data,
        });

        p.success(success);
        p.error(function(response, status) {
          Util.hideIosNotify();
          Util.notify('操作失败', 'error');
        });
      };

      // show edit panel
      scope.edit = function(item) {
        scope.editingItem = item;
        scope.itemModel = angular.copy(item);

        element.find('#panel-list').removeClass('db').addClass('dn');
        element.find('#panel-edit').removeClass('dn').addClass('db');

        element.find("#editForm .has-success").removeClass("has-success");
        element.find("#editForm .has-error").removeClass("has-error");
        //$animate.setClass(element.find("#panel-list"), 'db', 'dn')
        //.then($animate.setClass(element.find("#panel-edit"), 'dn', 'db'));
        //.then(function() {
        //  console.log('entered');
        //});
        //  // after animating, focus to the first textbox and select all text
        //  $("#panel-edit :text").first().focus();
        //  $("#panel-edit :text").first().select();

        // save when press enter
        $("#panel-edit .form-control").off('keydown');
        $("#panel-edit .form-control").on('keydown', function(e){
          if (e.keyCode == 13){
            scope.save();
          }
        });
      };

      scope.uniqueCount = 0;

      // 唯一验证虚函数
      scope.checkUnique = function(formControl, primaryKey, model, value) {
        var params = '?eq={"' + model + '":"' + value + '"}';
        if (primaryKey != undefined && primaryKey != '') {
          params += '&noteq={"' + $("#primaryKey").val() + '":"' + primaryKey + '"}';
        }
        //var count = scope.getDuplicateCount(params, focusForm);
        //var count = 0;

        var p = $http({
          method: 'GET',
          url: Util.getController() + '/get_count' + params,
        });

        p.success(function(response) {
          // 非 '0' 表示存在重复
          if (response != '0' && response != '"0"' && response != '""0""') {
            formControl.parent().removeClass('has-success');
            formControl.parent().addClass('has-error');
            formControl.focus();
            return;
          } else {
            formControl.parent().removeClass('has-error');
            formControl.parent().addClass('has-success');
          }

          scope.uniqueCount -= 1;

          if (scope.uniqueCount <= 0) {
            Util.showIosNotify('请稍候...');
            scope.saveCommit();
          }
        });
      }

      scope.saveCommit = function() {
        var item = {};
        var data = {};

        // 遍历model
        for(var key in scope.itemModel) {
          item[key] = scope.itemModel[key];

          // 排除angular生成的$$hashkey，以及其他自定义/特殊字段
          if (key.substring(0, 2) != '$$' && key != scope.primaryKey && key != 'created_at' && key != 'updated_at') {
            // 保存时排除关联字段
            if (!angular.isUndefined(scope.relation)) {
              var except = false;
              angular.forEach(scope.relation, function(relation_value, relation_key) {
                if (key == relation_value) {
                  except = true;
                }
              });

              if (except === false) {
                // 排除排除字段
                //if (key.indexOf(scope.exclude) == -1) {
                  data[key] = item[key];
               // }
              }
            } else {
              data[key] = item[key];
            }
          }
        }

        if (item[scope.primaryKey]) {
          scope.write(Util.getController(), 'edit', data, item);
        } else {
          scope.write(Util.getController(), 'create', data, item);
        }
      };

      scope.save = function() {
        var i, beforeResult = true;

        if (scope.itemModel[scope.primaryKey]) {
          for (i = 0; i < scope.beforeUpdate.length; i++) {
            beforeResult = beforeResult && (scope.beforeUpdate[i])();
          }
        } else {
          for (i = 0; i < scope.beforeCreate.length; i++) {
            beforeResult = beforeResult && (scope.beforeCreate[i])();
          }
        }
        if (beforeResult === false) {
          return false;
        }

        // validate required fields
        if (angular.isDefined(scope.validate)) {
          if (scope.validate() === false) {
            return;
          }
        }

        // validate unique fields
        var controls = $("#editForm input[unique]");
        scope.uniqueCount = controls.length;

        if (controls.length > 0) {
          for (var i = 0; i < controls.length; i++) {
            var err = scope.checkUnique($(controls[i]),
              scope.itemModel[scope.primaryKey],
              $(controls[i]).attr('id'),
              $(controls[i]).val().trim());
          }
        } else {
          Util.showIosNotify('请稍候...');
          scope.saveCommit();
        }
      };

      scope.validate = function() {
        var result = true;
        var value = '';
        var controlName = '';

        var controls = $("#editForm [required]");
        for (var i = 0; i < controls.length; i++) {
          // 如果必填输入框值为空字符串
          value = $(controls[i]).val().trim();

          if (value == '' || value == '? undefined:undefined ?' || value == '?') {
            Util.focus($(controls[i]));
            if (controls[i].type == 'text') {
              controls[i].select();
            }
            controlName = $(controls[i]).attr("placeholder");
            if (!angular.isUndefined(controlName) && controlName != '') {
              Util.notify("请输入" + controlName, 'error');
            }

            return false;
          }
        }

        var controls = $("#editForm input[numeric]");
        for (var i = 0; i < controls.length; i++) {
          // 输入框值为非数字
          if (isNaN($(controls[i]).val().trim())) {
            Util.focus($(controls[i]));
            controls[i].select();
            controlName = $(controls[i]).attr("placeholder");
            if (!angular.isUndefined(controlName) && controlName != '') {
              Util.notify(controlName + "请输入数字", 'error');
            }
            return false;
          }
        }

        return result;
      };

      scope.bulkDelete = function(selection) {
        var items = [];

        for(var key in selection) {
          items.push(selection[key]);
        }
        scope.delConfirm(items);
      }

      scope.delConfirm = function(item, message) {
        if (message == undefined) {
          message = '确定删除吗？';
        }
        // 如果模态对话框不存在则创建html
        if ($('#delConfirmModal').length <= 0) {
          var html = '
'
            + '
'
            + '
'
            + '
'
            + '               ×'
            + '
提示框
'
            + '
'
            + '
'
            + '
' + message + '
'
            + '
'
            + '
'
            + '                '
            + '                '
            + '
'
            + '
'
            + '
'
            + '
';

          element.append($compile(html)(scope));
        }

        scope.itemDel = item;
        $('#delConfirmModal').modal("show");
      };

      // delete data
      scope.del = function() {
        var beforeResult = true;
        for (var i = 0; i < scope.beforeDelete.length; i++) {
          beforeResult = beforeResult && (scope.beforeDelete[i])();
        }

        if (beforeResult === true) {
          Util.showIosNotify('processing...');

          // bulk delete
          if (Util.isArray(scope.itemDel)) {
            var ids = [];
            var authenticity_token = $("meta[name='csrf-token']").attr('content');

            for (var i = 0; i< scope.itemDel.length; i++) {
              ids.push(scope.itemDel[i].id);
            }

            // delete multiple rows
            var p = $http({
              method: 'DELETE',
              headers: {'Content-Type': 'application/json'},
              data: {ids: ids, authenticity_token: authenticity_token},
              url: Util.getController() + '/bulk_delete'
            });
            p.success(function(response){
              if (response.code === 1) {
                for(var i = 0; i< scope.itemDel.length; i++) {
                  // remove item from list, and reindex the list
                  var index = scope.list.data.indexOf(scope.selection.pop());
                  scope.list.data.splice(index, 1);

                  for (var i = 0; i < scope.afterDeleted.length; i++) {
                    (scope.afterDeleted[i])(response);
                  }
                }
              }

              Util.hideIosNotify();
              Util.notify(response.desc);
            })
            p.error(function(response, status) {
              Util.hideIosNotify();
              Util.notify('系统错误，请联系管理员', 'error');
            })
          } else {
            var data = {};
            data[scope.primaryKey] = scope.itemDel[scope.primaryKey];
            scope.write(Util.getController(), 'destroy', data, scope.itemDel);
          }
        }
      };

      /**
       * batchUpdate 专属的 write (后期应考虑与 scope.write 整合)
       * @param {
       *      method     : method name
       *      data       : http post data
       *      item       : item object
       *      next       : next after http success
       *      sucess     : call back after http sucess [可选]
       * }
       */
      scope.writeForBatchUpdate = function(options) {
        var p = $http({
          method: 'POST',
          url: Util.controller + '/' + options.method,
          headers: {'Content-Type': 'application/x-www-form-urlencoded'},
          data: options.data,
        });

        p.success(function(response, status){
          if (options.success) {
            options.success(options.item);
            Util.notify('更新成功');
          }
          options.next();
        });

        p.error(function(response, status) {
          Util.hideIosNotify('操作失败');
        });
      }

      /**
       * 批量更新
       *
       * @param {
       *   selection  : selected item collection
       *   method     : method name
       *   fnBefore   : call back before batch update
       *   fnAfter    : call back after batch update
       *   fnEach     : call back after each success
       * }
       */
      scope.batchUpdate = function(options) {
        var cancel = true;
        var items = [];

        for(var key in options.selection) {
          items.push(options.selection[key]);
        }

        var fnDone = function() {
          Util.hideIosNotify('完成', 'img/cross.png');
          options.fnAfter && options.fnAfter();
        }

        var index = 0;
        var fnContinue = function() {
          if (index <= items.length) {
            var item = items[index];
            if (index === 0) {
              Util.showIosNotify('请稍后...')
            }
            index++;

            scope.writeForBatchUpdate({
              method      : options.method,
              data        : scope.primaryKey + '=' + item[scope.primaryKey],
              item        : item,
              next        : (index >= items.length) ? fnDone : fnContinue,
              success     : options.fnEach,
            });
          }
        }

        if (options.fnBefore !== undefined && options.fnBefore !== null) {
          options.fnBefore(fnContinue);
        }
      }
    }
  };
}]);

<commit_msg>translation<commit_after>app.factory('crud', ['$http', '$compile', '$animate', '$q', function($http, $compile, $animate, $q) {
  'use strict';

  return {
    extend: function(scope, element) {
      document.title = '';
      scope.title = '';
      scope.primaryKey = '';

      scope.beforeRead = [];
      scope.afterRead = [];
      scope.beforeCreate = [];
      scope.afterCreated = [];
      scope.beforeUpdate = [];
      scope.afterUpdated = [];
      scope.beforeDelete = [];
      scope.afterDeleted= [];

      scope.list = {};
      scope.paginator = {};
      scope.paginator.count = 0;
      scope.paginator.page = 0;
      scope.paginator.perPage = 0;
      scope.paginator.lastPage = 0;

      // read data from server
      scope.read = function(c, m, params) {
        var beforeResult = true;
        for (var i = 0; i < scope.beforeRead.length; i++) {
          beforeResult = beforeResult && (scope.beforeRead[i])();
        }

        if (beforeResult === true) {
          if (!params) {
            params = '';
          }

          var p = $http({
            method: 'GET',
            url: c + '/' + m + params,
          });
          p.success(function(response){
            scope.title = response.title;
            document.title = scope.title;
            // must exclude relation fields when saving
            scope.relation = response.relation;

            for (var i = 0; i < scope.afterRead.length; i++) {
              (scope.afterRead[i])(response);
            }
          });
          p.error(function(response, status) {
            Util.hideIosNotify();
            Util.notify('Error', 'error');
          });
        }
      };

      // write data to server
      scope.write = function(c, m, data, item) {
        var url = '';
        var success = {};
        var method = '';

        data['authenticity_token'] = $("meta[name='csrf-token']").attr('content');

        switch (m) {
          case 'create':
            url = c;
            method = 'POST';
            success = function(response, status) {
              Util.hideIosNotify();
              if (response.code === 1) {
                // update primary key into new item
                item[scope.primaryKey] = response[scope.primaryKey];

                for (var i = 0; i < scope.afterCreated.length; i++) {
                  (scope.afterCreated[i])(item);
                }
                Util.notify(response.desc);
                element.find('#panel-list').removeClass('dn').addClass('db');
                element.find('#panel-edit').removeClass('db').addClass('dn');
              } else {
                Util.notify(response.desc, 'error');
              }
            };
            break;
        case 'edit':
          url = c + '/' + item[scope.primaryKey];
          method = 'PUT';
          data[scope.primaryKey] = item[scope.primaryKey];
          success = function(response, status) {
            Util.hideIosNotify();
            if (response.code === 1) {
              for (var i = 0; i < scope.afterUpdated.length; i++) {
                (scope.afterUpdated[i])(response);
              }
              Util.notify(response.desc);
              element.find('#panel-list').removeClass('dn').addClass('db');
              element.find('#panel-edit').removeClass('db').addClass('dn');
            } else {
              Util.notify(response.desc, 'error');
            }
          };
          break;
        case 'destroy':
          url = c + '/' + item[scope.primaryKey];
          method = 'DELETE';
          data[scope.primaryKey] = item[scope.primaryKey];
          success = function(response, status) {
            Util.hideIosNotify();
            if (response.code === 1) {
              // remove item from list, and reindex the list
              var index = scope.list.data.indexOf(item);
              scope.list.data.splice(index, 1);

              for (var i = 0; i < scope.afterDeleted.length; i++) {
                (scope.afterDeleted[i])(response);
              }
              Util.notify(response.desc);
              element.find('#panel-list').removeClass('dn').addClass('db');
              element.find('#panel-edit').removeClass('db').addClass('dn');
            } else {
              Util.notify(response.desc, 'error');
            }
          }
          break;
        default:
          Util.hideIosNotify();
          Util.notify('Error', 'error');
          return;
        }

        var p = $http({
          method: method,
          url: url,
          headers: {'Content-Type': 'application/json'},
          data: data,
        });

        p.success(success);
        p.error(function(response, status) {
          Util.hideIosNotify();
          Util.notify('Error', 'error');
        });
      };

      // show edit panel
      scope.edit = function(item) {
        scope.editingItem = item;
        scope.itemModel = angular.copy(item);

        element.find('#panel-list').removeClass('db').addClass('dn');
        element.find('#panel-edit').removeClass('dn').addClass('db');

        element.find("#editForm .has-success").removeClass("has-success");
        element.find("#editForm .has-error").removeClass("has-error");
        //$animate.setClass(element.find("#panel-list"), 'db', 'dn')
        //.then($animate.setClass(element.find("#panel-edit"), 'dn', 'db'));
        //.then(function() {
        //  console.log('entered');
        //});
        //  // after animating, focus to the first textbox and select all text
        //  $("#panel-edit :text").first().focus();
        //  $("#panel-edit :text").first().select();

        // save when press enter
        $("#panel-edit .form-control").off('keydown');
        $("#panel-edit .form-control").on('keydown', function(e){
          if (e.keyCode == 13){
            scope.save();
          }
        });
      };

      scope.uniqueCount = 0;

      scope.checkUnique = function(formControl, primaryKey, model, value) {
        var params = '?eq={"' + model + '":"' + value + '"}';
        if (primaryKey != undefined && primaryKey != '') {
          params += '&noteq={"' + $("#primaryKey").val() + '":"' + primaryKey + '"}';
        }

        var p = $http({
          method: 'GET',
          url: Util.getController() + '/get_count' + params,
        });

        p.success(function(response) {
          if (response !== 0) {
            formControl.parent().removeClass('has-success');
            formControl.parent().addClass('has-error');
            formControl.focus();
            return;
          } else {
            formControl.parent().removeClass('has-error');
            formControl.parent().addClass('has-success');
          }

          scope.uniqueCount -= 1;

          if (scope.uniqueCount <= 0) {
            Util.showIosNotify('Loading...');
            scope.saveCommit();
          }
        });
      }

      scope.saveCommit = function() {
        var item = {};
        var data = {};

        // iterate model
        for(var key in scope.itemModel) {
          item[key] = scope.itemModel[key];

          // exclude $$hashkey generated by angular, and some special fields
          if (key.substring(0, 2) != '$$' && key != scope.primaryKey && key != 'created_at' && key != 'updated_at') {
            // exclude relation fields
            if (!angular.isUndefined(scope.relation)) {
              var except = false;
              angular.forEach(scope.relation, function(relation_value, relation_key) {
                if (key == relation_value) {
                  except = true;
                }
              });

              if (except === false) {
                data[key] = item[key];
              }
            } else {
              data[key] = item[key];
            }
          }
        }

        if (item[scope.primaryKey]) {
          scope.write(Util.getController(), 'edit', data, item);
        } else {
          scope.write(Util.getController(), 'create', data, item);
        }
      };

      scope.save = function() {
        var i, beforeResult = true;

        if (scope.itemModel[scope.primaryKey]) {
          for (i = 0; i < scope.beforeUpdate.length; i++) {
            beforeResult = beforeResult && (scope.beforeUpdate[i])();
          }
        } else {
          for (i = 0; i < scope.beforeCreate.length; i++) {
            beforeResult = beforeResult && (scope.beforeCreate[i])();
          }
        }
        if (beforeResult === false) {
          return false;
        }

        // validate required fields
        if (angular.isDefined(scope.validate)) {
          if (scope.validate() === false) {
            return;
          }
        }

        // validate unique fields
        var controls = $("#editForm input[unique]");
        scope.uniqueCount = controls.length;

        if (controls.length > 0) {
          for (var i = 0; i < controls.length; i++) {
            var err = scope.checkUnique($(controls[i]),
              scope.itemModel[scope.primaryKey],
              $(controls[i]).attr('id'),
              $(controls[i]).val().trim());
          }
        } else {
          Util.showIosNotify('Saving...');
          scope.saveCommit();
        }
      };

      scope.validate = function() {
        var result = true;
        var value = '';
        var controlName = '';

        var controls = $("#editForm [required]");
        for (var i = 0; i < controls.length; i++) {
          // trim first
          value = $(controls[i]).val().trim();

          if (value == '' || value == '? undefined:undefined ?' || value == '?') {
            Util.focus($(controls[i]));
            if (controls[i].type == 'text') {
              controls[i].select();
            }
            controlName = $(controls[i]).attr("placeholder");
            if (!angular.isUndefined(controlName) && controlName != '') {
              Util.notify("请输入" + controlName, 'error');
            }

            return false;
          }
        }

        var controls = $("#editForm input[numeric]");
        for (var i = 0; i < controls.length; i++) {
          // NAN
          if (isNaN($(controls[i]).val().trim())) {
            Util.focus($(controls[i]));
            controls[i].select();
            controlName = $(controls[i]).attr("placeholder");
            if (!angular.isUndefined(controlName) && controlName != '') {
              Util.notify(controlName + "请输入数字", 'error');
            }
            return false;
          }
        }

        return result;
      };

      scope.bulkDelete = function(selection) {
        var items = [];

        for(var key in selection) {
          items.push(selection[key]);
        }
        scope.delConfirm(items);
      }

      scope.delConfirm = function(item, message) {
        message = message || 'Are you sure to delete?';

        // add html
        if ($('#delConfirmModal').length <= 0) {
          var html = '
'
            + '
'
            + '
'
            + '
'
            + '               ×'
            + '
Confirm Box
'
            + '
'
            + '
'
            + '
' + message + '
'
            + '
'
            + '
'
            + '                '
            + '                '
            + '
'
            + '
'
            + '
'
            + '
';

          element.append($compile(html)(scope));
        }

        scope.itemDel = item;
        $('#delConfirmModal').modal("show");
      };

      // delete data
      scope.del = function() {
        var beforeResult = true;
        for (var i = 0; i < scope.beforeDelete.length; i++) {
          beforeResult = beforeResult && (scope.beforeDelete[i])();
        }

        if (beforeResult === true) {
          Util.showIosNotify('processing...');

          // bulk delete
          if (Util.isArray(scope.itemDel)) {
            var ids = [];
            var authenticity_token = $("meta[name='csrf-token']").attr('content');

            for (var i = 0; i< scope.itemDel.length; i++) {
              ids.push(scope.itemDel[i].id);
            }

            // delete multiple rows
            var p = $http({
              method: 'DELETE',
              headers: {'Content-Type': 'application/json'},
              data: {ids: ids, authenticity_token: authenticity_token},
              url: Util.getController() + '/bulk_delete'
            });
            p.success(function(response){
              if (response.code === 1) {
                for(var i = 0; i< scope.itemDel.length; i++) {
                  // remove item from list, and reindex the list
                  var index = scope.list.data.indexOf(scope.selection.pop());
                  scope.list.data.splice(index, 1);

                  for (var i = 0; i < scope.afterDeleted.length; i++) {
                    (scope.afterDeleted[i])(response);
                  }
                }
              }

              Util.hideIosNotify();
              Util.notify(response.desc);
            })
            p.error(function(response, status) {
              Util.hideIosNotify();
              Util.notify('Error', 'error');
            })
          } else {
            var data = {};
            data[scope.primaryKey] = scope.itemDel[scope.primaryKey];
            scope.write(Util.getController(), 'destroy', data, scope.itemDel);
          }
        }
      };

      /**
       * batchUpdate 专属的 write (后期应考虑与 scope.write 整合)
       * @param {
       *      method     : method name
       *      data       : http post data
       *      item       : item object
       *      next       : next after http success
       *      sucess     : call back after http sucess [可选]
       * }
       */
      scope.writeForBatchUpdate = function(options) {
        var p = $http({
          method: 'POST',
          url: Util.controller + '/' + options.method,
          headers: {'Content-Type': 'application/x-www-form-urlencoded'},
          data: options.data,
        });

        p.success(function(response, status){
          if (options.success) {
            options.success(options.item);
            Util.notify('更新成功');
          }
          options.next();
        });

        p.error(function(response, status) {
          Util.hideIosNotify('Error');
        });
      }

      /**
       * 批量更新
       *
       * @param {
       *   selection  : selected item collection
       *   method     : method name
       *   fnBefore   : call back before batch update
       *   fnAfter    : call back after batch update
       *   fnEach     : call back after each success
       * }
       */
      scope.batchUpdate = function(options) {
        var cancel = true;
        var items = [];

        for(var key in options.selection) {
          items.push(options.selection[key]);
        }

        var fnDone = function() {
          Util.hideIosNotify('success', 'img/cross.png');
          options.fnAfter && options.fnAfter();
        }

        var index = 0;
        var fnContinue = function() {
          if (index <= items.length) {
            var item = items[index];
            if (index === 0) {
              Util.showIosNotify('Loading...')
            }
            index++;

            scope.writeForBatchUpdate({
              method      : options.method,
              data        : scope.primaryKey + '=' + item[scope.primaryKey],
              item        : item,
              next        : (index >= items.length) ? fnDone : fnContinue,
              success     : options.fnEach,
            });
          }
        }

        if (options.fnBefore !== undefined && options.fnBefore !== null) {
          options.fnBefore(fnContinue);
        }
      }
    }
  };
}]);

<|endoftext|>


<commit_before>import {Fragment, Slice} from "../model"

import {Transform} from "./transform"
import {Step, StepResult} from "./step"
import {PosMap, ReplacedRange} from "./map"

// !! **`replace`**
//   : Delete the part of the document between `from` and `to` and
//     optionally replace it with another chunk of content. `pos` must
//     point at the ‘root’ at which the cut starts—a position between
//     and above `from` and `to`.
//
//     When new content is to be inserted, the step's parameter should
//     be an object of shape `{content: `[`Fragment`](#Fragment)`,
//     openLeft: number, openRight: number}`. The step will insert the
//     given content at the root of the cut, and `openLeft` and
//     `openRight` indicate how much of the content on both sides
//     should be consided ‘open’.
//
//     A replace step will try to join open nodes on both sides of the
//     cut. That is, nodes in the original document that are partially
//     cut off by `from` and `to`, and nodes at the sides of the
//     replacement content as specificed by `openLeft` and
//     `openRight`. For example, if `openLeft` is 2, the first node of
//     the replacement content as well as its first child is
//     considered open. Whenever two open nodes with the same
//     [markup](#Node.sameMarkup) end up next to each other, they are
//     joined. Open nodes that aren't joined are [closed](#Node.close)
//     to ensure their content (or lack of it) is valid.

Step.define("replace", {
  apply(doc, step) {
    return StepResult.fromReplace(doc, step.from, step.to, step.param)
  },
  posMap(step) {
    return new PosMap([new ReplacedRange(step.from, step.to - step.from, step.param.size)])
  },
  invert(step, oldDoc) {
    return new Step("replace", step.from, step.from + step.param.size,
                    oldDoc.slice(step.from, step.to))
  },
  paramToJSON(param) { return param.toJSON() },
  paramFromJSON(schema, json) { return Slice.fromJSON(schema, json) }
})

// :: (number, number) → Transform
// Delete the content between the given positions.
Transform.define("delete", function(from, to) {
  this.replace(from, to, Slice.empty)
})

// :: (number, ?number, ?Slice) → Transform
// Replace the part of the document between `from` and `to` with the
// part of the `source` between `start` and `end`.
Transform.define("replace", function(from, to = from, slice = Slice.empty) {
  let rFrom = this.doc.resolve(from), rTo = this.doc.resolve(to)
  let {fitted, distAfter} = fitSliceInto(rFrom, rTo, slice), fSize = fitted.size
  if (from == to && !fSize) return
  this.step("replace", from, to, fitted)

  if (!fSize || !rTo.node[rTo.depth].isTextblock) return
  let after = from + fSize
  let inner = !slice.size ? from : distAfter < 0 ? -1 : after - distAfter, rInner
  if (inner == -1 || inner == after || !(rInner = this.doc.resolve(inner)).node[rInner.depth].isTextblock) return
  mergeTextblockAfter(this, rInner, this.doc.resolve(after))
})

// :: (number, number, union<Fragment, Node, [Node]>) → Transform
// Replace the given range with the given content, which may be a
// fragment, node, or array of nodes.
Transform.define("replaceWith", function(from, to, content) {
  this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
})

// :: (number, union<Fragment, Node, [Node]>) → Transform
// Insert the given content at the given position.
Transform.define("insert", function(pos, content) {
  this.replaceWith(pos, pos, content)
})

// :: (number, string) → Transform
// Insert the given text at `pos`, inheriting the marks of the
// existing content at that position.
Transform.define("insertText", function(pos, text) {
  this.insert(pos, this.doc.type.schema.text(text, this.doc.marksAt(pos)))
})

// :: (number, Node) → Transform
// Insert the given node at `pos`, inheriting the marks of the
// existing content at that position.
Transform.define("insertInline", function(pos, node) {
  this.insert(pos, node.mark(this.doc.marksAt(pos)))
})

function fitSliceInto(from, to, slice) {
  let base = from.sameDepth(to)
  let placed = placeSlice(from, slice), outer = outerPlaced(placed)
  if (outer) base = Math.min(outer.depth, base)

  let distAfter = [-1e10] // FIXME kludge
  let fragment = closeFragment(from.node[base].type, fillBetween(from, to, base, placed, distAfter), from, to, base)
  return {fitted: new Slice(fragment, from.depth - base, to.depth - base),
          distAfter: distAfter[0] - (to.depth - base)}
}

function outerPlaced(placed) {
  for (let i = 0; i < placed.length; i++) if (placed[i]) return placed[i]
}

function fillBetween(from, to, depth, placed, distAfter) {
  let fromNext = from.depth > depth && from.node[depth + 1]
  let toNext = to.depth > depth && to.node[depth + 1]
  let placedHere = placed[depth]

  if (fromNext && toNext && fromNext.type.canContainContent(toNext.type) && !placedHere)
    return Fragment.from(closeNode(fromNext, fillBetween(from, to, depth + 1, placed, distAfter),
                                   from, to, depth + 1))

  let content = Fragment.empty
  if (placedHere) {
    content = closeLeft(placedHere.content, placedHere.openLeft)
    if (placedHere.isEnd) distAfter[0] = placedHere.openRight
  }

  if (fromNext)
    content = content.addToStart(closeNode(fromNext, fillFrom(from, depth + 1, placed),
                                           from, null, depth + 1))
  if (toNext)
    content = closeTo(content, to, depth + 1, placedHere ? placedHere.openRight : 0, distAfter)
  else if (placedHere)
    content = closeRight(content, placedHere.openRight, distAfter)
  return content
}

function fillFrom(from, depth, placed) {
  let placedHere = placed[depth]
  let content = placedHere ? closeRight(placedHere.content, placedHere.openRight) : Fragment.empty
  if (from.depth > depth)
    content = content.addToStart(closeNode(from.node[depth + 1], fillFrom(from, depth + 1, placed),
                                           from, null, depth + 1))
  return content
}

function closeTo(content, to, depth, openDepth, distAfter) {
  let after = to.node[depth]
  if (openDepth == 0 || !after.type.canContainContent(content.lastChild.type)) {
    let finish = fillTo(to, depth)
    distAfter[0] += finish.size
    return closeRight(content, openDepth).addToEnd(closeNode(after, finish, null, to, depth))
  }
  let inner = content.lastChild.content
  if (depth < to.depth) inner = closeTo(inner, to, depth + 1, openDepth - 1, distAfter)
  return content.replace(content.childCount - 1, after.copy(inner))
}

function fillTo(to, depth) {
  if (to.depth == depth) return Fragment.empty
  return Fragment.from(closeNode(to.node[depth + 1], fillTo(to, depth + 1), null, to, depth + 1))
}

// Closing nodes is the process of ensuring that they contain valid
// content, optionally changing the content (that is inside of the
// replace) to make sure.

function closeRight(content, openDepth) {
  if (openDepth == 0) return content
  let last = content.lastChild, closed = closeNode(last, closeRight(last.content, openDepth - 1))
  return closed == last ? content : content.replace(content.childCount - 1, closed)
}

function closeLeft(content, openDepth) {
  if (openDepth == 0) return content
  let first = content.firstChild, closed = closeNode(first, first.content)
  return closed == first ? content : content.replace(0, closed)
}

function closeFragment(type, content, to, from, depth) {
  // FIXME replace this with a more general approach
  if (type.canBeEmpty) return content
  let hasContent = content.size || (to && (to.depth > depth || to.index[depth])) ||
      (from && (from.depth > depth || from.index[depth] < from.node[depth].childCount))
  return hasContent ? content : type.defaultContent()
}

function closeNode(node, content, to, from, depth) {
  return node.copy(closeFragment(node.type, content, to, from, depth))
}

// Algorithm for 'placing' the elements of a slice into a gap:
//
// We consider the content of each node that is open to the left to be
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
// paragraph on the left is open, "foo" can be placed (somewhere on
// the left side of the replacement gap) independently from p("bar").
//
// So placeSlice splits up a slice into a number of sub-slices,
// along with information on where they can be placed on the given
// left-side edge. It works by walking the open side of the slice,
// from the inside out, and trying to find a landing spot for each
// element, by simultaneously scanning over the gap side. When no
// place is found for an open node's content, it is left in that node.
//
// If the outer content can't be placed, a set of wrapper nodes is
// made up for it (by rooting it in the document node type using
// findConnection), and the algorithm continues to iterate over those.
// This is guaranteed to find a fit, since both stacks now start with
// the same node type (doc).

function fragmentSuperKind(fragment) {
  let kind
  for (let i = fragment.childCount - 1; i >= 0; i--) {
    let cur = fragment.child(i).type.kind
    kind = kind ? kind.sharedSuperKind(cur) : cur
  }
  return kind
}

function placeSlice(from, slice) {
  let dFrom = from.depth, unplaced = null, openLeftUnplaced = 0
  let placed = [], parents = null

  for (let dSlice = slice.openLeft;; --dSlice) {
    let curType, curAttrs, curFragment
    if (dSlice >= 0) {
      if (dSlice > 0) { // Inside slice
        ;({type: curType, attrs: curAttrs, content: curFragment} = slice.nodeLeft(dSlice))
      } else if (dSlice == 0) { // Top of slice
        curFragment = slice.content
      }
      if (dSlice < slice.openLeft) curFragment = curFragment.cut(curFragment.firstChild.nodeSize)
    } else { // Outside slice
      curFragment = Fragment.empty
      curType = parents[parents.length + dSlice - 1]
    }
    if (unplaced)
      curFragment = curFragment.addToStart(unplaced)

    if (curFragment.size == 0 && dSlice <= 0) break

    let found = findPlacement(curType, curFragment, from, dFrom)
    if (found > -1) {
      if (curFragment.size > 0)
        placed[found] = {content: curFragment,
                         openLeft: openLeftUnplaced,
                         openRight: dSlice > 0 ? 0 : slice.openRight - dSlice,
                         isEnd: dSlice <= 0,
                         depth: found}
      if (dSlice <= 0) break
      unplaced = null
      openLeftUnplaced = 0
      dFrom = Math.max(0, found - 1)
    } else {
      if (dSlice == 0) {
        parents = from.node[0].type.findConnectionToKind(fragmentSuperKind(curFragment))
        if (!parents) break
        parents.unshift(from.node[0].type)
        curType = parents[parents.length - 1]
      }
      unplaced = curType.create(curAttrs, curFragment)
      openLeftUnplaced++
    }
  }

  return placed
}

function findPlacement(type, fragment, from, start) {
  for (let d = start; d >= 0; d--) {
    let fromType = from.node[d].type
    if (type ? fromType.canContainContent(type) : fromType.canContainFragment(fragment))
      return d
  }
  return -1
}

// When a replace ends in an (open) textblock, and the content that
// ends up before it also ends in an open textblock, the textblock
// after is moved to and connected with the one before it. This
// influences content outside of the replaced range, so it is not done
// as part of the replace step itself, but instead tacked on as a set
// of split/ancestor/join steps.

function mergeTextblockAfter(tr, inside, after) {
  let base = inside.sameDepth(after)
  tr.try(() => {
    let end = after.end(after.depth), cutAt = end + 1, cutDepth = after.depth - 1
    while (cutDepth > base && after.index[cutDepth] + 1 == after.node[cutDepth].childCount) {
      --cutDepth
      ++cutAt
    }
    if (cutDepth > base) tr.split(cutAt, cutDepth - base)
    let types = [], attrs = []
    for (let i = base; i < inside.depth; i++) {
      let node = inside.node[i]
      types.push(node.type)
      attrs.push(node.attrs)
    }
    tr.step("ancestor", after.pos, end, {depth: after.depth - base, types, attrs})
    tr.join(after.pos - (after.depth - base), after.depth - base)
  })
}
<commit_msg>Properly line up end-of-inserted-content finding<commit_after>import {Fragment, Slice} from "../model"

import {Transform} from "./transform"
import {Step, StepResult} from "./step"
import {PosMap, ReplacedRange} from "./map"

// !! **`replace`**
//   : Delete the part of the document between `from` and `to` and
//     optionally replace it with another chunk of content. `pos` must
//     point at the ‘root’ at which the cut starts—a position between
//     and above `from` and `to`.
//
//     When new content is to be inserted, the step's parameter should
//     be an object of shape `{content: `[`Fragment`](#Fragment)`,
//     openLeft: number, openRight: number}`. The step will insert the
//     given content at the root of the cut, and `openLeft` and
//     `openRight` indicate how much of the content on both sides
//     should be consided ‘open’.
//
//     A replace step will try to join open nodes on both sides of the
//     cut. That is, nodes in the original document that are partially
//     cut off by `from` and `to`, and nodes at the sides of the
//     replacement content as specificed by `openLeft` and
//     `openRight`. For example, if `openLeft` is 2, the first node of
//     the replacement content as well as its first child is
//     considered open. Whenever two open nodes with the same
//     [markup](#Node.sameMarkup) end up next to each other, they are
//     joined. Open nodes that aren't joined are [closed](#Node.close)
//     to ensure their content (or lack of it) is valid.

Step.define("replace", {
  apply(doc, step) {
    return StepResult.fromReplace(doc, step.from, step.to, step.param)
  },
  posMap(step) {
    return new PosMap([new ReplacedRange(step.from, step.to - step.from, step.param.size)])
  },
  invert(step, oldDoc) {
    return new Step("replace", step.from, step.from + step.param.size,
                    oldDoc.slice(step.from, step.to))
  },
  paramToJSON(param) { return param.toJSON() },
  paramFromJSON(schema, json) { return Slice.fromJSON(schema, json) }
})

// :: (number, number) → Transform
// Delete the content between the given positions.
Transform.define("delete", function(from, to) {
  this.replace(from, to, Slice.empty)
})

// :: (number, ?number, ?Slice) → Transform
// Replace the part of the document between `from` and `to` with the
// part of the `source` between `start` and `end`.
Transform.define("replace", function(from, to = from, slice = Slice.empty) {
  let rFrom = this.doc.resolve(from), rTo = this.doc.resolve(to)
  let {fitted, distAfter} = fitSliceInto(rFrom, rTo, slice), fSize = fitted.size
  if (from == to && !fSize) return
  this.step("replace", from, to, fitted)

  if (!fSize || !rTo.node[rTo.depth].isTextblock) return
  let after = from + fSize
  let inner = !slice.size ? from : distAfter < 0 ? -1 : after - distAfter, rInner
  if (inner == -1 || inner == after || !(rInner = this.doc.resolve(inner)).node[rInner.depth].isTextblock) return
  mergeTextblockAfter(this, rInner, this.doc.resolve(after))
})

// :: (number, number, union<Fragment, Node, [Node]>) → Transform
// Replace the given range with the given content, which may be a
// fragment, node, or array of nodes.
Transform.define("replaceWith", function(from, to, content) {
  this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
})

// :: (number, union<Fragment, Node, [Node]>) → Transform
// Insert the given content at the given position.
Transform.define("insert", function(pos, content) {
  this.replaceWith(pos, pos, content)
})

// :: (number, string) → Transform
// Insert the given text at `pos`, inheriting the marks of the
// existing content at that position.
Transform.define("insertText", function(pos, text) {
  this.insert(pos, this.doc.type.schema.text(text, this.doc.marksAt(pos)))
})

// :: (number, Node) → Transform
// Insert the given node at `pos`, inheriting the marks of the
// existing content at that position.
Transform.define("insertInline", function(pos, node) {
  this.insert(pos, node.mark(this.doc.marksAt(pos)))
})

let distAfter = 0
function fitSliceInto(from, to, slice) {
  let base = from.sameDepth(to)
  let placed = placeSlice(from, slice), outer = outerPlaced(placed)
  if (outer) base = Math.min(outer.depth, base)

  distAfter = -1e10 // FIXME kludge
  let fragment = closeFragment(from.node[base].type, fillBetween(from, to, base, placed), from, to, base)
  return {fitted: new Slice(fragment, from.depth - base, to.depth - base),
          distAfter: distAfter - (to.depth - base)}
}

function outerPlaced(placed) {
  for (let i = 0; i < placed.length; i++) if (placed[i]) return placed[i]
}

function fillBetween(from, to, depth, placed) {
  let fromNext = from.depth > depth && from.node[depth + 1]
  let toNext = to.depth > depth && to.node[depth + 1]
  let placedHere = placed[depth]

  if (fromNext && toNext && fromNext.type.canContainContent(toNext.type) && !placedHere)
    return Fragment.from(closeNode(fromNext, fillBetween(from, to, depth + 1, placed),
                                   from, to, depth + 1))

  let content = Fragment.empty
  if (placedHere) {
    content = closeLeft(placedHere.content, placedHere.openLeft)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if (fromNext)
    content = content.addToStart(closeNode(fromNext, fillFrom(from, depth + 1, placed),
                                           from, null, depth + 1))
  if (toNext)
    content = closeTo(content, to, depth + 1, placedHere ? placedHere.openRight : 0)
  else if (placedHere)
    content = closeRight(content, placedHere.openRight)
  distAfter++

  return content
}

function fillFrom(from, depth, placed) {
  let placedHere = placed[depth], content = Fragment.empty
  if (placedHere) {
    content = closeRight(placedHere.content, placedHere.openRight)
    if (placedHere.isEnd) distAfter = placedHere.openRight
  }

  distAfter--
  if (from.depth > depth)
    content = content.addToStart(closeNode(from.node[depth + 1], fillFrom(from, depth + 1, placed),
                                           from, null, depth + 1))
  distAfter++

  return content
}

function closeTo(content, to, depth, openDepth) {
  let after = to.node[depth]
  if (openDepth == 0 || !after.type.canContainContent(content.lastChild.type)) {
    let finish = closeNode(after, fillTo(to, depth), null, to, depth)
    distAfter += finish.nodeSize
    return closeRight(content, openDepth).addToEnd(finish)
  }
  let inner = content.lastChild.content
  if (depth < to.depth) inner = closeTo(inner, to, depth + 1, openDepth - 1)
  return content.replace(content.childCount - 1, after.copy(inner))
}

function fillTo(to, depth) {
  if (to.depth == depth) return Fragment.empty
  return Fragment.from(closeNode(to.node[depth + 1], fillTo(to, depth + 1), null, to, depth + 1))
}

// Closing nodes is the process of ensuring that they contain valid
// content, optionally changing the content (that is inside of the
// replace) to make sure.

function closeRight(content, openDepth) {
  if (openDepth == 0) return content
  let last = content.lastChild, closed = closeNode(last, closeRight(last.content, openDepth - 1))
  return closed == last ? content : content.replace(content.childCount - 1, closed)
}

function closeLeft(content, openDepth) {
  if (openDepth == 0) return content
  let first = content.firstChild, closed = closeNode(first, first.content)
  return closed == first ? content : content.replace(0, closed)
}

function closeFragment(type, content, to, from, depth) {
  // FIXME replace this with a more general approach
  if (type.canBeEmpty) return content
  let hasContent = content.size || (to && (to.depth > depth || to.index[depth])) ||
      (from && (from.depth > depth || from.index[depth] < from.node[depth].childCount))
  return hasContent ? content : type.defaultContent()
}

function closeNode(node, content, to, from, depth) {
  return node.copy(closeFragment(node.type, content, to, from, depth))
}

// Algorithm for 'placing' the elements of a slice into a gap:
//
// We consider the content of each node that is open to the left to be
// independently placeable. I.e. in <p("foo"), p("bar")>, when the
// paragraph on the left is open, "foo" can be placed (somewhere on
// the left side of the replacement gap) independently from p("bar").
//
// So placeSlice splits up a slice into a number of sub-slices,
// along with information on where they can be placed on the given
// left-side edge. It works by walking the open side of the slice,
// from the inside out, and trying to find a landing spot for each
// element, by simultaneously scanning over the gap side. When no
// place is found for an open node's content, it is left in that node.
//
// If the outer content can't be placed, a set of wrapper nodes is
// made up for it (by rooting it in the document node type using
// findConnection), and the algorithm continues to iterate over those.
// This is guaranteed to find a fit, since both stacks now start with
// the same node type (doc).

function fragmentSuperKind(fragment) {
  let kind
  for (let i = fragment.childCount - 1; i >= 0; i--) {
    let cur = fragment.child(i).type.kind
    kind = kind ? kind.sharedSuperKind(cur) : cur
  }
  return kind
}

function placeSlice(from, slice) {
  let dFrom = from.depth, unplaced = null, openLeftUnplaced = 0
  let placed = [], parents = null

  for (let dSlice = slice.openLeft;; --dSlice) {
    let curType, curAttrs, curFragment
    if (dSlice >= 0) {
      if (dSlice > 0) { // Inside slice
        ;({type: curType, attrs: curAttrs, content: curFragment} = slice.nodeLeft(dSlice))
      } else if (dSlice == 0) { // Top of slice
        curFragment = slice.content
      }
      if (dSlice < slice.openLeft) curFragment = curFragment.cut(curFragment.firstChild.nodeSize)
    } else { // Outside slice
      curFragment = Fragment.empty
      curType = parents[parents.length + dSlice - 1]
    }
    if (unplaced)
      curFragment = curFragment.addToStart(unplaced)

    if (curFragment.size == 0 && dSlice <= 0) break

    let found = findPlacement(curType, curFragment, from, dFrom)
    if (found > -1) {
      if (curFragment.size > 0)
        placed[found] = {content: curFragment,
                         openLeft: openLeftUnplaced,
                         openRight: dSlice > 0 ? 0 : slice.openRight - dSlice,
                         isEnd: dSlice <= 0,
                         depth: found}
      if (dSlice <= 0) break
      unplaced = null
      openLeftUnplaced = 0
      dFrom = Math.max(0, found - 1)
    } else {
      if (dSlice == 0) {
        parents = from.node[0].type.findConnectionToKind(fragmentSuperKind(curFragment))
        if (!parents) break
        parents.unshift(from.node[0].type)
        curType = parents[parents.length - 1]
      }
      unplaced = curType.create(curAttrs, curFragment)
      openLeftUnplaced++
    }
  }

  return placed
}

function findPlacement(type, fragment, from, start) {
  for (let d = start; d >= 0; d--) {
    let fromType = from.node[d].type
    if (type ? fromType.canContainContent(type) : fromType.canContainFragment(fragment))
      return d
  }
  return -1
}

// When a replace ends in an (open) textblock, and the content that
// ends up before it also ends in an open textblock, the textblock
// after is moved to and connected with the one before it. This
// influences content outside of the replaced range, so it is not done
// as part of the replace step itself, but instead tacked on as a set
// of split/ancestor/join steps.

function mergeTextblockAfter(tr, inside, after) {
  let base = inside.sameDepth(after)
  tr.try(() => {
    let end = after.end(after.depth), cutAt = end + 1, cutDepth = after.depth - 1
    while (cutDepth > base && after.index[cutDepth] + 1 == after.node[cutDepth].childCount) {
      --cutDepth
      ++cutAt
    }
    if (cutDepth > base) tr.split(cutAt, cutDepth - base)
    let types = [], attrs = []
    for (let i = base + 1; i <= inside.depth; i++) {
      let node = inside.node[i]
      types.push(node.type)
      attrs.push(node.attrs)
    }
    tr.step("ancestor", after.pos, end, {depth: after.depth - base, types, attrs})
    tr.join(after.pos - (after.depth - base), inside.depth - base)
  })
}
<|endoftext|>


<commit_before>var AutoSelect;

AutoSelect = (function() {
  function AutoSelect(options) {
    this.options = options != null ? options : {};
    this.$input = $(this.options['input']);
    this.width = this.options['width'] || 240;
    this.placeholder = this.options['placeholder'];
  }

  AutoSelect.prototype.init = function() {
    var scope, url;
    scope = this.$input.attr('data-scope');
    url = this.$input.attr('data-url');
    this.$input.select2({
      placeholder: this.placeholder,
      allowClear: true,
      minimumInputLength: 1,
      width: this.width,
      ajax: {
        url: url,
        quietMillis: 300,
        data: function(term, page) {
          return {
            q: term,
            page_limit: 10,
            page: page,
            scope: scope
          };
        },
        results: function(data, page) {
          var more;
          more = data.length >= 10;
          return {
            results: data,
            more: more
          };
        }
      },
      formatResult: (function(_this) {
        return function(item, container, query, escapeFn) {
          var markup, text;
          markup = [];
          window.Select2.util.markMatch(_this.detailText(item), query.term, markup, escapeFn);
          return text = markup.join('');
        };
      })(this),
      formatSelection: (function(_this) {
        return function(item, container, escapeFn) {
          return _this.detailText(item);
        };
      })(this),
      initSelection: (function(_this) {
        return function(e, callback) {
          var id;
          id = $(e).val();
          if (id !== "") {
            return $.getJSON(url, {
              id: id
            }).done(function(data) {
              callback(data);
              return _this.$input.trigger("autocomplete:ajax:success", data);
            });
          }
        };
      })(this)
    });
    return this.$input.change('select2-selecting', function() {
      return $(this).closest('.filter_form').submit();
    });
  };

  AutoSelect.prototype.detailText = function(item) {
    return _.reject(_.uniq(_.toArray(item)), function(el) {
      return el === '';
    }).join(' - ');
  };

  return AutoSelect;

})();

// ---
// generated by coffee-script 1.9.2
<commit_msg>allow multiple selects<commit_after>var AutoSelect;

AutoSelect = (function() {
  function AutoSelect(options) {
    this.options = options != null ? options : {};
    this.$input = $(this.options['input']);
    this.width = this.options['width'] || 240;
    this.placeholder = this.options['placeholder'];
    this.multiple = this.options['multiple'];
  }

  AutoSelect.prototype.init = function() {
    var scope, url;
    scope = this.$input.attr('data-scope');
    url = this.$input.attr('data-url');
    this.$input.select2({
      placeholder: this.placeholder,
      allowClear: true,
      minimumInputLength: 1,
      width: this.width,
      multiple: this.multiple,
      ajax: {
        url: url,
        quietMillis: 300,
        data: function(term, page) {
          return {
            q: term,
            page_limit: 10,
            page: page,
            scope: scope
          };
        },
        results: function(data, page) {
          var more;
          more = data.length >= 10;
          return {
            results: data,
            more: more
          };
        }
      },
      formatResult: (function(_this) {
        return function(item, container, query, escapeFn) {
          var markup, text;
          markup = [];
          window.Select2.util.markMatch(_this.detailText(item), query.term, markup, escapeFn);
          return text = markup.join('');
        };
      })(this),
      formatSelection: (function(_this) {
        return function(item, container, escapeFn) {
          return _this.detailText(item);
        };
      })(this),
      initSelection: (function(_this) {
        return function(e, callback) {
          var id;
          id = $(e).val();
          if (id !== "") {
            return $.getJSON(url, {
              id: id
            }).done(function(data) {
              callback(data);
              return _this.$input.trigger("autocomplete:ajax:success", data);
            });
          }
        };
      })(this)
    });
    return this.$input.change('select2-selecting', function() {
      return $(this).closest('.filter_form').submit();
    });
  };

  AutoSelect.prototype.detailText = function(item) {
    return _.reject(_.uniq(_.toArray(item)), function(el) {
      return el === '';
    }).join(' - ');
  };

  return AutoSelect;

})();

// ---
// generated by coffee-script 1.9.2
<|endoftext|>