diff --git a/scripts/apps/archive/views/preview.html b/scripts/apps/archive/views/preview.html
index deaf3c7b50..75e4f7c3e5 100644
--- a/scripts/apps/archive/views/preview.html
+++ b/scripts/apps/archive/views/preview.html
@@ -130,11 +130,17 @@
-
+
+ ng-if="selected.preview.extra[field._id] && (field.field_type === 'text' || field.field_type === 'date' || field.field_type === 'urls')">
+
+
+
+
diff --git a/scripts/apps/authoring/authoring/article-url-fields.tsx b/scripts/apps/authoring/authoring/article-url-fields.tsx
new file mode 100644
index 0000000000..22ebb789f0
--- /dev/null
+++ b/scripts/apps/authoring/authoring/article-url-fields.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+
+interface IProps {
+ label: string;
+ urls: Array
;
+ helperText: string;
+ fieldId: string;
+ onChange: (fieldId: string, urls: Array) => void;
+}
+
+interface IState {
+ urls: Array;
+}
+
+export class ArticleUrlFields extends React.Component {
+ constructor(props) {
+ super(props);
+
+ // local state is used, because onChange is debounced
+ this.state = {
+ urls: Array.isArray(props.urls) && props.urls.length > 0 ? props.urls : [],
+ };
+ }
+ removeUrl(index) {
+ this.setState({
+ urls: this.state.urls.filter((_, i) => i !== index),
+ }, () => {
+ this.props.onChange(this.props.fieldId, this.state.urls);
+ });
+ }
+ addUrl() {
+ this.setState({
+ urls: this.state.urls.concat('https://'),
+ }, () => {
+ this.props.onChange(this.props.fieldId, this.state.urls);
+ });
+ }
+ handleChange(index, event) {
+ const nextUrls = this.state.urls.map((currentValue, i) => {
+ if (i === index) {
+ return event.target.value;
+ } else {
+ return currentValue;
+ }
+ });
+
+ this.setState({
+ urls: nextUrls,
+ }, () => {
+ this.props.onChange(this.props.fieldId, this.state.urls);
+ });
+ }
+ render() {
+ const {label, helperText} = this.props;
+
+ return (
+
+
+
+ {this.state.urls.map((url, i) => (
+
+
+
+
+ ))}
+
+
+
+
+
+ {
+ helperText == null ? null :
{helperText}
+ }
+
+ );
+ }
+}
diff --git a/scripts/apps/authoring/authoring/directives/ArticleEditDirective.js b/scripts/apps/authoring/authoring/directives/ArticleEditDirective.js
index 0432e69ebd..63990709e5 100644
--- a/scripts/apps/authoring/authoring/directives/ArticleEditDirective.js
+++ b/scripts/apps/authoring/authoring/directives/ArticleEditDirective.js
@@ -63,6 +63,11 @@ export function ArticleEditDirective(
templateUrl: 'scripts/apps/authoring/views/article-edit.html',
link: function(scope, elem) {
getLabelNameResolver().then((getLabelForFieldId) => {
+ scope.handleUrlsChange = function(fieldId, value) {
+ scope.item.extra[fieldId] = value;
+ scope.autosave(scope.item);
+ };
+
scope.toggleDetails = true;
scope.errorMessage = null;
scope.contentType = null;
diff --git a/scripts/apps/authoring/authoring/index.ts b/scripts/apps/authoring/authoring/index.ts
index 228815a78d..50e9a51fb4 100644
--- a/scripts/apps/authoring/authoring/index.ts
+++ b/scripts/apps/authoring/authoring/index.ts
@@ -7,6 +7,8 @@ import * as filter from './filters';
import '../suggest';
import mediaModule from '../media';
+import {reactToAngular1} from 'superdesk-ui-framework';
+import {ArticleUrlFields} from './article-url-fields';
angular.module('superdesk.apps.authoring.autosave', []).service('autosave', svc.AutosaveService);
@@ -76,6 +78,9 @@ angular.module('superdesk.apps.authoring', [
.directive('sdRemoveTags', directive.RemoveTagsDirective)
.directive('tansaScopeSync', directive.TansaScopeSyncDirective)
.directive('sdItemActionByIntent', directive.ItemActionsByIntentDirective)
+ .component('sdArticleUrlFields',
+ reactToAngular1(ArticleUrlFields, ['label', 'urls', 'helperText', 'onChange', 'fieldId']),
+ )
.filter('embeddedFilter', filter.EmbeddedFilter)
diff --git a/scripts/apps/authoring/views/article-edit.html b/scripts/apps/authoring/views/article-edit.html
index 3a93cead31..792b1b063a 100644
--- a/scripts/apps/authoring/views/article-edit.html
+++ b/scripts/apps/authoring/views/article-edit.html
@@ -674,3 +674,19 @@
{{field.helper_text}}
+
+
+
+
+
+
diff --git a/scripts/apps/vocabularies/controllers/VocabularyConfigController.ts b/scripts/apps/vocabularies/controllers/VocabularyConfigController.ts
index 0175a89b1b..12347da6ed 100644
--- a/scripts/apps/vocabularies/controllers/VocabularyConfigController.ts
+++ b/scripts/apps/vocabularies/controllers/VocabularyConfigController.ts
@@ -86,6 +86,7 @@ export function VocabularyConfigController($scope: IScope, $route, $routeParams,
tab === 'vocabularies' && !fieldType || fieldType &&
(tab === 'text-fields' && fieldType === 'text' ||
tab === 'date-fields' && fieldType === 'date' ||
+ tab === 'urls-fields' && fieldType === 'urls' ||
tab === 'related-content-fields' && MEDIA_TYPE_KEYS.includes(fieldType) ||
tab === 'embed-fields' && fieldType === 'embed');
diff --git a/scripts/apps/vocabularies/views/settings.html b/scripts/apps/vocabularies/views/settings.html
index a949b15965..d7aabf43ce 100644
--- a/scripts/apps/vocabularies/views/settings.html
+++ b/scripts/apps/vocabularies/views/settings.html
@@ -21,6 +21,9 @@ Metadata management
+
+
+
diff --git a/scripts/apps/vocabularies/views/vocabulary-config.html b/scripts/apps/vocabularies/views/vocabulary-config.html
index ea21e9fc05..4b0b3ab121 100644
--- a/scripts/apps/vocabularies/views/vocabulary-config.html
+++ b/scripts/apps/vocabularies/views/vocabulary-config.html
@@ -31,6 +31,12 @@
Add New
+
+
diff --git a/scripts/apps/workspace/content/services/ContentService.js b/scripts/apps/workspace/content/services/ContentService.js
index 6ed570a73a..da225199e6 100644
--- a/scripts/apps/workspace/content/services/ContentService.js
+++ b/scripts/apps/workspace/content/services/ContentService.js
@@ -320,7 +320,7 @@ export function ContentService(api, superdesk, templates, desks, packages, archi
self._fieldsPromise = api.getAll('vocabularies', {
where: {
$or: [
- {field_type: {$in: ['text', 'date', 'media', 'embed']}},
+ {field_type: {$in: ['text', 'date', 'media', 'embed', 'urls']}},
{service: {$exists: true}},
],
},