<commit_before>import { utils } from "../utils";
import { Locator } from "./Locator";
import { FieldDto, FormFieldTypeDTO } from "./KPRPCDTOs";

export type FieldType = "password" | "text" | "existing" | "boolean" | "otp" | "some-chars";

export class Field {
    // Unique ID that we can use to help UI operations and identification when there is no known name for this field.
    // Also may be useful in future for multiple-page reconfiguration or per-browser field configuration overrides?
    readonly uuid: string;

    // visual identifier for the field. May be specified by the user or inferred from
    // either default Entry data (username/password/OTP) or form field label or name attribute
    readonly name: string;

    // The value we'll fill into the form.
    // For existing determines which option to activate.
    // For boolean determines whether to activate or deactivate (to ignore, delete the field record)
    // For OTP, need to work out whether we will have to transfer the secrets and recalculate in the browser or just treat as standard value
    readonly value: string;

    // type of data this field represents. There's no 100% way to match this to the type of HTML form element but it's often logical.
    // E.g. password goes into password fields unless user explicitly re-configures that; existing would typically match something
    // like a radio button or select option already present on the HTML form; boolean would match a checkbox.
    readonly type: FieldType;

    // How we can find the correct target in the HTML.
    readonly locators: Locator[];

    // tracks the most recent value we want to reset to in cases where the user has that possibility
    readonly resetValue: string;

    constructor(field: Partial<Field>) {
        this.name = field.name || "";
        this.value = field.value || "";
        this.resetValue = field.resetValue || "";
        this.uuid = field.uuid || utils.newGUID();
        this.type = field.type || "text";
        this.locators = field.locators || [];
    }

    private static getDisplayValueInternal(
        field: Field,
        revealPasswords: boolean,
        replacementIfProtected: string
    ) {
        if (field.type === "boolean") {
            return field.value === "KEEFOX_CHECKED_FLAG_TRUE" ? $STR("enabled") : $STR("disabled");
        } else {
            return field.type === "password" && !revealPasswords
                ? replacementIfProtected
                : field.value;
        }
    }

    static getDisplayValue(field: Field, revealPasswords: boolean) {
        return Field.getDisplayValueInternal(
            field,
            revealPasswords,
            "*".repeat(field.value.length)
        );
    }

    static getDisplayName(field: Field) {
        if (field.name === "KeePass username") {
            return $STR("username");
        } else if (field.name === "KeePass password") {
            return $STR("password");
        } else {
            return field.name ? field.name : "[ " + $STR("no_name") + " ]";
        }
    }

    static getDisplayTooltip(field: Field, revealPasswords: boolean) {
        return (
            Field.getDisplayName(field) +
            ": " +
            Field.getDisplayValueInternal(field, revealPasswords, $STR("click_to_reveal_hide"))
        );
    }

    static typeFromDOMtype(domType: string): FieldType {
        // We can't know every type that may exist in future so assume:
        // 1. unknown types must be text
        // 2. we won't be asked to handle types for irrelevant elements such as buttons
        switch (domType) {
            case "password":
                return "password";
            case "radio":
                return "existing";
            case "checkbox":
                return "boolean";
            case "select-one":
                return "existing";
            default:
                return "text";
        }
    }

    // By convention the first non-password item will be the username and the password will be either 1st or 2nd in the list
    static combineDomFieldLists(usernameIndex: number, otherFields: Field[], passwords: Field[]) {
        const fields: Field[] = [];
        if (usernameIndex >= 0 && otherFields[usernameIndex]) {
            fields.push(otherFields[usernameIndex]);
        }
        passwords.forEach(f => {
            fields.push(f);
        });
        otherFields.forEach((f, index) => {
            if (index !== usernameIndex) {
                fields.push(f);
            }
        });
        return fields;
    }

    static fromDOM(element: any, domType: string, value: string) {
        const labels = collectLabels(element);
        return new Field({
            uuid: utils.newGUID(),
            name: element.name, //TODO:4: try to find a more user-facing name for the field (e.g. via a label element)
            locators: [
                new Locator({
                    name: element.name,
                    id: element.id,
                    type: domType,
                    labels
                })<commit_msg>Use first label as field display name if possible<commit_after>import { utils } from "../utils";
import { Locator } from "./Locator";
import { FieldDto, FormFieldTypeDTO } from "./KPRPCDTOs";

export type FieldType = "password" | "text" | "existing" | "boolean" | "otp" | "some-chars";

export class Field {
    // Unique ID that we can use to help UI operations and identification when there is no known name for this field.
    // Also may be useful in future for multiple-page reconfiguration or per-browser field configuration overrides?
    readonly uuid: string;

    // visual identifier for the field. May be specified by the user or inferred from
    // either default Entry data (username/password/OTP) or form field label or name attribute
    readonly name: string;

    // The value we'll fill into the form.
    // For existing determines which option to activate.
    // For boolean determines whether to activate or deactivate (to ignore, delete the field record)
    // For OTP, need to work out whether we will have to transfer the secrets and recalculate in the browser or just treat as standard value
    readonly value: string;

    // type of data this field represents. There's no 100% way to match this to the type of HTML form element but it's often logical.
    // E.g. password goes into password fields unless user explicitly re-configures that; existing would typically match something
    // like a radio button or select option already present on the HTML form; boolean would match a checkbox.
    readonly type: FieldType;

    // How we can find the correct target in the HTML.
    readonly locators: Locator[];

    // tracks the most recent value we want to reset to in cases where the user has that possibility
    readonly resetValue: string;

    constructor(field: Partial<Field>) {
        this.name = field.name || "";
        this.value = field.value || "";
        this.resetValue = field.resetValue || "";
        this.uuid = field.uuid || utils.newGUID();
        this.type = field.type || "text";
        this.locators = field.locators || [];
    }

    private static getDisplayValueInternal(
        field: Field,
        revealPasswords: boolean,
        replacementIfProtected: string
    ) {
        if (field.type === "boolean") {
            return field.value === "KEEFOX_CHECKED_FLAG_TRUE" ? $STR("enabled") : $STR("disabled");
        } else {
            return field.type === "password" && !revealPasswords
                ? replacementIfProtected
                : field.value;
        }
    }

    static getDisplayValue(field: Field, revealPasswords: boolean) {
        return Field.getDisplayValueInternal(
            field,
            revealPasswords,
            "*".repeat(field.value.length)
        );
    }

    static getDisplayName(field: Field) {
        if (field.name === "KeePass username") {
            return $STR("username");
        } else if (field.name === "KeePass password") {
            return $STR("password");
        } else {
            return field.name ? field.name : "[ " + $STR("no_name") + " ]";
        }
    }

    static getDisplayTooltip(field: Field, revealPasswords: boolean) {
        return (
            Field.getDisplayName(field) +
            ": " +
            Field.getDisplayValueInternal(field, revealPasswords, $STR("click_to_reveal_hide"))
        );
    }

    static typeFromDOMtype(domType: string): FieldType {
        // We can't know every type that may exist in future so assume:
        // 1. unknown types must be text
        // 2. we won't be asked to handle types for irrelevant elements such as buttons
        switch (domType) {
            case "password":
                return "password";
            case "radio":
                return "existing";
            case "checkbox":
                return "boolean";
            case "select-one":
                return "existing";
            default:
                return "text";
        }
    }

    // By convention the first non-password item will be the username and the password will be either 1st or 2nd in the list
    static combineDomFieldLists(usernameIndex: number, otherFields: Field[], passwords: Field[]) {
        const fields: Field[] = [];
        if (usernameIndex >= 0 && otherFields[usernameIndex]) {
            fields.push(otherFields[usernameIndex]);
        }
        passwords.forEach(f => {
            fields.push(f);
        });
        otherFields.forEach((f, index) => {
            if (index !== usernameIndex) {
                fields.push(f);
            }
        });
        return fields;
    }

    static fromDOM(element: any, domType: string, value: string) {
        const labels = collectLabels(element);
        return new Field({
            uuid: utils.newGUID(),
            name: labels && labels.length ? labels[0] : element.name,
            locators: [
                new Locator({
                    name: element.name,
                    id: element.id,
                    type: domType,
                    labels
                })
{'fraction_non_alphanumeric': 0.09807976366322009, 'fraction_numerical': 0.0017725258493353029, 'mean_word_length': 2.093512031678343, 'pattern_counts': {'":': 10, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, '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': '5134008', 'n_tokens_mistral': 2609, 'n_tokens_neox': 2543, 'n_words': 937}