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

Custom blocks tab #4519

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Custom blocks implementation
  • Loading branch information
thecalcc committed May 10, 2024
commit b8f6554f625c2956fc913d78744dc8308caa1042
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {DEFAULT_SCHEMA, getVocabularySelectionTypes, getMediaTypeKeys, getMediaTypes} from '../constants';
import {IVocabulary, IVocabularyItem, IVocabularyTag} from 'superdesk-api';
import {IVocabulary, IVocabularyTag} from 'superdesk-api';
import {IDirectiveScope} from 'types/Angular/DirectiveScope';
import {remove, reduce} from 'lodash';
import {gettext, downloadFile} from 'core/utils';
import {showModal} from '@superdesk/common';
import {UploadConfig} from '../components/UploadConfigModal';
import {EDITOR_BLOCK_FIELD_TYPE} from 'apps/workspace/content/constants';

function getOther() {
return gettext('Other');
Expand Down Expand Up @@ -110,7 +111,7 @@ export function VocabularyConfigController($scope: IScope, $route, $routeParams,
$scope.matchFieldTypeToTab = (tab, fieldType) =>
tab === 'vocabularies' && !fieldType || fieldType &&
(tab === 'text-fields' && fieldType === 'text' ||
tab === 'custom-editor-blocks' && fieldType === 'editor-block' ||
tab === 'custom-editor-blocks' && fieldType === EDITOR_BLOCK_FIELD_TYPE ||
tab === 'date-fields' && fieldType === 'date' ||
tab === 'urls-fields' && fieldType === 'urls' ||
tab === 'related-content-fields' && getMediaTypeKeys().includes(fieldType) ||
Expand Down
77 changes: 66 additions & 11 deletions scripts/apps/vocabularies/controllers/VocabularyEditController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import _ from 'lodash';
import {IVocabularySelectionTypes, getVocabularySelectionTypes, getMediaTypeKeys, getMediaTypes} from '../constants';
import {gettext} from 'core/utils';
import {getFields} from 'apps/fields';
import {IVocabulary} from 'superdesk-api';
import {IArticle, IVocabulary, RICH_FORMATTING_OPTION} from 'superdesk-api';
import {IScope as IScopeConfigController} from './VocabularyConfigController';
import {VocabularyItemsViewEdit} from '../components/VocabularyItemsViewEdit';
import {defaultAllowedWorkflows} from 'apps/relations/services/RelationsService';
import {EDITOR_BLOCK_FIELD_TYPE} from 'apps/workspace/content/constants';

VocabularyEditController.$inject = [
'$scope',
Expand All @@ -21,6 +22,7 @@ VocabularyEditController.$inject = [
];

interface IScope extends IScopeConfigController {
item?: Partial<IArticle>;
setFormDirty: () => void;
newItemTemplate: any;
idRegex: string;
Expand All @@ -32,13 +34,16 @@ interface IScope extends IScopeConfigController {
_errorUniqueness: boolean;
errorMessage: string;
save: () => void;
formattingOptionsOnChange: (options: Array<RICH_FORMATTING_OPTION>) => void;
requestEditor3DirectivesToGenerateHtml: Array<() => void>;
handleTemplateValueChange: (value: string) => void;
requireAllowedTypesSelection: () => void;
addItem: () => void;
cancel: () => void;
model: any;
schema: any;
schemaFields: Array<any>;
itemsValidation: { valid: boolean };
itemsValidation: {valid: boolean};
customFieldTypes: Array<{id: string, label: string}>;
setCustomFieldConfig: (config: any) => void;
editForm: any;
Expand All @@ -61,16 +66,22 @@ export function VocabularyEditController(
$scope.tab = tab;
};

$scope.requestEditor3DirectivesToGenerateHtml = [];

$scope.idRegex = idRegex;
$scope.selectionTypes = getVocabularySelectionTypes();

if ($scope.matchFieldTypeToTab('related-content-fields', $scope.vocabulary.field_type)) {
if (
$scope.matchFieldTypeToTab('related-content-fields', $scope.vocabulary.field_type)
&& $scope.vocabulary.field_type === 'related_content'
) {
const vocab = $scope.vocabulary;

// Insert default allowed workflows
if ($scope.vocabulary.field_options == null) {
$scope.vocabulary.field_options = {allowed_workflows: defaultAllowedWorkflows};
} else if ($scope.vocabulary.field_options.allowed_workflows == null) {
$scope.vocabulary.field_options.allowed_workflows = defaultAllowedWorkflows;
}
vocab.field_options = {
...(vocab.field_options ?? {}),
allowed_workflows: defaultAllowedWorkflows,
};
}

function onSuccess(result) {
Expand All @@ -85,9 +96,9 @@ export function VocabularyEditController(
if (angular.isDefined(response.data._issues)) {
if (angular.isDefined(response.data._issues['validator exception'])) {
notify.error(gettext('Error: ' +
response.data._issues['validator exception']));
response.data._issues['validator exception']));
} else if (angular.isDefined(response.data._issues.error) &&
response.data._issues.error.required_field) {
response.data._issues.error.required_field) {
let params = response.data._issues.params;

notify.error(gettext(
Expand All @@ -111,6 +122,39 @@ export function VocabularyEditController(
return true;
}

if ($scope.vocabulary.field_type === EDITOR_BLOCK_FIELD_TYPE) {
$scope.item = {
'_id': '666d100a-2d3f-4965-b0df-5e091f9fb77b',
'body_html': $scope.vocabulary.field_options?.template ?? '',
'type': 'text',
};
}

$scope.formattingOptionsOnChange = function(options) {
if ($scope.vocabulary.field_type === EDITOR_BLOCK_FIELD_TYPE) {
$scope.vocabulary.field_options = {
formatting_options: options,
};

$scope.editForm.$setDirty();
$scope.$applyAsync();

/**
* Apply current changes to item and save them to the field as html,
* so when formatting options are updated editor sill has the changes
*/
$scope.requestEditor3DirectivesToGenerateHtml.forEach((fn) => {
fn();
});

$scope.$broadcast('formattingOptions-update', {editorFormat: options});
}
};

$scope.handleTemplateChange = function(...args) {
console.log(args);
};

/**
* Save current edit modal contents on backend.
*/
Expand All @@ -120,6 +164,17 @@ export function VocabularyEditController(
$scope.errorMessage = null;
delete $scope.vocabulary['_deleted'];

if ($scope.vocabulary.field_type === EDITOR_BLOCK_FIELD_TYPE) {
$scope.requestEditor3DirectivesToGenerateHtml.forEach((fn) => {
fn();
});
$scope.vocabulary.field_options = $scope.vocabulary.field_options ?? {};
$scope.vocabulary.field_options = {
...$scope.vocabulary.field_options,
template: $scope.item.body_html,
};
}

if ($scope.vocabulary._id === 'crop_sizes') {
var activeItems = _.filter($scope.vocabulary.items, (o) => o.is_active);

Expand Down Expand Up @@ -175,7 +230,7 @@ export function VocabularyEditController(
return false;
}

if ($scope.vocabulary.field_options == null || $scope.vocabulary.field_options.allowed_types == null) {
if ($scope.vocabulary.field_type !== 'related_content') {
return true;
}

Expand Down
61 changes: 56 additions & 5 deletions scripts/apps/vocabularies/views/vocabulary-config-modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ <h3 class="modal__heading" ng-show="!vocabulary._id"><span translate>Create</spa
</div>
<div class="modal__body modal__body--with-navigation">
<ul class="modal__navigation" ng-if="matchFieldTypeToTab('vocabularies', vocabulary.field_type)">
<li ng-class="{'active': tab === 'general'}"><button ng-click="setTab('general')" translate>General</button></li>
<li ng-class="{'active': tab === 'items'}"><button ng-click="setTab('items')" translate>Items</button></li>
<li ng-class="{'active': tab === 'general'}">
<button ng-click="setTab('general')" translate>General</button>
</li>
<li ng-class="{'active': tab === 'items'}">
<button ng-click="setTab('items')" translate>Items</button>
</li>
</ul>

<div class="modal__navigation--content">
Expand Down Expand Up @@ -95,7 +99,8 @@ <h3 class="modal__heading" ng-show="!vocabulary._id"><span translate>Create</spa
</div>
</div>

<div class="form__row">
<div class="form__row"
ng-if="matchFieldTypeToTab('custom-editor-blocks', vocabulary.field_type) === false">
<div class="sd-line-input"
ng-class="{'sd-line-input--invalid': vocabularyForm.$error.maxlength}">
<label for="cv_helper_text" class="sd-line-input__label" translate>Helper
Expand Down Expand Up @@ -147,13 +152,59 @@ <h3 class="modal__heading" ng-show="!vocabulary._id"><span translate>Create</spa
</ul>
</div>

<div class="sd-line-input"
ng-if="matchFieldTypeToTab('vocabularies', vocabulary.field_type)">
<div
class="sd-line-input"
ng-if="matchFieldTypeToTab('vocabularies', vocabulary.field_type)"
>
<sd-check ng-model="vocabulary.disable_entire_category_selection">
<span translate>Disable selection of an entire category</span>
</sd-check>
</div>

<div
class="sd-line-input"
ng-if="matchFieldTypeToTab('custom-editor-blocks', vocabulary.field_type)"
>
<label class="sd-line-input__label" translate>Formatting Options</label>
<sd-formatting-options-tree-select
value="vocabulary.field_options.formatting_options"
fields="{body_html: {}}"
field-id="'body_html'"
on-change="formattingOptionsOnChange"
/>
</div>
<div
ng-if="matchFieldTypeToTab('custom-editor-blocks', vocabulary.field_type)"
class="fieldset main-article__fieldset"
>
<div
class="field body"
order="{{editor.body_html.order}}"
sd-width="{{editor.body_html.sdWidth|| 'full'}}"
ng-attr-title="{{schema.body_html.readonly === true ? readOnlyLabel : ''}}"
data-test-id="authoring-field"
data-test-value="body_html"
tansa-proofing="false"
>
<div
id="title"
sd-editor3
data-path-to-value="'body_html'"
data-editor-format="vocabulary.field_options.formatting_options"
data-single-line="false"
data-scroll-container=".page-content-container--scrollable"
data-language="'en'"
data-value="item.body_html"
data-on-change="editForm.$setDirty()"
data-read-only="false"
data-item="item"
data-refresh-trigger="refreshTrigger"
data-clean-pasted-html="item.body_html"
data-test-id="field--editor-block"
data-field-id="'Custom block'"
/>
</div>
</div>
<div class="sd-line-input" ng-if="matchFieldTypeToTab('vocabularies', vocabulary.field_type)">
<sd-check ng-model="vocabulary.preffered_items">
{{ :: 'Available in user/desk preferences' | translate }}</sd-check>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const formattingOptionsUnsafeToParseFromHTML: Array<RICH_FORMATTING_OPTIO
'pre',
'embed',
'embed articles',
'multi-line quote',
'media',
'table',
];
Expand Down
4 changes: 4 additions & 0 deletions scripts/apps/workspace/content/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export const EXTRA_EDITOR_FIELDS = Object.freeze({
media_description: {enabled: true},
});


export const EDITOR_BLOCK_FIELD_TYPE = 'editor-block';

/**
* Vocabulary types used for custom fields
*/
Expand All @@ -112,5 +115,6 @@ export const CUSTOM_FIELD_TYPES = [
'media',
'embed',
'urls',
EDITOR_BLOCK_FIELD_TYPE,
'custom',
];
4 changes: 2 additions & 2 deletions scripts/core/editor3/components/Editor3Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -582,8 +582,8 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
* add much performance overhead if it was replaced unconditionally, but I don't want to break it
* nor spend time on testing so I'm keeping it as is for now.
*/
const dropAreaEnabled =
this.props.editorFormat.includes('media') || this.props.editorFormat.includes('embed articles');
const dropAreaEnabled = this.props.editorFormat.includes('media')
|| this.props.editorFormat.includes('embed articles');

const blockRenderMap = DefaultDraftBlockRenderMap.merge(Map(
dropAreaEnabled ? {
Expand Down
22 changes: 19 additions & 3 deletions scripts/core/editor3/directive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ class Editor3Directive {
*/
value: '=',


/**
* @type {String}
* @description EditorValue is editorState instead of HTML.
*/
outputEditorState: '=',

/**
* @type {String}
* @description required for editor3 to be able to set metadata for fields. Mainly editor_state
Expand Down Expand Up @@ -330,7 +337,7 @@ class Editor3Directive {
pathValue || this.pathToValue
]?.characterLimitMode;

let store = createEditorStore(this, ng.get('spellcheck'));
let store = createEditorStore(this, ng.get('spellcheck'), true);

const fieldName: string | null = (() => {
if (this.fieldLabel != null) {
Expand All @@ -342,10 +349,12 @@ class Editor3Directive {
}
})();

const renderEditor3 = () => {
const renderEditor3 = (options?: {unmount: boolean}) => {
const element = $element.get(0);

ReactDOM.unmountComponentAtNode(element);
if (options?.unmount) {
ReactDOM.unmountComponentAtNode(element);
}

const textStatistics = (
<Spacer h gap="8" alignItems="center" noWrap noGrow>
Expand Down Expand Up @@ -380,6 +389,7 @@ class Editor3Directive {

const editor3 = (
<Editor3
uiTheme={{}}
scrollContainer={this.scrollContainer}
singleLine={this.singleLine}
cleanPastedHtml={this.cleanPastedHtml}
Expand Down Expand Up @@ -519,6 +529,12 @@ class Editor3Directive {
renderEditor3();
});

$scope.$on('formattingOptions-update', (_e, data) => {
store = createEditorStore({...this, editorFormat: data.editorFormat}, ng.get('spellcheck'), true);

renderEditor3({unmount: false});
});

// this is triggered from MacrosController.call
// if the current editor is for 'field' replace the current content with 'value'
$scope.$on(
Expand Down
2 changes: 1 addition & 1 deletion scripts/core/superdesk-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1445,7 +1445,7 @@ declare module 'superdesk-api' {
field_type: 'editor-block';
field_options?: {
formatting_options?: Array<string>;
template_value?: any;
template?: string;
};
}

Expand Down