<commit_before>import { AnnotationCommand, getKeyForPath } from 'substance'
import {
  BasePackage, EditorBasePackage, ModelComponentPackage, FindAndReplacePackage
} from '../../kit'

import ArticleToolbarPackage from '../shared/ArticleToolbarPackage'
import PersistencePackage from '../../PersistencePackage'
import EntityLabelsPackage from '../shared/EntityLabelsPackage'
import ManuscriptContentPackage from '../shared/ManuscriptContentPackage'

import AbstractsSectionComponent from './AbstractsSectionComponent'
import AddReferenceWorkflow from '../shared/AddReferenceWorkflow'
import AddEntityCommand from '../shared/AddEntityCommand'
import ArticleInformationSectionComponent from './ArticleInformationSectionComponent'
import ArticleMetadataComponent from './ArticleMetadataComponent'
import BibliographicEntryEditor from './BibliographicEntryEditor'
import CustomAbstractComponent from './CustomAbstractComponent'
import { MoveCollectionItemCommand, RemoveCollectionItemCommand } from './CollectionCommands'
import {
  AddFigurePanelCommand, MoveFigurePanelCommand,
  ReplaceFigurePanelImageCommand, RemoveFigurePanelCommand, OpenFigurePanelImageCommand
} from '../shared/FigurePanelCommands'
import EditEntityCommand from '../shared/EditEntityCommand'
import FiguresSectionComponent from './FiguresSectionComponent'
import InsertCustomAbstractCommand from '../shared/InsertCustomAbstractCommand'
import InsertFigurePanelTool from '../shared/InsertFigurePanelTool'
import InsertFootnoteCommand from '../shared/InsertFootnoteCommand'
import OpenFigurePanelImageTool from '../shared/OpenFigurePanelImageTool'
import ReplaceFigurePanelTool from '../shared/ReplaceFigurePanelTool'
import TableFigureComponent from '../shared/TableFigureComponent'
import TranslatableEntryEditor from './TranslatableEntryEditor'
import {
  AddCustomMetadataFieldCommand, MoveCustomMetadataFieldCommand, RemoveCustomMetadataFieldCommand
} from '../shared/CustomMetadataFieldCommands'
import RemoveReferenceCommand from './RemoveReferenceCommand'
import RemoveItemCommand from '../shared/RemoveItemCommand'
import SwitchViewCommand from '../shared/SwitchViewCommand'

export default {
  name: 'ArticleMetadata',
  configure (config) {
    config.import(BasePackage)
    config.import(EditorBasePackage)
    config.import(ArticleToolbarPackage)
    config.import(PersistencePackage)
    config.import(ManuscriptContentPackage)
    config.import(ModelComponentPackage)
    config.import(EntityLabelsPackage)
    config.import(FindAndReplacePackage)

    config.addComponent('add-reference', AddReferenceWorkflow)
    config.addComponent('article-metadata', ArticleMetadataComponent)
    config.addComponent('article-information', ArticleInformationSectionComponent)
    config.addComponent('custom-abstract', CustomAbstractComponent)
    config.addComponent('bibr', BibliographicEntryEditor, true)
    config.addComponent('table-figure', TableFigureComponent, true)
    config.addComponent('subject', TranslatableEntryEditor)
    config.addComponent('keyword', TranslatableEntryEditor)
    // Note: @figures and @tables are (dynamic) collections derived from the article's content
    config.addComponent('@abstracts', AbstractsSectionComponent)
    config.addComponent('@figures', FiguresSectionComponent)

    // SwitchTextTypes
    config.addTextTypeTool({
      name: 'switch-to-heading1',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 1
      },
      icon: 'fa-header',
      label: 'Heading 1',
      accelerator: 'CommandOrControl+Alt+1'
    })
    config.addTextTypeTool({
      name: 'switch-to-heading2',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 2
      },
      icon: 'fa-header',
      label: 'Heading 2',
      accelerator: 'CommandOrControl+Alt+2'
    })
    config.addTextTypeTool({
      name: 'switch-to-heading3',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 3
      },
      icon: 'fa-header',
      label: 'Heading 3',
      accelerator: 'CommandOrControl+Alt+3'
    })
    config.addTextTypeTool({
      name: 'switch-to-paragraph',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'paragraph'
      },
      icon: 'fa-paragraph',
      label: 'Paragraph',
      accelerator: 'CommandOrControl+Alt+0'
    })
    // Commands
    config.addCommand('add-metadata-field', AddCustomMetadataFieldCommand, {
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('add-figure-panel', AddFigurePanelCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('edit-author', EditEntityCommand, {
      selectionType: 'author',
      commandGroup: 'author'
    })
    config.addCommand('edit-reference', EditEntityCommand, {
      selectionType: 'reference',
      commandGroup: 'reference'
    })
    config.addCommand('move-down-col-item', MoveCollectionItemCommand, {
      direction: 'down',
      commandGroup: 'collection'
    })
    config.addCommand('move-down-metadata-field', MoveCustomMetadataFieldCommand, {
      direction: 'down',
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('move-down-figure-panel', MoveFigurePanelCommand, {
      direction: 'down',
      commandGroup: 'figure-panel'
    })
    config.addCommand('move-up-col-item', MoveCollectionItemCommand, {
      direction: 'up',
      commandGroup: 'collection'
    })
    config.addCommand('move-up-metadata-field', MoveCustomMetadataFieldCommand, {
      direction: 'up',
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('move-up-figure-panel', MoveFigurePanelCommand, {
      direction: 'up',
      commandGroup: 'figure-panel'
    })
    config.addCommand('open-figure-panel-image', OpenFigurePanelImageCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('open-manuscript', SwitchViewCommand, {
      viewName: 'manuscript',
      commandGroup: 'switch-view'
    })
    config.addCommand('open-metadata', SwitchViewCommand, {
      viewName: 'metadata',
      commandGroup: 'switch-view'
    })
    config.addCommand('remove-col-item', RemoveCollectionItemCommand, {
      commandGroup: 'collection'
    })
    config.addCommand('remove-footnote', RemoveItemCommand, {
      nodeType: 'footnote',
      commandGroup: 'footnote'
    })
    config.addCommand('remove-metadata-field', RemoveCustomMetadataFieldCommand, {
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('remove-figure-panel', RemoveFigurePanelCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('remove-reference', RemoveReferenceCommand, {
      commandGroup: 'reference'
    })
    config.addCommand('replace-figure-panel-image', ReplaceFigurePanelImageCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('toggle-bold', AnnotationCommand, {
      nodeType: 'bold',
      accelerator: 'CommandOrControl+B',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-italic', AnnotationCommand, {
      nodeType: 'italic',
      accelerator: 'CommandOrControl+I',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-monospace', AnnotationCommand, {
      nodeType: 'monospace',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-overline', AnnotationCommand, {
      nodeType: 'overline',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-small-caps', AnnotationCommand, {
      nodeType: 'small-caps',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-strike-through', AnnotationCommand, {
      nodeType: 'strike-through',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-subscript', AnnotationCommand, {
      nodeType: 'sub',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-superscript', AnnotationCommand, {
      nodeType: 'sup',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-underline', AnnotationCommand, {
      nodeType: 'underline',
      commandGroup: 'formatting'
    })

    // Tools
    config.addComponent('add-figure-panel', InsertFigurePanelTool)
    config.addComponent('open-figure-panel-image', OpenFigurePanelImageTool)
    config.addComponent('replace-figure-panel-image', ReplaceFigurePanelTool)

    // KeyboardShortcuts
    config.addKeyboardShortcut('CommandOrControl+Alt+Up', { command: 'move-up-col-item' })
    config.addKeyboardShortcut('CommandOrControl+Alt+Down', { command: 'move-down-col-item' })
    config.addKeyboardShortcut('CommandOrControl+Alt+Delete', { command: 'remove-col-item' })

    // Labels
    config.addLabel('abstracts', 'Abstracts')
    config.addLabel('article', 'Article Information')
    config.addLabel('article-information', 'Article Information')
    config.addLabel('authors', 'Authors')
    config.addLabel('figures', 'Figures')
    config.addLabel('footnotes', 'Footnotes')
    config.addLabel('groups', 'Groups')
    config.addLabel('issueTitle', 'Issue Title')
    config.addLabel('keywords', 'Keywords')
    config.addLabel('subjects', 'Subjects')
    config.addLabel('organisations', 'Affiliations')
    config.addLabel('references', 'References')
    config.addLabel('tables', 'Tables')
    config.addLabel('translations', 'Translations')
    config.addLabel('add-reference-title', 'Add Reference(s)')
    config.addLabel('add-ref-manually', 'Or create manually')
    config.addLabel('fetch-datacite', 'Fetch from DataCite')
    config.addLabel('enter-doi-placeholder', 'Enter one or more DOIs')
    config.addLabel('add-action', 'Add')
    config.addLabel('enter-url-placeholder', 'Enter url')
    config.addLabel('import-refs', 'Import')
    config.addLabel('supported-ref-formats', 'Supported formats')
    config.addLabel('original-translation', 'Original')
    config.addLabel('add-translation', 'Add Translation')
    config.addLabel('select-language', 'Select language')
    config.addLabel('add', 'Add')
    config.addLabel('edit', 'Edit')
    config.addLabel('remove', 'Remove')
    config.addLabel('workflows', 'Workflows')
    config.addLabel('select-abstract-type', 'Select abstract type')
    config.addLabel('select-license', 'Select license')
    config.addLabel('select-item', 'Choose')
    config.addLabel('move-down-figure-panel', 'Move Down Sub-Figure')
    config.addLabel('enter-custom-field-name', 'Enter name')
    config.addLabel('enter-custom-field-value', 'Enter value')
    config.addLabel('article-metadata', 'Article Metadata')
    config.addLabel('subtitle', 'Subtitle')
    config.addLabel('empty-figure-metadata', 'No fields specified')
    // Icons
    config.addIcon('move-down-figure-panel', { 'fontawesome': 'fa-caret-square-o-down' })
    config.addIcon('input-loading', { 'fontawesome': 'fa-spinner fa-spin' })
    config.addIcon('input-error', { 'fontawesome': 'fa-exclamation-circle' })

    // TODO: need to rethink this a some point
    registerCollectionCommand(config, 'author', ['metadata', 'authors'], { keyboardShortcut: 'CommandOrControl+Alt+A', nodeType: 'person' })
    registerCollectionCommand(config, 'funder', ['metadata', 'funders'], { keyboardShortcut: 'CommandOrControl+Alt+Y' })
    registerCollectionCommand(config, 'editor', ['metadata', 'editors'], { keyboardShortcut: 'CommandOrControl+Alt+E', nodeType: 'person' })
    registerCollectionCommand(config, 'footnote', ['article', 'footnotes'], { automaticOrder: true, Command: InsertFootnoteCommand })
    registerCollectionCommand(config, 'group', ['metadata', 'groups'], { keyboardShortcut: 'CommandOrControl+Alt+G' })
    registerCollectionCommand(config, 'keyword', ['metadata', 'keywords'], { keyboardShortcut: 'CommandOrControl+Alt+K' })
    registerCollectionCommand(config, 'organisation', ['metadata', 'organisations'], { keyboardShortcut: 'CommandOrControl+Alt+O' })
    registerCollectionCommand(config, 'subject', ['metadata', 'subjects'])
    registerCollectionCommand(config, 'custom-abstract', ['article', 'customAbstracts'], { Command: InsertCustomAbstractCommand })
    config.addCommand('insert-reference', AddEntityCommand, {
      workflow: 'add-reference',
      commandGroup: 'add-entity',
      type: 'bibr'
    })
  }
}

// TODO: this is like an overkill, registering one collection item command
// for every collection type
// it would be better to have just one set of commands
// which is detecting the collection automagically
// The challenge will then be how to control certain things,
// such as disabling move up/down etc.
function registerCollectionCommand (config, itemType, collectionPath, options = {}) {
  let nodeType = options.nodeType || itemType
  let xpathSelector = getKeyForPath(collectionPath)
  let Command = options.Command || AddEntityCommand
  config.addCommand(`insert-${itemType}`, Command, {
    type: nodeType,
    collection: collectionPath,
    commandGroup: 'add-entity'
  })
  if (options.keyboardShortcut) {
    config.addKeyboardShortcut(options.keyboardShortcut, { command: `add-${itemType}` })
  }
  if (!options.automaticOrder) {
    config.addCommand(`move-up-${itemType}`, MoveCollectionItemCommand, {
      direction: 'up',
      commandGroup: 'collection',
      xpathSelector
    })
    config.addIcon(`move-up-${itemType}`, { 'fontawesome': 'fa-caret-square-o-up' })
    config.addLabel(`move-up-${itemType}`, {
      en: 'Move item up'
    })
    config.addKeyboardShortcut('CommandOrControl+Alt+Up', { command: `move-up-${itemType}` })
    config.addCommand(`move-down-${itemType}`, MoveCollectionItemCommand, {
      direction: 'down',
      commandGroup: 'collection',
      xpathSelector
    })
    config.addIcon(`move-down-${itemType}`, { 'fontawesome': 'fa-caret-square-o-down' })
    config.addLabel(`move-down-${itemType}`, {
      en: 'Move item down'
    })
    config.addKeyboardShortcut('CommandOrControl+Alt+Down', { command: `move-down-${itemType}` })
    config.addCommand(`remove-${itemType}`, RemoveCollectionItemCommand, {
      commandGroup: 'collection',
      xpathSelector
    })

    config.addIcon('remove', { 'fontawesome': 'fa-trash' })
    config.addIcon('checked-item', { 'fontawesome': 'fa-check-square-o' })
    config.addIcon('unchecked-item', { 'fontawesome': 'fa-square-o' })
  }
  config.addIcon(`remove-${itemType}`, { 'fontawesome': 'fa-trash' })
  config.addLabel(`remove-${itemType}`, {
    en: 'Remove item'
  })
  config.addKeyboardShortcut('CommandOrControl+Alt+Delete', { command: `remove-${itemType}` })
}
<commit_msg>Make table context tools available in Metadata view.<commit_after>import { AnnotationCommand, getKeyForPath } from 'substance'
import {
  BasePackage, EditorBasePackage, ModelComponentPackage, FindAndReplacePackage
} from '../../kit'

import ArticleToolbarPackage from '../shared/ArticleToolbarPackage'
import PersistencePackage from '../../PersistencePackage'
import EntityLabelsPackage from '../shared/EntityLabelsPackage'
import ManuscriptContentPackage from '../shared/ManuscriptContentPackage'

import AbstractsSectionComponent from './AbstractsSectionComponent'
import AddReferenceWorkflow from '../shared/AddReferenceWorkflow'
import AddEntityCommand from '../shared/AddEntityCommand'
import ArticleInformationSectionComponent from './ArticleInformationSectionComponent'
import ArticleMetadataComponent from './ArticleMetadataComponent'
import BibliographicEntryEditor from './BibliographicEntryEditor'
import CustomAbstractComponent from './CustomAbstractComponent'
import { MoveCollectionItemCommand, RemoveCollectionItemCommand } from './CollectionCommands'
import {
  AddFigurePanelCommand, MoveFigurePanelCommand,
  ReplaceFigurePanelImageCommand, RemoveFigurePanelCommand, OpenFigurePanelImageCommand
} from '../shared/FigurePanelCommands'
import EditEntityCommand from '../shared/EditEntityCommand'
import FiguresSectionComponent from './FiguresSectionComponent'
import InsertCustomAbstractCommand from '../shared/InsertCustomAbstractCommand'
import InsertFigurePanelTool from '../shared/InsertFigurePanelTool'
import InsertFootnoteCommand from '../shared/InsertFootnoteCommand'
import {
  InsertTableCommand, InsertCellsCommand, DeleteCellsCommand,
  TableSelectAllCommand, ToggleCellHeadingCommand, ToggleCellMergeCommand
} from '../editor/TableCommands'
import OpenFigurePanelImageTool from '../shared/OpenFigurePanelImageTool'
import ReplaceFigurePanelTool from '../shared/ReplaceFigurePanelTool'
import TableFigureComponent from '../shared/TableFigureComponent'
import TranslatableEntryEditor from './TranslatableEntryEditor'
import {
  AddCustomMetadataFieldCommand, MoveCustomMetadataFieldCommand, RemoveCustomMetadataFieldCommand
} from '../shared/CustomMetadataFieldCommands'
import RemoveReferenceCommand from './RemoveReferenceCommand'
import RemoveItemCommand from '../shared/RemoveItemCommand'
import SwitchViewCommand from '../shared/SwitchViewCommand'

export default {
  name: 'ArticleMetadata',
  configure (config) {
    config.import(BasePackage)
    config.import(EditorBasePackage)
    config.import(ArticleToolbarPackage)
    config.import(PersistencePackage)
    config.import(ManuscriptContentPackage)
    config.import(ModelComponentPackage)
    config.import(EntityLabelsPackage)
    config.import(FindAndReplacePackage)

    config.addComponent('add-reference', AddReferenceWorkflow)
    config.addComponent('article-metadata', ArticleMetadataComponent)
    config.addComponent('article-information', ArticleInformationSectionComponent)
    config.addComponent('custom-abstract', CustomAbstractComponent)
    config.addComponent('bibr', BibliographicEntryEditor, true)
    config.addComponent('table-figure', TableFigureComponent, true)
    config.addComponent('subject', TranslatableEntryEditor)
    config.addComponent('keyword', TranslatableEntryEditor)
    // Note: @figures and @tables are (dynamic) collections derived from the article's content
    config.addComponent('@abstracts', AbstractsSectionComponent)
    config.addComponent('@figures', FiguresSectionComponent)

    // SwitchTextTypes
    config.addTextTypeTool({
      name: 'switch-to-heading1',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 1
      },
      icon: 'fa-header',
      label: 'Heading 1',
      accelerator: 'CommandOrControl+Alt+1'
    })
    config.addTextTypeTool({
      name: 'switch-to-heading2',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 2
      },
      icon: 'fa-header',
      label: 'Heading 2',
      accelerator: 'CommandOrControl+Alt+2'
    })
    config.addTextTypeTool({
      name: 'switch-to-heading3',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'heading',
        level: 3
      },
      icon: 'fa-header',
      label: 'Heading 3',
      accelerator: 'CommandOrControl+Alt+3'
    })
    config.addTextTypeTool({
      name: 'switch-to-paragraph',
      commandGroup: 'text-types',
      nodeSpec: {
        type: 'paragraph'
      },
      icon: 'fa-paragraph',
      label: 'Paragraph',
      accelerator: 'CommandOrControl+Alt+0'
    })
    // Commands
    config.addCommand('add-metadata-field', AddCustomMetadataFieldCommand, {
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('add-figure-panel', AddFigurePanelCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('delete-columns', DeleteCellsCommand, {
      spec: { dim: 'col' },
      commandGroup: 'table-delete'
    })
    config.addCommand('delete-rows', DeleteCellsCommand, {
      spec: { dim: 'row' },
      commandGroup: 'table-delete'
    })
    config.addCommand('edit-author', EditEntityCommand, {
      selectionType: 'author',
      commandGroup: 'author'
    })
    config.addCommand('edit-reference', EditEntityCommand, {
      selectionType: 'reference',
      commandGroup: 'reference'
    })
    config.addCommand('insert-columns-left', InsertCellsCommand, {
      spec: { dim: 'col', pos: 'left' },
      commandGroup: 'table-insert'
    })
    config.addCommand('insert-columns-right', InsertCellsCommand, {
      spec: { dim: 'col', pos: 'right' },
      commandGroup: 'table-insert'
    })
    config.addCommand('insert-rows-above', InsertCellsCommand, {
      spec: { dim: 'row', pos: 'above' },
      commandGroup: 'table-insert'
    })
    config.addCommand('insert-rows-below', InsertCellsCommand, {
      spec: { dim: 'row', pos: 'below' },
      commandGroup: 'table-insert'
    })
    config.addCommand('insert-table', InsertTableCommand, {
      nodeType: 'table-figure',
      commandGroup: 'insert'
    })
    config.addCommand('move-down-col-item', MoveCollectionItemCommand, {
      direction: 'down',
      commandGroup: 'collection'
    })
    config.addCommand('move-down-metadata-field', MoveCustomMetadataFieldCommand, {
      direction: 'down',
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('move-down-figure-panel', MoveFigurePanelCommand, {
      direction: 'down',
      commandGroup: 'figure-panel'
    })
    config.addCommand('move-up-col-item', MoveCollectionItemCommand, {
      direction: 'up',
      commandGroup: 'collection'
    })
    config.addCommand('move-up-metadata-field', MoveCustomMetadataFieldCommand, {
      direction: 'up',
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('move-up-figure-panel', MoveFigurePanelCommand, {
      direction: 'up',
      commandGroup: 'figure-panel'
    })
    config.addCommand('open-figure-panel-image', OpenFigurePanelImageCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('open-manuscript', SwitchViewCommand, {
      viewName: 'manuscript',
      commandGroup: 'switch-view'
    })
    config.addCommand('open-metadata', SwitchViewCommand, {
      viewName: 'metadata',
      commandGroup: 'switch-view'
    })
    config.addCommand('remove-col-item', RemoveCollectionItemCommand, {
      commandGroup: 'collection'
    })
    config.addCommand('remove-footnote', RemoveItemCommand, {
      nodeType: 'footnote',
      commandGroup: 'footnote'
    })
    config.addCommand('remove-metadata-field', RemoveCustomMetadataFieldCommand, {
      commandGroup: 'custom-metadata-fields'
    })
    config.addCommand('remove-figure-panel', RemoveFigurePanelCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('remove-reference', RemoveReferenceCommand, {
      commandGroup: 'reference'
    })
    config.addCommand('replace-figure-panel-image', ReplaceFigurePanelImageCommand, {
      commandGroup: 'figure-panel'
    })
    config.addCommand('table:select-all', TableSelectAllCommand)
    config.addCommand('toggle-bold', AnnotationCommand, {
      nodeType: 'bold',
      accelerator: 'CommandOrControl+B',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-cell-heading', ToggleCellHeadingCommand, {
      commandGroup: 'table'
    })
    config.addCommand('toggle-cell-merge', ToggleCellMergeCommand, {
      commandGroup: 'table'
    })
    config.addCommand('toggle-italic', AnnotationCommand, {
      nodeType: 'italic',
      accelerator: 'CommandOrControl+I',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-monospace', AnnotationCommand, {
      nodeType: 'monospace',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-overline', AnnotationCommand, {
      nodeType: 'overline',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-small-caps', AnnotationCommand, {
      nodeType: 'small-caps',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-strike-through', AnnotationCommand, {
      nodeType: 'strike-through',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-subscript', AnnotationCommand, {
      nodeType: 'sub',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-superscript', AnnotationCommand, {
      nodeType: 'sup',
      commandGroup: 'formatting'
    })
    config.addCommand('toggle-underline', AnnotationCommand, {
      nodeType: 'underline',
      commandGroup: 'formatting'
    })

    // Tools
    config.addComponent('add-figure-panel', InsertFigurePanelTool)
    config.addComponent('open-figure-panel-image', OpenFigurePanelImageTool)
    config.addComponent('replace-figure-panel-image', ReplaceFigurePanelTool)

    // KeyboardShortcuts
    config.addKeyboardShortcut('CommandOrControl+Alt+Up', { command: 'move-up-col-item' })
    config.addKeyboardShortcut('CommandOrControl+Alt+Down', { command: 'move-down-col-item' })
    config.addKeyboardShortcut('CommandOrControl+Alt+Delete', { command: 'remove-col-item' })

    // Labels
    config.addLabel('abstracts', 'Abstracts')
    config.addLabel('article', 'Article Information')
    config.addLabel('article-information', 'Article Information')
    config.addLabel('authors', 'Authors')
    config.addLabel('figures', 'Figures')
    config.addLabel('footnotes', 'Footnotes')
    config.addLabel('groups', 'Groups')
    config.addLabel('issueTitle', 'Issue Title')
    config.addLabel('keywords', 'Keywords')
    config.addLabel('subjects', 'Subjects')
    config.addLabel('organisations', 'Affiliations')
    config.addLabel('references', 'References')
    config.addLabel('tables', 'Tables')
    config.addLabel('translations', 'Translations')
    config.addLabel('add-reference-title', 'Add Reference(s)')
    config.addLabel('add-ref-manually', 'Or create manually')
    config.addLabel('fetch-datacite', 'Fetch from DataCite')
    config.addLabel('enter-doi-placeholder', 'Enter one or more DOIs')
    config.addLabel('add-action', 'Add')
    config.addLabel('enter-url-placeholder', 'Enter url')
    config.addLabel('import-refs', 'Import')
    config.addLabel('supported-ref-formats', 'Supported formats')
    config.addLabel('original-translation', 'Original')
    config.addLabel('add-translation', 'Add Translation')
    config.addLabel('select-language', 'Select language')
    config.addLabel('add', 'Add')
    config.addLabel('edit', 'Edit')
    config.addLabel('remove', 'Remove')
    config.addLabel('workflows', 'Workflows')
    config.addLabel('select-abstract-type', 'Select abstract type')
    config.addLabel('select-license', 'Select license')
    config.addLabel('select-item', 'Choose')
    config.addLabel('move-down-figure-panel', 'Move Down Sub-Figure')
    config.addLabel('enter-custom-field-name', 'Enter name')
    config.addLabel('enter-custom-field-value', 'Enter value')
    config.addLabel('article-metadata', 'Article Metadata')
    config.addLabel('subtitle', 'Subtitle')
    config.addLabel('empty-figure-metadata', 'No fields specified')
    // Icons
    config.addIcon('move-down-figure-panel', { 'fontawesome': 'fa-caret-square-o-down' })
    config.addIcon('input-loading', { 'fontawesome': 'fa-spinner fa-spin' })
    config.addIcon('input-error', { 'fontawesome': 'fa-exclamation-circle' })

    // TODO: need to rethink this a some point
    registerCollectionCommand(config, 'author', ['metadata', 'authors'], { keyboardShortcut: 'CommandOrControl+Alt+A', nodeType: 'person' })
    registerCollectionCommand(config, 'funder', ['metadata', 'funders'], { keyboardShortcut: 'CommandOrControl+Alt+Y' })
    registerCollectionCommand(config, 'editor', ['metadata', 'editors'], { keyboardShortcut: 'CommandOrControl+Alt+E', nodeType: 'person' })
    registerCollectionCommand(config, 'footnote', ['article', 'footnotes'], { automaticOrder: true, Command: InsertFootnoteCommand })
    registerCollectionCommand(config, 'group', ['metadata', 'groups'], { keyboardShortcut: 'CommandOrControl+Alt+G' })
    registerCollectionCommand(config, 'keyword', ['metadata', 'keywords'], { keyboardShortcut: 'CommandOrControl+Alt+K' })
    registerCollectionCommand(config, 'organisation', ['metadata', 'organisations'], { keyboardShortcut: 'CommandOrControl+Alt+O' })
    registerCollectionCommand(config, 'subject', ['metadata', 'subjects'])
    registerCollectionCommand(config, 'custom-abstract', ['article', 'customAbstracts'], { Command: InsertCustomAbstractCommand })
    config.addCommand('insert-reference', AddEntityCommand, {
      workflow: 'add-reference',
      commandGroup: 'add-entity',
      type: 'bibr'
    })
  }
}

// TODO: this is like an overkill, registering one collection item command
// for every collection type
// it would be better to have just one set of commands
// which is detecting the collection automagically
// The challenge will then be how to control certain things,
// such as disabling move up/down etc.
function registerCollectionCommand (config, itemType, collectionPath, options = {}) {
  let nodeType = options.nodeType || itemType
  let xpathSelector = getKeyForPath(collectionPath)
  let Command = options.Command || AddEntityCommand
  config.addCommand(`insert-${itemType}`, Command, {
    type: nodeType,
    collection: collectionPath,
    commandGroup: 'add-entity'
  })
  if (options.keyboardShortcut) {
    config.addKeyboardShortcut(options.keyboardShortcut, { command: `add-${itemType}` })
  }
  if (!options.automaticOrder) {
    config.addCommand(`move-up-${itemType}`, MoveCollectionItemCommand, {
      direction: 'up',
      commandGroup: 'collection',
      xpathSelector
    })
    config.addIcon(`move-up-${itemType}`, { 'fontawesome': 'fa-caret-square-o-up' })
    config.addLabel(`move-up-${itemType}`, {
      en: 'Move item up'
    })
    config.addKeyboardShortcut('CommandOrControl+Alt+Up', { command: `move-up-${itemType}` })
    config.addCommand(`move-down-${itemType}`, MoveCollectionItemCommand, {
      direction: 'down',
      commandGroup: 'collection',
      xpathSelector
    })
    config.addIcon(`move-down-${itemType}`, { 'fontawesome': 'fa-caret-square-o-down' })
    config.addLabel(`move-down-${itemType}`, {
      en: 'Move item down'
    })
    config.addKeyboardShortcut('CommandOrControl+Alt+Down', { command: `move-down-${itemType}` })
    config.addCommand(`remove-${itemType}`, RemoveCollectionItemCommand, {
      commandGroup: 'collection',
      xpathSelector
    })

    config.addIcon('remove', { 'fontawesome': 'fa-trash' })
    config.addIcon('checked-item', { 'fontawesome': 'fa-check-square-o' })
    config.addIcon('unchecked-item', { 'fontawesome': 'fa-square-o' })
  }
  config.addIcon(`remove-${itemType}`, { 'fontawesome': 'fa-trash' })
  config.addLabel(`remove-${itemType}`, {
    en: 'Remove item'
  })
  config.addKeyboardShortcut('CommandOrControl+Alt+Delete', { command: `remove-${itemType}` })
}
<|endoftext|>


<commit_before>// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

//
// create base UI tab and root window
//
var swarmWin = Titanium.UI.createWindow({
    title:'SwarmWindow',
    backgroundColor:'#fff'
});

var userSelection = false;

var swarmDefinition = {};

swarmDefinition.name = "Jump around";
swarmDefinition.description = "Raise your Arms in the air and jump around as if you just don't care.";
swarmDefinition.countdownSeconds = 15;
swarmDefinition.durationSeconds = 15;

var countdownSeconds = swarmDefinition.countdownSeconds;      // Wait time in seconds
var zwarmDuration = swarmDefinition.durationSeconds;

var durationString;
if(swarmDefinition.durationSeconds==60){
	durationString = '1 Minute';
} else if(swarmDefinition.durationSeconds>59 && swarmDefinition.durationSeconds%60==0){
	durationString = swarmDefinition.durationSeconds/60 + ' Minuten';
} else {
	durationString = swarmDefinition.durationSeconds + ' Sekunden';
}

var buttonWidth = '80%';

var buttonHeight = '10%';

var textWidth = buttonWidth;

var swarmLabel = Titanium.UI.createLabel({
	backgroundColor:'yellow',
	color:'black',
	top:'10%',
	width:textWidth,
	text:'Swarm: "' + swarmDefinition.name + '" '+ swarmDefinition.description + ' (' + durationString + ')',
	font:{fontSize:30,fontFamily:'Helvetica Neue'},
	textAlign:'center'
});

var switchLabelColor = function(color){
	swarmLabel.backgroundColor = color;
};

var toggleMediaButton = function(mediaButtonsActive){
	if(mediaButtonsActive){
		swarmWin.add(buttonPhoto);
		swarmWin.add(buttonVideo);
		swarmWin.add(buttonComment);
		swarmWin.remove(buttonParticipate);
		swarmWin.remove(buttonNotParticipate);
	} else {
		swarmWin.remove(buttonPhoto);
		swarmWin.remove(buttonVideo);
		swarmWin.remove(buttonComment);
		swarmWin.add(buttonParticipate);
		swarmWin.add(buttonNotParticipate);
	}
}

var closeView = function(){
	alert('Close view');
}

var buttonParticipate = Titanium.UI.createButton({
   title: 'Participate',
   top:'55%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonNotParticipate = Titanium.UI.createButton({
   title: 'Nope',
   top:'70%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonPhoto = Titanium.UI.createButton({
   title: 'Take a Photo',
   top:'55%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonVideo = Titanium.UI.createButton({
   title: 'Make a Video',
   top: '70%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonComment = Titanium.UI.createButton({
   title: 'Write a Comment',
   top: '85%',
   width: buttonWidth,
   height: buttonHeight
});

buttonPhoto.addEventListener('click',function(e){
	alert('Photo button click');
});

buttonParticipate.addEventListener('click',function(e){
	userSelection = true;
	toggleMediaButton(true);
});

buttonNotParticipate.addEventListener('click',function(e){
	closeView();
});

buttonVideo.addEventListener('click',function(e){
	alert('Video button click');
});

buttonComment.addEventListener('click',function(e){
	alert('Comment button click');
});

Titanium.include('countdown.js');

//label for the countdown
var countdownLabel = Ti.UI.createLabel({
	top:'40%',
	width:textWidth
});

countdownLabel.text = "Starting in " + Math.floor(countdownSeconds/60)+":"+countdownSeconds%60 + " Minutes.";
// timer for Starting in ...
var my_timer = new countDown(0,countdownSeconds,
	function() {
    	countdownLabel.text = "Starting in " + timeString(my_timer) + " Minutes.";
	},
    function() {
    	if(userSelection){
    		startZwarm();
    	} else {
    		closeView();
    	}
   }
).start();

var startZwarm = function(){
	swarmLabel.backgroundColor='green';
	countdownLabel.text = "Finishing in " + Math.floor(zwarmDuration/60)+":"+zwarmDuration%60 + " Minutes.";
	var my_timer = new countDown(0,zwarmDuration,
		function(){
	    	countdownLabel.text = "Finishing in " + timeString(my_timer) + " Minutes.";
		},
		function(){
			alert('Photo List View');
		}
	).start();
}

//my_timer.set(secToMin(countdownSeconds));
// seconds are calculated into minutes and vice versa in the funnction => refactor
//my_timer.start();

swarmWin.add(swarmLabel);
swarmWin.add(countdownLabel);
toggleMediaButton(false);

swarmWin.open();<commit_msg>SwarmView<commit_after>// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

//
// create base UI tab and root window
//
var swarmWin = Titanium.UI.createWindow({
    title:'SwarmWindow',
    backgroundColor:'#fff'
});

var userSelection = false;

var swarmDefinition = {};

swarmDefinition.name = "Jump around";
swarmDefinition.description = "Raise your Arms in the air and jump around as if you just don't care.";
swarmDefinition.countdownSeconds = 15;
swarmDefinition.durationSeconds = 15;

var countdownSeconds = swarmDefinition.countdownSeconds;      // Wait time in seconds
var zwarmDuration = swarmDefinition.durationSeconds;

var durationString;
if(swarmDefinition.durationSeconds==1){
	durationString = '1 Second';
} else if(swarmDefinition.durationSeconds==60){
	durationString = '1 Minute';
} else if(swarmDefinition.durationSeconds>59 && swarmDefinition.durationSeconds%60==0){
	durationString = swarmDefinition.durationSeconds/60 + ' Minutes';
} else {
	durationString = swarmDefinition.durationSeconds + ' Seconds';
}

var buttonWidth = '80%';

var buttonHeight = '10%';

var textWidth = buttonWidth;

var swarmLabel = Titanium.UI.createLabel({
	backgroundColor:'yellow',
	color:'black',
	top:'10%',
	width:textWidth,
	text:'Swarm: "' + swarmDefinition.name + '" '+ swarmDefinition.description + ' (' + durationString + ')',
	font:{fontSize:30,fontFamily:'Helvetica Neue'},
	textAlign:'center'
});

var switchLabelColor = function(color){
	swarmLabel.backgroundColor = color;
};

var toggleMediaButton = function(mediaButtonsActive){
	if(mediaButtonsActive){
		swarmWin.add(buttonPhoto);
		swarmWin.add(buttonVideo);
		swarmWin.add(buttonComment);
		swarmWin.remove(buttonParticipate);
		swarmWin.remove(buttonNotParticipate);
	} else {
		swarmWin.remove(buttonPhoto);
		swarmWin.remove(buttonVideo);
		swarmWin.remove(buttonComment);
		swarmWin.add(buttonParticipate);
		swarmWin.add(buttonNotParticipate);
	}
}

var closeView = function(){
	alert('Close view');
}

var buttonParticipate = Titanium.UI.createButton({
   title: 'Participate',
   top:'55%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonNotParticipate = Titanium.UI.createButton({
   title: 'Nope',
   top:'70%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonPhoto = Titanium.UI.createButton({
   title: 'Take a Photo',
   top:'55%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonVideo = Titanium.UI.createButton({
   title: 'Make a Video',
   top: '70%',
   width: buttonWidth,
   height: buttonHeight
});

var buttonComment = Titanium.UI.createButton({
   title: 'Write a Comment',
   top: '85%',
   width: buttonWidth,
   height: buttonHeight
});

buttonPhoto.addEventListener('click',function(e){
	alert('Photo button click');
});

buttonParticipate.addEventListener('click',function(e){
	userSelection = true;
	toggleMediaButton(true);
});

buttonNotParticipate.addEventListener('click',function(e){
	closeView();
});

buttonVideo.addEventListener('click',function(e){
	alert('Video button click');
});

buttonComment.addEventListener('click',function(e){
	alert('Comment button click');
});

Titanium.include('countdown.js');

//label for the countdown
var countdownLabel = Ti.UI.createLabel({
	top:'40%',
	width:textWidth
});

countdownLabel.text = "Starting in " + Math.floor(countdownSeconds/60)+":"+countdownSeconds%60 + " Minutes.";
// timer for Starting in ...
var my_timer = new countDown(0,countdownSeconds,
	function() {
    	countdownLabel.text = "Starting in " + timeString(my_timer) + " Minutes.";
	},
    function() {
    	if(userSelection){
    		startZwarm();
    	} else {
    		closeView();
    	}
   }
).start();

var startZwarm = function(){
	swarmLabel.backgroundColor='green';
	countdownLabel.text = "Finishing in " + Math.floor(zwarmDuration/60)+":"+zwarmDuration%60 + " Minutes.";
	var my_timer = new countDown(0,zwarmDuration,
		function(){
	    	countdownLabel.text = "Finishing in " + timeString(my_timer) + " Minutes.";
		},
		function(){
			alert('Photo List View');
		}
	).start();
}

swarmWin.add(swarmLabel);
swarmWin.add(countdownLabel);
toggleMediaButton(false);

swarmWin.open();<|endoftext|>


<commit_before>/**
 * Created by MinhTu on 3/31/2017.
 */
var config = {
    // basicUrl:  'https://damp-brushlands-61241.herokuapp.com',
    basicUrl : 'http://localhost:8000',
    // basicUrl : 'http://192.168.1.99:8000',
    countHost: 20,
    countPlayer : 200
};
<commit_msg>login with FB :(<commit_after>/**
 * Created by MinhTu on 3/31/2017.
 */e
var config = {
    basicUrl:  'https://damp-brushlands-61241.herokuapp.com',
    // basicUrl : 'http://localhost:8000',
    // basicUrl : 'http://192.168.1.99:8000',
    countHost: 20,
    countPlayer : 200
};
<|endoftext|>


<commit_before>function ajaxSetup(){
	$.ajaxSetup({
		beforeSend: function( xhr ) {
			var token = $("meta[name='_csrf']").attr("content");
			var header = $("meta[name='_csrf_header']").attr("content");
			xhr.setRequestHeader(header, token);
	 	},
	 	headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
	});
}

jQuery(document).ajaxStart(function(){
	var isAutocomplete = isTypeaheadActive($(arguments[0].currentTarget.activeElement));
	if(!isAutocomplete){
		$('#progressModal').modal('show');
	}
});

jQuery(document).ajaxStop(function(){
	$('#progressModal').modal('hide');
});

jQuery(document).ajaxComplete(function(){
	initBtnConfirmation();
	$('[data-toggle="tooltip"]').tooltip();
	$("td[rel='tooltip']").tooltip({
	    'placement': 'top',
	    'container':'body'
	});
	formatMask();
	fncUppercase();
	numbersOnly();
});

jQuery(document).ready(function(){
	initBtnConfirmation();
	initTimeField();
	initDateField();
	initDateFieldPeriodo();
	initNumberField();
	fncBlurCombo();
	fixCheckboxWithThymeleaf();
	initAutoComplete();
	formatMask();
	fncUppercase();
	numbersOnly();
	$('form').bootstrapValidator({});
	$("td[rel='tooltip']").tooltip({
	    'placement': 'top',
	    'container':'body'
	});
});<commit_msg>Colocando ajaxSetup no ready.<commit_after>function ajaxSetup(){
	$.ajaxSetup({
		beforeSend: function( xhr ) {
			var token = $("meta[name='_csrf']").attr("content");
			var header = $("meta[name='_csrf_header']").attr("content");
			xhr.setRequestHeader(header, token);
	 	},
	 	headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
	});
}

jQuery(document).ajaxStart(function(){
	var isAutocomplete = isTypeaheadActive($(arguments[0].currentTarget.activeElement));
	if(!isAutocomplete){
		$('#progressModal').modal('show');
	}
});

jQuery(document).ajaxStop(function(){
	$('#progressModal').modal('hide');
});

jQuery(document).ajaxError(function(){
	$('#progressModal').modal('hide');
});

jQuery(document).ajaxSuccess(function(){
	$('#progressModal').modal('hide');
});

jQuery(document).ajaxComplete(function(){
	initBtnConfirmation();
	$('[data-toggle="tooltip"]').tooltip();
	$("td[rel='tooltip']").tooltip({
	    'placement': 'top',
	    'container':'body'
	});
	formatMask();
	fncUppercase();
	numbersOnly();
});

jQuery(document).ready(function(){
	ajaxSetup();
	initBtnConfirmation();
	initTimeField();
	initDateField();
	initDateFieldPeriodo();
	initNumberField();
	fncBlurCombo();
	fixCheckboxWithThymeleaf();
	initAutoComplete();
	formatMask();
	fncUppercase();
	numbersOnly();
	$('form').bootstrapValidator({});
	$("td[rel='tooltip']").tooltip({
	    'placement': 'top',
	    'container':'body'
	});
});<|endoftext|>


<commit_before>'use strict';

angular.module('mean.listedecourse').config(['$stateProvider',
  function($stateProvider) {

    $stateProvider.state('Homepage', {
      url: '/listedecourse/home',
      templateUrl: 'listedecourse/views/index.html'
    });

  }
]);
<commit_msg>change url<commit_after>'use strict';

angular.module('mean.listedecourse').config(['$stateProvider','$locationProvider',
  function($stateProvider, $location) {
  	$location.html5Mode(html5pushstate);
    $location.hashPrefix('');

    $stateProvider.state('Homepage', {
      url: '/listedecourse/home',
      templateUrl: 'listedecourse/views/index.html'
    });

  }
]);
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Route.extend({
	createFeedFromGtfsService: Ember.inject.service('create-feed-from-gtfs'),
	model: function() {
    var oldModel = this.get('createFeedFromGtfsService').feedModel;
    this.store.unloadAll();
    var newModel = this.get('createFeedFromGtfsService').createFeedModel();
    // Copy over previous url & errors
    if (oldModel != null && oldModel.get('errors.length') > 0) {
      newModel.set('url', oldModel.get('url'));
      oldModel.get('errors').errorsFor('url').forEach(function(error){
        newModel.get('errors').add(error.attribute, error.message);
      });
    }
    return newModel
	},
  resetController: function(controller, isExiting, transition) {
    // Reset the controller loading state before transition
    if (isExiting) {
      controller.set('isLoading', false);
    }
  }
});
<commit_msg>Always reset controller<commit_after>import Ember from 'ember';

export default Ember.Route.extend({
	createFeedFromGtfsService: Ember.inject.service('create-feed-from-gtfs'),
	model: function() {
    var oldModel = this.get('createFeedFromGtfsService').feedModel;
    this.store.unloadAll();
    var newModel = this.get('createFeedFromGtfsService').createFeedModel();
    // Copy over previous url & errors
    if (oldModel != null && oldModel.get('errors.length') > 0) {
      newModel.set('url', oldModel.get('url'));
      oldModel.get('errors').errorsFor('url').forEach(function(error){
        newModel.get('errors').add(error.attribute, error.message);
      });
    }
    return newModel
	},
  resetController: function(controller, isExiting, transition) {
    // Reset the controller loading state before transition
    controller.set('isLoading', false);
  }
});
<|endoftext|>


<commit_before>/* @flow */

import path, { sep } from 'path'

type SingleCharString = string

type PathRestrictions = {
  pathMaxBytes: number,
  nameMaxBytes: number,
  dirNameMaxBytes: ?number,
  reservedChars: Set<SingleCharString>,
  reservedCharsRegExp: RegExp,
  forbiddenLastChars: Set<SingleCharString>,
  reservedNames: Set<string>
}

export type ReservedCharsIssue = {| type: 'reservedChars', name: string, platform: string, reservedChars?: Set<SingleCharString> |}
export type ReservedNameIssue = {| type: 'reservedName', name: string, platform: string, reservedName?: string |}
export type ForbiddenLastCharIssue = {| type: 'forbiddenLastChar', name: string, platform: string, forbiddenLastChar?: SingleCharString |}
export type NameMaxBytesIssue = {| type: 'nameMaxBytes', name: string, platform: string, nameMaxBytes: number |}
export type DirNameMaxBytesIssue = {| type: 'dirNameMaxBytes', name: string, platform: string, dirNameMaxBytes: number |}

// Describes a file/dir name issue so one could describe it in a user-friendly
// way: "File X cannot be saved on platform Y because it contains character Z"
type NameIssue =
  | ReservedCharsIssue
  | ReservedNameIssue
  | ForbiddenLastCharIssue
  | NameMaxBytesIssue
  | DirNameMaxBytesIssue

export type PathLengthIssue = {
  path: string,
  pathBytes?: number,
  pathMaxBytes?: number,
  platform: string
}

export type PathIssue =
  | (NameIssue & {path: string})
  | PathLengthIssue

function pathRestrictions (customs: Object): PathRestrictions {
  const reservedChars = customs.reservedChars || new Set()
  return Object.assign({
    dirNameMaxBytes: customs.dirNameMaxBytes || customs.nameMaxBytes,
    reservedChars,
    reservedCharsRegExp: new RegExp('[' +
      Array.from(reservedChars).join('')
        // Escape chars that would be interpreted by the RegExp
        .replace('\\', '\\\\') +
      ']', 'g'
    ),
    forbiddenLastChars: new Set(),
    reservedNames: new Set()
  }, customs)
}

// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
const win = pathRestrictions({
  pathMaxBytes: 1023, // MAX_PATH without nul
  nameMaxBytes: 1020, // pathMaxBytes without drive (ex: 'C:\')
  dirNameMaxBytes: 1007, // nameMaxBytes without an 8.3 filename + separator
  reservedChars: new Set('<>:"/\\|?*'),
  forbiddenLastChars: new Set('. '),
  reservedNames: new Set([
    'CON', 'PRN', 'AUX', 'NUL',
    'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
    'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
  ])
})

// See: /usr/include/sys/syslimits.h
const mac = pathRestrictions({
  pathMaxBytes: 1023, // PATH_MAX without nul
  nameMaxBytes: 255, // NAME_MAX
  reservedChars: new Set('/:') // macOS API forbids colons for legacy reasons
})

// See: /usr/include/linux/limits.h
const linux = pathRestrictions({
  pathMaxBytes: 4095, // PATH_MAX without nul
  nameMaxBytes: 255, // NAME_MAX
  reservedChars: new Set('/')
})

export default { win, mac, linux }

function restrictionsByPlatform (platform: string) {
  switch (platform) {
    case 'win32': return win
    case 'darwin': return mac
    case 'linux': return linux
    default: throw new Error(`Unsupported platform: ${platform}`)
  }
}

function detectReservedChars (name: string, restrictions: PathRestrictions): ?Array<string> {
  return name.match(restrictions.reservedCharsRegExp)
}

function detectForbiddenLastChar (name: string, restrictions: PathRestrictions): ?string {
  const lastChar = name.slice(-1)
  if (restrictions.forbiddenLastChars.has(lastChar)) return lastChar
}

function detectReservedName (name: string, restrictions: PathRestrictions): ?string {
  const upperCaseName = name.toUpperCase()
  const upperCaseBasename = path.basename(upperCaseName, path.extname(upperCaseName))
  if (restrictions.reservedNames.has(upperCaseBasename)) {
    return upperCaseBasename
  }
}

function detectNameLengthIssue (name: string, restrictions: PathRestrictions): ?number {
  const { nameMaxBytes } = restrictions
  const nameBytes = Buffer.byteLength(name) // TODO: utf16?
  if (nameBytes > nameMaxBytes) {
    return nameMaxBytes
  }
}

function detectDirNameLengthIssue (name: string, restrictions: PathRestrictions): ?number {
  const { dirNameMaxBytes } = restrictions
  if (dirNameMaxBytes == null) return detectNameLengthIssue(name, restrictions)
  // TODO: utf16?
  if (Buffer.byteLength(name) > dirNameMaxBytes) return dirNameMaxBytes
}

// Identifies file/dir name issues that will prevent local synchronization
export function detectNameIssues (name: string, type: string, platform: string): NameIssue[] {
  const restrictions = restrictionsByPlatform(platform)
  const issues = []

  const reservedChars = detectReservedChars(name, restrictions)
  if (reservedChars) {
    issues.push({type: 'reservedChars', name, platform, reservedChars: new Set(reservedChars)})
  }

  const reservedName = detectReservedName(name, restrictions)
  if (reservedName) {
    issues.push({type: 'reservedName', name, platform, reservedName})
  }

  const forbiddenLastChar = detectForbiddenLastChar(name, restrictions)
  if (forbiddenLastChar) {
    issues.push({type: 'forbiddenLastChar', name, platform, forbiddenLastChar})
  }

  if (type === 'folder') {
    const dirNameMaxBytes = detectDirNameLengthIssue(name, restrictions)
    if (dirNameMaxBytes) {
      issues.push({type: 'dirNameMaxBytes', name, platform, dirNameMaxBytes})
    }
  } else if (type === 'file') {
    const nameMaxBytes = detectNameLengthIssue(name, restrictions)
    if (nameMaxBytes) {
      issues.push({type: 'nameMaxBytes', name, platform, nameMaxBytes})
    }
  }

  return issues
}

// Identifies issues in every path item that will prevent local synchronization
export function detectPathIssues (path: string, type: string): Array<PathIssue> {
  const platform = process.platform
  const ancestorNames = path.split(sep)
  const basename = ancestorNames.pop()

  const pathIssues = detectNameIssues(basename, type, platform)
    .map(nameIssue => ({...nameIssue, path}))

  const recursivePathIssues = ancestorNames
    .reduceRight((previousIssues, name, index, pathComponents) => {
      const path = pathComponents.slice(0, index + 1).join(sep)
      const nameIssues = detectNameIssues(name, 'folder', platform)

      return previousIssues.concat(
        nameIssues.map(issue => ({
          ...issue,
          path
        })
      ))
    }, pathIssues)

  return recursivePathIssues.filter(issue => issue != null)
}

export function detectPathLengthIssue (path: string, platform: string): ?PathLengthIssue {
  const { pathMaxBytes } = restrictionsByPlatform(platform)
  const pathBytes = Buffer.byteLength(path) // TODO: utf16?
  if (pathBytes > pathMaxBytes) {
    // FIXME: The name is useless, but could not find a way to make flow happy
    // without it.
    return {name: path, path, pathBytes, pathMaxBytes, platform}
  }
}
<commit_msg>Fix Windows path limit values<commit_after>/* @flow */

import path, { sep } from 'path'

type SingleCharString = string

type PathRestrictions = {
  pathMaxBytes: number,
  nameMaxBytes: number,
  dirNameMaxBytes: ?number,
  reservedChars: Set<SingleCharString>,
  reservedCharsRegExp: RegExp,
  forbiddenLastChars: Set<SingleCharString>,
  reservedNames: Set<string>
}

export type ReservedCharsIssue = {| type: 'reservedChars', name: string, platform: string, reservedChars?: Set<SingleCharString> |}
export type ReservedNameIssue = {| type: 'reservedName', name: string, platform: string, reservedName?: string |}
export type ForbiddenLastCharIssue = {| type: 'forbiddenLastChar', name: string, platform: string, forbiddenLastChar?: SingleCharString |}
export type NameMaxBytesIssue = {| type: 'nameMaxBytes', name: string, platform: string, nameMaxBytes: number |}
export type DirNameMaxBytesIssue = {| type: 'dirNameMaxBytes', name: string, platform: string, dirNameMaxBytes: number |}

// Describes a file/dir name issue so one could describe it in a user-friendly
// way: "File X cannot be saved on platform Y because it contains character Z"
type NameIssue =
  | ReservedCharsIssue
  | ReservedNameIssue
  | ForbiddenLastCharIssue
  | NameMaxBytesIssue
  | DirNameMaxBytesIssue

export type PathLengthIssue = {
  path: string,
  pathBytes?: number,
  pathMaxBytes?: number,
  platform: string
}

export type PathIssue =
  | (NameIssue & {path: string})
  | PathLengthIssue

function pathRestrictions (customs: Object): PathRestrictions {
  const reservedChars = customs.reservedChars || new Set()
  return Object.assign({
    dirNameMaxBytes: customs.dirNameMaxBytes || customs.nameMaxBytes,
    reservedChars,
    reservedCharsRegExp: new RegExp('[' +
      Array.from(reservedChars).join('')
        // Escape chars that would be interpreted by the RegExp
        .replace('\\', '\\\\') +
      ']', 'g'
    ),
    forbiddenLastChars: new Set(),
    reservedNames: new Set()
  }, customs)
}

// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
const win = pathRestrictions({
  pathMaxBytes: 259, // MAX_PATH without nul
  nameMaxBytes: 256, // pathMaxBytes without drive (ex: 'C:\')
  dirNameMaxBytes: 243, // nameMaxBytes without an 8.3 filename + separator
  reservedChars: new Set('<>:"/\\|?*'),
  forbiddenLastChars: new Set('. '),
  reservedNames: new Set([
    'CON', 'PRN', 'AUX', 'NUL',
    'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
    'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
  ])
})

// See: /usr/include/sys/syslimits.h
const mac = pathRestrictions({
  pathMaxBytes: 1023, // PATH_MAX without nul
  nameMaxBytes: 255, // NAME_MAX
  reservedChars: new Set('/:') // macOS API forbids colons for legacy reasons
})

// See: /usr/include/linux/limits.h
const linux = pathRestrictions({
  pathMaxBytes: 4095, // PATH_MAX without nul
  nameMaxBytes: 255, // NAME_MAX
  reservedChars: new Set('/')
})

export default { win, mac, linux }

function restrictionsByPlatform (platform: string) {
  switch (platform) {
    case 'win32': return win
    case 'darwin': return mac
    case 'linux': return linux
    default: throw new Error(`Unsupported platform: ${platform}`)
  }
}

function detectReservedChars (name: string, restrictions: PathRestrictions): ?Array<string> {
  return name.match(restrictions.reservedCharsRegExp)
}

function detectForbiddenLastChar (name: string, restrictions: PathRestrictions): ?string {
  const lastChar = name.slice(-1)
  if (restrictions.forbiddenLastChars.has(lastChar)) return lastChar
}

function detectReservedName (name: string, restrictions: PathRestrictions): ?string {
  const upperCaseName = name.toUpperCase()
  const upperCaseBasename = path.basename(upperCaseName, path.extname(upperCaseName))
  if (restrictions.reservedNames.has(upperCaseBasename)) {
    return upperCaseBasename
  }
}

function detectNameLengthIssue (name: string, restrictions: PathRestrictions): ?number {
  const { nameMaxBytes } = restrictions
  const nameBytes = Buffer.byteLength(name) // TODO: utf16?
  if (nameBytes > nameMaxBytes) {
    return nameMaxBytes
  }
}

function detectDirNameLengthIssue (name: string, restrictions: PathRestrictions): ?number {
  const { dirNameMaxBytes } = restrictions
  if (dirNameMaxBytes == null) return detectNameLengthIssue(name, restrictions)
  // TODO: utf16?
  if (Buffer.byteLength(name) > dirNameMaxBytes) return dirNameMaxBytes
}

// Identifies file/dir name issues that will prevent local synchronization
export function detectNameIssues (name: string, type: string, platform: string): NameIssue[] {
  const restrictions = restrictionsByPlatform(platform)
  const issues = []

  const reservedChars = detectReservedChars(name, restrictions)
  if (reservedChars) {
    issues.push({type: 'reservedChars', name, platform, reservedChars: new Set(reservedChars)})
  }

  const reservedName = detectReservedName(name, restrictions)
  if (reservedName) {
    issues.push({type: 'reservedName', name, platform, reservedName})
  }

  const forbiddenLastChar = detectForbiddenLastChar(name, restrictions)
  if (forbiddenLastChar) {
    issues.push({type: 'forbiddenLastChar', name, platform, forbiddenLastChar})
  }

  if (type === 'folder') {
    const dirNameMaxBytes = detectDirNameLengthIssue(name, restrictions)
    if (dirNameMaxBytes) {
      issues.push({type: 'dirNameMaxBytes', name, platform, dirNameMaxBytes})
    }
  } else if (type === 'file') {
    const nameMaxBytes = detectNameLengthIssue(name, restrictions)
    if (nameMaxBytes) {
      issues.push({type: 'nameMaxBytes', name, platform, nameMaxBytes})
    }
  }

  return issues
}

// Identifies issues in every path item that will prevent local synchronization
export function detectPathIssues (path: string, type: string): Array<PathIssue> {
  const platform = process.platform
  const ancestorNames = path.split(sep)
  const basename = ancestorNames.pop()

  const pathIssues = detectNameIssues(basename, type, platform)
    .map(nameIssue => ({...nameIssue, path}))

  const recursivePathIssues = ancestorNames
    .reduceRight((previousIssues, name, index, pathComponents) => {
      const path = pathComponents.slice(0, index + 1).join(sep)
      const nameIssues = detectNameIssues(name, 'folder', platform)

      return previousIssues.concat(
        nameIssues.map(issue => ({
          ...issue,
          path
        })
      ))
    }, pathIssues)

  return recursivePathIssues.filter(issue => issue != null)
}

export function detectPathLengthIssue (path: string, platform: string): ?PathLengthIssue {
  const { pathMaxBytes } = restrictionsByPlatform(platform)
  const pathBytes = Buffer.byteLength(path) // TODO: utf16?
  if (pathBytes > pathMaxBytes) {
    // FIXME: The name is useless, but could not find a way to make flow happy
    // without it.
    return {name: path, path, pathBytes, pathMaxBytes, platform}
  }
}
<|endoftext|>


<commit_before>describe("go.components.tables", function() {

  describe(".TableFormView", function() {

    var $buttons;
    var $form;
    var tableFormView;
    beforeEach(function() {

      $buttons = $('

');
      $buttons.append('<button data-action="delete" disabled="disabled">Delete</button');
      $buttons.append('<button data-action="edit" disabled="disabled">Edit</button');

      $form = $('<form>

</form>');
      $form.find('thead').append('
<input name="h1" type="checkbox">	head
');
      $form.find('tbody').append('
<input name="c1" type="checkbox">	body
');
      $form.find('tbody').append('
<input name="c2" type="checkbox">	body
');

      tableFormView = new go.components.tables.TableFormView({
        el: $form,
        onCheckedSelector: $buttons.find('button')
      });

    });

    it('should toggle the header checkbox when all checkboxes are checked', function() {

      assert.isFalse($form.find('thead input:checkbox').prop('checked'));
      var $cbs = $form.find('tbody tr td:first-child');
      $cbs.trigger('click');
      assert.isTrue($form.find('thead input:checkbox').prop('checked'));
    });

    /*
    it('should execute `onCheckedCallback` event when a checkbox is toggled', function(done) {

      tableFormView.options.onCheckedCallback = function() {
        assert.isTrue($cb.prop('checked'));
        done();
      };

      var $cb = $form.find('tbody input:checkbox').eq(0);
      $cb.trigger('click');
    });

    it('should toggle disabled on elements from `onCheckedSelector.`', function() {

      // enabled
      var $cb = $form.find('tbody input:checkbox').eq(0);
      $cb.trigger('click');
      assert.isTrue($cb.prop('checked'));

      // disabled
      $cb.trigger('click');
      assert.isFalse($cb.prop('checked'));
      assert.isTrue($buttons.find('button').eq(0).prop('disabled'));

    });
    */

  });
});
<commit_msg>Uncomment failing TableFormView view tests, use skip instead<commit_after>describe("go.components.tables", function() {

  describe.skip(".TableFormView", function() {

    var $buttons;
    var $form;
    var tableFormView;
    beforeEach(function() {

      $buttons = $('

');
      $buttons.append('<button data-action="delete" disabled="disabled">Delete</button');
      $buttons.append('<button data-action="edit" disabled="disabled">Edit</button');

      $form = $('<form>

</form>');
      $form.find('thead').append('
<input name="h1" type="checkbox">	head
');
      $form.find('tbody').append('
<input name="c1" type="checkbox">	body
');
      $form.find('tbody').append('
<input name="c2" type="checkbox">	body
');

      tableFormView = new go.components.tables.TableFormView({
        el: $form,
        onCheckedSelector: $buttons.find('button')
      });

    });

    it('should toggle the header checkbox when all checkboxes are checked', function() {

      assert.isFalse($form.find('thead input:checkbox').prop('checked'));
      var $cbs = $form.find('tbody tr td:first-child');
      $cbs.trigger('click');
      assert.isTrue($form.find('thead input:checkbox').prop('checked'));
    });

    it('should execute `onCheckedCallback` event when a checkbox is toggled', function(done) {

      tableFormView.options.onCheckedCallback = function() {
        assert.isTrue($cb.prop('checked'));
        done();
      };

      var $cb = $form.find('tbody input:checkbox').eq(0);
      $cb.trigger('click');
    });

    it('should toggle disabled on elements from `onCheckedSelector.`', function() {

      // enabled
      var $cb = $form.find('tbody input:checkbox').eq(0);
      $cb.trigger('click');
      assert.isTrue($cb.prop('checked'));

      // disabled
      $cb.trigger('click');
      assert.isFalse($cb.prop('checked'));
      assert.isTrue($buttons.find('button').eq(0).prop('disabled'));

    });

  });
});
<|endoftext|>


<commit_before>import { h, defineComponent, ref, markRaw, TransitionGroup } from 'vue'

import QAvatar from '../components/avatar/QAvatar.js'
import QIcon from '../components/icon/QIcon.js'
import QBtn from '../components/btn/QBtn.js'
import QSpinner from '../components/spinner/QSpinner.js'

import { noop } from '../utils/event.js'
import { createGlobalNode } from '../utils/private/global-nodes.js'
import { createChildApp } from '../install-quasar.js'

let uid = 0, addNotification
const defaults = {}

const positionList = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right',
  'top', 'bottom', 'left', 'right', 'center'
]

const badgePositions = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right'
]

const notifTypes = {
  positive: {
    icon: $q => $q.iconSet.type.positive,
    color: 'positive'
  },

  negative: {
    icon: $q => $q.iconSet.type.negative,
    color: 'negative'
  },

  warning: {
    icon: $q => $q.iconSet.type.warning,
    color: 'warning',
    textColor: 'dark'
  },

  info: {
    icon: $q => $q.iconSet.type.info,
    color: 'info'
  },

  ongoing: {
    group: false,
    timeout: 0,
    spinner: true,
    color: 'grey-8'
  }
}

const groups = {}
const positionClass = {}

function logError (error, config) {
  console.error(`Notify: ${ error }`, config)
  return false
}

function getComponent ($q) {
  return defineComponent({
    name: 'QNotifications',

    setup () {
      const notificationsList = {}
      const notifRefs = []

      positionList.forEach(pos => {
        notificationsList[ pos ] = ref([])

        const
          vert = [ 'left', 'center', 'right' ].includes(pos) === true ? 'center' : (pos.indexOf('top') > -1 ? 'top' : 'bottom'),
          align = pos.indexOf('left') > -1 ? 'start' : (pos.indexOf('right') > -1 ? 'end' : 'center'),
          classes = [ 'left', 'right' ].includes(pos) ? `items-${ pos === 'left' ? 'start' : 'end' } justify-center` : (pos === 'center' ? 'flex-center' : `items-${ align }`)

        positionClass[ pos ] = `q-notifications__list q-notifications__list--${ vert } fixed column no-wrap ${ classes }`
      })

      addNotification = (config, originalApi) => {
        if (!config) {
          return logError('parameter required')
        }

        let Api
        const notif = { textColor: 'white' }

        if (config.ignoreDefaults !== true) {
          Object.assign(notif, defaults)
        }

        if (Object(config) !== config) {
          if (notif.type) {
            Object.assign(notif, notifTypes[ notif.type ])
          }

          config = { message: config }
        }

        Object.assign(notif, notifTypes[ config.type || notif.type ], config)

        if (typeof notif.icon === 'function') {
          notif.icon = notif.icon($q)
        }

        if (!notif.spinner) {
          notif.spinner = false
        }
        else {
          if (notif.spinner === true) {
            notif.spinner = QSpinner
          }

          notif.spinner = markRaw(notif.spinner)
        }

        notif.meta = {
          hasMedia: Boolean(notif.spinner !== false || notif.icon || notif.avatar)
        }

        if (notif.position) {
          if (positionList.includes(notif.position) === false) {
            return logError('wrong position', config)
          }
        }
        else {
          notif.position = 'bottom'
        }

        if (notif.timeout === void 0) {
          notif.timeout = 5000
        }
        else {
          const t = parseInt(notif.timeout, 10)
          if (isNaN(t) || t < 0) {
            return logError('wrong timeout', config)
          }
          notif.timeout = t
        }

        if (notif.timeout === 0) {
          notif.progress = false
        }
        else if (notif.progress === true) {
          notif.meta.progressClass = 'q-notification__progress' + (
            notif.progressClass
              ? ` ${ notif.progressClass }`
              : ''
          )

          notif.meta.progressStyle = {
            animationDuration: `${ notif.timeout + 1000 }ms`
          }
        }

        const actions = (
          Array.isArray(config.actions) === true
            ? config.actions
            : []
        ).concat(
          config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true
            ? defaults.actions
            : []
        ).concat(
          notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true
            ? notifTypes[ config.type ].actions
            : []
        )

        notif.closeBtn && actions.push({
          label: typeof notif.closeBtn === 'string'
            ? notif.closeBtn
            : $q.lang.label.close
        })

        notif.actions = actions.map(({ handler, noDismiss, ...item }) => ({
          flat: true,
          ...item,
          onClick: typeof handler === 'function'
            ? () => {
                handler()
                noDismiss !== true && dismiss()
              }
            : () => { dismiss() }
        }))

        if (notif.multiLine === void 0) {
          notif.multiLine = notif.actions.length > 1
        }

        Object.assign(notif.meta, {
          class: 'q-notification row items-stretch'
            + ` q-notification--${ notif.multiLine === true ? 'multi-line' : 'standard' }`
            + (notif.color !== void 0 ? ` bg-${ notif.color }` : '')
            + (notif.textColor !== void 0 ? ` text-${ notif.textColor }` : '')
            + (notif.classes !== void 0 ? ` ${ notif.classes }` : ''),

          wrapperClass: 'q-notification__wrapper col relative-position border-radius-inherit '
            + (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center'),

          contentClass: 'q-notification__content row items-center'
            + (notif.multiLine === true ? '' : ' col'),

          attrs: {
            role: 'alert',
            ...notif.attrs
          }
        })

        if (notif.group === false) {
          notif.group = void 0
          notif.meta.group = void 0
        }
        else {
          if (notif.group === void 0 || notif.group === true) {
            // do not replace notifications with different buttons
            notif.group = [
              notif.message,
              notif.caption,
              notif.multiline
            ].concat(
              notif.actions.map(props => `${ props.label }*${ props.icon }`)
            ).join('|')
          }

          notif.meta.group = notif.group + '|' + notif.position
        }

        if (notif.actions.length === 0) {
          notif.actions = void 0
        }
        else {
          notif.meta.actionsClass = 'q-notification__actions row items-center '
            + (notif.multiLine === true ? 'justify-end' : 'col-auto')
            + (notif.meta.hasMedia === true ? ' q-notification__actions--with-media' : '')
        }

        if (originalApi !== void 0) {
          // reset timeout if any
          clearTimeout(originalApi.notif.meta.timer)

          // retain uid
          notif.meta.uid = originalApi.notif.meta.uid

          // replace notif
          const index = notificationsList[ notif.position ].value.indexOf(originalApi.notif)
          notificationsList[ notif.position ].value[ index ] = notif
        }
        else {
          const original = groups[ notif.meta.group ]

          // woohoo, it's a new notification
          if (original === void 0) {
            notif.meta.uid = uid++
            notif.meta.badge = 1

            if ([ 'left', 'right', 'center' ].indexOf(notif.position) !== -1) {
              notificationsList[ notif.position ].value.splice(
                Math.floor(notificationsList[ notif.position ].value.length / 2),
                0,
                notif
              )
            }
            else {
              const action = notif.position.indexOf('top') > -1 ? 'unshift' : 'push'
              notificationsList[ notif.position ].value[ action ](notif)
            }

            if (notif.group !== void 0) {
              groups[ notif.meta.group ] = notif
            }
          }
          // ok, so it's NOT a new one
          else {
            // reset timeout if any
            clearTimeout(original.meta.timer)

            if (notif.badgePosition !== void 0) {
              if (badgePositions.includes(notif.badgePosition) === false) {
                return logError('wrong badgePosition', config)
              }
            }
            else {
              notif.badgePosition = `top-${ notif.position.indexOf('left') > -1 ? 'right' : 'left' }`
            }

            notif.meta.uid = original.meta.uid
            notif.meta.badge = original.meta.badge + 1
            notif.meta.badgeClass = `q-notification__badge q-notification__badge--${ notif.badgePosition }`
              + (notif.badgeColor !== void 0 ? ` bg-${ notif.badgeColor }` : '')
              + (notif.badgeTextColor !== void 0 ? ` text-${ notif.badgeTextColor }` : '')
              + (notif.badgeClass ? ` ${ notif.badgeClass }` : '')

            const index = notificationsList[ notif.position ].value.indexOf(original)
            notificationsList[ notif.position ].value[ index ] = groups[ notif.meta.group ] = notif
          }
        }

        const dismiss = () => {
          remove(notif)
          Api = void 0
        }

        if (notif.timeout > 0) {
          notif.meta.timer = setTimeout(() => {
            dismiss()
          }, notif.timeout + /* show duration */ 1000)
        }

        // only non-groupable can be updated
        if (notif.group !== void 0) {
          return props => {
            if (props !== void 0) {
              logError('trying to update a grouped one which is forbidden', config)
            }
            else {
              dismiss()
            }
          }
        }

        Api = {
          dismiss,
          config,
          notif
        }

        if (originalApi !== void 0) {
          Object.assign(originalApi, Api)
          return
        }

        return props => {
          // if notification wasn't previously dismissed
          if (Api !== void 0) {
            // if no params, then we must dismiss the notification
            if (props === void 0) {
              Api.dismiss()
            }
            // otherwise we're updating it
            else {
              const newNotif = Object.assign({}, Api.config, props, {
                group: false,
                position: notif.position
              })

              addNotification(newNotif, Api)
            }
          }
        }
      }

      function remove (notif) {
        clearTimeout(notif.meta.timer)

        const index = notificationsList[ notif.position ].value.indexOf(notif)
        if (index !== -1) {
          if (notif.group !== void 0) {
            delete groups[ notif.meta.group ]
          }

          const el = notifRefs[ '' + notif.meta.uid ]

          if (el) {
            const { width, height } = getComputedStyle(el)

            el.style.left = `${ el.offsetLeft }px`
            el.style.width = width
            el.style.height = height
          }

          notificationsList[ notif.position ].value.splice(index, 1)

          if (typeof notif.onDismiss === 'function') {
            notif.onDismiss()
          }
        }
      }

      return () => h('div', { class: 'q-notifications' }, positionList.map(pos => {
        return h(TransitionGroup, {
          key: pos,
          class: positionClass[ pos ],
          tag: 'div',
          name: `q-notification--${ pos }`
        }, () => notificationsList[ pos ].value.map(notif => {
          let msgChild

          const meta = notif.meta
          const msgData = { class: 'q-notification__message col' }

          if (notif.html === true) {
            msgData.innerHTML = notif.caption
              ? `<div>${ notif.message }</div><div class="q-notification__caption">${ notif.caption }</div>`
              : notif.message
          }
          else {
            const msgNode = [ notif.message ]
            msgChild = notif.caption
              ? [
                  h('div', msgNode),
                  h('div', { class: 'q-notification__caption' }, [ notif.caption ])
                ]
              : msgNode
          }

          const mainChild = []

          if (meta.hasMedia === true) {
            if (notif.spinner !== false) {
              mainChild.push(
                h(notif.spinner, { class: 'q-notification__spinner' })
              )
            }
            else if (notif.icon) {
              mainChild.push(
                h(QIcon, {
                  class: 'q-notification__icon',
                  name: notif.icon,
                  role: 'img'
                })
              )
            }
            else if (notif.avatar) {
              mainChild.push(
                h(QAvatar, {
                  class: 'q-notification__avatar'
                }, () => h('img', { src: notif.avatar, 'aria-hidden': 'true' }))
              )
            }
          }

          mainChild.push(
            h('div', msgData, msgChild)
          )

          const child = [
            h('div', { class: meta.contentClass }, mainChild)
          ]

          notif.progress === true && child.push(
            h('div', {
              key: `${ meta.uid }|p|${ meta.badge }`,
              class: meta.progressClass,
              style: meta.progressStyle
            })
          )

          notif.actions !== void 0 && child.push(
            h('div', {
              class: meta.actionsClass
            }, notif.actions.map(props => h(QBtn, props)))
          )

          meta.badge > 1 && child.push(
            h('div', {
              key: `${ meta.uid }|${ meta.badge }`,
              class: notif.meta.badgeClass,
              style: notif.badgeStyle
            }, [ meta.badge ])
          )

          return h('div', {
            ref: el => { notifRefs[ '' + meta.uid ] = el },
            key: meta.uid,
            class: meta.class,
            ...meta.attrs
          }, [
            h('div', { class: meta.wrapperClass }, child)
          ])
        }))
      }))
    }
  })
}

export default {
  create (opts) {
    return __QUASAR_SSR_SERVER__
      ? noop
      : addNotification(opts)
  },

  setDefaults (opts) {
    if (__QUASAR_SSR_SERVER__ !== true) {
      opts === Object(opts) && Object.assign(defaults, opts)
    }
  },

  registerType (typeName, typeOpts) {
    if (__QUASAR_SSR_SERVER__ !== true && typeOpts === Object(typeOpts)) {
      notifTypes[ typeName ] = typeOpts
    }
  },

  install ({ $q, parentApp }) {
    $q.notify = this.create
    $q.notify.setDefaults = this.setDefaults
    $q.notify.registerType = this.registerType

    if ($q.config.notify !== void 0) {
      this.setDefaults($q.config.notify)
    }

    if (this.__installed !== true) {
      const el = createGlobalNode('q-notify')
      createChildApp(getComponent($q), parentApp).mount(el)
    }
  }
}
<commit_msg>fix(Notify): SSR support<commit_after>import { h, defineComponent, ref, markRaw, TransitionGroup } from 'vue'

import QAvatar from '../components/avatar/QAvatar.js'
import QIcon from '../components/icon/QIcon.js'
import QBtn from '../components/btn/QBtn.js'
import QSpinner from '../components/spinner/QSpinner.js'

import { noop } from '../utils/event.js'
import { createGlobalNode } from '../utils/private/global-nodes.js'
import { createChildApp } from '../install-quasar.js'

let uid = 0, addNotification
const defaults = {}

const positionList = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right',
  'top', 'bottom', 'left', 'right', 'center'
]

const badgePositions = [
  'top-left', 'top-right',
  'bottom-left', 'bottom-right'
]

const notifTypes = {
  positive: {
    icon: $q => $q.iconSet.type.positive,
    color: 'positive'
  },

  negative: {
    icon: $q => $q.iconSet.type.negative,
    color: 'negative'
  },

  warning: {
    icon: $q => $q.iconSet.type.warning,
    color: 'warning',
    textColor: 'dark'
  },

  info: {
    icon: $q => $q.iconSet.type.info,
    color: 'info'
  },

  ongoing: {
    group: false,
    timeout: 0,
    spinner: true,
    color: 'grey-8'
  }
}

const groups = {}
const positionClass = {}

function logError (error, config) {
  console.error(`Notify: ${ error }`, config)
  return false
}

function getComponent ($q) {
  return defineComponent({
    name: 'QNotifications',

    setup () {
      const notificationsList = {}
      const notifRefs = []

      positionList.forEach(pos => {
        notificationsList[ pos ] = ref([])

        const
          vert = [ 'left', 'center', 'right' ].includes(pos) === true ? 'center' : (pos.indexOf('top') > -1 ? 'top' : 'bottom'),
          align = pos.indexOf('left') > -1 ? 'start' : (pos.indexOf('right') > -1 ? 'end' : 'center'),
          classes = [ 'left', 'right' ].includes(pos) ? `items-${ pos === 'left' ? 'start' : 'end' } justify-center` : (pos === 'center' ? 'flex-center' : `items-${ align }`)

        positionClass[ pos ] = `q-notifications__list q-notifications__list--${ vert } fixed column no-wrap ${ classes }`
      })

      addNotification = (config, originalApi) => {
        if (!config) {
          return logError('parameter required')
        }

        let Api
        const notif = { textColor: 'white' }

        if (config.ignoreDefaults !== true) {
          Object.assign(notif, defaults)
        }

        if (Object(config) !== config) {
          if (notif.type) {
            Object.assign(notif, notifTypes[ notif.type ])
          }

          config = { message: config }
        }

        Object.assign(notif, notifTypes[ config.type || notif.type ], config)

        if (typeof notif.icon === 'function') {
          notif.icon = notif.icon($q)
        }

        if (!notif.spinner) {
          notif.spinner = false
        }
        else {
          if (notif.spinner === true) {
            notif.spinner = QSpinner
          }

          notif.spinner = markRaw(notif.spinner)
        }

        notif.meta = {
          hasMedia: Boolean(notif.spinner !== false || notif.icon || notif.avatar)
        }

        if (notif.position) {
          if (positionList.includes(notif.position) === false) {
            return logError('wrong position', config)
          }
        }
        else {
          notif.position = 'bottom'
        }

        if (notif.timeout === void 0) {
          notif.timeout = 5000
        }
        else {
          const t = parseInt(notif.timeout, 10)
          if (isNaN(t) || t < 0) {
            return logError('wrong timeout', config)
          }
          notif.timeout = t
        }

        if (notif.timeout === 0) {
          notif.progress = false
        }
        else if (notif.progress === true) {
          notif.meta.progressClass = 'q-notification__progress' + (
            notif.progressClass
              ? ` ${ notif.progressClass }`
              : ''
          )

          notif.meta.progressStyle = {
            animationDuration: `${ notif.timeout + 1000 }ms`
          }
        }

        const actions = (
          Array.isArray(config.actions) === true
            ? config.actions
            : []
        ).concat(
          config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true
            ? defaults.actions
            : []
        ).concat(
          notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true
            ? notifTypes[ config.type ].actions
            : []
        )

        notif.closeBtn && actions.push({
          label: typeof notif.closeBtn === 'string'
            ? notif.closeBtn
            : $q.lang.label.close
        })

        notif.actions = actions.map(({ handler, noDismiss, ...item }) => ({
          flat: true,
          ...item,
          onClick: typeof handler === 'function'
            ? () => {
                handler()
                noDismiss !== true && dismiss()
              }
            : () => { dismiss() }
        }))

        if (notif.multiLine === void 0) {
          notif.multiLine = notif.actions.length > 1
        }

        Object.assign(notif.meta, {
          class: 'q-notification row items-stretch'
            + ` q-notification--${ notif.multiLine === true ? 'multi-line' : 'standard' }`
            + (notif.color !== void 0 ? ` bg-${ notif.color }` : '')
            + (notif.textColor !== void 0 ? ` text-${ notif.textColor }` : '')
            + (notif.classes !== void 0 ? ` ${ notif.classes }` : ''),

          wrapperClass: 'q-notification__wrapper col relative-position border-radius-inherit '
            + (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center'),

          contentClass: 'q-notification__content row items-center'
            + (notif.multiLine === true ? '' : ' col'),

          attrs: {
            role: 'alert',
            ...notif.attrs
          }
        })

        if (notif.group === false) {
          notif.group = void 0
          notif.meta.group = void 0
        }
        else {
          if (notif.group === void 0 || notif.group === true) {
            // do not replace notifications with different buttons
            notif.group = [
              notif.message,
              notif.caption,
              notif.multiline
            ].concat(
              notif.actions.map(props => `${ props.label }*${ props.icon }`)
            ).join('|')
          }

          notif.meta.group = notif.group + '|' + notif.position
        }

        if (notif.actions.length === 0) {
          notif.actions = void 0
        }
        else {
          notif.meta.actionsClass = 'q-notification__actions row items-center '
            + (notif.multiLine === true ? 'justify-end' : 'col-auto')
            + (notif.meta.hasMedia === true ? ' q-notification__actions--with-media' : '')
        }

        if (originalApi !== void 0) {
          // reset timeout if any
          clearTimeout(originalApi.notif.meta.timer)

          // retain uid
          notif.meta.uid = originalApi.notif.meta.uid

          // replace notif
          const index = notificationsList[ notif.position ].value.indexOf(originalApi.notif)
          notificationsList[ notif.position ].value[ index ] = notif
        }
        else {
          const original = groups[ notif.meta.group ]

          // woohoo, it's a new notification
          if (original === void 0) {
            notif.meta.uid = uid++
            notif.meta.badge = 1

            if ([ 'left', 'right', 'center' ].indexOf(notif.position) !== -1) {
              notificationsList[ notif.position ].value.splice(
                Math.floor(notificationsList[ notif.position ].value.length / 2),
                0,
                notif
              )
            }
            else {
              const action = notif.position.indexOf('top') > -1 ? 'unshift' : 'push'
              notificationsList[ notif.position ].value[ action ](notif)
            }

            if (notif.group !== void 0) {
              groups[ notif.meta.group ] = notif
            }
          }
          // ok, so it's NOT a new one
          else {
            // reset timeout if any
            clearTimeout(original.meta.timer)

            if (notif.badgePosition !== void 0) {
              if (badgePositions.includes(notif.badgePosition) === false) {
                return logError('wrong badgePosition', config)
              }
            }
            else {
              notif.badgePosition = `top-${ notif.position.indexOf('left') > -1 ? 'right' : 'left' }`
            }

            notif.meta.uid = original.meta.uid
            notif.meta.badge = original.meta.badge + 1
            notif.meta.badgeClass = `q-notification__badge q-notification__badge--${ notif.badgePosition }`
              + (notif.badgeColor !== void 0 ? ` bg-${ notif.badgeColor }` : '')
              + (notif.badgeTextColor !== void 0 ? ` text-${ notif.badgeTextColor }` : '')
              + (notif.badgeClass ? ` ${ notif.badgeClass }` : '')

            const index = notificationsList[ notif.position ].value.indexOf(original)
            notificationsList[ notif.position ].value[ index ] = groups[ notif.meta.group ] = notif
          }
        }

        const dismiss = () => {
          remove(notif)
          Api = void 0
        }

        if (notif.timeout > 0) {
          notif.meta.timer = setTimeout(() => {
            dismiss()
          }, notif.timeout + /* show duration */ 1000)
        }

        // only non-groupable can be updated
        if (notif.group !== void 0) {
          return props => {
            if (props !== void 0) {
              logError('trying to update a grouped one which is forbidden', config)
            }
            else {
              dismiss()
            }
          }
        }

        Api = {
          dismiss,
          config,
          notif
        }

        if (originalApi !== void 0) {
          Object.assign(originalApi, Api)
          return
        }

        return props => {
          // if notification wasn't previously dismissed
          if (Api !== void 0) {
            // if no params, then we must dismiss the notification
            if (props === void 0) {
              Api.dismiss()
            }
            // otherwise we're updating it
            else {
              const newNotif = Object.assign({}, Api.config, props, {
                group: false,
                position: notif.position
              })

              addNotification(newNotif, Api)
            }
          }
        }
      }

      function remove (notif) {
        clearTimeout(notif.meta.timer)

        const index = notificationsList[ notif.position ].value.indexOf(notif)
        if (index !== -1) {
          if (notif.group !== void 0) {
            delete groups[ notif.meta.group ]
          }

          const el = notifRefs[ '' + notif.meta.uid ]

          if (el) {
            const { width, height } = getComputedStyle(el)

            el.style.left = `${ el.offsetLeft }px`
            el.style.width = width
            el.style.height = height
          }

          notificationsList[ notif.position ].value.splice(index, 1)

          if (typeof notif.onDismiss === 'function') {
            notif.onDismiss()
          }
        }
      }

      return () => h('div', { class: 'q-notifications' }, positionList.map(pos => {
        return h(TransitionGroup, {
          key: pos,
          class: positionClass[ pos ],
          tag: 'div',
          name: `q-notification--${ pos }`
        }, () => notificationsList[ pos ].value.map(notif => {
          let msgChild

          const meta = notif.meta
          const msgData = { class: 'q-notification__message col' }

          if (notif.html === true) {
            msgData.innerHTML = notif.caption
              ? `<div>${ notif.message }</div><div class="q-notification__caption">${ notif.caption }</div>`
              : notif.message
          }
          else {
            const msgNode = [ notif.message ]
            msgChild = notif.caption
              ? [
                  h('div', msgNode),
                  h('div', { class: 'q-notification__caption' }, [ notif.caption ])
                ]
              : msgNode
          }

          const mainChild = []

          if (meta.hasMedia === true) {
            if (notif.spinner !== false) {
              mainChild.push(
                h(notif.spinner, { class: 'q-notification__spinner' })
              )
            }
            else if (notif.icon) {
              mainChild.push(
                h(QIcon, {
                  class: 'q-notification__icon',
                  name: notif.icon,
                  role: 'img'
                })
              )
            }
            else if (notif.avatar) {
              mainChild.push(
                h(QAvatar, {
                  class: 'q-notification__avatar'
                }, () => h('img', { src: notif.avatar, 'aria-hidden': 'true' }))
              )
            }
          }

          mainChild.push(
            h('div', msgData, msgChild)
          )

          const child = [
            h('div', { class: meta.contentClass }, mainChild)
          ]

          notif.progress === true && child.push(
            h('div', {
              key: `${ meta.uid }|p|${ meta.badge }`,
              class: meta.progressClass,
              style: meta.progressStyle
            })
          )

          notif.actions !== void 0 && child.push(
            h('div', {
              class: meta.actionsClass
            }, notif.actions.map(props => h(QBtn, props)))
          )

          meta.badge > 1 && child.push(
            h('div', {
              key: `${ meta.uid }|${ meta.badge }`,
              class: notif.meta.badgeClass,
              style: notif.badgeStyle
            }, [ meta.badge ])
          )

          return h('div', {
            ref: el => { notifRefs[ '' + meta.uid ] = el },
            key: meta.uid,
            class: meta.class,
            ...meta.attrs
          }, [
            h('div', { class: meta.wrapperClass }, child)
          ])
        }))
      }))
    }
  })
}

export default {
  create (opts) {
    return __QUASAR_SSR_SERVER__
      ? noop
      : addNotification(opts)
  },

  setDefaults (opts) {
    if (__QUASAR_SSR_SERVER__ !== true) {
      opts === Object(opts) && Object.assign(defaults, opts)
    }
  },

  registerType (typeName, typeOpts) {
    if (__QUASAR_SSR_SERVER__ !== true && typeOpts === Object(typeOpts)) {
      notifTypes[ typeName ] = typeOpts
    }
  },

  install ({ $q, parentApp }) {
    $q.notify = this.create
    $q.notify.setDefaults = this.setDefaults
    $q.notify.registerType = this.registerType

    if ($q.config.notify !== void 0) {
      this.setDefaults($q.config.notify)
    }

    if (__QUASAR_SSR_SERVER__ !== true && this.__installed !== true) {
      const el = createGlobalNode('q-notify')
      createChildApp(getComponent($q), parentApp).mount(el)
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

import DeepFramework from 'deep-framework';
import Handler from './Handler';

export default {
  handler: (event, context) => {
    DeepFramework.KernelFromLambdaContext(context).bootstrap((deepKernel) => {
      new Handler(deepKernel).run(event, context);
    });
  },
};
<commit_msg>Update bootstrap.es6<commit_after>'use strict';

import DeepFramework from 'deep-framework';
import Handler from './Handler';

export default DeepFramework.LambdaHandler(Handler);
<|endoftext|>


<commit_before>import React from 'react';
import CalendarContainer from './index';

import { shallow } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

describe('testing <CalendarContainer />', () => {
  test('renders 1 <CalendarContainer /> component,', () => {
    const store = createStore(console.log, {});
    const component = shallow(
      <Provider store={store}>
        <CalendarContainer />
      </Provider>
    );
    expect(component).toHaveLength(1);
  });
});
<commit_msg>yet more testing<commit_after>import React from 'react';
import CalendarContainer from './index';

import { shallow } from 'enzyme';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
// import {eventCreateRequest, eventReadRequest, eventUpdateRequest, eventDeleteRequest} from '../../action/event.js';
import {
  handleEventCreate,
  handleEventUpdate,
  handleEventDelete,
  handleEventClick,
} from './index.js';

describe('testing <CalendarContainer />', () => {
  test('renders 1 <CalendarContainer /> component,', () => {
    const store = createStore(console.log, {});
    const component = shallow(
      <Provider store={store}>
        <CalendarContainer />
      </Provider>
    );
    expect(component).toHaveLength(1);
  });

  test('handleEventCreate', () => {
    const store = createStore(console.log, {});
    const wrapper = shallow(
      // <Provider store={store}>
      <CalendarContainer store={store} />
      // </Provider>
    );
    // let eventCreated = wrapper.instance().handleEventCreate();
    console.log('WRAPPER', wrapper);

    // console.log('eventCreated', eventCreated);
    // expect(eventCreated).toExist();
    expect(wrapper).toHaveLength(1);
  });
});
<|endoftext|>