import ng from 'core/services/ng'; import {insertMedia} from './toolbar'; import {logger} from 'core/services/logger'; import {SelectionState, convertFromRaw, EditorState} from 'draft-js'; import {IArticle} from 'superdesk-api'; import {getFieldMetadata, fieldsMetaKeys} from '../helpers/fieldsMeta'; import {CharacterLimitUiBehavior} from 'apps/authoring/authoring/components/CharacterCountConfigButton'; /** * @ngdoc method * @name changeEditorState * @param {Object} editorState * @param {force} force update * @return {String} action * @description Creates the change editor action */ export function changeEditorState(editorState, force = false, skipOnChange = false) { return { type: 'EDITOR_CHANGE_STATE', payload: {editorState, force, skipOnChange}, }; } /** * @ngdoc method * @name forceUpdate * @return {String} action * @description Causes the editor to force update. This is used by the spellchecker * to cause the editor to re-render its decorators based on new information retrieved * in dictionaries. Use of this method should be avoided. */ export function forceUpdate() { return {type: 'EDITOR_FORCE_UPDATE'}; } /** * @ngdoc method * @name setAbbreviations * @return {Object} abbreviations * @description set the abbreviation dictionary */ export function setAbbreviations(abbreviations) { return { type: 'EDITOR_SET_ABBREVIATIONS', payload: abbreviations, }; } /** * @ngdoc method * @name handleEditorTab * @param {Object} e on tab event * @return {String} action * @description Creates the change editor action */ export function handleEditorTab(e) { return { type: 'EDITOR_TAB', payload: e, }; } /** * @ngdoc method * @name dragDrop * @param {DataTransfer} transfer * @return {String} mediaType * @return {String} blockKey * @description Creates the editor drop action. */ export function dragDrop(transfer, mediaType, blockKey = null) { if (mediaType === 'Files') { return insertMedia(transfer.files); } return (dispatch) => { const content = ng.get('content'); dispatch({type: 'EDITOR_LOADING', payload: true}); const item: IArticle = JSON.parse(transfer.getData(mediaType)); return content.dropItem(item, {fetchExternal: true}) .then((data) => { dispatch({ type: 'EDITOR_DRAG_DROP', payload: {data, blockKey}, }); }) .finally(() => { dispatch({type: 'EDITOR_LOADING', payload: false}); }); }; } /** * @ngdoc method * @name setLocked * @param {Event} v * @return {String} action * @description Dispatches the action to set the main editor as locked. The main editor * is locked when other atomic blocks are edited that manage their own editor, such * as tables or image descriptions. */ export function setLocked(v) { return { type: 'EDITOR_SET_LOCKED', payload: v, }; } /** * @ngdoc method * @name setReadOnly * @param {Event} v * @return {String} action * @description Dispatches the action to set the main editor as read-only. This is * mainly used externally to allow bi-directional binding of the readOnly attribute * on the Angular directive. Not used in React. */ export function setReadOnly(v) { return { type: 'EDITOR_SET_READONLY', payload: v, }; } /** * @ngdoc method * @name setActiveCell * @description Sets the active table and cell inside the editor. */ export function setActiveCell(i, j, key, currentStyle, selection) { return { type: 'EDITOR_SET_CELL', payload: {i, j, key, currentStyle, selection}, }; } /** * @ngdoc method * @name changeImageCaption * @param {string} entityKey * @param {string} newCaption * @param {string} field (headline or description) * @description Change the image caption contained in the given entity key. * @returns {Object} */ export function changeImageCaption(entityKey, newCaption, field: 'headline' | 'description_text') { return { type: 'EDITOR_CHANGE_IMAGE_CAPTION', payload: {entityKey, newCaption, field}, }; } export function mergeEntityDataByKey(blockKey, entityKey, valuesToMerge) { return { type: 'MERGE_ENTITY_DATA_BY_KEY', payload: {blockKey, entityKey, valuesToMerge}, }; } /** * @ngdoc method * @name setHtmlFromTansa * @param {string} html * @param {string} simpleReplace * @description For every block from editor content merge the changes received from Tansa. * If simpleReplace is true on merge will not try to preserve inline style and entity * @returns {Object} */ export function setHtmlFromTansa(html, simpleReplace) { return { type: 'EDITOR_SET_HTML_FROM_TANSA', payload: {html, simpleReplace}, }; } export const setEditorStateFromItem = (item: IArticle, field: string) => { const rawState = getFieldMetadata(item, field, fieldsMetaKeys.draftjsState); const contentState = convertFromRaw(rawState); return { type: 'EDITOR_PUSH_STATE', payload: {contentState}, }; }; /** * Move one block after another * * @param {String} block * @param {String} dest * @return {Object} */ export function moveBlock(block, dest, insertionMode) { return { type: 'EDITOR_MOVE_BLOCK', payload: {block, dest, insertionMode}, }; } export function processEmbedCode(data) { if (typeof data !== 'string' && (typeof data !== 'object' && typeof data.html !== 'string') ) { logger.error(new Error('embed format not recognized')); } return data; } /** * @ngdoc method * @name embed * @param {Object|string} oEmbed code, HTML string. * @return {Object} * @description Dispatches the action to use the given oEmbed data for media embedding. */ export function embed(code, targetBlockKey = null) { return { type: 'EDITOR_APPLY_EMBED', payload: { code: processEmbedCode(code), targetBlockKey, }, }; } export type ITextCase = 'uppercase' | 'lowercase'; export function changeCase(changeTo: ITextCase, selection: SelectionState) { return { type: 'CHANGE_CASE', payload: {changeTo, selection}, }; } export type EditorLimit = { ui: CharacterLimitUiBehavior, chars: number }; export function changeLimitConfig(payload: EditorLimit) { return { type: 'EDITOR_CHANGE_LIMIT_CONFIG', payload, }; }