<commit_before>var CursorMaintenanceComparison = (function () {
  'use strict';

  // requires: cursor_maintenance_examples.js
  //           dom_helpers.js
  //           note_expander.js

  // CursorMaintenanceComparison powers a web page that displays a table
  //  of results from several cursor-maintenance implementations applied
  //  in parallel to a formatting instance. There is one formatting
  //  instance for each of two formats, commatize and trimify. A table
  //  column is dedicated to each format. The various cursor-maintenance
  //  approaches correspond to rows. Each formatting instance can be
  //  edited by the user, and the results are immediately updated to
  //  reflect changes in the raw text or cursor position.

  var CME = CursorMaintenanceExamples,
      make = DOMHelpers.make,
      classRemove = DOMHelpers.classRemove,
      inputMaxLengths = {  // Maximum number of input characters for each
        commatize: 15,     //  format. These values are used to set the
        trimify: 60        //  maxlength attribute of the input element.
      },
      cellMinWidths = {
        commatize: 200,
        trimify: 600
      },
      inputs = {},
      outputs = {},
      activeButtons = {},
      activeInput,
      scores = {};

  // setOutput displays a cursor-maintenance result in an output element.
  function setOutput(element, text, cursor) {
    // The range test evaluates to false when cursor is null or undefined.
    if (!(cursor >= 0 && cursor <= text.length)) {
      element.innerHTML = text;
      return;
    }
    // Indicate the cursor position with a specially styled span. The start
    //  span makes the cursor span visible at position 0.
    element.innerHTML = '' +
        text.substring(0, cursor) + '' +
        text.substring(cursor);
  }

  // enableInput builds a handler for events in the input field for each
  //  format. The input element is hidden behind an output box that contains
  //  a graphical cursor. When the input element has focus, the output box
  //  is active, giving it special styling with a blinking cursor. In addition
  //  to managing the active output box, we have to format the text with the
  //  plain formatter and with all the cursor-maintaining formatters, and
  //  display the results in the table.
  function enableInput(formatName) {
    var input = inputs[formatName],
        formatOutputs = outputs[formatName],
        formatter = CME.format[formatName];  // This is the plain formatter.
    // update responds to any event that can potentially change the text.
    function update(event) {
      var rawText = input.value,
          rawCursor = input.selectionStart,
          formattedText = formatter(rawText).text;  // Plain formatting.
      // Display the formatted text without a cursor.
      setOutput(formatOutputs.after, formattedText);
      // Mirror the raw text in the output box directly over the input.
      setOutput(formatOutputs.before, rawText, rawCursor);
      // Deactivate the previously active input-mirroring output box.
      if (activeInput != input) {
        if (activeInput) {
          console.log('deactivating', activeInput.mirror);
          classRemove(activeInput.mirror, 'active');
        }
        // Activate the current input-mirroring output box.
        console.log(event.type, 'activating', formatName, formatOutputs.before);
        activeInput = input;
        input.mirror.className += ' active';
      }
      // Run every cursor-maintaining formatter and display the results.
      Object.keys(formatOutputs).forEach(function (approach) {
        var result;
        if (approach in CME) {
          result = CME[approach][formatName](rawText, rawCursor);
          setOutput(formatOutputs[approach], result.text, result.cursor);
        }
      });
      // Update the score display for the retrospective approach whose
      //  score button is currently active.
      if (activeButtons[formatName]) {
        activeButtons[formatName].click();
      }
    }
    // When the input loses focus, deactivate its input-mirroring output box.
    input.onblur = function () {
      console.log('blurring', formatName);
      classRemove(input.mirror, 'active');
    };
    // Attach update for events that can change the raw text or cursor.
    [ 'keydown', 'keyup', 'click' ].forEach(function (eventName) {
      input.addEventListener(eventName, update);
    });
    // To detect input cursor movement in WebKit browsers on iOS, we listen
    //  to selectionchange events on the document.
    //  Hat tip: http://stackoverflow.com/questions/23544937/
    //document.addEventListener('selectionchange', update);
  }

  // enableScoreButton builds a click handler for a score button that
  //  was made for a particular format and cursor-maintenance approach,
  //  as named by arguments. The format and approach names are used to
  //  look up the appropriate cursor-maintenance method and DOM elements.
  //  The response to a click is to generate a list of scores calculated
  //  for every cursor position by a retrospective cost function, and to
  //  display these in the score area at the bottom of the table column.
  function enableScoreButton(button, formatName, approachName) {
    var maintainer = CME[approachName][formatName],
        scoreArea = scores[formatName],
        input = inputs[formatName];
    button.onclick = function () {
      var text = input.value,
          cursor = input.selectionStart,
          result = maintainer(text, cursor),
          activeButton = activeButtons[formatName],
          i, item, output, score;
      scoreArea.innerHTML = '';
      for (i = 0; i < result.scores.length; ++i) {
        // Make a separate div for each cursor position.
        item = make('div', { parent: scoreArea, className: 'scoreItem' });
        if (i == result.cursor) {
          item.className += ' best';
        }
        output = make('span', { className: 'output', parent: item });
        setOutput(output, result.text, i);
        output.style.width = '250px';
        score = ' ' + result.scores[i];
        // Whole values don't get a decimal point. Display fractional values
        //  with up to six decimal digits.
        if (score.indexOf('.') != -1) {
          score = score.substring(0, score.indexOf('.') + 6);
        }
        make('span', { parent: item, className: 'score', innerHTML: score });
      }
      // The currently active score button gets special styling. When a
      //  different button is activated, alter the class name of the
      //  previously active button to remove the styling.
      if (activeButton && activeButton != button) {
        classRemove(activeButton, 'active');
      }
      activeButtons[formatName] = button;
      button.className += ' active';
    };
  }

  // makeApproachName takes label text extracted from the HTML table and
  //  and transforms it into the name of a member of the cursor-maintenance
  //  module. Examples:
  // "ad hoc" -> "adHoc" (a cursor-maintenance implementation)
  // "frequency ratios" -> "frequencyRatios"  (a retrospective cost function)
  function makeApproachName(s) {
    var parts = s.replace(/^\s+|\s+$/g, '').split(/\s+/),
        i, name;
    for (i = 1; i < parts.length; ++i) {
      parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
    }
    name = parts.join('');
    return name;
  }

  // load fills out a table that is initially defined in the static HTML
  //  in skeletal form, with single-cell rows that each contain the name
  //  of a cursor-maintenance approach. The names are extracted from the
  //  HTML and used to build result fields. Score buttons are built for
  //  rows that have the word "retrospective" in their class name. Input
  //  areas are built at the top of each column, and score displays are
  //  built at the bottom. The format names and approach names in the HTML
  //  are used to look up functions in the cursor-maintenance module.
  function load() {
    var table = document.getElementById('comparisons'),
        rows = table.getElementsByTagName('tr'),
        inputRow = document.getElementById('inputs'),
        scoreRow = document.getElementById('scores'),
        formatNames = [ 'commatize', 'trimify' ],
        i, row, cells,
        approach;
    formatNames.forEach(function (formatName) {
      outputs[formatName] = {};
      // Build the score displays in advance so that we'll be able to connect
      //  score buttons to displays as we traverse the table.
      scores[formatName] = make('div', { className: 'scoreList', parent:
          make('td', { parent: scoreRow }) });
    });
    // Traverse all rows in one shot, filling out cells as we go. Use
    //  class names to decide what to build for each row.
    for (i = 0; i < rows.length; ++i) {
      row = rows[i];
      // Don't do anything for table headers and score displays.
      if (row.className.indexOf('outputs') == -1) {
        continue;
      }
      // Extract the approach name from the first cell in the row.
      cells = row.getElementsByTagName('td');
      approach = makeApproachName(cells[0].innerHTML);
      // Build cells for each format. Most cells only get an output field.
      //  The top row, labeled "before", also gets an input field that
      //  hides behind the output field (which will contain a fancy graphical
      //  cursor). Retrospective result rows get a score button next to
      //  the output field.
      formatNames.forEach(function (formatName) {
        var button,
            cell = make('td', { parent: row }),
            output = outputs[formatName][approach] = make('span',
                { parent: cell, className: 'output' });
        if (approach == 'before') {
          inputs[formatName] = make('input', { parent: cell, type: 'text',
              spellcheck: false,
              maxlength: inputMaxLengths[formatName] });
          inputs[formatName].mirror = output;
          output.className += ' raw';
          cell.style.minWidth = cellMinWidths[formatName] + 'px';
        }
        if (row.className.indexOf('retrospective') != -1) {
          button = make('button', { innerHTML: 'scores', parent: cell });
          enableScoreButton(button, formatName, approach);
          output.button = button;
        }
      });
    }
    // Attach input handlers.
    formatNames.forEach(function (formatName) {
      enableInput(formatName);
    });
    // Make initial data.
    inputs.commatize.value = '129,00';
    inputs.commatize.setSelectionRange(4, 4);
    inputs.commatize.click();
    inputs.trimify.value = '    \'Twas   brillig,  and  ';
    inputs.trimify.setSelectionRange(10, 10);
    inputs.trimify.click();
    outputs.commatize.frequencyRatios.button.click();
    outputs.trimify.frequencyRatios.button.click();

    // Add expander to notes (collapsed by default).
    NoteExpander.enableByTagAndClass(document, 'div', 'notes');
  }

  return {
    load: load
  };
})();
onload = CursorMaintenanceComparison.load;
<commit_msg>Listen for selectionchange in active input only<commit_after>var CursorMaintenanceComparison = (function () {
  'use strict';

  // requires: cursor_maintenance_examples.js
  //           dom_helpers.js
  //           note_expander.js

  // CursorMaintenanceComparison powers a web page that displays a table
  //  of results from several cursor-maintenance implementations applied
  //  in parallel to a formatting instance. There is one formatting
  //  instance for each of two formats, commatize and trimify. A table
  //  column is dedicated to each format. The various cursor-maintenance
  //  approaches correspond to rows. Each formatting instance can be
  //  edited by the user, and the results are immediately updated to
  //  reflect changes in the raw text or cursor position.

  var CME = CursorMaintenanceExamples,
      make = DOMHelpers.make,
      classRemove = DOMHelpers.classRemove,
      inputMaxLengths = {  // Maximum number of input characters for each
        commatize: 15,     //  format. These values are used to set the
        trimify: 60        //  maxlength attribute of the input element.
      },
      cellMinWidths = {
        commatize: 200,
        trimify: 600
      },
      inputs = {},
      outputs = {},
      activeButtons = {},
      activeInput,
      scores = {};

  // setOutput displays a cursor-maintenance result in an output element.
  function setOutput(element, text, cursor) {
    // The range test evaluates to false when cursor is null or undefined.
    if (!(cursor >= 0 && cursor <= text.length)) {
      element.innerHTML = text;
      return;
    }
    // Indicate the cursor position with a specially styled span. The start
    //  span makes the cursor span visible at position 0.
    element.innerHTML = '' +
        text.substring(0, cursor) + '' +
        text.substring(cursor);
  }

  // enableInputHandling builds a handler for events in a given format's input
  //  element. The input element is overlaid with an output box that contains
  //  a graphical cursor. When the input element has focus, the output box
  //  is active, giving it special styling with a blinking cursor. In addition
  //  to managing the active output box, we have to format the text with the
  //  plain formatter and with all the cursor-maintaining formatters, and
  //  display the results in the table.
  function enableInputHandling(formatName) {
    var input = inputs[formatName],
        formatOutputs = outputs[formatName],
        formatter = CME.format[formatName];  // This is the plain formatter.
    // update responds to any event that can potentially change the text.
    function update(event) {
      var rawText = input.value,
          rawCursor = input.selectionStart,
          formattedText = formatter(rawText).text;  // Plain formatting.
      // Display the formatted text without a cursor.
      setOutput(formatOutputs.after, formattedText);
      // Mirror the raw text in the output box directly over the input.
      setOutput(formatOutputs.before, rawText, rawCursor);
      if (activeInput != input) {
        if (activeInput) {
          // Deactivate the previously active input.
          classRemove(activeInput.mirror, 'active');
          document.removeEventListener('selectionchange', update);
        }
        // Activate the current input.
        activeInput = input;
        input.mirror.className += ' active';
        // To detect input cursor movement in WebKit browsers on iOS, listen
        //  to selectionchange events on the document.
        //  Hat tip: http://stackoverflow.com/questions/23544937/
        document.addEventListener('selectionchange', update);
      }
      // Run every cursor-maintaining formatter and display the results.
      Object.keys(formatOutputs).forEach(function (approach) {
        var result;
        if (approach in CME) {
          result = CME[approach][formatName](rawText, rawCursor);
          setOutput(formatOutputs[approach], result.text, result.cursor);
        }
      });
      // Update the score display for the retrospective approach whose
      //  score button is currently active.
      if (activeButtons[formatName]) {
        activeButtons[formatName].click();
      }
    }
    // When the input loses focus, deactivate its output mirror and set
    //  activeInput to null. Can we assume that we aren't corrupting the value
    //  of activeInput? Yes, because if the other input has been clicked or
    //  tapped, the browser sends the blur event to this input before sending
    //  the focus event to the other input. References:
    //   https://w3c.github.io/uievents/#events-focusevent
    //   http://stackoverflow.com/questions/20340091/
    //  In applications that don't have a global activeInput variable, we
    //  can ignore blur and just listen to focus or click.
    input.onblur = function () {
      activeInput = null;
      classRemove(input.mirror, 'active');
      document.removeEventListener('selectionchange', update);
    };
    // Listen for events that can change the text or move the cursor.
    [ 'change', 'keydown', 'keyup', 'focus' ].forEach(function (eventName) {
      input.addEventListener(eventName, update);
    });
  }

  // enableScoreButton builds a click handler for a score button that
  //  was made for a particular format and cursor-maintenance approach,
  //  as named by arguments. The format and approach names are used to
  //  look up the appropriate cursor-maintenance method and DOM elements.
  //  The response to a click is to generate a list of scores calculated
  //  for every cursor position by a retrospective cost function, and to
  //  display these in the score area at the bottom of the table column.
  function enableScoreButton(button, formatName, approachName) {
    var maintainer = CME[approachName][formatName],
        scoreArea = scores[formatName],
        input = inputs[formatName];
    button.onclick = function () {
      var text = input.value,
          cursor = input.selectionStart,
          result = maintainer(text, cursor),
          activeButton = activeButtons[formatName],
          i, item, output, score;
      scoreArea.innerHTML = '';
      for (i = 0; i < result.scores.length; ++i) {
        // Make a separate div for each cursor position.
        item = make('div', { parent: scoreArea, className: 'scoreItem' });
        if (i == result.cursor) {
          item.className += ' best';
        }
        output = make('span', { className: 'output', parent: item });
        setOutput(output, result.text, i);
        output.style.width = '250px';
        score = ' ' + result.scores[i];
        // Whole values don't get a decimal point. Display fractional values
        //  with up to six decimal digits.
        if (score.indexOf('.') != -1) {
          score = score.substring(0, score.indexOf('.') + 6);
        }
        make('span', { parent: item, className: 'score', innerHTML: score });
      }
      // The currently active score button gets special styling. When a
      //  different button is activated, alter the class name of the
      //  previously active button to remove the styling.
      if (activeButton && activeButton != button) {
        classRemove(activeButton, 'active');
      }
      activeButtons[formatName] = button;
      button.className += ' active';
    };
  }

  // makeApproachName takes label text extracted from the HTML table and
  //  and transforms it into the name of a member of the cursor-maintenance
  //  module. Examples:
  // "ad hoc" -> "adHoc" (a cursor-maintenance implementation)
  // "frequency ratios" -> "frequencyRatios"  (a retrospective cost function)
  function makeApproachName(s) {
    var parts = s.replace(/^\s+|\s+$/g, '').split(/\s+/),
        i, name;
    for (i = 1; i < parts.length; ++i) {
      parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
    }
    name = parts.join('');
    return name;
  }

  // load fills out a table that is initially defined in the static HTML
  //  in skeletal form, with single-cell rows that each contain the name
  //  of a cursor-maintenance approach. The names are extracted from the
  //  HTML and used to build result fields. Score buttons are built for
  //  rows that have the word "retrospective" in their class name. Input
  //  areas are built at the top of each column, and score displays are
  //  built at the bottom. The format names and approach names in the HTML
  //  are used to look up functions in the cursor-maintenance module.
  function load() {
    var table = document.getElementById('comparisons'),
        rows = table.getElementsByTagName('tr'),
        inputRow = document.getElementById('inputs'),
        scoreRow = document.getElementById('scores'),
        formatNames = [ 'commatize', 'trimify' ],
        i, row, cells,
        approach;
    formatNames.forEach(function (formatName) {
      outputs[formatName] = {};
      // Build the score displays in advance so that we'll be able to connect
      //  score buttons to displays as we traverse the table.
      scores[formatName] = make('div', { className: 'scoreList', parent:
          make('td', { parent: scoreRow }) });
    });
    // Traverse all rows in one shot, filling out cells as we go. Use
    //  class names to decide what to build for each row.
    for (i = 0; i < rows.length; ++i) {
      row = rows[i];
      // Don't do anything for table headers and score displays.
      if (row.className.indexOf('outputs') == -1) {
        continue;
      }
      // Extract the approach name from the first cell in the row.
      cells = row.getElementsByTagName('td');
      approach = makeApproachName(cells[0].innerHTML);
      // Build cells for each format. Most cells only get an output field.
      //  The top row, labeled "before", also gets an input field that
      //  hides behind the output field (which will contain a fancy graphical
      //  cursor). Retrospective result rows get a score button next to
      //  the output field.
      formatNames.forEach(function (formatName) {
        var button,
            cell = make('td', { parent: row }),
            output = outputs[formatName][approach] = make('span',
                { parent: cell, className: 'output' });
        if (approach == 'before') {
          inputs[formatName] = make('input', { parent: cell, type: 'text',
              spellcheck: false,
              maxlength: inputMaxLengths[formatName] });
          inputs[formatName].mirror = output;
          output.className += ' raw';
          cell.style.minWidth = cellMinWidths[formatName] + 'px';
        }
        if (row.className.indexOf('retrospective') != -1) {
          button = make('button', { innerHTML: 'scores', parent: cell });
          enableScoreButton(button, formatName, approach);
          output.button = button;
        }
      });
    }
    // Attach input handlers.
    formatNames.forEach(function (formatName) {
      enableInputHandling(formatName);
    });
    // Make initial data.
    inputs.commatize.value = '129,00';
    inputs.commatize.setSelectionRange(4, 4);
    inputs.commatize.focus();
    inputs.trimify.value = '    \'Twas   brillig,  and  ';
    inputs.trimify.setSelectionRange(10, 10);
    inputs.trimify.focus();
    outputs.commatize.frequencyRatios.button.click();
    outputs.trimify.frequencyRatios.button.click();

    // Add expander to notes (collapsed by default).
    NoteExpander.enableByTagAndClass(document, 'div', 'notes');
  }

  return {
    load: load
  };
})();
onload = CursorMaintenanceComparison.load;
<|endoftext|>


<commit_before>import React from 'react';
import {Card} from 'semantic-ui-react';;
import { NavLink } from 'fluxible-router';
import { Grid, Divider, Button, Header, Image, Icon, Item, Label, Menu, Segment, Container } from 'semantic-ui-react';

import { connectToStores } from 'fluxible-addons-react';
import DeckPageStore from '../../stores/DeckPageStore';
import DeckViewStore from '../../stores/DeckViewStore';
import ContentLikeStore from '../../stores/ContentLikeStore';
import ContentModulesStore from '../../stores/ContentModulesStore';
import TranslationStore from '../../stores/TranslationStore';
import SimilarContentStore from '../../stores/SimilarContentStore';

import CustomDate from './util/CustomDate';
import {getLanguageDisplayName, getLanguageName, isEmpty} from '../../common';
import { flagForLocale } from '../../configs/locales';
import { Microservices } from '../../configs/microservices';

import CCBYSA from '../common/CC-BY-SA';
import ReportModal from '../Report/ReportModal';
import TagList from './ContentModulesPanel/TagsPanel/TagList';
import PresentationsPanel from './InfoPanel/PresentationsPanel';
import ActivityFeedPanel from './ActivityFeedPanel/ActivityFeedPanel';

import { getEducationLevel } from '../../lib/isced';
import lodash from 'lodash';

class DeckLandingPage extends React.Component {

    getPresentationHref() {
        let selector = this.props.DeckPageStore.selector;
        let presentationUrlParts = ['/presentation', selector.id, this.props.DeckPageStore.deckSlug || '_'];

        if (selector.spath.search(';') !== -1) {
            // if a subdeck is selected - use its selector
            presentationUrlParts.push(selector.spath.substring(0, selector.spath.search(';')));
        } else {
            // if it is the main/root deck - use that id
            presentationUrlParts.push(selector.id);
        }

        if (selector.stype === 'slide'){
            // if it is a slide, also add ID of slide
            presentationUrlParts.push(selector.sid);
        }

        let presLocation = presentationUrlParts.join('/');

        if (this.props.TranslationStore.currentLang) {
            presLocation += '?language=' + (this.props.TranslationStore.currentLang);
        }

        return presLocation;
    }

    render() {
        let deckData = this.props.DeckViewStore.deckData;

        let firstSlide = (this.props.DeckViewStore.slidesData && this.props.DeckViewStore.slidesData.children && this.props.DeckViewStore.slidesData.children[0]);
        const totalSlides = lodash.get(this.props.DeckViewStore.slidesData, 'children.length', undefined);

        let deckThumbURL = firstSlide && `${Microservices.file.uri}/thumbnail/slide/${firstSlide.id}`;
        if (deckThumbURL && firstSlide.theme) {
            deckThumbURL += '/' + firstSlide.theme;
        }
        let deckThumbAlt = firstSlide && (firstSlide.title ? firstSlide.title + ' | ' + firstSlide.id : firstSlide.id);

        let deckSlug = this.props.DeckPageStore.deckSlug || '_';
        let selector = this.props.DeckPageStore.selector;
        let openDeckUrl = ['', 'deck', selector.id , deckSlug, 'deck', selector.id].join('/');
        let presentationUrl = this.getPresentationHref();

        let deckTags = deckData.tags || [];
        let deckTopics = deckData.topics || [];

        if(!deckData.variants)
            deckData.variants = [];

        let deckLanguages = [this.props.TranslationStore.treeLanguage, ...this.props.TranslationStore.treeTranslations];

        let owner = this.props.DeckViewStore.ownerData;
        let creator = this.props.DeckViewStore.creatorData;

        let originInfo = null;
        let originCreator = this.props.DeckViewStore.originCreatorData;
        if (deckData.origin) {
            originInfo = (

                    Origin:
                    <NavLink href={['/deck', deckData.origin.id + '-' + deckData.origin.revision, deckData.origin.slug].join('/')}>{deckData.origin.title}</NavLink>
                    {originCreator ? ' by ' : ''}
                    {originCreator && {originCreator.displayName || originCreator.username}}
                    {/* TODO check if this URL is working with languages! */}

            );
        }

        const ColPadding = {
            paddingLeft: '0px'
        };

        let interestedInDecks = 'No decks to show';
        if (this.props.SimilarContentStore.contents && this.props.SimilarContentStore.contents.length >= 1) {
            interestedInDecks =  this.props.SimilarContentStore.contents.map((deck, i) => {
                return <Grid.Column key={i} width={5}>

                        <NavLink href={`/deck/${deck.deckId}`}>

                                    <img src={`${Microservices.file.uri}/thumbnail/slide/${deck.firstSlideId}`}  aria-hidden="true" tabIndex="-1" alt=' ' />

                                    {deck.title}

                        </NavLink>

                </Grid.Column>;
            });
            interestedInDecks = <Grid stackable> {interestedInDecks} </Grid>;
        }

        return (

                <Container fluid>
                    <Divider hidden/>
                    <Grid divided='vertically' stackable>
                        <Grid.Column only="tablet computer" tablet={1} computer={2}>
                        </Grid.Column>

                        <Grid.Column mobile={16} tablet={14} computer={12}>
                            <Grid.Row>
                                <Segment>
                                    <Grid stackable>
                                        <Grid.Column width={4}>
                                            <NavLink className="image" aria-hidden tabIndex='-1' href={openDeckUrl}>
                                                <Image src={deckThumbURL} alt={deckThumbAlt}
                                                       size='large' bordered spaced />
                                            </NavLink>
                                        </Grid.Column>
                                        <Grid.Column width={12}>

Deck title:

                                                    <NavLink href={openDeckUrl}>{deckData.title}</NavLink>

Deck status:

                                                    {(!deckData.hidden) ? Published : Unlisted}

                                            <Divider hidden />

Creator: <NavLink href={'/user/' + creator.username}>{creator.displayName || creator.username}</NavLink>

                                                            {originInfo}

Last Modified: {CustomDate.format(deckData.lastUpdate, 'Do MMMM YYYY')}

"Deck metadata"

                                                                    {getLanguageDisplayName(deckData.language)}

                                                                    {totalSlides}

                                                                { deckData.educationLevel &&

                                                                    {getEducationLevel(deckData.educationLevel)}

                                                                }

                                                                    {deckData.forkCount}

                                                                    {this.props.ContentLikeStore.usersWhoLikedDeck.length}

                                                                    {deckData.shareCount}

                                                                    {deckData.downloadCount}

Description:

{deckData.description}

                                                    { deckTopics.length > 0 &&

Subject:

{ deckTopics.map((t, i) =>

                                                              { !!i && ',\xa0' }
                                                                <a target="_blank" href={`/deckfamily/${t.tagName}`}>{t.defaultName || t.tagName}

                                                        ) }

                                                    }

                                        </Grid.Column>
                                    </Grid>
                                </Segment>
                            </Grid.Row>
                            <Grid.Row>

                                            <NavLink href={openDeckUrl} tabIndex={-1} >
                                                <Button icon size="huge" aria-label="Open Deck" data-tooltip="Open Deck" role="button">
                                                    <Icon name="open folder" />
                                                </Button>
                                            </NavLink>

                                                <Button icon size="huge" aria-label="Open slideshow in new tab" data-tooltip="Open Slideshow" role="button" >
                                                    <Icon name="play circle" />
                                                </Button>

                                            <PresentationsPanel deckPage={true} />

                            </Grid.Row>

                            <Divider hidden />

                            <Grid divided='vertically' stackable>
                                <Grid.Column only="tablet computer" width={12}>
                                    <Segment attached='top' >
                                        Available in the following languages:
                                        { deckLanguages.map((lang, i) =>

                                                {!!i && ',\xa0'}
                                                <NavLink href={'/deck/' + deckData._id + '-' + deckData.revision + '?language=' + lang}>

                                                    { getLanguageDisplayName(lang) }
                                                </NavLink>

                                        ) }
                                    </Segment>
                                    <Segment attached>
                                        Tags:
                                        {(deckTags.length === 0) ?
There are no tags assigned to this deck.
 : <TagList items={deckTags} editable={false}/>}
                                    </Segment>
                                    <Segment attached='bottom'>
                                        You may also be interested in:
                                        {interestedInDecks}
                                    </Segment
                                </Grid.Column>
                                <Grid.Column only="tablet computer" width={4}>
                                    <Segment>
                                        <ActivityFeedPanel /></Segment>
                                    <Segment attached='bottom'>

                                            <CCBYSA size='small' />

                                        This work is licensed under Creative Commons Attribution-ShareAlike 4.0 International License
                                    </Segment>
                                </Grid.Column>
                            </Grid>

                        </Grid.Column>

                        <Grid.Column only="tablet computer" tablet={1} computer={2}>
                        </Grid.Column>

                    </Grid>
                </Container>

        );
    }
}

DeckLandingPage = connectToStores(DeckLandingPage, [ContentLikeStore, DeckPageStore, DeckViewStore, TranslationStore, ContentModulesStore, SimilarContentStore], (context, props) => {
    return {
        ContentLikeStore: context.getStore(ContentLikeStore).getState(),
        DeckPageStore: context.getStore(DeckPageStore).getState(),
        DeckViewStore: context.getStore(DeckViewStore).getState(),
        TranslationStore: context.getStore(TranslationStore).getState(),
        ContentModulesStore: context.getStore(ContentModulesStore).getState(),
        SimilarContentStore: context.getStore(SimilarContentStore).getState(),
    };
});

export default DeckLandingPage;
<commit_msg>Fix typo<commit_after>import React from 'react';
import {Card} from 'semantic-ui-react';;
import { NavLink } from 'fluxible-router';
import { Grid, Divider, Button, Header, Image, Icon, Item, Label, Menu, Segment, Container } from 'semantic-ui-react';

import { connectToStores } from 'fluxible-addons-react';
import DeckPageStore from '../../stores/DeckPageStore';
import DeckViewStore from '../../stores/DeckViewStore';
import ContentLikeStore from '../../stores/ContentLikeStore';
import ContentModulesStore from '../../stores/ContentModulesStore';
import TranslationStore from '../../stores/TranslationStore';
import SimilarContentStore from '../../stores/SimilarContentStore';

import CustomDate from './util/CustomDate';
import {getLanguageDisplayName, getLanguageName, isEmpty} from '../../common';
import { flagForLocale } from '../../configs/locales';
import { Microservices } from '../../configs/microservices';

import CCBYSA from '../common/CC-BY-SA';
import ReportModal from '../Report/ReportModal';
import TagList from './ContentModulesPanel/TagsPanel/TagList';
import PresentationsPanel from './InfoPanel/PresentationsPanel';
import ActivityFeedPanel from './ActivityFeedPanel/ActivityFeedPanel';

import { getEducationLevel } from '../../lib/isced';
import lodash from 'lodash';

class DeckLandingPage extends React.Component {

    getPresentationHref() {
        let selector = this.props.DeckPageStore.selector;
        let presentationUrlParts = ['/presentation', selector.id, this.props.DeckPageStore.deckSlug || '_'];

        if (selector.spath.search(';') !== -1) {
            // if a subdeck is selected - use its selector
            presentationUrlParts.push(selector.spath.substring(0, selector.spath.search(';')));
        } else {
            // if it is the main/root deck - use that id
            presentationUrlParts.push(selector.id);
        }

        if (selector.stype === 'slide'){
            // if it is a slide, also add ID of slide
            presentationUrlParts.push(selector.sid);
        }

        let presLocation = presentationUrlParts.join('/');

        if (this.props.TranslationStore.currentLang) {
            presLocation += '?language=' + (this.props.TranslationStore.currentLang);
        }

        return presLocation;
    }

    render() {
        let deckData = this.props.DeckViewStore.deckData;

        let firstSlide = (this.props.DeckViewStore.slidesData && this.props.DeckViewStore.slidesData.children && this.props.DeckViewStore.slidesData.children[0]);
        const totalSlides = lodash.get(this.props.DeckViewStore.slidesData, 'children.length', undefined);

        let deckThumbURL = firstSlide && `${Microservices.file.uri}/thumbnail/slide/${firstSlide.id}`;
        if (deckThumbURL && firstSlide.theme) {
            deckThumbURL += '/' + firstSlide.theme;
        }
        let deckThumbAlt = firstSlide && (firstSlide.title ? firstSlide.title + ' | ' + firstSlide.id : firstSlide.id);

        let deckSlug = this.props.DeckPageStore.deckSlug || '_';
        let selector = this.props.DeckPageStore.selector;
        let openDeckUrl = ['', 'deck', selector.id , deckSlug, 'deck', selector.id].join('/');
        let presentationUrl = this.getPresentationHref();

        let deckTags = deckData.tags || [];
        let deckTopics = deckData.topics || [];

        if(!deckData.variants)
            deckData.variants = [];

        let deckLanguages = [this.props.TranslationStore.treeLanguage, ...this.props.TranslationStore.treeTranslations];

        let owner = this.props.DeckViewStore.ownerData;
        let creator = this.props.DeckViewStore.creatorData;

        let originInfo = null;
        let originCreator = this.props.DeckViewStore.originCreatorData;
        if (deckData.origin) {
            originInfo = (

                    Origin:
                    <NavLink href={['/deck', deckData.origin.id + '-' + deckData.origin.revision, deckData.origin.slug].join('/')}>{deckData.origin.title}</NavLink>
                    {originCreator ? ' by ' : ''}
                    {originCreator && {originCreator.displayName || originCreator.username}}
                    {/* TODO check if this URL is working with languages! */}

            );
        }

        const ColPadding = {
            paddingLeft: '0px'
        };

        let interestedInDecks = 'No decks to show';
        if (this.props.SimilarContentStore.contents && this.props.SimilarContentStore.contents.length >= 1) {
            interestedInDecks =  this.props.SimilarContentStore.contents.map((deck, i) => {
                return <Grid.Column key={i} width={5}>

                        <NavLink href={`/deck/${deck.deckId}`}>

                                    <img src={`${Microservices.file.uri}/thumbnail/slide/${deck.firstSlideId}`}  aria-hidden="true" tabIndex="-1" alt=' ' />

                                    {deck.title}

                        </NavLink>

                </Grid.Column>;
            });
            interestedInDecks = <Grid stackable> {interestedInDecks} </Grid>;
        }

        return (

                <Container fluid>
                    <Divider hidden/>
                    <Grid divided='vertically' stackable>
                        <Grid.Column only="tablet computer" tablet={1} computer={2}>
                        </Grid.Column>

                        <Grid.Column mobile={16} tablet={14} computer={12}>
                            <Grid.Row>
                                <Segment>
                                    <Grid stackable>
                                        <Grid.Column width={4}>
                                            <NavLink className="image" aria-hidden tabIndex='-1' href={openDeckUrl}>
                                                <Image src={deckThumbURL} alt={deckThumbAlt}
                                                       size='large' bordered spaced />
                                            </NavLink>
                                        </Grid.Column>
                                        <Grid.Column width={12}>

Deck title:

                                                    <NavLink href={openDeckUrl}>{deckData.title}</NavLink>

Deck status:

                                                    {(!deckData.hidden) ? Published : Unlisted}

                                            <Divider hidden />

Creator: <NavLink href={'/user/' + creator.username}>{creator.displayName || creator.username}</NavLink>

                                                            {originInfo}

Last Modified: {CustomDate.format(deckData.lastUpdate, 'Do MMMM YYYY')}

"Deck metadata"

                                                                    {getLanguageDisplayName(deckData.language)}

                                                                    {totalSlides}

                                                                { deckData.educationLevel &&

                                                                    {getEducationLevel(deckData.educationLevel)}

                                                                }

                                                                    {deckData.forkCount}

                                                                    {this.props.ContentLikeStore.usersWhoLikedDeck.length}

                                                                    {deckData.shareCount}

                                                                    {deckData.downloadCount}

Description:

{deckData.description}

                                                    { deckTopics.length > 0 &&

Subject:

{ deckTopics.map((t, i) =>

                                                              { !!i && ',\xa0' }
                                                                <a target="_blank" href={`/deckfamily/${t.tagName}`}>{t.defaultName || t.tagName}

                                                        ) }

                                                    }

                                        </Grid.Column>
                                    </Grid>
                                </Segment>
                            </Grid.Row>
                            <Grid.Row>

                                            <NavLink href={openDeckUrl} tabIndex={-1} >
                                                <Button icon size="huge" aria-label="Open Deck" data-tooltip="Open Deck" role="button">
                                                    <Icon name="open folder" />
                                                </Button>
                                            </NavLink>

                                                <Button icon size="huge" aria-label="Open slideshow in new tab" data-tooltip="Open Slideshow" role="button" >
                                                    <Icon name="play circle" />
                                                </Button>

                                            <PresentationsPanel deckPage={true} />

                            </Grid.Row>

                            <Divider hidden />

                            <Grid divided='vertically' stackable>
                                <Grid.Column only="tablet computer" width={12}>
                                    <Segment attached='top' >
                                        Available in the following languages:
                                        { deckLanguages.map((lang, i) =>

                                                {!!i && ',\xa0'}
                                                <NavLink href={'/deck/' + deckData._id + '-' + deckData.revision + '?language=' + lang}>

                                                    { getLanguageDisplayName(lang) }
                                                </NavLink>

                                        ) }
                                    </Segment>
                                    <Segment attached>
                                        Tags:
                                        {(deckTags.length === 0) ?
There are no tags assigned to this deck.
 : <TagList items={deckTags} editable={false}/>}
                                    </Segment>
                                    <Segment attached='bottom'>
                                        You may also be interested in:
                                        {interestedInDecks}
                                    </Segment>
                                </Grid.Column>
                                <Grid.Column only="tablet computer" width={4}>
                                    <Segment>
                                        <ActivityFeedPanel /></Segment>
                                    <Segment attached='bottom'>

                                            <CCBYSA size='small' />

                                        This work is licensed under Creative Commons Attribution-ShareAlike 4.0 International License
                                    </Segment>
                                </Grid.Column>
                            </Grid>

                        </Grid.Column>

                        <Grid.Column only="tablet computer" tablet={1} computer={2}>
                        </Grid.Column>

                    </Grid>
                </Container>

        );
    }
}

DeckLandingPage = connectToStores(DeckLandingPage, [ContentLikeStore, DeckPageStore, DeckViewStore, TranslationStore, ContentModulesStore, SimilarContentStore], (context, props) => {
    return {
        ContentLikeStore: context.getStore(ContentLikeStore).getState(),
        DeckPageStore: context.getStore(DeckPageStore).getState(),
        DeckViewStore: context.getStore(DeckViewStore).getState(),
        TranslationStore: context.getStore(TranslationStore).getState(),
        ContentModulesStore: context.getStore(ContentModulesStore).getState(),
        SimilarContentStore: context.getStore(SimilarContentStore).getState(),
    };
});

export default DeckLandingPage;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/alpaka/01.jpg'),
  require('../../images/animals/alpaka/02.jpg'),
  require('../../images/animals/alpaka/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/alpaka/01-thumb.jpg'),
  require('../../images/animals/alpaka/02-thumb.jpg'),
  require('../../images/animals/alpaka/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Jestli někdy pojedete do Jižní Ameriky, zkuste navštívit Peru. Pozor, to není název tamní prádelny! Jmenuje se tak celý jeden stát. Když si na dlouhé cestě konečně vyperete svoje smradlavé a zašpiněné peruánské ponožky, místní obyvatelé podle jejich barvy poznají, jakou srst mají lamy alpaky – zvířecí obyvatelky Peru a okolí. Dobří ponožkoví detektivové poznají, že alpaka může být:
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>BÍLÁ jako ovce</Text>
        </AnimalText>
        <AnimalText>
          Alpaku si Peruánci nevycvičili pro těžkou práci. Slouží jim podobně jako ovce u nás v Evropě – lidé ji jednou za čas ostříhají, čímž získají spoustu vlny. Pletou z ní nejen ponožky, ale i svetry nebo čepice.
        </AnimalText>
      <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          <Text style={styles.strong}>BÉŽOVÁ jako velbloud</Text>
        </AnimalText>
        <AnimalText>
          Věřte nebo ne, lama alpaka je blízká příbuzná velbloudů. Je jen menší, nemá hrby a díky tvrdším kopýtkům se lépe pohybuje ve skalách.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>HNĚDÁ jako planina</Text>
        </AnimalText>
        <AnimalText>
          Lidé si před mnoha tisíci lety ochočili alpaky v oblasti náhorní plošiny, a proto jsou zvyklé žít v drsných podmínkách. Dobře snáší velké rozdíly teplot a nažerou se i z pár trsů trávy. Zvláštní je na nich také to, že jim vepředu tlamy chybí horní zuby. Nezbývá jim nic jiného, než uřezávat rostliny zuby spodními.
        </AnimalText>
      <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          <Text style={styles.strong}>ČERNÁ jako nebezpečí</Text>
        </AnimalText>
        <AnimalText>
          Alpaky se pasou ve stádech a jsou u toho hodně ostražité. Blížící se nebezpečí dokážou zpozorovat na stovky metrů. Vedoucí samec si svého stáda natolik váží, že je ochotný nasadit vlastní život, aby ostatní zachránil.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>STRAKATÁ jako překvapení</Text>
        </AnimalText>
        <AnimalText>
          Alpakám se rodí zpravidla jedno mládě ročně. Jeho rodiče dopředu nevědí, jakou barvu bude mít. Občas se dokonce narodí mládě vícebarevné.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          Peruánci dokážou rozeznat ne pět, ale přes dvacet odstínů lamí srsti, a tak se jejich tržiště s oblečením hemží barvami. Až odtud budete odjíždět, nezapomeňte si koupit nějaký hřejivý suvenýr. Třeba dvacet párů ponožek, ať máte zásobu pro příští výlet a nemusíte tolik prát.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<commit_msg>Update alpaka.child.js<commit_after>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/alpaka/01.jpg'),
  require('../../images/animals/alpaka/02.jpg'),
  require('../../images/animals/alpaka/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/alpaka/01-thumb.jpg'),
  require('../../images/animals/alpaka/02-thumb.jpg'),
  require('../../images/animals/alpaka/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Jestli někdy pojedete do Jižní Ameriky, zkuste navštívit Peru. Pozor, to není název tamní prádelny! Jmenuje se tak celý jeden stát. Když si na dlouhé cestě konečně vyperete svoje smradlavé a zašpiněné peruánské ponožky, místní obyvatelé podle jejich barvy poznají, jakou srst měly lamy alpaky, zvířecí obyvatelky Peru a okolí, díky nimž mohly být tyto ponožky vyrobeny. Dobří ponožkoví detektivové poznají, že alpaka může být:
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>BÍLÁ jako ovce</Text>
        </AnimalText>
        <AnimalText>
          Alpaku si Peruánci nevycvičili pro těžkou práci. Slouží jim podobně jako ovce u nás v Evropě – lidé ji jednou za čas ostříhají, čímž získají spoustu vlny. Pletou z ní nejen ponožky, ale i svetry nebo čepice.
        </AnimalText>
      <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
      <AnimalText>
          <Text style={styles.strong}>BÉŽOVÁ jako velbloud</Text>
        </AnimalText>
        <AnimalText>
          Věřte nebo ne, lama alpaka je blízká příbuzná velbloudů. Je jen menší, nemá hrby a díky tvrdším kopýtkům se lépe pohybuje ve skalách.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>HNĚDÁ jako planina</Text>
        </AnimalText>
        <AnimalText>
          Lidé si před mnoha tisíci lety ochočili alpaky v oblasti náhorní plošiny, a proto jsou zvyklé žít v drsných podmínkách. Dobře snáší velké rozdíly teplot a nažerou se i z pár trsů trávy. Zvláštní je na nich také to, že jim vepředu tlamy chybí horní zuby. Nezbývá jim nic jiného, než uřezávat rostliny zuby spodními.
        </AnimalText>
      <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          <Text style={styles.strong}>ČERNÁ jako nebezpečí</Text>
        </AnimalText>
        <AnimalText>
          Alpaky se pasou ve stádech a jsou u toho hodně ostražité. Blížící se nebezpečí dokážou zpozorovat na stovky metrů. Vedoucí samec si svého stáda natolik váží, že je ochotný nasadit vlastní život, aby ostatní zachránil.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          <Text style={styles.strong}>STRAKATÁ jako překvapení</Text>
        </AnimalText>
        <AnimalText>
          Alpakám se rodí zpravidla jedno mládě ročně. Jeho rodiče dopředu nevědí, jakou barvu bude mít. Občas se dokonce narodí mládě vícebarevné.
        </AnimalText>
        <AnimalText>

        </AnimalText>
        <AnimalText>
          Peruánci dokážou rozeznat ne pět, ale přes dvacet odstínů lamí srsti, a tak se jejich tržiště s oblečením hemží barvami. Až odtud budete odjíždět, nezapomeňte si koupit nějaký hřejivý suvenýr. Třeba dvacet párů ponožek, ať máte zásobu pro příští výlet a nemusíte tolik prát.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Button, Icon, Table } from 'semantic-ui-react';
import Metric from './Metric.js';
import { SubjectTitle } from './SubjectTitle.js';

class Subject extends Component {
  constructor(props) {
    super(props);
    this.state = { sort_column: null, sort_direction: null, last_measurements: {} };
  }

  onAddMetric(event) {
    event.preventDefault();
    const self = this;
    fetch(`${window.server_url}/report/${this.props.report.report_uuid}/subject/${this.props.subject_uuid}/metric/new`, {
      method: 'post',
      mode: 'cors',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({})
    }).then(
      () => self.props.reload()
    );
  }

  handle_sort(event, column) {
    event.preventDefault();
    const sort_direction = this.state.sort_direction;
    this.setState(
      {
        sort_column: column,
        sort_direction: sort_direction === 'ascending' ? 'descending' : 'ascending'
      })
  }

  set_last_measurement(metric_uuid, last_measurement) {
    this.setState(prevState => (
      { last_measurements: { ...prevState.last_measurements, [metric_uuid]: last_measurement } }
    ));
  }

  render() {
    const subject = this.props.report.subjects[this.props.subject_uuid];
    const { sort_column, sort_direction } = this.state;
    let metric_components = [];
    Object.entries(subject.metrics).forEach(([metric_uuid, metric]) =>
      metric_components.push(
        <Metric
          datamodel={this.props.datamodel}
          key={metric_uuid}
          metric_uuid={metric_uuid}
          metric={metric}
          nr_new_measurements={this.props.nr_new_measurements}
          readOnly={this.props.readOnly}
          reload={this.props.reload}
          report={this.props.report}
          report_date={this.props.report_date}
          search_string={this.props.search_string}
          set_last_measurement={(m, l) => this.set_last_measurement(m, l)}
          subject_uuid={this.props.subject_uuid}
        />)
    );
    if (sort_column !== null) {
      let self = this;
      const status_order = { "": "0", target_not_met: "1", debt_target_met: "2", near_target_met: "3", target_met: "4" };
      const sorters = {
        name: function(m1, m2) {
          const attribute1 = m1.props.metric.name || self.props.datamodel.metrics[m1.props.metric.type].name;
          const attribute2 = m2.props.metric.name || self.props.datamodel.metrics[m2.props.metric.type].name;
          return attribute1.localeCompare(attribute2)
        },
        measurement: function(m1, m2) {
          const attribute1 = self.state.last_measurements[m1.props.metric_uuid] && self.state.last_measurements[m1.props.metric_uuid].value || '';
          const attribute2 = self.state.last_measurements[m2.props.metric_uuid] && self.state.last_measurements[m2.props.metric_uuid].value || '';
          return attribute1.localeCompare(attribute2)
        },
        target: function(m1, m2) {
          const attribute1 = m1.props.metric.accept_debt ? m1.props.metric.debt_target : m1.props.metric.target;
          const attribute2 = m2.props.metric.accept_debt ? m2.props.metric.debt_target : m2.props.metric.target;
          return attribute1.localeCompare(attribute2)
        },
        comment: function(m1, m2) {
          const attribute1 = m1.props.metric.comment || '';
          const attribute2 = m2.props.metric.comment || '';
          return attribute1.localeCompare(attribute2)
        },
        status: function(m1, m2) {
          const attribute1 = status_order[self.state.last_measurements[m1.props.metric_uuid] && self.state.last_measurements[m1.props.metric_uuid].status || ''];
          const attribute2 = status_order[self.state.last_measurements[m2.props.metric_uuid] && self.state.last_measurements[m2.props.metric_uuid].status || ''];
          return attribute1.localeCompare(attribute2)
        },
        source: function(m1, m2) {
          let m1_sources = Object.values(m1.props.metric.sources).map((source) => source.name || self.props.datamodel.sources[source.type].name);
          m1_sources.sort();
          let m2_sources = Object.values(m2.props.metric.sources).map((source) => source.name || self.props.datamodel.sources[source.type].name);
          m2_sources.sort();
          const attribute1 = m1_sources.length > 0 ? m1_sources[0] : '';
          const attribute2 = m2_sources.length > 0 ? m2_sources[0] : '';
          return attribute1.localeCompare(attribute2)
        },
        tags: function(m1, m2) {
          let m1_tags = m1.props.metric.tags;
          m1_tags.sort();
          let m2_tags = m2.props.metric.tags;
          m2_tags.sort();
          const attribute1 = m1_tags.length > 0 ? m1_tags[0] : '';
          const attribute2 = m2_tags.length > 0 ? m2_tags[0] : '';
          return attribute1.localeCompare(attribute2)
        }
      }
      metric_components.sort(sorters[sort_column]);
      if (sort_direction === 'descending') {
        metric_components.reverse()
      }
    }
    return (
      <>
        <SubjectTitle
          datamodel={this.props.datamodel}
          readOnly={this.props.readOnly}
          reload={this.props.reload}
          report_uuid={this.props.report.report_uuid}
          subject={subject}
          subject_uuid={this.props.subject_uuid}
        />
        <Table sortable>
          <Table.Header>
            <Table.Row>
              <Table.HeaderCell />
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'name')}
                sorted={sort_column === 'name' ? sort_direction : null}
              >
                Metric
              </Table.HeaderCell>
              <Table.HeaderCell width="2">Trend (7 days)</Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'status')}
                sorted={sort_column === 'status' ? sort_direction : null}
              >
                Status
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'measurement')}
                sorted={sort_column === 'measurement' ? sort_direction : null}
              >
                Measurement
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'target')}
                sorted={sort_column === 'target' ? sort_direction : null}
              >
                Target
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'source')}
                sorted={sort_column === 'source' ? sort_direction : null}
              >
                Source
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'comment')}
                sorted={sort_column === 'comment' ? sort_direction : null}
              >
                Comment</Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'tags')}
                sorted={sort_column === 'tags' ? sort_direction : null}
              >
                Tags
              </Table.HeaderCell>
            </Table.Row>
          </Table.Header>
          <Table.Body>{metric_components}</Table.Body>
          {!this.props.readOnly &&
            <Table.Footer>
              <Table.Row>
                <Table.HeaderCell colSpan='9'>
                  <Button floated='left' icon primary basic onClick={(e) => this.onAddMetric(e)}>
                    <Icon name='plus' /> Add metric
                </Button>
                </Table.HeaderCell>
              </Table.Row>
            </Table.Footer>}
        </Table>
      </>
    )
  }
}

export { Subject };
<commit_msg>Fix linter issues. (#332)<commit_after>import React, { Component } from 'react';
import { Button, Icon, Table } from 'semantic-ui-react';
import Metric from './Metric.js';
import { SubjectTitle } from './SubjectTitle.js';

class Subject extends Component {
  constructor(props) {
    super(props);
    this.state = { sort_column: null, sort_direction: null, last_measurements: {} };
  }

  onAddMetric(event) {
    event.preventDefault();
    const self = this;
    fetch(`${window.server_url}/report/${this.props.report.report_uuid}/subject/${this.props.subject_uuid}/metric/new`, {
      method: 'post',
      mode: 'cors',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({})
    }).then(
      () => self.props.reload()
    );
  }

  handle_sort(event, column) {
    event.preventDefault();
    const sort_direction = this.state.sort_direction;
    this.setState(
      {
        sort_column: column,
        sort_direction: sort_direction === 'ascending' ? 'descending' : 'ascending'
      })
  }

  set_last_measurement(metric_uuid, last_measurement) {
    this.setState(prevState => (
      { last_measurements: { ...prevState.last_measurements, [metric_uuid]: last_measurement } }
    ));
  }

  render() {
    const subject = this.props.report.subjects[this.props.subject_uuid];
    const { sort_column, sort_direction } = this.state;
    let metric_components = [];
    Object.entries(subject.metrics).forEach(([metric_uuid, metric]) =>
      metric_components.push(
        <Metric
          datamodel={this.props.datamodel}
          key={metric_uuid}
          metric_uuid={metric_uuid}
          metric={metric}
          nr_new_measurements={this.props.nr_new_measurements}
          readOnly={this.props.readOnly}
          reload={this.props.reload}
          report={this.props.report}
          report_date={this.props.report_date}
          search_string={this.props.search_string}
          set_last_measurement={(m, l) => this.set_last_measurement(m, l)}
          subject_uuid={this.props.subject_uuid}
        />)
    );
    if (sort_column !== null) {
      let self = this;
      const status_order = { "": "0", target_not_met: "1", debt_target_met: "2", near_target_met: "3", target_met: "4" };
      const sorters = {
        name: function(m1, m2) {
          const attribute1 = m1.props.metric.name || self.props.datamodel.metrics[m1.props.metric.type].name;
          const attribute2 = m2.props.metric.name || self.props.datamodel.metrics[m2.props.metric.type].name;
          return attribute1.localeCompare(attribute2)
        },
        measurement: function(m1, m2) {
          const attribute1 = (self.state.last_measurements[m1.props.metric_uuid] && self.state.last_measurements[m1.props.metric_uuid].value) || '';
          const attribute2 = (self.state.last_measurements[m2.props.metric_uuid] && self.state.last_measurements[m2.props.metric_uuid].value) || '';
          return attribute1.localeCompare(attribute2)
        },
        target: function(m1, m2) {
          const attribute1 = m1.props.metric.accept_debt ? m1.props.metric.debt_target : m1.props.metric.target;
          const attribute2 = m2.props.metric.accept_debt ? m2.props.metric.debt_target : m2.props.metric.target;
          return attribute1.localeCompare(attribute2)
        },
        comment: function(m1, m2) {
          const attribute1 = m1.props.metric.comment || '';
          const attribute2 = m2.props.metric.comment || '';
          return attribute1.localeCompare(attribute2)
        },
        status: function(m1, m2) {
          const attribute1 = status_order[(self.state.last_measurements[m1.props.metric_uuid] && self.state.last_measurements[m1.props.metric_uuid].status) || ''];
          const attribute2 = status_order[(self.state.last_measurements[m2.props.metric_uuid] && self.state.last_measurements[m2.props.metric_uuid].status) || ''];
          return attribute1.localeCompare(attribute2)
        },
        source: function(m1, m2) {
          let m1_sources = Object.values(m1.props.metric.sources).map((source) => source.name || self.props.datamodel.sources[source.type].name);
          m1_sources.sort();
          let m2_sources = Object.values(m2.props.metric.sources).map((source) => source.name || self.props.datamodel.sources[source.type].name);
          m2_sources.sort();
          const attribute1 = m1_sources.length > 0 ? m1_sources[0] : '';
          const attribute2 = m2_sources.length > 0 ? m2_sources[0] : '';
          return attribute1.localeCompare(attribute2)
        },
        tags: function(m1, m2) {
          let m1_tags = m1.props.metric.tags;
          m1_tags.sort();
          let m2_tags = m2.props.metric.tags;
          m2_tags.sort();
          const attribute1 = m1_tags.length > 0 ? m1_tags[0] : '';
          const attribute2 = m2_tags.length > 0 ? m2_tags[0] : '';
          return attribute1.localeCompare(attribute2)
        }
      }
      metric_components.sort(sorters[sort_column]);
      if (sort_direction === 'descending') {
        metric_components.reverse()
      }
    }
    return (
      <>
        <SubjectTitle
          datamodel={this.props.datamodel}
          readOnly={this.props.readOnly}
          reload={this.props.reload}
          report_uuid={this.props.report.report_uuid}
          subject={subject}
          subject_uuid={this.props.subject_uuid}
        />
        <Table sortable>
          <Table.Header>
            <Table.Row>
              <Table.HeaderCell />
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'name')}
                sorted={sort_column === 'name' ? sort_direction : null}
              >
                Metric
              </Table.HeaderCell>
              <Table.HeaderCell width="2">Trend (7 days)</Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'status')}
                sorted={sort_column === 'status' ? sort_direction : null}
              >
                Status
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'measurement')}
                sorted={sort_column === 'measurement' ? sort_direction : null}
              >
                Measurement
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'target')}
                sorted={sort_column === 'target' ? sort_direction : null}
              >
                Target
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'source')}
                sorted={sort_column === 'source' ? sort_direction : null}
              >
                Source
              </Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'comment')}
                sorted={sort_column === 'comment' ? sort_direction : null}
              >
                Comment</Table.HeaderCell>
              <Table.HeaderCell
                onClick={(event) => this.handle_sort(event, 'tags')}
                sorted={sort_column === 'tags' ? sort_direction : null}
              >
                Tags
              </Table.HeaderCell>
            </Table.Row>
          </Table.Header>
          <Table.Body>{metric_components}</Table.Body>
          {!this.props.readOnly &&
            <Table.Footer>
              <Table.Row>
                <Table.HeaderCell colSpan='9'>
                  <Button floated='left' icon primary basic onClick={(e) => this.onAddMetric(e)}>
                    <Icon name='plus' /> Add metric
                </Button>
                </Table.HeaderCell>
              </Table.Row>
            </Table.Footer>}
        </Table>
      </>
    )
  }
}

export { Subject };
<|endoftext|>


<commit_before>if (typeof require == 'function') var elation = require("utils/elation");
elation.extend("events", {
  events: {},
  cloneattrs: ['type', 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'shiftKey', 'altKey', 'metaKey', 'button', 'relatedTarget', 'target', 'element', 'data', 'origin', 'timeStamp', 'returnValue', 'cancelBubble'],

  fire: function(type, data, target, element, fn) {
    var extras = {};
    if (typeof type == 'object') {
      data = elation.utils.any(elation.utils.arrayget(type, 'data'), data);
      target = elation.utils.arrayget(type, 'target') || target;
      element = elation.utils.arrayget(type, 'element') || element;
      fn = elation.utils.arrayget(type, 'fn') || fn;
      var cloneev = type.event || {};

      for (var i = 0; i < this.cloneattrs.length; i++) {
        var attr = this.cloneattrs[i];
        if (!elation.utils.isNull(type[attr])) extras[attr] = type[attr];
        else if (!elation.utils.isNull(cloneev[attr])) extras[attr] = cloneev[attr];
      }
/*
      if (!elation.utils.isNull(type.clientX)) extras.clientX = type.clientX;
      if (!elation.utils.isNull(type.clientY)) extras.clientY = type.clientY;
      if (!elation.utils.isNull(type.button)) extras.button = type.button;
      if (!elation.utils.isNull(type.keyCode)) extras.keyCode = type.keyCode;
*/

      type = elation.utils.arrayget(type, 'type');
    }

    //console.log('fire:',type,fn,element,data);
    if (!type)
      return false;

    var list = this.events[type],
        original_events = [],
        events = [],
        event;

    if (!list) {
      this.events[type] = [];
      return;
    }

    // gather all the events associated with this event type
    // filter by [element] and/or [fn] if present
    for (var i=0; i<list.length; i++) {
      event = elation.events.fix(list[i]);
      if (fn || element) {
        if ((fn && event.origin == fn) || (element && event.target == element) || elation.utils.isNull(event.target)) {
          original_events.push(event);
        } else {
          continue;
        }
      } else {
        original_events.push(event);
      }
    }

    // fire each event
    var extrakeys = Object.keys(extras);
    for (var i=0; i<original_events.length; i++) {
      var eventObj = original_events[i],
          // break reference to eventObj so original doesn't get overwritten
          event = elation.events.clone(eventObj, {type: type, target: target, data: data, timeStamp: new Date().getTime()});

      for (var j = 0; j < extrakeys.length; j++) {
        event[extrakeys[j]] = extras[extrakeys[j]];
      }

      if (!event.origin)
        continue;

      var cont = true;
      if (typeof event.origin == 'function')
        cont = event.origin(event);
      else if (typeof event.origin.handleEvent != 'undefined')
        cont = event.origin.handleEvent(event);
      events.push(event);

      if (cont === false || event.cancelBubble) {
        break;
      }
    }

    // return all event objects that were fired
    return events;
  },

  register: function(types, fn, element) {
    var types = types.split(','),
        type;

    for (var i=0; i<types.length; i++) {
      type = types[i];

      if (!this.events[type]) {
        if (fn || element)
          this._register(element, type, fn);
        else
          this.events[type] = [];
      }
    }
  },

  _register: function(element, type, fn, custom_event_name) {
    if (custom_event_name)
      custom_event_name = custom_event_name.replace('.','_');

    var event = {
      type: type,
      target: element,
      origin: fn,
      custom_event: custom_event_name,
      preventDefault: function() { this.returnValue = false; return; },
      stopPropagation: function() { this.cancelBubble = true; return; },
      returnValue: true,
      cancelBubble: false
    };

    if (custom_event_name) {
      if (!elation.events.events[custom_event_name])
        elation.events.events[custom_event_name] = [];

      //console.log('BINDING '+type+' -> '+custom_event_name);
    }

    if (!elation.events.events[type])
      elation.events.events[type] = [];

    elation.events.events[type].push(event);
    /*
    if (custom_event_name) {
      if (!elation.events.events[custom_event_name])
        elation.events.events[custom_event_name] = [];

      elation.events.events[custom_event_name].push(event);
    }
    */
  },
  _unregister: function(element, type, fn) {
    if (elation.events.events[type]) {
      for (var i = 0; i < elation.events.events[type].length; i++) {
        var ev = elation.events.events[type][i];
        if (ev.type == type && ev.target == element && ev.origin == fn) {
          elation.events.events[type].splice(i--, 1);
        }
      }
    }
  },

  // syntax: add(element || [ elements ], "type1,type2,type3", function || object);
  add: function(elements, types, fn, custom_event_name) {
    if (custom_event_name)
      custom_event_name = custom_event_name.replace('.','_');

    if (!types || !fn || typeof types != "string")
      return;

    var elements = elation.utils.isNull(elements)
          ? [null]
          : !elation.utils.isArray(elements) || elements == window
            ? [ elements ]
            : elements,
        types = types.split(',');

    if (typeof fn == "string") {
      fn = (function(func) { return function(ev) { eval(func); }; })(fn);
    }

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._register(element, type, fn, custom_event_name);

        if (!element)
          continue;

        if ("addEventListener" in element) {
          if (type == 'mousewheel' && elation.browser.type != 'safari')
            type = 'DOMMouseScroll';

          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function(e) {
              if (custom_event_name)
                elation.events.fire({ type: custom_event_name, data: fn });

              fn.handleEvent(e);
            }
            element.addEventListener(type, element[(type + fn)], false);
          } else {
            element.addEventListener(type, fn, false);
          }
        } else if (element.attachEvent) {
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function() {
              if (custom_event_name)
                elation.events.fire({ type: custom_event_name, data: fn });

              fn.handleEvent(elation.events.fix(window.event));
            }
          } else {
            element["e" + type + fn] = fn;
            element[type + fn] = function() {
              if (typeof element["e" + type + fn] == 'function')
                element["e" + type + fn](elation.events.fix(window.event));
            }
          }

          element.attachEvent("on" + type, element[type + fn]);
        }
      }
    }

    return this;
  },

  // syntax: remove(element || [ elements ], "type1,type2,type3", reference);
  remove: function(elements, types, fn) {
    //if (!elements || !types || !fn || typeof types != "string")
    //  return;

    //var  elements = (!elation.utils.isNull(elements.nodeName) || elements == window) ? [ elements ] : elements;
    if (!elation.utils.isArray(elements)) {
      elements = [elements];
    }
    var types = types.split(',');

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._unregister(element, type, fn);

        if (element) {
          if (element.removeEventListener) {
            if (typeof fn == "object" && fn.handleEvent) {
              element.removeEventListener(type, element[type+fn], false);
              delete element[type + fn];
            } else {
              element.removeEventListener(type, fn, false);
            }
          } else if (element.detachEvent) {
            if (typeof element[type + fn] == "function")
              element.detachEvent("on" + type, element[type + fn]);

            element[type + fn] = null;
            element["e" + type + fn] = null;
          }
        }
      }
    }

    return this;
  },

  fix: function(event) {
    event.preventDefault = function() {
      this.returnValue = false;
    }

    event.stopPropagation = function() {
      this.cancelBubble = true;
    }

    //event.preventDefault = this.preventDefault;
    //event.stopPropagation = this.stopPropagation;

    return event;
  },

  getTarget: function(event) {
    return window.event ? event.srcElement : event.target;
  },

  getRelated: function(event) {
    var reltg;

    if (event.relatedTarget) {
      reltg = event.relatedTarget;
    } else {
      if (event.type == "mouseover")
        reltg = event.fromElement;
      else if (event.type == "mouseout")
        reltg = event.toElement;
      else
        reltg = document;
    }

    return reltg;
  },

  getEventTarget: function(event, parentClassName) {
    var target;

    if (!event)
      var event = window.event;

    if (event.target)
      target = event.target;
    else if (event.srcElement)
      target = event.srcElement;

    if (target.nodeType == 3)
      target = target.parentNode; // Defeat Safari bug

    if (parentClassName) {
      // Make sure we're working with the correct element
      var classUp, classDown;

      if (parentClassName.indexOf(">")) {
        var classes = parentClassName.split(">", 2);
        classDown = classes[0];
        classUp = classes[1];
      } else {
        classDown = parentClassName;
      }

      // First run DOWN the heirarchy to find the base class...
      while (!elation.html.hasclass(target,classDown) && target.parentNode) {
        target = target.parentNode;
      }

      // Now if we've specified a child to attach to, find it!
      if (classUp) {
        var elements;
        elements = elation.find("." + classUp, target);
        if (elements.length > 0) {
          target = elements[0];
        }
      }
    }

    return target;
  },
  isTransition: function(ev, parent) {
    var tg = this.getTarget(ev),
        reltg = this.getRelated(ev);
    return (elation.utils.isin(parent, tg) && !elation.utils.isin(parent, reltg));
  },

  // returns mouse or all finger touch coords
  coords: function(event) {
    if (typeof event.touches != 'undefined' && event.touches.length > 0) {
      var c = {
        x: event.touches[0].pageX,
        y: event.touches[0].pageY
      };
    } else {
      var  c = {
        x: (event.pageX || (event.clientX + document.body.scrollLeft)),
        y: (event.pageY || (event.clientY + document.body.scrollTop))
      };
    }

    return c;
  },

  clone: function(ev,  overrides) {
    var newev = {};
    for (var i = 0; i < this.cloneattrs.length; i++) {
      var foo = elation.utils.any(overrides[this.cloneattrs[i]], ev[this.cloneattrs[i]]);
      if (foo !== null) {
        newev[this.cloneattrs[i]] = foo;
      }
    }
    return elation.events.fix(newev);
  },

  handleEvent: function(ev) {
    if (typeof this[ev.type] == 'function') {
      this[ev.type](ev);
    }
  },

  schedule: function(args) {
    /*
    elation.events.schedule({ev: foo, in: 2000});
    elation.events.schedule({ev: foo, every: 200});
    elation.events.schedule({ev: foo, at: Date().getTime() + 3600});
    */
  }
});
<commit_msg>Remove unnecessary elation.events.fix() call<commit_after>if (typeof require == 'function') var elation = require("utils/elation");
elation.extend("events", {
  events: {},
  cloneattrs: ['type', 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'shiftKey', 'altKey', 'metaKey', 'button', 'relatedTarget', 'target', 'element', 'data', 'origin', 'timeStamp', 'returnValue', 'cancelBubble'],

  fire: function(type, data, target, element, fn) {
    var extras = {};
    if (typeof type == 'object') {
      data = elation.utils.any(elation.utils.arrayget(type, 'data'), data);
      target = elation.utils.arrayget(type, 'target') || target;
      element = elation.utils.arrayget(type, 'element') || element;
      fn = elation.utils.arrayget(type, 'fn') || fn;
      var cloneev = type.event || {};

      for (var i = 0; i < this.cloneattrs.length; i++) {
        var attr = this.cloneattrs[i];
        if (!elation.utils.isNull(type[attr])) extras[attr] = type[attr];
        else if (!elation.utils.isNull(cloneev[attr])) extras[attr] = cloneev[attr];
      }
/*
      if (!elation.utils.isNull(type.clientX)) extras.clientX = type.clientX;
      if (!elation.utils.isNull(type.clientY)) extras.clientY = type.clientY;
      if (!elation.utils.isNull(type.button)) extras.button = type.button;
      if (!elation.utils.isNull(type.keyCode)) extras.keyCode = type.keyCode;
*/

      type = elation.utils.arrayget(type, 'type');
    }

    //console.log('fire:',type,fn,element,data);
    if (!type)
      return false;

    var list = this.events[type],
        original_events = [],
        events = [],
        event;

    if (!list) {
      this.events[type] = [];
      return;
    }

    // gather all the events associated with this event type
    // filter by [element] and/or [fn] if present
    for (var i=0; i<list.length; i++) {
      event = list[i];
      if (fn || element) {
        if ((fn && event.origin == fn) || (element && event.target == element) || elation.utils.isNull(event.target)) {
          original_events.push(event);
        } else {
          continue;
        }
      } else {
        original_events.push(event);
      }
    }

    // fire each event
    var extrakeys = Object.keys(extras);
    for (var i=0; i<original_events.length; i++) {
      var eventObj = original_events[i],
          // break reference to eventObj so original doesn't get overwritten
          event = elation.events.clone(eventObj, {type: type, target: target, data: data, timeStamp: new Date().getTime()});

      for (var j = 0; j < extrakeys.length; j++) {
        event[extrakeys[j]] = extras[extrakeys[j]];
      }

      if (!event.origin)
        continue;

      var cont = true;
      if (typeof event.origin == 'function')
        cont = event.origin(event);
      else if (typeof event.origin.handleEvent != 'undefined')
        cont = event.origin.handleEvent(event);
      events.push(event);

      if (cont === false || event.cancelBubble) {
        break;
      }
    }

    // return all event objects that were fired
    return events;
  },

  register: function(types, fn, element) {
    var types = types.split(','),
        type;

    for (var i=0; i<types.length; i++) {
      type = types[i];

      if (!this.events[type]) {
        if (fn || element)
          this._register(element, type, fn);
        else
          this.events[type] = [];
      }
    }
  },

  _register: function(element, type, fn, custom_event_name) {
    if (custom_event_name)
      custom_event_name = custom_event_name.replace('.','_');

    var event = {
      type: type,
      target: element,
      origin: fn,
      custom_event: custom_event_name,
      preventDefault: function() { this.returnValue = false; return; },
      stopPropagation: function() { this.cancelBubble = true; return; },
      returnValue: true,
      cancelBubble: false
    };

    if (custom_event_name) {
      if (!elation.events.events[custom_event_name])
        elation.events.events[custom_event_name] = [];

      //console.log('BINDING '+type+' -> '+custom_event_name);
    }

    if (!elation.events.events[type])
      elation.events.events[type] = [];

    elation.events.events[type].push(event);
    /*
    if (custom_event_name) {
      if (!elation.events.events[custom_event_name])
        elation.events.events[custom_event_name] = [];

      elation.events.events[custom_event_name].push(event);
    }
    */
  },
  _unregister: function(element, type, fn) {
    if (elation.events.events[type]) {
      for (var i = 0; i < elation.events.events[type].length; i++) {
        var ev = elation.events.events[type][i];
        if (ev.type == type && ev.target == element && ev.origin == fn) {
          elation.events.events[type].splice(i--, 1);
        }
      }
    }
  },

  // syntax: add(element || [ elements ], "type1,type2,type3", function || object);
  add: function(elements, types, fn, custom_event_name) {
    if (custom_event_name)
      custom_event_name = custom_event_name.replace('.','_');

    if (!types || !fn || typeof types != "string")
      return;

    var elements = elation.utils.isNull(elements)
          ? [null]
          : !elation.utils.isArray(elements) || elements == window
            ? [ elements ]
            : elements,
        types = types.split(',');

    if (typeof fn == "string") {
      fn = (function(func) { return function(ev) { eval(func); }; })(fn);
    }

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._register(element, type, fn, custom_event_name);

        if (!element)
          continue;

        if ("addEventListener" in element) {
          if (type == 'mousewheel' && elation.browser.type != 'safari')
            type = 'DOMMouseScroll';

          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function(e) {
              if (custom_event_name)
                elation.events.fire({ type: custom_event_name, data: fn });

              fn.handleEvent(e);
            }
            element.addEventListener(type, element[(type + fn)], false);
          } else {
            element.addEventListener(type, fn, false);
          }
        } else if (element.attachEvent) {
          if (typeof fn == "object" && fn.handleEvent) {
            element[type+fn] = function() {
              if (custom_event_name)
                elation.events.fire({ type: custom_event_name, data: fn });

              fn.handleEvent(elation.events.fix(window.event));
            }
          } else {
            element["e" + type + fn] = fn;
            element[type + fn] = function() {
              if (typeof element["e" + type + fn] == 'function')
                element["e" + type + fn](elation.events.fix(window.event));
            }
          }

          element.attachEvent("on" + type, element[type + fn]);
        }
      }
    }

    return this;
  },

  // syntax: remove(element || [ elements ], "type1,type2,type3", reference);
  remove: function(elements, types, fn) {
    //if (!elements || !types || !fn || typeof types != "string")
    //  return;

    //var  elements = (!elation.utils.isNull(elements.nodeName) || elements == window) ? [ elements ] : elements;
    if (!elation.utils.isArray(elements)) {
      elements = [elements];
    }
    var types = types.split(',');

    for (var e=0; e<elements.length; e++) {
      var element = elements[e];

      if (typeof element != 'object')
        continue;

      for (var i=0; i<types.length; i++) {
        var type = types[i];

        elation.events._unregister(element, type, fn);

        if (element) {
          if (element.removeEventListener) {
            if (typeof fn == "object" && fn.handleEvent) {
              element.removeEventListener(type, element[type+fn], false);
              delete element[type + fn];
            } else {
              element.removeEventListener(type, fn, false);
            }
          } else if (element.detachEvent) {
            if (typeof element[type + fn] == "function")
              element.detachEvent("on" + type, element[type + fn]);

            element[type + fn] = null;
            element["e" + type + fn] = null;
          }
        }
      }
    }

    return this;
  },

  fix: function(event) {
    event.preventDefault = function() {
      this.returnValue = false;
    }

    event.stopPropagation = function() {
      this.cancelBubble = true;
    }

    //event.preventDefault = this.preventDefault;
    //event.stopPropagation = this.stopPropagation;

    return event;
  },

  getTarget: function(event) {
    return window.event ? event.srcElement : event.target;
  },

  getRelated: function(event) {
    var reltg;

    if (event.relatedTarget) {
      reltg = event.relatedTarget;
    } else {
      if (event.type == "mouseover")
        reltg = event.fromElement;
      else if (event.type == "mouseout")
        reltg = event.toElement;
      else
        reltg = document;
    }

    return reltg;
  },

  getEventTarget: function(event, parentClassName) {
    var target;

    if (!event)
      var event = window.event;

    if (event.target)
      target = event.target;
    else if (event.srcElement)
      target = event.srcElement;

    if (target.nodeType == 3)
      target = target.parentNode; // Defeat Safari bug

    if (parentClassName) {
      // Make sure we're working with the correct element
      var classUp, classDown;

      if (parentClassName.indexOf(">")) {
        var classes = parentClassName.split(">", 2);
        classDown = classes[0];
        classUp = classes[1];
      } else {
        classDown = parentClassName;
      }

      // First run DOWN the heirarchy to find the base class...
      while (!elation.html.hasclass(target,classDown) && target.parentNode) {
        target = target.parentNode;
      }

      // Now if we've specified a child to attach to, find it!
      if (classUp) {
        var elements;
        elements = elation.find("." + classUp, target);
        if (elements.length > 0) {
          target = elements[0];
        }
      }
    }

    return target;
  },
  isTransition: function(ev, parent) {
    var tg = this.getTarget(ev),
        reltg = this.getRelated(ev);
    return (elation.utils.isin(parent, tg) && !elation.utils.isin(parent, reltg));
  },

  // returns mouse or all finger touch coords
  coords: function(event) {
    if (typeof event.touches != 'undefined' && event.touches.length > 0) {
      var c = {
        x: event.touches[0].pageX,
        y: event.touches[0].pageY
      };
    } else {
      var  c = {
        x: (event.pageX || (event.clientX + document.body.scrollLeft)),
        y: (event.pageY || (event.clientY + document.body.scrollTop))
      };
    }

    return c;
  },

  clone: function(ev,  overrides) {
    //var newev = new Event(ev.type);
    var newev = {};
    for (var i = 0; i < this.cloneattrs.length; i++) {
      var foo = elation.utils.any(overrides[this.cloneattrs[i]], ev[this.cloneattrs[i]]);
      if (foo !== null) {
        newev[this.cloneattrs[i]] = foo;
      }
    }
    return elation.events.fix(newev);
    //return newev;
  },

  handleEvent: function(ev) {
    if (typeof this[ev.type] == 'function') {
      this[ev.type](ev);
    }
  },

  schedule: function(args) {
    /*
    elation.events.schedule({ev: foo, in: 2000});
    elation.events.schedule({ev: foo, every: 200});
    elation.events.schedule({ev: foo, at: Date().getTime() + 3600});
    */
  }
});
<|endoftext|>


<commit_before>frappe.ui.form.ControlMarkdownEditor = class ControlMarkdownEditor extends frappe.ui.form.ControlCode {
	static editor_class = 'markdown'
	make_ace_editor() {
		super.make_ace_editor();
		if (this.markdown_container) return;

		let editor_class = this.constructor.editor_class;
		this.ace_editor_target.wrap(`<div class="${editor_class}-container">`);
		this.markdown_container = this.$input_wrapper.find(`.${editor_class}-container`);

		this.editor.getSession().setUseWrapMode(true);

		this.showing_preview = false;
		this.preview_toggle_btn = $(`<button class="btn btn-default btn-xs ${editor_class}-toggle">${__('Preview')}</button>`)
			.click(e => {
				if (!this.showing_preview) {
					this.update_preview();
				}

				const $btn = $(e.target);
				this.markdown_preview.toggle(!this.showing_preview);
				this.ace_editor_target.toggle(this.showing_preview);

				this.showing_preview = !this.showing_preview;

				$btn.text(this.showing_preview ? __('Edit') : __('Preview'));
			});
		this.markdown_container.prepend(this.preview_toggle_btn);

		this.markdown_preview = $(`<div class="${editor_class}-preview border rounded">`).hide();
		this.markdown_container.append(this.markdown_preview);
	}

	set_language() {
		this.df.options = 'Markdown';
		super.set_language();
	}

	update_preview() {
		const value = this.get_value() || "";
		this.markdown_preview.html(frappe.markdown(value));
	}

	set_formatted_input(value) {
		super.set_formatted_input(value)
			.then(() => {
				this.update_preview();
			});
	}

	set_disp_area(value) {
		this.disp_area && $(this.disp_area).text(value);
	}
};
<commit_msg>fix: html mode for ace editor<commit_after>frappe.ui.form.ControlMarkdownEditor = class ControlMarkdownEditor extends frappe.ui.form.ControlCode {
	static editor_class = 'markdown'
	make_ace_editor() {
		super.make_ace_editor();
		if (this.markdown_container) return;

		let editor_class = this.constructor.editor_class;
		this.ace_editor_target.wrap(`<div class="${editor_class}-container">`);
		this.markdown_container = this.$input_wrapper.find(`.${editor_class}-container`);

		this.editor.getSession().setUseWrapMode(true);

		this.showing_preview = false;
		this.preview_toggle_btn = $(`<button class="btn btn-default btn-xs ${editor_class}-toggle">${__('Preview')}</button>`)
			.click(e => {
				if (!this.showing_preview) {
					this.update_preview();
				}

				const $btn = $(e.target);
				this.markdown_preview.toggle(!this.showing_preview);
				this.ace_editor_target.toggle(this.showing_preview);

				this.showing_preview = !this.showing_preview;

				$btn.text(this.showing_preview ? __('Edit') : __('Preview'));
			});
		this.markdown_container.prepend(this.preview_toggle_btn);

		this.markdown_preview = $(`<div class="${editor_class}-preview border rounded">`).hide();
		this.markdown_container.append(this.markdown_preview);
	}

	set_language() {
		if (!this.df.options) {
			this.df.options = 'Markdown';
		}
		super.set_language();
	}

	update_preview() {
		const value = this.get_value() || "";
		this.markdown_preview.html(frappe.markdown(value));
	}

	set_formatted_input(value) {
		super.set_formatted_input(value)
			.then(() => {
				this.update_preview();
			});
	}

	set_disp_area(value) {
		this.disp_area && $(this.disp_area).text(value);
	}
};
<|endoftext|>