Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save as template authoring react #4150

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 101 additions & 2 deletions scripts/api/templates.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,109 @@
import {
applyMiddleware,
canEdit,
cleanData,
prepareData,
willCreateNew,
} from 'apps/authoring-react/toolbar/template-helpers';
import {httpRequestJsonLocal} from 'core/helpers/network';
import ng from 'core/services/ng';
import {ITemplate} from 'superdesk-api';
import {clone} from 'lodash';
import {IArticle, IDesk, ITemplate} from 'superdesk-api';

function getById(id: ITemplate['_id']): Promise<ITemplate> {
return ng.get('templates').find(id);
return httpRequestJsonLocal<ITemplate>({
method: 'GET',
path: `/content_templates/${id}`,
});
}

function createTemplate(payload) {
return httpRequestJsonLocal<ITemplate>({
method: 'POST',
path: '/content_templates',
payload,
});
}

function updateTemplate(payload, template: ITemplate) {
return httpRequestJsonLocal<ITemplate>({
method: 'PATCH',
path: `/content_templates/${template._id}`,
payload,
headers: {'If-Match': template._etag},
});
}

/**
* Creates or updates a template. If the article has an existing template it will be updated.
*
* @templateName - template name from the form input
* @selectedDeskId - deskId selected in the form
*/
function createTemplateFromArticle(
// The new template will be based on this article
sourceArticle: IArticle,
templateName: string,
selectedDeskId: IDesk['_id'] | null,
): Promise<ITemplate> {
return getById(sourceArticle.template).then((resultTemplate) => {
const data = prepareData(resultTemplate);
const deskId = selectedDeskId || data.desk;
const templateArticle = data.template;
const item: IArticle = clone(ng.get('templates').pickItemData(sourceArticle));
const userId = ng.get('session').identity._id;

return applyMiddleware(item).then((itemAfterMiddleware) => {
const newTemplate: Partial<ITemplate> = {
template_name: templateName,
template_type: 'create',
template_desks: selectedDeskId != null ? [deskId] : null,
is_public: templateArticle.is_public,
data: itemAfterMiddleware,
};

let templateTemp: Partial<ITemplate> = templateArticle != null ? templateArticle : newTemplate;
let diff = templateArticle != null ? newTemplate : null;

if (willCreateNew(templateArticle, templateName, selectedDeskId != null)) {
templateTemp = newTemplate;
diff = null;

if (!canEdit(templateArticle, selectedDeskId != null)) {
templateTemp.is_public = false;
templateTemp.user = userId;
templateTemp.template_desks = null;
}
}

const hasLinks = templateTemp._links != null;
const payload: Partial<ITemplate> = diff != null ? cleanData(diff) : cleanData(templateTemp);

// if the template is made private, set the current user as template owner
if (templateArticle.is_public && (diff?.is_public === false || templateTemp.is_public === false)) {
payload.user = userId;
}

const requestPayload: Partial<ITemplate> = {
...payload,
data: cleanData<IArticle>(sourceArticle),
};

return (hasLinks
? updateTemplate(requestPayload, resultTemplate)
: createTemplate(requestPayload)
)
.then((_data) => {
return _data;
}, (response) => {
return Promise.reject(response);
});
});
});
}

export const templates = {
getById,
createTemplateFromArticle,
prepareData,
};
16 changes: 16 additions & 0 deletions scripts/apps/authoring-react/authoring-integration-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {CreatedModifiedInfo} from './subcomponents/created-modified-info';
import {dispatchInternalEvent} from 'core/internal-events';
import {IArticleActionInteractive} from 'core/interactive-article-actions-panel/interfaces';
import {ARTICLE_RELATED_RESOURCE_NAMES} from 'core/constants';
import {TemplateModal} from './toolbar/template-modal';
import {IProps} from './authoring-angular-integration';
import {showModal} from '@superdesk/common';
import ExportModal from './toolbar/export-modal';
Expand Down Expand Up @@ -222,6 +223,20 @@ export class AuthoringIntegrationWrapper extends React.PureComponent<IPropsWrapp
(Component) => (props: {item: IArticle}) => <Component article={props.item} />,
);

const saveAsTemplate = (item: IArticle): IAuthoringAction => ({
label: gettext('Save as template'),
onTrigger: () => (
showModal(({closeModal}) => {
return (
<TemplateModal
closeModal={closeModal}
item={item}
/>
);
})
),
});

return (
<WithInteractiveArticleActionsPanel location="authoring">
{(panelState, panelActions) => {
Expand Down Expand Up @@ -269,6 +284,7 @@ export class AuthoringIntegrationWrapper extends React.PureComponent<IPropsWrapp
const [authoringActionsFromExtensions, articleActionsFromExtensions] = res;

return [
saveAsTemplate(item),
getExportModal(getLatestItem, handleUnsavedChanges, hasUnsavedChanges),
...authoringActionsFromExtensions,
...articleActionsFromExtensions,
Expand Down
119 changes: 119 additions & 0 deletions scripts/apps/authoring-react/toolbar/template-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import ng from 'core/services/ng';
import {extensions} from 'appConfig';
import {IArticle, ICustomFieldType, ITemplate, IVocabulary} from 'superdesk-api';

export function applyMiddleware(_item: IArticle): Promise<IArticle> {
// Custom field types with `onTemplateCreate` defined. From all extensions.
const fieldTypes: {[id: string]: ICustomFieldType<any, any, any, any>} = {};

Object.values(extensions).forEach((ext) => {
ext?.activationResult?.contributions?.customFieldTypes?.forEach(
(customField: ICustomFieldType<any, any, any, any>) => {
if (customField.onTemplateCreate != null) {
fieldTypes[customField.id] = customField;
}
},
);
});

return ng.get('vocabularies').getVocabularies().then((_vocabularies: Array<IVocabulary>) => {
const fakeScope: any = {};

return ng.get('content').setupAuthoring(_item.profile, fakeScope, _item).then(() => {
let itemNext: IArticle = {..._item};

for (const fieldId of Object.keys(fakeScope.editor)) {
const vocabulary = _vocabularies.find(({_id}) => _id === fieldId);

if (vocabulary != null && fieldTypes[vocabulary.custom_field_type] != null) {
const config = vocabulary.custom_field_config ?? {};
const customField = fieldTypes[vocabulary.custom_field_type];

itemNext = {
...itemNext,
extra: {
...itemNext.extra,
[fieldId]: customField.onTemplateCreate(
itemNext?.extra?.[fieldId],
config,
),
},
};
}
}

return itemNext;
});
});
}

export function prepareData(template: ITemplate) {
return {
name: template.template_name,
desk: template.template_desks != null ? template.template_desks[0] : null,
template,
};
}

export function cleanData<T>(data: Partial<T>): Partial<T> {
[
'_type',
'_status',
'_updated',
'_created',
'_etag',
'_links',
'_id',
'_current_version',
'_etag',
'_links',
'expiry',
'lock_user',
'original_id',
'schedule_settings',
'semantics',
'_autosave',
'_editable',
'_latest_version',
'_locked',
'time_zone',
].forEach((field) => {
delete data[field];
});

return data;
}

/**
* Determines whether the template will be overwritten or a new one will be created.
* The is_public parameter is needed because we get it from user input and not from the fetched template.
* is_public is set from the user - if the template is made as a desk template then is_public is true.
*/
export function canEdit(template: ITemplate, isPublic: boolean): boolean {
const privileges = ng.get('privileges');

if (template == null) {
return false;
} else if (template?.is_public && !isPublic) {
// if template is changed from public to private, always
// create a copy of a template and don't modify the original one.
return false;
} else if (isPublic) {
return privileges.userHasPrivileges({content_templates: 1});
} else if (template?.user === ng.get('session').identity._id) {
return true;
} else {
return privileges.userHasPrivileges({personal_template: 1}); // can edit templates of other users
}
}

export const wasRenamed = (
template: ITemplate,
templateName: string,
) => template != null && templateName !== template.template_name;

export const willCreateNew = (
template: ITemplate,
templateName: string,
isPublic: boolean,
) => template == null || wasRenamed(template, templateName) || canEdit(template, isPublic) !== true;
Loading