<commit_before>import { basename, extname } from 'path';
import _ from 'lodash';
import Vue from 'vue';
import NProgress from 'vue-nprogress';
import keycode from 'keycode';
import store from './lib/core-data/store';
import { decorateAll } from './lib/decorators';
import { addSelectorButton } from './lib/utils/custom-buttons'; // eslint-disable-line
import { add as addBehavior } from './lib/forms/behaviors';
import { add as addPane } from './lib/forms/panes';
import toolbar from './lib/toolbar/edit-toolbar.vue';
import { HIDE_STATUS } from './lib/toolbar/mutationTypes';
import { init as initValidators } from './lib/validators';
import conditionalFocus from './directives/conditional-focus';
import hScrollDirective from './directives/horizontal-scroll';
import utilsAPI from './lib/utils/api';
import { hasClickedFocusableEl } from './lib/decorators/focus';

// TODO: Figure out saving/closing and reverting in panes
import { CLOSE_PANE } from './lib/panes/mutationTypes';

const behaviorReq = require.context('./behaviors', false, /\.vue$/),
  paneReq = require.context('./panes', false, /\.vue$/),
  // todo: in the future, we should queue up the saves
  connectionLostMessage = 'Connection Lost. Changes will <strong>NOT</strong> be saved.',
  progressOptions = {
    parent: '.nprogress-container',
    template: '<div class="bar" role="bar"></div>',
    showSpinner: false,
    easing: 'linear',
    speed: 500,
    trickle: false,
    minimum: 0.001
  },
  nprogress = new NProgress(progressOptions);

// Require all scss/css files needed
require.context('./styleguide', true, /^.*\.(scss|css)$/);

// Add behaviors
behaviorReq.keys().forEach(function (key) {
  addBehavior(basename(key, extname(key)), behaviorReq(key));
});

// Add panes
paneReq.keys().forEach(function (key) {
  addPane(basename(key, extname(key)), paneReq(key));
});

// init validators
initValidators();

// add progress bar
Vue.use(NProgress, {
  router: false,
  http: false
});

// Register keys to make key events easy to call
Vue.config.keyCodes.comma = 188;

// register directives
Vue.directive('conditional-focus', conditionalFocus());
Vue.directive('h-scroll', hScrollDirective());

// export api for plugins, validators, behaviors, buttons, etc
window.kiln = window.kiln || {};
// .plugins, .behaviors, .validators, and .panes objects should already exist
window.kiln.utils = utilsAPI;

// kick off loading when DOM is ready
// note: preloaded data, external behaviors, decorators, and validation rules should already be added
// when this event fires
document.addEventListener('DOMContentLoaded', function () {
  new Vue({
    strict: true,
    el: '#kiln-app',
    render(h) {
      return h('edit-toolbar');
    },
    store,
    nprogress,
    components: {
      'edit-toolbar': toolbar
    }
  });

  // page load indicator. will be finished by the preloader
  store.dispatch('startProgress', 'offline');

  // add external plugins
  _.forOwn(window.kiln.plugins || {}, (plugin) => plugin(store));

  store.dispatch('preload')
    .then(() => decorateAll())
    .then(() => store.dispatch('openHashedForm'))
    .then(() => {
      // test connection loss on page load
      if (!navigator.onLine) {
        store.dispatch('showStatus', { type: 'offline', message: connectionLostMessage, isPermanent: true});
      }
    });

  // when clicks bubble up to the document, close the current form or pane / unselect components
  document.body.addEventListener('click', (e) => {
    // unselect if clicking out of the current selection (if user isn't trying to select text)
    // todo: handle panes where we want to stay selected
    if (_.get(store, 'state.ui.currentSelection') && !window.kiln.isInvalidDrag) {
      // note: stopSelection is set in the 'select' action. see the comments there for details
      store.dispatch('unselect');
    }

    // always unfocus if clicking out of the current focus (and not directly clicking into another focusable el)
    if (_.get(store, 'state.ui.currentFocus') && !hasClickedFocusableEl(e) && !window.kiln.isInvalidDrag) {
      store.dispatch('unfocus').catch(_.noop);
    }

    // unset isInvalidDrag after checking for unfocus / unselect
    window.kiln.isInvalidDrag = false;

    // Close a pane
    if (_.get(store, 'state.ui.currentPane')) {
      store.commit(CLOSE_PANE, null);
    }<commit_msg>display message on load if edited in the last 5 minutes by another user<commit_after>import { basename, extname } from 'path';
import _ from 'lodash';
import Vue from 'vue';
import NProgress from 'vue-nprogress';
import keycode from 'keycode';
import differenceInMinutes from 'date-fns/difference_in_minutes';
import store from './lib/core-data/store';
import { decorateAll } from './lib/decorators';
import { addSelectorButton } from './lib/utils/custom-buttons'; // eslint-disable-line
import { add as addBehavior } from './lib/forms/behaviors';
import { add as addPane } from './lib/forms/panes';
import toolbar from './lib/toolbar/edit-toolbar.vue';
import { HIDE_STATUS } from './lib/toolbar/mutationTypes';
import { init as initValidators } from './lib/validators';
import conditionalFocus from './directives/conditional-focus';
import hScrollDirective from './directives/horizontal-scroll';
import utilsAPI from './lib/utils/api';
import { hasClickedFocusableEl } from './lib/decorators/focus';

// TODO: Figure out saving/closing and reverting in panes
import { CLOSE_PANE } from './lib/panes/mutationTypes';

const behaviorReq = require.context('./behaviors', false, /\.vue$/),
  paneReq = require.context('./panes', false, /\.vue$/),
  // todo: in the future, we should queue up the saves
  connectionLostMessage = 'Connection Lost. Changes will <strong>NOT</strong> be saved.',
  progressOptions = {
    parent: '.nprogress-container',
    template: '<div class="bar" role="bar"></div>',
    showSpinner: false,
    easing: 'linear',
    speed: 500,
    trickle: false,
    minimum: 0.001
  },
  nprogress = new NProgress(progressOptions);

// Require all scss/css files needed
require.context('./styleguide', true, /^.*\.(scss|css)$/);

// Add behaviors
behaviorReq.keys().forEach(function (key) {
  addBehavior(basename(key, extname(key)), behaviorReq(key));
});

// Add panes
paneReq.keys().forEach(function (key) {
  addPane(basename(key, extname(key)), paneReq(key));
});

// init validators
initValidators();

// add progress bar
Vue.use(NProgress, {
  router: false,
  http: false
});

// Register keys to make key events easy to call
Vue.config.keyCodes.comma = 188;

// register directives
Vue.directive('conditional-focus', conditionalFocus());
Vue.directive('h-scroll', hScrollDirective());

// export api for plugins, validators, behaviors, buttons, etc
window.kiln = window.kiln || {};
// .plugins, .behaviors, .validators, and .panes objects should already exist
window.kiln.utils = utilsAPI;

/**
 * get the last user who edited a page, who ISN'T the current user
 * @param  {object} store
 * @return {null|string}
 */
function getLastEditUser(store) {
  const currentUser = _.get(store, 'state.user'),
    lastUser = _.findLast(_.get(store, 'state.page.listData.users'), (user) => {
      const isDifferentUser = user.username !== currentUser.username,
        isWithinFiveMinutes = Math.abs(differenceInMinutes(user.updateTime, new Date())) < 5;

      return isDifferentUser && isWithinFiveMinutes;
    });

  return lastUser ? lastUser.name : null;
}

// kick off loading when DOM is ready
// note: preloaded data, external behaviors, decorators, and validation rules should already be added
// when this event fires
document.addEventListener('DOMContentLoaded', function () {
  new Vue({
    strict: true,
    el: '#kiln-app',
    render(h) {
      return h('edit-toolbar');
    },
    store,
    nprogress,
    components: {
      'edit-toolbar': toolbar
    }
  });

  // page load indicator. will be finished by the preloader
  store.dispatch('startProgress', 'offline');

  // add external plugins
  _.forOwn(window.kiln.plugins || {}, (plugin) => plugin(store));

  store.dispatch('preload')
    .then(() => decorateAll())
    .then(() => store.dispatch('openHashedForm'))
    .then(() => {
      // test connection loss on page load
      if (!navigator.onLine) {
        store.dispatch('showStatus', { type: 'offline', message: connectionLostMessage, isPermanent: true});
      } else if (getLastEditUser(store)) {
        store.dispatch('showStatus', { type: 'save', message: `Edited less than 5 minutes ago by ${getLastEditUser(store)}` });
      }
    });

  // when clicks bubble up to the document, close the current form or pane / unselect components
  document.body.addEventListener('click', (e) => {
    // unselect if clicking out of the current selection (if user isn't trying to select text)
    // todo: handle panes where we want to stay selected
    if (_.get(store, 'state.ui.currentSelection') && !window.kiln.isInvalidDrag) {
      // note: stopSelection is set in the 'select' action. see the comments there for details
      store.dispatch('unselect');
    }

    // always unfocus if clicking out of the current focus (and not directly clicking into another focusable el)
    if (_.get(store, 'state.ui.currentFocus') && !hasClickedFocusableEl(e) && !window.kiln.isInvalidDrag) {
      store.dispatch('unfocus').catch(_.noop);
    }

    // unset isInvalidDrag after checking for unfocus / unselect
    window.kiln.isInvalidDrag = false;

    // Close a pane
    if (_.get(store, 'state.ui.currentPane')) {
      store.commit(CLOSE_PANE, null);
    }
{'fraction_non_alphanumeric': 0.14281230382925297, 'fraction_numerical': 0.00240636116342331, 'mean_word_length': 3.767581047381546, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7667196', 'n_tokens_mistral': 2911, 'n_tokens_neox': 2825, 'n_words': 842}