<commit_before>/**
 * @ngdoc service
 * @module superdesk.apps.authoring.macros
 * @name macros
 * @requires api
 * @requires notify
 * @description MacrosService provides set of methods which allows fetching of macros
 * and triggering of macros to be apply on provided item
 */

import {gettext} from 'core/utils';

MacrosService.$inject = ['api', 'notify'];
function MacrosService(api, notify) {
    /**
     * Recursively returns all macros
     *
     * @return {*}
     */
    var _getAllMacros = function(criteria = {}, page = 1, macros = []) {
        return api.query('macros', _.extend({max_results: 200, page: page}, criteria), null, true)
            .then((result) => {
                let all = macros.concat(result._items);
                let pg = page;

                if (result._links.next) {
                    pg++;
                    return _getAllMacros(criteria, pg, all);
                }

                return _.sortBy(all, ['order', 'label']);
            });
    };

    /**
     * Returns all frontend macros if includeBackend is false or none
     * If includeBackend is true then results will include the backend macros
     *
     * @param {bool} includeBackend
     */
    this.get = function(includeBackend) {
        return _getAllMacros({backend: !!includeBackend}).then(angular.bind(this, function(macros) {
            this.macros = macros;
            return this.macros;
        }));
    };

    /**
     * Returns all frontend macros for a given desk if includeBackend is false or none
     * If includeBackend is true then results will include the backend macros for that desk
     *
     * @param {string} desk
     * @param {bool} includeBackend
     */
    this.getByDesk = function(desk, includeBackend) {
        return _getAllMacros({desk: desk, backend: !!includeBackend}).then(angular.bind(this, function(macros) {
            this.macros = macros;
            return this.macros;
        }));
    };

    this.setupShortcuts = function($scope) {
        this.get().then((macros) => {
            angular.forEach(macros, (macro) => {
                if (macro.shortcut) {
                    $scope.$on('key:ctrl:' + macro.shortcut, () => {
                        triggerMacro(macro, $scope.item);
                    });
                }
            });
        });
    };

    this.call = triggerMacro;

    function triggerMacro(macro, item, commit) {
        return api.save('macros', {
            macro: macro.name,
            item: item,
            commit: !!commit,
        }).then((res) => res, (err) => {
            if (angular.isDefined(err.data._message)) {
                notify.error(gettext('Error: {{message}}', {message: err.data._message}));
            }
        });
    }
}

/**
 * @ngdoc controller
 * @module superdesk.apps.authoring.macros
 * @name Macros
 * @requires https://docs.angularjs.org/api/ng/type/$rootScope.Scope $scope
 * @requires macros
 * @requires desks
 * @requires autosave
 * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope
 * @requires storage
 * @requires editorResolver
 * @description MacrosController holds a set of convenience functions used by macros widget
 */
MacrosController.$inject = ['$scope', 'macros', 'desks', 'autosave', '$rootScope', 'storage', 'editorResolver'];
function MacrosController($scope, macros, desks, autosave, $rootScope, storage, editorResolver) {
    let expandedGroup = storage.getItem('expandedGroup') || [];

    $scope.loading = true;

    macros.get().then(() => {
        let currentDeskId = desks.getCurrentDeskId();

        if (currentDeskId !== null) {
            macros.getByDesk(desks.getCurrentDesk().name).then((_macros) => {
                displayMacros(_macros);
            });
        } else {
            displayMacros(macros.macros);
        }
    })
        .finally(() => {
            $scope.loading = false;
        });

    /**
     * @ngdoc method
     * @name Macros#displayMacros
     * @private
     * @param {Array<object>} macros - each of macro contains name, label, group, order etc.
     * @description displays the list of fetched macros
     */
    function displayMacros(fetchedMacros) {
        $scope.macros = fetchedMacros;

        // grouped macros list
        let macrosByGroup = _.groupBy(_.filter($scope.macros, 'group'), 'group');

        $scope.groupedMacros = _.isEmpty(macrosByGroup) ? null : macrosByGroup;

        // provide grouping macros list option and prepare list, if group available.
        if ($scope.groupedMacros) {
            $scope.groupedList = true;
            prepareMacrosList($scope.macros);
        } else {
            $scope.groupedList = false;
        }
    }

    function isString(value) {
        return typeof value === 'string' || value instanceof String;
    }

    /**
     * @ngdoc method
     * @name Macros#call
     * @param {Object} macro - contains name, label, group, order etc.
     * @returns {Promise} - If resolved then macro is applied successfully
     * @description
     * Triggers macros service call to apply the provided macro on opened article.
     * The macros that changes the body should return diff; for other fields the entire
     * content is replaced with the value changed by macro
     */
    $scope.call = function(macro) {
        const editor = editorResolver.get();
        const isEditor3 = editor.version() !== '2';
        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);

        if (isEditor3 && useReplace) {
            item.body_html = editor.getHtmlForTansa() || item.body_html;
        }

        $scope.loading = true;
        return macros.call(macro, item).then((res) => {
            let ignoreFields = ['_etag', 'fields_meta'];

            // ignore fields is only required for editor3
            if (isEditor3) {
                ignoreFields.push('body_html');

                if (useReplace && item.body_html !== res.item.body_html) {
                    editor.setHtmlFromTansa(res.item.body_html, isSimpleReplace);
                }

                Object.keys(res.item || {}).forEach((field) => {
                    if (isString(res.item[field]) === false || field === 'body_html') {
                        return;
                    }
                    ignoreFields.push(field);
                    if (res.item[field] !== item[field] && res.diff == null) {
                        $rootScope.$broadcast('macro:refreshField', field, res.item[field]);
                    }
                });
            }

            if (isEditor3 || res.diff == null) {
                angular.extend($scope.item, _.omit(res.item, ignoreFields));
                $scope.autosave($scope.item);<commit_msg>fix(macro): Don't update body_html when diff is present (SDESK-3888)<commit_after>/**
 * @ngdoc service
 * @module superdesk.apps.authoring.macros
 * @name macros
 * @requires api
 * @requires notify
 * @description MacrosService provides set of methods which allows fetching of macros
 * and triggering of macros to be apply on provided item
 */

import {gettext} from 'core/utils';

MacrosService.$inject = ['api', 'notify'];
function MacrosService(api, notify) {
    /**
     * Recursively returns all macros
     *
     * @return {*}
     */
    var _getAllMacros = function(criteria = {}, page = 1, macros = []) {
        return api.query('macros', _.extend({max_results: 200, page: page}, criteria), null, true)
            .then((result) => {
                let all = macros.concat(result._items);
                let pg = page;

                if (result._links.next) {
                    pg++;
                    return _getAllMacros(criteria, pg, all);
                }

                return _.sortBy(all, ['order', 'label']);
            });
    };

    /**
     * Returns all frontend macros if includeBackend is false or none
     * If includeBackend is true then results will include the backend macros
     *
     * @param {bool} includeBackend
     */
    this.get = function(includeBackend) {
        return _getAllMacros({backend: !!includeBackend}).then(angular.bind(this, function(macros) {
            this.macros = macros;
            return this.macros;
        }));
    };

    /**
     * Returns all frontend macros for a given desk if includeBackend is false or none
     * If includeBackend is true then results will include the backend macros for that desk
     *
     * @param {string} desk
     * @param {bool} includeBackend
     */
    this.getByDesk = function(desk, includeBackend) {
        return _getAllMacros({desk: desk, backend: !!includeBackend}).then(angular.bind(this, function(macros) {
            this.macros = macros;
            return this.macros;
        }));
    };

    this.setupShortcuts = function($scope) {
        this.get().then((macros) => {
            angular.forEach(macros, (macro) => {
                if (macro.shortcut) {
                    $scope.$on('key:ctrl:' + macro.shortcut, () => {
                        triggerMacro(macro, $scope.item);
                    });
                }
            });
        });
    };

    this.call = triggerMacro;

    function triggerMacro(macro, item, commit) {
        return api.save('macros', {
            macro: macro.name,
            item: item,
            commit: !!commit,
        }).then((res) => res, (err) => {
            if (angular.isDefined(err.data._message)) {
                notify.error(gettext('Error: {{message}}', {message: err.data._message}));
            }
        });
    }
}

/**
 * @ngdoc controller
 * @module superdesk.apps.authoring.macros
 * @name Macros
 * @requires https://docs.angularjs.org/api/ng/type/$rootScope.Scope $scope
 * @requires macros
 * @requires desks
 * @requires autosave
 * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope
 * @requires storage
 * @requires editorResolver
 * @description MacrosController holds a set of convenience functions used by macros widget
 */
MacrosController.$inject = ['$scope', 'macros', 'desks', 'autosave', '$rootScope', 'storage', 'editorResolver'];
function MacrosController($scope, macros, desks, autosave, $rootScope, storage, editorResolver) {
    let expandedGroup = storage.getItem('expandedGroup') || [];

    $scope.loading = true;

    macros.get().then(() => {
        let currentDeskId = desks.getCurrentDeskId();

        if (currentDeskId !== null) {
            macros.getByDesk(desks.getCurrentDesk().name).then((_macros) => {
                displayMacros(_macros);
            });
        } else {
            displayMacros(macros.macros);
        }
    })
        .finally(() => {
            $scope.loading = false;
        });

    /**
     * @ngdoc method
     * @name Macros#displayMacros
     * @private
     * @param {Array<object>} macros - each of macro contains name, label, group, order etc.
     * @description displays the list of fetched macros
     */
    function displayMacros(fetchedMacros) {
        $scope.macros = fetchedMacros;

        // grouped macros list
        let macrosByGroup = _.groupBy(_.filter($scope.macros, 'group'), 'group');

        $scope.groupedMacros = _.isEmpty(macrosByGroup) ? null : macrosByGroup;

        // provide grouping macros list option and prepare list, if group available.
        if ($scope.groupedMacros) {
            $scope.groupedList = true;
            prepareMacrosList($scope.macros);
        } else {
            $scope.groupedList = false;
        }
    }

    function isString(value) {
        return typeof value === 'string' || value instanceof String;
    }

    /**
     * @ngdoc method
     * @name Macros#call
     * @param {Object} macro - contains name, label, group, order etc.
     * @returns {Promise} - If resolved then macro is applied successfully
     * @description
     * Triggers macros service call to apply the provided macro on opened article.
     * The macros that changes the body should return diff; for other fields the entire
     * content is replaced with the value changed by macro
     */
    $scope.call = function(macro) {
        const editor = editorResolver.get();
        const isEditor3 = editor.version() !== '2';
        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);

        if (isEditor3 && useReplace) {
            item.body_html = editor.getHtmlForTansa() || item.body_html;
        }

        $scope.loading = true;
        return macros.call(macro, item).then((res) => {
            let ignoreFields = ['_etag', 'fields_meta'];

            // ignore fields is only required for editor3
            if (isEditor3) {
                ignoreFields.push('body_html');

                if (res.diff == null && useReplace === true && item.body_html !== res.item.body_html) {
                    editor.setHtmlFromTansa(res.item.body_html, isSimpleReplace);
                }

                Object.keys(res.item || {}).forEach((field) => {
                    if (isString(res.item[field]) === false || field === 'body_html') {
                        return;
                    }
                    ignoreFields.push(field);
                    if (res.item[field] !== item[field]) {
                        $rootScope.$broadcast('macro:refreshField', field, res.item[field]);
                    }
                });
            }

            if (isEditor3 || res.diff == null) {
                angular.extend($scope.item, _.omit(res.item, ignoreFields));
                $scope.autosave($scope.item);
{'fraction_non_alphanumeric': 0.12861689814814814, 'fraction_numerical': 0.001736111111111111, 'mean_word_length': 2.092841163310962, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 27, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6599506', 'n_tokens_mistral': 3957, 'n_tokens_neox': 3781, 'n_words': 948}