<commit_before>/**
 * Used for adding, editing, and copying earnings entries.
 * @module kocomponents/posting/editor
 *
 */
import '../../utilities/icon-dec';
import '../../../utils/autofocusBindingHandler';
import '../../specialization/autocomplete';
import '../../question/editor';
import { ActionForValue } from '../../solution/autocomplete';
import Komponent from '../../helpers/KnockoutComponent';
import UserPosting from '../../../models/UserPosting';
import ko from 'knockout';
import { item as postingTemplateItem } from '../../../data/postingTemplates';
import { show as showConfirm } from '../../../modals/confirm';
import { show as showError } from '../../../modals/error';
import template from './template.html';
import { item as userPosting } from '../../../data/userPostings';

const TAG_NAME = 'posting-editor';

/**
 * @enum {string} Supported displaying modes
 */
export const EditorMode = {
    add: 'add',
    edit: 'edit',
    copy: 'copy'
};

/**
 * Component
 */
export default class PostingEditor extends Komponent {

    static get template() { return template; }

    /**
     * Parameters allowed are 'input only' when the value given is read at constructor
     * and keeps constant internally. If is an observable, any change from outside is
     * not read.
     * @param {object} params
     * @param {KnockoutObservable<EditorMode>} [params.editorMode] Input only value setting-up the mode in use
     * @param {(number|KnockoutObservable<number>)} [params.userPostingID] Input only ID to be edited or copied, or zero for new.
     * this let's add an earning from a listing component as a shortcut.
     * @param {function} [params.onSaved] Callback to notify after save the item, with the updated data included
     * @param {function} [params.onDeleted] Callback to notify after delete the item
     */
    constructor(params) {
        super();

        /**
         * Editable user posting. Same instance is used all the time, just
         * updating content, simplifying working with the form and summary.
         * @member {UserPosting}
         */
        this.data = new UserPosting({
            userPostingID: ko.unwrap(params.userPostingID) || 0
        });

        /**
         * Keeps a timestamp of the loaded data, allowing to track when there
         * are changes.
         * @member {Date}
         */
        this.dataTimestamp = ko.observable(this.data.model.dataTimestamp());

        /**
         * Number of total steps for fixed/common elements.
         * Take into account, that the questions provided by the posting template
         * may increase the count of total steps, by accumulate to this.
         * @const {number}
         */
        this.FIXED_STEPS_COUNT = 4;

        /**
         * Dynamic additional steps based on available posting questionsReponses
         * and the branch logic (where previous responses choosen by the user
         * define which is the next question and so if there are questions avoided
         * -not relevant given a previous answer- don't take part in this list
         * of steps available -and the total count-.
         * @member {KnockoutComputed<Array<UserPostingQuestionResponse>>}
         */
        this.questionsSteps = ko.pureComputed(() => {
            const questions = this.data.questionsResponses();
            return questions;
        });

        /**
         * Total of steps available, taking into account fixed and dynamic ones (questions)
         * @member {KnockoutComputed<number>}
         */
        this.stepsCount = ko.pureComputed(() => {
            const questions = this.questionsSteps();
            return (questions && questions.length || 0) + this.FIXED_STEPS_COUNT;
        });

        // Is allowed to request a 'copy' mode from outside if an ID is provided,
        // on any other case, the mode is automatic depending whether a positive ID
        // is given or not
        let requestedEditorMode = ko.unwrap(params.editorMode);
        if (requestedEditorMode !== EditorMode.copy || !this.data.userPostingID()) {
            requestedEditorMode = this.data.userPostingID() ? EditorMode.edit : EditorMode.add;
        }
        /**
         * Captures from the activity which "mode" the editor
         * component is to be used.
         * @member {EditorMode}
         */
        this.editorMode = ko.observable(requestedEditorMode);

        /**<commit_msg>Implemented branch logic on posting editor #820<commit_after>/**
 * Used for adding, editing, and copying earnings entries.
 * @module kocomponents/posting/editor
 *
 */
import '../../utilities/icon-dec';
import '../../../utils/autofocusBindingHandler';
import '../../specialization/autocomplete';
import '../../question/editor';
import { ActionForValue } from '../../solution/autocomplete';
import Komponent from '../../helpers/KnockoutComponent';
import UserPosting from '../../../models/UserPosting';
import ko from 'knockout';
import { item as postingTemplateItem } from '../../../data/postingTemplates';
import { show as showConfirm } from '../../../modals/confirm';
import { show as showError } from '../../../modals/error';
import template from './template.html';
import { item as userPosting } from '../../../data/userPostings';

const TAG_NAME = 'posting-editor';

/**
 * @enum {string} Supported displaying modes
 */
export const EditorMode = {
    add: 'add',
    edit: 'edit',
    copy: 'copy'
};

/**
 * Component
 */
export default class PostingEditor extends Komponent {

    static get template() { return template; }

    /**
     * Parameters allowed are 'input only' when the value given is read at constructor
     * and keeps constant internally. If is an observable, any change from outside is
     * not read.
     * @param {object} params
     * @param {KnockoutObservable<EditorMode>} [params.editorMode] Input only value setting-up the mode in use
     * @param {(number|KnockoutObservable<number>)} [params.userPostingID] Input only ID to be edited or copied, or zero for new.
     * this let's add an earning from a listing component as a shortcut.
     * @param {function} [params.onSaved] Callback to notify after save the item, with the updated data included
     * @param {function} [params.onDeleted] Callback to notify after delete the item
     */
    constructor(params) {
        super();

        /**
         * Editable user posting. Same instance is used all the time, just
         * updating content, simplifying working with the form and summary.
         * @member {UserPosting}
         */
        this.data = new UserPosting({
            userPostingID: ko.unwrap(params.userPostingID) || 0
        });

        /**
         * Keeps a timestamp of the loaded data, allowing to track when there
         * are changes.
         * @member {Date}
         */
        this.dataTimestamp = ko.observable(this.data.model.dataTimestamp());

        /**
         * Number of total steps for fixed/common elements.
         * Take into account, that the questions provided by the posting template
         * may increase the count of total steps, by accumulate to this.
         * @const {number}
         */
        this.FIXED_STEPS_COUNT = 4;

        /**
         * Dynamic additional steps based on available posting questionsReponses
         * and the branch logic (where previous responses choosen by the user
         * define which is the next question and so if there are questions avoided
         * -not relevant given a previous answer- don't take part in this list
         * of steps available -and the total count-.
         * @member {KnockoutComputed<Array<UserPostingQuestionResponse>>}
         */
        this.questionsSteps = ko.pureComputed(() => {
            const questions = this.data.questionsResponses();
            if (!questions || questions.length === 0) {
                return [];
            }
            // List of steps, the first is ever included
            const steps = [];
            const neededQuestionIds = [questions[0].questionID()];
            const getNaturalNextQuestionID = (currentIndex) => {
                const next = questions[currentIndex + 1];
                return next && next.questionID() || null;
            };
            questions.some((q, index) => {
                if (neededQuestionIds.includes(q.questionID())) {
                    steps.push(q);

                    // If no logic, use next natural question no matters which response
                    // is chosen
                    const logic = q.branchLogic();
                    const noLogic = !logic || Object.keys(logic).length === 0;
                    if (noLogic) {
                        const next = getNaturalNextQuestionID(index);
                        if (next) {
                            neededQuestionIds.push(next);
                        }
                        // continue next iteration
                        return;
                    }

                    const res = q.responses();
                    if (res.length === 0) {
                        // no responses, no next still: break loop
                        return true;
                    }
                    else {
                        // Only one response is valid for branch logic,
                        // use the first one
                        const optionID = res[0].optionID();
                        // Get the next questionID for the option based on branch logic
                        // or the default one if no specific branch for that one
                        const next = logic[optionID] || logic['default'];
                        // if we have a next step, add that question to the list
                        if (next) {
                            neededQuestionIds.push(next);
                        }
                        // else: Bad branch logic, not defining a next will make no more
                        // questions are added
                    }
                }
            });
            return steps;
        });

        /**
         * Total of steps available, taking into account fixed and dynamic ones (questions)
         * @member {KnockoutComputed<number>}
         */
        this.stepsCount = ko.pureComputed(() => {
            const questions = this.questionsSteps();
            return (questions && questions.length || 0) + this.FIXED_STEPS_COUNT;
        });

        // Is allowed to request a 'copy' mode from outside if an ID is provided,
        // on any other case, the mode is automatic depending whether a positive ID
        // is given or not
        let requestedEditorMode = ko.unwrap(params.editorMode);
        if (requestedEditorMode !== EditorMode.copy || !this.data.userPostingID()) {
            requestedEditorMode = this.data.userPostingID() ? EditorMode.edit : EditorMode.add;
        }
        /**
         * Captures from the activity which "mode" the editor
         * component is to be used.
         * @member {EditorMode}
         */
        this.editorMode = ko.observable(requestedEditorMode);

        /**
{'fraction_non_alphanumeric': 0.10538177228883079, 'fraction_numerical': 0.0013522040926710539, 'mean_word_length': 1.9655172413793103, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4886039', 'n_tokens_mistral': 2803, 'n_tokens_neox': 2706, 'n_words': 980}