<commit_before>import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/util/initial_state';
import unicodeMapping from './emoji_unicode_mapping_light';
import { assetHost } from 'flavours/glitch/util/config';
import Trie from 'substring-trie';

const trie = new Trie(Object.keys(unicodeMapping));

// Convert to file names from emojis. (For different variation selector emojis)
const emojiFilenames = (emojis) => {
  return emojis.map(v => unicodeMapping[v].filename);
};

// Emoji requiring extra borders depending on theme
const darkEmoji = emojiFilenames(['🎱', '🐜', '⚫', '🖤', '⬛', '◼', '◾', '◼', '✒', '▪', '💣', '🎳', '📷', '📸', '♣', '🕶', '✴', '🔌', '💂♀', '📽', '🍳', '🦍', '💂', '🔪', '🕳', '🕹', '🕋', '🖊', '🖋', '💂♂', '🎤', '🎓', '🎥', '🎼', '♠', '🎩', '🦃', '📼', '📹', '🎮', '🐃', '🏴', '🐞', '🕺', '📱', '📲', '🚲']);
const lightEmoji = emojiFilenames(['👽', '⚾', '🐔', '☁', '💨', '🕊', '👀', '🍥', '👻', '🐐', '❕', '❔', '⛸', '🌩', '🔊', '🔇', '📃', '🌧', '🐏', '🍚', '🍙', '🐓', '🐑', '💀', '☠', '🌨', '🔉', '🔈', '💬', '💭', '🏐', '🏳', '⚪', '⬜', '◽', '◻', '▫']);

const emojiFilename = (filename) => {
  const borderedEmoji = (document.body && document.body.classList.contains('skin-mastodon-light')) ? lightEmoji : darkEmoji;
  return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
};

const emojify = (str, customEmojis = {}) => {
  const tagCharsWithoutEmojis = '<&';
  const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
  let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
  for (;;) {
    let match, i = 0, tag;
    while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || useSystemEmojiFont || !(match = trie.search(str.slice(i))))) {
      i += str.codePointAt(i) < 65536 ? 1 : 2;
    }
    let rend, replacement = '';
    if (i === str.length) {
      break;
    } else if (str[i] === ':') {
      if (!(() => {
        rend = str.indexOf(':', i + 1) + 1;
        if (!rend) return false; // no pair of ':'
        const lt = str.indexOf('<', i + 1);
        if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
        const shortname = str.slice(i, rend);
        // now got a replacee as ':shortname:'
        // if you want additional emoji handler, add statements below which set replacement and return true.
        if (shortname in customEmojis) {
          const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
          replacement = ``;
          return true;
        }
        return false;
      })()) rend = ++i;
    } else if (tag >= 0) { // <, &
      rend = str.indexOf('>;'[tag], i + 1) + 1;
      if (!rend) {
        break;
      }
      if (tag === 0) {
        if (invisible) {
          if (str[i + 1] === '/') { // closing tag
            if (!--invisible) {
              tagChars = tagCharsWithEmojis;
            }
          } else if (str[rend - 2] !== '/') { // opening tag
            invisible++;
          }
        } else {
          if (str.startsWith('', i)) {
            // avoid emojifying on invisible text
            invisible = 1;
            tagChars = tagCharsWithoutEmojis;
          }
        }
      }
      i = rend;
    } else if (!useSystemEmojiFont) { // matched to unicode emoji
      const { filename, shortCode } = unicodeMapping[match];
      const title = shortCode ? `:${shortCode}:` : '';
      replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
      rend = i + match.length;
      // If the matched character was followed by VS15 (for selecting text presentation), skip it.
      if (str.codePointAt(rend) === 65038) {
        rend += 1;
      }
    }
    rtn += str.slice(0, i) + replacement;
    str = str.slice(rend);
  }
  return rtn + str;
};

const emojify_astarte = (str, customEmojis = {}) => [
  {re: /5,?000\s*兆円/g, file: '5000tyoen.svg', attrs: 'style="height: 1.8em !important;"'},
  {re: /熱盛/g, file: 'atumori.svg', attrs: 'style="height: 2.5em !important;"'},
  {re: /バジリスク\s*タイム/g, file: 'basilisktime.svg', attrs: 'style="height: 2.5em !important;"'},
  {re: /欲しい！/g, file: 'hosii.svg', attrs: 'style="height: 1.7em !important;"'},
  {re: /ささやき(たいまー|タイマー)/g, file: 'in_to_the_dark.svg', attrs: 'class="astarte-stamp"'},
  {re: /:ロケット:/g, file: 'rocket.gif', attrs: 'class="astarte-stamp"'},
  {re: /:おはよう:/g, file: 'morning.gif', attrs: 'class="astarte-stamp"'},
  {re: /:かえりたい:/g, file: 'kaeritai.jpeg', attrs: 'class="astarte-stamp"'},
  {re: /:はらへり:/g, file: 'haraheri.png', attrs: 'class="astarte-stamp"'},
  {re: /:かなしい:/g, file: 'kanashi.png', attrs: 'class="astarte-stamp"'},
  {re: /:うれしい:/g, file: 'ureshi.png', attrs: 'class="astarte-stamp"'},
  {re: /:大喜利:/g, file: 'ogiri.jpeg', attrs: 'class="astarte-stamp"'},
  {re: /:ヘディング:/g, file: 'heading.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ふたば_?おはよう:/g, file: 'hutaba.png', attrs: 'class="astarte-stamp"'},
  {re: /:じゃんけん:/g, file: 'janken.gif', attrs: 'class="astarte-stamp"'},
  {re: /:おやすみ:/g, file: 'good_night.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ごはん:/g, file: 'gohan.png', attrs: 'class="astarte-stamp"'},
  {re: /:おそよう:/g, file: 'osoyou.png', attrs: 'class="astarte-stamp"'},
  {re: /:ありがとう:/g, file: 'arigatou.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ルーレット:/g, file: 'roulette.gif', attrs: 'class="astarte-stamp"'},
  {re: /:働きたくない:/g, file: 'notWork.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:起床Chance:/g, file: '750746c7bd3a0edc.png', attrs: 'class="astarte-stamp"'},
  {re: /:入眠Rush:/g, file: '80bff0490a51d517.png', attrs: 'class="astarte-stamp"'},
  {re: /:眠気襲来:/g, file: '45bec8a22a29e958.png', attrs: 'class="astarte-stamp"'},
  {re: /:みぷ:/g, file: 'd000e5d479ce618c.png', attrs: 'class="astarte-stamp"'},
  {re: /:全裸報告:/g, file: '2f18e130e4bfb19b.png', attrs: 'class="astarte-stamp"'},
  {re: /:NFC襲来:/g, file: '6537d027313fd066.png', attrs: 'class="astarte-stamp"'},
  {re: /:椅子転発生:/g, file: '889f2f40ede5d058.png', attrs: 'class="astarte-stamp"'},
  {re: /:ねれない:/g, file: 'nerenine.gif', attrs: 'class="astarte-stamp"'},
  {re: /:配信します:/g, file: 'haishin.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ぴえん:/g, file: 'pien.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:踊る:/g, file: 'odoru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:わびる:/g, file: 'wabiru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:よろこぶ:/g, file: 'yorokobu.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:とどかない:/g, file: 'todokanai.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:こしかける:/g, file: 'kosikakeru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:52000rpm:/g, file: '52000rpm.gif', attrs: 'class="astarte-stamp"'},
  {re: /:花束:/g, file: 'hanataba.png', attrs: 'class="astarte-stamp"'},
  {re: /:花束2:/g, file: 'hanataba2.png', attrs: 'class="astarte-stamp"'},
  {re: /:花束3:/g, file: 'hanataba3.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年:/g, file: '4years.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年1:/g, file: '4years1.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年2:/g, file: '4years2.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年3:/g, file: '4years3.png', attrs: 'class="astarte-stamp"'},
  {re: /:スクワット:/g, file: 'sukuwatto.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:(筋|キン|きん)(トレ|とれ):/g, file: 'kintore.gif', attrs: 'class="astarte-stamp"'},
  {re: /:懸垂:/g, file: 'kensui.png', attrs: 'class="astarte-stamp"'},
  {re: /:ベンチプレス:/g, file: 'bentipuresu.png', attrs: 'class="astarte-stamp"'},
  {re: /:アームカール:/g, file: 'a-muka-ru.png', attrs: 'class="astarte-stamp"'},
  {re: /:プロテイン:/g, file: 'syakasyaka2.gif', attrs: 'class="astarte-stamp"'},
  {re: /:プロテイン1:/g, file: 'syakasyaka.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ポージング:/g, file: 'guttu.png', attrs: 'class="astarte-stamp"'},
].reduce((text, e) => text.replace(e.re, m => `<img alt="${m}" src="https://astarte.global.ssl.fastly.net/emoji/${e.file}" ${e.attrs}/>`), emojify(str, customEmojis));

export default emojify_astarte;

export { unicodeMapping };

export const buildCustomEmojis = (customEmojis) => {
  const emojis = [];

  customEmojis.forEach(emoji => {
    const shortcode = emoji.get('shortcode');
    const url       = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
    const name      = shortcode.replace(':', '');

    emojis.push({
      id: name,
      name,
      short_names: [name],
      text: '',
      emoticons: [],
      keywords: [name],
      imageUrl: url,
      custom: true,
      customCategory: emoji.get('category'),
    });
  });

  return emojis;
};

export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
<commit_msg>add new stamps<commit_after>import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/util/initial_state';
import unicodeMapping from './emoji_unicode_mapping_light';
import { assetHost } from 'flavours/glitch/util/config';
import Trie from 'substring-trie';

const trie = new Trie(Object.keys(unicodeMapping));

// Convert to file names from emojis. (For different variation selector emojis)
const emojiFilenames = (emojis) => {
  return emojis.map(v => unicodeMapping[v].filename);
};

// Emoji requiring extra borders depending on theme
const darkEmoji = emojiFilenames(['🎱', '🐜', '⚫', '🖤', '⬛', '◼', '◾', '◼', '✒', '▪', '💣', '🎳', '📷', '📸', '♣', '🕶', '✴', '🔌', '💂♀', '📽', '🍳', '🦍', '💂', '🔪', '🕳', '🕹', '🕋', '🖊', '🖋', '💂♂', '🎤', '🎓', '🎥', '🎼', '♠', '🎩', '🦃', '📼', '📹', '🎮', '🐃', '🏴', '🐞', '🕺', '📱', '📲', '🚲']);
const lightEmoji = emojiFilenames(['👽', '⚾', '🐔', '☁', '💨', '🕊', '👀', '🍥', '👻', '🐐', '❕', '❔', '⛸', '🌩', '🔊', '🔇', '📃', '🌧', '🐏', '🍚', '🍙', '🐓', '🐑', '💀', '☠', '🌨', '🔉', '🔈', '💬', '💭', '🏐', '🏳', '⚪', '⬜', '◽', '◻', '▫']);

const emojiFilename = (filename) => {
  const borderedEmoji = (document.body && document.body.classList.contains('skin-mastodon-light')) ? lightEmoji : darkEmoji;
  return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
};

const emojify = (str, customEmojis = {}) => {
  const tagCharsWithoutEmojis = '<&';
  const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
  let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
  for (;;) {
    let match, i = 0, tag;
    while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || useSystemEmojiFont || !(match = trie.search(str.slice(i))))) {
      i += str.codePointAt(i) < 65536 ? 1 : 2;
    }
    let rend, replacement = '';
    if (i === str.length) {
      break;
    } else if (str[i] === ':') {
      if (!(() => {
        rend = str.indexOf(':', i + 1) + 1;
        if (!rend) return false; // no pair of ':'
        const lt = str.indexOf('<', i + 1);
        if (!(lt === -1 || lt >= rend)) return false; // tag appeared before closing ':'
        const shortname = str.slice(i, rend);
        // now got a replacee as ':shortname:'
        // if you want additional emoji handler, add statements below which set replacement and return true.
        if (shortname in customEmojis) {
          const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
          replacement = ``;
          return true;
        }
        return false;
      })()) rend = ++i;
    } else if (tag >= 0) { // <, &
      rend = str.indexOf('>;'[tag], i + 1) + 1;
      if (!rend) {
        break;
      }
      if (tag === 0) {
        if (invisible) {
          if (str[i + 1] === '/') { // closing tag
            if (!--invisible) {
              tagChars = tagCharsWithEmojis;
            }
          } else if (str[rend - 2] !== '/') { // opening tag
            invisible++;
          }
        } else {
          if (str.startsWith('', i)) {
            // avoid emojifying on invisible text
            invisible = 1;
            tagChars = tagCharsWithoutEmojis;
          }
        }
      }
      i = rend;
    } else if (!useSystemEmojiFont) { // matched to unicode emoji
      const { filename, shortCode } = unicodeMapping[match];
      const title = shortCode ? `:${shortCode}:` : '';
      replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
      rend = i + match.length;
      // If the matched character was followed by VS15 (for selecting text presentation), skip it.
      if (str.codePointAt(rend) === 65038) {
        rend += 1;
      }
    }
    rtn += str.slice(0, i) + replacement;
    str = str.slice(rend);
  }
  return rtn + str;
};

const emojify_astarte = (str, customEmojis = {}) => [
  {re: /5,?000\s*兆円/g, file: '5000tyoen.svg', attrs: 'style="height: 1.8em !important;"'},
  {re: /熱盛/g, file: 'atumori.svg', attrs: 'style="height: 2.5em !important;"'},
  {re: /バジリスク\s*タイム/g, file: 'basilisktime.svg', attrs: 'style="height: 2.5em !important;"'},
  {re: /欲しい！/g, file: 'hosii.svg', attrs: 'style="height: 1.7em !important;"'},
  {re: /ささやき(たいまー|タイマー)/g, file: 'in_to_the_dark.svg', attrs: 'class="astarte-stamp"'},
  {re: /:ロケット:/g, file: 'rocket.gif', attrs: 'class="astarte-stamp"'},
  {re: /:おはよう:/g, file: 'morning.gif', attrs: 'class="astarte-stamp"'},
  {re: /:かえりたい:/g, file: 'kaeritai.jpeg', attrs: 'class="astarte-stamp"'},
  {re: /:はらへり:/g, file: 'haraheri.png', attrs: 'class="astarte-stamp"'},
  {re: /:かなしい:/g, file: 'kanashi.png', attrs: 'class="astarte-stamp"'},
  {re: /:うれしい:/g, file: 'ureshi.png', attrs: 'class="astarte-stamp"'},
  {re: /:大喜利:/g, file: 'ogiri.jpeg', attrs: 'class="astarte-stamp"'},
  {re: /:ヘディング:/g, file: 'heading.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ふたば_?おはよう:/g, file: 'hutaba.png', attrs: 'class="astarte-stamp"'},
  {re: /:じゃんけん:/g, file: 'janken.gif', attrs: 'class="astarte-stamp"'},
  {re: /:おやすみ:/g, file: 'good_night.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ごはん:/g, file: 'gohan.png', attrs: 'class="astarte-stamp"'},
  {re: /:おそよう:/g, file: 'osoyou.png', attrs: 'class="astarte-stamp"'},
  {re: /:ありがとう:/g, file: 'arigatou.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ルーレット:/g, file: 'roulette.gif', attrs: 'class="astarte-stamp"'},
  {re: /:働きたくない:/g, file: 'notWork.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:起床Chance:/g, file: '750746c7bd3a0edc.png', attrs: 'class="astarte-stamp"'},
  {re: /:入眠Rush:/g, file: '80bff0490a51d517.png', attrs: 'class="astarte-stamp"'},
  {re: /:眠気襲来:/g, file: '45bec8a22a29e958.png', attrs: 'class="astarte-stamp"'},
  {re: /:みぷ:/g, file: 'd000e5d479ce618c.png', attrs: 'class="astarte-stamp"'},
  {re: /:全裸報告:/g, file: '2f18e130e4bfb19b.png', attrs: 'class="astarte-stamp"'},
  {re: /:NFC襲来:/g, file: '6537d027313fd066.png', attrs: 'class="astarte-stamp"'},
  {re: /:椅子転発生:/g, file: '889f2f40ede5d058.png', attrs: 'class="astarte-stamp"'},
  {re: /:ねれない:/g, file: 'nerenine.gif', attrs: 'class="astarte-stamp"'},
  {re: /:配信します:/g, file: 'haishin.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ぴえん:/g, file: 'pien.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:踊る:/g, file: 'odoru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:わびる:/g, file: 'wabiru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:よろこぶ:/g, file: 'yorokobu.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:とどかない:/g, file: 'todokanai.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:こしかける:/g, file: 'kosikakeru.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:52000rpm:/g, file: '52000rpm.gif', attrs: 'class="astarte-stamp"'},
  {re: /:花束:/g, file: 'hanataba.png', attrs: 'class="astarte-stamp"'},
  {re: /:花束2:/g, file: 'hanataba2.png', attrs: 'class="astarte-stamp"'},
  {re: /:花束3:/g, file: 'hanataba3.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年:/g, file: '4years.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年1:/g, file: '4years1.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年2:/g, file: '4years2.png', attrs: 'class="astarte-stamp"'},
  {re: /:4周年3:/g, file: '4years3.png', attrs: 'class="astarte-stamp"'},
  {re: /:スクワット:/g, file: 'sukuwatto.jpg', attrs: 'class="astarte-stamp"'},
  {re: /:(筋|キン|きん)(トレ|とれ):/g, file: 'kintore.gif', attrs: 'class="astarte-stamp"'},
  {re: /:懸垂:/g, file: 'kensui.png', attrs: 'class="astarte-stamp"'},
  {re: /:ベンチプレス:/g, file: 'bentipuresu.png', attrs: 'class="astarte-stamp"'},
  {re: /:アームカール:/g, file: 'a-muka-ru.png', attrs: 'class="astarte-stamp"'},
  {re: /:プロテイン:/g, file: 'syakasyaka2.gif', attrs: 'class="astarte-stamp"'},
  {re: /:プロテイン1:/g, file: 'syakasyaka.gif', attrs: 'class="astarte-stamp"'},
  {re: /:ポージング:/g, file: 'guttu.png', attrs: 'class="astarte-stamp"'},
  {re: /:(よし|ヨシ)(!|！):/g, file: 'guttu.png', attrs: 'class="astarte-stamp"'},
].reduce((text, e) => text.replace(e.re, m => `<img alt="${m}" src="https://astarte.global.ssl.fastly.net/emoji/${e.file}" ${e.attrs}/>`), emojify(str, customEmojis));

export default emojify_astarte;

export { unicodeMapping };

export const buildCustomEmojis = (customEmojis) => {
  const emojis = [];

  customEmojis.forEach(emoji => {
    const shortcode = emoji.get('shortcode');
    const url       = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
    const name      = shortcode.replace(':', '');

    emojis.push({
      id: name,
      name,
      short_names: [name],
      text: '',
      emoticons: [],
      keywords: [name],
      imageUrl: url,
      custom: true,
      customCategory: emoji.get('category'),
    });
  });

  return emojis;
};

export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
<|endoftext|>


<commit_before>import TrackballController from '../../core/3d/controller/TrackballController.js';
import Matrix4 from '../../core/math/Matrix4.js';

import { Matrix4 as THREEMatrix4 } from '../../../three/src/math/Matrix4.js';
import { Object3D } from '../../../three/src/core/Object3D.js';

export default class THREETrackballController extends TrackballController {
  constructor({
    object3D = new Object3D(),
    ...options
  }) {
    object3D.updateMatrix();
    super({
      matrix: new Matrix4(object3D.matrix.elements),
      ...options,
    });
    this._matrix4 = new THREEMatrix4();
    this.object3D = object3D;

    const disabled = this.disabled;
    this.disabled = false;
    this.update();
    this.disabled = disabled;
  }

  update() {
    if (!this.object3D) {
      return;
    }
    this.matrix.x = this.object3D.position.x;
    this.matrix.y = this.object3D.position.y;
    this.matrix.z = this.object3D.position.z;
    super.update();
    this._matrix4.fromArray(this.matrix);
    this.object3D.matrix.identity();
    const matrixAutoUpdate = this.object3D.matrixAutoUpdate;
    this.object3D.matrixAutoUpdate = false;
    this.object3D.applyMatrix4(this._matrix4);
    this.object3D.matrixAutoUpdate = matrixAutoUpdate;
  }
}
<commit_msg>delete THREETrackballController<commit_after><|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/cs-version-control';
import { task } from 'ember-concurrency';
import { transitionTo } from '../private-api';

export default Ember.Component.extend({
  layout,
  tagName: '',
  opened: true,
  resourceMetadata: Ember.inject.service(),
  store: Ember.inject.service(),
  cardstackRouting: Ember.inject.service(),

  modelMeta: Ember.computed('model', function() {
    return this.get('resourceMetadata').read(this.get('model'));
  }),

  onMaster: Ember.computed('modelMeta.branch', function() {
    return this.get('modelMeta').branch === this.get('cardstackRouting.defaultBranch');
  }),

  upstreamMeta: Ember.computed('upstreamModel', function() {
    let upstream = this.get('upstreamModel');
    if (upstream) {
      return this.get('resourceMetadata').read(upstream);
    }
  }),

  fetchUpstreamModel: task(function * () {
    this.set('upstreamModel', null);
    if (this.get('onMaster')) {
      // Nothing to do, we're already on the default branch, so there
      // is no model more upstream than ours.
      return;
    }
    let model = this.get('model');
    let type = model.constructor.modelName;
    let id = model.get('id');
    if (id == null) {
      // Nothing to do, we don't have an id yet. It's vanishingly
      // unlikely that we're about to collide with something on
      // upstream anyway.
      return;
    }
    try {
      let upstreamModel = yield this.get('store').findRecord('cardstack-generic', `master/${type}/${id}`);
      this.set('upstreamModel', upstreamModel);
    } catch (err) {
      if (err.isAdapterError && err.errors && err.errors.length > 0 && err.errors[0].code === 404) {
        // There's no upstream model, that's OK.
        return;
      }
      throw err;
    }
  }).observes('modelMeta.branch', 'model.id').on('init'),

  // this describes the state of our model relative to its branch. So
  // "saved" here means it has been saved to its branch, etc.
  modificationState: Ember.computed('model.isNew', 'model.hasDirtyFields', function() {
    if (this.get('model.isNew')) {
      return "new";
    } else  if (this.get('model.hasDirtyFields')) {
      return "changed";
    } else {
      return "saved";
    }
  }),

  // this describes the state of our model relative to its value on
  // the default branch.
  upstreamState: Ember.computed('upstreamMeta.hash', 'modelMeta.hash', 'onMaster', 'fetchUpstreamModel.isRunning', function() {
    if (this.get('onMaster')) {
      return 'self';
    }

    if (this.get('fetchUpstreamModel.isRunning')) {
      return 'pending';
    }

    let upstreamMeta = this.get('upstreamMeta');
    if (!upstreamMeta) {
      return 'created';
    }

    let meta = this.get('modelMeta');
    if (meta.hash === upstreamMeta.hash) {
      return 'same';
    } else {
      return 'different';
    }

  }),

  titleClass: Ember.computed('upstreamState', function() {
    let state = this.get('upstreamState');
    if (state === 'created' || state === 'different') {
      return 'cs-version-control-preview';
    }
  }),

  title: Ember.computed('modificationState', 'upstreamState', function() {
    switch(this.get('modificationState')) {
    case 'new':
      return 'Drafted'
    case 'changed':
      return 'Changed';
    case 'saved':
      switch(this.get('upstreamState')) {
      case 'pending':
        return '...';
      case 'self':
        return 'Live';
      case 'created':
      case 'different':
      case 'same':
        return 'Preview';
      }
    }
  }),

  anythingPending: Ember.computed('model.isNew', 'model.hasDirtyFields', function() {
    return this.get('model.isNew') || this.get('model.hasDirtyFields');
  }),

  update: task(function * () {
    yield this.get('model').save();
  }).keepLatest(),

  delete: task(function * () {
    let model = this.get('model');
    if (model.get('isNew')) {
      transitionTo(Ember.getOwner(this), 'cardstack.new-content', [placeholder]);
      return;
    }
    let branch = this.get('modelMeta.branch');
    let placeholder = this.get('store').createRecord('cardstack-placeholder', { type: model.get('type'), slug: model.get('slug') });
    this.get('resourceMetadata').write(placeholder, { branch });
    let { name, queryParams } = this.get('cardstackRouting').routeFor(model.get('type'), model.get('slug'), branch);
    yield this.get('model').destroyRecord();
    transitionTo(Ember.getOwner(this), name, [placeholder], queryParams);
  }),

  actions: {
    open() {
      this.set('opened', true);
    },
    close() {
      this.set('opened', false);
    }
  }
});
<commit_msg>fix deletion<commit_after>import Ember from 'ember';
import layout from '../templates/components/cs-version-control';
import { task } from 'ember-concurrency';
import { transitionTo } from '../private-api';

export default Ember.Component.extend({
  layout,
  tagName: '',
  opened: true,
  resourceMetadata: Ember.inject.service(),
  store: Ember.inject.service(),
  cardstackRouting: Ember.inject.service(),

  modelMeta: Ember.computed('model', function() {
    return this.get('resourceMetadata').read(this.get('model'));
  }),

  onMaster: Ember.computed('modelMeta.branch', function() {
    return this.get('modelMeta').branch === this.get('cardstackRouting.defaultBranch');
  }),

  upstreamMeta: Ember.computed('upstreamModel', function() {
    let upstream = this.get('upstreamModel');
    if (upstream) {
      return this.get('resourceMetadata').read(upstream);
    }
  }),

  fetchUpstreamModel: task(function * () {
    this.set('upstreamModel', null);
    if (this.get('onMaster')) {
      // Nothing to do, we're already on the default branch, so there
      // is no model more upstream than ours.
      return;
    }
    let model = this.get('model');
    let type = model.constructor.modelName;
    let id = model.get('id');
    if (id == null) {
      // Nothing to do, we don't have an id yet. It's vanishingly
      // unlikely that we're about to collide with something on
      // upstream anyway.
      return;
    }
    try {
      let upstreamModel = yield this.get('store').findRecord('cardstack-generic', `master/${type}/${id}`);
      this.set('upstreamModel', upstreamModel);
    } catch (err) {
      if (err.isAdapterError && err.errors && err.errors.length > 0 && err.errors[0].code === 404) {
        // There's no upstream model, that's OK.
        return;
      }
      throw err;
    }
  }).observes('modelMeta.branch', 'model.id').on('init'),

  // this describes the state of our model relative to its branch. So
  // "saved" here means it has been saved to its branch, etc.
  modificationState: Ember.computed('model.isNew', 'model.hasDirtyFields', function() {
    if (this.get('model.isNew')) {
      return "new";
    } else  if (this.get('model.hasDirtyFields')) {
      return "changed";
    } else {
      return "saved";
    }
  }),

  // this describes the state of our model relative to its value on
  // the default branch.
  upstreamState: Ember.computed('upstreamMeta.hash', 'modelMeta.hash', 'onMaster', 'fetchUpstreamModel.isRunning', function() {
    if (this.get('onMaster')) {
      return 'self';
    }

    if (this.get('fetchUpstreamModel.isRunning')) {
      return 'pending';
    }

    let upstreamMeta = this.get('upstreamMeta');
    if (!upstreamMeta) {
      return 'created';
    }

    let meta = this.get('modelMeta');
    if (meta.hash === upstreamMeta.hash) {
      return 'same';
    } else {
      return 'different';
    }

  }),

  titleClass: Ember.computed('upstreamState', function() {
    let state = this.get('upstreamState');
    if (state === 'created' || state === 'different') {
      return 'cs-version-control-preview';
    }
  }),

  title: Ember.computed('modificationState', 'upstreamState', function() {
    switch(this.get('modificationState')) {
    case 'new':
      return 'Drafted'
    case 'changed':
      return 'Changed';
    case 'saved':
      switch(this.get('upstreamState')) {
      case 'pending':
        return '...';
      case 'self':
        return 'Live';
      case 'created':
      case 'different':
      case 'same':
        return 'Preview';
      }
    }
  }),

  anythingPending: Ember.computed('model.isNew', 'model.hasDirtyFields', function() {
    return this.get('model.isNew') || this.get('model.hasDirtyFields');
  }),

  update: task(function * () {
    yield this.get('model').save();
  }).keepLatest(),

  delete: task(function * () {
    let model = this.get('model');
    let placeholder = this.get('store').createRecord('cardstack-placeholder', { type: model.get('type'), slug: model.get('slug') });
    if (model.get('isNew')) {
      transitionTo(Ember.getOwner(this), 'cardstack.new-content', [placeholder]);
      return;
    }
    let branch = this.get('modelMeta.branch');
    this.get('resourceMetadata').write(placeholder, { branch });
    let { name, queryParams } = this.get('cardstackRouting').routeFor(model.get('type'), model.get('slug'), branch);
    yield this.get('model').destroyRecord();
    transitionTo(Ember.getOwner(this), name, [placeholder], queryParams);
  }),

  actions: {
    open() {
      this.set('opened', true);
    },
    close() {
      this.set('opened', false);
    }
  }
});
<|endoftext|>


<commit_before>import { registerFragment } from 'meteor/vulcan:core';

registerFragment(`
  fragment PostsBase on Post {
    # Core fields
    _id
    title
    url
    slug
    postedAt
    createdAt
    modifiedAt
    sticky
    metaSticky
    status
    frontpageDate
    meta
    draft
    deletedDraft
    viewCount
    clickCount

    commentCount
    voteCount
    baseScore
    unlisted
    score
    feedId
    feedLink
    lastVisitedAt
    isRead
    lastCommentedAt
    canonicalCollectionSlug
    curatedDate
    commentsLocked

    # questions
    question
    hiddenRelatedQuestion

    # vulcan:users
    userId

    # Local Event data
    groupId
    location
    googleLocation
    mongoLocation
    startTime
    endTime
    localStartTime
    localEndTime
    facebookLink
    website
    contactInfo
    isEvent
    types

    # Review data
    reviewedByUserId
    suggestForCuratedUserIds
    suggestForCuratedUsernames
    reviewForCuratedUserId
    authorIsUnreviewed

    # Alignment Forum
    af
    afDate
    suggestForAlignmentUserIds
    reviewForAlignmentUserId
    afBaseScore
    afCommentCount
    afLastCommentedAt
    afSticky

    isFuture
    hideAuthor
    moderationStyle
    hideCommentKarma
    submitToFrontpage
    shortform
    canonicalSource

    shareWithUsers

    nominationCount2018
    reviewCount2018

    group {
      _id
      name
    }
  }
`);

registerFragment(`
  fragment PostsAuthors on Post {
    user {
      ...UsersMinimumInfo

      # Author moderation info
      moderationStyle
      bannedUserIds
      moderatorAssistance
    }
    coauthors {
      ...UsersMinimumInfo
    }
  }
`);

registerFragment(`
  fragment PostsDetails on Post {
    ...PostsBase
    ...PostsAuthors

    # Sort settings
    commentSortOrder

    # Sequence navigation
    collectionTitle
    canonicalPrevPostSlug
    canonicalNextPostSlug
    canonicalCollectionSlug
    canonicalSequenceId
    canonicalBookId
    canonicalSequence {
      title
    }
    canonicalBook {
      title
    }
    canonicalCollection {
      title
    }

    # Moderation stuff
    showModerationGuidelines
    moderationGuidelines {
      version
      html
    }
    customHighlight {
      version
      html
    }
    bannedUserIds
    hideAuthor
    moderationStyle

    # Voting
    voteCount
    currentUserVotes{
      ...VoteFragment
    }
    feed {
      ...RSSFeedMinimumInfo
    }
    sourcePostRelations {
      _id
      sourcePostId
      sourcePost {
        ...PostsBase
        ...PostsAuthors
      }
      order
    }
    targetPostRelations {
      _id
      sourcePostId
      targetPostId
      targetPost {
        ...PostsBase
        ...PostsAuthors
      }
      order
    }
  }
`);

// Same as PostsPage, with added just optional arguments to the content field
registerFragment(`
  fragment PostsRevision on Post {
    ...PostsDetails

    # Content & Revisions
    version
    contents(version: $version) {
      ...RevisionDisplay
    }
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsRevisionEdit on Post {
    ...PostsDetails

    # Content & Revisions
    version
    contents(version: $version) {
      ...RevisionEdit
    }
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsWithNavigationAndRevision on Post {
    ...PostsRevision
    ...PostSequenceNavigation

    tableOfContentsRevision(version: $version)
  }
`)

registerFragment(`
  fragment PostsWithNavigation on Post {
    ...PostsPage
    ...PostSequenceNavigation

    tableOfContents
  }
`)

// This is a union of the fields needed by PostsTopNavigation and BottomNavigation.
registerFragment(`
  fragment PostSequenceNavigation on Post {
    # Prev/next sequence navigation
    sequence(sequenceId: $sequenceId) {
      _id
      title
    }
    prevPost(sequenceId: $sequenceId) {
      _id
      title
      slug
      commentCount
      baseScore
      sequence(sequenceId: $sequenceId) {
        _id
      }
    }
    nextPost(sequenceId: $sequenceId) {
      _id
      title
      slug
      commentCount
      baseScore
      sequence(sequenceId: $sequenceId) {
        _id
      }
    }
  }
`)

registerFragment(`
  fragment PostsPage on Post {
    ...PostsDetails
    version
    contents {
      ...RevisionDisplay
    }
  }
`)

registerFragment(`
  fragment PostsEdit on Post {
    ...PostsPage
    moderationGuidelines {
      ...RevisionEdit
    }
    contents {
      ...RevisionEdit
    }
    customHighlight {
      ...RevisionEdit
    }
  }
`);

registerFragment(`
  fragment EditModerationGuidelines on Post {
    moderationGuidelines {
      ...RevisionEdit
    },
    moderationStyle
  }
`)

registerFragment(`
  fragment PostsRevisionsList on Post {
    _id
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsList on Post {
    ...PostsBase
    ...PostsAuthors
    originalPostRelationSourceId
    contents {
      htmlHighlight
      wordCount
    }
    moderationGuidelines {
      ...RevisionDisplay
    }
    customHighlight {
      ...RevisionDisplay
    }
  }
`);

registerFragment(`
  fragment PostsRecentDiscussion on Post {
    ...PostsList
    recentComments(commentsLimit: $commentsLimit, maxAgeHours: $maxAgeHours, af: $af) {
      ...CommentsList
    }
  }
`);

registerFragment(`
  fragment UsersBannedFromPostsModerationLog on Post {
    user {
      ...UsersMinimumInfo
    }
    title
    slug
    _id
    bannedUserIds
  }
`)
<commit_msg>Fix an issue where after editing a post with a ToC, the edits don't preview until refresh<commit_after>import { registerFragment } from 'meteor/vulcan:core';

registerFragment(`
  fragment PostsBase on Post {
    # Core fields
    _id
    title
    url
    slug
    postedAt
    createdAt
    modifiedAt
    sticky
    metaSticky
    status
    frontpageDate
    meta
    draft
    deletedDraft
    viewCount
    clickCount

    commentCount
    voteCount
    baseScore
    unlisted
    score
    feedId
    feedLink
    lastVisitedAt
    isRead
    lastCommentedAt
    canonicalCollectionSlug
    curatedDate
    commentsLocked

    # questions
    question
    hiddenRelatedQuestion

    # vulcan:users
    userId

    # Local Event data
    groupId
    location
    googleLocation
    mongoLocation
    startTime
    endTime
    localStartTime
    localEndTime
    facebookLink
    website
    contactInfo
    isEvent
    types

    # Review data
    reviewedByUserId
    suggestForCuratedUserIds
    suggestForCuratedUsernames
    reviewForCuratedUserId
    authorIsUnreviewed

    # Alignment Forum
    af
    afDate
    suggestForAlignmentUserIds
    reviewForAlignmentUserId
    afBaseScore
    afCommentCount
    afLastCommentedAt
    afSticky

    isFuture
    hideAuthor
    moderationStyle
    hideCommentKarma
    submitToFrontpage
    shortform
    canonicalSource

    shareWithUsers

    nominationCount2018
    reviewCount2018

    group {
      _id
      name
    }
  }
`);

registerFragment(`
  fragment PostsAuthors on Post {
    user {
      ...UsersMinimumInfo

      # Author moderation info
      moderationStyle
      bannedUserIds
      moderatorAssistance
    }
    coauthors {
      ...UsersMinimumInfo
    }
  }
`);

registerFragment(`
  fragment PostsDetails on Post {
    ...PostsBase
    ...PostsAuthors

    # Sort settings
    commentSortOrder

    # Sequence navigation
    collectionTitle
    canonicalPrevPostSlug
    canonicalNextPostSlug
    canonicalCollectionSlug
    canonicalSequenceId
    canonicalBookId
    canonicalSequence {
      title
    }
    canonicalBook {
      title
    }
    canonicalCollection {
      title
    }

    # Moderation stuff
    showModerationGuidelines
    moderationGuidelines {
      version
      html
    }
    customHighlight {
      version
      html
    }
    bannedUserIds
    hideAuthor
    moderationStyle

    # Voting
    voteCount
    currentUserVotes{
      ...VoteFragment
    }
    feed {
      ...RSSFeedMinimumInfo
    }
    sourcePostRelations {
      _id
      sourcePostId
      sourcePost {
        ...PostsBase
        ...PostsAuthors
      }
      order
    }
    targetPostRelations {
      _id
      sourcePostId
      targetPostId
      targetPost {
        ...PostsBase
        ...PostsAuthors
      }
      order
    }
  }
`);

// Same as PostsPage, with added just optional arguments to the content field
registerFragment(`
  fragment PostsRevision on Post {
    ...PostsDetails

    # Content & Revisions
    version
    contents(version: $version) {
      ...RevisionDisplay
    }
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsRevisionEdit on Post {
    ...PostsDetails

    # Content & Revisions
    version
    contents(version: $version) {
      ...RevisionEdit
    }
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsWithNavigationAndRevision on Post {
    ...PostsRevision
    ...PostSequenceNavigation

    tableOfContentsRevision(version: $version)
  }
`)

registerFragment(`
  fragment PostsWithNavigation on Post {
    ...PostsPage
    ...PostSequenceNavigation

    tableOfContents
  }
`)

// This is a union of the fields needed by PostsTopNavigation and BottomNavigation.
registerFragment(`
  fragment PostSequenceNavigation on Post {
    # Prev/next sequence navigation
    sequence(sequenceId: $sequenceId) {
      _id
      title
    }
    prevPost(sequenceId: $sequenceId) {
      _id
      title
      slug
      commentCount
      baseScore
      sequence(sequenceId: $sequenceId) {
        _id
      }
    }
    nextPost(sequenceId: $sequenceId) {
      _id
      title
      slug
      commentCount
      baseScore
      sequence(sequenceId: $sequenceId) {
        _id
      }
    }
  }
`)

registerFragment(`
  fragment PostsPage on Post {
    ...PostsDetails
    version
    contents {
      ...RevisionDisplay
    }
  }
`)

registerFragment(`
  fragment PostsEdit on Post {
    ...PostsPage
    moderationGuidelines {
      ...RevisionEdit
    }
    contents {
      ...RevisionEdit
    }
    customHighlight {
      ...RevisionEdit
    }
    tableOfContents
  }
`);

registerFragment(`
  fragment EditModerationGuidelines on Post {
    moderationGuidelines {
      ...RevisionEdit
    },
    moderationStyle
  }
`)

registerFragment(`
  fragment PostsRevisionsList on Post {
    _id
    revisions {
      version
      editedAt
    }
  }
`)

registerFragment(`
  fragment PostsList on Post {
    ...PostsBase
    ...PostsAuthors
    originalPostRelationSourceId
    contents {
      htmlHighlight
      wordCount
    }
    moderationGuidelines {
      ...RevisionDisplay
    }
    customHighlight {
      ...RevisionDisplay
    }
  }
`);

registerFragment(`
  fragment PostsRecentDiscussion on Post {
    ...PostsList
    recentComments(commentsLimit: $commentsLimit, maxAgeHours: $maxAgeHours, af: $af) {
      ...CommentsList
    }
  }
`);

registerFragment(`
  fragment UsersBannedFromPostsModerationLog on Post {
    user {
      ...UsersMinimumInfo
    }
    title
    slug
    _id
    bannedUserIds
  }
`)
<|endoftext|>


<commit_before>import { registerPlugin } from "@finos/perspective-viewer/dist/esm/utils.js";
import L from "leaflet"
import { feature } from "topojson-client"
import "../../../assets/stylesheets/comp-perspective-viewer-map.css"

// default geoJSON column name
const geomColumn = "geometry"
// default HTML id
const idMap = "map"

function createMapNode(element, div) {
  if (document.getElementById(idMap)) {
    document.getElementById(idMap).remove()
  }
  // Attach the container div to the DOM
  const mapElement = document.createElement("div");
  mapElement.id = idMap
  div.innerHTML = ""
  div.appendChild(document.createElement("slot"))
  element.appendChild(mapElement);

  // Initialize Leaflet
  const map = L.map(idMap);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  // Add Topojson handlers
  L.TopoJSON = L.GeoJSON.extend({
    addData: function (jsonData) {
      if (jsonData.type === 'Topology') {
        for (let key in jsonData.objects) {
          L.GeoJSON.prototype.addData.call(this, feature(jsonData, jsonData.objects[key]));
        }
      } else {
        L.GeoJSON.prototype.addData.call(this, jsonData);
      }
    },
  });
  L.topoJSON = function (data, options) {
    return new L.TopoJSON(data, options);
  };

  return map;
}

function createLegend({ grades = [], getColor = d => d, fmt = d => d.toLocaleString() }) {
  const legend = L.control({ position: 'bottomleft' });

  legend.onAdd = function () {
      const div = L.DomUtil.create('div', 'perspective-map-legend')

      // loop through our density intervals and generate a label with a colored square for each interval
      for (var i = 0; i < grades.length; i++) {
          div.innerHTML +=
              ' ' +
              fmt(grades[i]) + (grades[i + 1] ? '–' + fmt(grades[i + 1]) + '
' : '+');
      }

      return div;
  };

  return legend;
}

function createTooltip() {
  const info = L.control();

  info.onAdd = function () {
      this._div = L.DomUtil.create('div', 'perspective-map-tooltip');
      this.update();
      return this._div;
  };

  // method that we will use to update the control based on feature properties passed
  info.update = function (props = {}) {
      this._div.innerHTML = Object.entries(props).map(([key, value]) => `<div><b>${key}</b>: ${value}</div>`).join("");
  };

  return info;
}

export class MapPlugin {
  // static name = "Map"

  static async create(div, view) {
    try {
      const columns = JSON.parse(this.getAttribute("columns"))

      // Enforces to have a geometry column
      if (!columns.includes(geomColumn)) {
        columns.push(geomColumn)
        this.setAttribute("columns", JSON.stringify(columns))
      }

      const map = createMapNode(this, div);

      // fetch the current displayed data
      const data = await view.to_json() || []
      if (data.some(({ [geomColumn]: geometry }) => !!geometry)) {

        // find first numeric field
        const [numericField] = (Object.entries(data[0]) || []).find(([, value]) => Number.isFinite(value))
        const mappedData = data.map(d => d[numericField])
        const isInteger = mappedData.every(x => Number.isInteger(x))

        // get range array
        const [min, max] = [Math.min(...mappedData), Math.max(...mappedData)]
        // max. categories
        const maxCategories = 5
        // substract one to maxCategories in order to keep the max. value INSIDE the range
        const step = (max - min) / (maxCategories - 1)
        const grades = Array.from({ length: maxCategories - 1 }, (_, i) => isInteger ? Math.floor(min + (i * step)) : min + (i * step))
        grades.push(max)

        const getColor = (value) => {
          // if don't substract 1, you'll never get the first index
          const ix = grades.findIndex(x => value <= x)
          // categories begins as of 1
          return ix >= 0 ? `var(--perspective-map-category-${ix + 1})` : ''
        }

        const style = ({ properties = {} }) => ({
          fillColor: getColor(properties[numericField]),
          fillOpacity: 1,
          color: getColor(properties[numericField]),
          opacity: 1,
        })

        // creates features ONLY if they're plane strings
        const features =
          data
            .map(({ [geomColumn]: geometry = "{}", ...properties }) => {
              // parse all the geoms as topojson, even they're not
              const { features: [ feature ] } = L.topoJSON(JSON.parse(geometry)).toGeoJSON()
              return { ...feature, properties };
            })
            .filter(Boolean) || [];

        if (features.length) {
          createLegend({ grades, getColor }).addTo(map)

          const tooltip = createTooltip({ grades, getColor })
          tooltip.addTo(map)

          const onEachFeature = (feature, layer) => {
            layer.on({
              mouseover: () => tooltip.update(feature.properties),
              mouseout: () => tooltip.update()
            });
          };

          const geojson = L.geoJSON(features, { style, onEachFeature }).addTo(map);
          map.fitBounds(geojson.getBounds());
        } else {
          // if there's nothing to display, you must set the default view
          // TODO: parametrize
          map.setView([40.3, -3.7], 13)
        }

      }
    } catch (e) {
      if (e.message !== "View is not initialized") {
        throw e;
      }
    }
  }

  static update() {}

  static resize() {}

  static delete() {}

  static save() {}

  static restore() {}
}

Object.defineProperty(MapPlugin, 'name', {
  writable: true,
  value: 'Map'
});

registerPlugin("map", MapPlugin);
<commit_msg>initialize map on 0,0<commit_after>import { registerPlugin } from "@finos/perspective-viewer/dist/esm/utils.js";
import L from "leaflet"
import { feature } from "topojson-client"
import "../../../assets/stylesheets/comp-perspective-viewer-map.css"

// default geoJSON column name
const geomColumn = "geometry"
// default HTML id
const idMap = "map"

function createMapNode(element, div) {
  if (document.getElementById(idMap)) {
    document.getElementById(idMap).remove()
  }
  // Attach the container div to the DOM
  const mapElement = document.createElement("div");
  mapElement.id = idMap
  div.innerHTML = ""
  div.appendChild(document.createElement("slot"))
  element.appendChild(mapElement);

  // Initialize Leaflet
  const map = L.map(idMap, {
    center: [0, 0],
    zoom: 1
 });
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  // Add Topojson handlers
  L.TopoJSON = L.GeoJSON.extend({
    addData: function (jsonData) {
      if (jsonData.type === 'Topology') {
        for (let key in jsonData.objects) {
          L.GeoJSON.prototype.addData.call(this, feature(jsonData, jsonData.objects[key]));
        }
      } else {
        L.GeoJSON.prototype.addData.call(this, jsonData);
      }
    },
  });
  L.topoJSON = function (data, options) {
    return new L.TopoJSON(data, options);
  };

  return map;
}

function createLegend({ grades = [], getColor = d => d, fmt = d => d.toLocaleString() }) {
  const legend = L.control({ position: 'bottomleft' });

  legend.onAdd = function () {
      const div = L.DomUtil.create('div', 'perspective-map-legend')

      // loop through our density intervals and generate a label with a colored square for each interval
      for (var i = 0; i < grades.length; i++) {
          div.innerHTML +=
              ' ' +
              fmt(grades[i]) + (grades[i + 1] ? '–' + fmt(grades[i + 1]) + '
' : '+');
      }

      return div;
  };

  return legend;
}

function createTooltip() {
  const info = L.control();

  info.onAdd = function () {
      this._div = L.DomUtil.create('div', 'perspective-map-tooltip');
      this.update();
      return this._div;
  };

  // method that we will use to update the control based on feature properties passed
  info.update = function (props = {}) {
      this._div.innerHTML = Object.entries(props).map(([key, value]) => `<div><b>${key}</b>: ${value}</div>`).join("");
  };

  return info;
}

export class MapPlugin {
  static async create(div, view) {
    try {
      const columns = JSON.parse(this.getAttribute("columns"))

      // Enforces to have a geometry column
      if (!columns.includes(geomColumn)) {
        columns.push(geomColumn)
        this.setAttribute("columns", JSON.stringify(columns))
      }

      const map = createMapNode(this, div);

      // fetch the current displayed data
      const data = await view.to_json() || []
      if (data.some(({ [geomColumn]: geometry }) => !!geometry)) {

        // find first numeric field
        const [numericField] = (Object.entries(data[0]) || []).find(([, value]) => Number.isFinite(value))
        const mappedData = data.map(d => d[numericField])
        const isInteger = mappedData.every(x => Number.isInteger(x))

        // get range array
        const [min, max] = [Math.min(...mappedData), Math.max(...mappedData)]
        // max. categories
        const maxCategories = 5
        // substract one to maxCategories in order to keep the max. value INSIDE the range
        const step = (max - min) / (maxCategories - 1)
        const grades = Array.from({ length: maxCategories - 1 }, (_, i) => isInteger ? Math.floor(min + (i * step)) : min + (i * step))
        grades.push(max)

        const getColor = (value) => {
          // if don't substract 1, you'll never get the first index
          const ix = grades.findIndex(x => value <= x)
          // categories begins as of 1
          return ix >= 0 ? `var(--perspective-map-category-${ix + 1})` : ''
        }

        const style = ({ properties = {} }) => ({
          fillColor: getColor(properties[numericField]),
          fillOpacity: 1,
          color: getColor(properties[numericField]),
          opacity: 1,
        })

        // creates features ONLY if they're plane strings
        const features = data.reduce(
          (acc, { [geomColumn]: geometry = "{}", ...properties }) => {
            if (typeof geometry === "string" || geometry instanceof String) {
              // parse all the geoms as topojson, even they're not
              const {
                features: [feature]
              } = L.topoJSON(JSON.parse(geometry)).toGeoJSON();
              acc.push({ ...feature, properties });
            }

            return acc;
          },
          []
        );

        if (features.length) {
          createLegend({ grades, getColor }).addTo(map)

          const tooltip = createTooltip({ grades, getColor })
          tooltip.addTo(map)

          const onEachFeature = (feature, layer) => {
            layer.on({
              mouseover: () => tooltip.update(feature.properties),
              mouseout: () => tooltip.update()
            });
          };

          const geojson = L.geoJSON(features, { style, onEachFeature }).addTo(map);
          map.fitBounds(geojson.getBounds());
        }
      }
    } catch (e) {
      if (e.message !== "View is not initialized") {
        throw e;
      }
    }
  }

  static update() {}

  static resize() {}

  static delete() {}

  static save() {}

  static restore() {}
}

Object.defineProperty(MapPlugin, 'name', {
  writable: true,
  value: 'Map'
});

registerPlugin("map", MapPlugin);
<|endoftext|>


<commit_before>import Users from "meteor/vulcan:users";
import { addCallback } from 'meteor/vulcan:core';
import { Posts } from '../posts'

const MODERATE_OWN_PERSONAL_THRESHOLD = 50
const TRUSTLEVEL1_THRESHOLD = 2000
import { addEditableCallbacks } from '../../../server/editor/make_editable_callbacks.js'
import { makeEditableOptionsModeration } from './custom_fields.js'

function updateTrustedStatus ({newDocument, vote}) {

  const user = Users.findOne(newDocument.userId)
  if (user.karma >= TRUSTLEVEL1_THRESHOLD && (!Users.getGroups(user).includes('trustLevel1'))) {
    Users.update(user._id, {$push: {groups: 'trustLevel1'}});
    const updatedUser = Users.findOne(newDocument.userId)
    //eslint-disable-next-line no-console
    console.info("User gained trusted status", updatedUser.username, updatedUser._id, updatedUser.karma, updatedUser.groups)
  }
}

addCallback("votes.smallUpvote.async", updateTrustedStatus);
addCallback("votes.bigUpvote.async", updateTrustedStatus);

function updateModerateOwnPersonal({newDocument, vote}) {
  const user = Users.findOne(newDocument.userId)
  if (user.karma >= MODERATE_OWN_PERSONAL_THRESHOLD && (!Users.getGroups(user).includes('canModeratePersonal'))) {
    Users.update(user._id, {$push: {groups: 'canModeratePersonal'}});
    const updatedUser = Users.findOne(newDocument.userId)
    //eslint-disable-next-line no-console
    console.info("User gained trusted status", updatedUser.username, updatedUser._id, updatedUser.karma, updatedUser.groups)
  }
}

addCallback("votes.smallUpvote.async", updateModerateOwnPersonal);
addCallback("votes.bigUpvote.async", updateModerateOwnPersonal);

function maybeSendVerificationEmail (modifier, user)
{
  if(modifier.$set.whenConfirmationEmailSent
      && (!user.whenConfirmationEmailSent
          || user.whenConfirmationEmailSent.getTime() !== modifier.$set.whenConfirmationEmailSent.getTime()))
  {
    Accounts.sendVerificationEmail(user._id);
  }
}

addCallback("users.edit.sync", maybeSendVerificationEmail);

addEditableCallbacks({collection: Users, options: makeEditableOptionsModeration})

function approveUnreviewedPosts (newUser, oldUser)
{
  if(newUser.reviewedByUserId && !oldUser.reviewedByUserId)
  {
    Posts.update({userId:newUser._id}, {$set:{authorIsUnreviewed:false, postedAt: new Date()}})
  }
}

addCallback("users.edit.async", approveUnreviewedPosts);
<commit_msg>Make first created user automatically approved and Sunshine<commit_after>import Users from "meteor/vulcan:users";
import { addCallback } from 'meteor/vulcan:core';
import { Posts } from '../posts'

const MODERATE_OWN_PERSONAL_THRESHOLD = 50
const TRUSTLEVEL1_THRESHOLD = 2000
import { addEditableCallbacks } from '../../../server/editor/make_editable_callbacks.js'
import { makeEditableOptionsModeration } from './custom_fields.js'

function updateTrustedStatus ({newDocument, vote}) {

  const user = Users.findOne(newDocument.userId)
  if (user.karma >= TRUSTLEVEL1_THRESHOLD && (!Users.getGroups(user).includes('trustLevel1'))) {
    Users.update(user._id, {$push: {groups: 'trustLevel1'}});
    const updatedUser = Users.findOne(newDocument.userId)
    //eslint-disable-next-line no-console
    console.info("User gained trusted status", updatedUser.username, updatedUser._id, updatedUser.karma, updatedUser.groups)
  }
}

addCallback("votes.smallUpvote.async", updateTrustedStatus);
addCallback("votes.bigUpvote.async", updateTrustedStatus);

function updateModerateOwnPersonal({newDocument, vote}) {
  const user = Users.findOne(newDocument.userId)
  if (user.karma >= MODERATE_OWN_PERSONAL_THRESHOLD && (!Users.getGroups(user).includes('canModeratePersonal'))) {
    Users.update(user._id, {$push: {groups: 'canModeratePersonal'}});
    const updatedUser = Users.findOne(newDocument.userId)
    //eslint-disable-next-line no-console
    console.info("User gained trusted status", updatedUser.username, updatedUser._id, updatedUser.karma, updatedUser.groups)
  }
}

addCallback("votes.smallUpvote.async", updateModerateOwnPersonal);
addCallback("votes.bigUpvote.async", updateModerateOwnPersonal);

function maybeSendVerificationEmail (modifier, user)
{
  if(modifier.$set.whenConfirmationEmailSent
      && (!user.whenConfirmationEmailSent
          || user.whenConfirmationEmailSent.getTime() !== modifier.$set.whenConfirmationEmailSent.getTime()))
  {
    Accounts.sendVerificationEmail(user._id);
  }
}

addCallback("users.edit.sync", maybeSendVerificationEmail);

addEditableCallbacks({collection: Users, options: makeEditableOptionsModeration})

function approveUnreviewedPosts (newUser, oldUser)
{
  if(newUser.reviewedByUserId && !oldUser.reviewedByUserId)
  {
    Posts.update({userId:newUser._id}, {$set:{authorIsUnreviewed:false, postedAt: new Date()}})
  }
}

addCallback("users.edit.async", approveUnreviewedPosts);

// When the very first user account is being created, add them to Sunshine
// Regiment. Patterned after a similar callback in
// vulcan-users/lib/server/callbacks.js which makes the first user an admin.
function makeFirstUserAdminAndApproved (user) {
  const realUsersCount = Users.find({'isDummy': {$in: [false,null]}}).count();
  if (realUsersCount === 0) {
    user.reviewedByUserId = "firstAccount"; //HACK

    // Add the first user to the Sunshine Regiment
    if (!user.groups) user.groups = [];
    user.groups.push("sunshineRegiment");
  }
  return user;
}

addCallback('users.new.sync', makeFirstUserAdminAndApproved);
<|endoftext|>


<commit_before>/**
 * Copyright 2015 James Sharkey & Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var PageController = ['$scope', 'api', 'tags', function($scope, api, tags) {

		api.questionsPage.get().$promise.then(function (page) {

			var randomFeaturedQuestions = [];
			while ((page.featuredQuestions.length > 0) && (randomFeaturedQuestions.length < 5)) {
				var q = page.featuredQuestions.splice(Math.floor(Math.random() * page.featuredQuestions.length), 1)[0];
				randomFeaturedQuestions.push(q);
			}

			page.topBoards.length = Math.min(page.topBoards.length, 5);
			page.extraordinaryQuestions.length = Math.min(page.extraordinaryQuestions.length, 5);

			$scope.featuredQuestions = randomFeaturedQuestions.map(function(q) {
				var item = {};
				item.title = q.title;
				item.subtitle = tags.getFieldTag(q.tags).title;
				item.level = q.level;
				item.url = "/questions/" + q.id + "?board=" + q.boardId;
				return item;
			});

			$scope.topBoards = page.topBoards.map(function(b) {
				var item = {};
				item.title = b.title;
				item.url = "/gameboards#" + b.id;
				return item;
			});

			$scope.extraordinaryQuestions = page.extraordinaryQuestions;
		});
	}];

	return {
		PageController: PageController
	};
});<commit_msg>added catch to promise in questions page controller so that have a go page is populated when content fails.<commit_after>/**
 * Copyright 2015 James Sharkey & Ian Davies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
define([], function() {

	var PageController = ['$scope', 'api', 'tags', function($scope, api, tags) {

		api.questionsPage.get().$promise.then(function (page) {

			var randomFeaturedQuestions = [];
			while ((page.featuredQuestions.length > 0) && (randomFeaturedQuestions.length < 5)) {
				var q = page.featuredQuestions.splice(Math.floor(Math.random() * page.featuredQuestions.length), 1)[0];
				randomFeaturedQuestions.push(q);
			}

			page.topBoards.length = Math.min(page.topBoards.length, 5);
			page.extraordinaryQuestions.length = Math.min(page.extraordinaryQuestions.length, 5);

			$scope.featuredQuestions = randomFeaturedQuestions.map(function(q) {
				var item = {};
				item.title = q.title;
				item.subtitle = tags.getFieldTag(q.tags).title;
				item.level = q.level;
				item.url = "/questions/" + q.id + "?board=" + q.boardId;
				return item;
			});

			$scope.topBoards = page.topBoards.map(function(b) {
				var item = {};
				item.title = b.title;
				item.url = "/gameboards#" + b.id;
				return item;
			});

			$scope.extraordinaryQuestions = page.extraordinaryQuestions;
		}).catch(function(e){
			console.warn("Couldn't load top boards page: ", e);
			$scope.topBoards = false;
			$scope.featuredQuestions = false;
			$scope.extraordinaryQuestions = false;
		});
	}];

	return {
		PageController: PageController
	};
});<|endoftext|>


<commit_before>'use strict';
/* Configurable navbar
 *
 * The following variables can be declared in a conf file
 *   disable - Boolean
 *   search - Boolean
 *   navigation - Boolean
 *   notifier - Boolean
 *   template - String
 *
 * Example;
 *
 * {
 *   "navbar" : {
 *     "search" : true,
 *     "navigation" : true,
 *     "template" : "templates/navbar.html"
 *   }
 *
 */
angular.module('arethusa.core').directive('arethusaNavbar', [
  'configurator',
  '$window',
  function (configurator, $window) {
    return {
      restrict: 'AE',
      scope: true,
      link: function (scope, element, attrs) {
        var conf = configurator.configurationFor('navbar');

        var win = angular.element($window);

        function setScreenValues() {
          setWindowWidth();
          setLogo();
        }

        function setWindowWidth() {
          scope.windowWidth = win.width();
        }

        function setLogo() {
          var icon = scope.windowWidth > 1330 ? '' : 'icon-';
          scope.logo = "images/arethusa-" + icon + "small.png";
        }

        function isVisible(threshold, defaultVal) {
          if (defaultVal) {
            return scope.windowWidth > threshold;
          }
        }

        setScreenValues();
        win.on('resize', setScreenValues);

        scope.template = conf.template;
        scope.disable = conf.disable;

        scope.showSearch = function () {
          return conf.search;
        };

        scope.showNavigation = function () {
          return conf.navigation;
        };

        scope.showNotifier = function () {
          return isVisible(820, conf.notifier);
        };

        // Foundation's topbar doesn't seem to work properly - could be an issue
        // with angular. If we make it fixed, it overlaps our body and no padding
        // is added. We do it manually through this directive.
        angular.element(document.body).css({ padding: '45px'});
      },
      template: '

'
    };
  }
]);
<commit_msg>Adjust collapse threshold for the logo<commit_after>'use strict';
/* Configurable navbar
 *
 * The following variables can be declared in a conf file
 *   disable - Boolean
 *   search - Boolean
 *   navigation - Boolean
 *   notifier - Boolean
 *   template - String
 *
 * Example;
 *
 * {
 *   "navbar" : {
 *     "search" : true,
 *     "navigation" : true,
 *     "template" : "templates/navbar.html"
 *   }
 *
 */
angular.module('arethusa.core').directive('arethusaNavbar', [
  'configurator',
  '$window',
  function (configurator, $window) {
    return {
      restrict: 'AE',
      scope: true,
      link: function (scope, element, attrs) {
        var conf = configurator.configurationFor('navbar');

        var win = angular.element($window);

        function setScreenValues() {
          setWindowWidth();
          setLogo();
        }

        function setWindowWidth() {
          scope.windowWidth = win.width();
        }

        function setLogo() {
          var icon = scope.windowWidth > 1370 ? '' : 'icon-';
          scope.logo = "images/arethusa-" + icon + "small.png";
        }

        function isVisible(threshold, defaultVal) {
          if (defaultVal) {
            return scope.windowWidth > threshold;
          }
        }

        setScreenValues();
        win.on('resize', setScreenValues);

        scope.template = conf.template;
        scope.disable = conf.disable;

        scope.showSearch = function () {
          return conf.search;
        };

        scope.showNavigation = function () {
          return conf.navigation;
        };

        scope.showNotifier = function () {
          return isVisible(820, conf.notifier);
        };

        // Foundation's topbar doesn't seem to work properly - could be an issue
        // with angular. If we make it fixed, it overlaps our body and no padding
        // is added. We do it manually through this directive.
        angular.element(document.body).css({ padding: '45px'});
      },
      template: '

'
    };
  }
]);
<|endoftext|>


<commit_before>module.exports = function(config) {
  config.set({
    basePath: '',

    frameworks: ['jasmine'],

    files: [
      'app/static/js/lib/angular.min.js',
      'app/static/js/lib/angular-resource.min.js',
      'app/static/js/lib/ui-bootstrap-tpls-0.13.1-SNAPSHOT.min.js',
      'app/static/js/lib/http-throttler.js',
      'app/static/js/lib/dropzone.min.js',
      'app/static/js/lib/rdf_store_min.js',
      'tests/angular-mocks.js',
      'app/static/js/profile.js',
      'app/static/js/value-constraint.js',
      'app/static/js/property.js',
      'app/static/js/property-template.js',
      'app/static/js/resource-template.js',
      'app/static/js/pred-object.js',
      'app/static/js/namespace.js',
      'app/static/js/services.js',
      'app/static/js/initializer.js',
      'app/static/js/controllers.js',
      'tests/unit/*.js'
    ],

    exclude: [
      'app/static/js/lib/angular.js'
    ],

    reporters: ['dots', 'junit'],

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: false,

    browsers: ['PhantomJS'],

    singleRun: true,

    junitReporter: {
      outputFile: 'test-results.xml'
    };
  });
};
<commit_msg>fix syntax<commit_after>module.exports = function(config) {
  config.set({
    basePath: '',

    frameworks: ['jasmine'],

    files: [
      'app/static/js/lib/angular.min.js',
      'app/static/js/lib/angular-resource.min.js',
      'app/static/js/lib/ui-bootstrap-tpls-0.13.1-SNAPSHOT.min.js',
      'app/static/js/lib/http-throttler.js',
      'app/static/js/lib/dropzone.min.js',
      'app/static/js/lib/rdf_store_min.js',
      'tests/angular-mocks.js',
      'app/static/js/profile.js',
      'app/static/js/value-constraint.js',
      'app/static/js/property.js',
      'app/static/js/property-template.js',
      'app/static/js/resource-template.js',
      'app/static/js/pred-object.js',
      'app/static/js/namespace.js',
      'app/static/js/services.js',
      'app/static/js/initializer.js',
      'app/static/js/controllers.js',
      'tests/unit/*.js'
    ],

    exclude: [
      'app/static/js/lib/angular.js'
    ],

    reporters: ['dots', 'junit'],

    port: 9876,

    colors: true,

    logLevel: config.LOG_INFO,

    autoWatch: false,

    browsers: ['PhantomJS'],

    singleRun: true,

    junitReporter: {
      outputFile: 'test-results.xml'
    }
  });
};
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var calcStats = function(data) {
  var min;
  for (var i=0;i<data.length;++i) {
    min = Math.min(data[i], min) || data[i];
  }

  return {
    count: 0,
    minValue: min
  };
};

var testEmptyStats = function() {
  var stats = calcStats([]);
  assert.equal(undefined, stats.minValue);
  assert.equal(undefined, stats.maxValue);
  assert.equal(0, stats.count);
  assert.equal(undefined, stats.average);
};

var testMinimum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.minValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.minValue);

  var zeroMin = calcStats([0]);
  assert.equal(0, zeroMin.minValue);
};

var test = function() {
  testEmptyStats();

  testMinimum();
};

test();
<commit_msg>failing because of the || trick<commit_after>"use strict";

var assert = require('assert');

var calcStats = function(data) {
  var min;
  for (var i=0;i<data.length;++i) {
    min = Math.min(data[i], min) || data[i];
  }

  return {
    count: 0,
    minValue: min
  };
};

var testEmptyStats = function() {
  var stats = calcStats([]);
  assert.equal(undefined, stats.minValue);
  assert.equal(undefined, stats.maxValue);
  assert.equal(0, stats.count);
  assert.equal(undefined, stats.average);
};

var testMinimum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.minValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.minValue);

  var zeroMin = calcStats([0]);
  assert.equal(0, zeroMin.minValue);

  var multiple = calcStats([0,1,2,3,4,5]);
  assert.equal(0, multiple.minValue);
};

var test = function() {
  testEmptyStats();

  testMinimum();
};

test();
<|endoftext|>


<commit_before>/*jslint
 browser: true,
 white: true,
 plusplus: true,
 vars: true,
 nomen: true */
/*global jQuery */

if(typeof window.GOVUK === 'undefined'){ window.GOVUK = {}; }
(function($) {
  "use strict";

  var documentFilter = {
    loading: false,
    $form: null,

    drawPagination: function (data) {
      var $existingNav = $('#show-more-documents'),
          $nav = $('');
      if ($existingNav.length > 0) {
        $existingNav.remove();
      }
      if (data.next_page_url) {
        var $ul = $('
'),
            $li, $a, $link;

        $nav = $('').append($ul);
        if (data.next_page_url) {
          $li = $('
');
          $a = $('Next page '+ documentFilter.progressSpan(data.next_page, data.total_pages) +'').attr('href', data.next_page_url);
          $link = $('link[rel=next][type=application/json]');
          if ($link.length) {
            $link.attr("href", data.next_page_url);
          }
          else {
            $ul.append($('<link rel="next" type="application/json" href="' + data.next_page_url + '">'));
          }
          $ul.append($li);
          $li.append($a);
        } else {
          $('link[rel=next][type=application/json]').remove();
        }
      }
      return $nav;
    },
    progressSpan: function(current, total) {
      return '' + current + " of " + total + '';
    },
    importantAttributes: ["id", "title", "url", "type"],
    importantAttribute: function(attribute) {
      return ($.inArray(attribute, documentFilter.importantAttributes) < 0);
    },
    capitalize: function(attribute) {
      return attribute.replace(/_/, " ").replace(/(^|\s)([a-z])/g, function(_,a,b){ return a+b.toUpperCase(); });
    },
    drawTableCell: function(attributeName, attributeValue) {
      var inner = attributeValue;
      return '' + inner + '	';
    },
    drawTableRows: function(results) {
      var $tBody = $('
'), i,l;

      for (i=0, l=results.length; i<l; i++) {
        var row = results[i],
            $tableRow = $('
'),
            $th = $(''),
            $a = $(''),
            attribute;

        $a.text(row.title);
        $a.attr('href', row.url);

        $tableRow.attr('id', row.type + '_' + row.id).addClass((i < 3 ? ' recent' : ''));
        $th.append($a);
        $tableRow.append($th);
        for(attribute in row) {
          if (documentFilter.importantAttribute(attribute)) {
            $tableRow.append($(documentFilter.drawTableCell(attribute, row[attribute])));
          }
        }
        $tBody.append($tableRow);
      }
      return $tBody;
    },
    drawTable: function(data) {
      var $container = $('.filter-results');
      if (data.results.length > 0) {
        var $tBody;
        if ($('#document-list').length < 1) {
          var $table = $('
'),
              $tHead = $('
'),
              $tr = $('
').append('Title	');

          for(var attribute in data.results[0]) {
            if (documentFilter.importantAttribute(attribute)) {
              $tr.append($('' + documentFilter.capitalize(attribute) + '	'))
            }
          }
          $tHead.append($tr);
          $table.append($tHead);
          $table.append('
');
          $container.empty().append($table);
        }
        $tBody = documentFilter.drawTableRows(data.results);
        $container.find('.document-list tbody').replaceWith($tBody);
        $container.append(documentFilter.drawPagination(data));
      } else {
        $container.empty();
        $container.append('

There are no matching documents.
' +
                        '
Try making your search broader and try again.

');
      }
    },
    extendTable: function(data){
      var $container = $('.filter-results');
      if (data.results.length > 0) {
        var $tBody = $container.find('tbody'),
            $rows = documentFilter.drawTableRows(data.results);

        $tBody.append($rows.contents());

        $container.find('nav').remove();
        $container.append(documentFilter.drawPagination(data));
      }
    },
    updateAtomFeed: function(data) {
      if (data.atom_feed_url) {
        $(".subscribe a.feed").attr("href", data.atom_feed_url);
      }
    },
    submitFilters: function(e){
      e.preventDefault();
      var $form = documentFilter.$form,
          $submitButton = $form.find('input[type=submit]'),
          url = $form.attr('action'),
          jsonUrl = url + ".json",
          params = $form.serializeArray();

      $submitButton.addClass('disabled');
      $(".filter-results-summary").find('.selections').text("Loading results…");
      documentFilter.loading = true;
      // TODO: make a spinny updating thing
      $.ajax(jsonUrl, {
        cache: false,
        dataType:'json',
        data: params,
        complete: function(){
          documentFilter.loading = false;
        },
        success: function(data) {
          documentFilter.updateAtomFeed(data);
          if (data.results) {
            documentFilter.drawTable(data);
            documentFilter.liveResultSummary(data, documentFilter.currentPageState());
          }
          var newUrl = url + "?" + $form.serialize();
          history.pushState(documentFilter.currentPageState(), null, newUrl);
          window._gaq && _gaq.push(['_trackPageview', newUrl]);
          // undo double-click protection
          //$submitButton.removeAttr('disabled').removeClass('disabled');

        },
        error: function() {
          $submitButton.removeAttr('disabled');
        }
      });
    },
    liveResultSummary: function(data, formStatus){
      var $selections = $('.selections'),
          $title = $('.page_title'),
          summary = '';

      $selections.html('');
      $title.find('span').remove();

      if (data.total_count > 0) {
        summary = 'Showing ' + data.total_count +' result';
        if (data.total_count != 1) summary += 's';
        summary += ' ';
      } else {
        summary = 'No results ';
      }

      if(formStatus.selected) {
        var i = formStatus.selected.length;

        while(i--) {
          var j = formStatus.selected[i].title.length;

          if (j > 0) {
            if (formStatus.selected[i].id == "publication_filter_option") {
              if (formStatus.selected[i].value != "all") {
                $title.append(': '+formStatus.selected[i].title[0]+'');
              }
            } else if (formStatus.selected[i].id != 'sub_orgs' && formStatus.selected[i].id != 'date') {
              if (formStatus.selected[i].id == 'topics') {
                summary += 'about ';
              } else if (formStatus.selected[i].id == 'departments') {
                summary += 'published by ';
              }

              summary += ' ';

              while(j--) {
                var selection = ""+formStatus.selected[i].title[j]+" × ";
                if (j > 1) {
                  selection += ", ";
                } else if (j == 1 && formStatus.selected[i].title.length > 1) {
                  selection += " and ";
                }

                summary += selection;
              }

              summary += ' ';
            }

          }
        }

      }

      $selections.html(summary);

      documentFilter.filterEvents();
    },
    filterEvents: function(){
      $(".selections .chosen span a").on("click", function(){
        documentFilter.removeFilters($(this).attr("data-val"));
        $(this).parent().remove();
        return false;
      });
    },
    removeFilters: function(removed){
      var options = $("select option");
      $(options).each(function(){
        if($(this).attr("value") == removed){
          $(this).removeAttr("selected");
          var $select = $(this).parent("select");
          if($select.children("option:selected").length == 0){
            $select.find(">:first-child").prop("selected", true);
          };
          $(this).parent("select").change();
        }
      });
    },
    currentPageState: function() {
      return {
        html: $('.filter-results').html(),
        selected: $.map(documentFilter.$form.find('select'), function(n) {
          var $n = $(n);
          var id = $n.attr('id');
          var titles = [];
          $("#" + id  + " option:selected").each(function(){
            titles.push($(this).text());
          });
          return {id: id, value: $n.val(), title: titles};
        }),
        text: $.map(documentFilter.$form.find('input[type=text]'), function(n) {
          var $n = $(n);
          return {id: $n.attr('id'), value: $n.val()};
        }),
        checked: $.map(documentFilter.$form.find('input[type=radio]:checked'), function(n) {
          return $(n).attr('id');
        })
      };
    },
    onPopState: function(event) {
      if (event.state && event.state.html) {
        $('.filter-results').html(event.state.html);
        $.each(event.state.selected, function(i, selected) {
          $("#" + selected.id).val(selected.value);
        });
        $.each(event.state.text, function(i, text) {
          $("#" + text.id).val(text.value);
        });
        $.each(event.state.checked, function(i, checked) {
          $("#" + checked).attr('checked', true);
        });
      }
    },
    loadMoreInline: function(){
      var $url = $('link[rel=next][type=application/json]').attr('href')

      if(!documentFilter.loading && $url){
        documentFilter.loading = true;
        $.ajax($url, {
          cache: false,
          dataType:'json',
          complete: function(){
            documentFilter.loading = false;
            $('.infinite.loading').removeClass('loading');
          },
          success: function(data) {
            if (data.results) {
              documentFilter.extendTable(data)
            }
          }
        });
      }
    },
    initScroll: function(){
      documentFilter.scrolled = false;
      $('#show-more-documents .previous-next-navigation').addClass('infinite');

      $(window).scroll(function(){
        documentFilter.scrolled = true;
      });

      var scrollInterval = window.setInterval(documentFilter.onScroll, 250);
    },
    onScroll: function(){
      if(documentFilter.scrolled){
        documentFilter.scrolled = false;
        var $window = $(window),
            $nav = $('.previous-next-navigation'),
            bottomOfWindow = $window.scrollTop() + $window.height(),
            navOffset = $nav.offset();

        documentFilter.hideFooter();
        $nav.addClass('loading').find('.next a').text('Loading more...');
        if(navOffset && (bottomOfWindow + 100 > navOffset.top)){
          documentFilter.loadMoreInline();
        } else {
          documentFilter.showFooter();
        }
      }
    },
    hideFooter: function(){
      if(documentFilter.footerHidden !== true){
        $('#footer').addClass('visuallyhidden');
        documentFilter.footerHidden = false;
      }
    },
    showFooter: function(){
      var $next = $('#show-more-documents .next a');

      if($next.length === 0){
        $('#footer').removeClass('visuallyhidden');
      }
    }
  };
  window.GOVUK.documentFilter = documentFilter;

  var enableDocumentFilter = function() {
    if (window.ieVersion && ieVersion === 6) {
      return this;
    }
    this.each(function(){
      if (window.GOVUK.support.history()) {
        var $form = $(this);
        $(window).on('popstate', function(evet) {
          documentFilter.onPopState(event);
        });
        documentFilter.$form = $form;

        history.replaceState(documentFilter.currentPageState(), null);
        $form.submit(documentFilter.submitFilters);
        $form.find('select, input[name=direction]:radio').change(function(e){
          $form.submit();
        });

        var delay = (function(){
          var timer = 0;
          return function(callback, ms){
            clearTimeout (timer);
            timer = setTimeout(callback, ms);
          }
        })();

        $('#keyword-filter')
        .find('input[name=keywords]').keyup(function () {
          delay(function () {
            $form.submit();
          }, 600);
        });

        $(".submit").addClass("js-hidden");

      }
      if($('#show-more-documents .previous').length === 0){
        documentFilter.initScroll();
      }
    });
    return this;
  }

  $.fn.extend({
    enableDocumentFilter: enableDocumentFilter
  });
})(jQuery);
<commit_msg>Update date not rendered in publications page<commit_after>/*jslint
 browser: true,
 white: true,
 plusplus: true,
 vars: true,
 nomen: true */
/*global jQuery */

if(typeof window.GOVUK === 'undefined'){ window.GOVUK = {}; }
(function($) {
  "use strict";

  var documentFilter = {
    loading: false,
    $form: null,

    drawPagination: function (data) {
      var $existingNav = $('#show-more-documents'),
          $nav = $('');
      if ($existingNav.length > 0) {
        $existingNav.remove();
      }
      if (data.next_page_url) {
        var $ul = $('
'),
            $li, $a, $link;

        $nav = $('').append($ul);
        if (data.next_page_url) {
          $li = $('
');
          $a = $('Next page '+ documentFilter.progressSpan(data.next_page, data.total_pages) +'').attr('href', data.next_page_url);
          $link = $('link[rel=next][type=application/json]');
          if ($link.length) {
            $link.attr("href", data.next_page_url);
          }
          else {
            $ul.append($('<link rel="next" type="application/json" href="' + data.next_page_url + '">'));
          }
          $ul.append($li);
          $li.append($a);
        } else {
          $('link[rel=next][type=application/json]').remove();
        }
      }
      return $nav;
    },
    progressSpan: function(current, total) {
      return '' + current + " of " + total + '';
    },
    importantAttributes: ["id", "title", "url", "type", "updated_at"],
    importantAttribute: function(attribute) {
      return ($.inArray(attribute, documentFilter.importantAttributes) < 0);
    },
    capitalize: function(attribute) {
      return attribute.replace(/_/, " ").replace(/(^|\s)([a-z])/g, function(_,a,b){ return a+b.toUpperCase(); });
    },
    drawTableCell: function(attributeName, attributeValue) {
      var inner = attributeValue;
      return '' + inner + '	';
    },
    drawTableRows: function(results) {
      var $tBody = $('
'), i,l;

      for (i=0, l=results.length; i<l; i++) {
        var row = results[i],
            $tableRow = $('
'),
            $th = $(''),
            $a = $(''),
            attribute;

        $a.text(row.title);
        $a.attr('href', row.url);

        $tableRow.attr('id', row.type + '_' + row.id).addClass((i < 3 ? ' recent' : ''));
        $th.append($a);
        $tableRow.append($th);
        for(attribute in row) {
          if (documentFilter.importantAttribute(attribute)) {
            $tableRow.append($(documentFilter.drawTableCell(attribute, row[attribute])));
          }
        }
        $tBody.append($tableRow);
      }
      return $tBody;
    },
    drawTable: function(data) {
      var $container = $('.filter-results');
      if (data.results.length > 0) {
        var $tBody;
        if ($('#document-list').length < 1) {
          var $table = $('
'),
              $tHead = $('
'),
              $tr = $('
').append('Title	');

          for(var attribute in data.results[0]) {
            if (documentFilter.importantAttribute(attribute)) {
              $tr.append($('' + documentFilter.capitalize(attribute) + '	'))
            }
          }
          $tHead.append($tr);
          $table.append($tHead);
          $table.append('
');
          $container.empty().append($table);
        }
        $tBody = documentFilter.drawTableRows(data.results);
        $container.find('.document-list tbody').replaceWith($tBody);
        $container.append(documentFilter.drawPagination(data));
      } else {
        $container.empty();
        $container.append('

There are no matching documents.
' +
                        '
Try making your search broader and try again.

');
      }
    },
    extendTable: function(data){
      var $container = $('.filter-results');
      if (data.results.length > 0) {
        var $tBody = $container.find('tbody'),
            $rows = documentFilter.drawTableRows(data.results);

        $tBody.append($rows.contents());

        $container.find('nav').remove();
        $container.append(documentFilter.drawPagination(data));
      }
    },
    updateAtomFeed: function(data) {
      if (data.atom_feed_url) {
        $(".subscribe a.feed").attr("href", data.atom_feed_url);
      }
    },
    submitFilters: function(e){
      e.preventDefault();
      var $form = documentFilter.$form,
          $submitButton = $form.find('input[type=submit]'),
          url = $form.attr('action'),
          jsonUrl = url + ".json",
          params = $form.serializeArray();

      $submitButton.addClass('disabled');
      $(".filter-results-summary").find('.selections').text("Loading results…");
      documentFilter.loading = true;
      // TODO: make a spinny updating thing
      $.ajax(jsonUrl, {
        cache: false,
        dataType:'json',
        data: params,
        complete: function(){
          documentFilter.loading = false;
        },
        success: function(data) {
          documentFilter.updateAtomFeed(data);
          if (data.results) {
            documentFilter.drawTable(data);
            documentFilter.liveResultSummary(data, documentFilter.currentPageState());
          }
          var newUrl = url + "?" + $form.serialize();
          history.pushState(documentFilter.currentPageState(), null, newUrl);
          window._gaq && _gaq.push(['_trackPageview', newUrl]);
          // undo double-click protection
          //$submitButton.removeAttr('disabled').removeClass('disabled');

        },
        error: function() {
          $submitButton.removeAttr('disabled');
        }
      });
    },
    liveResultSummary: function(data, formStatus){
      var $selections = $('.selections'),
          $title = $('.page_title'),
          summary = '';

      $selections.html('');
      $title.find('span').remove();

      if (data.total_count > 0) {
        summary = 'Showing ' + data.total_count +' result';
        if (data.total_count != 1) summary += 's';
        summary += ' ';
      } else {
        summary = 'No results ';
      }

      if(formStatus.selected) {
        var i = formStatus.selected.length;

        while(i--) {
          var j = formStatus.selected[i].title.length;

          if (j > 0) {
            if (formStatus.selected[i].id == "publication_filter_option") {
              if (formStatus.selected[i].value != "all") {
                $title.append(': '+formStatus.selected[i].title[0]+'');
              }
            } else if (formStatus.selected[i].id != 'sub_orgs' && formStatus.selected[i].id != 'date') {
              if (formStatus.selected[i].id == 'topics') {
                summary += 'about ';
              } else if (formStatus.selected[i].id == 'departments') {
                summary += 'published by ';
              }

              summary += ' ';

              while(j--) {
                var selection = ""+formStatus.selected[i].title[j]+" × ";
                if (j > 1) {
                  selection += ", ";
                } else if (j == 1 && formStatus.selected[i].title.length > 1) {
                  selection += " and ";
                }

                summary += selection;
              }

              summary += ' ';
            }

          }
        }

      }

      $selections.html(summary);

      documentFilter.filterEvents();
    },
    filterEvents: function(){
      $(".selections .chosen span a").on("click", function(){
        documentFilter.removeFilters($(this).attr("data-val"));
        $(this).parent().remove();
        return false;
      });
    },
    removeFilters: function(removed){
      var options = $("select option");
      $(options).each(function(){
        if($(this).attr("value") == removed){
          $(this).removeAttr("selected");
          var $select = $(this).parent("select");
          if($select.children("option:selected").length == 0){
            $select.find(">:first-child").prop("selected", true);
          };
          $(this).parent("select").change();
        }
      });
    },
    currentPageState: function() {
      return {
        html: $('.filter-results').html(),
        selected: $.map(documentFilter.$form.find('select'), function(n) {
          var $n = $(n);
          var id = $n.attr('id');
          var titles = [];
          $("#" + id  + " option:selected").each(function(){
            titles.push($(this).text());
          });
          return {id: id, value: $n.val(), title: titles};
        }),
        text: $.map(documentFilter.$form.find('input[type=text]'), function(n) {
          var $n = $(n);
          return {id: $n.attr('id'), value: $n.val()};
        }),
        checked: $.map(documentFilter.$form.find('input[type=radio]:checked'), function(n) {
          return $(n).attr('id');
        })
      };
    },
    onPopState: function(event) {
      if (event.state && event.state.html) {
        $('.filter-results').html(event.state.html);
        $.each(event.state.selected, function(i, selected) {
          $("#" + selected.id).val(selected.value);
        });
        $.each(event.state.text, function(i, text) {
          $("#" + text.id).val(text.value);
        });
        $.each(event.state.checked, function(i, checked) {
          $("#" + checked).attr('checked', true);
        });
      }
    },
    loadMoreInline: function(){
      var $url = $('link[rel=next][type=application/json]').attr('href')

      if(!documentFilter.loading && $url){
        documentFilter.loading = true;
        $.ajax($url, {
          cache: false,
          dataType:'json',
          complete: function(){
            documentFilter.loading = false;
            $('.infinite.loading').removeClass('loading');
          },
          success: function(data) {
            if (data.results) {
              documentFilter.extendTable(data)
            }
          }
        });
      }
    },
    initScroll: function(){
      documentFilter.scrolled = false;
      $('#show-more-documents .previous-next-navigation').addClass('infinite');

      $(window).scroll(function(){
        documentFilter.scrolled = true;
      });

      var scrollInterval = window.setInterval(documentFilter.onScroll, 250);
    },
    onScroll: function(){
      if(documentFilter.scrolled){
        documentFilter.scrolled = false;
        var $window = $(window),
            $nav = $('.previous-next-navigation'),
            bottomOfWindow = $window.scrollTop() + $window.height(),
            navOffset = $nav.offset();

        documentFilter.hideFooter();
        $nav.addClass('loading').find('.next a').text('Loading more...');
        if(navOffset && (bottomOfWindow + 100 > navOffset.top)){
          documentFilter.loadMoreInline();
        } else {
          documentFilter.showFooter();
        }
      }
    },
    hideFooter: function(){
      if(documentFilter.footerHidden !== true){
        $('#footer').addClass('visuallyhidden');
        documentFilter.footerHidden = false;
      }
    },
    showFooter: function(){
      var $next = $('#show-more-documents .next a');

      if($next.length === 0){
        $('#footer').removeClass('visuallyhidden');
      }
    }
  };
  window.GOVUK.documentFilter = documentFilter;

  var enableDocumentFilter = function() {
    if (window.ieVersion && ieVersion === 6) {
      return this;
    }
    this.each(function(){
      if (window.GOVUK.support.history()) {
        var $form = $(this);
        $(window).on('popstate', function(evet) {
          documentFilter.onPopState(event);
        });
        documentFilter.$form = $form;

        history.replaceState(documentFilter.currentPageState(), null);
        $form.submit(documentFilter.submitFilters);
        $form.find('select, input[name=direction]:radio').change(function(e){
          $form.submit();
        });

        var delay = (function(){
          var timer = 0;
          return function(callback, ms){
            clearTimeout (timer);
            timer = setTimeout(callback, ms);
          }
        })();

        $('#keyword-filter')
        .find('input[name=keywords]').keyup(function () {
          delay(function () {
            $form.submit();
          }, 600);
        });

        $(".submit").addClass("js-hidden");

      }
      if($('#show-more-documents .previous').length === 0){
        documentFilter.initScroll();
      }
    });
    return this;
  }

  $.fn.extend({
    enableDocumentFilter: enableDocumentFilter
  });
})(jQuery);
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var statistics = function(s) {

    var minFn = function(x,y) { return Math.min(x,y); };
    var maxFn = function(x,y) { return Math.max(x,y); };

    return {
	count: s.length,
	maximum: s.length === 0 ? undefined : s.reduce(maxFn, s[0]),
	minimum: s.length === 0 ? undefined : s.reduce(minFn, s[0])
    };
};

describe('sequence processing', function() {
    it('should return the count', function() {
	assert.equal(0, statistics([]).count);
	assert.equal(3, statistics([1,2,3]).count);
    });

    it('avg, min and max should be undefined for empty lists', function() {
	var stats = statistics([]);

	assert.equal(undefined, stats.minimum);
	assert.equal(undefined, stats.maximum);
	assert.equal(undefined, stats.average);
    });

    it('min works as expected on lists', function() {
	assert.equal(0, statistics([0]).minimum);
	assert.equal(3, statistics([5,4,3]).minimum);
    });

    it('max works as expected', function() {
	assert.equal(0, statistics([0]).maximum);
	assert.equal(5, statistics([5,4,3]).maximum);
    });

});
<commit_msg>refactor<commit_after>"use strict";

var assert = require('assert');

var statistics = function(s) {

    var minFn = function(x,y) { return Math.min(x,y); };
    var maxFn = function(x,y) { return Math.max(x,y); };

    return {
	count: s.length,
	maximum: s.length === 0 ? undefined : s.reduce(maxFn, s[0]),
	minimum: s.length === 0 ? undefined : s.reduce(minFn, s[0])
    };
};

describe('sequence processing', function() {
    it('should return the count', function() {
	assert.equal(0, statistics([]).count);
	assert.equal(3, statistics([1,2,3]).count);
    });

    it('avg, min and max should be undefined for empty lists', function() {
	var stats = statistics([]);

	assert.equal(undefined, stats.minimum);
	assert.equal(undefined, stats.maximum);
	assert.equal(undefined, stats.average);
    });

    it('min works as expected', function() {
	assert.equal(0, statistics([0]).minimum);
	assert.equal(3, statistics([5,4,3]).minimum);
    });

    it('max works as expected', function() {
	assert.equal(0, statistics([0]).maximum);
	assert.equal(5, statistics([5,4,3]).maximum);
    });

});
<|endoftext|>


<commit_before><commit_msg>Separate layout code for list cloud layout.<commit_after><|endoftext|>