From aba95d0af260547f2f71997a1ef4c116f8204afb Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 14 May 2020 15:44:31 +0200 Subject: [PATCH 0001/1070] add multi select component --- scripts/core/superdesk-api.d.ts | 1 + .../ui/components/generic-form/form-field.tsx | 3 + .../generate-filter-for-server.tsx | 1 + .../get-form-group-for-filtering.tsx | 2 + .../generic-form/get-initial-values.tsx | 1 + .../input-types/select_multiple_values.tsx | 65 +++++++++++++++++++ .../generic-form/interfaces/form.ts | 1 + .../generic-form/interfaces/input-types.tsx | 1 + .../generic-form/tests/generic-form.spec.tsx | 1 + 9 files changed, 76 insertions(+) create mode 100644 scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 150e7ee4b2..40fbceef6f 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -672,6 +672,7 @@ declare module 'superdesk-api' { stageSingleValue = 'stage_singstageSingleValuele_value', macroSingleValue = 'macroSingleValue', yesNo = 'yesNo', + selectMultiple = 'selectMultiple', } export interface IFormField { // don't forget to update runtime type checks diff --git a/scripts/core/ui/components/generic-form/form-field.tsx b/scripts/core/ui/components/generic-form/form-field.tsx index 84003c8f84..45d6ebd9ba 100644 --- a/scripts/core/ui/components/generic-form/form-field.tsx +++ b/scripts/core/ui/components/generic-form/form-field.tsx @@ -13,6 +13,7 @@ import {StageSingleValue} from './input-types/stage_single_value'; import {getMacroSingleValue} from './input-types/macro_single_value'; import {YesNo} from './input-types/yes-no'; import {IFormField, IFormGroup} from 'superdesk-api'; +import {SelectMultipleValues} from './input-types/select_multiple_values'; export function getFormFieldComponent(type: FormFieldType): React.ComponentType> { switch (type) { @@ -34,6 +35,8 @@ export function getFormFieldComponent(type: FormFieldType): React.ComponentType< return getMacroSingleValue(); case FormFieldType.yesNo: return YesNo; + case FormFieldType.selectMultiple: + return SelectMultipleValues; default: assertNever(type); } diff --git a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx index 535ac251cb..40fc50bb38 100644 --- a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx +++ b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx @@ -15,6 +15,7 @@ export function generateFilterForServer(type: FormFieldType, value: any): any { case FormFieldType.deskSingleValue: case FormFieldType.stageSingleValue: case FormFieldType.macroSingleValue: + case FormFieldType.selectMultiple: return value; case FormFieldType.textEditor3: diff --git a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx index 8038cc0d32..32a70c987b 100644 --- a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx +++ b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx @@ -24,6 +24,8 @@ function getFieldTypeForFiltering(type: FormFieldType): FormFieldType { return FormFieldType.macroSingleValue; case FormFieldType.yesNo: return FormFieldType.yesNo; + case FormFieldType.selectMultiple: + return FormFieldType.selectMultiple; default: assertNever(type); } diff --git a/scripts/core/ui/components/generic-form/get-initial-values.tsx b/scripts/core/ui/components/generic-form/get-initial-values.tsx index 64eb2ba2ea..f2b3a21c3d 100644 --- a/scripts/core/ui/components/generic-form/get-initial-values.tsx +++ b/scripts/core/ui/components/generic-form/get-initial-values.tsx @@ -17,6 +17,7 @@ function getInitialValueForFieldType(fieldConfig: IFormField): {readonly [field: case FormFieldType.stageSingleValue: case FormFieldType.macroSingleValue: case FormFieldType.yesNo: + case FormFieldType.selectMultiple: return {[field]: undefined}; case FormFieldType.checkbox: return {[field]: false}; diff --git a/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx b/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx new file mode 100644 index 0000000000..6d8a75d49a --- /dev/null +++ b/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import classNames from 'classnames'; +import {IInputType} from '../interfaces/input-types'; + +type IProps = IInputType>; + +export class SelectMultipleValues extends React.Component { + render() { + const {items} = this.props.formField.component_parameters; + + if (this.props.previewOutput) { + if (this.props.value == null) { + return null; + } + + const itemsWithLabels = this.props.value.map((id) => items.find((item) => item.id === id)); + + if (itemsWithLabels.some((item) => item == null)) { + return ( + {this.props.value.join(', ')} + ); + } else { + return ( + {itemsWithLabels.map((item) => item.label).join(', ')} + ); + } + } + + return ( +
0, + 'sd-line-input--required': this.props.formField.required === true, + }, + ) + }> + + + { + this.props.issues.map((str, i) => ( +
{str}
+ )) + } +
+ ); + } +} diff --git a/scripts/core/ui/components/generic-form/interfaces/form.ts b/scripts/core/ui/components/generic-form/interfaces/form.ts index 82e15cf760..e391c686f1 100644 --- a/scripts/core/ui/components/generic-form/interfaces/form.ts +++ b/scripts/core/ui/components/generic-form/interfaces/form.ts @@ -10,6 +10,7 @@ export enum FormFieldType { stageSingleValue = 'stage_singstageSingleValuele_value', macroSingleValue = 'macroSingleValue', yesNo = 'yesNo', + selectMultiple = 'selectMultiple', } export function isIFormField(x: IFormGroup['form'][0]): x is IFormField { // don't forget to update runtime type checks diff --git a/scripts/core/ui/components/generic-form/interfaces/input-types.tsx b/scripts/core/ui/components/generic-form/interfaces/input-types.tsx index e622b0364f..8887774002 100644 --- a/scripts/core/ui/components/generic-form/interfaces/input-types.tsx +++ b/scripts/core/ui/components/generic-form/interfaces/input-types.tsx @@ -15,6 +15,7 @@ export interface IInputType { readonly issues: Array; // renders a minimal representation of the value without any editing controls + // used for rendering selected filters readonly previewOutput: boolean; // fieldName is only passed by components which can change multiple fields diff --git a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx index eba24a9c7e..2fff27dd13 100644 --- a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx +++ b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx @@ -19,6 +19,7 @@ function getTestFieldConfig(type: FormFieldType): IFormField { case FormFieldType.contentFilterSingleValue: case FormFieldType.deskSingleValue: case FormFieldType.yesNo: + case FormFieldType.selectMultiple: return { type: type, field: 'test-field', From 2aeed32ef8dab796bb99111d89dbeca286f08c94 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 14 May 2020 16:14:55 +0200 Subject: [PATCH 0002/1070] add select single value component --- scripts/core/superdesk-api.d.ts | 1 + .../ui/components/generic-form/form-field.tsx | 3 + .../generate-filter-for-server.tsx | 1 + .../get-form-group-for-filtering.tsx | 2 + .../generic-form/get-initial-values.tsx | 1 + .../input-types/select_multiple_values.tsx | 2 +- .../select_single_value_static.tsx | 59 +++++++++++++++++++ .../generic-form/interfaces/form.ts | 1 + .../generic-form/tests/generic-form.spec.tsx | 1 + 9 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 40fbceef6f..ca72c01f18 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -672,6 +672,7 @@ declare module 'superdesk-api' { stageSingleValue = 'stage_singstageSingleValuele_value', macroSingleValue = 'macroSingleValue', yesNo = 'yesNo', + select = 'select', selectMultiple = 'selectMultiple', } diff --git a/scripts/core/ui/components/generic-form/form-field.tsx b/scripts/core/ui/components/generic-form/form-field.tsx index 45d6ebd9ba..09f02cbddd 100644 --- a/scripts/core/ui/components/generic-form/form-field.tsx +++ b/scripts/core/ui/components/generic-form/form-field.tsx @@ -14,6 +14,7 @@ import {getMacroSingleValue} from './input-types/macro_single_value'; import {YesNo} from './input-types/yes-no'; import {IFormField, IFormGroup} from 'superdesk-api'; import {SelectMultipleValues} from './input-types/select_multiple_values'; +import {SelectSingleValue} from './input-types/select_single_value_static'; export function getFormFieldComponent(type: FormFieldType): React.ComponentType> { switch (type) { @@ -35,6 +36,8 @@ export function getFormFieldComponent(type: FormFieldType): React.ComponentType< return getMacroSingleValue(); case FormFieldType.yesNo: return YesNo; + case FormFieldType.select: + return SelectSingleValue; case FormFieldType.selectMultiple: return SelectMultipleValues; default: diff --git a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx index 40fc50bb38..2b0efe1d6b 100644 --- a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx +++ b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx @@ -15,6 +15,7 @@ export function generateFilterForServer(type: FormFieldType, value: any): any { case FormFieldType.deskSingleValue: case FormFieldType.stageSingleValue: case FormFieldType.macroSingleValue: + case FormFieldType.select: case FormFieldType.selectMultiple: return value; diff --git a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx index 32a70c987b..641747323f 100644 --- a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx +++ b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx @@ -24,6 +24,8 @@ function getFieldTypeForFiltering(type: FormFieldType): FormFieldType { return FormFieldType.macroSingleValue; case FormFieldType.yesNo: return FormFieldType.yesNo; + case FormFieldType.select: + return FormFieldType.select; case FormFieldType.selectMultiple: return FormFieldType.selectMultiple; default: diff --git a/scripts/core/ui/components/generic-form/get-initial-values.tsx b/scripts/core/ui/components/generic-form/get-initial-values.tsx index f2b3a21c3d..444eeb0d57 100644 --- a/scripts/core/ui/components/generic-form/get-initial-values.tsx +++ b/scripts/core/ui/components/generic-form/get-initial-values.tsx @@ -17,6 +17,7 @@ function getInitialValueForFieldType(fieldConfig: IFormField): {readonly [field: case FormFieldType.stageSingleValue: case FormFieldType.macroSingleValue: case FormFieldType.yesNo: + case FormFieldType.select: case FormFieldType.selectMultiple: return {[field]: undefined}; case FormFieldType.checkbox: diff --git a/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx b/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx index 6d8a75d49a..94185e1cb9 100644 --- a/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx +++ b/scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx @@ -6,7 +6,7 @@ type IProps = IInputType>; export class SelectMultipleValues extends React.Component { render() { - const {items} = this.props.formField.component_parameters; + const items: Array<{id: string; label: string}> = this.props.formField.component_parameters.items; if (this.props.previewOutput) { if (this.props.value == null) { diff --git a/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx b/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx new file mode 100644 index 0000000000..77aca2c7b3 --- /dev/null +++ b/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import classNames from 'classnames'; +import {IInputType} from '../interfaces/input-types'; + +type IProps = IInputType; + +export class SelectSingleValue extends React.Component { + render() { + const items: Array<{id: string; label: string}> = this.props.formField.component_parameters.items; + + if (this.props.previewOutput) { + if (this.props.value == null) { + return null; + } + + const itemWithLabel = items.find((item) => item.id === this.props.value); + + return ( + {itemWithLabel?.label ?? this.props.value} + ); + } + + return ( +
0, + 'sd-line-input--required': this.props.formField.required === true, + }, + ) + }> + + + { + this.props.issues.map((str, i) => ( +
{str}
+ )) + } +
+ ); + } +} diff --git a/scripts/core/ui/components/generic-form/interfaces/form.ts b/scripts/core/ui/components/generic-form/interfaces/form.ts index e391c686f1..7dd7913750 100644 --- a/scripts/core/ui/components/generic-form/interfaces/form.ts +++ b/scripts/core/ui/components/generic-form/interfaces/form.ts @@ -10,6 +10,7 @@ export enum FormFieldType { stageSingleValue = 'stage_singstageSingleValuele_value', macroSingleValue = 'macroSingleValue', yesNo = 'yesNo', + select = 'select', selectMultiple = 'selectMultiple', } diff --git a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx index 2fff27dd13..29356d28bf 100644 --- a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx +++ b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx @@ -19,6 +19,7 @@ function getTestFieldConfig(type: FormFieldType): IFormField { case FormFieldType.contentFilterSingleValue: case FormFieldType.deskSingleValue: case FormFieldType.yesNo: + case FormFieldType.select: case FormFieldType.selectMultiple: return { type: type, From d292184ec09fb11749efb2c201bf38bc67df72a3 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 15 May 2020 17:01:04 +0200 Subject: [PATCH 0003/1070] ensure that field label, not ID is displayed in filters --- .../ui/components/ListPage/generic-list-page.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 4ba88e2b7e..38eacd8168 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -512,22 +512,24 @@ export class GenericListPageComponent data-test-id="list-page--filters-active" > { - Object.keys(activeFilters).map((fieldName, i) => { + Object.keys(activeFilters).map((field_id, i) => { + const currentField = getFormFieldsFlat(formConfigForFilters).find( + ({field}) => field === field_id, + ); + const filterValuePreview = getFormFieldPreviewComponent( this.props.items.activeFilters, - getFormFieldsFlat(formConfigForFilters).find( - ({field}) => field === fieldName, - ), + currentField, ); return ( { - this.removeFilter(fieldName); + this.removeFilter(field_id); }} > - {fieldName}:{' '} + {currentField.label}:  {filterValuePreview} ); From af86a2c0d61c4c03c794eece24e7d96746134a44 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 15 May 2020 17:03:45 +0200 Subject: [PATCH 0004/1070] add number input --- scripts/core/superdesk-api.d.ts | 1 + .../ui/components/generic-form/form-field.tsx | 3 ++ .../generate-filter-for-server.tsx | 1 + .../get-form-group-for-filtering.tsx | 2 + .../generic-form/get-initial-values.tsx | 1 + .../generic-form/input-types/number.tsx | 42 +++++++++++++++++++ .../generic-form/interfaces/form.ts | 1 + .../generic-form/tests/generic-form.spec.tsx | 1 + 8 files changed, 52 insertions(+) create mode 100644 scripts/core/ui/components/generic-form/input-types/number.tsx diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index ca72c01f18..604c8366c2 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -665,6 +665,7 @@ declare module 'superdesk-api' { export enum FormFieldType { textSingleLine = 'textSingleLine', textEditor3 = 'textEditor3', + number = 'number', vocabularySingleValue = 'vocabularySingleValue', checkbox = 'checkbox', contentFilterSingleValue = 'contentFilterSingleValue', diff --git a/scripts/core/ui/components/generic-form/form-field.tsx b/scripts/core/ui/components/generic-form/form-field.tsx index 09f02cbddd..f5035ed1af 100644 --- a/scripts/core/ui/components/generic-form/form-field.tsx +++ b/scripts/core/ui/components/generic-form/form-field.tsx @@ -15,6 +15,7 @@ import {YesNo} from './input-types/yes-no'; import {IFormField, IFormGroup} from 'superdesk-api'; import {SelectMultipleValues} from './input-types/select_multiple_values'; import {SelectSingleValue} from './input-types/select_single_value_static'; +import {NumberComponent} from './input-types/number'; export function getFormFieldComponent(type: FormFieldType): React.ComponentType> { switch (type) { @@ -22,6 +23,8 @@ export function getFormFieldComponent(type: FormFieldType): React.ComponentType< return TextSingleLine; case FormFieldType.textEditor3: return TextEditor3; + case FormFieldType.number: + return NumberComponent; case FormFieldType.vocabularySingleValue: return VocabularySingleValue; case FormFieldType.checkbox: diff --git a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx index 2b0efe1d6b..5f4f5500c6 100644 --- a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx +++ b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx @@ -17,6 +17,7 @@ export function generateFilterForServer(type: FormFieldType, value: any): any { case FormFieldType.macroSingleValue: case FormFieldType.select: case FormFieldType.selectMultiple: + case FormFieldType.number: return value; case FormFieldType.textEditor3: diff --git a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx index 641747323f..85ef8cf2e0 100644 --- a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx +++ b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx @@ -10,6 +10,8 @@ function getFieldTypeForFiltering(type: FormFieldType): FormFieldType { case FormFieldType.textEditor3: // even though textEditor3 outputs HTML, plaintext has to be used for filtering return FormFieldType.textSingleLine; + case FormFieldType.number: + return FormFieldType.number; case FormFieldType.vocabularySingleValue: return FormFieldType.vocabularySingleValue; case FormFieldType.checkbox: diff --git a/scripts/core/ui/components/generic-form/get-initial-values.tsx b/scripts/core/ui/components/generic-form/get-initial-values.tsx index 444eeb0d57..2c53d7b77a 100644 --- a/scripts/core/ui/components/generic-form/get-initial-values.tsx +++ b/scripts/core/ui/components/generic-form/get-initial-values.tsx @@ -19,6 +19,7 @@ function getInitialValueForFieldType(fieldConfig: IFormField): {readonly [field: case FormFieldType.yesNo: case FormFieldType.select: case FormFieldType.selectMultiple: + case FormFieldType.number: return {[field]: undefined}; case FormFieldType.checkbox: return {[field]: false}; diff --git a/scripts/core/ui/components/generic-form/input-types/number.tsx b/scripts/core/ui/components/generic-form/input-types/number.tsx new file mode 100644 index 0000000000..f40c004b2c --- /dev/null +++ b/scripts/core/ui/components/generic-form/input-types/number.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import classNames from 'classnames'; +import {IInputType} from '../interfaces/input-types'; + +export class NumberComponent extends React.Component> { + render() { + if (this.props.previewOutput) { + return
{this.props.value}
; + } + + // Default value is required to make it a controlled input. + // Empty string is used instead of a zero to allow for empty values. + const valueWithDefaultValue = this.props.value ?? ''; + + return ( +
0, + 'sd-line-input--required': this.props.formField.required === true, + }, + ) + }> + + this.props.onChange(parseFloat(event.target.value))} + className="sd-line-input__input" + data-test-id={`gform-input--${this.props.formField.field}`} + /> + { + this.props.issues.map((str, i) => ( +
{str}
+ )) + } +
+ ); + } +} diff --git a/scripts/core/ui/components/generic-form/interfaces/form.ts b/scripts/core/ui/components/generic-form/interfaces/form.ts index 7dd7913750..9a4435f024 100644 --- a/scripts/core/ui/components/generic-form/interfaces/form.ts +++ b/scripts/core/ui/components/generic-form/interfaces/form.ts @@ -3,6 +3,7 @@ import {IFormGroup, IFormField, IFormGroupCollapsible} from 'superdesk-api'; export enum FormFieldType { textSingleLine = 'textSingleLine', textEditor3 = 'textEditor3', + number = 'number', vocabularySingleValue = 'vocabularySingleValue', checkbox = 'checkbox', contentFilterSingleValue = 'contentFilterSingleValue', diff --git a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx index 29356d28bf..e6ae601d29 100644 --- a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx +++ b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx @@ -15,6 +15,7 @@ function getTestFieldConfig(type: FormFieldType): IFormField { switch (type) { case FormFieldType.textSingleLine: case FormFieldType.textEditor3: + case FormFieldType.number: case FormFieldType.checkbox: case FormFieldType.contentFilterSingleValue: case FormFieldType.deskSingleValue: From 979490ca30e408eacefc2f929b8ddd6c911aa10e Mon Sep 17 00:00:00 2001 From: Tomas Date: Sat, 16 May 2020 17:29:09 +0200 Subject: [PATCH 0005/1070] initial implementation of non-text content profiles --- package.json | 2 + .../ContentProfileConfigNonText.tsx | 164 ++++++++++++++++++ .../controllers/ContentProfilesController.ts | 21 +++ scripts/apps/workspace/content/index.ts | 2 + .../content/views/profile-settings.html | 15 +- scripts/core/superdesk-api.d.ts | 23 ++- .../generic-form/form-direction-wrapper.tsx | 2 +- 7 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx diff --git a/package.json b/package.json index 58b2ff16c0..f8e6cb1d1d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "angular-resource": "1.6.9", "angular-route": "1.6.9", "angular-vs-repeat": "1.1.7", + "array-move": "2.2.1", "bootstrap": "3.3.7", "classnames": "2.2.5", "css": "2.2.4", @@ -111,6 +112,7 @@ "react-paginate": "6.3.0", "react-portal": "4.1.3", "react-redux": "5.0.7", + "react-sortable-hoc": "1.11.0", "react-textarea-autosize": "5.2.1", "redux": "4.0.4", "redux-logger": "3.0.6", diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx new file mode 100644 index 0000000000..90920e922e --- /dev/null +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -0,0 +1,164 @@ +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import {IContentProfileNonText, IFormGroup, IFormField, IContentProfileField, IArrayKeyed} from 'superdesk-api'; +import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc'; +import {FormViewEdit} from 'core/ui/components/generic-form/from-group'; +import {gettext} from 'core/utils'; +import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; +import {Button} from 'superdesk-ui-framework'; + +interface IProps { + profile: IContentProfileNonText; +} + +interface IState { + fields: IArrayKeyed; +} + +class FieldComponent extends React.PureComponent<{ + field: IArrayKeyed[0], + onChange(field: IArrayKeyed[0]): void; + onRemove(): void} +> { + render() { + const {field} = this.props; + + const idField: IFormField = { + label: gettext('ID'), + type: FormFieldType.textSingleLine, + field: 'id', + required: true, + }; + + const labelField: IFormField = { + label: gettext('Label'), + type: FormFieldType.textSingleLine, + field: 'label', + required: true, + }; + + const requiredField: IFormField = { + label: gettext('Required'), + type: FormFieldType.checkbox, + field: 'required', + required: false, + }; + + const formConfig: IFormGroup = { + direction: 'vertical', + type: 'inline', + form: [labelField, idField, requiredField], + }; + + return ( +
+ { + this.props.onChange({...field, value: {...field.value, [key]: value}}); + }} + issues={{}} + /> +
+ ); + } +} + +const FieldSortable = SortableElement(FieldComponent); + +class FieldsComponent extends React.Component<{ + fields: IArrayKeyed; + onChange(fields: IArrayKeyed): void; +}> { + render() { + const {fields} = this.props; + + return ( +
+ { + fields.map((field, index) => ( + { + const fieldsNext = this.props.fields.map((f) => f.key === _field.key ? _field : f); + + this.props.onChange(fieldsNext); + }} + onRemove={() => { + this.props.onChange(fields.filter((f) => f.key !== field.key)); + }} + /> + )) + } +
+ ); + } +} + +const FieldsSortable = SortableContainer(FieldsComponent); + +export class ContentProfileConfigNonText extends React.Component { + private generateKey: () => string; + private lastKey: number; + + constructor(props: IProps) { + super(props); + + this.lastKey = 0; + this.generateKey = () => (++this.lastKey).toString(); + + this.state = { + fields: (props.profile.fields ?? []).map( + (field) => ({key: this.generateKey(), value: field}), + ), + }; + } + + componentDidUpdate() { + this.props.profile.fields = this.state.fields.map(({value}) => value); + } + + render() { + const {fields} = this.state; + + return ( +
+
+ ); + } +} diff --git a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts index eada49eb94..c74370bcb3 100644 --- a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts +++ b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts @@ -2,6 +2,16 @@ import {cloneDeep, get, isEqual} from 'lodash'; import {gettext} from 'core/utils'; import {IContentProfile} from 'superdesk-api'; import {appConfig} from 'appConfig'; +import {assertNever} from 'core/helpers/typescript-helpers'; + +export enum IContentProfileTypeNonText { + image = 'image', + video = 'video', +} + +function getAllContentProfileTypes(): Array { + return Object.keys(IContentProfileTypeNonText).map((key) => IContentProfileTypeNonText[key]); +} ContentProfilesController.$inject = ['$scope', '$location', 'notify', 'content', 'modal', '$q']; export function ContentProfilesController($scope, $location, notify, content, modal, $q) { @@ -33,6 +43,17 @@ export function ContentProfilesController($scope, $location, notify, content, mo }); }; + $scope.contentProfileTypes = getAllContentProfileTypes().map((type) => { + switch (type) { + case IContentProfileTypeNonText.image: + return {label: gettext('Image'), value: IContentProfileTypeNonText.image}; + case IContentProfileTypeNonText.video: + return {label: gettext('Video'), value: IContentProfileTypeNonText.video}; + default: + return assertNever(type); + } + }); + $scope.withEditor3 = appConfig.features != null && appConfig.features.editor3; /** diff --git a/scripts/apps/workspace/content/index.ts b/scripts/apps/workspace/content/index.ts index cd97f1ba33..db1f7ae647 100644 --- a/scripts/apps/workspace/content/index.ts +++ b/scripts/apps/workspace/content/index.ts @@ -7,6 +7,7 @@ import {WidgetsConfig} from './components/WidgetsConfig'; import {gettext} from 'core/utils'; import ContentProfileFields from './controllers/ContentProfileFields'; import {ContentProfilesController} from './controllers/ContentProfilesController'; +import {ContentProfileConfigNonText} from './components/ContentProfileConfigNonText'; /** * @ngdoc module @@ -31,6 +32,7 @@ angular.module('superdesk.apps.workspace.content', [ .directive('sdSortContentProfiles', directive.SortContentProfiles) .component('sdWidgetsConfig', reactToAngular1(WidgetsConfig, ['initialWidgetsConfig', 'onUpdate'])) + .component('sdContentProfileConfigNonText', reactToAngular1(ContentProfileConfigNonText, ['profile'])) .component('sdSchemaEditorFieldsDropdown', { template: require('./views/schema-editor-fields-dropdown.html'), bindings: { diff --git a/scripts/apps/workspace/content/views/profile-settings.html b/scripts/apps/workspace/content/views/profile-settings.html index 8dc772b74f..ea568144d9 100644 --- a/scripts/apps/workspace/content/views/profile-settings.html +++ b/scripts/apps/workspace/content/views/profile-settings.html @@ -61,6 +61,15 @@ diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 604c8366c2..5bdf241dad 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -18,6 +18,8 @@ declare module 'superdesk-api' { export type Omit = Pick>; + export type IArrayKeyed = Array<{key: string; value: T}>; + // EXTENSIONS @@ -545,6 +547,7 @@ declare module 'superdesk-api' { export interface IContentProfile { _id: string; + type: 'text'; label: string; description: string; schema: Object; @@ -553,8 +556,24 @@ declare module 'superdesk-api' { priority: number; enabled: boolean; is_used: boolean; - created_by: string; - updated_by: string; + created_by: IUser['_id']; + updated_by: IUser['_id']; + } + + export interface IContentProfileField { + id: string; + label: string; + required: boolean; + } + + export enum IContentProfileTypeNonText { + image = 'image', + video = 'video', + } + + export interface IContentProfileNonText { + type: keyof typeof IContentProfileTypeNonText; + fields: Array; } diff --git a/scripts/core/ui/components/generic-form/form-direction-wrapper.tsx b/scripts/core/ui/components/generic-form/form-direction-wrapper.tsx index 36c827a754..0dd66f438b 100644 --- a/scripts/core/ui/components/generic-form/form-direction-wrapper.tsx +++ b/scripts/core/ui/components/generic-form/form-direction-wrapper.tsx @@ -21,7 +21,7 @@ export class FormGroupDirectionWrapper extends React.Component { ? `${currentClassname} form-group-horizontal` : 'form-group-horizontal'; - return
; + return
{this.props.children}
; } else { assertNever(direction); } From 0ff1ccc35c959bc3d22bf356c4ea9bde63aee738 Mon Sep 17 00:00:00 2001 From: Tomas Date: Mon, 18 May 2020 12:19:13 +0200 Subject: [PATCH 0006/1070] implement different profile schemas based on profile type --- .../ContentProfileConfigNonText.tsx | 117 +++++++++++++----- .../controllers/ContentProfilesController.ts | 2 +- scripts/apps/workspace/content/index.ts | 5 +- .../content/views/profile-settings.html | 2 +- 4 files changed, 95 insertions(+), 31 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 90920e922e..4509b094db 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -6,49 +6,107 @@ import {FormViewEdit} from 'core/ui/components/generic-form/from-group'; import {gettext} from 'core/utils'; import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; import {Button} from 'superdesk-ui-framework'; +import {IContentProfileTypeNonText} from '../controllers/ContentProfilesController'; +import {assertNever} from 'core/helpers/typescript-helpers'; interface IProps { profile: IContentProfileNonText; + profileType: keyof typeof IContentProfileTypeNonText; } interface IState { fields: IArrayKeyed; } +function getImageFormConfig(): IFormGroup { + const idField: IFormField = { + label: gettext('ID'), + type: FormFieldType.textSingleLine, + field: 'id', + required: true, + }; + + const labelField: IFormField = { + label: gettext('Label'), + type: FormFieldType.textSingleLine, + field: 'label', + required: true, + }; + + const requiredField: IFormField = { + label: gettext('Required'), + type: FormFieldType.checkbox, + field: 'required', + required: false, + }; + + const displayInMediaEditor: IFormField = { + label: gettext('Display in media editor'), + type: FormFieldType.checkbox, + field: 'displayOnMediaEditor', + required: false, + }; + + const formConfig: IFormGroup = { + direction: 'vertical', + type: 'inline', + form: [labelField, idField, requiredField, displayInMediaEditor], + }; + + return formConfig; +} + +function getVideoFormConfig(): IFormGroup { + const idField: IFormField = { + label: gettext('ID'), + type: FormFieldType.textSingleLine, + field: 'id', + required: true, + }; + + const labelField: IFormField = { + label: gettext('Label'), + type: FormFieldType.textSingleLine, + field: 'label', + required: true, + }; + + const requiredField: IFormField = { + label: gettext('Required'), + type: FormFieldType.checkbox, + field: 'required', + required: false, + }; + + const formConfig: IFormGroup = { + direction: 'vertical', + type: 'inline', + form: [labelField, idField, requiredField], + }; + + return formConfig; +} + +function getFormConfig(type: IContentProfileTypeNonText): IFormGroup { + switch (type) { + case IContentProfileTypeNonText.image: + return getImageFormConfig(); + case IContentProfileTypeNonText.video: + return getVideoFormConfig(); + default: + return assertNever(type); + } +} + class FieldComponent extends React.PureComponent<{ - field: IArrayKeyed[0], + field: IArrayKeyed[0]; + profileType: IContentProfileTypeNonText; onChange(field: IArrayKeyed[0]): void; onRemove(): void} > { render() { const {field} = this.props; - - const idField: IFormField = { - label: gettext('ID'), - type: FormFieldType.textSingleLine, - field: 'id', - required: true, - }; - - const labelField: IFormField = { - label: gettext('Label'), - type: FormFieldType.textSingleLine, - field: 'label', - required: true, - }; - - const requiredField: IFormField = { - label: gettext('Required'), - type: FormFieldType.checkbox, - field: 'required', - required: false, - }; - - const formConfig: IFormGroup = { - direction: 'vertical', - type: 'inline', - form: [labelField, idField, requiredField], - }; + const formConfig = getFormConfig(this.props.profileType); return (
; + profileType: IContentProfileTypeNonText; onChange(fields: IArrayKeyed): void; }> { render() { @@ -91,6 +150,7 @@ class FieldsComponent extends React.Component<{ key={field.key} index={index} field={field} + profileType={this.props.profileType} onChange={(_field) => { const fieldsNext = this.props.fields.map((f) => f.key === _field.key ? _field : f); @@ -149,6 +209,7 @@ export class ContentProfileConfigNonText extends React.Component { this.setState({fields: _fields}); }} diff --git a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts index c74370bcb3..da58046fc2 100644 --- a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts +++ b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts @@ -9,7 +9,7 @@ export enum IContentProfileTypeNonText { video = 'video', } -function getAllContentProfileTypes(): Array { +export function getAllContentProfileTypes(): Array { return Object.keys(IContentProfileTypeNonText).map((key) => IContentProfileTypeNonText[key]); } diff --git a/scripts/apps/workspace/content/index.ts b/scripts/apps/workspace/content/index.ts index db1f7ae647..707389e44c 100644 --- a/scripts/apps/workspace/content/index.ts +++ b/scripts/apps/workspace/content/index.ts @@ -32,7 +32,10 @@ angular.module('superdesk.apps.workspace.content', [ .directive('sdSortContentProfiles', directive.SortContentProfiles) .component('sdWidgetsConfig', reactToAngular1(WidgetsConfig, ['initialWidgetsConfig', 'onUpdate'])) - .component('sdContentProfileConfigNonText', reactToAngular1(ContentProfileConfigNonText, ['profile'])) + .component( + 'sdContentProfileConfigNonText', + reactToAngular1(ContentProfileConfigNonText, ['profile', 'profileType']), + ) .component('sdSchemaEditorFieldsDropdown', { template: require('./views/schema-editor-fields-dropdown.html'), bindings: { diff --git a/scripts/apps/workspace/content/views/profile-settings.html b/scripts/apps/workspace/content/views/profile-settings.html index ea568144d9..4300c7f937 100644 --- a/scripts/apps/workspace/content/views/profile-settings.html +++ b/scripts/apps/workspace/content/views/profile-settings.html @@ -153,7 +153,7 @@
- +
From 6766d299e3667614c1a9320552269a2c41ee8cbd Mon Sep 17 00:00:00 2001 From: Tomas Date: Mon, 18 May 2020 14:33:46 +0200 Subject: [PATCH 0007/1070] introduce field attributes --- .../ContentProfileConfigNonText.tsx | 85 ++++++++++++++++++- scripts/core/superdesk-api.d.ts | 7 ++ .../select_single_value_static.tsx | 8 +- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 4509b094db..9287079c90 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -18,6 +18,16 @@ interface IState { fields: IArrayKeyed; } +// subset of FormFieldType +enum IContentProfileFieldTypes { + textSingleLine = 'textSingleLine', + number = 'number', +} + +function getAllContentProfileFieldTypes(): Array { + return Object.keys(IContentProfileFieldTypes).map((key) => IContentProfileFieldTypes[key]); +} + function getImageFormConfig(): IFormGroup { const idField: IFormField = { label: gettext('ID'), @@ -33,6 +43,31 @@ function getImageFormConfig(): IFormGroup { required: true, }; + const fieldType: IFormField = { + label: gettext('Field type'), + type: FormFieldType.select, + component_parameters: { + items: getAllContentProfileFieldTypes().map((type) => { + switch (type) { + case IContentProfileFieldTypes.textSingleLine: + return { + id: IContentProfileFieldTypes.textSingleLine, + label: gettext('Plain text (single line)'), + }; + case IContentProfileFieldTypes.number: + return { + id: IContentProfileFieldTypes.number, + label: gettext('Number'), + }; + default: + return assertNever(type); + } + }), + }, + field: 'type', + required: true, + }; + const requiredField: IFormField = { label: gettext('Required'), type: FormFieldType.checkbox, @@ -50,7 +85,7 @@ function getImageFormConfig(): IFormGroup { const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [labelField, idField, requiredField, displayInMediaEditor], + form: [labelField, idField, fieldType, requiredField, displayInMediaEditor], }; return formConfig; @@ -98,6 +133,35 @@ function getFormConfig(type: IContentProfileTypeNonText): IFormGroup { } } +function getAttributesForTextSingleLine(): Array { + const minLengthField: IFormField = { + label: gettext('Minimum length'), + type: FormFieldType.number, + field: 'minlength', + required: false, + }; + + const maxLengthField: IFormField = { + label: gettext('Maximum length'), + type: FormFieldType.number, + field: 'maxlength', + required: false, + }; + + return [minLengthField, maxLengthField]; +} + +export function getAttributesForFormFieldType(type: IContentProfileFieldTypes): Array { + switch (type) { + case IContentProfileFieldTypes.textSingleLine: + return getAttributesForTextSingleLine(); + case IContentProfileFieldTypes.number: + return []; + default: + assertNever(type); + } +} + class FieldComponent extends React.PureComponent<{ field: IArrayKeyed[0]; profileType: IContentProfileTypeNonText; @@ -108,6 +172,20 @@ class FieldComponent extends React.PureComponent<{ const {field} = this.props; const formConfig = getFormConfig(this.props.profileType); + if (field.value.type != null) { + const fieldAttributes = getAttributesForFormFieldType(IContentProfileFieldTypes[field.value.type]); + + if (fieldAttributes.length > 0) { + const attributesFormGroup: IFormGroup = { + direction: 'vertical', + type: 'inline', + form: fieldAttributes, + }; + + formConfig.form.push(attributesFormGroup); + } + } + return (
onClick={() => { this.setState({ fields: [ - {key: this.generateKey(), value: {id: '', label: '', required: false}}, + { + key: this.generateKey(), + value: {id: '', label: '', type: 'textSingleLine', required: false}, + }, ...fields, ], }); diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 5bdf241dad..036bbd8db5 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -560,9 +560,16 @@ declare module 'superdesk-api' { updated_by: IUser['_id']; } + // subset of FormFieldType + export enum IContentProfileFieldTypes { + textSingleLine = 'textSingleLine', + number = 'number', + } + export interface IContentProfileField { id: string; label: string; + type: keyof typeof IContentProfileFieldTypes; required: boolean; } diff --git a/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx b/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx index 77aca2c7b3..e013c4aa43 100644 --- a/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx +++ b/scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import {IInputType} from '../interfaces/input-types'; -type IProps = IInputType; +type IProps = IInputType; export class SelectSingleValue extends React.Component { render() { @@ -35,7 +35,7 @@ export class SelectSingleValue extends React.Component { disabled={this.props.disabled || items == null || items.length < 1} value={this.props.value ?? ''} onChange={(event) => { - this.props.onChange(event.target.value); + this.props.onChange(event.target.value === '' ? null : event.target.value); }} data-test-id={`gform-input--${this.props.formField.field}`} > @@ -43,8 +43,8 @@ export class SelectSingleValue extends React.Component { { items == null ? null - : items.map(({id, label}, i) => ( - + : items.map(({id, label}) => ( + )) } From c3613461472a001cfefbae350d3a7a82fbbd4df6 Mon Sep 17 00:00:00 2001 From: Tomas Date: Mon, 18 May 2020 16:39:33 +0200 Subject: [PATCH 0008/1070] add width field --- .../components/ContentProfileConfigNonText.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 9287079c90..75b28aa347 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -43,6 +43,20 @@ function getImageFormConfig(): IFormGroup { required: true, }; + const sdWidth: IFormField = { + label: gettext('Width'), + type: FormFieldType.select, + component_parameters: { + items: [ + {id: 'full', label: gettext('Full')}, + {id: 'half', label: gettext('Half')}, + {id: 'quarter', label: gettext('Quarter')}, + ], + }, + field: 'sdWidth', + required: true, + }; + const fieldType: IFormField = { label: gettext('Field type'), type: FormFieldType.select, @@ -85,7 +99,7 @@ function getImageFormConfig(): IFormGroup { const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [labelField, idField, fieldType, requiredField, displayInMediaEditor], + form: [labelField, idField, sdWidth, fieldType, requiredField, displayInMediaEditor], }; return formConfig; From 0977c73f83d1e2a48e1b0ef56616bb2d718b14b5 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 21 May 2020 16:04:43 +0200 Subject: [PATCH 0009/1070] don't require the entire IBaseRestApiResponse. IItemWithId is enough. --- scripts/core/helpers/CrudManager.tsx | 1 - scripts/core/superdesk-api.d.ts | 32 +++++++++++++------ .../components/ListPage/generic-list-page.tsx | 9 +++--- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/scripts/core/helpers/CrudManager.tsx b/scripts/core/helpers/CrudManager.tsx index 914b823616..7425be22ad 100644 --- a/scripts/core/helpers/CrudManager.tsx +++ b/scripts/core/helpers/CrudManager.tsx @@ -226,7 +226,6 @@ export function connectCrudManager> { + export interface IPropsGenericForm> { formConfig: IFormGroup; defaultSortOption: ISortOption; defaultFilters?: Partial; @@ -741,29 +741,41 @@ declare module 'superdesk-api' { direction: 'ascending' | 'descending'; } + export interface IItemWithId { + _id: string; + } + + export interface ICrudManagerResponse { + _items: Array; + _meta: { + max_results: number; + page: number; + total: number; + }; + } - export interface ICrudManagerState extends IRestApiResponse { + export interface ICrudManagerState extends ICrudManagerResponse { activeFilters: ICrudManagerFilters; activeSortOption?: ISortOption; } - export interface ICrudManagerMethods { + export interface ICrudManagerMethods { read( page: number, sort: ISortOption, filterValues?: ICrudManagerFilters, - ): Promise>; + ): Promise>; update(item: Entity): Promise; create(item: Entity): Promise; delete(item: Entity): Promise; - refresh(): Promise>; - sort(nextSortOption: ISortOption): Promise>; - removeFilter(fieldName: string): Promise>; - goToPage(nextPage: number): Promise>; + refresh(): Promise>; + sort(nextSortOption: ISortOption): Promise>; + removeFilter(fieldName: string): Promise>; + goToPage(nextPage: number): Promise>; } - export interface ICrudManager extends ICrudManagerState, ICrudManagerMethods { + export interface ICrudManager extends ICrudManagerState, ICrudManagerMethods { // allow exposing it as one interface for consumer components } @@ -830,7 +842,7 @@ declare module 'superdesk-api' { onClose?(): void; } - export interface IGenericListPageComponent> { + export interface IGenericListPageComponent> { openPreview(id: string): void; startEditing(id: string): void; closePreview(): void; diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 38eacd8168..5849156d38 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -28,15 +28,16 @@ import {getInitialValues} from '../generic-form/get-initial-values'; import {generateFilterForServer} from '../generic-form/generate-filter-for-server'; import {getFormFieldsFlat} from '../generic-form/get-form-fields-flat'; import { - IBaseRestApiResponse, + IItemWithId, IPropsGenericForm, IGenericListPageComponent, ICrudManager, IFormGroup, + IBaseRestApiResponse, } from 'superdesk-api'; import {gettext} from 'core/utils'; -interface IState> { +interface IState> { previewItemId: string | null; editItemId: string | null; newItem: {[key: string]: any} | null; @@ -46,14 +47,14 @@ interface IState { +interface IPropsConnected { items?: ICrudManager; modal: any; notify: any; $rootScope: any; } -export class GenericListPageComponent +export class GenericListPageComponent extends React.Component & IPropsConnected, IState> implements IGenericListPageComponent { From 686449d642f3ea4984e0f19a99df656fd1667468 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 09:52:22 +0200 Subject: [PATCH 0010/1070] check if the component is mounted --- .../generic-list-page-item-view-edit.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx index b68169020f..96674f4c37 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx @@ -40,6 +40,8 @@ const getInitialState = (props: IProps) => ({ }); class GenericListPageItemViewEditComponent extends React.Component { + _mounted: boolean; + constructor(props) { super(props); @@ -51,6 +53,12 @@ class GenericListPageItemViewEditComponent extends React.Component { - this.setState({ - issues: {}, - }, () => { - this.props.onEditModeChange(false); - }); + if (this._mounted) { + this.setState({ + issues: {}, + }, () => { + this.props.onEditModeChange(false); + }); + } }) .catch((res) => { if (isHttpApiError(res)) { From 55a8de130f84193d6747273aaf0a62106147e465 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 10:05:42 +0200 Subject: [PATCH 0011/1070] id won't be present when creating a new item --- scripts/core/superdesk-api.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index ba8d641fa0..bc04714640 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -766,7 +766,7 @@ declare module 'superdesk-api' { filterValues?: ICrudManagerFilters, ): Promise>; update(item: Entity): Promise; - create(item: Entity): Promise; + create(item: Omit): Promise; delete(item: Entity): Promise; refresh(): Promise>; sort(nextSortOption: ISortOption): Promise>; From 4683435ad5c88b74599d4fc65af3a4e6b5d86c5a Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 10:06:39 +0200 Subject: [PATCH 0012/1070] get services in constructor instead of passing as props --- .../components/ListPage/generic-list-page.tsx | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 5849156d38..8096245429 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -20,7 +20,6 @@ import {Button} from 'core/ui/components/Nav'; import {SortBar, ISortFields} from 'core/ui/components/SortBar'; import {connectCrudManager} from 'core/helpers/CrudManager'; import {TagLabel} from 'core/ui/components/TagLabel'; -import {connectServices} from 'core/helpers/ReactRenderAsync'; import {getFormGroupForFiltering} from 'core/ui/components/generic-form/get-form-group-for-filtering'; import {getFormFieldsRecursive, getFormFieldPreviewComponent} from 'core/ui/components/generic-form/form-field'; import {FormViewEdit} from 'core/ui/components/generic-form/from-group'; @@ -36,6 +35,7 @@ import { IBaseRestApiResponse, } from 'superdesk-api'; import {gettext} from 'core/utils'; +import ng from 'core/services/ng'; interface IState> { previewItemId: string | null; @@ -47,11 +47,8 @@ interface IState> { refetchDataScheduled: boolean; } -interface IPropsConnected { +export interface IPropsConnected { items?: ICrudManager; - modal: any; - notify: any; - $rootScope: any; } export class GenericListPageComponent @@ -59,6 +56,9 @@ export class GenericListPageComponent implements IGenericListPageComponent { searchBarRef: SearchBar | null; + modal: any; + notify: any; + $rootScope: any; constructor(props: IPropsGenericForm & IPropsConnected) { super(props); @@ -89,17 +89,21 @@ export class GenericListPageComponent this.removeFilter = this.removeFilter.bind(this); this.refetchDataUsingCurrentFilters = this.refetchDataUsingCurrentFilters.bind(this); this.filter = this.filter.bind(this); + + this.modal = ng.get('modal'); + this.notify = ng.get('notify'); + this.$rootScope = ng.get('$rootScope'); } openPreview(id) { if (this.state.editItemId != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'Can\'t open a preview while in edit mode', ), }); } else if (this.state.newItem != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'Can\'t open a preview while in create mode', @@ -135,13 +139,13 @@ export class GenericListPageComponent } deleteItem(item: T) { const deleteNow = () => this.props.items.delete(item).then(() => { - this.props.notify.success(gettext('The item has been deleted.')); + this.notify.success(gettext('The item has been deleted.')); }); - this.props.modal.confirm(gettext('Are you sure you want to delete this item?')) + this.modal.confirm(gettext('Are you sure you want to delete this item?')) .then(() => { if (this.state.editItemId != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'Edit mode must closed before you can delete an item.', @@ -158,7 +162,7 @@ export class GenericListPageComponent } startEditing(id: string) { if (this.state.editItemId != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'Can\'t edit this item, because another item is in edit mode.', @@ -223,7 +227,7 @@ export class GenericListPageComponent } filter() { if (this.state.editItemId != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'The item in edit mode must be closed before you can filter.', @@ -272,7 +276,7 @@ export class GenericListPageComponent } openNewItemForm() { if (this.state.editItemId != null) { - this.props.modal.alert({ + this.modal.alert({ headerText: gettext('Warning'), bodyText: gettext( 'Can\'t add a new item, because another item is in edit mode.', @@ -295,7 +299,7 @@ export class GenericListPageComponent if (this.props.refreshOnEvents != null) { this.props.refreshOnEvents.forEach((eventName) => { - this.props.$rootScope.$on(eventName, () => { + this.$rootScope.$on(eventName, () => { // will update the list using selected filtering / sort options this.refetchDataUsingCurrentFilters(); }); @@ -559,7 +563,7 @@ export class GenericListPageComponent }} item={this.state.newItem} onSave={(item: T) => this.props.items.create(item).then((res) => { - this.props.notify.success(gettext('The item has been created.')); + this.notify.success(gettext('The item has been created.')); this.closeNewItemForm(); this.openPreview(res._id); })} @@ -584,7 +588,7 @@ export class GenericListPageComponent this.props.items._items.find(({_id}) => _id === this.state.editItemId) } onSave={(nextItem) => this.props.items.update(nextItem).then(() => { - this.props.notify.success(gettext('The item has been updated.')); + this.notify.success(gettext('The item has been updated.')); })} onClose={this.closePreview} /> @@ -619,27 +623,24 @@ export class GenericListPageComponent export const getGenericListPageComponent = (resource: string, formConfig: IFormGroup) => - connectServices>( - connectCrudManager, IPropsConnected, T>( - GenericListPageComponent, - 'items', - resource, - (filters: IFormGroup) => { - const formConfigForFilters = getFormGroupForFiltering(formConfig); - const fieldTypesLookup = getFormFieldsFlat(formConfigForFilters) - .reduce((accumulator, item) => ({...accumulator, ...{[item.field]: item.type}}), {}); - - let filtersFormatted = {}; - - for (let fieldName in filters) { - filtersFormatted[fieldName] = generateFilterForServer( - fieldTypesLookup[fieldName], - filters[fieldName], - ); - } + connectCrudManager, IPropsConnected, T>( + GenericListPageComponent, + 'items', + resource, + (filters: IFormGroup) => { + const formConfigForFilters = getFormGroupForFiltering(formConfig); + const fieldTypesLookup = getFormFieldsFlat(formConfigForFilters) + .reduce((accumulator, item) => ({...accumulator, ...{[item.field]: item.type}}), {}); + + let filtersFormatted = {}; + + for (let fieldName in filters) { + filtersFormatted[fieldName] = generateFilterForServer( + fieldTypesLookup[fieldName], + filters[fieldName], + ); + } - return filtersFormatted; - }, - ) - , ['modal', '$rootScope', 'notify'], + return filtersFormatted; + }, ); From 47a20ebb5459298b092ce2e5b2eb77708a4f070a Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 10:16:22 +0200 Subject: [PATCH 0013/1070] use GenericListPageComponent --- .../ContentProfileConfigNonText.tsx | 204 +++++++++--------- 1 file changed, 100 insertions(+), 104 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 75b28aa347..26392275df 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -1,13 +1,21 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; -import {IContentProfileNonText, IFormGroup, IFormField, IContentProfileField, IArrayKeyed} from 'superdesk-api'; -import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc'; -import {FormViewEdit} from 'core/ui/components/generic-form/from-group'; +import { + IContentProfileNonText, + IFormGroup, + IFormField, + IContentProfileField, + IArrayKeyed, + ICrudManager, + ICrudManagerResponse, + IItemWithId, +} from 'superdesk-api'; + import {gettext} from 'core/utils'; import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; -import {Button} from 'superdesk-ui-framework'; import {IContentProfileTypeNonText} from '../controllers/ContentProfilesController'; import {assertNever} from 'core/helpers/typescript-helpers'; +import {GenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; interface IProps { profile: IContentProfileNonText; @@ -176,91 +184,27 @@ export function getAttributesForFormFieldType(type: IContentProfileFieldTypes): } } -class FieldComponent extends React.PureComponent<{ - field: IArrayKeyed[0]; - profileType: IContentProfileTypeNonText; - onChange(field: IArrayKeyed[0]): void; - onRemove(): void} -> { - render() { - const {field} = this.props; - const formConfig = getFormConfig(this.props.profileType); +const renderRow = ( + key: string, + item: IContentProfileFieldWithSystemId, + page: GenericListPageComponent, +) => ( +
+ {item.label} + +
+); - if (field.value.type != null) { - const fieldAttributes = getAttributesForFormFieldType(IContentProfileFieldTypes[field.value.type]); +type IContentProfileFieldWithSystemId = IContentProfileField & IItemWithId; - if (fieldAttributes.length > 0) { - const attributesFormGroup: IFormGroup = { - direction: 'vertical', - type: 'inline', - form: fieldAttributes, - }; +function stripSystemId(item: IContentProfileFieldWithSystemId): IContentProfileField { + const copy = {...item}; - formConfig.form.push(attributesFormGroup); - } - } + delete copy['_id']; - return ( -
- { - this.props.onChange({...field, value: {...field.value, [key]: value}}); - }} - issues={{}} - /> -
- ); - } + return copy; } -const FieldSortable = SortableElement(FieldComponent); - -class FieldsComponent extends React.Component<{ - fields: IArrayKeyed; - profileType: IContentProfileTypeNonText; - onChange(fields: IArrayKeyed): void; -}> { - render() { - const {fields} = this.props; - - return ( -
- { - fields.map((field, index) => ( - { - const fieldsNext = this.props.fields.map((f) => f.key === _field.key ? _field : f); - - this.props.onChange(fieldsNext); - }} - onRemove={() => { - this.props.onChange(fields.filter((f) => f.key !== field.key)); - }} - /> - )) - } -
- ); - } -} - -const FieldsSortable = SortableContainer(FieldsComponent); - export class ContentProfileConfigNonText extends React.Component { private generateKey: () => string; private lastKey: number; @@ -285,34 +229,86 @@ export class ContentProfileConfigNonText extends React.Component render() { const {fields} = this.state; - return ( -
- - -
- - -); + { + item.is_active ? null : ( + + {gettext('Inactive')} + + ) + } + +
+ + +
+
+ + ); + } +} export class InternalDestinations extends React.Component { render() { @@ -144,7 +145,7 @@ export class InternalDestinations extends React.Component { return ( , -) => ( -
- {item.label} - -
-); +class ItemComponent extends React.PureComponent> { + render() { + const {item, page} = this.props; + + return ( +
+ {item.label} + +
+ ); + } +} type IContentProfileFieldWithSystemId = IContentProfileField & IItemWithId; @@ -306,7 +309,7 @@ export class ContentProfileConfigNonText extends React.Component diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 744a0f800a..452f75f06a 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -672,12 +672,17 @@ declare module 'superdesk-api' { // GENERIC FORM + export interface IGenericFormItemComponent { + item: T; + page: IGenericListPageComponent; + } + export interface IPropsGenericForm> { formConfig: IFormGroup; defaultSortOption: ISortOption; additionalSortOptions?: Array<{label: string; field: string;}>; defaultFilters?: Partial; - renderRow(key: string, item: T, page: IGenericListPageComponent): JSX.Element; + ItemComponent: React.ComponentType; // Allows initializing a new item with some fields already filled. getNewItemTemplate?(page: IGenericListPageComponent): Partial; diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 1407161015..f8b48648d3 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -323,7 +323,7 @@ export class GenericListPageComponent const pageSize = this.props.items._meta.max_results; const pageCount = Math.ceil(totalResults / pageSize); - const {formConfig, renderRow} = this.props; + const {formConfig, ItemComponent} = this.props; const formConfigForFilters = getFormGroupForFiltering(formConfig); const fieldsList = getFormFieldsRecursive(formConfig.form); @@ -360,7 +360,7 @@ export class GenericListPageComponent > { this.props.items._items.map( - (item) => renderRow(item._id, item, this), + (item) => , ) }
diff --git a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx index 38dd0c6e5c..4edf93c0de 100644 --- a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx +++ b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx @@ -1,8 +1,10 @@ +/* eslint-disable react/no-multi-comp */ + import * as React from 'react'; import { IFormGroup, - IGenericListPageComponent, ISuperdesk, + IGenericFormItemComponent, } from 'superdesk-api'; import {IKnowledgeBaseItem, IKnowledgeBaseItemBase} from './interfaces'; import {getFields} from './GetFields'; @@ -38,49 +40,51 @@ export function getAnnotationsLibraryPage(superdesk: ISuperdesk) { const AnnotationsLibraryPageComponent = getGenericListPageComponent('concept_items', formConfig); - const renderRow = ( - key: string, - item: IKnowledgeBaseItem, - page: IGenericListPageComponent, - ) => ( - page.openPreview(item._id)}> - - {getFormFieldPreviewComponent(item, nameField)} - - - {getFormFieldPreviewComponent(item, definitionField, {showAsPlainText: true})} - - -
- + class ItemComponent extends React.PureComponent> { + render() { + const {item, page} = this.props; - -
-
-
- ); + return ( + page.openPreview(item._id)}> + + {getFormFieldPreviewComponent(item, nameField)} + + + {getFormFieldPreviewComponent(item, definitionField, {showAsPlainText: true})} + + +
+ + + +
+
+
+ ); + } + } return ( { const baseTemplate: Partial = { cpnat_type: 'cpnat:abstract', From dab4410b74fe4706ea1f96c7637339f58089d1bf Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 13:38:13 +0200 Subject: [PATCH 0016/1070] implement sorting --- .../InternalDestinations.tsx | 4 +- .../ContentProfileConfigNonText.tsx | 56 ++++++++++++++++++- scripts/core/superdesk-api.d.ts | 6 +- .../components/ListPage/generic-list-page.tsx | 23 ++++++-- .../src/annotations-library-page.tsx | 4 +- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/scripts/apps/internal-destinations/InternalDestinations.tsx b/scripts/apps/internal-destinations/InternalDestinations.tsx index 364701e532..06acd52ced 100644 --- a/scripts/apps/internal-destinations/InternalDestinations.tsx +++ b/scripts/apps/internal-destinations/InternalDestinations.tsx @@ -5,7 +5,7 @@ import {getGenericListPageComponent} from 'core/ui/components/ListPage/generic-l import {ListItemColumn, ListItemActionsMenu, ListItem} from 'core/components/ListItem'; import {getFormFieldPreviewComponent} from 'core/ui/components/generic-form/form-field'; import {IInternalDestination} from 'superdesk-interfaces/InternalDestination'; -import {IFormField, IFormGroup, IGenericFormItemComponent} from 'superdesk-api'; +import {IFormField, IFormGroup, IPropsGenericFormItemComponent} from 'superdesk-api'; import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; import {gettext} from 'core/utils'; @@ -73,7 +73,7 @@ function getSendAfterScheduleField(): IFormField { }; } -class ItemComponent extends React.PureComponent> { +class ItemComponent extends React.PureComponent> { render() { const {item, page} = this.props; diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index c7171c634c..965a716864 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -9,7 +9,7 @@ import { ICrudManager, ICrudManagerResponse, IItemWithId, - IGenericFormItemComponent, + IPropsGenericFormItemComponent, } from 'superdesk-api'; import {gettext} from 'core/utils'; @@ -17,6 +17,8 @@ import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; import {IContentProfileTypeNonText} from '../controllers/ContentProfilesController'; import {assertNever} from 'core/helpers/typescript-helpers'; import {GenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; +import {SortableContainer, SortableElement} from 'react-sortable-hoc'; +import arrayMove from 'array-move'; interface IProps { profile: IContentProfileNonText; @@ -185,12 +187,12 @@ export function getAttributesForFormFieldType(type: IContentProfileFieldTypes): } } -class ItemComponent extends React.PureComponent> { +class ItemBase extends React.PureComponent> { render() { const {item, page} = this.props; return ( -
+
{item.label}
@@ -198,6 +200,34 @@ class ItemComponent extends React.PureComponent> { + render() { + const {item, page, index} = this.props; + + return ( + + ); + } +} + +class ItemsContainerBase extends React.PureComponent { + render() { + return ( +
+ {this.props.children} +
+ ); + } +} + +const ItemsContainerBaseSortable = SortableContainer(ItemsContainerBase); + type IContentProfileFieldWithSystemId = IContentProfileField & IItemWithId; function stripSystemId(item: IContentProfileFieldWithSystemId): IContentProfileField { @@ -211,6 +241,7 @@ function stripSystemId(item: IContentProfileFieldWithSystemId): IContentProfileF export class ContentProfileConfigNonText extends React.Component { private generateKey: () => string; private lastKey: number; + private ItemsContainerComponent: React.ComponentType; constructor(props: IProps) { super(props); @@ -223,6 +254,24 @@ export class ContentProfileConfigNonText extends React.Component (field) => ({key: this.generateKey(), value: field}), ), }; + + const onSortEnd = ({oldIndex, newIndex}) => { + this.setState({ + fields: arrayMove(this.state.fields, oldIndex, newIndex), + }); + }; + + class ItemsContainerComponent extends React.PureComponent { + render() { + return ( + + {this.props.children} + + ); + } + } + + this.ItemsContainerComponent = ItemsContainerComponent; } componentDidUpdate() { @@ -310,6 +359,7 @@ export class ContentProfileConfigNonText extends React.Component formConfig={formConfig} defaultSortOption={{field: 'name', direction: 'ascending'}} ItemComponent={ItemComponent} + ItemsContainerComponent={this.ItemsContainerComponent} disallowFiltering items={crudManagerForContentProfileFields} /> diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 452f75f06a..9f40b66f96 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -672,9 +672,10 @@ declare module 'superdesk-api' { // GENERIC FORM - export interface IGenericFormItemComponent { + export interface IPropsGenericFormItemComponent { item: T; page: IGenericListPageComponent; + index: number; } export interface IPropsGenericForm> { @@ -682,7 +683,8 @@ declare module 'superdesk-api' { defaultSortOption: ISortOption; additionalSortOptions?: Array<{label: string; field: string;}>; defaultFilters?: Partial; - ItemComponent: React.ComponentType; + ItemComponent: React.ComponentType>; + ItemsContainerComponent?: React.ComponentType; // Allows initializing a new item with some fields already filled. getNewItemTemplate?(page: IGenericListPageComponent): Partial; diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index f8b48648d3..c591d8cc6c 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -52,6 +52,19 @@ export interface IPropsConnected { items?: ICrudManager; } +class DefaultItemsContainerComponent extends React.PureComponent { + render() { + return ( +
+ {this.props.children} +
+ ); + } +} + export class GenericListPageComponent extends React.Component & IPropsConnected, IState> implements IGenericListPageComponent @@ -324,6 +337,7 @@ export class GenericListPageComponent const pageCount = Math.ceil(totalResults / pageSize); const {formConfig, ItemComponent} = this.props; + const ItemsContainerComponent = this.props.ItemsContainerComponent ?? DefaultItemsContainerComponent; const formConfigForFilters = getFormGroupForFiltering(formConfig); const fieldsList = getFormFieldsRecursive(formConfig.form); @@ -354,16 +368,13 @@ export class GenericListPageComponent } } else { return ( -
+ { this.props.items._items.map( - (item) => , + (item, i) => , ) } -
+ ); } }; diff --git a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx index 4edf93c0de..3fbc2580f9 100644 --- a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx +++ b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { IFormGroup, ISuperdesk, - IGenericFormItemComponent, + IPropsGenericFormItemComponent, } from 'superdesk-api'; import {IKnowledgeBaseItem, IKnowledgeBaseItemBase} from './interfaces'; import {getFields} from './GetFields'; @@ -40,7 +40,7 @@ export function getAnnotationsLibraryPage(superdesk: ISuperdesk) { const AnnotationsLibraryPageComponent = getGenericListPageComponent('concept_items', formConfig); - class ItemComponent extends React.PureComponent> { + class ItemComponent extends React.PureComponent> { render() { const {item, page} = this.props; From 33fa87758dc64716d9e9355888a8131fe1905113 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 14:11:54 +0200 Subject: [PATCH 0017/1070] show pagination onlu when there is more than one page --- .../components/ListPage/generic-list-page.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index c591d8cc6c..76fb915e8d 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -326,14 +326,16 @@ export class GenericListPageComponent } } render() { - if (this.props.items._items == null) { + const {items} = this.props; + + if (items._items == null) { // loading return null; } - const {activeFilters} = this.props.items; - const totalResults = this.props.items._meta.total; - const pageSize = this.props.items._meta.max_results; + const {activeFilters} = items; + const totalResults = items._meta.total; + const pageSize = items._meta.max_results; const pageCount = Math.ceil(totalResults / pageSize); const {formConfig, ItemComponent} = this.props; @@ -348,7 +350,7 @@ export class GenericListPageComponent ]; const getContents = () => { - if (this.props.items._items.length === 0) { + if (items._items.length === 0) { if (Object.keys(activeFilters).length > 0) { return ( @@ -370,7 +372,7 @@ export class GenericListPageComponent return ( { - this.props.items._items.map( + items._items.map( (item, i) => , ) } @@ -426,9 +428,9 @@ export class GenericListPageComponent
{ @@ -494,7 +496,7 @@ export class GenericListPageComponent
{ - this.props.items._items.length === 0 ? null : ( + items._meta.max_results === items._meta.total || items._items.length === 0 ? null : (
marginPagesDisplayed={2} pageRangeDisplayed={5} onPageChange={({selected}) => { - if (this.props.items._meta.page !== (selected + 1)) { - this.props.items.goToPage(selected + 1); + if (items._meta.page !== (selected + 1)) { + items.goToPage(selected + 1); } }} - initialPage={this.props.items._meta.page - 1} + initialPage={items._meta.page - 1} containerClassName={'bs-pagination'} activeClassName="active" /> @@ -528,7 +530,7 @@ export class GenericListPageComponent ); const filterValuePreview = getFormFieldPreviewComponent( - this.props.items.activeFilters, + items.activeFilters, currentField, ); @@ -567,7 +569,7 @@ export class GenericListPageComponent })); }} item={this.state.newItem} - onSave={(item: T) => this.props.items.create(item).then((res) => { + onSave={(item: T) => items.create(item).then((res) => { this.notify.success(gettext('The item has been created.')); this.closeNewItemForm(); this.openPreview(res._id); @@ -590,9 +592,9 @@ export class GenericListPageComponent operation="editing" formConfig={formConfig} item={ - this.props.items._items.find(({_id}) => _id === this.state.editItemId) + items._items.find(({_id}) => _id === this.state.editItemId) } - onSave={(nextItem) => this.props.items.update(nextItem).then(() => { + onSave={(nextItem) => items.update(nextItem).then(() => { this.notify.success(gettext('The item has been updated.')); })} onClose={this.closePreview} @@ -612,9 +614,9 @@ export class GenericListPageComponent operation="editing" formConfig={formConfig} item={ - this.props.items._items.find(({_id}) => _id === this.state.previewItemId) + items._items.find(({_id}) => _id === this.state.previewItemId) } - onSave={(nextItem) => this.props.items.update(nextItem)} + onSave={(nextItem) => items.update(nextItem)} onClose={this.closePreview} /> From db536baebdd508efbae503f04a29b32360f67452 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 16:02:24 +0200 Subject: [PATCH 0018/1070] don't render the submenu if it has no items --- .../ContentProfileConfigNonText.tsx | 5 +- scripts/core/superdesk-api.d.ts | 2 +- .../components/ListPage/generic-list-page.tsx | 131 ++++++++++-------- .../core/ui/components/only-with-children.tsx | 57 ++++++++ 4 files changed, 132 insertions(+), 63 deletions(-) create mode 100644 scripts/core/ui/components/only-with-children.tsx diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 965a716864..136cdbf040 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -290,7 +290,6 @@ export class ContentProfileConfigNonText extends React.Component const crudManagerForContentProfileFields: ICrudManager = { activeFilters: {}, - activeSortOption: {field: 'label', direction: 'ascending'}, read: () => Promise.resolve(fieldsResponse), update: (item) => { return new Promise((resolve) => { @@ -357,11 +356,11 @@ export class ContentProfileConfigNonText extends React.Component
); diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 9f40b66f96..2014ad9c9a 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -680,7 +680,7 @@ declare module 'superdesk-api' { export interface IPropsGenericForm> { formConfig: IFormGroup; - defaultSortOption: ISortOption; + defaultSortOption?: ISortOption; additionalSortOptions?: Array<{label: string; field: string;}>; defaultFilters?: Partial; ItemComponent: React.ComponentType>; diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 76fb915e8d..4a2e55c0ce 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -37,6 +37,7 @@ import { } from 'superdesk-api'; import {gettext} from 'core/utils'; import ng from 'core/services/ng'; +import {OnlyWithChildren} from '../only-with-children'; interface IState> { previewItemId: string | null; @@ -65,6 +66,21 @@ class DefaultItemsContainerComponent extends React.PureComponent { } } +const subNavWrapper: React.ComponentType = (props) => ( +
+
+
+ {props.children} +
+
+
+); + export class GenericListPageComponent extends React.Component & IPropsConnected, IState> implements IGenericListPageComponent @@ -383,73 +399,70 @@ export class GenericListPageComponent return (
-
-
+ + { + this.props.disallowFiltering ? null : ( +
+
+ ) + } + + { + this.props.fieldForSearch == null ? null : ( +
+ { + this.searchBarRef = instance; + }} + value="" + allowCollapsed={false} + onSearch={(value) => { + this.handleFilterFieldChange( + this.props.fieldForSearch.field, + value, + this.filter, + ); + }} + /> +
+ ) + } + + { - this.props.disallowFiltering ? null : ( -
-
+ items.activeSortOption == null ? null : ( + ) } { - this.props.fieldForSearch == null ? null : ( -
- { - this.searchBarRef = instance; - }} - value="" - allowCollapsed={false} - onSearch={(value) => { - this.handleFilterFieldChange( - this.props.fieldForSearch.field, - value, - this.filter, - ); - }} - /> + this.props.disallowCreatingNewItem === true ? null : ( +
+
) } - -
- - - { - this.props.disallowCreatingNewItem === true ? null : ( -
- -
- ) - } -
-
-
+ + { this.state.filtersOpen ? ( diff --git a/scripts/core/ui/components/only-with-children.tsx b/scripts/core/ui/components/only-with-children.tsx new file mode 100644 index 0000000000..6d2bfbbaf1 --- /dev/null +++ b/scripts/core/ui/components/only-with-children.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +interface IWrapper { + wrapper: React.ComponentType; +} + +interface IDirectElement { + tagName?: keyof React.ReactHTML; + className?: string; + style?: React.CSSProperties; +} + +function isWrapper(x: IProps): x is IWrapper { + return x['wrapper'] != null; +} + +type IProps = IWrapper | IDirectElement; + +function hasChildren(children) { + if (Array.isArray(children)) { + return children.some((child) => hasChildren(child)); + } else if (children?.type?.name === 'OnlyWithChildren' && children?.props?.children != null) { + return hasChildren(children.props.children); + } else { + return children != null; + } +} + +/** + * Doesn't render the parent when all children are null. + * It only works with static values. + * React components inside children array are treated as non-null, unless it's an instance of OnlyWithChildren. +*/ +export class OnlyWithChildren extends React.PureComponent { + render() { + if (hasChildren(this.props.children) !== true) { + return null; + } + + if (isWrapper(this.props)) { + const Wrapper = this.props.wrapper; + + return ( + {this.props.children} + ); + } else { + return React.createElement( + this.props.tagName ?? 'div', + { + className: this.props.className, + style: this.props.style, + }, + this.props.children, + ); + } + } +} \ No newline at end of file From f8d47d9a9bce03a96e442e64981f9558414288e8 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 22 May 2020 16:14:13 +0200 Subject: [PATCH 0019/1070] move default sort option from generic list to crud manager --- scripts/core/helpers/CrudManager.tsx | 3 ++- scripts/core/superdesk-api.d.ts | 2 +- scripts/core/ui/components/ListPage/generic-list-page.tsx | 5 ++++- .../src/AnnotationInputWithKnowledgeBase.tsx | 6 +++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/core/helpers/CrudManager.tsx b/scripts/core/helpers/CrudManager.tsx index 7425be22ad..083b60b8c5 100644 --- a/scripts/core/helpers/CrudManager.tsx +++ b/scripts/core/helpers/CrudManager.tsx @@ -212,6 +212,7 @@ export function connectCrudManager name: string, endpoint: string, + defaultSortOption?: ISortOption, formatFiltersForServer?: (filters: ICrudManagerFilters) => ICrudManagerFilters, ): React.ComponentType { const component = class extends React.Component> @@ -226,7 +227,7 @@ export function connectCrudManager> { formConfig: IFormGroup; - defaultSortOption?: ISortOption; additionalSortOptions?: Array<{label: string; field: string;}>; defaultFilters?: Partial; ItemComponent: React.ComponentType>; @@ -1048,6 +1047,7 @@ declare module 'superdesk-api' { WrappedComponent: React.ComponentType, name: string, endpoint: string, + defaultSortOption?: ISortOption, formatFiltersForServer?: (filters: ICrudManagerFilters) => ICrudManagerFilters, ): React.ComponentType; ListItem: React.ComponentType; diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 4a2e55c0ce..ab06e8eb03 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -34,6 +34,7 @@ import { ICrudManager, IFormGroup, IBaseRestApiResponse, + ISortOption, } from 'superdesk-api'; import {gettext} from 'core/utils'; import ng from 'core/services/ng'; @@ -325,7 +326,7 @@ export class GenericListPageComponent componentDidMount() { const filters = this.props.defaultFilters ? this.validateFilters(this.props.defaultFilters) : {}; - this.props.items.read(1, this.props.defaultSortOption, filters); + this.props.items.read(1, this.props.items.activeSortOption, filters); if (this.props.refreshOnEvents != null) { this.props.refreshOnEvents.forEach((eventName) => { @@ -644,11 +645,13 @@ export class GenericListPageComponent export const getGenericListPageComponent = ( resource: string, formConfig: IFormGroup, + defaultSortOption?: ISortOption, ) => { var Component = connectCrudManager, IPropsConnected, T>( GenericListPageComponent, 'items', resource, + defaultSortOption, (filters: IFormGroup) => { const formConfigForFilters = getFormGroupForFiltering(formConfig); const fieldTypesLookup = getFormFieldsFlat(formConfigForFilters) diff --git a/scripts/extensions/annotationsLibrary/src/AnnotationInputWithKnowledgeBase.tsx b/scripts/extensions/annotationsLibrary/src/AnnotationInputWithKnowledgeBase.tsx index 3ed605f026..a486789526 100644 --- a/scripts/extensions/annotationsLibrary/src/AnnotationInputWithKnowledgeBase.tsx +++ b/scripts/extensions/annotationsLibrary/src/AnnotationInputWithKnowledgeBase.tsx @@ -6,6 +6,7 @@ import { ICrudManager, IPropsAnnotationInputComponent, ISuperdesk, + ISortOption, } from 'superdesk-api'; import {getFields} from './GetFields'; @@ -13,6 +14,8 @@ interface IPropsConnected { conceptItems: ICrudManager; } +const defaultSortOption: ISortOption = {field: 'name', direction: 'ascending'}; + export function getAnnotationInputWithKnowledgeBase(superdesk: ISuperdesk) { class AnnotationInputWithKnowledgeBaseComponent extends React.Component { @@ -23,7 +26,7 @@ export function getAnnotationInputWithKnowledgeBase(superdesk: ISuperdesk) { this.props.conceptItems.read( 1, - {field: 'name', direction: 'ascending'}, + defaultSortOption, {name: generateFilterForServer(nameField.type, this.props.annotationText)}, ); } @@ -50,5 +53,6 @@ export function getAnnotationInputWithKnowledgeBase(superdesk: ISuperdesk) { AnnotationInputWithKnowledgeBaseComponent, 'conceptItems', 'concept_items', + defaultSortOption, ); } From 7a4a0d0d4a6be2352b5b97e08ddeb8d8c647a285 Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 26 May 2020 11:40:31 +0200 Subject: [PATCH 0020/1070] add header/content sections --- .../ContentProfileConfigNonText.tsx | 167 ++++++++++++------ scripts/core/superdesk-api.d.ts | 6 + 2 files changed, 122 insertions(+), 51 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 136cdbf040..d6ce9281ac 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -19,6 +19,8 @@ import {assertNever} from 'core/helpers/typescript-helpers'; import {GenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; import {SortableContainer, SortableElement} from 'react-sortable-hoc'; import arrayMove from 'array-move'; +import {Button} from 'superdesk-ui-framework'; +import {groupBy} from 'lodash'; interface IProps { profile: IContentProfileNonText; @@ -26,7 +28,8 @@ interface IProps { } interface IState { - fields: IArrayKeyed; + fields: {[key in IContentProfileSection]: IArrayKeyed}; + activeTab: keyof typeof IContentProfileSection; } // subset of FormFieldType @@ -35,11 +38,30 @@ enum IContentProfileFieldTypes { number = 'number', } +enum IContentProfileSection { + header = 'header', + content = 'content', +} + function getAllContentProfileFieldTypes(): Array { return Object.keys(IContentProfileFieldTypes).map((key) => IContentProfileFieldTypes[key]); } -function getImageFormConfig(): IFormGroup { +function getAllContentProfileSections(): Array { + return Object.keys(IContentProfileSection).map((key) => IContentProfileSection[key]); +} + +function getLabelForSection(section: IContentProfileSection) { + if (section === IContentProfileSection.header) { + return gettext('Header'); + } else if (section === IContentProfileSection.content) { + return gettext('Content'); + } else { + return assertNever(section); + } +} + +function getCommonContentProfileConfig(): Array { const idField: IFormField = { label: gettext('ID'), type: FormFieldType.textSingleLine, @@ -54,6 +76,17 @@ function getImageFormConfig(): IFormGroup { required: true, }; + const sectionField: IFormField = { + label: gettext('Section'), + type: FormFieldType.select, + component_parameters: { + items: getAllContentProfileSections() + .map((section) => ({id: section, label: getLabelForSection(section)})), + }, + field: 'section', + required: true, + }; + const sdWidth: IFormField = { label: gettext('Width'), type: FormFieldType.select, @@ -100,6 +133,10 @@ function getImageFormConfig(): IFormGroup { required: false, }; + return [idField, labelField, sectionField, sdWidth, fieldType, requiredField]; +} + +function getImageFormConfig(): IFormGroup { const displayInMediaEditor: IFormField = { label: gettext('Display in media editor'), type: FormFieldType.checkbox, @@ -110,38 +147,17 @@ function getImageFormConfig(): IFormGroup { const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [labelField, idField, sdWidth, fieldType, requiredField, displayInMediaEditor], + form: [...getCommonContentProfileConfig(), displayInMediaEditor], }; return formConfig; } function getVideoFormConfig(): IFormGroup { - const idField: IFormField = { - label: gettext('ID'), - type: FormFieldType.textSingleLine, - field: 'id', - required: true, - }; - - const labelField: IFormField = { - label: gettext('Label'), - type: FormFieldType.textSingleLine, - field: 'label', - required: true, - }; - - const requiredField: IFormField = { - label: gettext('Required'), - type: FormFieldType.checkbox, - field: 'required', - required: false, - }; - const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [labelField, idField, requiredField], + form: [...getCommonContentProfileConfig()], }; return formConfig; @@ -195,6 +211,7 @@ class ItemBase extends React.PureComponent {item.label} +
); } @@ -249,16 +266,23 @@ export class ContentProfileConfigNonText extends React.Component this.lastKey = 0; this.generateKey = () => (++this.lastKey).toString(); + // adding keys to items because they will be reordered and they don't have static IDs to be used as react keys + const fieldsKeyed = (props.profile.fields ?? []).map( + (field) => ({key: this.generateKey(), value: field}), + ); + + var grouped = groupBy(fieldsKeyed, (item) => item.value.section); + this.state = { - fields: (props.profile.fields ?? []).map( - (field) => ({key: this.generateKey(), value: field}), - ), + fields: { + header: grouped[IContentProfileSection.header] ?? [], + content: grouped[IContentProfileSection.content] ?? [], + }, + activeTab: getAllContentProfileSections()[0], }; const onSortEnd = ({oldIndex, newIndex}) => { - this.setState({ - fields: arrayMove(this.state.fields, oldIndex, newIndex), - }); + this.updateCurrentFields((_fields) => arrayMove(_fields, oldIndex, newIndex)); }; class ItemsContainerComponent extends React.PureComponent { @@ -272,14 +296,42 @@ export class ContentProfileConfigNonText extends React.Component } this.ItemsContainerComponent = ItemsContainerComponent; + + this.updateCurrentFields = this.updateCurrentFields.bind(this); + } + + updateCurrentFields( + fn: (items: IArrayKeyed) => IArrayKeyed, + callback?: () => void, + ) { + this.setState( + { + fields: { + ...this.state.fields, + [this.state.activeTab]: fn(this.state.fields[this.state.activeTab]), + }, + }, + () => { + if (callback != null) { + callback(); + } + }, + ); } componentDidUpdate() { - this.props.profile.fields = this.state.fields.map(({value}) => value); + const initialArray: Array = []; + + this.props.profile.fields = getAllContentProfileSections() + .reduce>( + (acc, key) => [...acc, ...this.state.fields[key].map(({value}) => value)], + initialArray, + ); } render() { - const {fields} = this.state; + const {activeTab} = this.state; + const fields = this.state.fields[this.state.activeTab]; const formConfig = getFormConfig(IContentProfileTypeNonText[this.props.profileType]); @@ -293,15 +345,15 @@ export class ContentProfileConfigNonText extends React.Component read: () => Promise.resolve(fieldsResponse), update: (item) => { return new Promise((resolve) => { - this.setState( - { - fields: this.state.fields.map((field) => { + this.updateCurrentFields( + (_fields) => { + return _fields.map((field) => { if (field.key === item._id) { return {...field, value: stripSystemId(item)}; } else { return field; } - }), + }); }, () => { resolve(item); @@ -316,16 +368,14 @@ export class ContentProfileConfigNonText extends React.Component _id: this.generateKey(), }; - this.setState( - { - fields: [ - { - key: this.generateKey(), - value: item, - }, - ...fields, - ], - }, + this.updateCurrentFields( + (_fields) => [ + { + key: this.generateKey(), + value: item, + }, + ..._fields, + ], () => { resolve(itemWithId); }, @@ -334,10 +384,10 @@ export class ContentProfileConfigNonText extends React.Component }, delete: (item) => { return new Promise((resolve) => { - this.setState( - { - fields: this.state.fields.filter(({key}) => key !== item._id), - }, + this.updateCurrentFields( + (_fields) => _fields.filter( + ({key}) => key !== item._id, + ), () => { resolve(); }, @@ -354,6 +404,21 @@ export class ContentProfileConfigNonText extends React.Component return (
+ { + getAllContentProfileSections().map((section) => { + return ( +
diff --git a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx index 6a284570a0..5613007a45 100644 --- a/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx +++ b/scripts/extensions/annotationsLibrary/src/annotations-library-page.tsx @@ -86,7 +86,7 @@ export function getAnnotationsLibraryPage(superdesk: ISuperdesk) { return ( formConfig} ItemComponent={ItemComponent} getNewItemTemplate={(page) => { const baseTemplate: Partial = { From bd063301c37f8b0bb717ce4ae98284c130659aea Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 26 May 2020 16:17:14 +0200 Subject: [PATCH 0023/1070] improve types --- scripts/core/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/core/utils.ts b/scripts/core/utils.ts index e16ad294f4..b8d30309a1 100644 --- a/scripts/core/utils.ts +++ b/scripts/core/utils.ts @@ -42,7 +42,7 @@ export const getSuperdeskType = (event, supportExternalFiles = true) => export const gettext = ( text: string, params: {[key: string]: string | number} = {}, -) => { +): string => { if (!text) { return ''; } @@ -75,7 +75,7 @@ export const gettextPlural = ( text: string, pluralText: string, params: {[key: string]: string | number} = {}, -) => { +): string => { if (!text) { return ''; } From 3c1acc72dbcac991b0bd12b47ffb19e84ee6ad46 Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 26 May 2020 16:45:37 +0200 Subject: [PATCH 0024/1070] display extra settings by field type --- .../ContentProfileConfigNonText.tsx | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 4d0ee0e34d..189f3cdcec 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -61,7 +61,9 @@ function getLabelForSection(section: IContentProfileSection) { } } -function getCommonContentProfileConfig(): Array { +function getCommonContentProfileConfig( + field: Partial | undefined, +): Array { const idField: IFormField = { label: gettext('ID'), type: FormFieldType.textSingleLine, @@ -133,10 +135,26 @@ function getCommonContentProfileConfig(): Array { required: false, }; - return [idField, labelField, sectionField, sdWidth, fieldType, requiredField]; + const fieldTypeGroup: IFormGroup = { + type: 'inline', + direction: 'vertical', + form: [ + fieldType, + ...(field?.type == null ? [] : getAttributesForFormFieldType(IContentProfileFieldTypes[field.type])), + ], + }; + + return [ + idField, + labelField, + sectionField, + sdWidth, + fieldTypeGroup, + requiredField, + ]; } -function getImageFormConfig(): IFormGroup { +function getImageFormConfig(field: Partial | undefined): IFormGroup { const displayInMediaEditor: IFormField = { label: gettext('Display in media editor'), type: FormFieldType.checkbox, @@ -147,28 +165,31 @@ function getImageFormConfig(): IFormGroup { const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [...getCommonContentProfileConfig(), displayInMediaEditor], + form: [...getCommonContentProfileConfig(field), displayInMediaEditor], }; return formConfig; } -function getVideoFormConfig(): IFormGroup { +function getVideoFormConfig(field: Partial): IFormGroup { const formConfig: IFormGroup = { direction: 'vertical', type: 'inline', - form: [...getCommonContentProfileConfig()], + form: [...getCommonContentProfileConfig(field)], }; return formConfig; } -function getFormConfig(type: IContentProfileTypeNonText): IFormGroup { +function getFormConfig( + type: IContentProfileTypeNonText, + field: Partial | undefined, +): IFormGroup { switch (type) { case IContentProfileTypeNonText.image: - return getImageFormConfig(); + return getImageFormConfig(field); case IContentProfileTypeNonText.video: - return getVideoFormConfig(); + return getVideoFormConfig(field); default: return assertNever(type); } @@ -192,7 +213,7 @@ function getAttributesForTextSingleLine(): Array { return [minLengthField, maxLengthField]; } -export function getAttributesForFormFieldType(type: IContentProfileFieldTypes): Array { +function getAttributesForFormFieldType(type: IContentProfileFieldTypes): Array { switch (type) { case IContentProfileFieldTypes.textSingleLine: return getAttributesForTextSingleLine(); @@ -333,8 +354,6 @@ export class ContentProfileConfigNonText extends React.Component const {activeTab} = this.state; const fields = this.state.fields[this.state.activeTab]; - const formConfig = getFormConfig(IContentProfileTypeNonText[this.props.profileType]); - const fieldsResponse: ICrudManagerResponse = { _items: fields.map(({key, value}) => ({...value, _id: key})), _meta: {total: fields.length, page: 1, max_results: fields.length}, @@ -420,7 +439,7 @@ export class ContentProfileConfigNonText extends React.Component } formConfig} + getFormConfig={(item) => getFormConfig(IContentProfileTypeNonText[this.props.profileType], item)} ItemComponent={ItemComponent} ItemsContainerComponent={this.ItemsContainerComponent} items={crudManagerForContentProfileFields} From 274bfa90257f9fa92c1c8758e2f3fd7af218cd6f Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 26 May 2020 17:07:44 +0200 Subject: [PATCH 0025/1070] improve types --- .../generic-list-page-item-view-edit.tsx | 37 +++++++++---------- .../ui/components/generic-form/from-group.tsx | 10 ++--- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx index 96674f4c37..ae94ff179e 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx @@ -9,42 +9,44 @@ import { SidePanelContent, SidePanelContentBlock, } from 'core/components/SidePanel'; -import {connectServices} from 'core/helpers/ReactRenderAsync'; import {FormViewEdit} from 'core/ui/components/generic-form/from-group'; import {IFormGroup} from 'superdesk-api'; import {isHttpApiError} from 'core/helpers/network'; import {gettext} from 'core/utils'; +import ng from 'core/services/ng'; -interface IProps { +interface IProps { operation: 'editing' | 'creation'; editMode: boolean; formConfig: IFormGroup; - item: {[key: string]: any}; + item: Partial; onEditModeChange(nextValue: boolean): void; onClose: () => void; onCancel?: () => void; onSave: (nextItem) => Promise; - - // connected services - modal?: any; } -interface IState { - nextItem: IProps['item']; +interface IState { + nextItem: IProps['item']; issues: {[field: string]: Array}; } -const getInitialState = (props: IProps) => ({ - nextItem: props.item, - issues: {}, -}); +function getInitialState(props: IProps) { + return { + nextItem: props.item, + issues: {}, + }; +} -class GenericListPageItemViewEditComponent extends React.Component { +export class GenericListPageItemViewEdit extends React.Component, IState> { _mounted: boolean; + modal: any; constructor(props) { super(props); + this.modal = ng.get('modal'); + this.state = getInitialState(props); this.enableEditMode = this.enableEditMode.bind(this); @@ -66,7 +68,7 @@ class GenericListPageItemViewEditComponent extends React.Component) { + handleFieldChange(field: string, nextValue: valueof['item']>) { // using updater function to avoid race conditions this.setState((prevState) => ({ ...prevState, @@ -88,7 +90,7 @@ class GenericListPageItemViewEditComponent extends React.Component { // do nothing @@ -206,8 +208,3 @@ class GenericListPageItemViewEditComponent extends React.Component( - GenericListPageItemViewEditComponent, - ['modal'], -); diff --git a/scripts/core/ui/components/generic-form/from-group.tsx b/scripts/core/ui/components/generic-form/from-group.tsx index ca2dd8c7a4..46bee5a1df 100644 --- a/scripts/core/ui/components/generic-form/from-group.tsx +++ b/scripts/core/ui/components/generic-form/from-group.tsx @@ -6,16 +6,16 @@ import {assertNever} from 'core/helpers/typescript-helpers'; import {FormGroupWrapper} from './form-group-wrapper'; import {IFormGroup} from 'superdesk-api'; -interface IProps { +interface IProps { formConfig: IFormGroup; - item: {[key: string]: any}; + item: Partial; editMode: boolean; issues: {[field: string]: Array}; - handleFieldChange(field: keyof IProps['item'], nextValue: valueof): void; + handleFieldChange(field: string, nextValue: valueof['item']>): void; } // The component is recursive! -export class FormViewEdit extends React.Component { +export class FormViewEdit extends React.Component> { render() { const group: IFormGroup = this.props.formConfig; @@ -47,7 +47,7 @@ export class FormViewEdit extends React.Component { issues={this.props.issues[item.field] || []} previewOutput={false} onChange={ - (nextValue, fieldName?: string) => + (nextValue, fieldName?) => this.props.handleFieldChange( item.field != null ? item.field : fieldName, nextValue, From dddd9d9b845e4ad673ba4ba738bea6ebc69c891e Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 27 May 2020 11:39:54 +0200 Subject: [PATCH 0026/1070] where possible, pass getFormConfig as an argument so it can be called on each render of child components and display the correct fields based on the data of the item being edited --- .../generic-list-page-item-view-edit.tsx | 4 +- .../components/ListPage/generic-list-page.tsx | 94 +++++++++---------- 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx index ae94ff179e..3e275d698a 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx @@ -18,7 +18,7 @@ import ng from 'core/services/ng'; interface IProps { operation: 'editing' | 'creation'; editMode: boolean; - formConfig: IFormGroup; + getFormConfig(item?: Partial): IFormGroup; item: Partial; onEditModeChange(nextValue: boolean): void; onClose: () => void; @@ -197,7 +197,7 @@ export class GenericListPageItemViewEdit extends React.Component, I const {getFormConfig, ItemComponent} = this.props; const ItemsContainerComponent = this.props.ItemsContainerComponent ?? DefaultItemsContainerComponent; - const formConfigForFilters = getFormGroupForFiltering(getFormConfig(this.state.filterValues)); + const formConfigForFilters = getFormGroupForFiltering(this.props.getFormConfig()); const fieldsList = getFormFieldsRecursive(getFormConfig().form); const sortOptions: Array = [ @@ -488,7 +488,9 @@ export class GenericListPageComponent }}> key="new-item" operation="creation" item={this.state.newItem} - formConfig={getFormConfig(this.state.newItem)} + getFormConfig={getFormConfig} editMode={true} onEditModeChange={() => { this.setState((prevState) => ({ @@ -592,53 +594,45 @@ export class GenericListPageComponent onCancel={this.closeNewItemForm} /> - ) : this.state.editItemId != null ? (() => { - const item = items._items.find(({_id}) => _id === this.state.editItemId); - - return ( - - { - this.setState((prevState) => ({ - ...prevState, - editItemId: null, - })); - }} - item={item} - formConfig={getFormConfig(item)} - onSave={(nextItem) => items.update(nextItem).then(() => { - this.notify.success(gettext('The item has been updated.')); - })} - onClose={this.closePreview} - /> - - ); - })() : this.state.previewItemId != null ? (() => { - const item = items._items.find(({_id}) => _id === this.state.previewItemId); - - return ( - - { - this.setState((prevState) => ({ - ...prevState, - editItemId: prevState.previewItemId, - })); - }} - item={item} - formConfig={getFormConfig(item)} - onSave={(nextItem) => items.update(nextItem)} - onClose={this.closePreview} - /> - - ); - })() : null + ) : this.state.editItemId != null ? ( + + { + this.setState((prevState) => ({ + ...prevState, + editItemId: null, + })); + }} + item={items._items.find(({_id}) => _id === this.state.editItemId)} + getFormConfig={getFormConfig} + onSave={(nextItem) => items.update(nextItem).then(() => { + this.notify.success(gettext('The item has been updated.')); + })} + onClose={this.closePreview} + /> + + ) : this.state.previewItemId != null ? ( + + { + this.setState((prevState) => ({ + ...prevState, + editItemId: prevState.previewItemId, + })); + }} + item={items._items.find(({_id}) => _id === this.state.previewItemId)} + getFormConfig={getFormConfig} + onSave={(nextItem) => items.update(nextItem)} + onClose={this.closePreview} + /> + + ) : null }
From 025268bafce3032d847d8bb7da3b0f83c1e53e6c Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 27 May 2020 11:40:09 +0200 Subject: [PATCH 0027/1070] add a separate group for field options --- .../components/ContentProfileConfigNonText.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 189f3cdcec..2785bcb28c 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -10,6 +10,7 @@ import { ICrudManagerResponse, IItemWithId, IPropsGenericFormItemComponent, + IFormGroupCollapsible, } from 'superdesk-api'; import {gettext} from 'core/utils'; @@ -140,10 +141,25 @@ function getCommonContentProfileConfig( direction: 'vertical', form: [ fieldType, - ...(field?.type == null ? [] : getAttributesForFormFieldType(IContentProfileFieldTypes[field.type])), ], }; + const fieldOptionsGroupType: IFormGroupCollapsible = {label: gettext('Field options'), openByDefault: true}; + + const fieldOptions = field?.type == null + ? [] + : getAttributesForFormFieldType(IContentProfileFieldTypes[field.type]); + + if (fieldOptions.length > 0) { + const optionsGroup: IFormGroup = { + type: fieldOptionsGroupType, + direction: 'vertical', + form: fieldOptions, + }; + + fieldTypeGroup.form.push(optionsGroup); + } + return [ idField, labelField, From 566315d80a4e14af1e56791f0be807a53f2a337d Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 27 May 2020 12:33:37 +0200 Subject: [PATCH 0028/1070] don't send values for fields which aren't in form config at the time of saving --- .../generic-list-page-item-view-edit.tsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx index 3e275d698a..15b7e12444 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx @@ -14,6 +14,7 @@ import {IFormGroup} from 'superdesk-api'; import {isHttpApiError} from 'core/helpers/network'; import {gettext} from 'core/utils'; import ng from 'core/services/ng'; +import {getFormFieldsFlat} from '../generic-form/get-form-fields-flat'; interface IProps { operation: 'editing' | 'creation'; @@ -100,7 +101,28 @@ export class GenericListPageItemViewEdit extends React.Component, I return JSON.stringify(this.props.item) !== JSON.stringify(this.state.nextItem); } handleSave() { - this.props.onSave(this.state.nextItem).then(() => { + const formConfig = this.props.getFormConfig(this.state.nextItem); + const currentFieldsIds = getFormFieldsFlat(formConfig).map(({field}) => field).concat('_id'); + + /* + Form config is dynamic and can change during editing. + For example users can select a dropdown value + which would cause more fields specific to that option to appear or others to disappear. + + There might be data in the state for fields which no longer exist in form config. + Only fields in form config at the time of saving will be sent. + */ + const nextItemCleaned: Partial = currentFieldsIds.reduce>((acc, field) => { + const value = this.state.nextItem[field]; + + if (value != null) { + acc[field] = value; + } + + return acc; + }, {}); + + this.props.onSave(nextItemCleaned).then(() => { if (this._mounted) { this.setState({ issues: {}, From 699c135657995c0f45c2d4b9ac73124bb0455c0c Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 27 May 2020 13:05:20 +0200 Subject: [PATCH 0029/1070] validate for required fields before saving --- .../generic-list-page-item-view-edit.tsx | 30 +++++++++++++++++-- .../ui/components/generic-form/has-value.tsx | 26 ++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 scripts/core/ui/components/generic-form/has-value.tsx diff --git a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx index 15b7e12444..22ad003435 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page-item-view-edit.tsx @@ -15,6 +15,7 @@ import {isHttpApiError} from 'core/helpers/network'; import {gettext} from 'core/utils'; import ng from 'core/services/ng'; import {getFormFieldsFlat} from '../generic-form/get-form-fields-flat'; +import {hasValue} from '../generic-form/has-value'; interface IProps { operation: 'editing' | 'creation'; @@ -27,9 +28,13 @@ interface IProps { onSave: (nextItem) => Promise; } +interface IIssues { + [field: string]: Array; +} + interface IState { nextItem: IProps['item']; - issues: {[field: string]: Array}; + issues: IIssues; } function getInitialState(props: IProps) { @@ -102,7 +107,8 @@ export class GenericListPageItemViewEdit extends React.Component, I } handleSave() { const formConfig = this.props.getFormConfig(this.state.nextItem); - const currentFieldsIds = getFormFieldsFlat(formConfig).map(({field}) => field).concat('_id'); + const currentFields = getFormFieldsFlat(formConfig); + const currentFieldsIds = currentFields.map(({field}) => field).concat('_id'); /* Form config is dynamic and can change during editing. @@ -122,6 +128,24 @@ export class GenericListPageItemViewEdit extends React.Component, I return acc; }, {}); + const issues = currentFields + .filter( + (fieldConfig) => hasValue(fieldConfig, nextItemCleaned[fieldConfig.field]) !== true, + ) + .reduce((acc, fieldConfig) => { + acc[fieldConfig.field] = [gettext('Field is required')]; + + return acc; + }, {}); + + if (Object.keys(issues).length > 0) { + this.setState({ + issues, + }); + + return; + } + this.props.onSave(nextItemCleaned).then(() => { if (this._mounted) { this.setState({ @@ -133,7 +157,7 @@ export class GenericListPageItemViewEdit extends React.Component, I }) .catch((res) => { if (isHttpApiError(res)) { - let issues = {}; + let issues: IIssues = {}; for (let fieldName in res._issues) { let issuesForField = []; diff --git a/scripts/core/ui/components/generic-form/has-value.tsx b/scripts/core/ui/components/generic-form/has-value.tsx new file mode 100644 index 0000000000..43436ea157 --- /dev/null +++ b/scripts/core/ui/components/generic-form/has-value.tsx @@ -0,0 +1,26 @@ +import {FormFieldType} from './interfaces/form'; +import {IFormField} from 'superdesk-api'; +import {assertNever} from 'core/helpers/typescript-helpers'; + +export function hasValue(fieldConfig: IFormField, value: any): boolean { + const type: FormFieldType = fieldConfig.type; + + switch (type) { + case FormFieldType.textSingleLine: + case FormFieldType.textEditor3: + return typeof value === 'string' && value.trim().length > 0; + case FormFieldType.vocabularySingleValue: + case FormFieldType.contentFilterSingleValue: + case FormFieldType.deskSingleValue: + case FormFieldType.stageSingleValue: + case FormFieldType.macroSingleValue: + case FormFieldType.yesNo: + case FormFieldType.select: + case FormFieldType.selectMultiple: + case FormFieldType.number: + case FormFieldType.checkbox: + return typeof value !== 'undefined'; + default: + assertNever(type); + } +} From d5446dd945e15fc43f8a171ba0621b0baccfd534 Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 27 May 2020 13:59:43 +0200 Subject: [PATCH 0030/1070] add required indicator --- .../content/components/ContentProfileConfigNonText.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 2785bcb28c..cbd46eb313 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -249,6 +249,9 @@ class ItemBase extends React.PureComponent page.startEditing(item._id)}>{gettext('Edit')} + { + item.required === true ? ({gettext('required')}) : null + }
); } From fb1f7b057b74337aeaf907b638677f09d1c11c67 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 28 May 2020 11:03:46 +0200 Subject: [PATCH 0031/1070] only pass relevant methods as a `page` instead of the entire class --- scripts/core/superdesk-api.d.ts | 10 +++++++--- .../components/ListPage/generic-list-page.tsx | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 4c6958549b..557a22065a 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -678,6 +678,10 @@ declare module 'superdesk-api' { // GENERIC FORM + export interface IPropsGenericFormContainer { + page: IGenericListPageComponent; + } + export interface IPropsGenericFormItemComponent { item: T; page: IGenericListPageComponent; @@ -689,7 +693,7 @@ declare module 'superdesk-api' { additionalSortOptions?: Array<{label: string; field: string;}>; defaultFilters?: Partial; ItemComponent: React.ComponentType>; - ItemsContainerComponent?: React.ComponentType; + ItemsContainerComponent?: React.ComponentType>; // Allows initializing a new item with some fields already filled. getNewItemTemplate?(page: IGenericListPageComponent): Partial; @@ -855,7 +859,7 @@ declare module 'superdesk-api' { onClose?(): void; } - export interface IGenericListPageComponent> { + export interface IGenericListPageComponent { openPreview(id: string): void; startEditing(id: string): void; closePreview(): void; @@ -864,7 +868,7 @@ declare module 'superdesk-api' { openNewItemForm(): void; closeNewItemForm(): void; deleteItem(item: T): void; - getActiveFilters(): Partial; + getActiveFilters(): Partial; removeFilter(fieldName: string): void; } diff --git a/scripts/core/ui/components/ListPage/generic-list-page.tsx b/scripts/core/ui/components/ListPage/generic-list-page.tsx index 3562233c74..c156d7750d 100644 --- a/scripts/core/ui/components/ListPage/generic-list-page.tsx +++ b/scripts/core/ui/components/ListPage/generic-list-page.tsx @@ -110,6 +110,7 @@ export class GenericListPageComponent }; this.openPreview = this.openPreview.bind(this); + this.startEditing = this.startEditing.bind(this); this.closePreview = this.closePreview.bind(this); this.setFiltersVisibility = this.setFiltersVisibility.bind(this); this.handleFilterFieldChange = this.handleFilterFieldChange.bind(this); @@ -118,6 +119,7 @@ export class GenericListPageComponent this.deleteItem = this.deleteItem.bind(this); this.getActiveFilters = this.getActiveFilters.bind(this); this.removeFilter = this.removeFilter.bind(this); + this.refetchDataUsingCurrentFilters = this.refetchDataUsingCurrentFilters.bind(this); this.filter = this.filter.bind(this); @@ -366,6 +368,19 @@ export class GenericListPageComponent ...(this.props.additionalSortOptions ?? []), ]; + var page: IGenericListPageComponent = { + openPreview: this.openPreview, + startEditing: this.startEditing, + closePreview: this.closePreview, + setFiltersVisibility: this.setFiltersVisibility, + handleFilterFieldChange: this.handleFilterFieldChange, + openNewItemForm: this.openNewItemForm, + closeNewItemForm: this.closeNewItemForm, + deleteItem: this.deleteItem, + getActiveFilters: this.getActiveFilters, + removeFilter: this.removeFilter, + }; + const getContents = () => { if (items._items.length === 0) { if (Object.keys(activeFilters).length > 0) { @@ -387,10 +402,10 @@ export class GenericListPageComponent } } else { return ( - + { items._items.map( - (item, i) => , + (item, i) => , ) } From 1eec728c15d5f7c7c1666c9e3c357462fa661928 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 28 May 2020 11:31:47 +0200 Subject: [PATCH 0032/1070] add a comment --- scripts/core/superdesk-api.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 557a22065a..1539e6884d 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -577,6 +577,8 @@ declare module 'superdesk-api' { type: keyof typeof IContentProfileFieldTypes; section: keyof typeof IContentProfileSection; required: boolean; + + // it will also contain more fields specific to field type } export enum IContentProfileTypeNonText { From 5a5958394fcb9ddb771912d575be0a861df70d47 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 28 May 2020 11:48:44 +0200 Subject: [PATCH 0033/1070] fix types --- .../content/components/ContentProfileConfigNonText.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index cbd46eb313..94ea823487 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -11,6 +11,7 @@ import { IItemWithId, IPropsGenericFormItemComponent, IFormGroupCollapsible, + IPropsGenericFormContainer, } from 'superdesk-api'; import {gettext} from 'core/utils'; @@ -298,7 +299,7 @@ function stripSystemId(item: IContentProfileFieldWithSystemId): IContentProfileF export class ContentProfileConfigNonText extends React.Component { private generateKey: () => string; private lastKey: number; - private ItemsContainerComponent: React.ComponentType; + private ItemsContainerComponent: React.ComponentType>; constructor(props: IProps) { super(props); @@ -325,7 +326,8 @@ export class ContentProfileConfigNonText extends React.Component this.updateCurrentFields((_fields) => arrayMove(_fields, oldIndex, newIndex)); }; - class ItemsContainerComponent extends React.PureComponent { + class ItemsContainerComponent + extends React.PureComponent> { render() { return ( From e0af4d320d3a35a4610fa780917b4fe30b76e19f Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 29 May 2020 08:37:38 +0200 Subject: [PATCH 0034/1070] rename to plain text --- .../InternalDestinations.tsx | 2 +- .../components/ContentProfileConfigNonText.tsx | 16 ++++++++-------- scripts/core/superdesk-api.d.ts | 4 ++-- .../ui/components/generic-form/form-field.tsx | 6 +++--- .../generic-form/generate-filter-for-server.tsx | 2 +- .../get-form-group-for-filtering.tsx | 6 +++--- .../generic-form/get-initial-values.tsx | 2 +- .../ui/components/generic-form/has-value.tsx | 2 +- .../{text-single-line.tsx => plain-text.tsx} | 2 +- .../components/generic-form/interfaces/form.ts | 2 +- .../generic-form/tests/generic-form.spec.tsx | 2 +- .../annotationsLibrary/src/GetFields.ts | 2 +- 12 files changed, 24 insertions(+), 24 deletions(-) rename scripts/core/ui/components/generic-form/input-types/{text-single-line.tsx => plain-text.tsx} (95%) diff --git a/scripts/apps/internal-destinations/InternalDestinations.tsx b/scripts/apps/internal-destinations/InternalDestinations.tsx index 5e29cee86e..6f85006ffa 100644 --- a/scripts/apps/internal-destinations/InternalDestinations.tsx +++ b/scripts/apps/internal-destinations/InternalDestinations.tsx @@ -12,7 +12,7 @@ import {gettext} from 'core/utils'; function getNameField(): IFormField { return { label: gettext('Destination name'), - type: FormFieldType.textSingleLine, + type: FormFieldType.plainText, field: 'name', required: true, }; diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 94ea823487..2fea01b06a 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -36,7 +36,7 @@ interface IState { // subset of FormFieldType enum IContentProfileFieldTypes { - textSingleLine = 'textSingleLine', + plainText = 'plainText', number = 'number', } @@ -68,14 +68,14 @@ function getCommonContentProfileConfig( ): Array { const idField: IFormField = { label: gettext('ID'), - type: FormFieldType.textSingleLine, + type: FormFieldType.plainText, field: 'id', required: true, }; const labelField: IFormField = { label: gettext('Label'), - type: FormFieldType.textSingleLine, + type: FormFieldType.plainText, field: 'label', required: true, }; @@ -111,9 +111,9 @@ function getCommonContentProfileConfig( component_parameters: { items: getAllContentProfileFieldTypes().map((type) => { switch (type) { - case IContentProfileFieldTypes.textSingleLine: + case IContentProfileFieldTypes.plainText: return { - id: IContentProfileFieldTypes.textSingleLine, + id: IContentProfileFieldTypes.plainText, label: gettext('Plain text (single line)'), }; case IContentProfileFieldTypes.number: @@ -212,7 +212,7 @@ function getFormConfig( } } -function getAttributesForTextSingleLine(): Array { +function getAttributesForPlainText(): Array { const minLengthField: IFormField = { label: gettext('Minimum length'), type: FormFieldType.number, @@ -232,8 +232,8 @@ function getAttributesForTextSingleLine(): Array { function getAttributesForFormFieldType(type: IContentProfileFieldTypes): Array { switch (type) { - case IContentProfileFieldTypes.textSingleLine: - return getAttributesForTextSingleLine(); + case IContentProfileFieldTypes.plainText: + return getAttributesForPlainText(); case IContentProfileFieldTypes.number: return []; default: diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 1539e6884d..dac1b6180b 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -562,7 +562,7 @@ declare module 'superdesk-api' { // subset of FormFieldType export enum IContentProfileFieldTypes { - textSingleLine = 'textSingleLine', + plainText = 'plainText', number = 'number', } @@ -708,7 +708,7 @@ declare module 'superdesk-api' { } export enum FormFieldType { - textSingleLine = 'textSingleLine', + plainText = 'plainText', textEditor3 = 'textEditor3', number = 'number', vocabularySingleValue = 'vocabularySingleValue', diff --git a/scripts/core/ui/components/generic-form/form-field.tsx b/scripts/core/ui/components/generic-form/form-field.tsx index f5035ed1af..68eb9d25ff 100644 --- a/scripts/core/ui/components/generic-form/form-field.tsx +++ b/scripts/core/ui/components/generic-form/form-field.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {TextSingleLine} from './input-types/text-single-line'; +import {PlainText} from './input-types/plain-text'; import {assertNever} from 'core/helpers/typescript-helpers'; import {isIFormGroup, isIFormField, FormFieldType} from './interfaces/form'; import {VocabularySingleValue} from './input-types/vocabulary_single_value'; @@ -19,8 +19,8 @@ import {NumberComponent} from './input-types/number'; export function getFormFieldComponent(type: FormFieldType): React.ComponentType> { switch (type) { - case FormFieldType.textSingleLine: - return TextSingleLine; + case FormFieldType.plainText: + return PlainText; case FormFieldType.textEditor3: return TextEditor3; case FormFieldType.number: diff --git a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx index 5f4f5500c6..88191d6939 100644 --- a/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx +++ b/scripts/core/ui/components/generic-form/generate-filter-for-server.tsx @@ -3,7 +3,7 @@ import {FormFieldType} from './interfaces/form'; export function generateFilterForServer(type: FormFieldType, value: any): any { switch (type) { - case FormFieldType.textSingleLine: + case FormFieldType.plainText: return { $regex: value, $options: 'i', diff --git a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx index 85ef8cf2e0..8d712c2b13 100644 --- a/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx +++ b/scripts/core/ui/components/generic-form/get-form-group-for-filtering.tsx @@ -5,11 +5,11 @@ import {IFormField, IFormGroup} from 'superdesk-api'; // different components must be used for filtering than for entering/updating items function getFieldTypeForFiltering(type: FormFieldType): FormFieldType { switch (type) { - case FormFieldType.textSingleLine: - return FormFieldType.textSingleLine; + case FormFieldType.plainText: + return FormFieldType.plainText; case FormFieldType.textEditor3: // even though textEditor3 outputs HTML, plaintext has to be used for filtering - return FormFieldType.textSingleLine; + return FormFieldType.plainText; case FormFieldType.number: return FormFieldType.number; case FormFieldType.vocabularySingleValue: diff --git a/scripts/core/ui/components/generic-form/get-initial-values.tsx b/scripts/core/ui/components/generic-form/get-initial-values.tsx index 2c53d7b77a..8d695cdaec 100644 --- a/scripts/core/ui/components/generic-form/get-initial-values.tsx +++ b/scripts/core/ui/components/generic-form/get-initial-values.tsx @@ -8,7 +8,7 @@ function getInitialValueForFieldType(fieldConfig: IFormField): {readonly [field: const type: FormFieldType = fieldConfig.type; switch (type) { - case FormFieldType.textSingleLine: + case FormFieldType.plainText: case FormFieldType.textEditor3: return {[field]: ''}; case FormFieldType.vocabularySingleValue: diff --git a/scripts/core/ui/components/generic-form/has-value.tsx b/scripts/core/ui/components/generic-form/has-value.tsx index 43436ea157..126da9ad2b 100644 --- a/scripts/core/ui/components/generic-form/has-value.tsx +++ b/scripts/core/ui/components/generic-form/has-value.tsx @@ -6,7 +6,7 @@ export function hasValue(fieldConfig: IFormField, value: any): boolean { const type: FormFieldType = fieldConfig.type; switch (type) { - case FormFieldType.textSingleLine: + case FormFieldType.plainText: case FormFieldType.textEditor3: return typeof value === 'string' && value.trim().length > 0; case FormFieldType.vocabularySingleValue: diff --git a/scripts/core/ui/components/generic-form/input-types/text-single-line.tsx b/scripts/core/ui/components/generic-form/input-types/plain-text.tsx similarity index 95% rename from scripts/core/ui/components/generic-form/input-types/text-single-line.tsx rename to scripts/core/ui/components/generic-form/input-types/plain-text.tsx index 53d7542e8b..68e08e920d 100644 --- a/scripts/core/ui/components/generic-form/input-types/text-single-line.tsx +++ b/scripts/core/ui/components/generic-form/input-types/plain-text.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import {IInputType} from '../interfaces/input-types'; -export class TextSingleLine extends React.Component> { +export class PlainText extends React.Component> { render() { if (this.props.previewOutput) { return
{this.props.value}
; diff --git a/scripts/core/ui/components/generic-form/interfaces/form.ts b/scripts/core/ui/components/generic-form/interfaces/form.ts index 9a4435f024..643c2bbd4e 100644 --- a/scripts/core/ui/components/generic-form/interfaces/form.ts +++ b/scripts/core/ui/components/generic-form/interfaces/form.ts @@ -1,7 +1,7 @@ import {IFormGroup, IFormField, IFormGroupCollapsible} from 'superdesk-api'; export enum FormFieldType { - textSingleLine = 'textSingleLine', + plainText = 'plainText', textEditor3 = 'textEditor3', number = 'number', vocabularySingleValue = 'vocabularySingleValue', diff --git a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx index e6ae601d29..cdff4023f1 100644 --- a/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx +++ b/scripts/core/ui/components/generic-form/tests/generic-form.spec.tsx @@ -13,7 +13,7 @@ function getAllInputTypes(): Array { function getTestFieldConfig(type: FormFieldType): IFormField { switch (type) { - case FormFieldType.textSingleLine: + case FormFieldType.plainText: case FormFieldType.textEditor3: case FormFieldType.number: case FormFieldType.checkbox: diff --git a/scripts/extensions/annotationsLibrary/src/GetFields.ts b/scripts/extensions/annotationsLibrary/src/GetFields.ts index 8b0df82ddb..9d9db2f1b8 100644 --- a/scripts/extensions/annotationsLibrary/src/GetFields.ts +++ b/scripts/extensions/annotationsLibrary/src/GetFields.ts @@ -6,7 +6,7 @@ export function getFields(superdesk: ISuperdesk) { const nameField: IFormField = { label: gettext('Name'), - type: FormFieldType.textSingleLine, + type: FormFieldType.plainText, field: 'name', required: true, }; From 36788d7d4abba668b496b5933f26f962701d1ec4 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 29 May 2020 09:22:32 +0200 Subject: [PATCH 0035/1070] add multi-line option for plain text field --- .../ContentProfileConfigNonText.tsx | 11 +++++-- .../generic-form/input-types/plain-text.tsx | 32 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx index 2fea01b06a..a2d476b8f4 100644 --- a/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileConfigNonText.tsx @@ -114,7 +114,7 @@ function getCommonContentProfileConfig( case IContentProfileFieldTypes.plainText: return { id: IContentProfileFieldTypes.plainText, - label: gettext('Plain text (single line)'), + label: gettext('Plain text'), }; case IContentProfileFieldTypes.number: return { @@ -227,7 +227,14 @@ function getAttributesForPlainText(): Array { required: false, }; - return [minLengthField, maxLengthField]; + const multilineField: IFormField = { + label: gettext('Multi-line'), + type: FormFieldType.checkbox, + field: 'multiline', + required: false, + }; + + return [minLengthField, maxLengthField, multilineField]; } function getAttributesForFormFieldType(type: IContentProfileFieldTypes): Array { diff --git a/scripts/core/ui/components/generic-form/input-types/plain-text.tsx b/scripts/core/ui/components/generic-form/input-types/plain-text.tsx index 68e08e920d..f721661041 100644 --- a/scripts/core/ui/components/generic-form/input-types/plain-text.tsx +++ b/scripts/core/ui/components/generic-form/input-types/plain-text.tsx @@ -11,6 +11,29 @@ export class PlainText extends React.Component> { // default value is required so React doesn't complain that uncontrolled component is changed to controlled. const valueWithDefaultValue = this.props.value || ''; + const fieldElement = this.props.formField?.component_parameters?.multiline === true + ? ( +
-
- -
-
- +
+
+ +
+
+ +
-
+
Date: Tue, 30 Mar 2021 13:46:55 +0200 Subject: [PATCH 0073/1070] fix(inputs): Fixed boxed input fields --- .../components/ContentProfileFieldsConfig.tsx | 1 - .../get-content-profiles-form-config.tsx | 8 ++++++- .../content/views/profile-settings.html | 22 ++++++++++--------- .../generic-form/input-types/checkbox.tsx | 6 ++--- .../generic-form/input-types/number.tsx | 1 + .../input-types/select_multiple_values.tsx | 1 + .../select_single_value_static.tsx | 2 ++ 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx b/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx index 6913422e82..e395d1732f 100644 --- a/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx +++ b/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx @@ -125,7 +125,6 @@ class ItemBase extends React.PureComponent<{wrapper: IPropsItem}> { }))} >
+ ); + } +} diff --git a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts index 3c87bfaac3..f869d2041f 100644 --- a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts +++ b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts @@ -1,13 +1,14 @@ import _ from 'lodash'; import * as helpers from 'apps/authoring/authoring/helpers'; import {gettext} from 'core/utils'; -import {appConfig} from 'appConfig'; +import {appConfig, authoringReactViewEnabled} from 'appConfig'; import {canPrintPreview} from 'apps/search/helpers'; AuthoringEmbeddedDirective.$inject = ['api', 'notify', '$filter']; export function AuthoringEmbeddedDirective(api, notify, $filter) { return { - templateUrl: 'scripts/apps/authoring/views/authoring.html', + template: authoringReactViewEnabled ? '
' : undefined, + templateUrl: authoringReactViewEnabled ? undefined : 'scripts/apps/authoring/views/authoring.html', scope: { item: '=', action: '=', diff --git a/scripts/apps/authoring/authoring/index.ts b/scripts/apps/authoring/authoring/index.ts index ca6d2d8921..43df80fa30 100644 --- a/scripts/apps/authoring/authoring/index.ts +++ b/scripts/apps/authoring/authoring/index.ts @@ -32,6 +32,7 @@ import {AuthoringTopbar2React} from './authoring-topbar2-react'; import {appConfig} from 'appConfig'; import {FullPreview} from '../preview/fullPreview'; import {sdApi} from 'api'; +import {AuthoringReact} from 'apps/authoring-react/authoring-react'; export interface IOnChangeParams { item: IArticle; @@ -101,6 +102,7 @@ angular.module('superdesk.apps.authoring', [ .directive('sdDashboardCard', directive.DashboardCard) .directive('sdSendItem', directive.SendItem) .component('sdCharacterCount', reactToAngular1(CharacterCount, ['item', 'html', 'limit'], [], 'display: inline')) + .component('sdAuthoringReact', reactToAngular1(AuthoringReact, ['itemId'], [])) .component('sdCharacterCountConfigButton', reactToAngular1( CharacterCountConfigButton, ['field'], [], 'display: inline', )) diff --git a/styles/sass/app.scss b/styles/sass/app.scss index ec9b6f042a..8ac5be71ab 100644 --- a/styles/sass/app.scss +++ b/styles/sass/app.scss @@ -65,5 +65,6 @@ // Application components @import 'saved-searches'; @import '../../scripts/apps/dashboard/widget-heading.scss'; +@import '../../scripts/apps/authoring-react/authoring-react.scss'; @import '../../scripts/apps/authoring/authoring/services/quick-publish-modal.scss' From 2ddff0f8acdc86d17b3f972bdc5f6bc9e578ac80 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Thu, 21 Oct 2021 16:54:26 +0200 Subject: [PATCH 0081/1070] improve authoring service (no logic changed) --- .../authoring/services/AuthoringService.ts | 72 ++++++++++++++----- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/scripts/apps/authoring/authoring/services/AuthoringService.ts b/scripts/apps/authoring/authoring/services/AuthoringService.ts index 39a2622fa0..3f3dccbf1a 100644 --- a/scripts/apps/authoring/authoring/services/AuthoringService.ts +++ b/scripts/apps/authoring/authoring/services/AuthoringService.ts @@ -125,12 +125,47 @@ interface Iparams { * * @description Authoring Service is responsible for management of the actions on a story */ -AuthoringService.$inject = ['$q', '$location', 'api', 'lock', 'autosave', 'confirm', 'privileges', 'desks', - 'superdeskFlags', 'notify', 'session', '$injector', 'moment', 'familyService', 'modal', 'archiveService']; -export function AuthoringService($q, $location, api, lock, autosave, confirm, privileges, desks, superdeskFlags, - notify, session, $injector, moment, familyService, modal, archiveService) { +AuthoringService.$inject = [ + '$q', + '$location', + 'api', + 'lock', + 'autosave', + 'confirm', + 'privileges', + 'desks', + 'superdeskFlags', + 'notify', + 'session', + '$injector', + 'moment', + 'familyService', + 'modal', + 'archiveService', +]; + +export function AuthoringService( + $q, + $location, + api, + lock, + autosave, + confirm, + privileges, + desks, + superdeskFlags, + notify, + session, + $injector, + moment, + familyService, + modal, + archiveService, +) { var self = this; + const isEditable = (item: Readonly>) => lock.isLockedInCurrentSession(item); + // TODO: have to trap desk update event for refereshing users desks. this.userDesks = []; const publishFromPersonal = appConfig?.features?.publishFromPersonal; @@ -283,18 +318,24 @@ export function AuthoringService($q, $location, api, lock, autosave, confirm, pr }); /** - * Close an item - * - * and save it if dirty, unlock if editable, and remove from work queue at all times + * * close an item and save it if dirty + * * unlock if editable + * * remove from work queue at all times + * * (is called after publishing) * * @param {Object} diff * @param {Object} orig * @param {boolean} isDirty $scope dirty status. */ - this.close = function closeAuthoring(diff, orig, isDirty, closeItem) { + this.close = function closeAuthoring( + diff: Readonly>, + orig: Readonly, + isDirty: boolean, + closeItem: boolean, + ): Promise { var promise = $q.when(); - if (this.isEditable(diff)) { + if (isEditable(diff)) { if (isDirty) { if (!_.includes(['published', 'corrected'], orig.state)) { promise = confirm.confirm() @@ -333,7 +374,7 @@ export function AuthoringService($q, $location, api, lock, autosave, confirm, pr this.publishConfirmation = function publishAuthoring(orig, diff, isDirty, action) { var promise = $q.when(); - if (this.isEditable(diff) && isDirty) { + if (isEditable(diff) && isDirty) { promise = confirm.confirmPublish(action) .then(angular.bind(this, function save() { return true; @@ -522,7 +563,7 @@ export function AuthoringService($q, $location, api, lock, autosave, confirm, pr var promise = $q.when(); if (isDirty) { - if (this.isEditable(diff)) { + if (isEditable(diff)) { promise = confirm.confirmSaveWork(message) .then(angular.bind(this, function save() { return this.saveWork(orig, diff); @@ -625,14 +666,7 @@ export function AuthoringService($q, $location, api, lock, autosave, confirm, pr }); }; - /** - * Test if an item is editable - * - * @param {Object} item - */ - this.isEditable = function isEditable(item) { - return lock.isLockedInCurrentSession(item); - }; + this.isEditable = isEditable; /** * Unlock an item - callback for item:unlock event From 8db12055fb92ee64d38dba75799c28c77343727c Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Fri, 22 Oct 2021 13:31:25 +0200 Subject: [PATCH 0082/1070] add a wrapper component to isolate interaction with angular; add initial data layer --- scripts/appConfig.ts | 2 +- .../authoring-integration-wrapper.tsx | 26 +++++ .../apps/authoring-react/authoring-react.tsx | 104 ++++++++++++++---- scripts/apps/authoring-react/data-layer.ts | 64 +++++++++++ .../directives/AuthoringEmbeddedDirective.ts | 2 +- scripts/apps/authoring/authoring/index.ts | 4 +- 6 files changed, 179 insertions(+), 23 deletions(-) create mode 100644 scripts/apps/authoring-react/authoring-integration-wrapper.tsx create mode 100644 scripts/apps/authoring-react/data-layer.ts diff --git a/scripts/appConfig.ts b/scripts/appConfig.ts index 32f57965bb..53cd42afc7 100644 --- a/scripts/appConfig.ts +++ b/scripts/appConfig.ts @@ -37,6 +37,6 @@ export const debugInfo = { translationsLoaded: false, }; -export const authoringReactViewEnabled = true; +export const authoringReactViewEnabled = false; export const extensions: IExtensions = {}; diff --git a/scripts/apps/authoring-react/authoring-integration-wrapper.tsx b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx new file mode 100644 index 0000000000..266a1d42e9 --- /dev/null +++ b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import {IArticle} from 'superdesk-api'; +import ng from 'core/services/ng'; +import {AuthoringReact} from './authoring-react'; + +interface IProps { + itemId: IArticle['_id']; +} + +/** + * The purpose of the wrapper is to handle integration with the angular part of the application. + * The main component will not know about angular. + */ +export class AuthoringIntegrationWrapper extends React.PureComponent { + render() { + return ( + { + ng.get('authoringWorkspace').close(); + ng.get('$rootScope').$applyAsync(); + }} + /> + ); + } +} diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index d34f3f6b69..2411599a4c 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -1,17 +1,39 @@ import React from 'react'; import {IArticle} from 'superdesk-api'; -import ng from 'core/services/ng'; import {Button} from 'superdesk-ui-framework'; import {gettext} from 'core/utils'; - -// TODO: how to get item by ID +import {dataApi} from 'core/helpers/CrudManager'; +import {getContentProfile, IContentProfileV2} from './data-layer'; interface IProps { itemId: IArticle['_id']; + onClose(): void; +} + +interface IStateLoaded { + loading: false; + item: IArticle; + profile: IContentProfileV2; +} + +type IState = {loading: true} | IStateLoaded; + +function waitForCssAnimation(): Promise { + return new Promise((resolve) => { + setTimeout( + () => { + resolve(); + }, + + // transition time taken from styles/sass/layouts.scss #authoring-container + 500, + ); + }); } -interface IState { - cssOpeningAnimationCompleted: boolean; +function fetchArticle(id: IArticle['_id']): Promise { + // TODO: take published items into account + return dataApi.findOne('archive', id); } export class AuthoringReact extends React.PureComponent { @@ -19,34 +41,78 @@ export class AuthoringReact extends React.PureComponent { super(props); this.state = { - cssOpeningAnimationCompleted: false, + loading: true, }; } componentDidMount() { - setTimeout(() => { - this.setState({cssOpeningAnimationCompleted: true}); - }, 500); + Promise.all( + [ + fetchArticle(this.props.itemId).then((item) => { + return getContentProfile(item).then((profile) => { + return { + item, + profile, + }; + }); + }), + waitForCssAnimation(), + ], + ).then((res) => { + const [{item, profile}] = res; + + this.setState({loading: false, item, profile}); + }); } render() { - if (this.state.cssOpeningAnimationCompleted !== true) { + const state = this.state; + + if (state.loading === true) { return null; } return (
-
hello world
+
+

{gettext('Toolbar')}

+ +
+ +
+

{gettext('Header')}

+ + { + state.profile.header.map((field) => ( +
+ +
{state.item[field.name]}
+
+ )).toArray() + } +
-
+
+

{gettext('Content')}

-
); diff --git a/scripts/apps/authoring-react/data-layer.ts b/scripts/apps/authoring-react/data-layer.ts new file mode 100644 index 0000000000..47a2fa9e5d --- /dev/null +++ b/scripts/apps/authoring-react/data-layer.ts @@ -0,0 +1,64 @@ +import {OrderedMap} from 'immutable'; +import {IArticle} from 'superdesk-api'; +import ng from 'core/services/ng'; + +export interface IAuthoringFieldV2 { + name: string; + type: 'plain-text' | 'html' | 'dropdown'; +} + +export type IFieldsV2 = OrderedMap; + +export interface IContentProfileV2 { + name: string; + header: IFieldsV2; + content: IFieldsV2; +} + +export function getContentProfile(item: IArticle): Promise { + interface IFakeScope { + schema: any, + editor: any; + fields: any; + } + + let fakeScope: Partial = {}; + + /** + * !!! The use of `setupAuthoring` outside of angular is experimental. + * I'm only using for getting some test data on the screen. + */ + return ng.get('content').setupAuthoring(item.profile, fakeScope, item).then(() => { + const {editor} = fakeScope; + const editorOrdered = + Object.keys(editor) + .map((key) => ({...editor[key], name: key})) + .sort((a, b) => a.order - b.order); + + let headerFields: IFieldsV2 = OrderedMap(); + let contentFields: IFieldsV2 = OrderedMap(); + + for (const editorItem of editorOrdered) { + const field: IAuthoringFieldV2 = { + name: editorItem.name, + type: 'plain-text', + }; + + if (editorItem.section === 'header') { + headerFields = headerFields.set(field.name, field); + } else if (editorItem.section === 'content') { + contentFields = contentFields.set(field.name, field); + } else { + throw new Error('invalid section'); + } + } + + const profile: IContentProfileV2 = { + name: 'test content profile', + header: headerFields, + content: contentFields, + }; + + return profile; + }); +} \ No newline at end of file diff --git a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts index f869d2041f..45a9121a0c 100644 --- a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts +++ b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts @@ -7,7 +7,7 @@ import {canPrintPreview} from 'apps/search/helpers'; AuthoringEmbeddedDirective.$inject = ['api', 'notify', '$filter']; export function AuthoringEmbeddedDirective(api, notify, $filter) { return { - template: authoringReactViewEnabled ? '
' : undefined, + template: authoringReactViewEnabled ? '
' : undefined, templateUrl: authoringReactViewEnabled ? undefined : 'scripts/apps/authoring/views/authoring.html', scope: { item: '=', diff --git a/scripts/apps/authoring/authoring/index.ts b/scripts/apps/authoring/authoring/index.ts index 43df80fa30..f40201d1fb 100644 --- a/scripts/apps/authoring/authoring/index.ts +++ b/scripts/apps/authoring/authoring/index.ts @@ -32,7 +32,7 @@ import {AuthoringTopbar2React} from './authoring-topbar2-react'; import {appConfig} from 'appConfig'; import {FullPreview} from '../preview/fullPreview'; import {sdApi} from 'api'; -import {AuthoringReact} from 'apps/authoring-react/authoring-react'; +import {AuthoringIntegrationWrapper} from 'apps/authoring-react/authoring-integration-wrapper'; export interface IOnChangeParams { item: IArticle; @@ -102,7 +102,7 @@ angular.module('superdesk.apps.authoring', [ .directive('sdDashboardCard', directive.DashboardCard) .directive('sdSendItem', directive.SendItem) .component('sdCharacterCount', reactToAngular1(CharacterCount, ['item', 'html', 'limit'], [], 'display: inline')) - .component('sdAuthoringReact', reactToAngular1(AuthoringReact, ['itemId'], [])) + .component('sdAuthoringIntegrationWrapper', reactToAngular1(AuthoringIntegrationWrapper, ['itemId'], [])) .component('sdCharacterCountConfigButton', reactToAngular1( CharacterCountConfigButton, ['field'], [], 'display: inline', )) From e906070ef410608a575fba0a04f30ddecbef60b8 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Fri, 22 Oct 2021 19:45:18 +0200 Subject: [PATCH 0083/1070] fix lint --- scripts/apps/authoring-react/data-layer.ts | 4 ++-- .../authoring/directives/AuthoringEmbeddedDirective.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/apps/authoring-react/data-layer.ts b/scripts/apps/authoring-react/data-layer.ts index 47a2fa9e5d..259e9a5589 100644 --- a/scripts/apps/authoring-react/data-layer.ts +++ b/scripts/apps/authoring-react/data-layer.ts @@ -17,7 +17,7 @@ export interface IContentProfileV2 { export function getContentProfile(item: IArticle): Promise { interface IFakeScope { - schema: any, + schema: any; editor: any; fields: any; } @@ -61,4 +61,4 @@ export function getContentProfile(item: IArticle): Promise { return profile; }); -} \ No newline at end of file +} diff --git a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts index 45a9121a0c..e272bdd950 100644 --- a/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts +++ b/scripts/apps/authoring/authoring/directives/AuthoringEmbeddedDirective.ts @@ -7,7 +7,9 @@ import {canPrintPreview} from 'apps/search/helpers'; AuthoringEmbeddedDirective.$inject = ['api', 'notify', '$filter']; export function AuthoringEmbeddedDirective(api, notify, $filter) { return { - template: authoringReactViewEnabled ? '
' : undefined, + template: authoringReactViewEnabled + ? '
' + : undefined, templateUrl: authoringReactViewEnabled ? undefined : 'scripts/apps/authoring/views/authoring.html', scope: { item: '=', From f5defed104caf9d692a8c8ba061a21094c405a1d Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Fri, 22 Oct 2021 19:44:08 +0200 Subject: [PATCH 0084/1070] remove editor2 --- package-lock.json | 2365 +---------------- package.json | 4 - .../directives/ArticleEditDirective.ts | 9 - .../authoring/services/HistoryFactory.ts | 9 +- scripts/apps/authoring/macros/macros.spec.ts | 2 - scripts/apps/authoring/styles/authoring.scss | 53 - .../apps/authoring/tests/authoring.spec.ts | 7 +- .../apps/authoring/views/article-edit.html | 134 +- scripts/apps/index.ts | 1 - scripts/apps/ingest/services/SendService.ts | 1 - scripts/apps/workspace/content/constants.ts | 3 +- .../content/views/schema-editor.html | 10 - scripts/core/editor2/add-content.ts | 146 - scripts/core/editor2/add-embed.ctrl.ts | 133 - scripts/core/editor2/customAnchor.ts | 48 - scripts/core/editor2/editor.ctrl.ts | 401 --- scripts/core/editor2/editor.spec.ts | 165 -- scripts/core/editor2/editor.ts | 1771 ------------ .../core/editor2/embedCodeHandlers.spec.ts | 107 - scripts/core/editor2/embedCodeHandlers.ts | 25 - scripts/core/editor2/index.ts | 13 - scripts/core/editor2/styles/editor.scss | 245 -- scripts/core/editor2/views/add-content.html | 25 - scripts/core/editor2/views/add-embed.html | 21 - scripts/core/editor2/views/block-embed.html | 37 - scripts/core/editor2/views/block-text.html | 29 - scripts/core/editor2/views/editor.html | 45 - scripts/core/editor3/index.ts | 12 +- scripts/core/editor3/service.ts | 3 +- scripts/core/index.ts | 1 - scripts/core/spellcheck/spellcheck.spec.ts | 1 - scripts/core/superdesk-api.d.ts | 1 - scripts/index.ts | 2 - scripts/vendor.ts | 2 - styles/sass/app.scss | 1 - styles/sass/medium-editor.scss | 274 -- webpack.config.js | 7 - 37 files changed, 59 insertions(+), 6054 deletions(-) delete mode 100644 scripts/core/editor2/add-content.ts delete mode 100644 scripts/core/editor2/add-embed.ctrl.ts delete mode 100644 scripts/core/editor2/customAnchor.ts delete mode 100644 scripts/core/editor2/editor.ctrl.ts delete mode 100644 scripts/core/editor2/editor.spec.ts delete mode 100644 scripts/core/editor2/editor.ts delete mode 100644 scripts/core/editor2/embedCodeHandlers.spec.ts delete mode 100644 scripts/core/editor2/embedCodeHandlers.ts delete mode 100644 scripts/core/editor2/index.ts delete mode 100644 scripts/core/editor2/styles/editor.scss delete mode 100644 scripts/core/editor2/views/add-content.html delete mode 100644 scripts/core/editor2/views/add-embed.html delete mode 100644 scripts/core/editor2/views/block-embed.html delete mode 100644 scripts/core/editor2/views/block-text.html delete mode 100644 scripts/core/editor2/views/editor.html delete mode 100644 styles/sass/medium-editor.scss diff --git a/package-lock.json b/package-lock.json index b11a0a3984..7b24f524d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -540,14 +540,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" }, - "active-x-obfuscator": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/active-x-obfuscator/-/active-x-obfuscator-0.0.1.tgz", - "integrity": "sha1-CJuJs3FF/x2ex0r2UwvlUmyuHxo=", - "requires": { - "zeparser": "0.0.5" - } - }, "addressparser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", @@ -696,108 +688,6 @@ "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.32.tgz", "integrity": "sha1-0H/qdSgfJbqUpb5bRt8qAsXjWDY=" }, - "angular-embed": { - "version": "github:superdesk/angular-embed#d75968e9eedc2b255f68d0c298a6c91981bc9444", - "from": "github:superdesk/angular-embed#d75968e" - }, - "angular-embedly": { - "version": "github:Urigo/angular-embedly#e48ff27af8bd702f1cefea6119c863df9b0e6042", - "from": "github:Urigo/angular-embedly#0.0.8", - "requires": { - "gulp": "~3.8.2", - "gulp-concat": "~2.2.0", - "gulp-uglify": "~0.3.1", - "karma": "~0.12.16", - "karma-chrome-launcher": "~0.1.4", - "karma-jasmine": "~0.1.5" - }, - "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "karma": { - "version": "0.12.37", - "resolved": "https://registry.npmjs.org/karma/-/karma-0.12.37.tgz", - "integrity": "sha1-Gp9/3szWneLoNeBO26wuzT+mReQ=", - "requires": { - "chokidar": "^1.0.1", - "colors": "^1.1.0", - "connect": "^2.29.2", - "di": "^0.0.1", - "glob": "^5.0.6", - "graceful-fs": "^3.0.6", - "http-proxy": "^0.10", - "lodash": "^3.8.0", - "log4js": "^0.6.25", - "mime": "^1.3.4", - "minimatch": "^2.0.7", - "optimist": "^0.6.1", - "q": "^1.4.1", - "rimraf": "^2.3.3", - "socket.io": "0.9.16", - "source-map": "^0.4.2", - "useragent": "^2.1.6" - } - }, - "karma-chrome-launcher": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-0.1.12.tgz", - "integrity": "sha1-CsDiLlc2UPZUExL9ynlcOCTM+WI=", - "requires": { - "which": "^1.0.9" - } - }, - "karma-jasmine": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-0.1.6.tgz", - "integrity": "sha1-MFRQV2mOvcvGMTLUe+Elt1svvFU=" - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "angular-gettext": { "version": "github:tomaskikutis/angular-gettext#5615f0058ecf7f4aeeb7ec1a24e2cff4426fffa6", "from": "github:tomaskikutis/angular-gettext#master" @@ -859,135 +749,16 @@ "type-fest": "^0.21.3" } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" }, - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=" - }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } - } - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" - }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -1054,11 +825,6 @@ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -1081,11 +847,6 @@ "is-string": "^1.0.7" } }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" - }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1274,11 +1035,6 @@ "postcss-value-parser": "^3.2.3" } }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1469,26 +1225,6 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, - "base64-url": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", - "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=" - }, - "base64id": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz", - "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=" - }, - "basic-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", - "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" - }, - "basic-auth-connect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", - "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" - }, "batch": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz", @@ -1502,11 +1238,6 @@ "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" - }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -1637,38 +1368,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, - "body-parser": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", - "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=", - "requires": { - "bytes": "2.1.0", - "content-type": "~1.0.1", - "debug": "~2.2.0", - "depd": "~1.0.1", - "http-errors": "~1.3.1", - "iconv-lite": "0.4.11", - "on-finished": "~2.3.0", - "qs": "4.0.0", - "raw-body": "~2.1.2", - "type-is": "~1.6.6" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -2067,18 +1766,6 @@ "lazy-cache": "^1.0.3" } }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, "change-case": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.0.2.tgz", @@ -2251,46 +1938,6 @@ } } }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - }, - "dependencies": { - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -2473,11 +2120,6 @@ } } }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" - }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -2546,11 +2188,6 @@ "color-name": "^1.0.0" } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" - }, "colormin": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", @@ -2564,7 +2201,8 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "combine-lists": { "version": "1.0.1", @@ -2583,11 +2221,6 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", - "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=" - }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -2690,104 +2323,25 @@ } } }, - "connect": { - "version": "2.30.2", - "resolved": "https://registry.npmjs.org/connect/-/connect-2.30.2.tgz", - "integrity": "sha1-jam8vooFTT0xjXTf7JA7XDmhtgk=", - "requires": { - "basic-auth-connect": "1.0.0", - "body-parser": "~1.13.3", - "bytes": "2.1.0", - "compression": "~1.5.2", - "connect-timeout": "~1.6.2", - "content-type": "~1.0.1", - "cookie": "0.1.3", - "cookie-parser": "~1.3.5", - "cookie-signature": "1.0.6", - "csurf": "~1.8.3", - "debug": "~2.2.0", - "depd": "~1.0.1", - "errorhandler": "~1.4.2", - "express-session": "~1.11.3", - "finalhandler": "0.4.0", - "fresh": "0.3.0", - "http-errors": "~1.3.1", - "method-override": "~2.3.5", - "morgan": "~1.6.1", - "multiparty": "3.3.2", - "on-headers": "~1.0.0", - "parseurl": "~1.3.0", - "pause": "0.1.0", - "qs": "4.0.0", - "response-time": "~2.3.1", - "serve-favicon": "~2.3.0", - "serve-index": "~1.7.2", - "serve-static": "~1.10.0", - "type-is": "~1.6.6", - "utils-merge": "1.0.0", - "vhost": "~3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" - }, - "connect-timeout": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.6.2.tgz", - "integrity": "sha1-3ppexh4zoStu2qt7XwYumMWZuI4=", - "requires": { - "debug": "~2.2.0", - "http-errors": "~1.3.1", - "ms": "0.7.1", - "on-headers": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "constant-case": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", - "integrity": "sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY=", + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "constant-case": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY=", "requires": { "snake-case": "^2.1.0", "upper-case": "^1.1.1" @@ -2819,20 +2373,6 @@ "safe-buffer": "~5.1.1" } }, - "cookie": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", - "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" - }, - "cookie-parser": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", - "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", - "requires": { - "cookie": "0.1.3", - "cookie-signature": "1.0.6" - } - }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -2853,11 +2393,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "crc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz", - "integrity": "sha1-+mIuG8OIvyVzCQgta2UgDOZwkLo=" - }, "create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -2980,16 +2515,6 @@ } } }, - "csrf": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", - "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=", - "requires": { - "rndm": "1.2.0", - "tsscmp": "1.0.5", - "uid-safe": "2.1.4" - } - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3119,17 +2644,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" }, - "csurf": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", - "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=", - "requires": { - "cookie": "0.1.3", - "cookie-signature": "1.0.6", - "csrf": "~3.0.0", - "http-errors": "~1.3.1" - } - }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3184,11 +2698,6 @@ "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", "dev": true }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" - }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -3212,35 +2721,6 @@ "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" }, - "deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "requires": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - } - } - }, "deep-for-each": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-1.0.6.tgz", @@ -3254,19 +2734,6 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, - "deepmerge": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-0.2.10.tgz", - "integrity": "sha1-iQa/nlJaT78bIDsq/LRkAkmCEhk=" - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "requires": { - "clone": "^1.0.2" - } - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3368,16 +2835,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, - "depd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", - "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" - }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=" - }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -3392,11 +2849,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" - }, "detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -3405,7 +2857,8 @@ "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=" + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true }, "diff": { "version": "3.5.0", @@ -3579,14 +3032,6 @@ "resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.0.tgz", "integrity": "sha512-8s9FFuKC+lOWGwJ0b3om2PF+uXrqQPaEQlPJI7UxdzxTYGMeKouMPA9+YlPn52zcAVElIZtd2tXj6eQmvlKelw==" }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "requires": { - "readable-stream": "~1.1.9" - } - }, "each-async": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/each-async/-/each-async-0.1.3.tgz", @@ -3683,24 +3128,6 @@ } } }, - "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", - "requires": { - "once": "~1.3.0" - }, - "dependencies": { - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "requires": { - "wrappy": "1" - } - } - } - }, "engine.io": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", @@ -4098,31 +3525,6 @@ "is-arrayish": "^0.2.1" } }, - "errorhandler": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", - "integrity": "sha1-t7cO2PNZ6duICS8tIMD4MUIK2D8=", - "requires": { - "accepts": "~1.3.0", - "escape-html": "~1.0.3" - }, - "dependencies": { - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - } - } - }, "es-abstract": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", @@ -4156,28 +3558,6 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, - "es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - } - } - }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -4583,11 +3963,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, - "etag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", - "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" - }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -4821,14 +4196,6 @@ } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -5050,45 +4417,6 @@ } } }, - "express-session": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz", - "integrity": "sha1-XMmPP1/4Ttg1+Ry/CqvQxxB0AK8=", - "requires": { - "cookie": "0.1.3", - "cookie-signature": "1.0.6", - "crc": "3.3.0", - "debug": "~2.2.0", - "depd": "~1.0.1", - "on-headers": "~1.0.0", - "parseurl": "~1.3.0", - "uid-safe": "~2.0.0", - "utils-merge": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "uid-safe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz", - "integrity": "sha1-p/PGymSh9qXQTsDvPkw9U2cxcTc=", - "requires": { - "base64-url": "1.2.1" - } - } - } - }, "ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", @@ -5243,17 +4571,6 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5382,42 +4699,6 @@ } } }, - "finalhandler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", - "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", - "requires": { - "debug": "~2.2.0", - "escape-html": "1.0.2", - "on-finished": "~2.3.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "escape-html": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", - "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" - }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -5453,49 +4734,6 @@ } } }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - } - }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=" - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" - }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -5544,11 +4782,6 @@ "for-in": "^1.0.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -5577,11 +4810,6 @@ "map-cache": "^0.2.2" } }, - "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" - }, "fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", @@ -5723,14 +4951,6 @@ } } }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", - "requires": { - "globule": "~0.1.0" - } - }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -5963,98 +5183,6 @@ } } }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "requires": { - "gaze": "^0.5.1" - } - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "requires": { - "find-index": "^0.1.1" - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6079,68 +5207,6 @@ } } }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" - }, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "requires": { - "sparkles": "^1.0.0" - } - }, - "graceful-fs": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", - "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", - "requires": { - "natives": "^1.1.3" - } - }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", @@ -6629,265 +5695,6 @@ "lodash": "^4.7.0" } }, - "gulp": { - "version": "3.8.11", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.8.11.tgz", - "integrity": "sha1-1Vfgpyg+tBNkkZabBJd2eXLx0oo=", - "requires": { - "archy": "^1.0.0", - "chalk": "^0.5.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^0.3.2", - "liftoff": "^2.0.1", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^0.2.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" - }, - "dependencies": { - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" - } - } - }, - "gulp-concat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.2.0.tgz", - "integrity": "sha1-7vw+FYDEzKxkOAbSBnwyK0QaY0Y=", - "requires": { - "gulp-util": "^2.2.5", - "through": "^2.3.4" - }, - "dependencies": { - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - } - }, - "lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=" - }, - "lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", - "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, - "lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", - "requires": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" - } - }, - "lodash.templatesettings": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", - "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" - } - }, - "minimist": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", - "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", - "requires": { - "clone-stats": "~0.0.1" - } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=" - } - } - }, - "gulp-uglify": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-0.3.2.tgz", - "integrity": "sha1-+je/bUrZopo0nGy6hiq7Iuumeqw=", - "requires": { - "deepmerge": ">=0.2.7 <0.3.0-0", - "gulp-util": ">=3.0.0 <4.0.0-0", - "through2": ">=0.6.1 <1.0.0-0", - "uglify-js": "2.4.6" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "requires": { - "glogg": "^1.0.0" - } - }, "handle-thing": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", @@ -6915,14 +5722,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "requires": { - "ansi-regex": "^0.2.0" - } - }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -6961,14 +5760,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "requires": { - "sparkles": "^1.0.0" - } - }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -7132,14 +5923,6 @@ "react-is": "^16.7.0" } }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "requires": { - "parse-passwd": "^1.0.0" - } - }, "hooker": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", @@ -7368,49 +6151,17 @@ "http-errors": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", - "requires": { - "inherits": "~2.0.1", - "statuses": "1" - } - }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" - }, - "http-proxy": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-0.10.4.tgz", - "integrity": "sha1-FLoM6qIZf4n6MN6p57CeGc2Twi8=", - "requires": { - "colors": "0.x.x", - "optimist": "0.6.x", - "pkginfo": "0.3.x", - "utile": "~0.2.1" - }, - "dependencies": { - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" - }, - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - } + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" } }, + "http-parser-js": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + }, "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", @@ -7631,16 +6382,6 @@ } } }, - "i": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", - "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==" - }, - "iconv-lite": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", - "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" - }, "icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", @@ -7790,11 +6531,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, "inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -7891,11 +6627,6 @@ "side-channel": "^1.0.4" } }, - "interpret": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.3.10.tgz", - "integrity": "sha1-CIwl3nMcbFsRKpDwBxz69Fnlp7s=" - }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7919,15 +6650,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", @@ -8108,11 +6830,6 @@ "lower-case": "^1.1.0" } }, - "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" - }, "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", @@ -8225,25 +6942,12 @@ "has-tostringtag": "^1.0.0" } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "requires": { - "is-unc-path": "^1.0.0" - } - }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" - }, "is-shared-array-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", @@ -8284,31 +6988,11 @@ "has-symbols": "^1.0.2" } }, - "is-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", - "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "requires": { - "unc-path-regex": "^0.1.2" - } - }, "is-upper-case": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", @@ -8322,11 +7006,6 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" - }, "is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -8335,11 +7014,6 @@ "call-bind": "^1.0.0" } }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==" - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8353,7 +7027,9 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true, + "optional": true }, "isbinaryfile": { "version": "3.0.3", @@ -9138,21 +7814,6 @@ "dev": true, "optional": true }, - "liftoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", - "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", - "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, "load-grunt-config": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/load-grunt-config/-/load-grunt-config-0.19.2.tgz", @@ -9289,108 +7950,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" - }, - "lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "requires": { - "lodash._htmlescapes": "~2.4.1" - } - }, - "lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" - }, - "lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", - "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", @@ -9411,35 +7970,6 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "requires": { - "lodash._root": "^3.0.0" - } - }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", @@ -9461,40 +7991,12 @@ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -9525,11 +8027,6 @@ "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -9540,31 +8037,6 @@ "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", @@ -9576,53 +8048,6 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "requires": { - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "log4js": { - "version": "0.6.38", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", - "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", - "requires": { - "readable-stream": "~1.0.2", - "semver": "~4.3.3" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" - } - } - }, "loggly": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz", @@ -9872,11 +8297,6 @@ "lower-case": "^1.1.2" } }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, "mailcomposer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", @@ -9935,14 +8355,6 @@ } } }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "requires": { - "kind-of": "^6.0.2" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -9986,16 +8398,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "medium-editor": { - "version": "5.23.3", - "resolved": "https://registry.npmjs.org/medium-editor/-/medium-editor-5.23.3.tgz", - "integrity": "sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==" - }, - "medium-editor-tables": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/medium-editor-tables/-/medium-editor-tables-0.6.1.tgz", - "integrity": "sha1-CKWZgvYdI4kp4S/hiszCMGLpbPM=" - }, "mem": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", @@ -10083,37 +8485,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "method-override": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", - "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", - "requires": { - "debug": "2.6.9", - "methods": "~1.1.2", - "parseurl": "~1.3.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - } - } - }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -10268,33 +8639,6 @@ "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", "dev": true }, - "morgan": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", - "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", - "requires": { - "basic-auth": "~1.0.3", - "debug": "~2.2.0", - "depd": "~1.0.1", - "on-finished": "~2.3.0", - "on-headers": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - } - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10325,33 +8669,11 @@ "minimatch": "^3.0.0" } }, - "multiparty": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", - "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", - "requires": { - "readable-stream": "~1.1.9", - "stream-counter": "~0.2.0" - } - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "nan": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz", - "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg=" - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -10370,11 +8692,6 @@ "to-regex": "^3.0.1" } }, - "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==" - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10388,11 +8705,6 @@ "xml-char-classes": "^1.0.0" } }, - "ncp": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", - "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=" - }, "nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -10935,11 +9247,6 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" - }, "object-component": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", @@ -11012,17 +9319,6 @@ "object-keys": "^1.1.1" } }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } - }, "object.entries": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", @@ -11043,15 +9339,6 @@ "es-abstract": "^1.19.1" } }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -11131,14 +9418,6 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", - "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", - "requires": { - "wordwrap": "~0.0.2" - } - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -11152,26 +9431,6 @@ "word-wrap": "~1.2.3" } }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" - }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" - } - }, - "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=" - }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -11391,16 +9650,6 @@ "safe-buffer": "^5.1.1" } }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - } - }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -11435,16 +9684,6 @@ "error-ex": "^1.2.0" } }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" - }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -11617,20 +9856,7 @@ "dev": true, "optional": true } - } - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "requires": { - "path-root-regex": "^0.1.0" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" + } }, "path-to-regexp": { "version": "0.1.7", @@ -11654,11 +9880,6 @@ } } }, - "pause": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz", - "integrity": "sha1-68ikqGGf8LioGsFRPDQ0/0af23Q=" - }, "pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -11726,11 +9947,6 @@ "find-up": "^1.0.0" } }, - "pkginfo": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", - "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" - }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -11751,11 +9967,6 @@ "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz", "integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==" }, - "policyfile": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/policyfile/-/policyfile-0.0.4.tgz", - "integrity": "sha1-1rgurZiueeviKOLa9ZAzEeyYLk0=" - }, "popper.js": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz", @@ -12377,11 +10588,6 @@ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, - "pretty-hrtime": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-0.2.2.tgz", - "integrity": "sha1-1P2INR46R0H4Fzr31qS4RvmJXAA=" - }, "primeicons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-2.0.0.tgz", @@ -12598,11 +10804,6 @@ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, - "qs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", - "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" - }, "query-string": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", @@ -12659,11 +10860,6 @@ "ret": "~0.1.10" } }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -12713,28 +10909,6 @@ "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.22.3.tgz", "integrity": "sha512-pIzHpAggyTOGJE3ruAKdZNK5qhO4V21kR7lwpdUM875yHpq1cqeGzvs78/RufF3g7NaAvVmMPCbaV9uUhQzJ3A==" }, - "raw-body": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", - "requires": { - "bytes": "2.4.0", - "iconv-lite": "0.4.13", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" - } - } - }, "react": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", @@ -13054,6 +11228,8 @@ "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -13140,12 +11316,6 @@ "strip-indent": "^1.0.1" } }, - "redis": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/redis/-/redis-0.7.3.tgz", - "integrity": "sha1-7le3pE0l7BWU5ENl2BZfp9HUgRo=", - "optional": true - }, "redis-commands": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", @@ -13309,11 +11479,6 @@ "is-finite": "^1.0.0" } }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -13431,15 +11596,6 @@ } } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -13465,22 +11621,6 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, - "response-time": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", - "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", - "requires": { - "depd": "~1.1.0", - "on-headers": "~1.0.1" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - } - } - }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -13520,11 +11660,6 @@ "inherits": "^2.0.1" } }, - "rndm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", - "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" - }, "rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -13792,55 +11927,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "send": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", - "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", - "requires": { - "debug": "~2.2.0", - "depd": "~1.1.0", - "destroy": "~1.0.4", - "escape-html": "~1.0.3", - "etag": "~1.7.0", - "fresh": "0.3.0", - "http-errors": "~1.3.1", - "mime": "1.3.4", - "ms": "0.7.1", - "on-finished": "~2.3.0", - "range-parser": "~1.0.3", - "statuses": "~1.2.1" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "requires": { - "ms": "0.7.1" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "statuses": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", - "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" - } - } - }, "sentence-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", @@ -13850,29 +11936,6 @@ "upper-case-first": "^1.1.2" } }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=" - }, - "serve-favicon": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.2.tgz", - "integrity": "sha1-3UGeJo3gEqtysxnTN/IQUBP5OB8=", - "requires": { - "etag": "~1.7.0", - "fresh": "0.3.0", - "ms": "0.7.2", - "parseurl": "~1.3.1" - }, - "dependencies": { - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" - } - } - }, "serve-index": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.7.3.tgz", @@ -13902,16 +11965,6 @@ } } }, - "serve-static": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", - "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", - "requires": { - "escape-html": "~1.0.3", - "parseurl": "~1.3.1", - "send": "0.13.2" - } - }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -14028,11 +12081,6 @@ "object-inspect": "^1.9.0" } }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, "signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", @@ -14224,41 +12272,12 @@ "hoek": "2.x.x" } }, - "socket.io": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-0.9.16.tgz", - "integrity": "sha1-O6sEROSbVfu8FXQk29Qao3WlGnY=", - "requires": { - "base64id": "0.1.0", - "policyfile": "0.0.4", - "redis": "0.7.3", - "socket.io-client": "0.9.16" - } - }, "socket.io-adapter": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", "dev": true }, - "socket.io-client": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-0.9.16.tgz", - "integrity": "sha1-TadRXF53MEHRtCOXBBW8xDDzX8Y=", - "requires": { - "active-x-obfuscator": "0.0.1", - "uglify-js": "1.2.5", - "ws": "0.4.x", - "xmlhttprequest": "1.4.2" - }, - "dependencies": { - "uglify-js": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.5.tgz", - "integrity": "sha1-tULCx29477NLIAsgF3Y0Mw/3ArY=" - } - } - }, "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", @@ -14421,11 +12440,6 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" - }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -14666,19 +12680,6 @@ } } }, - "stream-consume": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==" - }, - "stream-counter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", - "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", - "requires": { - "readable-stream": "~1.1.8" - } - }, "stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -14835,7 +12836,9 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true, + "optional": true }, "stringstream": { "version": "0.0.6", @@ -14844,23 +12847,6 @@ "dev": true, "optional": true }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "requires": { - "first-chunk-stream": "^1.0.0", - "is-utf8": "^0.2.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -15412,11 +13398,6 @@ } } }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" - }, "svgo": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", @@ -15523,44 +13504,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "thunkify": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", @@ -15573,19 +13516,6 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "requires": { - "os-homedir": "^1.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" - }, "timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -15601,11 +13531,6 @@ "dev": true, "optional": true }, - "tinycolor": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz", - "integrity": "sha1-MgtaUtg6u1l42Bo+iH1K77FaYWQ=" - }, "title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", @@ -15827,7 +13752,9 @@ "tsscmp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", - "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=", + "dev": true, + "optional": true }, "tsutils": { "version": "3.21.0", @@ -15924,27 +13851,6 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" }, - "uglify-js": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.6.tgz", - "integrity": "sha1-MXZqTYIrq/XzLBQJYlHtklkpitM=", - "requires": { - "async": "~0.2.6", - "optimist": "~0.3.5", - "source-map": "~0.1.7", - "uglify-to-browserify": "~1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", @@ -15972,14 +13878,6 @@ } } }, - "uid-safe": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", - "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=", - "requires": { - "random-bytes": "~1.0.0" - } - }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -15997,11 +13895,6 @@ "which-boxed-primitive": "^1.0.2" } }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" - }, "uncontrollable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz", @@ -16041,11 +13934,6 @@ "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" }, - "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=" - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -16163,31 +14051,6 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=" - }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - } - } - }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -16208,24 +14071,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "utile": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/utile/-/utile-0.2.1.tgz", - "integrity": "sha1-kwyI6ZCY1iIINMNWy9mncFItkNc=", - "requires": { - "async": "~0.2.9", - "deep-equal": "*", - "i": "0.3.x", - "mkdirp": "0.x.x", - "ncp": "0.4.x", - "rimraf": "2.x.x" - } - }, - "utils-merge": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" - }, "uuid": { "version": "8.3.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", @@ -16243,14 +14088,6 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, - "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "requires": { - "user-home": "^1.1.1" - } - }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -16287,72 +14124,6 @@ } } }, - "vhost": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", - "integrity": "sha1-L7HezUxGaqiLD5NBrzPcGv8keNU=" - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - }, - "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" - }, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" - } - } - } - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -17117,35 +14888,11 @@ "is-symbol": "^1.0.3" } }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, - "which-typed-array": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", - "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.7" - } - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -17203,7 +14950,8 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true }, "wrap-ansi": { "version": "2.1.0", @@ -17260,27 +15008,11 @@ "mkdirp": "^0.5.1" } }, - "ws": { - "version": "0.4.32", - "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz", - "integrity": "sha1-eHphVEFPPJntg8V3IVOyD+sM7DI=", - "requires": { - "commander": "~2.1.0", - "nan": "~1.0.0", - "options": ">=0.0.5", - "tinycolor": "0.x" - } - }, "xml-char-classes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=" }, - "xmlhttprequest": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.4.2.tgz", - "integrity": "sha1-AUU6HZvtHo8XL2SVu/TIxCYyFQA=" - }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", @@ -17348,11 +15080,6 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", "dev": true - }, - "zeparser": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/zeparser/-/zeparser-0.0.5.tgz", - "integrity": "sha1-A3JlYbwmjy5URPVMZlt/1KjAKeI=" } } } diff --git a/package.json b/package.json index 62baf5ddda..c027ce2121 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,6 @@ "angular": "1.6.9", "angular-contenteditable": "0.3.9", "angular-dynamic-locale": "0.1.32", - "angular-embed": "github:superdesk/angular-embed#d75968e", - "angular-embedly": "github:Urigo/angular-embedly#0.0.8", "angular-gettext": "github:tomaskikutis/angular-gettext#master", "angular-history": "github:decipherinc/angular-history#v0.8.0", "angular-i18n": "1.6.9", @@ -88,8 +86,6 @@ "load-grunt-config": "0.19.2", "load-grunt-tasks": "3.5.2", "lodash": "4.17.19", - "medium-editor": "5.23.3", - "medium-editor-tables": "0.6.1", "ment.io": "0.9.23", "moment": "2.20.1", "moment-timezone": "0.5.14", diff --git a/scripts/apps/authoring/authoring/directives/ArticleEditDirective.ts b/scripts/apps/authoring/authoring/directives/ArticleEditDirective.ts index 0798f30a7c..ce49964fe8 100644 --- a/scripts/apps/authoring/authoring/directives/ArticleEditDirective.ts +++ b/scripts/apps/authoring/authoring/directives/ArticleEditDirective.ts @@ -147,15 +147,6 @@ export function ArticleEditDirective( } }); - // watch item and save every change in history in order to perform undo/redo later - // ONLY for editor2 (with blocks) - try { - angular.module('superdesk.apps.editor2'); - history.watch('item', mainEditScope || scope); - } catch (e) { - // no-op - } - scope.$on('History.undone', triggerAutosave); scope.$on('History.redone', triggerAutosave); diff --git a/scripts/apps/authoring/authoring/services/HistoryFactory.ts b/scripts/apps/authoring/authoring/services/HistoryFactory.ts index 59252c13c3..7a96a8450b 100644 --- a/scripts/apps/authoring/authoring/services/HistoryFactory.ts +++ b/scripts/apps/authoring/authoring/services/HistoryFactory.ts @@ -23,14 +23,7 @@ export function HistoryFactory(History, $window, $timeout) { }; var onHistoryKeydown = function(event) { onHistoryKey(event, () => { - const editor2FieldsExist = document.querySelector('[sd-text-editor]') != null; - - if (editor2FieldsExist) { - // preventing default generally breaks undo via ctrl+z for native inputs/textareas, - // but is required for editor2 - event.preventDefault(); - } - // action is on keydown becuase command key (event.metakey) on OSX is not detected on keyup events + // action is on keydown because command key (event.metakey) on OSX is not detected on keyup events // for some reason. scope.$apply(() => { KeyOperations[event.keyCode].bind(History)(expression, scope); diff --git a/scripts/apps/authoring/macros/macros.spec.ts b/scripts/apps/authoring/macros/macros.spec.ts index 0af3c777a1..d142df5754 100644 --- a/scripts/apps/authoring/macros/macros.spec.ts +++ b/scripts/apps/authoring/macros/macros.spec.ts @@ -25,7 +25,6 @@ describe('macros', () => { }, ]; - beforeEach(window.module('angular-embed')); beforeEach(window.module('superdesk.apps.publish')); beforeEach(window.module('superdesk.core.preferences')); beforeEach(window.module('superdesk.apps.archive')); @@ -39,7 +38,6 @@ describe('macros', () => { beforeEach(window.module('superdesk.apps.vocabularies')); beforeEach(window.module('superdesk.apps.searchProviders')); beforeEach(window.module('superdesk.core.editor3')); - beforeEach(window.module('superdesk.apps.editor2')); beforeEach(window.module('superdesk.apps.authoring.macros')); beforeEach(window.module('superdesk.apps.authoring.autosave')); diff --git a/scripts/apps/authoring/styles/authoring.scss b/scripts/apps/authoring/styles/authoring.scss index 5f93c57810..0b65f16916 100644 --- a/scripts/apps/authoring/styles/authoring.scss +++ b/scripts/apps/authoring/styles/authoring.scss @@ -140,59 +140,6 @@ bottom: 32px; } - -// Medium editor -// ------------------------------------------------- -.medium-editor-toolbar-actions { - li button { - height: 30px !important; - min-width: 40px !important; - padding-top: 10px !important; - } -} -.medium-toolbar-arrow-under:after { - top: 40px !important; -} - -.text-editor-info { - font-size: 89%; -} - -.text-editor, .text-editor.medium-editor-element { - min-height: 20px; - - .sderror { - border-bottom: 1px dotted #cc0000; - background-color: rgba(204,0,0,0.1); - line-height: normal; - } - - .sdfindreplace { - background-color: rgba(0,0,0,0.1); - } - - .sdfindreplace.sdactive { - background-color: rgba(255,230,0,1); - } - - a { - color: #5d9bc0; - } -} - -.typing { - .text-editor { - .sderror, .typing .sdfindreplace { - border-bottom: none; - background-color: transparent; - } - - &.clone { - display: none; - } - } -} - // Placeholders // ------------------------------------------------- [contenteditable=true] { diff --git a/scripts/apps/authoring/tests/authoring.spec.ts b/scripts/apps/authoring/tests/authoring.spec.ts index 3fb3c3f44c..feb1c28818 100644 --- a/scripts/apps/authoring/tests/authoring.spec.ts +++ b/scripts/apps/authoring/tests/authoring.spec.ts @@ -13,7 +13,6 @@ describe('authoring', () => { $provide.constant('lodash', _); })); - beforeEach(window.module('angular-embed')); beforeEach(window.module('superdesk.apps.publish')); beforeEach(window.module('superdesk.core.preferences')); beforeEach(window.module('superdesk.apps.archive')); @@ -27,7 +26,6 @@ describe('authoring', () => { beforeEach(window.module('superdesk.apps.vocabularies')); beforeEach(window.module('superdesk.apps.searchProviders')); beforeEach(window.module('superdesk.core.editor3')); - beforeEach(window.module('superdesk.apps.editor2')); beforeEach(window.module('superdesk.apps.extension-points')); beforeEach(window.module('superdesk.apps.spellcheck')); @@ -636,7 +634,6 @@ describe('Item Crops directive', () => { beforeEach(window.module('superdesk.apps.vocabularies')); beforeEach(window.module('superdesk.apps.searchProviders')); beforeEach(window.module('superdesk.core.editor3')); - beforeEach(window.module('superdesk.apps.editor2')); it('showCrops return true if image renditions are present', inject(($rootScope, $compile, $q, metadata, vocabularies) => { @@ -2209,14 +2206,12 @@ describe('send item directive', () => { server: {url: undefined, ws: undefined}, iframely: {key: '123'}, editor: {}, - features: {onlyEditor3: false}, }; Object.assign(appConfig, testConfig); }); beforeEach(window.module('superdesk.core.editor3')); - beforeEach(window.module('superdesk.apps.editor2')); beforeEach(window.module('superdesk.core.preferences')); beforeEach(window.module('superdesk.apps.authoring')); beforeEach(window.module('superdesk.templates-cache')); @@ -2428,7 +2423,7 @@ describe('send item directive', () => { _id: 'New Stage', name: 'new stage', }; - beforeEach(inject(($q, $compile, $rootScope, api, editor) => { + beforeEach(inject(($q, $compile, $rootScope, api) => { spyOn(api, 'find').and.returnValue($q.when({})); spyOn(api, 'save').and.returnValue($q.when({task: {desk: 'new', stage: 'new'}})); diff --git a/scripts/apps/authoring/views/article-edit.html b/scripts/apps/authoring/views/article-edit.html index 107680b724..acc9df5d7e 100644 --- a/scripts/apps/authoring/views/article-edit.html +++ b/scripts/apps/authoring/views/article-edit.html @@ -16,33 +16,7 @@
- - - - - - -
-
- -
- - - - -
-
-
- -
-
- - - - - -
-
-
-
-
- - - - -
-
{{item.description_text}}
-
- -
- - -
- -
@@ -870,7 +750,7 @@
- -
- -
-
-
diff --git a/scripts/core/editor2/add-content.ts b/scripts/core/editor2/add-content.ts deleted file mode 100644 index bd6739e095..0000000000 --- a/scripts/core/editor2/add-content.ts +++ /dev/null @@ -1,146 +0,0 @@ -import {appConfig} from 'appConfig'; - -angular.module('superdesk.apps.editor2.content', []).directive('sdAddContent', ['$window', - function($window) { - return { - // the scope is not isolated because we require the medium instance - controller: AddContentCtrl, - require: ['sdAddContent', '^sdTextEditorBlockText', '^sdTextEditor'], - templateUrl: 'scripts/core/editor2/views/add-content.html', - controllerAs: 'vm', - bindToController: true, - link: function(scope, element, attrs, ctrls) { - var vm = ctrls[0]; - - angular.extend(vm, { - textBlockCtrl: ctrls[1], - sdEditorCtrl: ctrls[2], - }); - if (!vm.config.embeds) { - return; - } - // initialize state - vm.updateState(); - // listen for update state signals - var unbindListener = scope.$parent.$on('sdAddContent::updateState', - (signal, event, editorElem) => { - vm.updateState(event, editorElem); - }); - // update on resize - - angular.element($window).on('resize', vm.updateState); - scope.$on('$destroy', () => { - angular.element($window).off('resize', vm.updateState); - unbindListener(); - }); - }, - }; - }]); - -AddContentCtrl.$inject = ['$scope', '$element', 'superdesk', 'editor', '$timeout', '$q']; -function AddContentCtrl(scope, element, superdesk, editor, $timeout, $q) { - var elementHolder = element.find('div:first-child').first(); - var self = this; - - /** Return true if the event come from this directive's element */ - function elementContainsEventTarget(event) { - if (!angular.isDefined(event)) { - return false; - } - return $.contains(element.get(0), event.target); - } - - angular.extend(self, { - expanded: false, - config: angular.extend({embeds: true}, appConfig.editor || {}), // should be on by default - // update the (+) vertical position on the left and his visibility (hidden/shown) - updateState: function(event, editorElem) { - // hide if medium is not defined yett (can happen at initialization) or - // hide if the text input is not selected and if it was not a click on the (+) button - if (!angular.isDefined(scope.medium) - || !angular.element(editorElem).is(':focus') && !elementContainsEventTarget(event)) { - return self.hide(); - } - - var currentParagraph; - - try { - currentParagraph = angular.element(scope.medium.getSelectedParentElement()); - } catch (e) { - return; - } - var position = currentParagraph.position().top; - // move the (+) button at the caret position - - elementHolder.css('top', position > 0 ? position : 0); - // handle resize: Do nothing, only positioning was needed - if (event && event.type === 'resize') { - return; - } - - const isFocusEvent = event && event.type === 'focus'; - const isClickEvent = event && event.type === 'click'; - const containsTarget = elementContainsEventTarget(event) && currentParagraph.text() === ''; - const isFocused = editorElem.is(':focus') && currentParagraph.text() === ''; - const isEmptyLine = currentParagraph.text() === ''; - const shouldShow = isFocusEvent && containsTarget || isClickEvent && isFocused || isEmptyLine; - - return shouldShow ? self.show() : self.hide(); - }, - hide: function() { - $timeout(() => { - elementHolder.css({ - display: 'none', - }); - self.expanded = false; - }); - }, - show: function() { - $timeout(() => { - elementHolder.css({ - display: 'block', - }); - }); - }, - toogleExpand: function() { - self.expanded = !self.expanded; - }, - isSomethingInClipboard: function() { - return angular.isDefined(self.sdEditorCtrl.getCutBlock()); - }, - triggerAction: function(action) { - self.hide(); - self.actions[action](); - }, - actions: { - addEmbed: function() { - self.sdEditorCtrl.splitAndInsert(self.textBlockCtrl).then(() => { - // show the add-embed form - self.textBlockCtrl.block.showAndFocusLowerAddAnEmbedBox(); - }); - }, - addPicture: function() { - superdesk.intent('upload', 'media', {uniqueUpload: true}).then((images) => { - $q.all(images.map((image) => editor.generateMediaTag(image).then((imgTag) => ({ - blockType: 'embed', - embedType: 'Image', - body: imgTag, - caption: image.description_text, - association: image, - })))).then((renderedImages) => { - self.sdEditorCtrl.splitAndInsert(self.textBlockCtrl, renderedImages); - }); - }, () => { - scope.node.focus(); - self.textBlockCtrl.restoreSelection(); - }); - }, - pasteBlock: function() { - if (!self.sdEditorCtrl.getCutBlock()) { - return false; - } - self.sdEditorCtrl.splitAndInsert(self.textBlockCtrl, self.sdEditorCtrl.getCutBlock(true)); - }, - }, - }); -} diff --git a/scripts/core/editor2/add-embed.ctrl.ts b/scripts/core/editor2/add-embed.ctrl.ts deleted file mode 100644 index 27b94711b8..0000000000 --- a/scripts/core/editor2/add-embed.ctrl.ts +++ /dev/null @@ -1,133 +0,0 @@ -import embedCodeHandlers from './embedCodeHandlers'; - -angular.module('superdesk.apps.editor2.embed', []).controller('SdAddEmbedController', SdAddEmbedController); - -SdAddEmbedController.$inject = ['embedService', '$element', '$timeout', '$q', 'lodash', - 'EMBED_PROVIDERS', '$scope', 'editor', '$injector']; -function SdAddEmbedController(embedService, $element, $timeout, $q, _, - EMBED_PROVIDERS, $scope, editor, $injector) { - var self = this; - - angular.extend(self, { - editorCtrl: undefined, // defined in link method - previewLoading: false, - toggle: function(close) { - // use parameter or toggle - self.extended = angular.isDefined(close) ? !close : !self.extended; - }, - retrieveEmbed: function() { - function retrieveEmbedFromUrl() { - return embedService.get(self.input).then((data) => { - var embed = data.html; - - if (!angular.isDefined(embed)) { - if (data.type === 'link') { - embed = editor.generateLinkTag({ - url: data.url, - title: data.meta.title, - description: data.meta.description, - illustration: data.thumbnail_url, - }); - } else { - embed = editor.generateMediaTag({url: data.url, altText: data.description}); - } - } - return $q.when(embed).then((embedElement) => ({ - body: embedElement, - provider: data.provider_name || EMBED_PROVIDERS.custom, - })); - }); - } - function parseRawEmbedCode() { - var waitFor = []; - var embedBlock = { - body: self.input, - provider: EMBED_PROVIDERS.custom, - }; - - function updateEmbedBlock(partialUpdate) { - angular.extend(embedBlock, partialUpdate); - } - // try to guess the provider of the custom embed - for (var i = 0; i < embedCodeHandlers.length; i++) { - var provider = $injector.invoke(embedCodeHandlers[i]); - - if (angular.isDefined(provider.condition)) { - if (!provider.condition()) { - continue; - } - } - var match = provider.pattern.exec(self.input); - - if (match) { - updateEmbedBlock({provider: provider.name}); - if (provider.callback) { - waitFor.push(provider.callback(match).then(updateEmbedBlock)); - } - break; - } - } - return $q.all(waitFor).then(() => embedBlock); - } - var embedCode; - // if it's an url, use embedService to retrieve the embed code - - if (_.startsWith(self.input, 'http')) { - embedCode = retrieveEmbedFromUrl(); - // otherwise we use the content of the field directly - } else { - embedCode = parseRawEmbedCode(); - } - return $q.when(embedCode); - }, - updatePreview: function() { - self.previewLoading = true; - self.retrieveEmbed().then((embed) => { - angular.element($element) - .find('.preview') - .html(embed.body.replace('\\n', '')); - self.previewLoading = false; - }); - }, - createFigureBlock: function(data) { - // create a new block containing the embed - data.blockType = 'embed'; - return self.editorCtrl.insertNewBlock(self.addToPosition, data); - }, - createBlockFromEmbed: function() { - self.retrieveEmbed().then((embed) => { - self.createFigureBlock({ - embedType: embed.provider, - body: embed.body, - association: embed.association, - }); - // close the addEmbed form - self.toggle(true); - }); - }, - closeEmbed: function() { - // put block back together on embed form close. - self.toggle(); - }, - }); - - // toggle when the `extended` directive attribute changes - $scope.$watch(() => self.extended, (extended, wasExtended) => { - // on enter, focus on input - if (angular.isDefined(extended)) { - if (extended) { - $timeout(() => { - angular.element($element) - .find('input') - .focus(); - }, 500, false); // positive timeout because of a chrome issue - // on leave, clear field - } else if (wasExtended) { - self.input = ''; - if (typeof self.onClose === 'function') { - $timeout(self.onClose); - } - } - } - }); -} diff --git a/scripts/core/editor2/customAnchor.ts b/scripts/core/editor2/customAnchor.ts deleted file mode 100644 index ad0533322c..0000000000 --- a/scripts/core/editor2/customAnchor.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This module extends (and overrides) MediumEditor's anchor button to disallow - * malformed URLs when the 'linkValidation: true' option is set. - * - * It additionally introduces the possibility to set the validation pattern - * using the 'pattern' option when configuring the editor. - */ -import MediumEditor from 'medium-editor'; - -class CustomAnchorButton extends MediumEditor.extensions.anchor { - linkValidation: any; - pattern: any; - getForm: any; - - createForm() { - let form = super.createForm(); - - if (!this.linkValidation) { - return form; - } - - let input = form.getElementsByTagName('input')[0]; - - input.setAttribute('type', 'url'); - input.setAttribute('required', ''); - if (this.pattern) { - input.setAttribute('pattern', this.pattern); - } - - return form; - } - - doFormSave() { - let input = this.getForm().getElementsByTagName('input')[0]; - - // prevent double encoding; medium editor don't detect if the url is already encoded - input.value = decodeURIComponent(input.value); - - let isValid = input.checkValidity(); - - if (isValid) { - super.doFormSave(); - } - } -} - -// override default anchor -MediumEditor.extensions.anchor = CustomAnchorButton; diff --git a/scripts/core/editor2/editor.ctrl.ts b/scripts/core/editor2/editor.ctrl.ts deleted file mode 100644 index 6e8cd03149..0000000000 --- a/scripts/core/editor2/editor.ctrl.ts +++ /dev/null @@ -1,401 +0,0 @@ -import {appConfig} from 'appConfig'; - -angular.module('superdesk.apps.editor2.ctrl', []).controller('SdTextEditorController', SdTextEditorController); - -SdTextEditorController.$inject = ['lodash', 'EMBED_PROVIDERS', '$timeout', 'editor', '$q']; -function SdTextEditorController(_, EMBED_PROVIDERS, $timeout, editor, $q) { - var self = this; - - function Block(attrs: any = {}) { - // eslint-disable-next-line consistent-this - var BlockFnThis = this; - - angular.extend(BlockFnThis, _.defaults({ - body: attrs.body, - loading: attrs.loading, - caption: attrs.caption, - blockType: attrs.blockType, - embedType: attrs.embedType, - association: attrs.association, - lowerAddEmbedIsExtended: undefined, - showAndFocusLowerAddAnEmbedBox: function() { - BlockFnThis.lowerAddEmbedIsExtended = true; - }, - }, { - body: '


', - loading: false, - blockType: 'text', - })); - } - /** - * For the given blocks, merge text blocks when there are following each other - * and add empty text block arround embeds if needed - */ - function prepareBlocks(blocks) { - var newBlocks = []; - - blocks.forEach((block, i) => { - // if not the first block and there is a text block following another one - if (i > 0 && block.blockType === 'text' && blocks[i - 1].blockType === 'text') { - // we merge the content with the previous block - newBlocks[newBlocks.length - 1].body += block.body; - } else { - // otherwise we add the full block - newBlocks.push(block); - } - // add emtpy text block if block is between 2 embed blocks or at the end - if (blocks[i].blockType === 'embed') { - if (i === blocks.length - 1 || blocks[i + 1].blockType === 'embed') { - newBlocks.push(new Block()); - } - } - }); - // add emtpy text block at the top if needed - if (newBlocks[0].blockType === 'embed') { - newBlocks.unshift(new Block()); - } - return newBlocks; - } - function splitIntoBlock(bodyHtml) { - var blocks = [], block; - /** - * push the current block into the blocks collection if it is not empty - */ - - function commitBlock() { - if (block !== undefined && block.body.trim() !== '') { - blocks.push(block); - block = undefined; - } - } - - function handleParagraph(element) { - commitBlock(); - if (angular.isDefined(element.innerHTML) && element.textContent !== '' - && element.textContent !== '\n') { - blocks.push(new Block({body: element.outerHTML.trim()})); - } - } - - $('
' + bodyHtml + '
') - .contents() - .toArray() - .forEach((element) => { - // if we get a

, we push the current block and create a new one - // for the paragraph content - if (element.nodeName === 'P') { - handleParagraph(element); - // detect if it's an embed - } else if (element.nodeName === '#comment') { - if (element.nodeValue.indexOf('EMBED START') > -1) { - commitBlock(); - // retrieve the embed type following the comment - var embedType; - var embedTypeRegex = /EMBED START ([\w-]+)/; - var match; - - if ((match = embedTypeRegex.exec(angular.copy(element.nodeValue).trim())) !== null) { - embedType = match[1]; - } else { - embedType = EMBED_PROVIDERS.custom; - } - // retrieve the association reference - var association; - var embedAssoKey = /{id: "(embedded\d+)"}/; - - if ((match = embedAssoKey.exec(angular.copy(element.nodeValue).trim())) !== null - && self.associations) { - association = angular.copy(self.associations[match[1]]); - } - // create the embed block - block = new Block({ - blockType: 'embed', - embedType: embedType, - association: association, - body: '', - }); - } - if (element.nodeValue.indexOf('EMBED END') > -1) { - commitBlock(); - } - // if it's not a paragraph or an embed, we update the current block - } else { - if (block === undefined) { - block = new Block({body: ''}); - } - // we want the outerHTML (ex: 'text') or the node value for text and comment - block.body += (element.outerHTML || element.nodeValue || '').trim(); - } - }); - // at the end of the loop, we push the last current block - if (block !== undefined && block.body.trim() !== '') { - blocks.push(block); - } - // Complete embeds with metadata (from association datadata or html) - blocks.forEach((blockObject) => { - if (blockObject.blockType === 'embed') { - // for images that come from Superdesk, we use the association - if (blockObject.association && blockObject.embedType === 'Image') { - blockObject.caption = blockObject.association.description_text; - blockObject.body = ''; - editor.generateMediaTag(blockObject.association).then((img) => { - blockObject.body = img; - }); - } else { - // extract body and caption from embed blockObject html - var originalBody = angular.element(angular.copy(blockObject.body)); - - if (originalBody.get(0).nodeName === 'FIGURE') { - blockObject.body = ''; - originalBody.contents() - .toArray() - .forEach((element) => { - if (element.nodeName === 'FIGCAPTION') { - blockObject.caption = element.innerHTML; - } else { - blockObject.body += element.outerHTML || element.nodeValue || ''; - } - }); - } - } - } - }); - // if no block, create an empty one to start - if (blocks.length === 0) { - blocks.push(new Block()); - } - return blocks; - } - angular.extend(self, { - configuration: angular.extend({embeds: true}, appConfig.editor || {}), - blocks: [], - initEditorWithOneBlock: function(model) { - self.model = model; - self.blocks = [new Block({body: model.$modelValue})]; - }, - initEditorWithMultipleBlock: function(model) { - // save the model to update it later - self.model = model; - // parse the given model and create blocks per paragraph and embed - var content = model.$modelValue || ''; - // update the actual blocks value at the end to prevent more digest cycle as needed - - self.blocks = splitIntoBlock(content); - self.renderBlocks(); - }, - serializeBlock: function(blocks = self.blocks) { - // in case blocks are not ready - if (blocks.length === 0) { - return ''; - } - var newBody = ''; - - if (self.config.multiBlockEdition) { - blocks.forEach((block) => { - if (angular.isDefined(block.body) && block.body.trim() !== '') { - if (block.blockType === 'embed') { - var blockName = block.embedType.trim(); - // add an id to the image in order to retrieve it in `assocations` field - - if (block.association) { - blockName += ' {id: "embedded' + self.generateBlockId(block) + '"}'; - } - newBody += [ - '\n', - '

', - block.body, - '
', - block.caption, - '
', - '
', - '\n\n'].join(''); - } else { - newBody += block.body + '\n'; - } - } - }); - } else { - newBody = blocks.length > 0 && typeof blocks[0].body === 'string' ? blocks[0].body : ''; - } - // strip
and

- newBody = newBody.trim().replace(/


<\/p>$/, ''); - newBody = newBody.replace(/
$/, ''); - return newBody; - }, - commitChanges: function() { - var associations = angular.copy(self.associations); - // initialize associations if doesn't exist - - if (typeof associations !== 'object') { - associations = {}; - } - // remove older associations - angular.forEach(associations, (value, key) => { - if (_.startsWith(key, 'embedded')) { - associations[key] = null; - } - }); - - if (Object.keys(associations).length || self.associations) { - // update associations with the ones stored in blocks - self.associations = angular.extend({}, associations, self.getAssociations()); - } - - // save model with latest state of blocks - var serialized = self.serializeBlock(); - - if (serialized !== self.model.$viewValue) { - self.model.$setViewValue(serialized); - } - }, - /** - * Return an object that contains the embedded images in the story - */ - getAssociations: function() { - var association = {}; - - self.blocks.forEach((block) => { - // we keep the association only for Superdesk images - if (block.association) { - // add the association - association['embedded' + self.generateBlockId(block)] = angular.copy(block.association); - } - }); - return association; - }, - getBlockPosition: function(block) { - return _.indexOf(self.blocks, block); - }, - splitAndInsert: function(textBlockCtrl, blocksToInsert) { - // index where to add the new block - var index = self.getBlockPosition(textBlockCtrl.block) + 1; - - if (index === 0) { - throw new Error('Block to split not found'); - } - // cut the text that is after the caret in the block and save it in order to add it after the embed later - var after = textBlockCtrl.extractEndOfBlock().innerHTML; - - return $q.when((function() { - if (after) { - // save the blocks (with removed leading text) - textBlockCtrl.updateModel(); - // add new text block for the remaining text - return self.insertNewBlock(index, { - body: after, - }, true); - } - })()).then(() => { - if (angular.isDefined(blocksToInsert)) { - var isArray = true; - var arrayBlocks = blocksToInsert; - - if (!angular.isArray(arrayBlocks)) { - isArray = false; - arrayBlocks = [arrayBlocks]; - } - var waitFor = $q.when(); - var createdBlocks = []; - - arrayBlocks.forEach((bti) => { - waitFor = waitFor.then(() => { - var newBlock = self.insertNewBlock(index, bti); - - createdBlocks.push(newBlock); - return newBlock; - }); - }); - return waitFor.then(() => { - if (isArray) { - return $q.all(createdBlocks); - } - - return createdBlocks[0]; - }); - } - }); - }, - /** - ** Merge text blocks when there are following each other and add empty text block arround embeds if needed - ** @param {Integer} position - ** @param {Object} block ; block attributes - ** @param {boolean} doNotRenderBlocks ; if true, it won't merge text blocks and - ** add empty text block if needed through the `renderBlocks()` function. - ** @returns {object} this - */ - insertNewBlock: function(position, attrs, doNotRenderBlocks) { - var newBlock = new Block(attrs); - - return $q((resolve) => { - $timeout(() => { - self.blocks.splice(position, 0, newBlock); - $timeout(() => { - self.commitChanges(); - if (!doNotRenderBlocks) { - $timeout(() => { - self.renderBlocks(); - resolve(newBlock); - }, 0, false); - } else { - resolve(newBlock); - } - }, 0, false); - }); - }); - }, - /** - * Merge text blocks when there are following each other and add empty text block arround embeds if needed - */ - renderBlocks: function() { - self.blocks = prepareBlocks(self.blocks); - }, - removeBlock: function(block) { - // remove block only if it's not the only one - var blockPosition = self.getBlockPosition(block); - - if (self.blocks.length > 1) { - self.blocks.splice(blockPosition, 1); - } else { - // if it's the first block, just remove the content - block.body = ''; - } - self.renderBlocks(); - return $timeout(self.commitChanges); - }, - clipboard: undefined, - cutBlock: function(block) { - self.clipboard = angular.copy(block); - return self.removeBlock(block); - }, - getCutBlock: function(remove) { - var block = self.clipboard; - - if (remove) { - self.clipboard = undefined; - } - return block; - }, - /** - * Compute an id for the block with its content and its position. - * Used as `track by` value, it allows the blocks to be well rendered. - */ - generateBlockId: function(block) { - function hashCode(string) { - var hash = 0, i, chr, len; - - if (string.length === 0) { - return hash; - } - for (i = 0, len = string.length; i < len; i++) { - chr = string.charCodeAt(i); - // tslint:disable-next-line:no-bitwise - hash = (hash << 5) - hash + chr; - // tslint:disable-next-line:no-bitwise - hash |= 0; // Convert to 32bit integer - } - return hash; - } - return String(Math.abs(hashCode(block.body))) + String(self.getBlockPosition(block)); - }, - }); -} diff --git a/scripts/core/editor2/editor.spec.ts b/scripts/core/editor2/editor.spec.ts deleted file mode 100644 index 400c9a2806..0000000000 --- a/scripts/core/editor2/editor.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import {ISuperdeskGlobalConfig} from 'superdesk-api'; -import {appConfig} from 'appConfig'; - -describe('text editor', () => { - beforeEach(() => { - const testConfig: Partial = { - server: {url: undefined, ws: undefined}, - iframely: {key: '123'}, - }; - - Object.assign(appConfig, testConfig); - }); - - beforeEach(window.module('superdesk.apps.publish')); - beforeEach(window.module('superdesk.config')); - beforeEach(window.module('superdesk.apps.editor2')); - beforeEach(window.module('superdesk.apps.spellcheck')); - beforeEach(window.module('superdesk.apps.vocabularies')); - beforeEach(window.module('superdesk.apps.searchProviders')); - - beforeEach(() => { - // remove all elements from body - document.body = document.createElement('body'); - }); - - function createScope(text, $rootScope) { - var scope = $rootScope.$new(); - - scope.node = document.createElement('p'); - scope.node.innerHTML = text; - scope.model = { - $viewValue: text, - $setViewValue: function(value) { - this.$viewValue = value; - }, - }; - scope.medium = { - exportSelection: function() { - return {start: 0, end: 0}; - }, - importSelection: function(pos) { /* no-op */ }, - }; - document.body.appendChild(scope.node); - - // eslint-disable-next-line jasmine/no-unsafe-spy - spyOn(scope.model, '$setViewValue').and.callThrough(); - return scope; - } - - it('can spellcheck', inject((editor, spellcheck, $q, $rootScope) => { - spyOn(spellcheck, 'errors').and.returnValue($q.when([{word: 'test', index: 0}])); - spyOn(spellcheck, 'getDictionary').and.returnValue($q.when([{language_id: 'en'}])); - - var scope = createScope('test', $rootScope); - - editor.registerScope(scope); - editor.renderScope(scope); - $rootScope.$digest(); - - expect(scope.node.innerHTML).toBe('test'); - expect(scope.node.parentNode.lastChild.innerHTML) - .toBe('test'); - })); - - it('can replace word in node', inject((editor, $q, $rootScope) => { - var content = 'test foo'; - var scope = createScope(content, $rootScope); - - editor.replaceWord(scope, 5, 3, 'bars'); - expect(scope.node.innerHTML).toBe('test bars'); - })); - - it('can findreplace', inject((editor, spellcheck, $q, $rootScope) => { - spyOn(spellcheck, 'errors').and.returnValue($q.when([{word: 'test', index: 0}])); - spyOn(spellcheck, 'getDictionary').and.returnValue($q.when([{language_id: 'en'}])); - - var scope = createScope('test foo and foo', $rootScope); - - editor.registerScope(scope); - - editor.setSettings({findreplace: {diff: {foo: '', bar: ''}}}); - editor.render(); - $rootScope.$digest(); - editor.selectNext(); - - var foo1 = 'foo'; - var foo1active = 'foo'; - var foo2 = 'foo'; - var foo2active = 'foo'; - - expect(scope.node.innerHTML).toBe('test foo and foo'); - expect(scope.node.parentNode.lastChild.innerHTML).toBe('test ' + foo1active + ' and ' + foo2); - - editor.selectNext(); - expect(scope.node.innerHTML).toBe('test foo and foo'); - expect(scope.node.parentNode.lastChild.innerHTML).toBe('test ' + foo1 + ' and ' + foo2active); - - editor.selectPrev(); - expect(scope.node.parentNode.lastChild.innerHTML).toBe('test ' + foo1active + ' and ' + foo2); - - editor.replace('tic'); - $rootScope.$digest(); - expect(scope.node.innerHTML).toBe('test tic and foo'); - editor.render(); - $rootScope.$digest(); - editor.selectNext(); - expect(scope.node.parentNode.lastChild.innerHTML).toBe('test tic and ' + foo2active); - - editor.setSettings({findreplace: {diff: {test: ''}}}); - editor.render(); - $rootScope.$digest(); - editor.replaceAll('bars'); - expect(scope.node.innerHTML).toBe('bars tic and foo'); - - editor.setSettings({findreplace: null}); - editor.render(); - $rootScope.$digest(); - expect(scope.node.parentNode.lastChild.innerHTML).toContain('sderror'); - expect(scope.node.parentNode.lastChild.innerHTML).not.toContain('active'); - expect(scope.node.innerHTML).toBe('bars tic and foo'); - })); - - it('can replace abbreviations', inject((editor, spellcheck, $q, $rootScope, $timeout) => { - editor.setSettings({spellcheck: true}); - var abbreviations = { - IMF: 'International Monetory Fund', - WHO: 'World Health Organisation', - UN: 'United Nations', - }; - - spyOn(spellcheck, 'getAbbreviationsDict').and.returnValue($q.when(abbreviations)); - var scope = createScope('test', $rootScope); - - editor.registerScope(scope); - scope.node.parentNode.classList.add('typing'); - - scope.node.innerHTML = 'foo'; - editor.commit(); - expect(scope.model.$setViewValue).not.toHaveBeenCalled(); - $rootScope.$digest(); - expect(scope.model.$setViewValue).toHaveBeenCalledWith('foo'); - expect(scope.node.innerHTML).toBe('foo'); - - scope.node.innerHTML = 'foo IMF*'; - editor.commit(); - $rootScope.$digest(); - expect(scope.node.innerHTML).toBe('foo ' + abbreviations.IMF); - - scope.node.innerHTML = 'foo IMF IMF* IMF*'; - editor.commit(); - $rootScope.$digest(); - expect(scope.node.innerHTML).toBe('foo IMF ' + abbreviations.IMF + ' ' + abbreviations.IMF); - - scope.node.innerHTML = 'foo IMF* WHO*'; - editor.commit(); - $rootScope.$digest(); - expect(scope.node.innerHTML).toBe('foo ' + abbreviations.IMF + ' ' + abbreviations.WHO); - })); - - it('can check if keyboard event is important or not', inject((editor) => { - expect(editor.shouldIgnore({keyCode: 16})).toBe(true); - expect(editor.shouldIgnore({shiftKey: true, ctrlKey: true, keyCode: 65})).toBe(true); - expect(editor.shouldIgnore({keyCode: 65})).toBe(false); - })); -}); diff --git a/scripts/core/editor2/editor.ts b/scripts/core/editor2/editor.ts deleted file mode 100644 index 0fed26ca7e..0000000000 --- a/scripts/core/editor2/editor.ts +++ /dev/null @@ -1,1771 +0,0 @@ -/** - * This file is part of Superdesk. - * - * Copyright 2013, 2014 Sourcefabric z.u. and contributors. - * - * For the full copyright and license information, please see the - * AUTHORS and LICENSE files distributed with this source code, or - * at https://www.sourcefabric.org/superdesk/license - */ -import MediumEditor from 'medium-editor'; -import MediumEditorTable from 'medium-editor-tables'; -import _ from 'lodash'; -import {get} from 'lodash'; -import {gettext, escapeRegExp} from 'core/utils'; - -import './customAnchor'; -import {appConfig} from 'appConfig'; - -var TYPING_CLASS = 'typing'; - -/** - * History stack - * - * It supports undo/redo operations - * - * @param {string} initialValue - */ -function HistoryStack(initialValue) { - var stack = []; - var index = -1; - - /** - * Add a new value to stack and remove all furhter redo values - * so after manual change there is no way to redo. - * - * @param {string} value - */ - this.add = function(value) { - index = index + 1; - stack[index] = value; - stack.splice(index + 1, stack.length); - }; - - /** - * Select previous value (undo) - */ - this.selectPrev = function() { - index = Math.max(-1, index - 1); - }; - - /** - * Select next value (redo) - */ - this.selectNext = function() { - index = !_.isNil(stack[index + 1]) ? index + 1 : index; - }; - - /** - * Get current value - */ - this.get = function() { - var state = index > -1 ? stack[index] : initialValue; - - return state; - }; - - /** - * Get current index - * - * @return {Number} - */ - this.getIndex = function() { - return index; - }; -} - -EditorService.$inject = ['spellcheck', '$q', 'renditions', 'editorUtils']; -function EditorService(spellcheck, $q, renditionsService, utils) { - this.settings = {spellcheck: true}; - - this.version = () => '2'; - - this.KEY_CODES = Object.freeze({ - Y: 'Y'.charCodeAt(0), - Z: 'Z'.charCodeAt(0), - UP: 38, - DOWN: 40, - F3: 114, - }); - - this.ARROWS = Object.freeze({ - 33: 1, // page up - 34: 1, // page down - 35: 1, // end - 36: 1, // home - 37: 1, // left - 38: 1, // up - 39: 1, // right - 40: 1, // down - }); - - this.META = Object.freeze({ - 16: 1, // shift - 17: 1, // ctrl - 18: 1, // alt - 20: 1, // caps lock - 91: 1, // left meta in webkit - 93: 1, // right meta in webkit - 224: 1, // meta in firefox - }); - - /** - * Test if given keyboard event should be ignored as it's not changing content. - * - * @param {Event} event - * @return {boolen} - */ - this.shouldIgnore = function(event) { - // ignore arrows - if (self.ARROWS[event.keyCode]) { - return true; - } - - // ignore meta keys (ctrl, shift or meta only) - if (self.META[event.keyCode]) { - return true; - } - - // ignore shift + ctrl/meta + something - if (event.shiftKey && (event.ctrlKey || event.metaKey)) { - return true; - } - - return false; - }; - - var ERROR_CLASS = 'sderror'; - var HILITE_CLASS = 'sdhilite'; - var ACTIVE_CLASS = 'sdactive'; - var FINDREPLACE_CLASS = 'sdfindreplace'; - - var self = this; - var scopes = []; - - /** - * Register given scope - it adds history stack to it and keeps reference - * - * @param {Scope} scope - */ - this.registerScope = function(scope) { - scopes.push(scope); - scope.history = new HistoryStack(scope.model.$viewValue || ''); - scope.$on('$destroy', () => { - var index = scopes.indexOf(scope); - - scopes.splice(index, 1); - }); - }; - - /** - * Render highlights for given scope based on settings - * - * @param {Scope} scope - * @param {Scope} force force rendering manually - eg. via keyboard - */ - this.renderScope = function(scope, force, preventStore) { - if (self.settings.findreplace) { - renderFindreplace(scope.node); - } else if (self.settings.spellcheck || force) { - spellcheck.getDictionary(scope.language).then((dictionaries) => { - if (dictionaries && dictionaries.length) { - renderSpellcheck(scope.node, preventStore); - } - }); - } else { - utils.removeHilites(scope.node); - } - }; - - /** - * Render highlights in all registered scopes - * - * @param {Boolean} force rendering - */ - this.render = function(force) { - scopes.forEach((scope) => { - self.renderScope(scope, force); - }); - }; - - /** - * Highlight find&replace matches in given node - * - * @param {Node} node - */ - function renderFindreplace(node) { - var tokens = utils.getFindReplaceTokens(node, self.settings); - - utils.hilite(node, tokens, FINDREPLACE_CLASS); - } - - /** - * Highlight spellcheck errors in given node - * - * @param {Node} node - */ - function renderSpellcheck(node, preventStore) { - spellcheck.errors(node).then((tokens) => { - utils.hilite(node, tokens, ERROR_CLASS, preventStore); - }); - } - - /** - * Set next highlighted node active. - * - * In case there is no node selected select first one. - */ - this.selectNext = function() { - var nodes = document.body.getElementsByClassName(HILITE_CLASS); - - for (var i = 0; i < nodes.length; i++) { - var node = nodes.item(i); - - if (node.classList.contains(ACTIVE_CLASS)) { - node.classList.remove(ACTIVE_CLASS); - var nextNode = nodes.item((i + 1) % nodes.length); - - nextNode.classList.add(ACTIVE_CLASS); - scrollHighlightedNodeToTop(); - return node; - } - } - - if (nodes.length) { - nodes.item(0).classList.add(ACTIVE_CLASS); - scrollHighlightedNodeToTop(); - } - }; - - function scrollHighlightedNodeToTop() { - let containerElem = angular.element('.page-content-container'); - - if (containerElem.offset()) { - // This offset is to make visible what the top section of the document will hide when scrolled - let baseOffset = angular.element('.subnav__button-stack').prop('clientHeight') + - angular.element('.authoring-sticky').prop('clientHeight') + - angular.element('#top-menu').prop('clientHeight'); - - let classList = '.' + FINDREPLACE_CLASS + '.' + ACTIVE_CLASS + '.' + HILITE_CLASS; - - let nodeElem = angular.element(classList); - - if (nodeElem.length > 0) { - // All set, scroll to the highlighted element - containerElem.scrollTop(nodeElem.offset().top - containerElem.offset().top + - containerElem.scrollTop() - baseOffset); - } - } - } - - /** - * Set previous highlighted node active. - */ - this.selectPrev = function() { - var nodes = document.body.getElementsByClassName(HILITE_CLASS); - - for (var i = 0; i < nodes.length; i++) { - var node = nodes.item(i); - - if (node.classList.contains(ACTIVE_CLASS)) { - node.classList.remove(ACTIVE_CLASS); - nodes.item(i === 0 ? nodes.length - 1 : i - 1).classList.add(ACTIVE_CLASS); - scrollHighlightedNodeToTop(); - return; - } - } - }; - - function replaceText(scope, text, className = ACTIVE_CLASS) { - var nodes = scope.node.parentNode.getElementsByClassName(className); - var nodesLength = nodes.length; - var replacementOffset = self.replaceNodes(nodes, text, scope); - - if (replacementOffset && className === ACTIVE_CLASS) { - updateIndexOnReplace(scope.node, replacementOffset); - } - return nodesLength; - } - - // updates the data-index of remaining find-replace candidates subsequent to just replaced active node - function updateIndexOnReplace(node, replacementOffset) { - var nodes = node.parentNode.getElementsByClassName(HILITE_CLASS); - var nextElem, newIndex, activeIndex; - - for (var i = 0; i < nodes.length; i++) { - var currentNode = nodes.item(i); - - if (currentNode.classList.contains(ACTIVE_CLASS)) { - currentNode.classList.remove(FINDREPLACE_CLASS); - activeIndex = i; - } - } - - if (!_.isNil(activeIndex)) { - for (var j = activeIndex + 1; j < nodes.length; j++) { - nextElem = nodes.item(j); - newIndex = parseInt(nextElem.getAttribute('data-index'), 10) + replacementOffset; - nextElem.setAttribute('data-index', newIndex); - } - } - } - - /** - * Replace active node with given text. - * - * @param {string} text - */ - this.replace = function(text) { - scopes.forEach((scope) => { - if (replaceText(scope, text)) { - this.commitScope(scope); - } - }); - }; - - /** - * Replace all highlighted nodes with given text. - * - * @param {string} text - */ - this.replaceAll = function(text) { - scopes.forEach((scope) => { - var nodes = scope.node.parentNode.getElementsByClassName(HILITE_CLASS); - - this.replaceNodes(nodes, text, scope); - this.commitScope(scope); - }); - }; - - /** - * Replace text at given index with word - * - * @param {Object} scope - * @param {Number} index - * @param {Number} length - * @param {String} word - */ - this.replaceWord = function(scope, index, length, word) { - var node = scope.node; - var start = utils.findWordNode(node, index, length); - var characters = start.node.textContent.split(''); - - characters.splice(start.offset, length, word); - start.node.textContent = characters.join(''); - }; - - /** - * Replace all nodes with text - * - * @param {HTMLCollection} nodes - * @param {string} text - */ - this.replaceNodes = function(nodes, text, scope) { - var index, replacementOffset = 0; - - for (var i = 0, l = nodes.length; i < l; i++) { - var node = nodes.item(i); - var word = node.dataset.word; - - index = parseInt(node.dataset.index, 10) + replacementOffset; - this.replaceWord(scope, index, word.length, text); - replacementOffset += text.length - word.length; - } - return replacementOffset; - }; - - /** - * Update settings - * - * @param {Object} settings - */ - this.setSettings = function(settings) { - self.settings = angular.extend({}, self.settings, settings); - }; - - /** - * Test if given elem is a spellcheck error node - * - * @param {Node} elem - * @return {boolean} - */ - this.isErrorNode = function(elem) { - return elem.classList.contains(ERROR_CLASS); - }; - - /** - * Commit changes in all scopes - */ - this.commit = function() { - scopes.forEach(self.commitScope); - }; - - /** - * Replace abbreviations. - * @param {Scope} scope - */ - function replaceAbbreviations(scope) { - if (!scope.node.parentNode.classList.contains(TYPING_CLASS)) { - return $q.when({}); - } - - if (scope.node.innerText !== '') { - return spellcheck.getAbbreviationsDict().then((abbreviations) => { - if (_.keys(abbreviations).length) { - var pattern = '\\b(' - + _.map(_.keys(abbreviations), (item) => escapeRegExp(item)).join('|') + ')(\\*)'; - var found = scope.node.innerText.match(new RegExp(pattern, 'g')); - - if (found) { - // store old settings - var oldSettings = angular.extend({}, self.settings); - var caretPosition = scope.medium.exportSelection(); - - _.forEach(_.uniq(found), (val: string) => { - var replacementValue = abbreviations[val.replace('*', '')]; - - if (replacementValue) { - var diff = {}; - - diff[val] = replacementValue; - self.setSettings({findreplace: {diff: diff, caseSensitive: true}}); - renderFindreplace(scope.node); - var nodesLength = replaceText(scope, replacementValue, FINDREPLACE_CLASS); - - if (nodesLength > 0) { - var incrementCaretPosition = (replacementValue.length - val.length) * nodesLength; - - caretPosition.start += incrementCaretPosition; - caretPosition.end += incrementCaretPosition; - } - } - }); - - scope.medium.importSelection(caretPosition); - // apply old settings - self.setSettings({findreplace: oldSettings.findreplace ? oldSettings.findreplace : null}); - } - } - }); - } - - return $q.when({}); - } - - /** - * Commit changes in given scope to its model - * - * @param {Scope} scope - */ - this.commitScope = function(scope) { - replaceAbbreviations(scope).then(() => { - var nodeValue = scope.node.innerHTML; - - if (nodeValue !== scope.model.$viewValue) { - scope.model.$setViewValue(nodeValue); - scope.history.add(scope.model.$viewValue); - } - }); - }; - - /** - * Returns the cleaned node text - * - * @return {string} - */ - this.getNodeText = function(scope) { - return scope.node.innerHTML; - }; - - /** - * Get active node text - * - * @return {string} - */ - this.getActiveText = function() { - var active; - - scopes.forEach((scope) => { - var nodes = scope.node.parentNode.getElementsByClassName(ACTIVE_CLASS); - - active = nodes.length ? nodes.item(0) : active; - }); - - return active ? active.textContent : null; - }; - - /** - * Return html code to represent an embedded link - * - * @param {string} url - * @param {string} titleg - * @param {string} description - * @param {string} illustration - * @return {string} html - */ - this.generateLinkTag = ({url, title, description, illustration}) => [ - '

', - ].join('\n'); - - this.generateMediaTag = function(data) { - var mediaTypes: any = { - video: function() { - var videoTag = [''); - return videoTag.join(''); - }, - picture: function() { - var url = data.url, altText = data.altText; - var promiseFinished; - // if this is a SD archive, we use its properties - - if (data._type === 'archive' || data.type === 'picture' || data.type === 'graphic') { - // get expected renditions list - promiseFinished = renditionsService.get().then((renditionsList) => { - // ]use the first rendtion as default - var firstRendition = data.renditions[renditionsList[0].name]; - - if (angular.isDefined(firstRendition)) { - url = firstRendition.href; - } else { - // use "viewImage" rendition as fallback - url = data.renditions.viewImage.href; - } - // if a `alt_text` exists, otherwise we fill w/ `description_text` - altText = data.alt_text || data.description_text; - return renditionsList; - }); - } - // when previous promise is finished, compose the html - return $q.when(promiseFinished, (renditionsList) => { - var html = [' { - if (r.width) { - var rendition = data.renditions[r.name]; - - if (angular.isDefined(rendition)) { - renditionsHtml.push(rendition.href.replace('http://', '//') - + ' ' + rendition.width + 'w'); - } - } - }); - if (renditionsHtml.length > 0) { - html.push('srcset="' + renditionsHtml.join(', ') + '"'); - } - } - html.push('/>'); - return html.join(' '); - }); - }, - }; - - mediaTypes.graphic = mediaTypes.picture; - return $q.when(mediaTypes[data.type]()); - }; - - this.getSelectedText = function() { - var text = ''; - - if (window.getSelection) { - text = window.getSelection().toString(); - } else if (document['selection'] && document['selection'].type !== 'Control') { - text = document['selection'].createRange().text; - } - return text; - }; -} - -SdTextEditorBlockEmbedController.$inject = ['$timeout', 'editor', 'renditions']; -function SdTextEditorBlockEmbedController($timeout, editor, renditions) { - var self = this; - - angular.extend(self, { - embedCode: undefined, // defined below - caption: undefined, // defined below - title: undefined, // defined below - editable: false, - toggleEdition: function() { - self.editable = !self.editable; - }, - saveEmbedCode: function() { - // update the block's model - angular.extend(self.model, { - body: self.embedCode, - }); - // on change callback - self.onBlockChange(); - }, - cancel: function() { - self.embedCode = self.model.body; - }, - saveCaption: function(caption, title) { - // if block is a superdesk image (with association), we update the description_text and headline - if (self.model.association) { - self.model.association.description_text = caption; - self.model.association.headline = title; - } - // update the caption in the model - self.model.caption = caption; - self.model.title = title; - - // update the caption in the view - self.caption = caption; - self.title = title; - - // on change callback - $timeout(() => { - self.onBlockChange(); - }); - }, - handlePaste: function(e) { - e.preventDefault(); - e.stopPropagation(); - - var clipboardData = e.originalEvent.clipboardData || window.clipboardData; - var pastedData = clipboardData.getData('Text'); - - $timeout(() => { - document.execCommand('insertHTML', false, pastedData); - }); - }, - isEditable: function(picture) { - return picture._type !== 'externalsource'; - }, - editPicture: function(picture) { - // only for SD images (with association) - if (!self.model.association) { - return false; - } - self.model.loading = true; - renditions.crop(picture).then((croppedPicture) => { - // update block - self.model.association = croppedPicture; - editor.generateMediaTag(croppedPicture).then((img) => { - self.model.body = img; - }); - // update caption - self.saveCaption(self.model.association.description_text, self.model.association.headline); - }) - .finally(() => { - self.model.loading = false; - }); - }, - }); - $timeout(() => { - angular.extend(self, { - embedCode: self.model.body, - caption: self.model.caption, - - // model.association has no value when embed source is not superdesk - title: get(self, 'model.association.headline', ''), - }); - }); -} - -angular.module('superdesk.apps.editor2', [ - 'superdesk.apps.editor2.ctrl', - 'superdesk.apps.editor2.embed', - 'superdesk.apps.editor2.content', - 'superdesk.apps.editor2.utils', - 'superdesk.apps.spellcheck', - 'superdesk.apps.authoring', - 'angular-embed', -]) - .service('editor', EditorService) - .constant('EMBED_PROVIDERS', { // see http://noembed.com/#supported-sites - custom: 'Custom', - twitter: 'Twitter', - youtube: 'YouTube', - vidible: 'Vidible', - }) - .directive('sdAddEmbed', ['$timeout', function($timeout) { - return { - scope: {addToPosition: '=', extended: '=', onClose: '&'}, - require: ['sdAddEmbed', '^sdTextEditor'], - templateUrl: 'scripts/core/editor2/views/add-embed.html', - controllerAs: 'vm', - controller: 'SdAddEmbedController', - bindToController: true, - link: function(scope, element, attrs, controllers) { - var vm = controllers[0]; - - angular.extend(vm, { - editorCtrl: controllers[1], - }); - // listen to the escape touch to close the field when pressed - element.bind('keyup', (e) => { - if (e.keyCode === 27) { // escape - $timeout(() => { - vm.extended = false; - }); - } - }); - }, - }; - }]) - .directive('sdTextEditorDropZone', ['embedService', 'EMBED_PROVIDERS', 'editor', '$timeout', '$q', - (embedService, EMBED_PROVIDERS, editor, $timeout, $q) => { - var dragOverClass = 'medium-editor-dragover'; - - return { - require: '^sdTextEditorBlockText', - scope: {sdTextEditorDropZone: '@'}, - link: function(scope, element, attrs, ctrl) { - if (scope.sdTextEditorDropZone === 'false') { - return; - } - - var MEDIA_TYPES = [ - 'application/superdesk.item.picture', - 'application/superdesk.item.graphic', - 'application/superdesk.item.video', - 'application/superdesk.item.audio', - 'text/html', - ]; - - let getType = (event) => MEDIA_TYPES.find( - (_type) => event.originalEvent.dataTransfer.types.indexOf(_type) >= 0, - ); - - element.on('drop dragdrop', (event) => { - event.preventDefault(); - event.stopPropagation(); - const mediaType = getType(event); - const paragraph = angular.element(event.target); - - let item = event.originalEvent.dataTransfer.getData(mediaType); - - // we want to ensure that the field is empty before inserting something - if (paragraph.text() !== '') { - return false; - } - // remove the UI state - paragraph.removeClass(dragOverClass); - // select paragraph element in order to register position - ctrl.selectElement(paragraph.get(0)); - // assume this is an item in json format when it comes from superdesk - if (mediaType.indexOf('application/superdesk') === 0) { - item = angular.fromJson(item); - // insert the item - ctrl.insertMedia(item); - } else if (mediaType === 'text/html' && typeof item === 'string') { - $q.when((() => { - // if it's a link (...), create an embed by using iframely - // if not, create an embed based on the item content - const urlMatch = //.exec(item); - - if (urlMatch) { - return embedService.get(urlMatch[1]).then((data) => ({ - blockType: 'embed', - embedType: data.provider_name || EMBED_PROVIDERS.custom, - body: data.html || editor.generateLinkTag({ - url: data.url, - title: data.meta.title, - description: data.meta.description, - illustration: data.thumbnail_url, - }), - })); - } - return { - blockType: 'embed', - embedType: EMBED_PROVIDERS.custom, - body: item, - }; - })()) - // split the current block and insert the new block, then commit changes - .then((block) => { - ctrl.sdEditorCtrl.splitAndInsert(ctrl, block) - .then(() => $timeout(ctrl.sdEditorCtrl.commitChanges)); - }); - } - }) - .on('dragover', (event) => { - const paragraph = angular.element(event.target); - - let matching = getType(event); - - if (matching) { - // allow to overwite the drop binder (see above) - event.preventDefault(); - event.stopPropagation(); - // if dragged element is a picture and if the paragraph is empty, - // highlight the paragraph - if (paragraph.text() === '') { - return paragraph.addClass(dragOverClass); - } - } - // otherwise, remove the style - paragraph.removeClass(dragOverClass); - }) - .on('dragleave', (event) => { - const paragraph = angular.element(event.target); - - paragraph.removeClass(dragOverClass); - }); - }, - }; - }]) - .directive('sdTextEditor', ['$timeout', function($timeout) { - return { - scope: {type: '=', config: '=', editorformat: '=', language: '=', associations: '=?', readOnly: '=?'}, - require: ['sdTextEditor', 'ngModel'], - templateUrl: 'scripts/core/editor2/views/editor.html', - controllerAs: 'vm', - controller: 'SdTextEditorController', - bindToController: true, - link: function(scope, element, attr, controllers) { - var controller = controllers[0]; - var ngModel = controllers[1]; - - window.dispatchEvent(new CustomEvent('editorInitialized')); - - function init() { - scope.$applyAsync(() => { - if (controller.config.multiBlockEdition) { - controller.initEditorWithMultipleBlock(ngModel); - } else { - controller.initEditorWithOneBlock(ngModel); - } - }); - } - // init editor based on model - init(); - // when the model changes from outside, updates the editor - scope.$watch(function outsideModelChange() { - return ngModel.$viewValue; - }, () => { - $timeout(() => { - // if controller is ready and the value has changed - if (controller.blocks.length > 0 && ngModel.$viewValue !== controller.serializeBlock()) { - // if blocks are not loading - if (!_.some(controller.blocks, (block) => block.loading)) { - init(); - } - } - }, 250, false); - }); - }, - }; - }]) - .directive('sdTextEditorBlockEmbed', () => - ({ - scope: {type: '=', config: '=', language: '=', model: '=sdTextEditorBlockEmbed', onBlockChange: '&'}, - templateUrl: 'scripts/core/editor2/views/block-embed.html', - controllerAs: 'vm', - bindToController: true, - controller: SdTextEditorBlockEmbedController, - }), - ) - .directive('sdTextEditorBlockText', ['editor', 'spellcheck', '$timeout', - '$q', '$rootScope', - function(editor, spellcheck, $timeout, $q, $rootScope) { - var TOP_OFFSET = 134; // header height - - var EDITOR_CONFIG = { - toolbar: { - static: true, - align: 'left', - sticky: true, - stickyTopOffset: TOP_OFFSET, - updateOnEmptySelection: true, - }, - paste: { - // Both are disabled because it overwrites the `ctrl`+`v` binding - // and we need it for the block paste feature - forcePlainText: false, - cleanPastedHTML: false, - // SDESK-714 chrome will replace

by

. This line reverts it (SDESK-714) - cleanReplacements: [[new RegExp(//gi), '

'], [new RegExp(/<\/div>/gi), '

']], - }, - anchor: { - placeholderText: gettext('Paste or type a full link'), - linkValidation: true, - }, - anchorPreview: { - showWhenToolbarIsVisible: true, - }, - placeholder: false, - disableReturn: false, - spellcheck: false, - targetBlank: true, - }; - - if (appConfig.editor) { - angular.extend(EDITOR_CONFIG, appConfig.editor); - } - - /** - * Get number of lines for all p nodes before given node withing same parent. - */ - function getLinesBeforeNode(p) { - function getLineCount(text) { - return text.split('\n').length; - } - - var lines = 0; - var pos = p; - - while (pos) { - if (pos.childNodes.length && pos.childNodes[0].nodeType === Node.TEXT_NODE) { - lines += getLineCount(pos.childNodes[0].wholeText); - } else if (pos.childNodes.length) { - lines += 1; // empty paragraph - } - pos = pos.previousSibling; - } - - return lines; - } - - /** - * Get line/column coordinates for given cursor position. - */ - function getLineColumn() { - var column, lines, - selection = window.getSelection(); - - if (selection.anchorNode.nodeType === Node.TEXT_NODE) { - var text = selection.anchorNode['wholeText'].substring(0, selection.anchorOffset); - var node = selection.anchorNode; - - column = text.length + 1; - while (node.nodeName !== 'P') { - if (node.previousSibling) { - column += node.previousSibling['wholeText'] ? - node.previousSibling['wholeText'].length : - node.previousSibling.textContent.length; - node = node.previousSibling; - } else { - node = node.parentNode; - } - } - - lines = 0 + getLinesBeforeNode(node); - } else { - lines = 0 + getLinesBeforeNode(selection.anchorNode); - column = 1; - } - - return { - line: lines, - column: column, - }; - } - - function extractBlockContentsFromCaret() { - function getBlockContainer(node) { - var pos = node; - - while (pos) { - if (pos.nodeType === 1 && /^(DIV)$/i.test(pos.nodeName)) { - return pos; - } - pos = pos.parentNode; - } - } - var sel = window.getSelection(); - - if (sel.rangeCount) { - var selRange = sel.getRangeAt(0); - var blockEl = getBlockContainer(selRange.endContainer); - - if (blockEl) { - var range = selRange.cloneRange(); - - range.selectNodeContents(blockEl); - range.setStart(selRange.endContainer, selRange.endOffset); - var remaining = range.extractContents(); - var $blockEl = $(blockEl); - // clear if empty of text - - if ($blockEl.text() === '') { - $blockEl.html(''); - } - // remove empty last line - $blockEl.find('p:last').each(function() { - if ($(this).text() === '') { - this.remove(); - } - }); - return remaining; - } - } - } - - function setEditorFormatOptions(editorConfig, editorFormat, scope) { - function addButton(format) { - editorConfig.toolbar.buttons.push({ - h1: { - name: 'h1', - action: 'append-h2', - aria: gettext('header type 1'), - tagNames: ['h2'], - contentDefault: '' + gettext('H1') + '', - classList: ['custom-class-h1'], - attrs: { - 'data-custom-attr': 'attr-value-h1', - }, - }, - - h2: { - name: 'h2', - action: 'append-h3', - aria: gettext('header type 2'), - tagNames: ['h3'], - contentDefault: '' + gettext('H2') + '', - classList: ['custom-class-h2'], - attrs: { - 'data-custom-attr': 'attr-value-h2', - }, - }, - - bold: { - name: 'bold', - action: 'bold', - aria: gettext('bold'), - tagNames: ['b'], - contentDefault: '' + gettext('B') + '', - }, - - underline: { - name: 'underline', - action: 'underline', - aria: gettext('underline'), - tagNames: ['u'], - }, - - italic: { - name: 'italic', - action: 'italic', - aria: gettext('italic'), - tagNames: ['i'], - contentDefault: '' + gettext('I') + '', - }, - - quote: { - name: 'quote', - action: 'append-blockquote', - aria: gettext('quote'), - }, - - removeFormat: { - name: 'removeFormat', - action: 'removeFormat', - aria: gettext('remove formatting'), - }, - - link: { - name: 'anchor', - action: 'createLink', - aria: gettext('link'), - }, - }[format] || format); - } - - _.each(editorFormat, addButton); - } - - return { - scope: {type: '=', config: '=', language: '=', sdTextEditorBlockText: '=', readOnly: '=?'}, - require: ['ngModel', '^sdTextEditor', 'sdTextEditorBlockText'], - templateUrl: 'scripts/core/editor2/views/block-text.html', - link: function(scope, elem, attrs, controllers) { - var ngModel = controllers[0]; - var sdTextEditor = controllers[1]; - - scope.model = ngModel; - // give the block model and the editor controller to the text block controller - var vm = controllers[2]; - - angular.extend(vm, { - block: scope.sdTextEditorBlockText, - sdEditorCtrl: sdTextEditor, - }); - vm.block = scope.sdTextEditorBlockText; - var editorElem; - var updateTimeout; - var renderTimeout; - - ngModel.$viewChangeListeners.push(changeListener); - ngModel.$render = function() { - editor.registerScope(scope); - var editorConfig = angular.merge( - {}, - EDITOR_CONFIG, scope.config || {}, - {disableEditing: scope.readOnly}, - ); - - if (editorConfig.toolbar) { - editorConfig.toolbar.buttons = []; - setEditorFormatOptions(editorConfig, sdTextEditor.editorformat, scope); - // if config.multiBlockEdition is true, add Embed and Image button to the toolbar - if (scope.config.multiBlockEdition) { - // this dummy imageDragging stop preventing drag & drop events - editorConfig.extensions = {imageDragging: {}}; - if (editorConfig.toolbar.buttons.indexOf('table') !== -1 - && angular.isDefined(MediumEditorTable)) { - editorConfig.extensions.table = new MediumEditorTable({ - aria: gettext('insert table'), - }); - } - } - } - - spellcheck.setLanguage(scope.language); - editorElem = elem.find(scope.type === 'preformatted' ? '.editor-type-text' - : '.editor-type-html'); - // events could be attached already, so remove these - editorElem.off('mouseup keydown keyup click contextmenu'); - editorElem.empty(); - editorElem.html(ngModel.$viewValue || ''); - scope.node = editorElem[0]; - scope.model = ngModel; - // destroy exiting instance - if (scope.medium) { - scope.medium.destroy(); - } - - // create a new instance of the medium editor binded to this node - scope.medium = new MediumEditor(scope.node, editorConfig); - // restore the selection if exist - if (scope.sdTextEditorBlockText.caretPosition) { - scope.node.focus(); - vm.restoreSelection(); - // clear the saved position - scope.sdTextEditorBlockText.caretPosition = undefined; - } - // listen for paste event and insert a block if exists in clipboard - scope.medium.subscribe('editablePaste', (e) => { - var clipboard = vm.sdEditorCtrl.getCutBlock(true); - - if (clipboard) { - e.preventDefault(); - vm.sdEditorCtrl.splitAndInsert(vm, clipboard); - } - }); - // listen caret moves in order to show or hide the (+) button beside the caret - function updateAddContentButton(e) { - scope.$emit('sdAddContent::updateState', e, editorElem); - } - editorElem.on('mouseup', updateAddContentButton); - ['editableInput', 'focus', 'blur', 'editableClick', 'editableKeyup'] - .forEach((eventName) => { - scope.medium.subscribe(eventName, updateAddContentButton); - }); - // listen updates by medium editor to update the model - scope.medium.subscribe('editableInput', (e, element) => { - element.querySelectorAll('span[style]').forEach((span) => { - span.before(span.firstChild); - span.remove(); - }); - - cancelTimeout(); - updateTimeout = $timeout(vm.updateModel, 200, false); - }); - scope.medium.subscribe('blur', () => { - // save latest know caret position - vm.savePosition(); - - vm.updateModel(); - }); - // update the toolbar, bc it can be displayed at the - // wrong place if offset of block has changed - scope.medium.subscribe('focus', () => { - var toolbar = scope.medium.getExtensionByName('toolbar'); - - if (toolbar) { - toolbar.positionStaticToolbar(scope.medium.getFocusedElement()); - } - }); - - // hide toolbar if element is under header - scope.medium.subscribe('positionedToolbar', (e, element) => { - var toolbar = scope.medium.getExtensionByName('toolbar'), - elemPosition = element.getBoundingClientRect(); - - if (toolbar) { - toolbar.toolbar.hidden = elemPosition.top + elemPosition.height < TOP_OFFSET; - } - }); - - function cancelTimeout(event?) { - $timeout.cancel(updateTimeout); - startTyping(); - } - - function changeSelectedParagraph(direction) { - var selectedParagraph = angular.element(scope.medium.getSelectedParentElement()); - var paragraphToBeSelected = selectedParagraph[direction > 0 ? 'next' : 'prev']('p'); - - if (paragraphToBeSelected.length > 0) { - // select the paragraph - scope.medium.selectElement(paragraphToBeSelected.get(0)); - // scroll to the paragraph - var $scrollableParent = $('.page-content-container'); - var offset = $scrollableParent.scrollTop(); - - offset += paragraphToBeSelected.position().top; - offset += paragraphToBeSelected.closest('.block__container').offset().top; - offset -= 100; // margin to prevent the top bar to hide the selected paragraph - $scrollableParent.scrollTop(offset); - } - } - - function toggleCase() { - var selectedText = editor.getSelectedText(); - - if (selectedText.length > 0) { - // looks the first character, and inverse the case of the all selection - if (selectedText[0].toUpperCase() === selectedText[0]) { - selectedText = selectedText.toLowerCase(); - } else { - selectedText = selectedText.toUpperCase(); - } - scope.medium.saveSelection(); - // replace the selected text - scope.medium.cleanPaste(selectedText); - scope.medium.restoreSelection(); - } - } - - var ctrlOperations = {}, shiftOperations = {}; - - ctrlOperations[editor.KEY_CODES.UP] = changeSelectedParagraph.bind(null, -1); - ctrlOperations[editor.KEY_CODES.DOWN] = changeSelectedParagraph.bind(null, 1); - shiftOperations[editor.KEY_CODES.F3] = toggleCase; - editorElem.on('keydown', (event) => { - if (editor.shouldIgnore(event)) { - return; - } - // prevent default behaviour for ctrl or shift operations - if (event.ctrlKey && ctrlOperations[event.keyCode] || - event.shiftKey && shiftOperations[event.keyCode]) { - event.preventDefault(); - } - cancelTimeout(event); - }); - editorElem.on('keyup', (event) => { - if (editor.shouldIgnore(event)) { - return; - } - if (event.ctrlKey && ctrlOperations[event.keyCode]) { - ctrlOperations[event.keyCode](); - return; - } - if (event.shiftKey && shiftOperations[event.keyCode]) { - shiftOperations[event.keyCode](); - return; - } - cancelTimeout(event); - updateTimeout = $timeout(vm.updateModel, 200, false); - }); - - /** - * Test if given point {x, y} is in given bouding rectangle. - */ - function isPointInRect(point, rect) { - return rect.left < point.x && rect.right > point.x && rect.top < point.y - && rect.bottom > point.y; - } - - editorElem.on('contextmenu', (event) => { - var err, pos; - var point = {x: event.clientX, y: event.clientY}; - var errors = elem[0].parentNode.getElementsByClassName('sderror'); - - for (var i = 0, l = errors.length; i < l; i++) { - err = errors.item(i); - pos = err.getBoundingClientRect(); - if (isPointInRect(point, pos)) { - event.preventDefault(); - event.stopPropagation(); - renderContextMenu(err); - return false; - } - } - }); - - function renderContextMenu(node) { - // close previous menu (if any) - scope.$apply(() => { - scope.suggestions = null; - scope.openDropdown = false; - }); - - // set data needed for replacing - scope.replaceWord = node.dataset.word; - scope.replaceIndex = parseInt(node.dataset.index, 10); - scope.sentenceWord = node.dataset.sentenceWord === 'true'; - - spellcheck.suggest(node.textContent).then((suggestions) => { - if (scope.sentenceWord) { - suggestions.push({ - key: scope.replaceWord[0].toUpperCase() + scope.replaceWord.slice(1), - value: scope.replaceWord[0].toUpperCase() + scope.replaceWord.slice(1), - }); - - scope.suggestions = suggestions.filter((suggestion) => - suggestion.key !== scope.replaceWord, - ); - } else { - scope.suggestions = suggestions; - } - scope.replaceTarget = node; - scope.$applyAsync(() => { - var menu = elem[0].getElementsByClassName('dropdown__menu')[0]; - - menu.style.left = node.offsetLeft + 'px'; - menu.style.top = node.offsetTop + node.offsetHeight + 'px'; - menu.style.position = 'absolute'; - scope.openDropdown = true; - }); - }); - return false; - } - - if (scope.type === 'preformatted') { - editorElem.on('keydown keyup click', () => { - scope.$apply(() => { - angular.extend(scope.cursor, getLineColumn()); - }); - }); - } - - scope.$on('$destroy', () => { - scope.medium.destroy(); - editorElem.off(); - }); - scope.cursor = {}; - render(null, null, true); - }; - - scope.removeBlock = function() { - sdTextEditor.removeBlock(scope.sdTextEditorBlockText); - }; - - scope.$on('spellcheck:run', render); - scope.$on('key:ctrl:shift:s', render); - - function render($event, event, preventStore) { - if (!$rootScope.config.features || !$rootScope.config.features.useTansaProofing) { - stopTyping(); - editor.renderScope(scope, $event, preventStore); - if (event) { - event.preventDefault(); - } - } - } - - scope.replace = function(text) { - editor.replaceWord(scope, scope.replaceIndex, scope.replaceWord.length, text); - editor.commitScope(scope); - }; - - scope.addWordToDictionary = function() { - var word = scope.replaceTarget.textContent; - - spellcheck.addWordToUserDictionary(word); - editor.render(); - }; - - scope.ignoreWord = function() { - var word = scope.replaceTarget.textContent; - - spellcheck.ignoreWord(word); - editor.render(); - }; - - function changeListener() { - $timeout.cancel(renderTimeout); - renderTimeout = $timeout(render, 0, false); - } - - function startTyping() { - scope.node.parentNode.classList.add(TYPING_CLASS); - } - - function stopTyping() { - scope.node.parentNode.classList.remove(TYPING_CLASS); - } - }, - controller: ['$scope', 'api', 'superdesk', 'renditions', 'send', - function(scope, api, superdesk, renditions, send) { - var self = this; - - angular.extend(self, { - block: undefined, // provided in link method - sdEditorCtrl: undefined, // provided in link method - selectElement: function(element) { - scope.medium.selectElement(element); - // save position - self.savePosition(); - }, - restoreSelection: function() { - scope.medium.importSelection(self.block.caretPosition); - // put the caret at end of the selection - scope.medium.options.ownerDocument.getSelection().collapseToEnd(); - }, - savePosition: function() { - self.block.caretPosition = scope.medium.exportSelection(); - }, - extractEndOfBlock: function() { - // it can happen that user lost the focus on the block when this fct in called - // so we restore the latest known position - self.restoreSelection(); - // extract the text after the cursor - var remainingElementsContainer = document.createElement('div'); - - remainingElementsContainer.appendChild(extractBlockContentsFromCaret().cloneNode(true)); - // remove the first line if empty - $(remainingElementsContainer).find('p:first') - .each(function() { - if ($(this).text() === '') { - this.remove(); - } - }); - return remainingElementsContainer; - }, - updateModel: function() { - editor.commitScope(scope); - }, - insertMedia: function(media) { - const validItems = send.getValidItems([media]); - - if (validItems.length > 0) { - var mediaType = { - picture: 'Image', - graphic: 'Image', - video: 'Video', - }; - var imageBlock = { - blockType: 'embed', - embedType: mediaType[media.type], - caption: media.description_text, - loading: true, - association: media, - }; - - self.sdEditorCtrl.splitAndInsert(self, imageBlock).then((block) => { - // load the media and update the block - $q.when((function() { - if ( - appConfig.features != null - && appConfig.features.editFeaturedImage != null - && !appConfig.features.editFeaturedImage - && media._type === 'externalsource' - ) { - return media; - } - return renditions.ingest(media); - })()).then((mediaObject) => { - editor.generateMediaTag(mediaObject).then((imgTag) => { - angular.extend(block, { - body: imgTag, - association: mediaObject, - loading: false, - }); - $timeout(self.sdEditorCtrl.commitChanges); - }); - }); - }); - } - }, - }); - }], - }; - }]) - .run(['embedService', 'iframelyService', function(embedService, iframelyService) { - // eslint-disable-next-line no-useless-escape - var playBuzzPattern = 'https?:\/\/(?:www)\.playbuzz\.com(.*)$'; - var playBuzzlLoader = '//snappa.embed.pressassociation.io/playbuzz.js'; - var playBuzzEmbed = [ - '', - '
', - ].join(''); - - embedService.registerHandler({ - name: 'PlayBuzz', - patterns: [playBuzzPattern], - embed: function(url) { - return iframelyService.embed(url) - .then((result) => { - result.html = playBuzzEmbed - .replace('$_LOADER', playBuzzlLoader) - .replace('$_URL', url.match(playBuzzPattern)[1]); - return result; - }); - }, - }); - var samdeskEmbed = [ - '', - '
', - ].join(''); - var samDeskPattern = 'https?://embed.samdesk.io/(?:embed|preview)/(.+)'; - - embedService.registerHandler({ - name: 'SAMDesk', - patterns: [samDeskPattern], - embed: function(url) { - var embed = samdeskEmbed.replace('$_ID', url.match(samDeskPattern)[1]); - - return { - provider_name: 'SAMDesk', - html: embed, - url: url, - type: 'rich', - }; - }, - }); - }]) - .config(['embedServiceProvider', 'iframelyServiceProvider', - function(embedServiceProvider, iframelyServiceProvider) { - iframelyServiceProvider.setKey(appConfig.iframely.key); - // don't use noembed as first choice - embedServiceProvider.setConfig('useOnlyFallback', true); - // iframely respect the original embed for more services than 'embedly' - embedServiceProvider.setConfig('fallbackService', 'iframely'); - }]); - -function EditorUtilsFactory() { - var CLONE_CLASS = 'clone'; - var HILITE_CLASS = 'sdhilite'; - var ACTIVE_CLASS = 'sdactive'; - var FINDREPLACE_CLASS = 'sdfindreplace'; - - /** - * Function for sorting array of strings from longest to shortest - * - * @param {string} a - * @param {string} b - * @return {number} - */ - function reverseLengthSort(a, b) { - return b.length - a.length; - } - - /** - * Find text node within given node for given character offset - * - * This will find text node within given node that contains character on given offset - * - * @param {Node} node - * @param {numeric} offset - * @return {Object} {node: {Node}, offset: {numeric}} - */ - function findOffsetNode(node, offset) { - var tree = document.createTreeWalker(node, NodeFilter.SHOW_TEXT); - var currentLength; - var currentOffset = 0; - var ZERO_WIDTH_SPACE = String.fromCharCode(65279); - - while (tree.nextNode()) { - tree.currentNode.textContent = tree.currentNode.textContent.replace(ZERO_WIDTH_SPACE, ''); - currentLength = tree.currentNode.textContent.length; - if (currentOffset + currentLength >= offset) { - return {node: tree.currentNode, offset: offset - currentOffset}; - } - - currentOffset += currentLength; - } - } - - return { - - /** - * Find all matches for current find&replace needle in given node - * - * Each match is {word: {string}, offset: {number}} in given node, - * we can't return nodes here because those will change when we start - * highlighting and offsets wouldn't match - * - * @param {Node} node - * @param {Object} settings - * @return {Array} list of matches - */ - getFindReplaceTokens: function(node, settings) { - var tokens = []; - var diff = settings.findreplace.diff || {}; - var pattern = Object.keys(diff) - .sort(reverseLengthSort) - .map(escapeRegExp) - .join('|'); - - if (!pattern) { - return tokens; - } - - var flags = settings.findreplace.caseSensitive ? 'm' : 'im'; - var re = new RegExp(pattern, flags); - var nodeOffset = 0; - var text, match, offset; - var isActive, elementClone, elementFindReplace; - - elementClone = node.parentNode.getElementsByClassName(CLONE_CLASS); - - if (elementClone && elementClone.length) { - elementFindReplace = elementClone.item(0).getElementsByClassName(FINDREPLACE_CLASS); - } - - function isTokenActive(index) { - var active, matched; - - if (elementFindReplace && elementFindReplace.length) { - matched = _.filter(elementFindReplace, - (elem) => parseInt(elem.getAttribute('data-index'), 10) === index); - - active = matched && matched.length ? matched[0].classList.contains(ACTIVE_CLASS) : false; - } - return active; - } - - var tree = document.createTreeWalker(node, NodeFilter.SHOW_TEXT); - - while (tree.nextNode()) { - text = tree.currentNode.textContent; - - while (!_.isNil(match = text.match(re))) { - isActive = isTokenActive(nodeOffset + match.index); - - tokens.push({ - word: match[0], - index: nodeOffset + match.index, - title: diff[match[0]] || '', - active: isActive, - }); - - offset = match.index + match[0].length; - text = text.substr(offset); - nodeOffset += offset; - } - nodeOffset += text.length; - } - - return tokens; - }, - - /** - * Hilite all tokens within node using span with given className - * - * This first stores caret position, updates markup, and then restores the caret. - * - * @param {Node} node - * @param {Array} tokens - * @param {string} className - * @param {Boolean} preventStore - */ - hilite: function(node, tokens, className, preventStore) { - // remove old hilites - this.removeHilites(node); - - // create a clone - var hiliteNode = node.cloneNode(true); - - hiliteNode.classList.add(CLONE_CLASS); - - // generate hilite markup in clone - tokens.forEach((token) => { - this.hiliteToken(hiliteNode, token, className); - }); - - // render clone - node.parentNode.appendChild(hiliteNode); - }, - - /** - * Highlight single `token` via putting it into a span with given class - * - * @param {Node} node - * @param {Object} token - * @param {string} className - */ - hiliteToken: function(node, token, className) { - var start = this.findWordNode(node, token.index, token.word.length); - var replace = start.node.splitText(start.offset); - var span = document.createElement('span'); - - span.classList.add(className); - span.classList.add(HILITE_CLASS); - - if (token.active) { - span.classList.add(ACTIVE_CLASS); - } - - replace.splitText(token.word.length); - span.textContent = replace.textContent; - span.dataset.word = token.word; - span.dataset.index = token.index; - if (token.sentenceWord) { - span.dataset.sentenceWord = 'true'; - span.classList.add('sdCapitalize'); - } - replace.parentNode.replaceChild(span, replace); - }, - - /** - * Remove hilites node from nodes parent - * - * @param {Node} node - */ - removeHilites: function(node) { - var parentNode = node.parentNode; - var clones = parentNode.getElementsByClassName(CLONE_CLASS); - - if (clones.length) { - parentNode.removeChild(clones.item(0)); - } - }, - - /** - * Find text node within given node where word is located - * - * It will find text type node where the whole word is located - * - * @param {Node} node - * @param {Number} index - * @param {Number} length - * @return {Object} {node: {Node}, offset: {Number}} - */ - findWordNode: function(node, index, length) { - var start = findOffsetNode(node, index); - var end = findOffsetNode(node, index + length); - - // correction for linebreaks - first node on a new line is set to - // linebreak text node which is not even visible in dom, maybe dom bug? - if (start.node !== end.node) { - start.node = end.node; - start.offset = 0; - } - - return start; - }, - }; -} - -angular.module('superdesk.apps.editor2.utils', []) - .factory('editorUtils', EditorUtilsFactory); diff --git a/scripts/core/editor2/embedCodeHandlers.spec.ts b/scripts/core/editor2/embedCodeHandlers.spec.ts deleted file mode 100644 index efad3f1a7b..0000000000 --- a/scripts/core/editor2/embedCodeHandlers.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {ISuperdeskGlobalConfig} from 'superdesk-api'; -import {appConfig} from 'appConfig'; - -describe('Embed Code Handlers', () => { - var ctrl, scope; - - beforeEach(() => { - const testConfig: Partial = { - server: {url: undefined, ws: undefined}, - iframely: {key: '123'}, - editor: {}, - }; - - Object.assign(appConfig, testConfig); - }); - - beforeEach(window.module('superdesk.apps.editor2')); - beforeEach(window.module('superdesk.apps.vocabularies')); - beforeEach(window.module('superdesk.apps.searchProviders')); - - beforeEach(inject(($controller, $rootScope) => { - var element = angular.element('
'); - - scope = $rootScope.$new(); - ctrl = $controller('SdAddEmbedController', {$scope: scope, $element: element}); - })); - - it('match a twitter url', inject(($rootScope, $q, $httpBackend, embedService) => { - ctrl.input = 'https://twitter.com/letzi83/status/764062125996113921'; - jasmine.createSpy(embedService, 'get').and.returnValue($q.when({ - meta: {site: 'Twitter'}, - html: 'embed', - })); - $httpBackend.whenJSONP(/https:\/\/iframe\.ly\/api\/.*/).respond(400); - $httpBackend.whenGET(/https:\/\/iframe\.ly\/api\/.*/).respond(400); - ctrl.retrieveEmbed().then((d) => { - expect(d).toEqual({ - body: 'embed', - provider: 'Twitter', - }); - }); - $rootScope.$digest(); - })); - - it('match a twitter embed', () => { - ctrl.input = '
' + - ''; - ctrl.retrieveEmbed().then((d) => { - expect(d).toEqual({ - body: ctrl.input, - provider: 'Twitter', - }); - }); - scope.$digest(); - }); - - it('match a vidible embed', inject(($httpBackend) => { - ctrl.input = '
' + - '
'; - // retrieve embed with Vidible disabled - ctrl.retrieveEmbed().then((d) => { - expect(d).toEqual({ - body: ctrl.input, - provider: 'Custom', - }); - }); - scope.$digest(); - - const testConfig: Partial = { - editor: { - ...appConfig.editor, - // retrieve embed with Vidible enabled - vidible: true, - }, - }; - - Object.assign(appConfig, testConfig); - - var apiResponse = { - height: 360, - mimeType: 'video/ogg', - size: 2004079, - type: 'video', - url: 'http://delivery.vidible.tv/video/redirect/56bb4688e4b0b6448ed479dd' + - '?bcid=538612f0e4b00fbb8e898655&w=640&h=360', - width: 640, - }; - - $httpBackend - .expectGET(appConfig.server.url + '/vidible/bcid/538612f0e4b00fbb8e898655/pid/56bb474de4b0568f54a23ed7') - .respond(apiResponse); - ctrl.retrieveEmbed().then((d) => { - expect(d).toEqual({ - body: ctrl.input, - provider: 'Vidible', - association: apiResponse, - }); - }); - $httpBackend.flush(); - scope.$digest(); - })); -}); diff --git a/scripts/core/editor2/embedCodeHandlers.ts b/scripts/core/editor2/embedCodeHandlers.ts deleted file mode 100644 index 4bdfe6a19c..0000000000 --- a/scripts/core/editor2/embedCodeHandlers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {appConfig} from 'appConfig'; - -export default [ - ['EMBED_PROVIDERS', function(EMBED_PROVIDERS) { - return { - pattern: /twitter\.com\/widgets\.js/g, - name: EMBED_PROVIDERS.twitter, - }; - }], - ['EMBED_PROVIDERS', function(EMBED_PROVIDERS) { - return { - pattern: /www\.youtube\.com/g, - name: EMBED_PROVIDERS.youtube, - }; - }], - ['EMBED_PROVIDERS', 'api', function(EMBED_PROVIDERS, api) { - return { - pattern: /src=".*vidible\.tv.*pid=(.+)\/(.+).js/g, - name: EMBED_PROVIDERS.vidible, - condition: () => appConfig.editor != null && appConfig.editor.vidible, - callback: (match) => api.get(`vidible/bcid/${match[2]}/pid/${match[1]}`) - .then((data) => ({association: data})), - }; - }], -]; diff --git a/scripts/core/editor2/index.ts b/scripts/core/editor2/index.ts deleted file mode 100644 index 185742c738..0000000000 --- a/scripts/core/editor2/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @ngdoc module - * @module superdesk.core.editor2 - * @name superdesk.core.editor2 - * @packageName superdesk.core - * @description Superdesk core editor version 2. - */ -import './styles/editor.scss'; - -import './editor'; -import './editor.ctrl'; -import './add-embed.ctrl'; -import './add-content'; diff --git a/scripts/core/editor2/styles/editor.scss b/scripts/core/editor2/styles/editor.scss deleted file mode 100644 index 2a8c51ad10..0000000000 --- a/scripts/core/editor2/styles/editor.scss +++ /dev/null @@ -1,245 +0,0 @@ -@import '~variables.scss'; -@import '~mixins.scss'; -@import "~sf-additional.scss"; - -.editor-type-html { - &:before{ - display: block; /* For Firefox */ - } - &:empty:before{ - content: attr(placeholder); - pointer-events: none; - display: block; /* For Firefox */ - color: rgba(150, 150, 150, 0.5) - } - &:focus:before{ - display: none; - } - // prevent chrome to add extra markup with style in contenteditable - span, p, h1, h2, h3, h4 { - background-color: transparent; - font-family: inherit; - } - - span, h3 { - font-size: inherit; - line-height: 150%; - } - - pre { - font-size: inherit; - line-height: 150%; - - span, h3 { - font-size: inherit; - line-height: 150%; - } - } -} - -.headline { - .editor-type-html { - span { - font-size: inherit; - line-height: 120%; - font-weight: 500; - } - } -} - -.dropdown__menu--suggestions { - font-size: 14px; - font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; -} - -.medium-editor-toolbar { - z-index: 1020; - // disabled state for toolbar button - li .medium-editor-button-disabled { - background-color: gray; - cursor: default; - pointer-events: none; - } -} -.block { - // hide text field on mouse out when empty if not alone - &--not-alone-and-empty { - .text-editor:not(.medium-editor-dragover) { - opacity: 0; - @include transition(opacity .25s); - &:focus, &:hover { - opacity: 1; - } - } - } - &__container { - position: relative; - .text-editor { - padding: 0; - // leave the space for the `add-embed` when the toolbar is shown - margin-top: 6px; - &.medium-editor-dragover, & .medium-editor-dragover { - background: $sd-hover; - border: 2px dashed $sd-blue; - } - } - .preview--caption, .editor--caption { - margin: 0; - border: 1px solid rgba(150, 150, 150, 0.15); - input, select, textarea { - color: inherit; - } - } - .editor--caption [contenteditable] { - padding: 8px; - min-height: 18px; - } - .editor--embed { - min-height: 200px; - } - } - &__actions { - position: absolute; - right: -38px; - top: 0; - cursor: pointer; - opacity: .1; - i { - color: inherit; - } - & > div { - display: inline-block; - } - } - // shows actions on hover - &__container:hover .block__actions {opacity: 1;} -} -.add-content { // (+) button - position: absolute; - z-index: 1; - // translate the button by its dimension - transform: translate(-48px, -12px); - -moz-transform: translate(-48px, -12px); - -wekit-transform: translate(-48px, -12px); - &--expanded { - .add-content__plus-btn { - opacity: 1; - i {transform: rotate(45deg);} - } - .add-content__actions span { - transform: scale(1); - &:hover { - z-index: 1; // for sd-tooltip - } - } - } - &__plus-btn { - position: absolute; - opacity: .7; - &:hover { - opacity: 1; - } - i { - @include transition(transform .25s); - } - } - &__actions { - position: relative; - top: 10px; - left: 50px; - &.ng-hide:not(.ng-hide-animate) { - display: block !important; - position: absolute; - pointer-events: none; - width: 200px; // ensure that inner inline-block is inlined - } - span { - display: inline-block; - transform: scale(0); - @include transition(transform .25s); - &:nth-of-type(2) { - transition-delay: .1s; - -moz-transition-delay: .1s; - -webkit-transition-delay: .1s; - } - border-radius: 24px; - border: 2px solid $sd-blue; - padding: 5px 5px 0px; - background-color: $white; - i {color: $sd-blue;} - &:hover { - cursor: pointer; - background-color: $sd-blue; - i {color: $white;} - } - } - } -} -.add-embed { - &__body { - margin: 10px 0; - padding: 10px; - border: 1px solid $sd-blue; - @include border-radius(3px); - form { - position: relative; - } - } - &__input { - // leave place for action buttons on the sides - margin-right: 100px; - } - // action buttons - &__actions { - // align right - position: absolute; - top: 0; - right: 0; - // centers icons - i { - left: 2px; - position: relative; - } - } -} - -// Embed previews -.block__container .preview--embed, -.add-embed__body .preview { - &--loading { - min-height: 100px; - img { - opacity: .3; - } - &:before { - content: ''; - position: absolute; - top: 50%; - transform: translateY(-50%); - left: 0; - @include spinner-big; - } - } - text-align: center; - margin: 10px 0 0; - .instagram-media { - margin: auto !important; - } - iframe, video { - max-width: 100%; - } - img { - max-width: 100%; - height: auto; - &:hover { - cursor: pointer; - } - } - .embed--link { - &__title { - font-size: 1.75em; - line-height: 1.25em; - margin-bottom: .50em; - } - } -} diff --git a/scripts/core/editor2/views/add-content.html b/scripts/core/editor2/views/add-content.html deleted file mode 100644 index 6fb90bde61..0000000000 --- a/scripts/core/editor2/views/add-content.html +++ /dev/null @@ -1,25 +0,0 @@ -
- -
- - - - - - - - - -
-
diff --git a/scripts/core/editor2/views/add-embed.html b/scripts/core/editor2/views/add-embed.html deleted file mode 100644 index 03cc4c1cc6..0000000000 --- a/scripts/core/editor2/views/add-embed.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
- -
-
- - -
-
{{:: 'loading' | translate }} ...
-
-
-
diff --git a/scripts/core/editor2/views/block-embed.html b/scripts/core/editor2/views/block-embed.html deleted file mode 100644 index 3c4c3229f7..0000000000 --- a/scripts/core/editor2/views/block-embed.html +++ /dev/null @@ -1,37 +0,0 @@ -
- - Cancel - Save -
-
-
-
- - -
-
- - -
diff --git a/scripts/core/editor2/views/block-text.html b/scripts/core/editor2/views/block-text.html deleted file mode 100644 index 12bdcd99ad..0000000000 --- a/scripts/core/editor2/views/block-text.html +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/scripts/core/editor2/views/editor.html b/scripts/core/editor2/views/editor.html deleted file mode 100644 index 5ea50f5dea..0000000000 --- a/scripts/core/editor2/views/editor.html +++ /dev/null @@ -1,45 +0,0 @@ -
- -
-
- -
-
- -
-
- - - - -
-
-
-
-
-
- - - -
diff --git a/scripts/core/editor3/index.ts b/scripts/core/editor3/index.ts index 2108cf11b0..7e4a8e7039 100644 --- a/scripts/core/editor3/index.ts +++ b/scripts/core/editor3/index.ts @@ -2,9 +2,6 @@ import './styles.scss'; import {EditorService} from './service'; import {sdEditor3} from './directive'; -import ng from 'core/services/ng'; -import {appConfig} from 'appConfig'; - /** * @ngdoc module * @module superdesk.core.editor3 @@ -14,14 +11,9 @@ import {appConfig} from 'appConfig'; */ export default angular.module('superdesk.core.editor3', ['superdesk.apps.spellcheck']) .service('editor3', EditorService) - .service('editorResolver', ['editor', 'editor3', function(editor2, editor3) { - // Enables the use of editor2 and editor3 in parallel. - // Resolves to old editor in case editor3 is inactive. + .service('editorResolver', ['editor3', function(editor3) { this.get = function() { - const authoring = ng.get('authoring'); - const editor3Active = authoring.editor && authoring.editor.body_html && authoring.editor.body_html.editor3; - - return appConfig.features.onlyEditor3 || editor3Active ? editor3 : editor2; + return editor3; }; }]) .directive('sdEditor3', sdEditor3); diff --git a/scripts/core/editor3/service.ts b/scripts/core/editor3/service.ts index 12e01b2aa1..34623b45f2 100644 --- a/scripts/core/editor3/service.ts +++ b/scripts/core/editor3/service.ts @@ -26,8 +26,7 @@ const spellcheckerStores = []; * @module superdesk.core.editor3 * @name editor3 * @description editor3 is the service that allows interacting with the editor from - * the outside. It uses the same interface as the editor service of core/editor2/editor.js - * to allow plugging one or the other based on the editor of the item being edited. + * the outside. */ export class EditorService { /** diff --git a/scripts/core/index.ts b/scripts/core/index.ts index 6d6353f140..ada8c3f6c3 100644 --- a/scripts/core/index.ts +++ b/scripts/core/index.ts @@ -13,7 +13,6 @@ import 'core/elastic'; import 'core/filters'; import 'core/services'; import 'core/directives'; -import 'core/editor2'; import 'core/spellcheck'; import 'core/editor3'; import 'core/features'; diff --git a/scripts/core/spellcheck/spellcheck.spec.ts b/scripts/core/spellcheck/spellcheck.spec.ts index 0b90131f81..caeec2c0e7 100644 --- a/scripts/core/spellcheck/spellcheck.spec.ts +++ b/scripts/core/spellcheck/spellcheck.spec.ts @@ -31,7 +31,6 @@ describe('spellcheck', () => { Object.assign(appConfig, testConfig); }); - beforeEach(window.module('superdesk.apps.editor2')); beforeEach(window.module('superdesk.core.editor3')); beforeEach(window.module('superdesk.apps.spellcheck')); beforeEach(window.module('superdesk.core.preferences')); diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 4946c26720..921549e1e1 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -1925,7 +1925,6 @@ declare module 'superdesk-api' { hideRoutedDesks?: any; autorefreshContent?: any; elasticHighlight?: any; - onlyEditor3?: any; nestedItemsInOutputStage?: boolean; keepMetaTermsOpenedOnClick?: boolean; showCharacterLimit?: number; diff --git a/scripts/index.ts b/scripts/index.ts index c1224f5834..28ef2748ef 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -6,8 +6,6 @@ import 'app.scss'; // styles import 'jquery-jcrop/css/jquery.Jcrop.css'; import 'jquery-ui/themes/smoothness/jquery-ui.css'; -import 'medium-editor/dist/css/medium-editor.css'; -import 'medium-editor-tables/dist/css/medium-editor-tables.css'; import 'owl.carousel/dist/assets/owl.carousel.min.css'; import 'superdesk-ui-framework/dist/superdesk-ui.bundle.css'; diff --git a/scripts/vendor.ts b/scripts/vendor.ts index 48a3fbfd6f..152b8aabe0 100644 --- a/scripts/vendor.ts +++ b/scripts/vendor.ts @@ -8,8 +8,6 @@ import 'angular'; import 'angular-resource'; import 'angular-route'; import 'angular-gettext'; -import 'angular-embedly'; -import 'angular-embed'; import 'angular-contenteditable'; import 'angular-vs-repeat'; import 'angular-moment'; diff --git a/styles/sass/app.scss b/styles/sass/app.scss index 8ac5be71ab..d46d3694c0 100644 --- a/styles/sass/app.scss +++ b/styles/sass/app.scss @@ -52,7 +52,6 @@ // Components: Misc @import 'labels'; -@import 'medium-editor'; // custom skin for medium-editor @import 'sf-additional'; @import 'sf-icons'; @import 'search-list'; diff --git a/styles/sass/medium-editor.scss b/styles/sass/medium-editor.scss deleted file mode 100644 index 85a5b34a5b..0000000000 --- a/styles/sass/medium-editor.scss +++ /dev/null @@ -1,274 +0,0 @@ -@-webkit-keyframes medium-editor-image-loading { - 0% { - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - -webkit-transform: scale(1); - transform: scale(1); } } - -@keyframes medium-editor-image-loading { - 0% { - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - -webkit-transform: scale(1); - transform: scale(1); } } - -@-webkit-keyframes medium-editor-pop-upwards { - 0% { - opacity: 0; - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); } - 20% { - opacity: .7; - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); } - 40% { - opacity: 1; - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); } - 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); } } - -@keyframes medium-editor-pop-upwards { - 0% { - opacity: 0; - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); } - 20% { - opacity: .7; - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); } - 40% { - opacity: 1; - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); } - 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); } } - -.medium-editor-anchor-preview { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - left: 0; - line-height: 1.4; - max-width: 280px; - position: absolute; - text-align: center; - top: 0; - word-break: break-all; - word-wrap: break-word; - visibility: hidden; - z-index: 2000; } - .medium-editor-anchor-preview a { - color: $white; - display: inline-block; - margin: 5px 5px 10px; } - -.medium-editor-anchor-preview-active { - visibility: visible; } - -.medium-editor-dragover { - background: #ddd; } - -.medium-editor-image-loading { - -webkit-animation: medium-editor-image-loading 1s infinite ease-in-out; - animation: medium-editor-image-loading 1s infinite ease-in-out; - background-color: #333; - border-radius: 100%; - display: inline-block; - height: 40px; - width: 40px; } - -.medium-editor-placeholder { - position: relative; } - .medium-editor-placeholder:after { - content: attr(data-placeholder) !important; - font-style: italic; - left: 0; - position: absolute; - top: 0; - white-space: pre; - padding: inherit; - margin: inherit; } - -.medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before { - border-style: solid; - content: ''; - display: block; - height: 0; - left: 50%; - margin-left: -8px; - position: absolute; - width: 0; } - -.medium-toolbar-arrow-under:after { - border-width: 8px 8px 0 8px; } - -.medium-toolbar-arrow-over:before { - border-width: 0 8px 8px 8px; - top: -8px; } - -.medium-editor-toolbar { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - left: 0; - position: absolute; - top: 0; - visibility: hidden; - z-index: 9999; } - .medium-editor-toolbar ul { - margin: 0; - padding: 0; } - .medium-editor-toolbar li { - float: left; - list-style: none; - margin: 0; - padding: 0; } - .medium-editor-toolbar li button { - box-sizing: border-box; - cursor: pointer; - display: block; - font-size: 14px; - line-height: 1; - margin: 0; - padding: 15px; - text-decoration: none; } - .medium-editor-toolbar li button:focus { - outline: none; } - .medium-editor-toolbar li .medium-editor-action-underline { - text-decoration: underline; } - .medium-editor-toolbar li .medium-editor-action-pre { - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - font-weight: 100; - padding: 15px 0; } - -.medium-editor-toolbar-active { - visibility: visible; } - -.medium-editor-sticky-toolbar { - position: fixed; - top: 1px; } - -.medium-editor-relative-toolbar { - position: relative; } - -.medium-editor-toolbar-active.medium-editor-stalker-toolbar { - -webkit-animation: medium-editor-pop-upwards 160ms forwards linear; - animation: medium-editor-pop-upwards 160ms forwards linear; } - -.medium-editor-action-bold { - font-weight: bolder; } - -.medium-editor-action-italic { - font-style: italic; } - -.medium-editor-toolbar-form { - display: none; } - .medium-editor-toolbar-form input, - .medium-editor-toolbar-form a { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } - .medium-editor-toolbar-form .medium-editor-toolbar-form-row { - line-height: 14px; - margin-left: 5px; - padding-bottom: 5px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input, - .medium-editor-toolbar-form label { - border: none; - box-sizing: border-box; - font-size: 14px; - margin: 0; - padding: 6px; - width: 316px; - display: inline-block; } - .medium-editor-toolbar-form .medium-editor-toolbar-input:focus, - .medium-editor-toolbar-form label:focus { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border: none; - box-shadow: none; - outline: 0; } - .medium-editor-toolbar-form a { - display: inline-block; - font-size: 24px; - font-weight: bolder; - margin: 0 10px; - text-decoration: none; } - -.medium-editor-toolbar-actions:after { - clear: both; - content: ""; - display: table; } - -[data-medium-editor-element] img { - max-width: 100%; } - -[data-medium-editor-element] sub { - vertical-align: sub; } - -[data-medium-editor-element] sup { - vertical-align: super; } - -.medium-editor-hidden { - display: none; } -.medium-toolbar-arrow-under:after { -top: 60px; -border-color: $sd-blue transparent transparent transparent; } - -.medium-toolbar-arrow-over:before { -top: -8px; -border-color: transparent transparent $sd-blue transparent; } - -.medium-editor-toolbar { -background-color: $sd-blue; } -.medium-editor-toolbar li { - padding: 0; } - .medium-editor-toolbar li button { - min-width: 60px; - height: 60px; - border: none; - border-right: 1px solid $sd-background; - background-color: transparent; - color: $white; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: darken($sd-blue, 20%); - color: $white; } - .medium-editor-toolbar li .medium-editor-button-active { - background-color: darken($sd-blue, 30%); - color: $white; } - .medium-editor-toolbar li .medium-editor-button-last { - border-right: none; } - -.medium-editor-toolbar-form .medium-editor-toolbar-input { -height: 60px; -background: $sd-blue; -color: $white; } -.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { - color: $white; - color: rgba(255, 255, 255, 0.8); } -.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder { - /* Firefox 18- */ - color: $white; - color: rgba(255, 255, 255, 0.8); } -.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder { - /* Firefox 19+ */ - color: $white; - color: rgba(255, 255, 255, 0.8); } -.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder { - color: $white; - color: rgba(255, 255, 255, 0.8); } - -.medium-editor-toolbar-form a { -color: $white; } - -.medium-editor-toolbar-anchor-preview { -background: $sd-blue; -color: $white; } - -.medium-editor-placeholder:after { -color: $white; } diff --git a/webpack.config.js b/webpack.config.js index a913a6115d..bd84a37f32 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -46,9 +46,6 @@ module.exports = function makeConfig(grunt) { jQuery: jQueryModule, 'window.jQuery': jQueryModule, moment: 'moment', - // MediumEditor needs to be globally available, because - // its plugins will not be able to find it otherwise. - MediumEditor: 'medium-editor', }), new webpack.DefinePlugin({ __SUPERDESK_CONFIG__: JSON.stringify(sdConfig), @@ -68,7 +65,6 @@ module.exports = function makeConfig(grunt) { alias: { 'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022', 'rangy-saverestore': 'rangy/lib/rangy-selectionsaverestore', - 'angular-embedly': 'angular-embedly/em-minified/angular-embedly.min', 'jquery-gridster': 'gridster/dist/jquery.gridster.min', 'external-apps': path.join(process.cwd(), 'dist', 'app-importer.generated.js'), }, @@ -231,9 +227,6 @@ function getDefaults(grunt) { features: { // tansa spellchecker useTansaProofing: false, - - // replace editor2 - onlyEditor3: false, }, // workspace defaults From 56fc4656e96a58da7214f90e57fb7ffd7040f9cd Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Mon, 25 Oct 2021 11:48:40 +0200 Subject: [PATCH 0085/1070] restore angular embed --- package-lock.json | 2463 ++++++++++++++++- package.json | 2 + scripts/apps/authoring/authoring/index.ts | 11 +- scripts/apps/authoring/macros/macros.spec.ts | 1 + .../apps/authoring/tests/authoring.spec.ts | 1 + scripts/vendor.ts | 2 + webpack.config.js | 1 + 7 files changed, 2356 insertions(+), 125 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b24f524d3..dfb25cfd32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "requires": { "@babel/highlight": "^7.14.5" } @@ -61,9 +61,9 @@ } }, "@babel/parser": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", - "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==" + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==" }, "@babel/runtime": { "version": "7.15.4", @@ -351,9 +351,9 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" }, "@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { "@types/minimatch": "*", @@ -404,9 +404,9 @@ } }, "@types/node": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", - "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" + "version": "16.11.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.3.tgz", + "integrity": "sha512-aIYL9Eemcecs1y77XzFGiSc+FdfN58k4J23UEe6+hynf4Wd9g4DzQPwIKL080vSMuubFqy2hWwOzCtJdc6vFKw==" }, "@types/parse5": { "version": "5.0.3", @@ -456,9 +456,9 @@ } }, "@types/webpack-env": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.2.tgz", - "integrity": "sha512-vKx7WNQNZDyJveYcHAm9ZxhqSGLYwoyLhrHjLBOkw3a7cT76sTdjgtwyijhk1MaHyRIuSztcVwrUOO/NEu68Dw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz", + "integrity": "sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw==", "dev": true }, "@typescript-eslint/experimental-utils": { @@ -540,6 +540,14 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" }, + "active-x-obfuscator": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/active-x-obfuscator/-/active-x-obfuscator-0.0.1.tgz", + "integrity": "sha1-CJuJs3FF/x2ex0r2UwvlUmyuHxo=", + "requires": { + "zeparser": "0.0.5" + } + }, "addressparser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", @@ -688,6 +696,108 @@ "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.32.tgz", "integrity": "sha1-0H/qdSgfJbqUpb5bRt8qAsXjWDY=" }, + "angular-embed": { + "version": "github:superdesk/angular-embed#d75968e9eedc2b255f68d0c298a6c91981bc9444", + "from": "github:superdesk/angular-embed#d75968e" + }, + "angular-embedly": { + "version": "github:Urigo/angular-embedly#e48ff27af8bd702f1cefea6119c863df9b0e6042", + "from": "github:Urigo/angular-embedly#0.0.8", + "requires": { + "gulp": "~3.8.2", + "gulp-concat": "~2.2.0", + "gulp-uglify": "~0.3.1", + "karma": "~0.12.16", + "karma-chrome-launcher": "~0.1.4", + "karma-jasmine": "~0.1.5" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "karma": { + "version": "0.12.37", + "resolved": "https://registry.npmjs.org/karma/-/karma-0.12.37.tgz", + "integrity": "sha1-Gp9/3szWneLoNeBO26wuzT+mReQ=", + "requires": { + "chokidar": "^1.0.1", + "colors": "^1.1.0", + "connect": "^2.29.2", + "di": "^0.0.1", + "glob": "^5.0.6", + "graceful-fs": "^3.0.6", + "http-proxy": "^0.10", + "lodash": "^3.8.0", + "log4js": "^0.6.25", + "mime": "^1.3.4", + "minimatch": "^2.0.7", + "optimist": "^0.6.1", + "q": "^1.4.1", + "rimraf": "^2.3.3", + "socket.io": "0.9.16", + "source-map": "^0.4.2", + "useragent": "^2.1.6" + } + }, + "karma-chrome-launcher": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-0.1.12.tgz", + "integrity": "sha1-CsDiLlc2UPZUExL9ynlcOCTM+WI=", + "requires": { + "which": "^1.0.9" + } + }, + "karma-jasmine": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-0.1.6.tgz", + "integrity": "sha1-MFRQV2mOvcvGMTLUe+Elt1svvFU=" + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, "angular-gettext": { "version": "github:tomaskikutis/angular-gettext#5615f0058ecf7f4aeeb7ec1a24e2cff4426fffa6", "from": "github:tomaskikutis/angular-gettext#master" @@ -749,16 +859,135 @@ "type-fest": "^0.21.3" } }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" }, + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=" + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + } + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -825,6 +1054,11 @@ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -847,6 +1081,11 @@ "is-string": "^1.0.7" } }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1035,6 +1274,11 @@ "postcss-value-parser": "^3.2.3" } }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1225,6 +1469,26 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", + "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=" + }, + "base64id": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz", + "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8=" + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + }, + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" + }, "batch": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz", @@ -1238,6 +1502,11 @@ "tweetnacl": "^0.14.3" } }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" + }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -1368,6 +1637,38 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, + "body-parser": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", + "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=", + "requires": { + "bytes": "2.1.0", + "content-type": "~1.0.1", + "debug": "~2.2.0", + "depd": "~1.0.1", + "http-errors": "~1.3.1", + "iconv-lite": "0.4.11", + "on-finished": "~2.3.0", + "qs": "4.0.0", + "raw-body": "~2.1.2", + "type-is": "~1.6.6" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -1748,9 +2049,9 @@ } }, "caniuse-db": { - "version": "1.0.30001264", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001264.tgz", - "integrity": "sha512-KwFcCSAaHNukq2lVN/rkxbagGmQKHNwBHZj5ThnEP0bqAIJdIk9pULCbkL9rs0Nf4RSI0zTzl4fmsTg1Pp0Jdg==" + "version": "1.0.30001271", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001271.tgz", + "integrity": "sha512-V7/O06sNu6e54ArdzLA1xFuYJ+kK9XXsO4pYa53ixujAVX/OVCGvDfRICSgkxdk8pdMZ+tA1v5ylLKDHNwUqEg==" }, "caseless": { "version": "0.12.0", @@ -1766,6 +2067,18 @@ "lazy-cache": "^1.0.3" } }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + } + }, "change-case": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.0.2.tgz", @@ -1879,9 +2192,9 @@ } }, "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", + "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", "dev": true }, "dom-serializer": { @@ -1938,6 +2251,37 @@ } } }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -2120,6 +2464,11 @@ } } }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -2188,6 +2537,11 @@ "color-name": "^1.0.0" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colormin": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", @@ -2201,8 +2555,7 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "combine-lists": { "version": "1.0.1", @@ -2221,6 +2574,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=" + }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -2323,11 +2681,90 @@ } } }, + "connect": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.30.2.tgz", + "integrity": "sha1-jam8vooFTT0xjXTf7JA7XDmhtgk=", + "requires": { + "basic-auth-connect": "1.0.0", + "body-parser": "~1.13.3", + "bytes": "2.1.0", + "compression": "~1.5.2", + "connect-timeout": "~1.6.2", + "content-type": "~1.0.1", + "cookie": "0.1.3", + "cookie-parser": "~1.3.5", + "cookie-signature": "1.0.6", + "csurf": "~1.8.3", + "debug": "~2.2.0", + "depd": "~1.0.1", + "errorhandler": "~1.4.2", + "express-session": "~1.11.3", + "finalhandler": "0.4.0", + "fresh": "0.3.0", + "http-errors": "~1.3.1", + "method-override": "~2.3.5", + "morgan": "~1.6.1", + "multiparty": "3.3.2", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "pause": "0.1.0", + "qs": "4.0.0", + "response-time": "~2.3.1", + "serve-favicon": "~2.3.0", + "serve-index": "~1.7.2", + "serve-static": "~1.10.0", + "type-is": "~1.6.6", + "utils-merge": "1.0.0", + "vhost": "~3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" }, + "connect-timeout": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.6.2.tgz", + "integrity": "sha1-3ppexh4zoStu2qt7XwYumMWZuI4=", + "requires": { + "debug": "~2.2.0", + "http-errors": "~1.3.1", + "ms": "0.7.1", + "on-headers": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -2373,6 +2810,20 @@ "safe-buffer": "~5.1.1" } }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -2393,6 +2844,11 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "crc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz", + "integrity": "sha1-+mIuG8OIvyVzCQgta2UgDOZwkLo=" + }, "create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -2515,6 +2971,16 @@ } } }, + "csrf": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", + "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.5", + "uid-safe": "2.1.4" + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -2644,6 +3110,17 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" }, + "csurf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", + "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "csrf": "~3.0.0", + "http-errors": "~1.3.1" + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -2698,6 +3175,11 @@ "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", "dev": true }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -2721,6 +3203,35 @@ "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" }, + "deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "requires": { + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "deep-for-each": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-1.0.6.tgz", @@ -2734,6 +3245,19 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, + "deepmerge": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-0.2.10.tgz", + "integrity": "sha1-iQa/nlJaT78bIDsq/LRkAkmCEhk=" + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2835,6 +3359,16 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "deprecated": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", + "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=" + }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -2849,6 +3383,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, "detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -2857,8 +3396,7 @@ "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=" }, "diff": { "version": "3.5.0", @@ -3032,6 +3570,14 @@ "resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.0.tgz", "integrity": "sha512-8s9FFuKC+lOWGwJ0b3om2PF+uXrqQPaEQlPJI7UxdzxTYGMeKouMPA9+YlPn52zcAVElIZtd2tXj6eQmvlKelw==" }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "requires": { + "readable-stream": "~1.1.9" + } + }, "each-async": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/each-async/-/each-async-0.1.3.tgz", @@ -3070,9 +3616,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.859", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.859.tgz", - "integrity": "sha512-gXRXKNWedfdiKIzwr0Mg/VGCvxXzy+4SuK9hp1BDvfbCwx0O5Ot+2f4CoqQkqEJ3Zj/eAV/GoAFgBVFgkBLXuQ==" + "version": "1.3.877", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.877.tgz", + "integrity": "sha512-fT5mW5Giw5iyVukeHb2XvB4joBKvzHtl8Vs3QzE7APATpFMt/T7RWyUcIKSZzYkKQgpMbu+vDBTCHfQZvh8klA==" }, "elliptic": { "version": "6.5.4", @@ -3128,6 +3674,24 @@ } } }, + "end-of-stream": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", + "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "requires": { + "once": "~1.3.0" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "requires": { + "wrappy": "1" + } + } + } + }, "engine.io": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", @@ -3525,6 +4089,31 @@ "is-arrayish": "^0.2.1" } }, + "errorhandler": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", + "integrity": "sha1-t7cO2PNZ6duICS8tIMD4MUIK2D8=", + "requires": { + "accepts": "~1.3.0", + "escape-html": "~1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + } + } + }, "es-abstract": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", @@ -3558,6 +4147,28 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -3963,6 +4574,11 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -4196,6 +4812,14 @@ } } }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -4417,6 +5041,45 @@ } } }, + "express-session": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz", + "integrity": "sha1-XMmPP1/4Ttg1+Ry/CqvQxxB0AK8=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "crc": "3.3.0", + "debug": "~2.2.0", + "depd": "~1.0.1", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "uid-safe": "~2.0.0", + "utils-merge": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "uid-safe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz", + "integrity": "sha1-p/PGymSh9qXQTsDvPkw9U2cxcTc=", + "requires": { + "base64-url": "1.2.1" + } + } + } + }, "ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", @@ -4571,6 +5234,17 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4699,6 +5373,42 @@ } } }, + "finalhandler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", + "requires": { + "debug": "~2.2.0", + "escape-html": "1.0.2", + "on-finished": "~2.3.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -4734,6 +5444,49 @@ } } }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=" + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -4782,6 +5535,11 @@ "for-in": "^1.0.1" } }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -4810,6 +5568,11 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, "fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", @@ -4841,6 +5604,24 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "dependencies": { + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true + } + } + }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -4951,6 +5732,14 @@ } } }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "requires": { + "globule": "~0.1.0" + } + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -5173,16 +5962,108 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "requires": { + "glob": "^4.3.1", + "glob2base": "^0.0.12", + "minimatch": "^2.0.1", + "ordered-read-streams": "^0.1.0", + "through2": "^0.6.1", + "unique-stream": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "requires": { - "is-extglob": "^1.0.0" + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } } } }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "requires": { + "gaze": "^0.5.1" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "requires": { + "find-index": "^0.1.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -5207,6 +6088,68 @@ } } }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "requires": { + "glob": "~3.1.21", + "lodash": "~1.0.1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" + }, + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=" + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "requires": { + "natives": "^1.1.3" + } + }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", @@ -5695,6 +6638,265 @@ "lodash": "^4.7.0" } }, + "gulp": { + "version": "3.8.11", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.8.11.tgz", + "integrity": "sha1-1Vfgpyg+tBNkkZabBJd2eXLx0oo=", + "requires": { + "archy": "^1.0.0", + "chalk": "^0.5.0", + "deprecated": "^0.0.1", + "gulp-util": "^3.0.0", + "interpret": "^0.3.2", + "liftoff": "^2.0.1", + "minimist": "^1.1.0", + "orchestrator": "^0.3.0", + "pretty-hrtime": "^0.2.0", + "semver": "^4.1.0", + "tildify": "^1.0.0", + "v8flags": "^2.0.2", + "vinyl-fs": "^0.3.0" + }, + "dependencies": { + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + } + } + }, + "gulp-concat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.2.0.tgz", + "integrity": "sha1-7vw+FYDEzKxkOAbSBnwyK0QaY0Y=", + "requires": { + "gulp-util": "^2.2.5", + "through": "^2.3.4" + }, + "dependencies": { + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "requires": { + "chalk": "^0.5.0", + "dateformat": "^1.0.7-1.2.3", + "lodash._reinterpolate": "^2.4.1", + "lodash.template": "^2.4.1", + "minimist": "^0.2.0", + "multipipe": "^0.1.0", + "through2": "^0.5.0", + "vinyl": "^0.2.1" + } + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=" + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "requires": { + "lodash._escapehtmlchar": "~2.4.1", + "lodash._reunescapedhtml": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "requires": { + "lodash._escapestringchar": "~2.4.1", + "lodash._reinterpolate": "~2.4.1", + "lodash.defaults": "~2.4.1", + "lodash.escape": "~2.4.1", + "lodash.keys": "~2.4.1", + "lodash.templatesettings": "~2.4.1", + "lodash.values": "~2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "requires": { + "lodash._reinterpolate": "~2.4.1", + "lodash.escape": "~2.4.1" + } + }, + "minimist": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", + "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "requires": { + "clone-stats": "~0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=" + } + } + }, + "gulp-uglify": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-0.3.2.tgz", + "integrity": "sha1-+je/bUrZopo0nGy6hiq7Iuumeqw=", + "requires": { + "deepmerge": ">=0.2.7 <0.3.0-0", + "gulp-util": ">=3.0.0 <4.0.0-0", + "through2": ">=0.6.1 <1.0.0-0", + "uglify-js": "2.4.6" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "requires": { + "glogg": "^1.0.0" + } + }, "handle-thing": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", @@ -5722,6 +6924,14 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "requires": { + "ansi-regex": "^0.2.0" + } + }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -5760,6 +6970,14 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "requires": { + "sparkles": "^1.0.0" + } + }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -5923,6 +7141,14 @@ "react-is": "^16.7.0" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hooker": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", @@ -6006,9 +7232,9 @@ }, "dependencies": { "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "requires": { "source-map": "~0.6.0" } @@ -6162,6 +7388,38 @@ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, + "http-proxy": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-0.10.4.tgz", + "integrity": "sha1-FLoM6qIZf4n6MN6p57CeGc2Twi8=", + "requires": { + "colors": "0.x.x", + "optimist": "0.6.x", + "pkginfo": "0.3.x", + "utile": "~0.2.1" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + } + } + }, "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", @@ -6382,6 +7640,16 @@ } } }, + "i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==" + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" + }, "icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", @@ -6531,6 +7799,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -6627,6 +7900,11 @@ "side-channel": "^1.0.4" } }, + "interpret": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.3.10.tgz", + "integrity": "sha1-CIwl3nMcbFsRKpDwBxz69Fnlp7s=" + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6650,6 +7928,15 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", @@ -6731,9 +8018,9 @@ } }, "is-core-module": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", "requires": { "has": "^1.0.3" } @@ -6830,6 +8117,11 @@ "lower-case": "^1.1.0" } }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" + }, "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", @@ -6942,12 +8234,25 @@ "has-tostringtag": "^1.0.0" } }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "requires": { + "is-unc-path": "^1.0.0" + } + }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==" + }, "is-shared-array-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", @@ -6988,11 +8293,31 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", + "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "requires": { + "unc-path-regex": "^0.1.2" + } + }, "is-upper-case": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", @@ -7006,6 +8331,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, "is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -7014,6 +8344,11 @@ "call-bind": "^1.0.0" } }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -7027,9 +8362,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true, - "optional": true + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "isbinaryfile": { "version": "3.0.3", @@ -7406,16 +8739,6 @@ "unpipe": "~1.0.0" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -7814,6 +9137,21 @@ "dev": true, "optional": true }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "requires": { + "extend": "^3.0.0", + "findup-sync": "^2.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, "load-grunt-config": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/load-grunt-config/-/load-grunt-config-0.19.2.tgz", @@ -7950,6 +9288,108 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "requires": { + "lodash._htmlescapes": "~2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "requires": { + "lodash._htmlescapes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", @@ -7970,6 +9410,35 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "requires": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "requires": { + "lodash._root": "^3.0.0" + } + }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", @@ -7991,12 +9460,40 @@ "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -8027,6 +9524,11 @@ "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -8037,6 +9539,31 @@ "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", @@ -8048,6 +9575,53 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "requires": { + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "log4js": { + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "requires": { + "readable-stream": "~1.0.2", + "semver": "~4.3.3" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + } + } + }, "loggly": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz", @@ -8297,6 +9871,11 @@ "lower-case": "^1.1.2" } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, "mailcomposer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", @@ -8355,6 +9934,14 @@ } } }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "requires": { + "kind-of": "^6.0.2" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -8485,6 +10072,37 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "method-override": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", + "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "requires": { + "debug": "2.6.9", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -8639,6 +10257,33 @@ "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", "dev": true }, + "morgan": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", + "requires": { + "basic-auth": "~1.0.3", + "debug": "~2.2.0", + "depd": "~1.0.1", + "on-finished": "~2.3.0", + "on-headers": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8669,11 +10314,33 @@ "minimatch": "^3.0.0" } }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", + "requires": { + "readable-stream": "~1.1.9", + "stream-counter": "~0.2.0" + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "requires": { + "duplexer2": "0.0.2" + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "nan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz", + "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg=" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -8692,6 +10359,11 @@ "to-regex": "^3.0.1" } }, + "natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8705,6 +10377,11 @@ "xml-char-classes": "^1.0.0" } }, + "ncp": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", + "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=" + }, "nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -9247,6 +10924,11 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + }, "object-component": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", @@ -9319,6 +11001,17 @@ "object-keys": "^1.1.1" } }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, "object.entries": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", @@ -9339,6 +11032,15 @@ "es-abstract": "^1.19.1" } }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -9418,6 +11120,14 @@ "is-wsl": "^1.1.0" } }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "requires": { + "wordwrap": "~0.0.2" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -9431,6 +11141,26 @@ "word-wrap": "~1.2.3" } }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "orchestrator": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", + "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "requires": { + "end-of-stream": "~0.1.5", + "sequencify": "~0.0.7", + "stream-consume": "~0.1.0" + } + }, + "ordered-read-streams": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", + "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=" + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -9650,6 +11380,16 @@ "safe-buffer": "^5.1.1" } }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -9684,6 +11424,16 @@ "error-ex": "^1.2.0" } }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -9858,6 +11608,19 @@ } } }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -9880,6 +11643,11 @@ } } }, + "pause": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz", + "integrity": "sha1-68ikqGGf8LioGsFRPDQ0/0af23Q=" + }, "pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -9947,6 +11715,11 @@ "find-up": "^1.0.0" } }, + "pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" + }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -9967,6 +11740,11 @@ "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz", "integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==" }, + "policyfile": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/policyfile/-/policyfile-0.0.4.tgz", + "integrity": "sha1-1rgurZiueeviKOLa9ZAzEeyYLk0=" + }, "popper.js": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz", @@ -10588,6 +12366,11 @@ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, + "pretty-hrtime": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-0.2.2.tgz", + "integrity": "sha1-1P2INR46R0H4Fzr31qS4RvmJXAA=" + }, "primeicons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-2.0.0.tgz", @@ -10804,6 +12587,11 @@ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" + }, "query-string": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", @@ -10860,6 +12648,11 @@ "ret": "~0.1.10" } }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -10909,6 +12702,28 @@ "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.22.3.tgz", "integrity": "sha512-pIzHpAggyTOGJE3ruAKdZNK5qhO4V21kR7lwpdUM875yHpq1cqeGzvs78/RufF3g7NaAvVmMPCbaV9uUhQzJ3A==" }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + } + } + }, "react": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", @@ -11228,8 +13043,6 @@ "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -11316,6 +13129,12 @@ "strip-indent": "^1.0.1" } }, + "redis": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.7.3.tgz", + "integrity": "sha1-7le3pE0l7BWU5ENl2BZfp9HUgRo=", + "optional": true + }, "redis-commands": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", @@ -11479,6 +13298,11 @@ "is-finite": "^1.0.0" } }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -11596,6 +13420,15 @@ } } }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -11616,11 +13449,27 @@ } } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "requires": { + "depd": "~1.1.0", + "on-headers": "~1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + } + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -11660,6 +13509,11 @@ "inherits": "^2.0.1" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -11927,6 +13781,55 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "send": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", + "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", + "requires": { + "debug": "~2.2.0", + "depd": "~1.1.0", + "destroy": "~1.0.4", + "escape-html": "~1.0.3", + "etag": "~1.7.0", + "fresh": "0.3.0", + "http-errors": "~1.3.1", + "mime": "1.3.4", + "ms": "0.7.1", + "on-finished": "~2.3.0", + "range-parser": "~1.0.3", + "statuses": "~1.2.1" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + } + } + }, "sentence-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", @@ -11936,6 +13839,29 @@ "upper-case-first": "^1.1.2" } }, + "sequencify": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", + "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=" + }, + "serve-favicon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.2.tgz", + "integrity": "sha1-3UGeJo3gEqtysxnTN/IQUBP5OB8=", + "requires": { + "etag": "~1.7.0", + "fresh": "0.3.0", + "ms": "0.7.2", + "parseurl": "~1.3.1" + }, + "dependencies": { + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + } + } + }, "serve-index": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.7.3.tgz", @@ -11965,6 +13891,16 @@ } } }, + "serve-static": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", + "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", + "requires": { + "escape-html": "~1.0.3", + "parseurl": "~1.3.1", + "send": "0.13.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -12081,6 +14017,11 @@ "object-inspect": "^1.9.0" } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, "signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", @@ -12272,12 +14213,41 @@ "hoek": "2.x.x" } }, + "socket.io": { + "version": "0.9.16", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-0.9.16.tgz", + "integrity": "sha1-O6sEROSbVfu8FXQk29Qao3WlGnY=", + "requires": { + "base64id": "0.1.0", + "policyfile": "0.0.4", + "redis": "0.7.3", + "socket.io-client": "0.9.16" + } + }, "socket.io-adapter": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", "dev": true }, + "socket.io-client": { + "version": "0.9.16", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-0.9.16.tgz", + "integrity": "sha1-TadRXF53MEHRtCOXBBW8xDDzX8Y=", + "requires": { + "active-x-obfuscator": "0.0.1", + "uglify-js": "1.2.5", + "ws": "0.4.x", + "xmlhttprequest": "1.4.2" + }, + "dependencies": { + "uglify-js": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.5.tgz", + "integrity": "sha1-tULCx29477NLIAsgF3Y0Mw/3ArY=" + } + } + }, "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", @@ -12440,6 +14410,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -12680,6 +14655,19 @@ } } }, + "stream-consume": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==" + }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", + "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", + "requires": { + "readable-stream": "~1.1.8" + } + }, "stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -12836,9 +14824,7 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "stringstream": { "version": "0.0.6", @@ -12847,6 +14833,23 @@ "dev": true, "optional": true }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "strip-bom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", + "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "requires": { + "first-chunk-stream": "^1.0.0", + "is-utf8": "^0.2.0" + } + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -13392,12 +15395,17 @@ }, "dependencies": { "@types/node": { - "version": "14.17.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", - "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==" + "version": "14.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.28.tgz", + "integrity": "sha512-1Nh2wEWN8y9hyC4yTDPccUnRunqNeBEFmmTU+K5FpGZOEixqAzMhUXzrBwajNYCZq5uoGaztaMN9Yy1PDMMB6Q==" } } }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" + }, "svgo": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", @@ -13504,6 +15512,44 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "thunkify": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", @@ -13516,6 +15562,19 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "requires": { + "os-homedir": "^1.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, "timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -13531,6 +15590,11 @@ "dev": true, "optional": true }, + "tinycolor": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz", + "integrity": "sha1-MgtaUtg6u1l42Bo+iH1K77FaYWQ=" + }, "title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", @@ -13752,9 +15816,7 @@ "tsscmp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", - "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=", - "dev": true, - "optional": true + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" }, "tsutils": { "version": "3.21.0", @@ -13847,9 +15909,30 @@ } }, "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" + "version": "0.7.30", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.30.tgz", + "integrity": "sha512-uXEtSresNUlXQ1QL4/3dQORcGv7+J2ookOG2ybA/ga9+HYEXueT2o+8dUJQkpedsyTyCJ6jCCirRcKtdtx1kbg==" + }, + "uglify-js": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.6.tgz", + "integrity": "sha1-MXZqTYIrq/XzLBQJYlHtklkpitM=", + "requires": { + "async": "~0.2.6", + "optimist": "~0.3.5", + "source-map": "~0.1.7", + "uglify-to-browserify": "~1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } }, "uglify-to-browserify": { "version": "1.0.2", @@ -13878,6 +15961,14 @@ } } }, + "uid-safe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", + "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=", + "requires": { + "random-bytes": "~1.0.0" + } + }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -13895,6 +15986,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + }, "uncontrollable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz", @@ -13934,6 +16030,11 @@ "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" }, + "unique-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", + "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -14051,6 +16152,31 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=" + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -14071,6 +16197,24 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "utile": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/utile/-/utile-0.2.1.tgz", + "integrity": "sha1-kwyI6ZCY1iIINMNWy9mncFItkNc=", + "requires": { + "async": "~0.2.9", + "deep-equal": "*", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "0.4.x", + "rimraf": "2.x.x" + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, "uuid": { "version": "8.3.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", @@ -14088,6 +16232,14 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "requires": { + "user-home": "^1.1.1" + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -14124,6 +16276,72 @@ } } }, + "vhost": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", + "integrity": "sha1-L7HezUxGaqiLD5NBrzPcGv8keNU=" + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "requires": { + "defaults": "^1.0.0", + "glob-stream": "^3.1.5", + "glob-watcher": "^0.0.6", + "graceful-fs": "^3.0.0", + "mkdirp": "^0.5.0", + "strip-bom": "^1.0.0", + "through2": "^0.6.1", + "vinyl": "^0.4.0" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + } + } + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -14315,15 +16533,6 @@ "upath": "^1.1.1" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -14718,15 +16927,6 @@ "ms": "^2.1.1" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0" - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -14888,46 +17088,41 @@ "is-symbol": "^1.0.3" } }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, + "which-typed-array": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", + "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.5", + "foreach": "^2.0.5", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.7" + } + }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "window-size": { @@ -14950,8 +17145,7 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" }, "wrap-ansi": { "version": "2.1.0", @@ -15008,11 +17202,27 @@ "mkdirp": "^0.5.1" } }, + "ws": { + "version": "0.4.32", + "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz", + "integrity": "sha1-eHphVEFPPJntg8V3IVOyD+sM7DI=", + "requires": { + "commander": "~2.1.0", + "nan": "~1.0.0", + "options": ">=0.0.5", + "tinycolor": "0.x" + } + }, "xml-char-classes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=" }, + "xmlhttprequest": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.4.2.tgz", + "integrity": "sha1-AUU6HZvtHo8XL2SVu/TIxCYyFQA=" + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", @@ -15080,6 +17290,11 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", "dev": true + }, + "zeparser": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/zeparser/-/zeparser-0.0.5.tgz", + "integrity": "sha1-A3JlYbwmjy5URPVMZlt/1KjAKeI=" } } } diff --git a/package.json b/package.json index c027ce2121..99d7a618d7 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "angular": "1.6.9", "angular-contenteditable": "0.3.9", "angular-dynamic-locale": "0.1.32", + "angular-embed": "github:superdesk/angular-embed#d75968e", + "angular-embedly": "github:Urigo/angular-embedly#0.0.8", "angular-gettext": "github:tomaskikutis/angular-gettext#master", "angular-history": "github:decipherinc/angular-history#v0.8.0", "angular-i18n": "1.6.9", diff --git a/scripts/apps/authoring/authoring/index.ts b/scripts/apps/authoring/authoring/index.ts index f40201d1fb..559697b240 100644 --- a/scripts/apps/authoring/authoring/index.ts +++ b/scripts/apps/authoring/authoring/index.ts @@ -86,6 +86,7 @@ angular.module('superdesk.apps.authoring', [ 'contenteditable', 'decipher.history', 'superdesk.config', + 'angular-embed', mediaModule.name, ]) @@ -423,7 +424,15 @@ angular.module('superdesk.apps.authoring', [ }, }); }]) - .run(['keyboardManager', 'gettext', function(keyboardManager) { + .config(['embedServiceProvider', 'iframelyServiceProvider', + function(embedServiceProvider, iframelyServiceProvider) { + iframelyServiceProvider.setKey(appConfig.iframely.key); + // don't use noembed as first choice + embedServiceProvider.setConfig('useOnlyFallback', true); + // iframely respect the original embed for more services than 'embedly' + embedServiceProvider.setConfig('fallbackService', 'iframely'); + }]) + .run(['keyboardManager', 'embedService', function(keyboardManager) { keyboardManager.register('Authoring', 'ctrl + shift + u', gettext('Unlock current item')); keyboardManager.register('Authoring', 'ctrl + shift + e', gettext('Close current item')); keyboardManager.register('Authoring', 'ctrl + shift + s', gettext('Save current item')); diff --git a/scripts/apps/authoring/macros/macros.spec.ts b/scripts/apps/authoring/macros/macros.spec.ts index d142df5754..eee7e8136f 100644 --- a/scripts/apps/authoring/macros/macros.spec.ts +++ b/scripts/apps/authoring/macros/macros.spec.ts @@ -25,6 +25,7 @@ describe('macros', () => { }, ]; + beforeEach(window.module('angular-embed')); beforeEach(window.module('superdesk.apps.publish')); beforeEach(window.module('superdesk.core.preferences')); beforeEach(window.module('superdesk.apps.archive')); diff --git a/scripts/apps/authoring/tests/authoring.spec.ts b/scripts/apps/authoring/tests/authoring.spec.ts index feb1c28818..66f0c73ed9 100644 --- a/scripts/apps/authoring/tests/authoring.spec.ts +++ b/scripts/apps/authoring/tests/authoring.spec.ts @@ -13,6 +13,7 @@ describe('authoring', () => { $provide.constant('lodash', _); })); + beforeEach(window.module('angular-embed')); beforeEach(window.module('superdesk.apps.publish')); beforeEach(window.module('superdesk.core.preferences')); beforeEach(window.module('superdesk.apps.archive')); diff --git a/scripts/vendor.ts b/scripts/vendor.ts index 152b8aabe0..48a3fbfd6f 100644 --- a/scripts/vendor.ts +++ b/scripts/vendor.ts @@ -8,6 +8,8 @@ import 'angular'; import 'angular-resource'; import 'angular-route'; import 'angular-gettext'; +import 'angular-embedly'; +import 'angular-embed'; import 'angular-contenteditable'; import 'angular-vs-repeat'; import 'angular-moment'; diff --git a/webpack.config.js b/webpack.config.js index bd84a37f32..912bb0b45d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -65,6 +65,7 @@ module.exports = function makeConfig(grunt) { alias: { 'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022', 'rangy-saverestore': 'rangy/lib/rangy-selectionsaverestore', + 'angular-embedly': 'angular-embedly/em-minified/angular-embedly.min', 'jquery-gridster': 'gridster/dist/jquery.gridster.min', 'external-apps': path.join(process.cwd(), 'dist', 'app-importer.generated.js'), }, From 22ac1c498eb2955c14f13a57b4377fbc8d1a0a97 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Mon, 25 Oct 2021 12:27:00 +0200 Subject: [PATCH 0086/1070] remove conditionals checking whether editor3 is enabled --- scripts/apps/authoring/macros/macros.ts | 2 +- scripts/apps/authoring/track-changes/inline-comments.ts | 9 ++++++--- scripts/apps/authoring/track-changes/suggestions.ts | 9 ++++++--- .../content/controllers/ContentProfilesController.ts | 2 -- .../content/directives/ContentProfileSchemaEditor.ts | 5 +---- .../apps/workspace/content/views/profile-settings.html | 3 --- scripts/apps/workspace/content/views/schema-editor.html | 4 ++-- scripts/core/superdesk-api.d.ts | 1 - 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/scripts/apps/authoring/macros/macros.ts b/scripts/apps/authoring/macros/macros.ts index 747f75e90d..ef859d71ed 100644 --- a/scripts/apps/authoring/macros/macros.ts +++ b/scripts/apps/authoring/macros/macros.ts @@ -165,7 +165,7 @@ function MacrosController($scope, macros, desks, autosave, $rootScope, storage, */ $scope.call = function(macro) { const editor = editorResolver.get(); - const isEditor3 = editor.version() !== '2'; + const isEditor3 = editor.version() === '3'; const useReplace = macro.replace_type != null && macro.replace_type !== 'no-replace'; const isSimpleReplace = macro.replace_type === 'simple-replace'; let item = _.extend({}, $scope.origItem, $scope.item); diff --git a/scripts/apps/authoring/track-changes/inline-comments.ts b/scripts/apps/authoring/track-changes/inline-comments.ts index 13b54cf73a..d927361f7c 100644 --- a/scripts/apps/authoring/track-changes/inline-comments.ts +++ b/scripts/apps/authoring/track-changes/inline-comments.ts @@ -155,9 +155,12 @@ angular return new Promise((resolve) => { content.getType(item.profile).then((type) => { - const editor3enabled = get(type, 'editor.body_html.editor3') === true; - - resolve(editor3enabled); + /** + * It used to be checked whether editor3 is enabled, + * but now that editor3 is the only editor + * it is only checked whether content profile has body_html field + */ + resolve(type?.editor?.body_html != null); }); }); }], diff --git a/scripts/apps/authoring/track-changes/suggestions.ts b/scripts/apps/authoring/track-changes/suggestions.ts index d20a679892..4395695810 100644 --- a/scripts/apps/authoring/track-changes/suggestions.ts +++ b/scripts/apps/authoring/track-changes/suggestions.ts @@ -109,9 +109,12 @@ angular return new Promise((resolve) => { content.getType(item.profile).then((type) => { - const editor3enabled = get(type, 'editor.body_html.editor3') === true; - - resolve(editor3enabled); + /** + * It used to be checked whether editor3 is enabled, + * but now that editor3 is the only editor + * it is only checked whether content profile has body_html field + */ + resolve(type?.editor?.body_html != null); }); }); }], diff --git a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts index eada49eb94..b4eb2100f5 100644 --- a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts +++ b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts @@ -33,8 +33,6 @@ export function ContentProfilesController($scope, $location, notify, content, mo }); }; - $scope.withEditor3 = appConfig.features != null && appConfig.features.editor3; - /** * @description Refreshes the list of content profiles by fetching them. * @returns {Promise} diff --git a/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts b/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts index b5cb062ea8..943107b632 100644 --- a/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts +++ b/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts @@ -194,10 +194,7 @@ export function ContentProfileSchemaEditor(vocabularies) { */ scope.hasFormatOptions = (field) => Object.keys(HAS_RICH_FORMATTING_OPTIONS).includes(field) - || ( - scope.model.editor.body_html.editor3 === true - && Object.keys(HAS_PLAINTEXT_FORMATTING_OPTIONS).includes(field) - ) + || Object.keys(HAS_PLAINTEXT_FORMATTING_OPTIONS).includes(field) || hasCustomFieldFormatOptions(field); /** diff --git a/scripts/apps/workspace/content/views/profile-settings.html b/scripts/apps/workspace/content/views/profile-settings.html index 8dc772b74f..4ab89f78e3 100644 --- a/scripts/apps/workspace/content/views/profile-settings.html +++ b/scripts/apps/workspace/content/views/profile-settings.html @@ -107,9 +107,6 @@
-
From 82a0350d67485719ccaea8dea0c74e92f34302cc Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Thu, 4 Nov 2021 12:43:52 +0100 Subject: [PATCH 0120/1070] enable full preview --- scripts/apps/authoring-react/authoring-react.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index e435d584cc..26d32d52fb 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -4,6 +4,7 @@ import {Button, Loader} from 'superdesk-ui-framework'; import {gettext} from 'core/utils'; import {IContentProfileV2, authoringStorage} from './data-layer'; import {AuthoringSection} from './authoring-section'; +import {previewItems} from 'apps/authoring/preview/fullPreviewMultiple'; interface IProps { itemId: IArticle['_id']; @@ -132,6 +133,7 @@ export class AuthoringReact extends React.PureComponent {
From 5853f7aaed9bd3d864436f761b4908cf62292e5d Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Thu, 4 Nov 2021 15:06:52 +0100 Subject: [PATCH 0121/1070] display custom fields --- .../authoring-react/authoring-section.tsx | 31 +++++--- scripts/apps/authoring-react/data-layer.ts | 79 ++++++++++++++----- 2 files changed, 78 insertions(+), 32 deletions(-) diff --git a/scripts/apps/authoring-react/authoring-section.tsx b/scripts/apps/authoring-react/authoring-section.tsx index a6bb3b3fb6..5cec4d6684 100644 --- a/scripts/apps/authoring-react/authoring-section.tsx +++ b/scripts/apps/authoring-react/authoring-section.tsx @@ -3,6 +3,7 @@ import {IFieldsV2} from './data-layer'; import {IArticle} from 'superdesk-api'; import {FieldText} from './fields/field-text'; import {assertNever} from 'core/helpers/typescript-helpers'; +import {getField} from 'apps/fields'; interface IProps { fields: IFieldsV2; @@ -19,16 +20,13 @@ export class AuthoringSection extends React.PureComponent { { fields.map((field) => (
-

{field.id}

+

{field.name}

{(() => { - const {type} = field; - - switch (type) { - case 'text': + if (field.type === 'text') { return ( { this.props.onChange({ ...item, @@ -37,21 +35,28 @@ export class AuthoringSection extends React.PureComponent { }} /> ); + } else if (field.type === 'from-extension') { + const FieldType = getField(field.extension_field_type); - case 'dropdown': return ( - { + { this.props.onChange({ ...item, - [field.id]: valueChanged, + extra: { + ...(item.extra ?? {}), + [field.id]: valueChanged, + }, }); }} + readOnly={false} + config={field.extension_field_config} /> ); - default: - return assertNever(type); + } else { + return assertNever(field); } })()}
diff --git a/scripts/apps/authoring-react/data-layer.ts b/scripts/apps/authoring-react/data-layer.ts index 6b62934707..67d924b256 100644 --- a/scripts/apps/authoring-react/data-layer.ts +++ b/scripts/apps/authoring-react/data-layer.ts @@ -6,20 +6,24 @@ import {dataApi} from 'core/helpers/CrudManager'; import {authoringApiCommon} from 'apps/authoring-bridge/authoring-api-common'; import {generatePatch} from 'core/patch'; import {appConfig} from 'appConfig'; +import {getLabelNameResolver} from 'apps/workspace/helpers/getLabelForFieldId'; interface IFieldBase { id: string; + name: string; } interface IFieldText extends IFieldBase { type: 'text'; } -interface IFieldDropdown extends IFieldBase { - type: 'dropdown'; +interface IFieldFromExtension extends IFieldBase { + type: 'from-extension'; + extension_field_type: string; + extension_field_config: any; } -export type IAuthoringFieldV2 = IFieldText | IFieldDropdown; +export type IAuthoringFieldV2 = IFieldText | IFieldFromExtension; export type IFieldsV2 = OrderedMap; @@ -38,30 +42,67 @@ function getContentProfile(item: IArticle): Promise { let fakeScope: Partial = {}; - /** - * !!! The use of `setupAuthoring` outside of angular is experimental. - * I'm only using for getting some test data on the screen. - */ - return ng.get('content').setupAuthoring(item.profile, fakeScope, item).then(() => { - const {editor} = fakeScope; - const editorOrdered = + return Promise.all([ + getLabelNameResolver(), + ng.get('content').setupAuthoring(item.profile, fakeScope, item), + ]).then((res) => { + const [getLabelForFieldId] = res; + + const {editor, fields} = fakeScope; + + const fieldsOrdered = Object.keys(editor) - .map((key) => ({...editor[key], name: key})) - .sort((a, b) => a.order - b.order); + .map((key) => { + const result: {fieldId: string, editorItem: any} = + { + fieldId: key, + editorItem: editor[key], + }; + + return result; + }) + .sort((a, b) => a.editorItem.order - b.editorItem.order); let headerFields: IFieldsV2 = OrderedMap(); let contentFields: IFieldsV2 = OrderedMap(); - for (const editorItem of editorOrdered) { - const field: IAuthoringFieldV2 = { - id: editorItem.name, - type: 'text', - }; + for (const {fieldId, editorItem} of fieldsOrdered) { + const field = fields.find(({_id}) => _id === fieldId); + + const fieldV2: IAuthoringFieldV2 = (() => { + if (field == null) { + const result: IAuthoringFieldV2 = { + id: fieldId, + name: getLabelForFieldId(fieldId), + type: 'text', + }; + + return result; + } else if (field.field_type === 'custom') { + const result: IAuthoringFieldV2 = { + id: fieldId, + name: getLabelForFieldId(fieldId), + type: 'from-extension', + extension_field_type: field.custom_field_type, + extension_field_config: field.custom_field_config, + }; + + return result; + } else { + const result: IAuthoringFieldV2 = { + id: fieldId, + name: getLabelForFieldId(fieldId), + type: 'text', + }; + + return result; + } + })(); if (editorItem.section === 'header') { - headerFields = headerFields.set(field.id, field); + headerFields = headerFields.set(fieldV2.id, fieldV2); } else if (editorItem.section === 'content') { - contentFields = contentFields.set(field.id, field); + contentFields = contentFields.set(fieldV2.id, fieldV2); } else { throw new Error('invalid section'); } From b7ea2b91cfac53be1266069488739284dfb6442a Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Mon, 8 Nov 2021 13:04:07 +0100 Subject: [PATCH 0122/1070] close authoring - SDESK-6301 --- .../authoring-bridge/authoring-api-common.ts | 38 ++++++ .../apps/authoring-react/authoring-react.tsx | 11 +- scripts/apps/authoring-react/data-layer.ts | 19 ++- .../directives/AuthoringDirective.ts | 20 ++- .../authoring/services/AuthoringService.ts | 46 +++---- .../apps/authoring/tests/authoring.spec.ts | 101 +++------------ .../components/prompt-for-unsaved-changes.tsx | 117 +++++++++++++----- 7 files changed, 189 insertions(+), 163 deletions(-) diff --git a/scripts/apps/authoring-bridge/authoring-api-common.ts b/scripts/apps/authoring-bridge/authoring-api-common.ts index 1ef719c9b3..4805d09276 100644 --- a/scripts/apps/authoring-bridge/authoring-api-common.ts +++ b/scripts/apps/authoring-bridge/authoring-api-common.ts @@ -1,9 +1,21 @@ import {IArticle} from 'superdesk-api'; import {runBeforeUpdateMiddlware, runAfterUpdateEvent} from 'apps/authoring/authoring/services/AuthoringService'; +import {showUnsavedChangesPrompt, IUnsavedChangesActionWithSaving} from 'core/ui/components/prompt-for-unsaved-changes'; +import {assertNever} from 'core/helpers/typescript-helpers'; +import {ITEM_STATE} from 'apps/archive/constants'; +import {isLockedInCurrentSession} from 'core/get-superdesk-api-implementation'; export interface IAuthoringApiCommon { saveBefore(current: IArticle, original: IArticle): Promise; saveAfter(current: IArticle, original: IArticle): void; + closeAuthoring( + original: IArticle, + hasUnsavedChanges: boolean, + save: () => Promise, + unlock: () => Promise, + cancelAutoSave: () => Promise, + doClose: () => void, + ): Promise; } /** @@ -16,4 +28,30 @@ export const authoringApiCommon: IAuthoringApiCommon = { saveAfter: (current, original) => { runAfterUpdateEvent(original, current); }, + closeAuthoring: (original: IArticle, hasUnsavedChanges, save, unlock, cancelAutoSave, doClose) => { + if (!isLockedInCurrentSession(original)) { + return Promise.resolve().then(() => doClose()); + } + + if (hasUnsavedChanges && (original.state !== ITEM_STATE.PUBLISHED && original.state !== ITEM_STATE.CORRECTED)) { + return showUnsavedChangesPrompt(hasUnsavedChanges).then(({action, closePromptFn}) => { + const unlockAndClose = () => unlock().then(() => { + closePromptFn(); + doClose(); + }); + + if (action === IUnsavedChangesActionWithSaving.cancelAction) { + return closePromptFn(); + } else if (action === IUnsavedChangesActionWithSaving.discardChanges) { + return cancelAutoSave().then(() => unlockAndClose()); + } else if (action === IUnsavedChangesActionWithSaving.save) { + return save().then(() => unlockAndClose()); + } else { + assertNever(action); + } + }); + } else { + return unlock().then(() => doClose()); + } + }, }; diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index 26d32d52fb..618d7fcd03 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -140,8 +140,15 @@ export class AuthoringReact extends React.PureComponent { loading: true, }); - authoringStorage.unlockArticle(state.itemOriginal._id).then(() => { - this.props.onClose(); + authoringStorage.closeAuthoring( + state.itemWithChanges, + state.itemOriginal, + () => this.props.onClose(), + ).then(() => { + this.setState({ + ...state, + loading: false, + }); }); }} /> diff --git a/scripts/apps/authoring-react/data-layer.ts b/scripts/apps/authoring-react/data-layer.ts index 67d924b256..bbc589e331 100644 --- a/scripts/apps/authoring-react/data-layer.ts +++ b/scripts/apps/authoring-react/data-layer.ts @@ -121,7 +121,7 @@ function getContentProfile(item: IArticle): Promise { interface IAuthoringStorage { getArticle(id: string): Promise; saveArticle(current: IArticle, original: IArticle): Promise; - unlockArticle(id: string): Promise; + closeAuthoring(current: IArticle, original: IArticle, doClose: () => void): Promise; getContentProfile(item: IArticle): Promise; } @@ -156,11 +156,24 @@ export const authoringStorage: IAuthoringStorage = { }); }, getContentProfile, - unlockArticle: (id) => { - return httpRequestJsonLocal({ + closeAuthoring: (current, original, doClose) => { + const diff = generatePatch(original, current); + const hasUnsavedChanges = Object.keys(diff).length > 0; + const cancelAutoSave = () => Promise.resolve(); // no auto-save yet + + const unlockArticle = (id: string) => httpRequestJsonLocal({ method: 'POST', payload: {}, path: `/archive/${id}/unlock`, }); + + return authoringApiCommon.closeAuthoring( + original, + hasUnsavedChanges, + () => authoringStorage.saveArticle(current, original).then(() => undefined), + () => unlockArticle(original._id), + cancelAutoSave, + doClose, + ); }, }; diff --git a/scripts/apps/authoring/authoring/directives/AuthoringDirective.ts b/scripts/apps/authoring/authoring/directives/AuthoringDirective.ts index fdb6c698cd..a8ebbb92fa 100644 --- a/scripts/apps/authoring/authoring/directives/AuthoringDirective.ts +++ b/scripts/apps/authoring/authoring/directives/AuthoringDirective.ts @@ -810,10 +810,15 @@ export function AuthoringDirective( _closing = true; // returned promise used by superdesk-fi - return authoring.close($scope.item, $scope.origItem, $scope.save_enabled()).then(() => { - authoringWorkspace.close(true); - $rootScope.$broadcast('item:close', $scope.origItem._id); - }); + return authoring.close( + $scope.item, + $scope.origItem, + $scope.save_enabled(), + () => { + authoringWorkspace.close(true); + $rootScope.$broadcast('item:close', $scope.origItem._id); + }, + ); }; /** @@ -832,13 +837,6 @@ export function AuthoringDirective( authoringWorkspace.close(true); }; - $scope.closeOpenNew = function(createFunction, paramValue) { - _closing = true; - authoring.close($scope.item, $scope.origItem, $scope.dirty, true).then(() => { - createFunction(paramValue); - }); - }; - /** * Called by the sendItem directive before send. * If the $scope is dirty then upon confirmation save the item and then unlock the item. diff --git a/scripts/apps/authoring/authoring/services/AuthoringService.ts b/scripts/apps/authoring/authoring/services/AuthoringService.ts index 8df29f9932..0fe410a420 100644 --- a/scripts/apps/authoring/authoring/services/AuthoringService.ts +++ b/scripts/apps/authoring/authoring/services/AuthoringService.ts @@ -139,8 +139,8 @@ AuthoringService.$inject = [ '$injector', 'moment', 'familyService', - 'modal', 'archiveService', + '$rootScope', ]; export function AuthoringService( @@ -158,8 +158,8 @@ export function AuthoringService( $injector, moment, familyService, - modal, archiveService, + $rootScope, ) { var self = this; @@ -330,35 +330,21 @@ export function AuthoringService( diff: Readonly>, orig: Readonly, isDirty: boolean, - closeItem: boolean, + doClose: () => void, ): Promise { - var promise = $q.when(); - - if (isEditable(diff)) { - if (isDirty) { - if (!_.includes(['published', 'corrected'], orig.state)) { - promise = confirm.confirm() - .then(angular.bind(this, function save() { - return this.save(orig, diff); - }), () => // ignore saving - $q.when('ignore')); - } else { - promise = $q.when('ignore'); - } - } - - promise = promise.then(function unlock(cancelType) { - if (cancelType && cancelType === 'ignore') { - autosave.drop(orig); - } - - if (!closeItem) { - return lock.unlock(diff); - } - }); - } - - return promise; + return authoringApiCommon.closeAuthoring( + orig, + isDirty, + () => this.save(orig, diff), + () => lock.unlock(diff), + () => new Promise((resolve) => { + autosave.drop(orig); + resolve(); + }), + doClose, + ).then(() => { + $rootScope.$applyAsync(); // update angular UI + }); }; /** diff --git a/scripts/apps/authoring/tests/authoring.spec.ts b/scripts/apps/authoring/tests/authoring.spec.ts index 66f0c73ed9..40f003fedc 100644 --- a/scripts/apps/authoring/tests/authoring.spec.ts +++ b/scripts/apps/authoring/tests/authoring.spec.ts @@ -387,66 +387,35 @@ describe('authoring', () => { .toBe(true); })); - it('can close a read-only item', inject((authoring, confirm, lock, $rootScope) => { - var done = jasmine.createSpy('done'); + it('can close a read-only item', (done) => inject((authoring, confirm, lock, $rootScope) => { + const onClose = jasmine.createSpy('onClose1'); - authoring.close({}).then(done); + authoring.close({}, {}, true, onClose); $rootScope.$digest(); - expect(confirm.confirm).not.toHaveBeenCalled(); - expect(lock.unlock).not.toHaveBeenCalled(); - expect(done).toHaveBeenCalled(); + setTimeout(() => { + expect(confirm.confirm).not.toHaveBeenCalled(); + expect(lock.unlock).not.toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + done(); + }, 100); })); it('can unlock on close editable item without changes made', - inject((authoring, confirm, lock, $rootScope) => { - expect(authoring.isEditable(ITEM)).toBe(true); - authoring.close(ITEM, false); - $rootScope.$digest(); - expect(confirm.confirm).not.toHaveBeenCalled(); - expect(lock.unlock).toHaveBeenCalled(); - })); - - it('confirms if an item is dirty and saves', - inject((authoring, confirm, lock, $q, $rootScope) => { - var edit = Object.create(ITEM); - - edit.headline = 'test'; - - authoring.close(edit, ITEM, true); - $rootScope.$digest(); - - expect(confirm.confirm).toHaveBeenCalled(); - expect(lock.unlock).not.toHaveBeenCalled(); + inject((authoring, confirm, lock, $rootScope, session) => { + const itemLocked = { + ...ITEM, + lock_user: session.identity._id, + lock_session: session.sessionId, + }; - spyOn(authoring, 'save').and.returnValue($q.when()); - confirmDefer.resolve(); + expect(authoring.isEditable(itemLocked)).toBe(true); + authoring.close(itemLocked, itemLocked, false); $rootScope.$digest(); - - expect(authoring.save).toHaveBeenCalledWith(ITEM, edit); + expect(confirm.confirm).not.toHaveBeenCalled(); expect(lock.unlock).toHaveBeenCalled(); })); - it('confirms if an item is dirty on opening new or existing item and not unlocking on save', - inject((authoring, confirm, lock, $q, $rootScope) => { - var edit = Object.create(ITEM); - - edit.headline = 'test'; - - authoring.close(edit, ITEM, true, true); - $rootScope.$digest(); - - expect(confirm.confirm).toHaveBeenCalled(); - expect(lock.unlock).not.toHaveBeenCalled(); - - spyOn(authoring, 'save').and.returnValue($q.when()); - confirmDefer.resolve(); - $rootScope.$digest(); - - expect(authoring.save).toHaveBeenCalledWith(ITEM, edit); - expect(lock.unlock).not.toHaveBeenCalled(); - })); - it('can unlock an item', inject((authoring, session, confirm, autosave) => { var item = {lock_user: session.identity._id, lock_session: session.sessionId}; @@ -516,40 +485,6 @@ describe('authoring', () => { expect(api.save).toHaveBeenCalledWith('archive', {}, edit); })); - it('close the published dirty item without confirmation', - inject((authoring, api, confirm, lock, autosave, $q, $rootScope) => { - var publishedItem = Object.create(ITEM); - - publishedItem.state = 'published'; - var edit = Object.create(publishedItem); - - edit.headline = 'test'; - spyOn(authoring, 'isEditable').and.returnValue(true); - spyOn(autosave, 'drop').and.returnValue($q.when({})); - authoring.close(edit, publishedItem, true, false); - $rootScope.$digest(); - expect(confirm.confirm).not.toHaveBeenCalled(); - expect(lock.unlock).toHaveBeenCalled(); - expect(autosave.drop).toHaveBeenCalled(); - })); - - it('close the corrected dirty item without confirmation', - inject((authoring, api, confirm, lock, autosave, $q, $rootScope) => { - var publishedItem = Object.create(ITEM); - - publishedItem.state = 'corrected'; - var edit = Object.create(publishedItem); - - edit.headline = 'test'; - spyOn(authoring, 'isEditable').and.returnValue(true); - spyOn(autosave, 'drop').and.returnValue($q.when({})); - authoring.close(edit, publishedItem, true, false); - $rootScope.$digest(); - expect(confirm.confirm).not.toHaveBeenCalled(); - expect(lock.unlock).toHaveBeenCalled(); - expect(autosave.drop).toHaveBeenCalled(); - })); - it('can validate schedule', inject((authoring) => { var errors = authoring.validateSchedule('2010-10-10', '08:10:10', '2010-10-10T08:10:10', 'Europe/Prague'); diff --git a/scripts/core/ui/components/prompt-for-unsaved-changes.tsx b/scripts/core/ui/components/prompt-for-unsaved-changes.tsx index 291e0f5992..69a3130ba3 100644 --- a/scripts/core/ui/components/prompt-for-unsaved-changes.tsx +++ b/scripts/core/ui/components/prompt-for-unsaved-changes.tsx @@ -7,46 +7,95 @@ export enum IUnsavedChangesAction { cancelAction = 'cancelAction', } -interface IResult { - action: IUnsavedChangesAction; +export enum IUnsavedChangesActionWithSaving { + discardChanges = 'discardChanges', + save = 'save', + cancelAction = 'cancelAction', +} + +interface IResult { + action: T; closePromptFn: () => void; } -export function showUnsavedChangesPrompt(): Promise { +// overloads +export function showUnsavedChangesPrompt(itemOpen: true): Promise>; +export function showUnsavedChangesPrompt(): Promise>; + +export function showUnsavedChangesPrompt( + itemOpen?: boolean, // if not open, instead of option to save item, it will show an option to open it first +) { return new Promise((resolve) => { - showOptionsModal( - gettext('Save changes?'), - gettext('There are some unsaved changes, go to the article to save changes?'), - [ - { - label: gettext('Ignore'), - onSelect: (closePromptFn) => { - resolve({ - action: IUnsavedChangesAction.discardChanges, - closePromptFn, - }); + if (itemOpen === true) { + showOptionsModal( + gettext('Save changes?'), + gettext('There are some unsaved changes, save it now?'), + [ + { + label: gettext('Ignore'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesActionWithSaving.discardChanges, + closePromptFn, + }); + }, + }, + { + label: gettext('Cancel'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesActionWithSaving.cancelAction, + closePromptFn, + }); + }, + }, + { + label: gettext('Save'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesActionWithSaving.save, + closePromptFn, + }); + }, + highlightOption: true, + }, + ], + ); + } else { + showOptionsModal( + gettext('Save changes?'), + gettext('There are some unsaved changes, go to the article to save changes?'), + [ + { + label: gettext('Ignore'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesAction.discardChanges, + closePromptFn, + }); + }, }, - }, - { - label: gettext('Cancel'), - onSelect: (closePromptFn) => { - resolve({ - action: IUnsavedChangesAction.cancelAction, - closePromptFn, - }); + { + label: gettext('Cancel'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesAction.cancelAction, + closePromptFn, + }); + }, }, - }, - { - label: gettext('Go-To'), - onSelect: (closePromptFn) => { - resolve({ - action: IUnsavedChangesAction.openItem, - closePromptFn, - }); + { + label: gettext('Go-To'), + onSelect: (closePromptFn) => { + resolve({ + action: IUnsavedChangesAction.openItem, + closePromptFn, + }); + }, + highlightOption: true, }, - highlightOption: true, - }, - ], - ); + ], + ); + } }); } From d1b7d196ff75fc7da3d77a25be86e9b3a4747b9b Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Mon, 8 Nov 2021 17:37:53 +0100 Subject: [PATCH 0123/1070] test authoring UI components from UI framework --- scripts/appConfig.ts | 3 +- .../apps/authoring-react/authoring-react.tsx | 10 + .../ui-framework-authoring-test.tsx | 389 ++++++++++++++++++ 3 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 scripts/apps/authoring-react/ui-framework-authoring-test.tsx diff --git a/scripts/appConfig.ts b/scripts/appConfig.ts index 7628baf3e8..6f27baa5fd 100644 --- a/scripts/appConfig.ts +++ b/scripts/appConfig.ts @@ -38,6 +38,7 @@ export const debugInfo = { }; // -export const authoringReactViewEnabled = false; +export const authoringReactViewEnabled = true; +export const uiFrameworkAuthoringPanelTest = true; export const extensions: IExtensions = {}; diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index 618d7fcd03..bf261222ae 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -5,6 +5,8 @@ import {gettext} from 'core/utils'; import {IContentProfileV2, authoringStorage} from './data-layer'; import {AuthoringSection} from './authoring-section'; import {previewItems} from 'apps/authoring/preview/fullPreviewMultiple'; +import {EditorTest} from './ui-framework-authoring-test'; +import {uiFrameworkAuthoringPanelTest} from 'appConfig'; interface IProps { itemId: IArticle['_id']; @@ -119,6 +121,14 @@ export class AuthoringReact extends React.PureComponent { return null; } + if (uiFrameworkAuthoringPanelTest) { + return ( +
+ +
+ ); + } + return (
diff --git a/scripts/apps/authoring-react/ui-framework-authoring-test.tsx b/scripts/apps/authoring-react/ui-framework-authoring-test.tsx new file mode 100644 index 0000000000..d87c64869e --- /dev/null +++ b/scripts/apps/authoring-react/ui-framework-authoring-test.tsx @@ -0,0 +1,389 @@ +/* tslint:disable: max-line-length */ + +import * as React from 'react'; +import * as Layout from 'superdesk-ui-framework/react/components/Layouts'; +import * as Form from 'superdesk-ui-framework/react/components/Form'; +import * as Nav from 'superdesk-ui-framework/react/components/Navigation'; +import { + SubNav, + ButtonGroup, + Button, + Divider, + Dropdown, + NavButton, + Tooltip, + IconButton, + Input, + Select, + BoxedList, + BoxedListItem, + Icon, + BoxedListContentRow, + AvatarWrapper, + AvatarContentText, + EmptyState, + SimpleList, + SimpleListItem, + Switch, + Text, + Option, +} from 'superdesk-ui-framework/react'; + +interface IProps { + children?: React.ReactNode; +} + +interface IState { + theme: 'dark' | 'light' | string; + itemType: string; + dropDownState: string; + itemSelected1: boolean; + itemSelected2: boolean; + itemSelected3: boolean; + value1: boolean; + value2: boolean; + value3: boolean; + leftPanelOpen: boolean; + rightPanelOpen: boolean; + rightPanelPinned: boolean; + sideOverlayOpen: boolean; +} + +export class EditorTest extends React.Component { + constructor(props: IProps) { + super(props); + this.state = { + theme: 'light', + itemType: 'itemtype01', + dropDownState: '', + itemSelected1: false, + itemSelected2: false, + itemSelected3: false, + value1: false, + value2: false, + value3: false, + leftPanelOpen: false, + rightPanelOpen: false, + rightPanelPinned: false, + sideOverlayOpen: false, + }; + this.handleTheme = this.handleTheme.bind(this); + } + + handleTheme(newTheme: string) { + this.setState({ + theme: newTheme, + }); + } + + changeStatus(item: any, status: string) { + if (item.status.includes(status)) { + item.status.splice(item.status.indexOf(status), 1); + } else { + item.status.push(status); + } + } + + render() { + return ( + + +
- -
-

{gettext('Header')}

- - { - const nextState: IStateLoaded = { - ...state, - itemWithChanges: itemChanged, - }; - - this.setState(nextState); - }} - /> -
- -
-

{gettext('Content')}

- - { - const nextState: IStateLoaded = { - ...state, - itemWithChanges: itemChanged, - }; - - this.setState(nextState); - }} - /> -
- + + +
); } diff --git a/scripts/apps/authoring-react/ui-framework-authoring-test.tsx b/scripts/apps/authoring-react/ui-framework-authoring-test.tsx index d87c64869e..0904b96b9b 100644 --- a/scripts/apps/authoring-react/ui-framework-authoring-test.tsx +++ b/scripts/apps/authoring-react/ui-framework-authoring-test.tsx @@ -88,7 +88,7 @@ export class EditorTest extends React.Component { return ( +
- + false} /> false} /> false} /> @@ -265,7 +265,7 @@ export class EditorTest extends React.Component { Curabitur blandit tempus porttitor. ) : null} - + { {this.props.selectedDesk != null && this.isDeskMember(this.props.selectedDesk) ? ( - +
); diff --git a/scripts/apps/authoring-react/widget-header-component.tsx b/scripts/apps/authoring-react/widget-header-component.tsx new file mode 100644 index 0000000000..dabacd2784 --- /dev/null +++ b/scripts/apps/authoring-react/widget-header-component.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import * as Layout from 'superdesk-ui-framework/react/components/Layouts'; +import classNames from 'classnames'; +import {IWidgetIntegrationComponentProps} from 'apps/authoring/widgets/widgets'; + +export class WidgetHeaderComponent extends React.PureComponent { + render() { + const { + widget, + pinned, + pinWidget, + } = this.props; + + return ( + + + + { + this.props.editMode && ( + + {this.props.children} + + ) + } + + ); + } +} diff --git a/scripts/apps/authoring-react/widget-layout-component.tsx b/scripts/apps/authoring-react/widget-layout-component.tsx new file mode 100644 index 0000000000..1275c1a618 --- /dev/null +++ b/scripts/apps/authoring-react/widget-layout-component.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import * as Layout from 'superdesk-ui-framework/react/components/Layouts'; +import {IAuthoringWidgetLayoutProps} from 'superdesk-api'; + +/** + * Uses layout components from ui-framework. Is only meant to be used in {@link AuthoringReact} + */ +export class AuthoringWidgetLayoutComponent extends React.PureComponent { + render() { + const {header, body, footer} = this.props; + + return ( + + {header && {header}} + + + + {body} + + + + {footer && ({footer})} + + ); + } +} diff --git a/scripts/apps/authoring/widgets/WidgetHeaderComponent.tsx b/scripts/apps/authoring/widgets/WidgetHeaderComponent.tsx new file mode 100644 index 0000000000..b77a0675cc --- /dev/null +++ b/scripts/apps/authoring/widgets/WidgetHeaderComponent.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import classNames from 'classnames'; +import {IWidgetIntegrationComponentProps} from './widgets'; + +/** + * Uses markup/styles from angular-based authoring and is intended to be rendered there. + */ +export class WidgetHeaderComponent extends React.PureComponent { + render() { + const { + widget, + pinWidget, + } = this.props; + + return ( +
+
+ {this.props.widgetName} + + + +
+ + { + this.props.editMode && ( +
+ {this.props.children} +
+ ) + } +
+ ); + } +} diff --git a/scripts/apps/authoring/widgets/WidgetLayoutComponent.tsx b/scripts/apps/authoring/widgets/WidgetLayoutComponent.tsx new file mode 100644 index 0000000000..47d0eeedd0 --- /dev/null +++ b/scripts/apps/authoring/widgets/WidgetLayoutComponent.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {IAuthoringWidgetLayoutProps} from 'superdesk-api'; + +/** + * Uses markup/styles from angular-based authoring and is intended to be rendered there. + */ +export class WidgetLayoutComponent extends React.PureComponent { + render() { + const {header, body, footer} = this.props; + + return ( + + {header && {header}} + +
+ {body} + + {footer && (
{footer}
)} +
+
+ ); + } +} diff --git a/scripts/apps/authoring/widgets/widgets.ts b/scripts/apps/authoring/widgets/widgets.ts index 268d84c9e1..f67b03ad33 100644 --- a/scripts/apps/authoring/widgets/widgets.ts +++ b/scripts/apps/authoring/widgets/widgets.ts @@ -1,10 +1,13 @@ +import React from 'react'; import {flatMap, noop} from 'lodash'; import {isWidgetVisibleForContentProfile} from 'apps/workspace/content/components/WidgetsConfig'; import {gettext} from 'core/utils'; import {isKilled} from 'apps/archive/utils'; import {AuthoringWorkspaceService} from '../authoring/services/AuthoringWorkspaceService'; -import {IArticle, IContentProfile} from 'superdesk-api'; +import {IArticle, IAuthoringWidgetLayoutProps, IContentProfile} from 'superdesk-api'; import {appConfig, extensions} from 'appConfig'; +import {WidgetHeaderComponent} from './WidgetHeaderComponent'; +import {WidgetLayoutComponent} from './WidgetLayoutComponent'; const USER_PREFERENCE_SETTINGS = 'editor:pinned_widget'; @@ -92,10 +95,34 @@ function AuthoringWidgetsProvider() { }; } -export const widgetReactIntegration = { +export interface IWidgetIntegrationComponentProps { + widgetName: string; + pinned: boolean; + widget: any; + editMode: boolean; + pinWidget(widget: any): void; +} + +/** + * This was initially written for {@link AuthoringWidgetHeading} to work. + * Wrapper components for header/layout were later added in order to be able to use + * react-based layout components from ui-framework while maintaining existing markup + * and styles in the angular based authoring. + */ +interface IWidgetIntegration { + pinWidget(widget: any): void; + getActiveWidget(): any; + getPinnedWidget(): any; + WidgetHeaderComponent: React.ComponentType; + WidgetLayoutComponent: React.ComponentType; +} + +export const widgetReactIntegration: IWidgetIntegration = { pinWidget: noop as any, getActiveWidget: noop as any, getPinnedWidget: noop as any, + WidgetHeaderComponent: () => null, + WidgetLayoutComponent: () => null, }; WidgetsManagerCtrl.$inject = ['$scope', '$routeParams', 'authoringWidgets', 'archiveService', 'authoringWorkspace', @@ -298,6 +325,11 @@ function WidgetsManagerCtrl( widgetReactIntegration.pinWidget = $scope.pinWidget; widgetReactIntegration.getActiveWidget = () => $scope.active ?? $scope.pinnedWidget; + widgetReactIntegration.getPinnedWidget = + () => $scope.widgets.find(({pinned}) => pinned === true)?.name ?? null; + + widgetReactIntegration.WidgetHeaderComponent = WidgetHeaderComponent; + widgetReactIntegration.WidgetLayoutComponent = WidgetLayoutComponent; this.updateUserPreferences = (widget?: IWidget) => { let update = []; diff --git a/scripts/apps/dashboard/widget-heading.tsx b/scripts/apps/dashboard/widget-heading.tsx index c2758e8a18..e0ed06ba13 100644 --- a/scripts/apps/dashboard/widget-heading.tsx +++ b/scripts/apps/dashboard/widget-heading.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; import {IPropsWidgetHeading} from 'superdesk-api'; import {widgetReactIntegration} from 'apps/authoring/widgets/widgets'; @@ -10,46 +9,25 @@ import {widgetReactIntegration} from 'apps/authoring/widgets/widgets'; * * It also encapsulates widget actions like pinning * without exposing the implementation details to extensions. + * + * !!! Can't use React.PureComponent because it gets props from outside */ -export class WidgetHeading extends React.PureComponent { +export class AuthoringWidgetHeading extends React.Component { render() { const widget = widgetReactIntegration.getActiveWidget(); - const {pinWidget} = widgetReactIntegration; + const pinned = widgetReactIntegration.getPinnedWidget() === this.props.widgetName; + const {pinWidget, WidgetHeaderComponent} = widgetReactIntegration; return ( -
-
- {this.props.widgetName} - - - -
- - { - this.props.editMode && ( -
- {this.props.children} -
- ) - } -
+ + {this.props.children} + ); } } diff --git a/scripts/apps/dashboard/widget-layout.tsx b/scripts/apps/dashboard/widget-layout.tsx new file mode 100644 index 0000000000..42efee5a92 --- /dev/null +++ b/scripts/apps/dashboard/widget-layout.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import {widgetReactIntegration} from 'apps/authoring/widgets/widgets'; +import {IAuthoringWidgetLayoutProps} from 'superdesk-api'; + +export class AuthoringWidgetLayout extends React.PureComponent { + render() { + const {WidgetLayoutComponent} = widgetReactIntegration; + + return ( + + ); + } +} diff --git a/scripts/core/get-superdesk-api-implementation.tsx b/scripts/core/get-superdesk-api-implementation.tsx index 34af7a06fd..89a9d018bb 100644 --- a/scripts/core/get-superdesk-api-implementation.tsx +++ b/scripts/core/get-superdesk-api-implementation.tsx @@ -79,7 +79,8 @@ import {querySelectorParent} from './helpers/dom/querySelectorParent'; import {showIgnoreCancelSaveDialog} from './ui/components/IgnoreCancelSaveDialog'; import {Editor3Html} from './editor3/Editor3Html'; import {arrayToTree, treeToArray} from './helpers/tree'; -import {WidgetHeading} from 'apps/dashboard/widget-heading'; +import {AuthoringWidgetHeading} from 'apps/dashboard/widget-heading'; +import {AuthoringWidgetLayout} from 'apps/dashboard/widget-layout'; function getContentType(id): Promise { return dataApi.findOne('content_types', id); @@ -350,7 +351,8 @@ export function getSuperdeskApiImplementation( getLiveQueryHOC: () => WithLiveQuery, WithLiveResources, Editor3Html, - WidgetHeading, + AuthoringWidgetHeading, + AuthoringWidgetLayout, }, forms: { FormFieldType, diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index eb1b7645e1..1b04b89fec 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -65,6 +65,12 @@ declare module 'superdesk-api' { query: {[key: string]: any}; } + export interface IAuthoringWidgetLayoutProps { + header?: JSX.Element; + body: JSX.Element; + footer?: JSX.Element; + } + export interface IAuthoringSideWidget { _id: string; // required for configuring widget visibility in content profile label: string; @@ -1700,7 +1706,8 @@ declare module 'superdesk-api' { WithLiveResources: React.ComponentType; Spacer: React.ComponentType; Editor3Html: React.ComponentType; - WidgetHeading: React.ComponentType; + AuthoringWidgetHeading: React.ComponentType; + AuthoringWidgetLayout: React.ComponentType; }; forms: { FormFieldType: typeof FormFieldType; diff --git a/scripts/extensions/auto-tagging-widget/src/auto-tagging.tsx b/scripts/extensions/auto-tagging-widget/src/auto-tagging.tsx index 84e79afb55..e7b2ad3703 100644 --- a/scripts/extensions/auto-tagging-widget/src/auto-tagging.tsx +++ b/scripts/extensions/auto-tagging-widget/src/auto-tagging.tsx @@ -119,7 +119,7 @@ export function getAutoTaggingComponent(superdesk: ISuperdesk, label: string) { const {httpRequestJsonLocal} = superdesk; const {gettext, gettextPlural} = superdesk.localization; const {memoize, generatePatch, arrayToTree} = superdesk.utilities; - const {WidgetHeading, Alert} = superdesk.components; + const {AuthoringWidgetHeading, AuthoringWidgetLayout, Alert} = superdesk.components; const groupLabels = getGroups(superdesk); const TagListComponent = getTagsListComponent(superdesk); @@ -279,287 +279,257 @@ export function getAutoTaggingComponent(superdesk: ISuperdesk, label: string) { const readOnly = superdesk.entities.article.isLockedInOtherSession(this.props.article); return ( - - { - (() => { - if (data === 'loading' || data === 'not-initialized') { - return null; - } else { - const treeErrors = arrayToTree( - data.changes.analysis.toArray(), - (item) => item.qcode, - (item) => item.parent, - ).errors; - - // only show errors when there are unsaved changes - if (treeErrors.length > 0 && dirty) { - return ( - { - showImatricsServiceErrorModal(superdesk, treeErrors); - }, - icon: 'info-sign', - }, - ]} + + { + data === 'loading' || data === 'not-initialized' || !dirty ? null : ( + + - - -
- ) - } - - -
-
-
- - { - const newValue = !runAutomaticallyPreference; - - this.setState({runAutomaticallyPreference: newValue}); - - superdesk.preferences.set(RUN_AUTOMATICALLY_PREFERENCE, newValue); - - if (newValue && this.state.data === 'not-initialized') { - this.runAnalysis(); - } - }} - label={{text: gettext('Run automatically')}} - /> - -
- + }} + /> + + ) + } + + )} + body={( + { - data === 'loading' || data === 'not-initialized' ? null : ( -
-
- { - let cancelled = false; - - httpRequestJsonLocal({ - method: 'POST', - path: '/ai_data_op/', - payload: { - service: 'imatrics', - operation: 'search', - data: {term: searchString}, + (() => { + if (data === 'loading' || data === 'not-initialized') { + return null; + } else { + const treeErrors = arrayToTree( + data.changes.analysis.toArray(), + (item) => item.qcode, + (item) => item.parent, + ).errors; + + // only show errors when there are unsaved changes + if (treeErrors.length > 0 && dirty) { + return ( + { + showImatricsServiceErrorModal(superdesk, treeErrors); + }, + icon: 'info-sign', }, - }).then((res) => { - if (cancelled !== true) { - const result = toClientFormat(res.result.tags).toArray(); + ]} + /> + ); + } else { + return null; + } + } + })() + } - const withoutExistingTags = result.filter( - (searchTag) => tagAlreadyExists( - data, - searchTag.qcode, - ) !== true, - ); +
+
+ + { + const newValue = !runAutomaticallyPreference; - callback(withoutExistingTags); - } - }); + this.setState({runAutomaticallyPreference: newValue}); - return { - cancel: () => { - cancelled = true; - }, - }; - }} - listItemTemplate={(__item: any) => { - const _item: ITagUi = __item; - - return ( -
- {_item.name} - - { - _item?.group?.value == null ? null : ( -

{_item.group.value}

- ) - } + superdesk.preferences.set(RUN_AUTOMATICALLY_PREFERENCE, newValue); + + if (newValue && this.state.data === 'not-initialized') { + this.runAnalysis(); + } + }} + label={{text: gettext('Run automatically')}} + /> + +
- { - _item?.description == null ? null : ( -

{_item.description}

- ) + { + data === 'loading' || data === 'not-initialized' ? null : ( +
+
+ { + let cancelled = false; + + httpRequestJsonLocal({ + method: 'POST', + path: '/ai_data_op/', + payload: { + service: 'imatrics', + operation: 'search', + data: {term: searchString}, + }, + }).then((res) => { + if (cancelled !== true) { + const result = toClientFormat( + res.result.tags, + ).toArray(); + + const withoutExistingTags = result.filter( + (searchTag) => tagAlreadyExists( + data, + searchTag.qcode, + ) !== true, + ); + + callback(withoutExistingTags); } -
- ); - }} - onSelect={(_value: any) => { - const value = _value as ITagUi; - - this.insertTagFromSearch(value, data); - // TODO: clear autocomplete? - }} - onChange={noop} - /> -
+ }); -
-
-
- ) - } -
- - {(() => { - if (data === 'loading') { - return ( -
-
-
- ); - } else if (data === 'not-initialized') { - return ( - - ); - } else { - const items = data.changes.analysis; - const savedTags = data.original.analysis.keySeq().toSet(); - - const isEntity = (tag: ITagUi) => entityGroups.has(tag.group.value); - - const entities = items.filter((tag) => isEntity(tag)); - const entitiesGrouped = entities.groupBy((tag) => tag?.group.value); - - const entitiesGroupedAndSortedByConfig = entitiesGrouped - .filter((_, key) => hasConfig(key, this.iMatricsFields.entities)) - .sortBy((_, key) => this.iMatricsFields.entities[key].order, - (a, b) => a - b); - - const entitiesGroupedAndSortedNotInConfig = entitiesGrouped - .filter((_, key) => !hasConfig(key, this.iMatricsFields.entities)) - .sortBy((_, key) => key!.toString().toLocaleLowerCase(), - (a, b) => a.localeCompare(b)); - - const entitiesGroupedAndSorted = entitiesGroupedAndSortedByConfig - .concat(entitiesGroupedAndSortedNotInConfig); - - const others = items.filter((tag) => isEntity(tag) === false); - const othersGrouped = others.groupBy((tag) => tag.group.value); - - let allGrouped = OrderedMap(); - - othersGrouped.forEach((tags, groupId) => { - if (tags != null && groupId != null) { - allGrouped = allGrouped.set(groupId, - - { - this.updateTags( - ids.reduce( - (analysis, id) => analysis.remove(id), - data.changes.analysis, - ), - data, + return { + cancel: () => { + cancelled = true; + }, + }; + }} + listItemTemplate={(__item: any) => { + const _item: ITagUi = __item; + + return ( +
+ {_item.name} + + { + _item?.group?.value == null ? null : ( +

{_item.group.value}

+ ) + } + + { + _item?.description == null ? null : ( +

{_item.description}

+ ) + } +
); }} + onSelect={(_value: any) => { + const value = _value as ITagUi; + + this.insertTagFromSearch(value, data); + // TODO: clear autocomplete? + }} + onChange={noop} /> -
, - ); - } - }); - - if (entitiesGroupedAndSorted.size > 0) { - allGrouped = allGrouped.set('entities', - - {entitiesGroupedAndSorted.map((tags, key) => ( -
-
- {groupLabels.get(key).plural} -
+
+ +
+
+
+ ) + } +
+ + {(() => { + if (data === 'loading') { + return ( +
+
+
+ ); + } else if (data === 'not-initialized') { + return ( + + ); + } else { + const items = data.changes.analysis; + const savedTags = data.original.analysis.keySeq().toSet(); + + const isEntity = (tag: ITagUi) => entityGroups.has(tag.group.value); + + const entities = items.filter((tag) => isEntity(tag)); + const entitiesGrouped = entities.groupBy((tag) => tag?.group.value); + + const entitiesGroupedAndSortedByConfig = entitiesGrouped + .filter((_, key) => hasConfig(key, this.iMatricsFields.entities)) + .sortBy((_, key) => this.iMatricsFields.entities[key].order, + (a, b) => a - b); + + const entitiesGroupedAndSortedNotInConfig = entitiesGrouped + .filter((_, key) => !hasConfig(key, this.iMatricsFields.entities)) + .sortBy((_, key) => key!.toString().toLocaleLowerCase(), + (a, b) => a.localeCompare(b)); + + const entitiesGroupedAndSorted = entitiesGroupedAndSortedByConfig + .concat(entitiesGroupedAndSortedNotInConfig); + + const others = items.filter((tag) => isEntity(tag) === false); + const othersGrouped = others.groupBy((tag) => tag.group.value); + + let allGrouped = OrderedMap(); + + othersGrouped.forEach((tags, groupId) => { + if (tags != null && groupId != null) { + allGrouped = allGrouped.set(groupId, + -
- )).toArray()} - , - ); - } - - const allGroupedAndSortedByConfig = allGrouped - .filter((_, key) => hasConfig(key, this.iMatricsFields.others)) - .sortBy((_, key) => this.iMatricsFields.others[key].order, - (a, b) => a - b); - - const allGroupedAndSortedNotInConfig = allGrouped - .filter((_, key) => !hasConfig(key, this.iMatricsFields.others)); - - const allGroupedAndSorted = allGroupedAndSortedByConfig - .concat(allGroupedAndSortedNotInConfig); - - return ( - - { - this.state.newItem == null ? null : ( - { - this.setState({newItem}); - }} - save={(newItem: INewItem) => { - this.createNewTag(newItem, data); - }} - cancel={() => { - this.setState({newItem: null}); - }} - tagAlreadyExists={ - (qcode) => tagAlreadyExists(data, qcode) - } - insertTagFromSearch={(tag: ITagUi) => { - this.insertTagFromSearch(tag, data); - }} - /> - ) + , + ); } + }); -
- {allGroupedAndSorted.map((item) => item).toArray()} -
-
- ); - } - })()} + if (entitiesGroupedAndSorted.size > 0) { + allGrouped = allGrouped.set('entities', + + {entitiesGroupedAndSorted.map((tags, key) => ( +
+
+ {groupLabels.get(key).plural} +
+ { + this.updateTags( + ids.reduce( + (analysis, id) => analysis.remove(id), + data.changes.analysis, + ), + data, + ); + }} + /> +
+ )).toArray()} +
, + ); + } + + const allGroupedAndSortedByConfig = allGrouped + .filter((_, key) => hasConfig(key, this.iMatricsFields.others)) + .sortBy((_, key) => this.iMatricsFields.others[key].order, + (a, b) => a - b); + + const allGroupedAndSortedNotInConfig = allGrouped + .filter((_, key) => !hasConfig(key, this.iMatricsFields.others)); + + const allGroupedAndSorted = allGroupedAndSortedByConfig + .concat(allGroupedAndSortedNotInConfig); -
- {(() => { - if (data === 'loading') { - return null; - } else if (data === 'not-initialized') { - return ( -
-
-
+ + )} + footer={(() => { + if (data === 'loading') { + return undefined; + } else if (data === 'not-initialized') { + return ( + + + + { this.props.editMode && ( diff --git a/scripts/apps/authoring/tests/authoring.spec.ts b/scripts/apps/authoring/tests/authoring.spec.ts index 40f003fedc..2e5c66deae 100644 --- a/scripts/apps/authoring/tests/authoring.spec.ts +++ b/scripts/apps/authoring/tests/authoring.spec.ts @@ -402,7 +402,9 @@ describe('authoring', () => { })); it('can unlock on close editable item without changes made', - inject((authoring, confirm, lock, $rootScope, session) => { + (done) => inject((authoring, confirm, lock, $rootScope, session) => { + const onClose = jasmine.createSpy('onClose2'); + const itemLocked = { ...ITEM, lock_user: session.identity._id, @@ -410,10 +412,15 @@ describe('authoring', () => { }; expect(authoring.isEditable(itemLocked)).toBe(true); - authoring.close(itemLocked, itemLocked, false); + authoring.close(itemLocked, itemLocked, false, onClose); $rootScope.$digest(); - expect(confirm.confirm).not.toHaveBeenCalled(); - expect(lock.unlock).toHaveBeenCalled(); + + setTimeout(() => { + expect(confirm.confirm).not.toHaveBeenCalled(); + expect(lock.unlock).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + done(); + }, 100); })); it('can unlock an item', inject((authoring, session, confirm, autosave) => { diff --git a/scripts/apps/authoring/widgets/widgets.ts b/scripts/apps/authoring/widgets/widgets.ts index f67b03ad33..aa8eb16a39 100644 --- a/scripts/apps/authoring/widgets/widgets.ts +++ b/scripts/apps/authoring/widgets/widgets.ts @@ -101,6 +101,7 @@ export interface IWidgetIntegrationComponentProps { widget: any; editMode: boolean; pinWidget(widget: any): void; + closeWidget(): void; } /** @@ -112,6 +113,7 @@ export interface IWidgetIntegrationComponentProps { interface IWidgetIntegration { pinWidget(widget: any): void; getActiveWidget(): any; + closeActiveWidget(): any; getPinnedWidget(): any; WidgetHeaderComponent: React.ComponentType; WidgetLayoutComponent: React.ComponentType; @@ -121,6 +123,7 @@ export const widgetReactIntegration: IWidgetIntegration = { pinWidget: noop as any, getActiveWidget: noop as any, getPinnedWidget: noop as any, + closeActiveWidget: noop, WidgetHeaderComponent: () => null, WidgetLayoutComponent: () => null, }; diff --git a/scripts/apps/dashboard/widget-heading.tsx b/scripts/apps/dashboard/widget-heading.tsx index e0ed06ba13..7bc3cf5f08 100644 --- a/scripts/apps/dashboard/widget-heading.tsx +++ b/scripts/apps/dashboard/widget-heading.tsx @@ -25,6 +25,7 @@ export class AuthoringWidgetHeading extends React.Component widget={widget} widgetName={this.props.widgetName} editMode={this.props.editMode} + closeWidget={() => widgetReactIntegration.closeActiveWidget()} > {this.props.children} From cf59f1b0375744fc44ef8bd55f3e56898ab3c9f4 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Fri, 12 Nov 2021 17:12:47 +0100 Subject: [PATCH 0130/1070] update usage of ui-framework components to match updated interface --- .../sams/src/components/assets/assetEditorPanel.tsx | 2 +- .../sams/src/components/assets/selectAssetModal.tsx | 2 +- .../extensions/sams/src/components/sets/manageSetsModal.tsx | 2 +- .../extensions/sams/src/components/sets/setEditorPanel.tsx | 2 +- scripts/extensions/sams/src/components/workspaceSubnav.tsx | 4 ++-- scripts/extensions/sams/src/containers/FileUploadModal.tsx | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/extensions/sams/src/components/assets/assetEditorPanel.tsx b/scripts/extensions/sams/src/components/assets/assetEditorPanel.tsx index ddfbab9887..bc90c50a73 100644 --- a/scripts/extensions/sams/src/components/assets/assetEditorPanel.tsx +++ b/scripts/extensions/sams/src/components/assets/assetEditorPanel.tsx @@ -120,7 +120,7 @@ export class AssetEditorPanelComponent extends React.PureComponent - +
+ )} + footer={( +
test footer
+ )} + /> + ); + } +} + +export function getDemoWidget() { + const metadataWidget: IAuthoringSideWidget = { + _id: 'metadata', + label: getLabel(), + order: 2, + icon: 'info', + component: DemoWidget, + }; + + return metadataWidget; +} diff --git a/scripts/core/get-superdesk-api-implementation.tsx b/scripts/core/get-superdesk-api-implementation.tsx index 89a9d018bb..f7e24da1dd 100644 --- a/scripts/core/get-superdesk-api-implementation.tsx +++ b/scripts/core/get-superdesk-api-implementation.tsx @@ -81,6 +81,7 @@ import {Editor3Html} from './editor3/Editor3Html'; import {arrayToTree, treeToArray} from './helpers/tree'; import {AuthoringWidgetHeading} from 'apps/dashboard/widget-heading'; import {AuthoringWidgetLayout} from 'apps/dashboard/widget-layout'; +import {patchArticle} from 'api/article-patch'; function getContentType(id): Promise { return dataApi.findOne('content_types', id); @@ -207,36 +208,7 @@ export function getSuperdeskApiImplementation( isLocked: sdApi.article.isLocked, isLockedInCurrentSession: sdApi.article.isLockedInCurrentSession, isLockedInOtherSession: sdApi.article.isLockedInOtherSession, - patch: (article, patch, dangerousOptions) => { - const onPatchBeforeMiddlewares = Object.values(extensions) - .map((extension) => extension.activationResult?.contributions?.entities?.article?.onPatchBefore) - .filter((middleware) => middleware != null); - - return onPatchBeforeMiddlewares.reduce( - (current, next) => current.then((result) => next(article._id, result, dangerousOptions)), - Promise.resolve(patch), - ).then((patchFinal) => { - return dataApi.patchRaw( - // distinction between handling published and non-published items - // should be removed: SDESK-4687 - (sdApi.article.isPublished(article) ? 'published' : 'archive'), - article._id, - article._etag, - patchFinal, - ).then((res) => { - if (dangerousOptions?.patchDirectlyAndOverwriteAuthoringValues === true) { - dispatchInternalEvent( - 'dangerouslyOverwriteAuthoringData', - {...patch, _etag: res._etag, _id: res._id}, - ); - } - }); - }).catch((err) => { - if (err instanceof Error) { - logger.error(err); - } - }); - }, + patch: patchArticle, isArchived: sdApi.article.isArchived, isPublished: (article) => sdApi.article.isPublished(article), }, diff --git a/scripts/index.ts b/scripts/index.ts index 28ef2748ef..f733767527 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -22,6 +22,7 @@ import {setupTansa} from 'apps/tansa'; import {i18n} from 'core/utils'; import {configurableAlgorithms} from 'core/ui/configurable-algorithms'; import {merge} from 'lodash'; +import {registerAuthoringReactWidgets} from 'apps/authoring-react/manage-widget-registration'; let body = angular.element('body'); @@ -180,7 +181,9 @@ export function startApp( metadata, _workspaceMenu, preferencesService, - ); + ).then(() => { + registerAuthoringReactWidgets(); + }); }, ]); From 91604950e7e1ce641796029528ab592724464acf Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Wed, 17 Nov 2021 12:53:33 +0100 Subject: [PATCH 0132/1070] merge tomaskikutis:content-profiles-for-non-text-content-types --- e2e/client/specs/_unstable-tests.txt | 1 + package-lock.json | 160 +++-- package.json | 1 + .../apps/archive/directives/MediaPreview.ts | 4 +- .../archive/directives/RelatedItemsPreview.ts | 4 +- .../authoring/authoring-custom-field.tsx | 10 +- .../authoring/preview-custom-field.tsx | 4 +- scripts/apps/authoring/macros/macros.spec.ts | 10 +- .../apps/authoring/preview/fullPreview.tsx | 2 +- .../apps/authoring/tests/authoring.spec.ts | 14 +- .../apps/desks/directives/DeskConfigModal.ts | 2 +- .../apps/desks/tests/contentExpiry.spec.ts | 1 + .../InternalDestinations.tsx | 114 ++-- .../monitoring/controllers/AggregateCtrl.ts | 2 +- .../directives/RelatedItemsDirective.ts | 4 +- .../relations/services/RelationsService.ts | 4 +- scripts/apps/search/components/Item.tsx | 16 +- .../search/components/preview-subject.tsx | 4 +- .../components/system-messages-settings.tsx | 115 ++-- .../directives/TemplatesDirective.ts | 2 +- .../components/ContentProfileFieldsConfig.tsx | 638 ++++++++++++++++++ .../get-content-profiles-form-config.tsx | 346 ++++++++++ .../content/components/get-editor-config.tsx | 113 ++++ scripts/apps/workspace/content/constants.ts | 61 -- .../controllers/ContentProfileFields.ts | 208 ------ .../controllers/ContentProfilesController.ts | 132 +++- .../directives/ContentProfileSchemaEditor.ts | 227 ------- .../content/directives/SortContentProfiles.ts | 52 -- .../workspace/content/directives/index.ts | 2 - scripts/apps/workspace/content/index.ts | 20 +- .../content/services/ContentService.ts | 82 +-- .../workspace/content/styles/profiles.scss | 5 +- .../workspace/content/tests/content.spec.ts | 30 +- .../content/views/profile-settings.html | 135 ++-- .../views/schema-editor-fields-dropdown.html | 17 - .../content/views/schema-editor.html | 119 ---- .../workspace/helpers/getTypeForFieldId.ts | 6 +- scripts/core/editor3/store/index.ts | 4 +- scripts/core/helpers/CrudManager.tsx | 4 +- scripts/core/helpers/network.tsx | 7 +- scripts/core/superdesk-api.d.ts | 126 +++- .../generic-list-page-item-view-edit.tsx | 114 ++-- .../components/ListPage/generic-list-page.tsx | 416 +++++++----- scripts/core/ui/components/Page/index.tsx | 2 +- .../generic-form/form-direction-wrapper.tsx | 2 +- .../ui/components/generic-form/form-field.tsx | 13 +- .../ui/components/generic-form/from-group.tsx | 10 +- .../generate-filter-for-server.tsx | 4 +- .../get-form-group-for-filtering.tsx | 10 +- .../generic-form/get-initial-values.tsx | 4 +- .../ui/components/generic-form/has-value.tsx | 12 +- .../generic-form/input-types/checkbox.tsx | 6 +- .../{text-single-line.tsx => number.tsx} | 12 +- .../generic-form/input-types/plain-text.tsx | 57 ++ .../input-types/select_multiple_values.tsx | 66 ++ .../select_single_value_static.tsx | 61 ++ .../generic-form/interfaces/form.ts | 4 +- .../generic-form/interfaces/input-types.tsx | 1 + .../generic-form/tests/generic-form.spec.tsx | 22 +- .../core/ui/components/only-with-children.tsx | 57 ++ scripts/core/utils.ts | 26 +- .../src/AnnotationInputWithKnowledgeBase.tsx | 6 +- .../annotationsLibrary/src/GetFields.ts | 2 +- .../src/annotations-library-page.tsx | 94 +-- styles/sass/forms.scss | 3 + styles/sass/sf-additional.scss | 11 + 66 files changed, 2450 insertions(+), 1373 deletions(-) create mode 100644 scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx create mode 100644 scripts/apps/workspace/content/components/get-content-profiles-form-config.tsx create mode 100644 scripts/apps/workspace/content/components/get-editor-config.tsx delete mode 100644 scripts/apps/workspace/content/controllers/ContentProfileFields.ts delete mode 100644 scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts delete mode 100644 scripts/apps/workspace/content/directives/SortContentProfiles.ts delete mode 100644 scripts/apps/workspace/content/views/schema-editor-fields-dropdown.html delete mode 100644 scripts/apps/workspace/content/views/schema-editor.html rename scripts/core/ui/components/generic-form/input-types/{text-single-line.tsx => number.tsx} (71%) create mode 100644 scripts/core/ui/components/generic-form/input-types/plain-text.tsx create mode 100644 scripts/core/ui/components/generic-form/input-types/select_multiple_values.tsx create mode 100644 scripts/core/ui/components/generic-form/input-types/select_single_value_static.tsx create mode 100644 scripts/core/ui/components/only-with-children.tsx diff --git a/e2e/client/specs/_unstable-tests.txt b/e2e/client/specs/_unstable-tests.txt index 8ff5c94c84..1a855b14f8 100644 --- a/e2e/client/specs/_unstable-tests.txt +++ b/e2e/client/specs/_unstable-tests.txt @@ -3,3 +3,4 @@ monitoring can display the item in Desk Output when it's published in a producti authoring related item widget can open published item send warns that there are spelling mistakes master_desk show content view - edit item +can save and use language preferences diff --git a/package-lock.json b/package-lock.json index 52965a3973..08eadf6bf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,11 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.0" } }, "@babel/helper-validator-identifier": { @@ -18,11 +18,11 @@ "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -61,14 +61,14 @@ } }, "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==" + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", + "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==" }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", + "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==", "requires": { "regenerator-runtime": "^0.13.4" }, @@ -264,9 +264,9 @@ }, "dependencies": { "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "dom-helpers": { "version": "5.2.1", @@ -404,9 +404,9 @@ } }, "@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==" + "version": "16.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", + "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==" }, "@types/parse5": { "version": "5.0.3", @@ -1156,9 +1156,9 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "requires": { "safer-buffer": "~2.1.0" } @@ -2049,9 +2049,9 @@ } }, "caniuse-db": { - "version": "1.0.30001272", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001272.tgz", - "integrity": "sha512-/NplzwUm4ZCpKOJs/zq3newlycZ/T7sW9xVFINmDyRBmmSoqTpQQTAJ0f+a4xfOODI8T6aM23GGGVQwoxZAgdw==" + "version": "1.0.30001282", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001282.tgz", + "integrity": "sha512-czsWYzaRcSfPVTWzxwl6xo6JdVPTTShJoWjAO7ZYArI3oKFe7COLk3G9k/WVysOCEs5Jiu66Ly+8nRLlj85vdw==" }, "caseless": { "version": "0.12.0", @@ -3616,9 +3616,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.884", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.884.tgz", - "integrity": "sha512-kOaCAa+biA98PwH5BpCkeUeTL6mCeg8p3Q3OhqzPyqhu/5QUnWAN2wr/3IK8xMQxIV76kfoQpP+Bn/wij/jXrg==" + "version": "1.3.900", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz", + "integrity": "sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==" }, "elliptic": { "version": "6.5.4", @@ -5518,9 +5518,9 @@ "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" }, "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" }, "for-in": { "version": "1.0.2", @@ -8130,16 +8130,16 @@ "optional": true }, "is-my-json-valid": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", - "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", "dev": true, "optional": true, "requires": { "generate-function": "^2.0.0", "generate-object-property": "^1.1.0", "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", + "jsonpointer": "^5.0.0", "xtend": "^4.0.0" } }, @@ -8559,9 +8559,9 @@ } }, "jsonpointer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", - "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", "dev": true, "optional": true }, @@ -9051,9 +9051,9 @@ } }, "keycode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", + "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" }, "killable": { "version": "1.0.1", @@ -10150,16 +10150,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "requires": { - "mime-db": "1.50.0" + "mime-db": "1.51.0" } }, "mimic-fn": { @@ -11258,9 +11258,9 @@ }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "dev": true, "optional": true }, @@ -11272,17 +11272,17 @@ "optional": true }, "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "optional": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.4", - "setprototypeof": "1.1.1", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" } }, "https-proxy-agent": { @@ -11319,17 +11319,31 @@ } }, "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "dev": true, "optional": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "optional": true + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "optional": true } } }, @@ -12948,6 +12962,16 @@ } } }, + "react-sortable-hoc": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz", + "integrity": "sha512-v1CDCvdfoR3zLGNp6qsBa4J1BWMEVH25+UKxF/RvQRh+mrB+emqtVHMgZ+WreUiKJoEaiwYoScaueIKhMVBHUg==", + "requires": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + } + }, "react-test-renderer": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", @@ -14444,9 +14468,9 @@ } }, "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" }, "spdy": { "version": "3.4.7", @@ -15400,9 +15424,9 @@ }, "dependencies": { "@types/node": { - "version": "14.17.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.32.tgz", - "integrity": "sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ==" + "version": "14.17.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.33.tgz", + "integrity": "sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==" } } }, diff --git a/package.json b/package.json index 7d844c6390..712af581b5 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "react-paginate": "6.3.0", "react-portal": "4.1.3", "react-redux": "7.2.1", + "react-sortable-hoc": "1.11.0", "react-textarea-autosize": "5.2.1", "redux": "4.0.5", "redux-logger": "3.0.6", diff --git a/scripts/apps/archive/directives/MediaPreview.ts b/scripts/apps/archive/directives/MediaPreview.ts index 478549700d..e7e3c42d97 100644 --- a/scripts/apps/archive/directives/MediaPreview.ts +++ b/scripts/apps/archive/directives/MediaPreview.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import {checkRenditions, getAssociationsByFieldId} from 'apps/authoring/authoring/controllers/AssociationController'; -import {IArticleField} from 'superdesk-api'; +import {IVocabulary} from 'superdesk-api'; import {appConfig} from 'appConfig'; /** @@ -29,7 +29,7 @@ export function MediaPreview(api, $rootScope, desks, superdesk, content, storage const setSubjectPreviewFields = () => { scope.subjectPreviewFields = content.previewFields(scope.editor, scope.fields) - .filter((field: IArticleField) => field.field_type == null); + .filter((field: IVocabulary) => field.field_type == null); }; scope.isCorrectionWorkflowEnabled = appConfig?.corrections_workflow; diff --git a/scripts/apps/archive/directives/RelatedItemsPreview.ts b/scripts/apps/archive/directives/RelatedItemsPreview.ts index c6479e3f31..393454fb6f 100644 --- a/scripts/apps/archive/directives/RelatedItemsPreview.ts +++ b/scripts/apps/archive/directives/RelatedItemsPreview.ts @@ -1,4 +1,4 @@ -import {IArticle, IArticleField, IRendition} from 'superdesk-api'; +import {IArticle, IVocabulary, IRendition} from 'superdesk-api'; import {gettext} from 'core/utils'; import {getThumbnailForItem} from 'core/helpers/item'; @@ -15,7 +15,7 @@ import {getThumbnailForItem} from 'core/helpers/item'; interface IScope extends ng.IScope { item: IArticle; - field: IArticleField; + field: IVocabulary; preview: boolean; loading: boolean; relatedItems: Array; diff --git a/scripts/apps/authoring/authoring/authoring-custom-field.tsx b/scripts/apps/authoring/authoring/authoring-custom-field.tsx index 4c0e1e4ac5..9b000faaae 100644 --- a/scripts/apps/authoring/authoring/authoring-custom-field.tsx +++ b/scripts/apps/authoring/authoring/authoring-custom-field.tsx @@ -2,13 +2,13 @@ import React from 'react'; import {get, throttle, Cancelable} from 'lodash'; import {getField} from 'apps/fields'; -import {IArticle, IArticleField, ITemplate} from 'superdesk-api'; +import {IArticle, IVocabulary, ITemplate} from 'superdesk-api'; interface IProps { item: IArticle; - field: IArticleField; + field: IVocabulary; editable: boolean; - onChange: (field: IArticleField, value: any) => any; + onChange: (field: IVocabulary, value: any) => any; template?: ITemplate; } @@ -25,7 +25,7 @@ function getValue(props: IProps) { } export class AuthoringCustomField extends React.PureComponent { - onChangeThrottled: ((field: IArticleField, value: any) => void) & Cancelable; + onChangeThrottled: ((field: IVocabulary, value: any) => void) & Cancelable; // IProps['item'] is mutated when updating so prevProps from `componentDidUpdate` // can't be used to compare the previous value. This property is used instead. @@ -40,7 +40,7 @@ export class AuthoringCustomField extends React.PureComponent { this.lastPropsValue = this.state.value; - this.onChangeThrottled = throttle((field: IArticleField, value: any) => { + this.onChangeThrottled = throttle((field: IVocabulary, value: any) => { this.props.onChange(field, value); }, 300, {leading: false}); diff --git a/scripts/apps/authoring/authoring/preview-custom-field.tsx b/scripts/apps/authoring/authoring/preview-custom-field.tsx index 1cfb77854a..77c1de8af9 100644 --- a/scripts/apps/authoring/authoring/preview-custom-field.tsx +++ b/scripts/apps/authoring/authoring/preview-custom-field.tsx @@ -2,11 +2,11 @@ import React from 'react'; import {get, isEmpty} from 'lodash'; import {getField} from 'apps/fields'; -import {IArticle, IArticleField} from 'superdesk-api'; +import {IArticle, IVocabulary} from 'superdesk-api'; interface IProps { item: IArticle; - field: IArticleField; + field: IVocabulary; } export class PreviewCustomField extends React.PureComponent { diff --git a/scripts/apps/authoring/macros/macros.spec.ts b/scripts/apps/authoring/macros/macros.spec.ts index eee7e8136f..2096719faf 100644 --- a/scripts/apps/authoring/macros/macros.spec.ts +++ b/scripts/apps/authoring/macros/macros.spec.ts @@ -54,7 +54,9 @@ describe('macros', () => { var $controller; - beforeEach(inject((_$controller_, macros, $q) => { + beforeEach(inject((_$controller_, macros, $q, $httpBackend) => { + $httpBackend.whenGET(/api$/).respond({_links: {child: []}}); + $controller = _$controller_; spyOn(macros, 'get').and.returnValue($q.when([])); })); @@ -94,7 +96,7 @@ describe('macros', () => { it('trigger macro with diff does not update item', inject((macros, $q, autosave, $rootScope) => { var diff = {foo: 'bar'}; - var item = {_id: '1'}; + var item = {_id: '1', profile: '123'}; var $scope = startAuthoring(item, 'edit'); spyOn(macros, 'call').and.returnValue($q.when({item: item, diff: diff})); @@ -149,6 +151,7 @@ describe('macros', () => { genre: [{qcode: 'foo', name: 'bar'}], slugline: 'slugline', _etag: 'foo', + profile: '123', }; let macroItem = { _id: '1', @@ -156,6 +159,7 @@ describe('macros', () => { abstract: 'abstract', genre: [{qcode: 'zoo', name: 'zoo'}], _etag: 'bar', + profile: '123', }; let $scope = startAuthoring(item, 'edit'); @@ -186,6 +190,7 @@ describe('macros', () => { abstract: 'abstract', genre: [{qcode: 'foo', name: 'bar'}], slugline: 'slugline', + profile: '123', }; let macroItem = { _id: '1', @@ -193,6 +198,7 @@ describe('macros', () => { abstract: 'new abstract', slugline: 'new slugline', genre: [{qcode: 'zoo', name: 'zoo'}], + profile: '123', }; let $scope = startAuthoring(item, 'edit'); diff --git a/scripts/apps/authoring/preview/fullPreview.tsx b/scripts/apps/authoring/preview/fullPreview.tsx index c513d46fbf..44c2894ab3 100644 --- a/scripts/apps/authoring/preview/fullPreview.tsx +++ b/scripts/apps/authoring/preview/fullPreview.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {IArticle, IVocabulary} from 'superdesk-api'; import {getLabelNameResolver} from 'apps/workspace/helpers/getLabelForFieldId'; -import {ARTICLE_HEADER_FIELDS, ARTICLE_COMMON_FIELDS} from 'apps/workspace/content/controllers/ContentProfileFields'; +import {ARTICLE_HEADER_FIELDS, ARTICLE_COMMON_FIELDS} from 'apps/workspace/content/components/get-editor-config'; import {dataApi} from 'core/helpers/CrudManager'; import {PreviewFieldType} from './previewFieldByType'; import {IAuthoringField} from './types'; diff --git a/scripts/apps/authoring/tests/authoring.spec.ts b/scripts/apps/authoring/tests/authoring.spec.ts index 2e5c66deae..a5c843ad06 100644 --- a/scripts/apps/authoring/tests/authoring.spec.ts +++ b/scripts/apps/authoring/tests/authoring.spec.ts @@ -99,7 +99,7 @@ describe('authoring', () => { })); it('can save while item is being autosaved', (done) => inject(($rootScope, $timeout, $q, api) => { - var $scope = startAuthoring({headline: 'test', task: 'desk:1'}, 'edit'); + var $scope = startAuthoring({headline: 'test', task: 'desk:1', profile: '123'}, 'edit'); $scope.item.body_html = 'test'; $rootScope.$digest(); @@ -119,7 +119,7 @@ describe('authoring', () => { })); it('can close item after save work confirm', inject(($rootScope, $q, $location, authoring, reloadService) => { - startAuthoring({headline: 'test'}, 'edit'); + startAuthoring({headline: 'test', profile: '123'}, 'edit'); $location.search('item', 'foo'); $location.search('action', 'edit'); $rootScope.$digest(); @@ -136,7 +136,7 @@ describe('authoring', () => { })); it('can populate content metadata for undo', inject(($rootScope) => { - var orig = {headline: 'foo'}; + var orig = {headline: 'foo', profile: '123'}; var scope = startAuthoring(orig, 'edit'); expect(scope.origItem.headline).toBe('foo'); @@ -155,11 +155,13 @@ describe('authoring', () => { let item = { _id: 'test', headline: 'headline', + profile: '123', }; let rewriteOf = { _id: 'rewriteOf', headline: 'rewrite', + profile: '123', associations: { featuremedia: { @@ -193,11 +195,13 @@ describe('authoring', () => { let item = { _id: 'test', headline: 'headline', + profile: '123', }; let rewriteOf = { _id: 'rewriteOf', headline: 'rewrite', + profile: '123', associations: { featuremedia: { @@ -233,6 +237,7 @@ describe('authoring', () => { _id: 'test', headline: 'headline', rewrite_of: 'rewriteOf', + profile: '123', }; let rewriteOf = { @@ -243,6 +248,7 @@ describe('authoring', () => { }, }, + profile: '123', }; let defered = $q.defer(); @@ -285,6 +291,7 @@ describe('authoring', () => { let item = { _id: 'test', rewrite_of: 'rewriteOf', + profile: '123', }; let rewriteOf = { @@ -294,6 +301,7 @@ describe('authoring', () => { test: 'test', }, }, + profile: '123', }; let defered = $q.defer(); diff --git a/scripts/apps/desks/directives/DeskConfigModal.ts b/scripts/apps/desks/directives/DeskConfigModal.ts index 5b675632c4..6534b9d6b7 100644 --- a/scripts/apps/desks/directives/DeskConfigModal.ts +++ b/scripts/apps/desks/directives/DeskConfigModal.ts @@ -56,7 +56,7 @@ export function DeskConfigModal(metadata, content, templates, api) { * Initialize content types * @return {Object} profiles */ - content.getTypes().then((profiles) => { + content.getTypes('text').then((profiles) => { scope.profiles = profiles; }); diff --git a/scripts/apps/desks/tests/contentExpiry.spec.ts b/scripts/apps/desks/tests/contentExpiry.spec.ts index aeb0e3ae93..a182aa50f2 100644 --- a/scripts/apps/desks/tests/contentExpiry.spec.ts +++ b/scripts/apps/desks/tests/contentExpiry.spec.ts @@ -1,6 +1,7 @@ describe('content expiry', () => { beforeEach(window.module('superdesk.apps.desks')); beforeEach(window.module('superdesk.templates-cache')); + beforeEach(window.module('superdesk.apps.spellcheck')); function setupElement(context, contentExpiry) { let scope, elem, iscope; diff --git a/scripts/apps/internal-destinations/InternalDestinations.tsx b/scripts/apps/internal-destinations/InternalDestinations.tsx index 9fe8f96c8f..0b76d3eb52 100644 --- a/scripts/apps/internal-destinations/InternalDestinations.tsx +++ b/scripts/apps/internal-destinations/InternalDestinations.tsx @@ -1,18 +1,18 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; -import {getGenericListPageComponent, GenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; +import {getGenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; import {ListItemColumn, ListItemActionsMenu, ListItem} from 'core/components/ListItem'; import {getFormFieldPreviewComponent} from 'core/ui/components/generic-form/form-field'; import {IInternalDestination} from 'superdesk-interfaces/InternalDestination'; -import {IFormField, IFormGroup} from 'superdesk-api'; +import {IFormField, IFormGroup, IPropsGenericFormItemComponent} from 'superdesk-api'; import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; import {gettext} from 'core/utils'; function getNameField(): IFormField { return { label: gettext('Destination name'), - type: FormFieldType.textSingleLine, + type: FormFieldType.plainText, field: 'name', required: true, }; @@ -73,55 +73,56 @@ function getSendAfterScheduleField(): IFormField { }; } -const renderRow = ( - key: string, - item: IInternalDestination, - page: GenericListPageComponent, -) => ( - page.openPreview(item._id)} - inactive={!item.is_active} - data-test-id="internal-destinations-item" - > - - {getFormFieldPreviewComponent(item, getNameField())} - - { - item.is_active ? null : ( - - {gettext('Inactive')} +class ItemComponent extends React.PureComponent> { + render() { + const {item, page} = this.props; + + return ( + page.openPreview(item._id)} + inactive={!item.is_active} + data-test-id="internal-destinations-item" + > + + {getFormFieldPreviewComponent(item, getNameField())} - ) - } - -
- - -
-
-
-); + { + item.is_active ? null : ( + + {gettext('Inactive')} + + ) + } + +
+ + +
+
+
+ ); + } +} export class InternalDestinations extends React.Component { render() { @@ -140,13 +141,16 @@ export class InternalDestinations extends React.Component { }; const InternalDestinationsPageComponent = - getGenericListPageComponent('internal_destinations', formConfig); + getGenericListPageComponent( + 'internal_destinations', + formConfig, + {field: 'name', direction: 'ascending'}, + ); return ( formConfig} fieldForSearch={getNameField()} /> ); diff --git a/scripts/apps/monitoring/controllers/AggregateCtrl.ts b/scripts/apps/monitoring/controllers/AggregateCtrl.ts index 6dc6e0fdaa..81adddc862 100644 --- a/scripts/apps/monitoring/controllers/AggregateCtrl.ts +++ b/scripts/apps/monitoring/controllers/AggregateCtrl.ts @@ -617,7 +617,7 @@ export function AggregateCtrl($scope, desks, workspaces, preferencesService, sto }); function getActiveProfiles() { - content.getTypes(false).then((profiles) => { + content.getTypes('text', false).then((profiles) => { self.loading = false; self.activeProfiles = profiles; diff --git a/scripts/apps/relations/directives/RelatedItemsDirective.ts b/scripts/apps/relations/directives/RelatedItemsDirective.ts index 6a3d9a2835..d1e1b03301 100644 --- a/scripts/apps/relations/directives/RelatedItemsDirective.ts +++ b/scripts/apps/relations/directives/RelatedItemsDirective.ts @@ -1,7 +1,7 @@ import {getSuperdeskType} from 'core/utils'; import {gettext} from 'core/utils'; import {AuthoringWorkspaceService} from 'apps/authoring/authoring/services/AuthoringWorkspaceService'; -import {IArticle, IArticleField, IRendition} from 'superdesk-api'; +import {IArticle, IVocabulary, IRendition} from 'superdesk-api'; import {IDirectiveScope} from 'types/Angular/DirectiveScope'; import {getAssociationsByFieldId} from '../../authoring/authoring/controllers/AssociationController'; import {getThumbnailForItem} from 'core/helpers/item'; @@ -12,7 +12,7 @@ const isInArchive = (item: IArticle) => item._type != null && ARCHIVE_TYPES.incl interface IScope extends IDirectiveScope { onCreated: (items: Array) => void; gettext: (text: any, params?: any) => string; - field: IArticleField; + field: IVocabulary; editable: boolean; item: IArticle; loading: boolean; diff --git a/scripts/apps/relations/services/RelationsService.ts b/scripts/apps/relations/services/RelationsService.ts index def79e0f1e..44d2552cbb 100644 --- a/scripts/apps/relations/services/RelationsService.ts +++ b/scripts/apps/relations/services/RelationsService.ts @@ -1,5 +1,5 @@ import {zipObject} from 'lodash'; -import {IArticle, IArticleField} from 'superdesk-api'; +import {IArticle, IVocabulary} from 'superdesk-api'; import {isPublished, isIngested} from 'apps/archive/utils'; const RELATED_LINK_KEYS = 3; // links only have _id, type keys and order (and some old ones only _id) @@ -44,7 +44,7 @@ export function RelationsService(api, $q) { }; }; - this.itemHasAllowedStatus = function(item: IArticle, field: IArticleField) { + this.itemHasAllowedStatus = function(item: IArticle, field: IVocabulary) { const ALLOWED_WORKFLOWS = { ...this.getDefaultAllowedWorkflows(), ...(field?.field_options?.allowed_workflows || {}), diff --git a/scripts/apps/search/components/Item.tsx b/scripts/apps/search/components/Item.tsx index 486ffc6660..e3ef4830f0 100644 --- a/scripts/apps/search/components/Item.tsx +++ b/scripts/apps/search/components/Item.tsx @@ -271,15 +271,17 @@ export class Item extends React.Component { httpRequestJsonLocal<{_items: Array}>({ method: 'GET', path: '/published', - urlParams: {source: JSON.stringify({ - query: { - bool: { - must: {term: {family_id: item.archive_item.family_id}}, - must_not: {term: {_id: item.item_id}}, + urlParams: { + source: { + query: { + bool: { + must: {term: {family_id: item.archive_item.family_id}}, + must_not: {term: {_id: item.item_id}}, + }, }, + sort: [{'versioncreated': 'desc'}], }, - sort: [{'versioncreated': 'desc'}], - })}, + }, }).then((data) => { this.setState({loading: false, nested: data._items}); }).catch(() => { diff --git a/scripts/apps/search/components/preview-subject.tsx b/scripts/apps/search/components/preview-subject.tsx index 1fcd7de58c..3469b5e86a 100644 --- a/scripts/apps/search/components/preview-subject.tsx +++ b/scripts/apps/search/components/preview-subject.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import {IArticle, IArticleField} from 'superdesk-api'; +import {IArticle, IVocabulary} from 'superdesk-api'; interface IProps { item: IArticle; - fields: Array; + fields: Array; } export class PreviewSubject extends React.PureComponent { diff --git a/scripts/apps/system-messages/components/system-messages-settings.tsx b/scripts/apps/system-messages/components/system-messages-settings.tsx index e1f97b0428..0a11e55348 100644 --- a/scripts/apps/system-messages/components/system-messages-settings.tsx +++ b/scripts/apps/system-messages/components/system-messages-settings.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-multi-comp */ import * as React from 'react'; import {gettext} from 'core/utils'; @@ -5,7 +6,7 @@ import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; import {ListItem, ListItemActionsMenu, ListItemColumn, ListItemRow} from 'core/components/ListItem'; import {getFormFieldPreviewComponent} from 'core/ui/components/generic-form/form-field'; import {getGenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; -import {IFormField, IFormGroup, IGenericListPageComponent} from 'superdesk-api'; +import {IFormField, IFormGroup, IPropsGenericFormItemComponent} from 'superdesk-api'; import {Label} from 'superdesk-ui-framework/react/components/Label'; import {assertNever} from 'core/helpers/typescript-helpers'; import {ISystemMessage, RESOURCE} from '..'; @@ -30,52 +31,58 @@ const getTypeLabel = (type: ISystemMessage['type']) => { } }; -export class SystemMessagesSettingsComponent extends React.PureComponent { - render() { - const formConfig: IFormGroup = { - type: 'inline', - direction: 'vertical', - form: [ - { - field: 'is_active', - label: gettext('Active'), - type: FormFieldType.checkbox, - }, - { - field: 'type', - label: gettext('Style'), - type: FormFieldType.select, - required: true, - component_parameters: { - options: [ - {id: 'primary', label: getTypeLabel('primary')}, - {id: 'success', label: getTypeLabel('success')}, - {id: 'warning', label: getTypeLabel('warning')}, - {id: 'alert', label: getTypeLabel('alert')}, - ], - }, - }, - { - field: 'message_title', - label: gettext('Title'), - type: FormFieldType.textSingleLine, - required: true, +/** + * It needs to be a function because calling gettext in the top level doesn't work + */ +function getFormConfig(): IFormGroup { + const formConfig: IFormGroup = { + type: 'inline', + direction: 'vertical', + form: [ + { + field: 'is_active', + label: gettext('Active'), + type: FormFieldType.checkbox, + }, + { + field: 'type', + label: gettext('Style'), + type: FormFieldType.select, + required: true, + component_parameters: { + options: [ + {id: 'primary', label: getTypeLabel('primary')}, + {id: 'success', label: getTypeLabel('success')}, + {id: 'warning', label: getTypeLabel('warning')}, + {id: 'alert', label: getTypeLabel('alert')}, + ], }, - { - field: 'message', - label: gettext('Message'), - type: FormFieldType.textEditor3, - required: true, - }, - ], - }; - - const renderRow = ( - key: string, - item: ISystemMessage, - page: IGenericListPageComponent, - ) => ( - page.openPreview(item._id)}> + }, + { + field: 'message_title', + label: gettext('Title'), + type: FormFieldType.plainText, + required: true, + }, + { + field: 'message', + label: gettext('Message'), + type: FormFieldType.textEditor3, + required: true, + }, + ], + }; + + return formConfig; +} + +class ItemComponent extends React.PureComponent> { + render() { + const {item, page} = this.props; + const formConfig = getFormConfig(); + + return ( + page.openPreview(item._id)}> @@ -127,15 +134,23 @@ export class SystemMessagesSettingsComponent extends React.PureComponent { ); + } +} - const ListComponent = getGenericListPageComponent(RESOURCE, formConfig); +export class SystemMessagesSettingsComponent extends React.PureComponent { + render() { + const formConfig = getFormConfig(); + const ListComponent = getGenericListPageComponent( + RESOURCE, + formConfig, + {field: 'message_title', direction: 'ascending'}, + ); return ( formConfig} /> ); diff --git a/scripts/apps/templates/directives/TemplatesDirective.ts b/scripts/apps/templates/directives/TemplatesDirective.ts index f28e75f0c6..e69719b543 100644 --- a/scripts/apps/templates/directives/TemplatesDirective.ts +++ b/scripts/apps/templates/directives/TemplatesDirective.ts @@ -35,7 +35,7 @@ export function TemplatesDirective(notify, api, templates, modal, desks, weekday selectDesk(null); }); - content.getTypes().then(() => { + content.getTypes('text').then(() => { $scope.content_types = content.types; }); diff --git a/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx b/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx new file mode 100644 index 0000000000..ce5cf4167f --- /dev/null +++ b/scripts/apps/workspace/content/components/ContentProfileFieldsConfig.tsx @@ -0,0 +1,638 @@ +/* eslint-disable react/no-multi-comp */ +import React from 'react'; +import { + ICrudManager, + ICrudManagerResponse, + IItemWithId, + IPropsGenericFormItemComponent, + IPropsGenericFormContainer, + IContentProfile, + IContentProfileEditorConfig, + IVocabulary, +} from 'superdesk-api'; + +import {gettext, arrayInsert, arrayMove} from 'core/utils'; +import {IContentProfileType} from '../controllers/ContentProfilesController'; +import {assertNever} from 'core/helpers/typescript-helpers'; +import {GenericListPageComponent} from 'core/ui/components/ListPage/generic-list-page'; +import {SortableContainer, SortableElement} from 'react-sortable-hoc'; +import {Button, IconButton, Dropdown} from 'superdesk-ui-framework/react'; +import {groupBy} from 'lodash'; +import {querySelectorParent} from 'core/helpers/dom/querySelectorParent'; +import ng from 'core/services/ng'; +import {getLabelForFieldId} from 'apps/workspace/helpers/getLabelForFieldId'; +import {getContentProfileFormConfig} from './get-content-profiles-form-config'; +import {getEditorConfig} from './get-editor-config'; +import {WidgetsConfig} from './WidgetsConfig'; + +// should be stored in schema rather than editor section of the content profile +// but the fields should be editable via GUI +enum ISchemaFields { + readonly = 'readonly', + required = 'required', + minlength = 'minlength', + maxlength = 'maxlength', +} + +const allSchemaFieldKeys: Array = + Object.keys(ISchemaFields).map((key) => ISchemaFields[key]); + +function isSchemaKey(x: string): x is keyof typeof ISchemaFields { + return ISchemaFields[x] != null; +} + +type ISchemaKey = keyof typeof ISchemaFields; + +// this is UI specific data structure +// when saving, data from it will be converted and written to schema/editor sections of the content profile +type IContentProfileField = valueof & {id: string} & {[key in ISchemaKey]?: any}; + +interface IAdditionalProps { + additionalProps: { + sortingInProgress: boolean; + setIndexForNewItem(index: number): void; + getLabel(id: string): string; + availableIds: Array<{id: string; label: string}>; + }; +} + +interface IProps { + profile: IContentProfile; + profileType: keyof typeof IContentProfileType; + patchContentProfile(patch: Partial): void; +} + +interface IState { + fields: {[key in IContentProfileSection]: Array} | null; + allFieldIds: Array | null; + selectedSection: keyof typeof IContentProfileSection; + activeTab: IState['selectedSection'] | 'widgets'; + sortingInProgress: boolean; + insertNewItemAtIndex: number | null; + vocabularies: Array; + editor: IContentProfileEditorConfig | null; + schema: any | null; + customFields: any | null; + loading: boolean; +} + +enum IContentProfileSection { + header = 'header', + content = 'content', +} + +function getAllContentProfileSections(): Array { + return Object.keys(IContentProfileSection).map((key) => IContentProfileSection[key]); +} + +function getTabs(): Array<{label: string, value: IState['activeTab']}> { + return [ + ...getAllContentProfileSections().map((section) => ({ + label: getLabelForSection(section), + value: section, + })), + {label: gettext('Widgets'), value: 'widgets'}, + ]; +} + +function getLabelForSection(section: IContentProfileSection) { + if (section === IContentProfileSection.header) { + return gettext('Header fields'); + } else if (section === IContentProfileSection.content) { + return gettext('Content fields'); + } else { + return assertNever(section); + } +} + +type IPropsItem = IPropsGenericFormItemComponent & IAdditionalProps; + +// wrapper is used because sortable HOC considers `index` to be its internal prop and doesn't forward it +class ItemBase extends React.PureComponent<{wrapper: IPropsItem}> { + render() { + const {item, page, index, inEditMode} = this.props.wrapper; + const {sortingInProgress, setIndexForNewItem, getLabel, availableIds} = this.props.wrapper.additionalProps; + const isLast = index === page.getItemsCount() - 1; + + const newFieldSelect = (newItemIndex) => ( + ({ + label: label, + onSelect: () => { + setIndexForNewItem(newItemIndex); + page.openNewItemForm({_id: id}); + }, + }))} + > + + )) + } +
+ ); + + if (this.state.activeTab === 'widgets') { + return ( + + {tabs} + +
+ { + this.props.patchContentProfile({ + widgets_config, + }); + }} + /> +
+
+ ); + } else { + const {sortingInProgress} = this.state; + const fields = this.state.fields[this.state.selectedSection]; + + const fieldsResponse: ICrudManagerResponse = { + _items: fields.map((field) => ({...field, _id: field.id})), + _meta: {total: fields.length, page: 1, max_results: fields.length}, + }; + + const crudManagerForContentProfileFields: ICrudManager = { + activeFilters: {}, + read: () => Promise.resolve(fieldsResponse), + update: (item) => { + const itemWithId = {...item, id: item._id}; + + return new Promise((resolve) => { + this.setState( + { + fields: this.updateCurrentFields( + (_fields) => { + return _fields.map((field) => { + if (field.id === itemWithId._id) { + return stripSystemId(itemWithId); + } else { + return field; + } + }); + }, + ), + }, + () => { + resolve(item); + }, + ); + }); + }, + create: (item) => { + if (item._id == null) { + throw new Error('id must be provided'); + } + + return new Promise((resolve) => { + const itemWithId: IContentProfileFieldWithSystemId = { + ...item, + _id: item._id, + id: item._id, + }; + + this.setState( + { + insertNewItemAtIndex: null, + fields: this.updateCurrentFields( + (_fields) => { + return arrayInsert( + _fields, + stripSystemId(itemWithId), + this.state.insertNewItemAtIndex ?? 0, + ); + }, + ), + }, + () => { + resolve(itemWithId); + }, + ); + }); + }, + delete: (item) => { + return new Promise((resolve) => { + this.setState( + { + fields: this.updateCurrentFields( + (_fields) => _fields.filter( + (field) => field.id !== item._id, + ), + ), + }, + () => { + resolve(); + }, + ); + }); + }, + refresh: () => Promise.resolve(fieldsResponse), + sort: () => Promise.resolve(fieldsResponse), + removeFilter: () => Promise.resolve(fieldsResponse), + goToPage: () => Promise.resolve(fieldsResponse), + _items: fieldsResponse._items, + _meta: fieldsResponse._meta, + }; + + const getLabel = (id) => { + return this.state.editor[id]?.field_name ?? getLabelForFieldId(id, this.state.vocabularies); + }; + + const availableIds: Array<{id: string; label: string}> = this.state.allFieldIds + .filter((id) => { + return ( + this.isAllowedForSection(this.state.selectedSection, id) + && !this.existsInFields(id) + ); + }) + .map((id) => ({id, label: getLabel(id)})); + + return ( +
+ {tabs} + + { + return getContentProfileFormConfig( + this.state.editor, + this.state.schema, + this.state.customFields, + item, + ); + }} + ItemComponent={ItemComponent} + ItemsContainerComponent={this.ItemsContainerComponent} + items={crudManagerForContentProfileFields} + additionalProps={{ + sortingInProgress, + availableIds, + setIndexForNewItem: (index) => { + this.setState({insertNewItemAtIndex: index}); + }, + getLabel, + }} + disallowFiltering + disallowCreatingNewItem + contentMargin={0} + /> +
+ ); + } + } +} diff --git a/scripts/apps/workspace/content/components/get-content-profiles-form-config.tsx b/scripts/apps/workspace/content/components/get-content-profiles-form-config.tsx new file mode 100644 index 0000000000..fcd82d810e --- /dev/null +++ b/scripts/apps/workspace/content/components/get-content-profiles-form-config.tsx @@ -0,0 +1,346 @@ +import {FormFieldType} from 'core/ui/components/generic-form/interfaces/form'; +import { + IFormField, + IFormGroup, + IContentProfileEditorConfig, + FORMATTING_OPTION, + PLAINTEXT_FORMATTING_OPTION, + RICH_FORMATTING_OPTION, +} from 'superdesk-api'; +import {gettext} from 'core/utils'; +import {IContentProfileFieldWithSystemId} from './ContentProfileFieldsConfig'; +import {appConfig} from 'appConfig'; + +const HAS_PLAINTEXT_FORMATTING_OPTIONS = Object.freeze({ + headline: true, +}); + +const HAS_RICH_FORMATTING_OPTIONS = Object.freeze({ + abstract: true, + body_html: true, + footer: true, + body_footer: true, +}); + +const FORMATTING_OPTIONS: Array = [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'justifyLeft', + 'justifyCenter', + 'justifyRight', + 'justifyFull', + 'outdent', + 'indent', + 'unordered list', + 'ordered list', + 'pre', + 'quote', + 'media', + 'link', + 'superscript', + 'subscript', + 'strikethrough', + 'underline', + 'italic', + 'bold', + 'table', +]; + +const EDITOR3_PLAINTEXT_FORMATTING_OPTIONS: Array = [ + 'uppercase', + 'lowercase', +]; + +const EDITOR3_RICH_FORMATTING_OPTIONS: Array = [ + ...EDITOR3_PLAINTEXT_FORMATTING_OPTIONS, + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'ordered list', + 'unordered list', + 'quote', + 'media', + 'link', + 'embed', + 'underline', + 'italic', + 'bold', + 'table', + 'formatting marks', + 'remove format', + 'remove all format', + 'annotation', + 'comments', + 'suggestions', + 'pre', + 'superscript', + 'subscript', + 'strikethrough', + 'tab', + 'tab as spaces', + 'undo', + 'redo', +]; + +export const formattingOptionsUnsafeToParseFromHTML: Array = [ + // these aren't outputted to HTML at all + 'comments', + 'suggestions', + + // no standard in HTML, parsing according to our output format is not implemented + 'annotation', + + // may not be parsed well + 'pre', + 'embed', + 'media', + 'table', +]; + +function hasFormattingOptions(fieldId: string, editor, customFields: Array) { + return Object.keys(HAS_RICH_FORMATTING_OPTIONS).includes(fieldId) + || ( + editor?.body_html?.editor3 === true + && Object.keys(HAS_PLAINTEXT_FORMATTING_OPTIONS).includes(fieldId) + ) + || customFields.find(({_id}) => fieldId === _id)?.field_type === 'text'; +} + +function getEditor3FormattingOptions(fieldId, customFields: Array) { + const isCustomPlainTextField = customFields.find(({_id}) => fieldId === _id)?.field_type === 'text'; + + if (Object.keys(HAS_RICH_FORMATTING_OPTIONS).includes(fieldId) || isCustomPlainTextField) { + return EDITOR3_RICH_FORMATTING_OPTIONS; + } else { + return EDITOR3_PLAINTEXT_FORMATTING_OPTIONS; + } +} + +export function getContentProfileFormConfig( + editor: IContentProfileEditorConfig, + schema: any, + customFields: Array, + field?: Partial | undefined, +): IFormGroup { + const customField = field?.id != null ? customFields.find(({_id}) => field.id === _id) : null; + + const sdWidthField: IFormField = { + label: gettext('Width'), + type: FormFieldType.select, + component_parameters: { + options: [ + {id: 'full', label: gettext('Full')}, + {id: 'half', label: gettext('Half')}, + {id: 'quarter', label: gettext('Quarter')}, + ], + style: {boxed: true}, + }, + field: 'sdWidth', + required: true, + }; + + const requiredField: IFormField = { + label: gettext('Required'), + type: FormFieldType.checkbox, + field: 'required', + required: false, + }; + + const readonlyField: IFormField = { + label: gettext('Read-only'), + type: FormFieldType.checkbox, + field: 'readonly', + required: false, + }; + + const fields: Array = [ + requiredField, + readonlyField, + sdWidthField, + ]; + + if ( + field?.id != null + && ( + schema[field.id].type === 'string' + || customField?.field_type === 'text' + ) + ) { + const minimumLengthField: IFormField = { + label: gettext('Minimum length'), + type: FormFieldType.number, + field: 'minlength', + required: false, + component_parameters: { + style: {boxed: true}, + }, + }; + + const maximumLengthField: IFormField = { + label: gettext('Maximum length'), + type: FormFieldType.number, + field: 'maxlength', + required: false, + component_parameters: { + style: {boxed: true}, + }, + }; + + const minMax: IFormGroup = { + direction: 'horizontal', + type: 'inline', + form: [minimumLengthField, maximumLengthField], + }; + + fields.push(minMax); + } + + if (field?.id === 'dateline') { + const hideDateField: IFormField = { + label: gettext('Hide Date'), + type: FormFieldType.checkbox, + field: 'hideDate', + required: false, + }; + + fields.push(hideDateField); + } + + // enable preview config for CVs + // we display other fields by default already + if (field?.id != null && customField != null && customField.field_type == null) { + const showInPreviewField: IFormField = { + label: gettext('Show in preview'), + type: FormFieldType.checkbox, + field: 'preview', + required: false, + }; + + fields.push(showInPreviewField); + } + + if (field?.id != null && schema[field.id]?.type === 'string') { + const cleanPastedHtmlField: IFormField = { + label: gettext('Clean Pasted HTML'), + type: FormFieldType.checkbox, + field: 'cleanPastedHTML', + required: false, + }; + + fields.push(cleanPastedHtmlField); + } + + const characterValidationEnabled = appConfig?.disallowed_characters != null; + + if ( + field?.id != null + && characterValidationEnabled + && ( + schema[field.id]?.type === 'string' + || customField?.field_type === 'text' + ) + ) { + const validateCharactersField: IFormField = { + label: gettext('Validate Characters'), + type: FormFieldType.checkbox, + field: 'validate_characters', + required: false, + }; + + fields.push(validateCharactersField); + } + + const formConfig: IFormGroup = { + direction: 'vertical', + type: 'inline', + form: fields, + }; + + if (field?.id != null && hasFormattingOptions(field.id, editor, customFields)) { + const editor3Enabled = editor?.body_html?.editor3 === true; + + if (editor3Enabled) { + const formattingOptionsEditor3Field: IFormField = { + label: gettext('Formatting options'), + type: FormFieldType.selectMultiple, + field: 'formatOptions', + required: false, + component_parameters: { + items: getEditor3FormattingOptions(field.id, customFields) + .map((option) => ({id: option, label: option})), + }, + }; + + fields.push(formattingOptionsEditor3Field); + } else { + const formattingOptionsEditor2Field: IFormField = { + label: gettext('Formatting options'), + type: FormFieldType.selectMultiple, + field: 'formatOptions', + required: false, + component_parameters: { + items: FORMATTING_OPTIONS.map((option) => ({id: option, label: option})), + }, + }; + + fields.push(formattingOptionsEditor2Field); + } + } + + if (field?.id != null && field.id === 'feature_media' && schema[field.id].type === 'media') { + const showCropsField: IFormField = { + label: gettext('Show Crops'), + type: FormFieldType.checkbox, + field: 'showCrops', + required: false, + }; + + fields.push(showCropsField); + } + + if ( + field?.id != null + && ( + schema[field.id].type === 'media' + || ( + hasFormattingOptions(field.id, editor, customFields) + && field.formatOptions?.includes('media') === true + ) + ) + ) { + const showImageTitleField: IFormField = { + label: gettext('Show Image Title'), + type: FormFieldType.checkbox, + field: 'imageTitle', + required: false, + }; + + fields.push(showImageTitleField); + } + + if (field?.id === 'sms') { + const prefillSmsField: IFormField = { + label: gettext('Prefill the field with text from:'), + type: FormFieldType.select, + component_parameters: { + options: [ + {id: '', label: gettext('Abstract')}, + {id: 'headline', label: gettext('Headline')}, + ], + }, + field: 'sourceField', + required: false, + }; + + fields.push(prefillSmsField); + } + + return formConfig; +} diff --git a/scripts/apps/workspace/content/components/get-editor-config.tsx b/scripts/apps/workspace/content/components/get-editor-config.tsx new file mode 100644 index 0000000000..434015da6d --- /dev/null +++ b/scripts/apps/workspace/content/components/get-editor-config.tsx @@ -0,0 +1,113 @@ +import ng from 'core/services/ng'; +import {assertNever} from 'core/helpers/typescript-helpers'; +import {IContentProfileEditorConfig, IArticle} from 'superdesk-api'; +import {getFields} from 'apps/fields'; + +export const ARTICLE_HEADER_FIELDS = new Set([ + 'keywords', + 'genre', + 'anpa_take_key', + 'place', + 'language', + 'priority', + 'urgency', + 'anpa_category', + 'subject', + 'company_codes', + 'ednote', + 'authors', +]); + +export const ARTICLE_COMMON_FIELDS = new Set([ + 'slugline', +]); + +export const getArticleHeaderFields = (customVocabulariesForArticleHeader): Set => { + const articleHeaderFields = new Set(); + + ARTICLE_HEADER_FIELDS.forEach((id) => { + articleHeaderFields.add(id); + }); + + customVocabulariesForArticleHeader.forEach((filteredCustomField) => { + articleHeaderFields.add(filteredCustomField._id); + }); + + return articleHeaderFields; +}; + +const getArticleCommonFields = (customTextAndDateVocabularies, customFields): Set => { + const articleCommonFields = new Set(); + + ARTICLE_COMMON_FIELDS.forEach((id) => { + articleCommonFields.add(id); + }); + + const fieldsFromExtensions = Object.keys(getFields()); + + customFields.forEach((customField) => { + if ( + customField.custom_field_type != null + && fieldsFromExtensions.includes(customField.custom_field_type) + ) { + articleCommonFields.add(customField._id); + } + }); + + customTextAndDateVocabularies.forEach((filteredCustomField) => { + articleCommonFields.add(filteredCustomField._id); + }); + + return articleCommonFields; +}; + +export function getEditorConfig(contentTypeId) { + const content = ng.get('content'); + const metadata = ng.get('metadata'); + + return Promise.all([ + content.getCustomFields(), + content.getTypeMetadata(contentTypeId), + ]).then((res) => { + const [customFields, typeMetadata] = res; + + let editor: IContentProfileEditorConfig = angular.extend({}, content.contentProfileEditor); + + editor = angular.extend({}, typeMetadata.editor); + + let schema = angular.extend({}, content.contentProfileSchema); + + schema = angular.extend({}, typeMetadata.schema); + + return metadata.getAllCustomVocabulariesForArticleHeader( + editor, + schema, + ).then(({customVocabulariesForArticleHeader, customTextAndDateVocabularies}) => { + const articleHeaderFields = getArticleHeaderFields(customVocabulariesForArticleHeader); + const articleCommonFields = getArticleCommonFields(customTextAndDateVocabularies, customFields); + + for (const key in editor) { + const isHeaderField = articleHeaderFields.has(key) || articleCommonFields.has(key); + const section = editor[key].section || (isHeaderField ? 'header' : 'content'); + + if (editor[key] != null) { + editor[key].section = section; + } + } + + return { + editor, + schema, + isAllowedForSection: (section: 'header' | 'content', fieldId) => { + if (section === 'header') { + return articleHeaderFields.has(fieldId) || articleCommonFields.has(fieldId); + } else if (section === 'content') { + return !articleHeaderFields.has(fieldId); + } else { + return assertNever(section); + } + }, + }; + }); + }); +} diff --git a/scripts/apps/workspace/content/constants.ts b/scripts/apps/workspace/content/constants.ts index 4acafaaf5c..23f4588fe0 100644 --- a/scripts/apps/workspace/content/constants.ts +++ b/scripts/apps/workspace/content/constants.ts @@ -1,67 +1,6 @@ import {gettext} from 'core/utils'; import {IStage} from 'superdesk-api'; import {ICard} from 'apps/monitoring/services/CardsService'; -import {SplitFilter} from 'apps/monitoring/filters'; - -// http://docs.python-cerberus.org/en/stable/usage.html -export const DEFAULT_SCHEMA = Object.freeze({ - slugline: {maxlength: 24, type: 'string', required: true}, - relatedItems: {}, - genre: {type: 'list'}, - anpa_take_key: {type: 'string'}, - place: {type: 'list'}, - priority: {type: 'integer'}, - urgency: {type: 'integer'}, - anpa_category: {type: 'list', required: true}, - subject: {type: 'list', required: true}, - company_codes: {type: 'list'}, - ednote: {type: 'string'}, - headline: {maxlength: 42, type: 'string', required: true}, - sms: {maxlength: 160, type: 'string'}, - abstract: {maxlength: 160, type: 'string'}, - body_html: {required: true, type: 'string'}, - byline: {type: 'string'}, - dateline: {type: 'dict', required: true}, - sign_off: {type: 'string'}, - footer: {}, - body_footer: {type: 'string'}, -}); - -export function GET_DEFAULT_EDITOR() { - const editor3enabled = true; - - return Object.freeze({ - slugline: {order: 1, sdWidth: 'full', enabled: true}, - genre: {order: 2, sdWidth: 'half', enabled: true}, - anpa_take_key: {order: 3, sdWidth: 'half', enabled: true}, - place: {order: 4, sdWidth: 'half', enabled: true}, - priority: {order: 5, sdWidth: 'quarter', enabled: true}, - urgency: {order: 6, sdWidth: 'quarter', enabled: true}, - anpa_category: {order: 7, sdWidth: 'full', enabled: true}, - subject: {order: 8, sdWidth: 'full', enabled: true}, - company_codes: {order: 9, sdWidth: 'full', enabled: true}, - ednote: {order: 10, sdWidth: 'full', enabled: true}, - headline: {order: 11, formatOptions: ['underline', 'link', 'bold'], editor3: editor3enabled, enabled: true}, - sms: {order: 12, enabled: true}, - abstract: { - order: 13, - formatOptions: ['bold', 'italic', 'underline', 'link'], - enabled: true, - editor3: editor3enabled, - }, - byline: {order: 14, enabled: true}, - dateline: {order: 15, enabled: true}, - body_html: { - order: 16, - formatOptions: ['h2', 'bold', 'italic', 'underline', 'quote', 'link', 'embed', 'media'], - enabled: true, - editor3: editor3enabled, - }, - footer: {order: 17, enabled: true}, - body_footer: {order: 18, enabled: true}, - sign_off: {order: 19, enabled: true}, - }); -} // labelMap maps schema entry keys to their display names. export const GET_LABEL_MAP = () => ({ diff --git a/scripts/apps/workspace/content/controllers/ContentProfileFields.ts b/scripts/apps/workspace/content/controllers/ContentProfileFields.ts deleted file mode 100644 index 2f5ffb7abb..0000000000 --- a/scripts/apps/workspace/content/controllers/ContentProfileFields.ts +++ /dev/null @@ -1,208 +0,0 @@ -import {get, keyBy} from 'lodash'; -import {IArticle} from 'superdesk-api'; -import {getLabelForFieldId} from '../../helpers/getLabelForFieldId'; -import {getTypeForFieldId} from '../../helpers/getTypeForFieldId'; -import {getFields} from 'apps/fields'; - -export const ARTICLE_HEADER_FIELDS = new Set([ - 'keywords', - 'genre', - 'anpa_take_key', - 'place', - 'language', - 'priority', - 'urgency', - 'anpa_category', - 'subject', - 'company_codes', - 'ednote', - 'authors', -]); - -export const ARTICLE_COMMON_FIELDS = new Set([ - 'slugline', -]); - -ContentProfileFields.$inject = ['$scope', 'content', 'vocabularies', 'metadata']; -export default function ContentProfileFields($scope, content, vocabularies, metadata) { - this.model = $scope.editing.form; - this.fieldsById = {}; - this.fieldsBySection = {}; - - const getOrder = (field) => get(this.model.editor[field], 'order') || 99; - const isEnabled = (field) => get(this.model.editor[field], 'enabled', false); - - Promise.all([ - content.getCustomFields(), - content.getTypeMetadata(this.model._id), - vocabularies.getVocabularies(), - ]).then((res) => { - const [customFields, typeMetadata, vocabulariesCollection] = res; - - const label = (id) => getLabelForFieldId(id, vocabulariesCollection); - - const getArticleHeaderFields = (customVocabulariesForArticleHeader) => { - const articleHeaderFields = new Set(); - - ARTICLE_HEADER_FIELDS.forEach((id) => { - articleHeaderFields.add(id); - }); - - customVocabulariesForArticleHeader.forEach((filteredCustomField) => { - articleHeaderFields.add(filteredCustomField._id); - }); - - return articleHeaderFields; - }; - - const getArticleCommonFields = (customTextAndDateVocabularies) => { - const articleCommonFields = new Set(); - - ARTICLE_COMMON_FIELDS.forEach((id) => { - articleCommonFields.add(id); - }); - - const fieldsFromExtensions = Object.keys(getFields()); - - customFields.forEach((customField) => { - if ( - customField.custom_field_type != null - && fieldsFromExtensions.includes(customField.custom_field_type) - ) { - articleCommonFields.add(customField._id); - } - }); - - customTextAndDateVocabularies.forEach((filteredCustomField) => { - articleCommonFields.add(filteredCustomField._id); - }); - - return articleCommonFields; - }; - - const initFields = (customVocabulariesForArticleHeader, customTextAndDateVocabularies) => { - this.articleHeaderFields = getArticleHeaderFields(customVocabulariesForArticleHeader); - this.articleCommonFields = getArticleCommonFields(customTextAndDateVocabularies); - - this.enabled = Object.keys(this.model.editor) - .filter(isEnabled) - .sort((a, b) => getOrder(a) - getOrder(b)); - - updateSections(); - }; - - this.model.schema = angular.extend({}, content.contentProfileSchema); - this.model.editor = angular.extend({}, content.contentProfileEditor); - - this.model.schema = angular.extend({}, typeMetadata.schema); - this.model.editor = angular.extend({}, typeMetadata.editor); - this.fieldsById = keyBy(customFields, '_id'); - - metadata.getAllCustomVocabulariesForArticleHeader( - this.model.editor, - this.model.schema, - ).then(({customVocabulariesForArticleHeader, customTextAndDateVocabularies}) => { - initFields(customVocabulariesForArticleHeader, customTextAndDateVocabularies); - }); - - const formatKey = (key: string) => ({ - key: key, - name: this.model.editor[key].field_name || label(key), - type: getTypeForFieldId(key, vocabulariesCollection), - }); - - const updateSections = () => { - this.sections = { - header: { - enabled: [], - available: [], - }, - content: { - enabled: [], - available: [], - }, - }; - - this.enabled.forEach((key, index) => { - const headerField = this.articleHeaderFields.has(key) || this.articleCommonFields.has(key); - const section = this.model.editor[key].section || (headerField ? 'header' : 'content'); - - this.sections[section].enabled.push(key); - this.model.editor[key].order = index + 1; // keep order in sync - // keep required value of editor fields in sync with the schema fields SDNTB-615 - // some keys might be missing from schema but will be present inside editor - // as we store value of some keys inside another key ex: any custom cv is stored inside "subject" - this.model.editor[key].required = this.model.schema[key]?.required ?? false; - }); - - Object.keys(this.model.editor) - .filter((key) => !isEnabled(key)) - .forEach((key) => { - const headerField = this.articleHeaderFields.has(key) || this.articleCommonFields.has(key); - const contentField = this.articleCommonFields.has(key) || !headerField; - - if (headerField) { - this.sections.header.available.push(formatKey(key)); - } - - if (contentField) { - this.sections.content.available.push(formatKey(key)); - } - - this.model.editor[key].order = null; - }); - }; - - const updateModel = (key, editorUpdates, schemaUpdates) => { - const editor = {...this.model.editor}; - const schema = {...this.model.schema}; - - editor[key] = Object.assign({}, editor[key], editorUpdates); - schema[key] = Object.assign({}, schema[key], schemaUpdates); - - this.model.editor = editor; - this.model.schema = schema; - }; - - /** - * @description Disable field in content profile - * @param {String} key the key of the field to toggle. - */ - this.remove = (key) => { - updateModel(key, {enabled: false, section: null}, {enabled: false}); - this.removeEnabled(key); - updateSections(); - }; - - this.add = (key, dest, position, section) => { - updateModel(key, {enabled: true, section: section}, {enabled: true}); - - let destIndex = this.enabled.indexOf(dest); - - if (position === 'after') { - destIndex++; - } - - this.enabled.splice(destIndex, 0, key); - updateSections(); - }; - - this.reorder = (key) => { - this.removeEnabled(key); - this.enabled.splice(this.model.editor[key].order, 0, key); - updateSections(); - }; - - this.drag = (start, end, key) => { - const dest = this.enabled.indexOf(key) + end - start; - - this.removeEnabled(key); - this.enabled.splice(dest, 0, key); - updateSections(); - }; - - this.removeEnabled = (key) => { - this.enabled = this.enabled.filter((_key) => _key !== key); - }; - }); -} diff --git a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts index b4eb2100f5..b623a4a95b 100644 --- a/scripts/apps/workspace/content/controllers/ContentProfilesController.ts +++ b/scripts/apps/workspace/content/controllers/ContentProfilesController.ts @@ -1,12 +1,82 @@ import {cloneDeep, get, isEqual} from 'lodash'; import {gettext} from 'core/utils'; -import {IContentProfile} from 'superdesk-api'; +import {IContentProfile, IRestApiResponse} from 'superdesk-api'; import {appConfig} from 'appConfig'; +import {assertNever, nameof} from 'core/helpers/typescript-helpers'; +import {httpRequestJsonLocal} from 'core/helpers/network'; + +export enum IContentProfileType { + text = 'text', + image = 'image', + audio = 'audio', + video = 'video', + package = 'package', +} + +const allContentProfileTypes: Array = + Object.keys(IContentProfileType).map((key) => IContentProfileType[key]); + +interface IScope extends ng.IScope { + showInfoBubble: boolean; + creating: boolean; + editing: {[key: string]: any}; + new: {[key: string]: any}; + active_only: boolean; + contentTypeFilter: string | null; + ngForm: any; + contentProfileTypes: Array<{ + label: string; + value: string; + disabled: boolean; + icon: string; + }>; + setNgForm(ngForm): void; + patchContentProfile(patch: Partial): void; + getContentProfileIconByProfileType(type: IContentProfile['type']): string; + toggleContentProfileFilter(type: IContentProfile['type']): void; +} + +function getContentProfileIcon(type: IContentProfileType): string { + switch (type) { + case IContentProfileType.text: + return 'icon-text'; + case IContentProfileType.image: + return 'icon-picture'; + case IContentProfileType.audio: + return 'icon-audio'; + case IContentProfileType.video: + return 'icon-video'; + case IContentProfileType.package: + return 'icon-composite'; + default: + return 'icon-text'; + } +} + +function getLabelForContentProfileType(type: IContentProfileType): string { + switch (type) { + case IContentProfileType.text: + return gettext('Text'); + case IContentProfileType.image: + return gettext('Image'); + case IContentProfileType.audio: + return gettext('Audio'); + case IContentProfileType.video: + return gettext('Video'); + case IContentProfileType.package: + return gettext('Package'); + default: + return assertNever(type); + } +} ContentProfilesController.$inject = ['$scope', '$location', 'notify', 'content', 'modal', '$q']; -export function ContentProfilesController($scope, $location, notify, content, modal, $q) { +export function ContentProfilesController($scope: IScope, $location, notify, content, modal, $q) { var self = this; + // info bubble + $scope.showInfoBubble = true; + // creating will be true while the modal for creating a new content // profile is visible. $scope.creating = false; @@ -16,9 +86,7 @@ export function ContentProfilesController($scope, $location, notify, content, mo // be null. $scope.editing = null; - // if true, only active Content Profiles will be shown - // can be changed with a button - $scope.active_only = true; + $scope.active_only = false; // required for being able to mark the form as dirty and enable the save button // after saving content profile widgets config @@ -26,20 +94,35 @@ export function ContentProfilesController($scope, $location, notify, content, mo $scope.ngForm = ngForm; }; - $scope.saveContentProfileWidgetsConfig = (nextWidgetsConfig: IContentProfile['widgets_config']) => { - $scope.editing.form.widgets_config = nextWidgetsConfig; + $scope.patchContentProfile = (patch: Partial) => { + Object.assign($scope.editing.form, patch); + $scope.$applyAsync(() => { $scope.ngForm.$dirty = true; }); }; + $scope.getContentProfileIconByProfileType = (type: IContentProfile['type']) => { + return getContentProfileIcon(IContentProfileType[type]); + }; + + $scope.contentTypeFilter = null; + + $scope.toggleContentProfileFilter = (type: IContentProfile['type']) => { + if ($scope.contentTypeFilter === type) { + $scope.contentTypeFilter = null; + } else { + $scope.contentTypeFilter = type; + } + }; + /** * @description Refreshes the list of content profiles by fetching them. * @returns {Promise} * @private */ function refreshList(callEditActive) { - return content.getTypes(true).then((types) => { + return content.getTypes(null, true).then((types) => { self.items = types; if (callEditActive) { editActive(); @@ -73,6 +156,27 @@ export function ContentProfilesController($scope, $location, notify, content, mo } } + function setContentProfiles() { + $scope.contentProfileTypes = []; // loading + + httpRequestJsonLocal>({ + method: 'GET', + path: '/content_types', + urlParams: { + where: {type: {$ne: 'text'}}, + }, + }).then((res) => { + const existingTypes = new Set(res._items.map((profile) => IContentProfileType[profile.type])); + + $scope.contentProfileTypes = allContentProfileTypes.map((type) => ({ + label: getLabelForContentProfileType(type), + value: type, + disabled: existingTypes.has(type), + icon: getContentProfileIcon(type), + })); + }); + } + /** * @description Reports that an error has occurred. * @private @@ -85,6 +189,12 @@ export function ContentProfilesController($scope, $location, notify, content, mo return $q.reject(resp); } + $scope.$on('resource:updated', (event, data) => { + if (data.resource === 'content_types' && data.fields[nameof('type')] === 1) { + setContentProfiles(); + } + }); + /** * @description Middle-ware that checks an error response to verify whether * it is a duplication error. @@ -129,6 +239,11 @@ export function ContentProfilesController($scope, $location, notify, content, mo * @description Creates a new content profile. */ this.save = function() { + if ($scope.new?.type == null) { + notify.error(gettext('"{{x}}" field is required', {x: 'content type'})); + return; + } + var onSuccess = function(resp) { refreshList(true); self.toggleCreate(); @@ -171,4 +286,5 @@ export function ContentProfilesController($scope, $location, notify, content, mo }; refreshList(true); + setContentProfiles(); } diff --git a/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts b/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts deleted file mode 100644 index 943107b632..0000000000 --- a/scripts/apps/workspace/content/directives/ContentProfileSchemaEditor.ts +++ /dev/null @@ -1,227 +0,0 @@ -import {includes} from 'lodash'; -import {getLabelForFieldId} from '../../helpers/getLabelForFieldId'; -import {appConfig} from 'appConfig'; -import {IArticleField, FORMATTING_OPTION, RICH_FORMATTING_OPTION} from 'superdesk-api'; - -interface IScope extends ng.IScope { - getEditor3FormattingOptions: (fieldName: string) => Array; - model: any; - fields: {[key: string]: IArticleField}; - form: any; - formattingOptions: Array; - schemaKeysOrdering: any; - schemaKeysDisabled: any; - characterValidationEnabled: boolean; - hasFormatOptions(field): boolean; - hasImageSelected(field): boolean; - label(id): string; - remove(key: string): void; - toggle(schema: { key: string; }, key: string, position: 'before' | 'after'): void; - reorder(start: number, end: number, key: string): void; - setDirty(): void; - updateOrder(key?: any): void; - onRemove(locals: { key: string; }): void; - onOrderUpdate(locals: { key: string; }): any; - onDrag(locals: { start: number; end: number; key: string; }): any; - onToggle(locals: { key: string; dest: string; position: string; }): any; - showPreviewConfig(field: IArticleField): boolean; -} - -const HAS_PLAINTEXT_FORMATTING_OPTIONS = Object.freeze({ - headline: true, -}); - -const HAS_RICH_FORMATTING_OPTIONS = Object.freeze({ - abstract: true, - body_html: true, - footer: true, - body_footer: true, -}); - -const FORMATTING_OPTIONS: Array = [ - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'justifyLeft', - 'justifyCenter', - 'justifyRight', - 'justifyFull', - 'outdent', - 'indent', - 'unordered list', - 'ordered list', - 'pre', - 'quote', - 'media', - 'link', - 'superscript', - 'subscript', - 'strikethrough', - 'underline', - 'italic', - 'bold', - 'table', -]; - -export type PLAINTEXT_FORMATTING_OPTION = 'uppercase' | 'lowercase'; - -const EDITOR3_PLAINTEXT_FORMATTING_OPTIONS: Array = [ - 'uppercase', - 'lowercase', -]; - -export const formattingOptionsUnsafeToParseFromHTML: Array = [ - // these aren't outputted to HTML at all - 'comments', - 'suggestions', - - // no standard in HTML, parsing according to our output format is not implemented - 'annotation', - - // may not be parsed well - 'pre', - 'embed', - 'media', - 'table', -]; - -const EDITOR3_RICH_FORMATTING_OPTIONS: Array = [ - ...EDITOR3_PLAINTEXT_FORMATTING_OPTIONS, - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'ordered list', - 'unordered list', - 'quote', - 'media', - 'link', - 'embed', - 'underline', - 'italic', - 'bold', - 'table', - 'formatting marks', - 'remove format', - 'remove all format', - 'annotation', - 'comments', - 'suggestions', - 'pre', - 'superscript', - 'subscript', - 'strikethrough', - 'tab', - 'tab as spaces', - 'undo', - 'redo', -]; - -/** - * @ngdoc directive - * @module superdesk.apps.workspace - * @name ContentProfileSchemaEditor - * - * @requires content - * - * @description Handles content profile schema editing - */ -ContentProfileSchemaEditor.$inject = ['vocabularies']; -export function ContentProfileSchemaEditor(vocabularies) { - return { - restrict: 'E', - templateUrl: 'scripts/apps/workspace/content/views/schema-editor.html', - require: '^form', - scope: { - model: '=', - fields: '=', - available: '=', - enabled: '=', - onRemove: '&', - onToggle: '&', - onOrderUpdate: '&', - onDrag: '&', - }, - link: function(scope: IScope, elem, attr, form) { - scope.formattingOptions = FORMATTING_OPTIONS; - scope.characterValidationEnabled = appConfig?.disallowed_characters != null; - - scope.getEditor3FormattingOptions = (fieldName) => { - const isCustomPlainTextField = typeof scope.fields[fieldName] === 'object' - && scope.fields[fieldName].field_type === 'text'; - - if (Object.keys(HAS_RICH_FORMATTING_OPTIONS).includes(fieldName) || isCustomPlainTextField) { - return EDITOR3_RICH_FORMATTING_OPTIONS; - } else { - return EDITOR3_PLAINTEXT_FORMATTING_OPTIONS; - } - }; - - scope.remove = (key) => { - scope.onRemove({key}); - scope.setDirty(); - }; - - scope.toggle = (schema, key, position) => { - scope.onToggle({key: schema.key, dest: key, position: position}); - scope.setDirty(); - }; - - scope.updateOrder = (key) => { - scope.onOrderUpdate({key}); - scope.setDirty(); - }; - - scope.reorder = (start, end, key) => { - scope.onDrag({start, end, key}); - scope.setDirty(); - }; - - scope.setDirty = () => { - form.$setDirty(); - }; - - /** - * Test if given field should have format options config - * - * @param {string} field - * @return {Boolean} - */ - scope.hasFormatOptions = (field) => - Object.keys(HAS_RICH_FORMATTING_OPTIONS).includes(field) - || Object.keys(HAS_PLAINTEXT_FORMATTING_OPTIONS).includes(field) - || hasCustomFieldFormatOptions(field); - - /** - * Test if given field should have format options config - * - * @param {string} field - * @return {Boolean} - */ - scope.hasImageSelected = (field) => scope.hasFormatOptions(field) && - includes(scope.model.editor[field].formatOptions, 'media'); - - /** - * Test if given field is custom field - * - * @param {string} field - * @return {Boolean} - */ - const hasCustomFieldFormatOptions = (field) => - scope.fields[field] && scope.fields[field].field_type === 'text'; - - vocabularies.getVocabularies().then((vocabulariesCollection) => { - scope.label = (id) => getLabelForFieldId(id, vocabulariesCollection); - }); - - // enable preview config for CVs - // we display other fields by default already - scope.showPreviewConfig = (field) => field != null && field.field_type == null; - }, - }; -} diff --git a/scripts/apps/workspace/content/directives/SortContentProfiles.ts b/scripts/apps/workspace/content/directives/SortContentProfiles.ts deleted file mode 100644 index d25077bd82..0000000000 --- a/scripts/apps/workspace/content/directives/SortContentProfiles.ts +++ /dev/null @@ -1,52 +0,0 @@ -export function SortContentProfiles() { - return { - link: function(scope, element) { - var updated = false; - - element.sortable({ - items: '.schema-item:not(.schema-item__disabled)', - cursor: 'move', - distance: 20, - containment: element, - tolerance: 'pointer', - placeholder: { - element: function(current) { - var height = current.height() - 20; - - return $('
  • ')[0]; - }, - update: function() { /* no-op */ }, - }, - start: function(event, ui) { - ui.item.data('start_index', - ui.item - .parent() - .find('li.schema-item') - .index(ui.item), - ); - }, - stop: function(event, ui) { - if (updated) { - updated = false; - - var start = ui.item.data('start_index'), - end = ui.item.parent().find('li.schema-item') - .index(ui.item), - key = ui.item.data('id'); - - scope.$applyAsync(() => { - scope.reorder(start, end, key); - }); - } - }, - update: function(event, ui) { - updated = true; - }, - }); - - scope.$on('$destroy', () => { - element.sortable('destroy'); - }); - }, - }; -} diff --git a/scripts/apps/workspace/content/directives/index.ts b/scripts/apps/workspace/content/directives/index.ts index 29ecaf3c64..57878b5f06 100644 --- a/scripts/apps/workspace/content/directives/index.ts +++ b/scripts/apps/workspace/content/directives/index.ts @@ -1,4 +1,2 @@ export {ContentCreateDirective} from './ContentCreateDirective'; -export {ContentProfileSchemaEditor} from './ContentProfileSchemaEditor'; export {ItemProfileDirective} from './ItemProfileDirective'; -export {SortContentProfiles} from './SortContentProfiles'; diff --git a/scripts/apps/workspace/content/index.ts b/scripts/apps/workspace/content/index.ts index cd97f1ba33..54f939903b 100644 --- a/scripts/apps/workspace/content/index.ts +++ b/scripts/apps/workspace/content/index.ts @@ -3,10 +3,9 @@ import {reactToAngular1} from 'superdesk-ui-framework'; import {ContentService} from './services'; import * as directive from './directives'; import {coreMenuGroups} from 'core/activity/activity'; -import {WidgetsConfig} from './components/WidgetsConfig'; import {gettext} from 'core/utils'; -import ContentProfileFields from './controllers/ContentProfileFields'; import {ContentProfilesController} from './controllers/ContentProfilesController'; +import {ContentProfileFieldsConfig} from './components/ContentProfileFieldsConfig'; /** * @ngdoc module @@ -26,22 +25,13 @@ angular.module('superdesk.apps.workspace.content', [ .service('content', ContentService) .directive('sdContentCreate', directive.ContentCreateDirective) - .directive('sdContentSchemaEditor', directive.ContentProfileSchemaEditor) .directive('sdItemProfile', directive.ItemProfileDirective) - .directive('sdSortContentProfiles', directive.SortContentProfiles) - - .component('sdWidgetsConfig', reactToAngular1(WidgetsConfig, ['initialWidgetsConfig', 'onUpdate'])) - .component('sdSchemaEditorFieldsDropdown', { - template: require('./views/schema-editor-fields-dropdown.html'), - bindings: { - bottom: '@', - fields: '=', - onSelect: '&', - }, - }) + .component( + 'sdContentProfileFieldsConfig', + reactToAngular1(ContentProfileFieldsConfig, ['profile', 'profileType', 'patchContentProfile']), + ) .controller('ContentProfilesController', ContentProfilesController) - .controller('ContentProfileFields', ContentProfileFields) .config(['superdeskProvider', function(superdesk) { superdesk diff --git a/scripts/apps/workspace/content/services/ContentService.ts b/scripts/apps/workspace/content/services/ContentService.ts index 78f8137ddb..f9003c2a68 100644 --- a/scripts/apps/workspace/content/services/ContentService.ts +++ b/scripts/apps/workspace/content/services/ContentService.ts @@ -4,7 +4,8 @@ import {gettext} from 'core/utils'; import {isMediaEditable} from 'core/config'; import {appConfig} from 'appConfig'; import {dataApi} from 'core/helpers/CrudManager'; -import {IArticle, IContentProfileEditorConfig, IArticleField} from 'superdesk-api'; +import {IVocabulary, IContentProfile} from 'superdesk-api'; +import {IArticle, IContentProfileEditorConfig} from 'superdesk-api'; import {IPackagesService} from 'types/Services/Packages'; import {isMediaType} from 'core/helpers/item'; @@ -165,24 +166,21 @@ export function ContentService(api, templates, desks, packages: IPackagesService * @param {Boolean} includeDisabled * @return {Promise} */ - this.getTypes = function(includeDisabled) { - // eslint-disable-next-line consistent-this - var getTypesFnThis = this; - var params = {}; + this.getTypes = function(type?: IContentProfile['type'] | null, includeDisabled?) { + var params: {where?: any} = {}; if (!includeDisabled) { params = {where: {enabled: true}}; } - // cache when fetching all types - return api.getAll('content_types', params, !!includeDisabled).then((result) => { - getTypesFnThis.types = result.sort((a, b) => b.priority - a.priority, // with higher priority goes up - ); - return getTypesFnThis.types; - }, (reason) => { - getTypesFnThis.types = []; - return getTypesFnThis.types; - }); + if (type != null) { + params.where = params.where ?? {}; + + params.where.type = type; + } + + return api.getAll('content_types', params, !!includeDisabled) + .then((result) => result.sort((a, b) => b.priority - a.priority)); }; /** @@ -191,7 +189,7 @@ export function ContentService(api, templates, desks, packages: IPackagesService * @return {Promise} */ this.getTypesLookup = function() { - return this.getTypes(true).then((profiles) => { + return this.getTypes(null, true).then((profiles) => { var lookup = {}; profiles.forEach((profile) => { @@ -231,11 +229,8 @@ export function ContentService(api, templates, desks, packages: IPackagesService * @param {String} contentType * @return {Object} */ - this.schema = function(profile, contentType) { - const schema = get(profile, 'schema', - get(appConfig.schema, contentType, constant.DEFAULT_SCHEMA)); - - return angular.extend({}, schema); + this.schema = function(profile: IContentProfile, contentType) { + return angular.extend({}, profile.schema); }; /** @@ -245,11 +240,8 @@ export function ContentService(api, templates, desks, packages: IPackagesService * @param {String} contentType * @return {Object} */ - this.editor = function(profile, contentType) { - const editor = get(profile, 'editor', - get(appConfig.editor, contentType, constant.GET_DEFAULT_EDITOR())); - - return angular.extend({}, editor); + this.editor = function(profile: IContentProfile, contentType) { + return angular.extend({}, profile.editor); }; /** @@ -267,7 +259,7 @@ export function ContentService(api, templates, desks, packages: IPackagesService /** * Get fields with preview enabled */ - this.previewFields = (editor: IContentProfileEditorConfig, fields: Array): Array => + this.previewFields = (editor: IContentProfileEditorConfig, fields: Array): Array => editor == null || fields == null ? [] : fields.filter((field) => editor[field._id] != null && editor[field._id].preview); @@ -279,14 +271,14 @@ export function ContentService(api, templates, desks, packages: IPackagesService * @return {Promise} */ this.getDeskProfiles = function(desk, profileId) { - return this.getTypes().then((profiles) => !desk || isEmpty(desk.content_profiles) ? + return this.getTypes('text').then((profiles) => !desk || isEmpty(desk.content_profiles) ? profiles : profiles.filter((profile) => desk.content_profiles[profile._id] || profile._id === profileId), ); }; - this.contentProfileSchema = angular.extend({}, constant.DEFAULT_SCHEMA, constant.EXTRA_SCHEMA_FIELDS); - this.contentProfileEditor = angular.extend({}, constant.GET_DEFAULT_EDITOR(), constant.EXTRA_EDITOR_FIELDS); + this.contentProfileSchema = angular.extend({}, constant.EXTRA_SCHEMA_FIELDS); + this.contentProfileEditor = angular.extend({}, constant.EXTRA_EDITOR_FIELDS); $rootScope.$on('vocabularies:updated', resetFields); @@ -367,29 +359,15 @@ export function ContentService(api, templates, desks, packages: IPackagesService * Setup authoring scope for item */ this.setupAuthoring = (profileId, scope, item) => { - if (profileId) { - return this.getType(profileId).then((profile) => { - scope.schema = this.schema(profile, item.type); - scope.editor = this.editor(profile, item.type); - scope.fields = this.fields(profile); - return profile; - }); - } else { - return this.getCustomFields().then(() => { - scope.schema = this.schema(null, get(item, 'type', 'text')); - scope.editor = this.editor(null, get(item, 'type', 'text')); - - // sign_off field used to always be hidden for media items in authoring, but was visible in preview - // to fix the inconsistency, I am making it read only so it's displayed in both places, - // but not editable, to keep it similar to past behaviour. - // In the future when we drop support for default profiles, this setting should be applied to - // customer configurations and removed from here. - if (isMediaType(item) && scope.schema.sign_off != null) { - scope.schema.sign_off.readonly = true; - } - - scope.fields = this.fields({editor: scope.editor}); - }); + if (profileId == null) { + throw new Error('profile ID must be provided'); } + + return this.getType(profileId).then((profile) => { + scope.schema = this.schema(profile, item.type); + scope.editor = this.editor(profile, item.type); + scope.fields = this.fields(profile); + return profile; + }); }; } diff --git a/scripts/apps/workspace/content/styles/profiles.scss b/scripts/apps/workspace/content/styles/profiles.scss index ef29f056c6..6e0f9461cb 100644 --- a/scripts/apps/workspace/content/styles/profiles.scss +++ b/scripts/apps/workspace/content/styles/profiles.scss @@ -11,10 +11,7 @@ sd-content-schema-editor { ul.pills-list { padding-top: 10px; - .vocabulary-field-type { - font-weight: 300; - font-style: italic; - } + > li { padding: 20px; margin-bottom: 12px; diff --git a/scripts/apps/workspace/content/tests/content.spec.ts b/scripts/apps/workspace/content/tests/content.spec.ts index 65e7ccffd3..e1d6bfca23 100644 --- a/scripts/apps/workspace/content/tests/content.spec.ts +++ b/scripts/apps/workspace/content/tests/content.spec.ts @@ -76,7 +76,6 @@ describe('superdesk.apps.workspace.content', () => { $rootScope.$digest(); expect(api.getAll).toHaveBeenCalledWith('content_types', {where: {enabled: true}}, false); expect(success).toHaveBeenCalledWith(types); - expect(content.types).toBe(types); })); it('can fetch content types and filter by desk', inject((content, $rootScope, $q) => { @@ -123,33 +122,6 @@ describe('superdesk.apps.workspace.content', () => { expect(success).toHaveBeenCalledWith(type); })); - it('can get schema for content type', inject((content) => { - var schema = content.schema(); - - expect(schema.headline).toBeTruthy(); - expect(schema.slugline).toBeTruthy(); - expect(schema.body_html).toBeTruthy(); - - var contentType = {schema: {foo: 1}}; - - schema = content.schema(contentType); - expect(schema.headline).toBeFalsy(); - expect(schema.foo).toBeTruthy(); - expect(schema).not.toBe(content.schema(contentType)); // check it is a copy - })); - - it('can get editor config for content type', inject((content) => { - var editor = content.editor(); - - expect(editor.slugline.order).toBe(1); - - var contentType = {editor: {foo: 2}}; - - editor = content.editor(contentType); - expect(editor.foo).toBe(2); - expect(editor.slugline).toBeFalsy(); - })); - it('can filter custom fields per profile', inject((content) => { content._fields = [ {_id: 'foo'}, @@ -173,7 +145,7 @@ describe('superdesk.apps.workspace.content', () => { var ctrl = $controller('ContentProfilesController', {$scope: scope}); scope.$digest(); - expect(content.getTypes).toHaveBeenCalledWith(true); + expect(content.getTypes).toHaveBeenCalledWith(null, true); expect(ctrl.items).toBe('list'); })); diff --git a/scripts/apps/workspace/content/views/profile-settings.html b/scripts/apps/workspace/content/views/profile-settings.html index 4ab89f78e3..732b6de138 100644 --- a/scripts/apps/workspace/content/views/profile-settings.html +++ b/scripts/apps/workspace/content/views/profile-settings.html @@ -5,9 +5,22 @@

    Content Profiles

    - - {{:: 'All'| translate}} - {{:: 'Active only'| translate}} + +
    +
    + +
    +
    + + + + +
    -
    {{ type.label}}
    +
    + + {{ type.label}} +
      @@ -60,16 +77,43 @@