<commit_before>/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit, Optional, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { KeyCodes } from '@deja-js/core';
import { BehaviorSubject, from as observableFrom, fromEvent as observableFromEvent, timer as observableTimer } from 'rxjs';
import { filter, first, map, takeUntil, takeWhile, tap } from 'rxjs/operators';

const noop = () => { };

@Directive({
    selector: '[deja-editable]',
})
export class DejaEditableDirective implements ControlValueAccessor, OnDestroy, OnInit {
    public onTouchedCallback: () => void = noop;
    public onChangeCallback: (_: any) => void = noop;
    private model: string;
    private _inEdition = false;
    private _editMode = false;
    private _mandatory = false;
    private _multiline = false;
    private edit$ = new BehaviorSubject<[boolean, boolean]>([false, false]);
    private element: HTMLElement;
    private isAlive = true;

    @HostBinding('attr.disabled') public _disabled: boolean = null;

    constructor(elementRef: ElementRef, @Self() @Optional() public _control: NgControl) {
        if (this._control) {
            this._control.valueAccessor = this;
        }

        this.element = elementRef.nativeElement as HTMLElement;

        observableFromEvent(this.element, 'mousedown').pipe(
            takeWhile(() => this.isAlive))
            .subscribe((e: MouseEvent) => {
                if (this.inEdition || this.disabled) {
                    e.cancelBubble = true;
                    return false;
                } else if (this.editMode) {
                    this.edit$.next([true, true]);
                    e.cancelBubble = true;
                    return false;
                }
            });

        const inEdition$ = observableFrom(this.edit$).pipe(
            filter(([value]) => value !== this._inEdition),
            map(([value, selectOnFocus]) => {
                if (selectOnFocus !== false) {
                    observableTimer(10).pipe(
                        first())
                        .subscribe(() => {
                            this.selectAll();
                            this.focus();
                        });
                }
                return value;
            }),
            tap((value) => {
                this._inEdition = value;
                if (value) {
                    this.element.setAttribute('contenteditable', 'true');
                } else {
                    this.element.removeAttribute('contenteditable');
                }
                this.refreshView();
            }));

        const kill$ = inEdition$.pipe(
            filter((value) => !value));

        inEdition$.pipe(
            filter((value) => value))
            .subscribe(() => {
                observableFromEvent(this.element.ownerDocument, 'mousedown').pipe(
                    first(),
                    takeUntil(kill$),
                    filter((event: MouseEvent) => !this.isChildElement(event.target as HTMLElement)))
                    .subscribe(() => {
                        const text = this.element.innerText.replace(/\n/g, '<br />').replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
                        this.onTouchedCallback();
                        if (text || !this.mandatory) {
                            this.value = text;
                        }

                        this.inEdition = false;
                    });

                observableFromEvent(this.element, 'keydown').pipe(
                    first(),
                    takeUntil(kill$))
                    .subscribe((e: KeyboardEvent) => {
                        e.cancelBubble = true;
                        e.stopPropagation();
                        if (e.keyCode === KeyCodes.Enter && !this.multiline) {
                            const text = this.element.innerText;
                            if (text || !this.mandatory) {
                                this.value = text;
                            }
                            this.inEdition = false;
                            return false;

                        } else if (e.keyCode === KeyCodes.Escape) {
                            this.inEdition = false;
                            return false;
                        }
                        return false;
                    });
            });
    }

    /** Définit une valeur indiquant si le contenu édité est obligatoire. Si la valeur est 'true' la sortie du mode édition ne sera pas possible tant qu'un contenu n'est pas ajouté. */
    @Input()
    public set mandatory(value: boolean | string) {
        this._mandatory = coerceBooleanProperty(value);
    }

    /** Retourne une valeur indiquant si le contenu édité est obligatoire. Si la valeur est 'true' la sortie du mode édition ne sera pas possible tant qu'un contenu n'est pas ajouté. */
    public get mandatory() {
        return this._mandatory;
    }<commit_msg>fix(ContentEditable): Removed useless first on observables<commit_after>/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit, Optional, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { KeyCodes } from '@deja-js/core';
import { BehaviorSubject, from as observableFrom, fromEvent as observableFromEvent, timer as observableTimer } from 'rxjs';
import { filter, first, map, takeUntil, takeWhile, tap } from 'rxjs/operators';

const noop = () => { };

@Directive({
    selector: '[deja-editable]',
})
export class DejaEditableDirective implements ControlValueAccessor, OnDestroy, OnInit {
    public onTouchedCallback: () => void = noop;
    public onChangeCallback: (_: any) => void = noop;
    private model: string;
    private _inEdition = false;
    private _editMode = false;
    private _mandatory = false;
    private _multiline = false;
    private edit$ = new BehaviorSubject<[boolean, boolean]>([false, false]);
    private element: HTMLElement;
    private isAlive = true;

    @HostBinding('attr.disabled') public _disabled: boolean = null;

    constructor(elementRef: ElementRef, @Self() @Optional() public _control: NgControl) {
        if (this._control) {
            this._control.valueAccessor = this;
        }

        this.element = elementRef.nativeElement as HTMLElement;

        observableFromEvent(this.element, 'mousedown').pipe(
            takeWhile(() => this.isAlive))
            .subscribe((e: MouseEvent) => {
                if (this.inEdition || this.disabled) {
                    e.cancelBubble = true;
                    return false;
                } else if (this.editMode) {
                    this.edit$.next([true, true]);
                    e.cancelBubble = true;
                    return false;
                }
            });

        const inEdition$ = observableFrom(this.edit$).pipe(
            filter(([value]) => value !== this._inEdition),
            map(([value, selectOnFocus]) => {
                if (selectOnFocus !== false) {
                    observableTimer(10).pipe(
                        first())
                        .subscribe(() => {
                            this.selectAll();
                            this.focus();
                        });
                }
                return value;
            }),
            tap((value) => {
                this._inEdition = value;
                if (value) {
                    this.element.setAttribute('contenteditable', 'true');
                } else {
                    this.element.removeAttribute('contenteditable');
                }
                this.refreshView();
            }));

        const kill$ = inEdition$.pipe(
            filter((value) => !value));

        inEdition$.pipe(
            filter((value) => value))
            .subscribe(() => {
                observableFromEvent(this.element.ownerDocument, 'mousedown').pipe(
                    takeUntil(kill$),
                    filter((event: MouseEvent) => !this.isChildElement(event.target as HTMLElement)))
                    .subscribe(() => {
                        const text = this.element.innerText.replace(/\n/g, '<br />').replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
                        this.onTouchedCallback();
                        if (text || !this.mandatory) {
                            this.value = text;
                        }

                        this.inEdition = false;
                    });

                observableFromEvent(this.element, 'keydown').pipe(
                    takeUntil(kill$))
                    .subscribe((e: KeyboardEvent) => {
                        e.cancelBubble = true;
                        e.stopPropagation();
                        if (e.keyCode === KeyCodes.Enter && !this.multiline) {
                            const text = this.element.innerText;
                            if (text || !this.mandatory) {
                                this.value = text;
                            }
                            this.inEdition = false;
                            return false;

                        } else if (e.keyCode === KeyCodes.Escape) {
                            this.inEdition = false;
                            return false;
                        }
                        return false;
                    });
            });
    }

    /** Définit une valeur indiquant si le contenu édité est obligatoire. Si la valeur est 'true' la sortie du mode édition ne sera pas possible tant qu'un contenu n'est pas ajouté. */
    @Input()
    public set mandatory(value: boolean | string) {
        this._mandatory = coerceBooleanProperty(value);
    }

    /** Retourne une valeur indiquant si le contenu édité est obligatoire. Si la valeur est 'true' la sortie du mode édition ne sera pas possible tant qu'un contenu n'est pas ajouté. */
    public get mandatory() {
        return this._mandatory;
    }
{'fraction_non_alphanumeric': 0.1143684063112688, 'fraction_numerical': 0.000746895714685837, 'mean_word_length': 1.5787193066923448, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 41, 'https://': 2, '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': '4116015', 'n_tokens_mistral': 2817, 'n_tokens_neox': 2694, 'n_words': 673}