<commit_before>import {
  Component, ViewChild, ElementRef,
  forwardRef, OnDestroy, ViewEncapsulation, OnInit,
  Output, EventEmitter, Input, TemplateRef,
  OnChanges, SimpleChanges,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Node as ProsemirrorNode } from 'prosemirror-model';

import { NgxEditorService, NgxEditorServiceConfig } from './editor.service';
import { SharedService } from './services/shared/shared.service';
import { Toolbar } from './types';
import { editable as editablePlugin, placeholder as placeholderPlugin } from 'ngx-editor/plugins';
import { toDoc, toHTML } from './html';

@Component({
  selector: 'ngx-editor',
  templateUrl: './editor.component.html',
  styleUrls: ['./editor.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => NgxEditorComponent),
    multi: true
  }],
  encapsulation: ViewEncapsulation.None
})

export class NgxEditorComponent implements ControlValueAccessor, OnInit, OnDestroy, OnChanges {
  @ViewChild('ngxEditor', { static: true }) ngxEditor: ElementRef;

  view: EditorView;
  private onChange: (value: Record<string, any> | string) => void;
  private onTouched: () => void;

  private config: NgxEditorServiceConfig;
  private editorInitialized = false;

  @Input() customMenuRef: TemplateRef<any>;
  @Input() placeholder = 'Type here...';
  @Input() editable = true;
  @Output() init = new EventEmitter<EditorView>();
  @Output() focusOut = new EventEmitter<void>();
  @Output() focusIn = new EventEmitter<void>();

  constructor(
    ngxEditorService: NgxEditorService,
    private sharedService: SharedService,
  ) {
    this.config = ngxEditorService.config;
  }

  get toolbar(): Toolbar {
    return this.config.menu?.toolbar;
  }

  writeValue(value: Record<string, any> | string | null): void {
    if (!this.editorInitialized) {
      return;
    }

    this.updateContent(value);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  private parse(value: Record<string, any> | string): ProsemirrorNode {
    if (!value) {
      return null;
    }

    let contentJson = null;

    if (typeof value === 'string') {
      contentJson = toDoc(value, this.config.schema);
    } else {
      contentJson = value;
    }

    const { schema } = this.config;
    return schema.nodeFromJSON(contentJson);
  }

  private updateContent(value: Record<string, any> | string): void {
    try {
      const { state } = this.view;
      const { tr, doc } = state;

      const newDoc = this.parse(value);
      tr.replaceWith(0, state.doc.content.size, newDoc)
        .setMeta('PREVENT_EMIT', true);

      // don't emit if both content is same
      if (doc !== null && doc.eq(tr.doc)) {
        return;
      }

      if (!tr.docChanged) {
        return;
      }

      this.view.dispatch(tr);
    } catch (err) {
      console.error('Unable to update document.', err);
    }
  }

  private handleTransactions(tr: Transaction): void {
    const { state } = this.view.state.applyTransaction(tr);
    this.view.updateState(state);

    if (!tr.docChanged || !this.onChange || tr.getMeta('PREVENT_UPDATE')) {
      return;
    }

    const json = state.doc.toJSON();
    this.onChange(json);
  }

  private createUpdateWatcherPlugin(): Plugin {
    const plugin = new Plugin({
      key: new PluginKey('ngx-update-watcher'),
      view: () => {
        return {
          update: (view: EditorView) => this.sharedService.plugin.update.next(view),
          destroy: () => this.sharedService.plugin.destroy.next()<commit_msg>fix: don't emit onchange on init<commit_after>import {
  Component, ViewChild, ElementRef,
  forwardRef, OnDestroy, ViewEncapsulation, OnInit,
  Output, EventEmitter, Input, TemplateRef,
  OnChanges, SimpleChanges,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Node as ProsemirrorNode } from 'prosemirror-model';

import { NgxEditorService, NgxEditorServiceConfig } from './editor.service';
import { SharedService } from './services/shared/shared.service';
import { Toolbar } from './types';
import { editable as editablePlugin, placeholder as placeholderPlugin } from 'ngx-editor/plugins';
import { toDoc, toHTML } from './html';

@Component({
  selector: 'ngx-editor',
  templateUrl: './editor.component.html',
  styleUrls: ['./editor.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => NgxEditorComponent),
    multi: true
  }],
  encapsulation: ViewEncapsulation.None
})

export class NgxEditorComponent implements ControlValueAccessor, OnInit, OnDestroy, OnChanges {
  @ViewChild('ngxEditor', { static: true }) ngxEditor: ElementRef;

  view: EditorView;
  private onChange: (value: Record<string, any> | string) => void;
  private onTouched: () => void;

  private config: NgxEditorServiceConfig;
  private editorInitialized = false;

  @Input() customMenuRef: TemplateRef<any>;
  @Input() placeholder = 'Type here...';
  @Input() editable = true;
  @Output() init = new EventEmitter<EditorView>();
  @Output() focusOut = new EventEmitter<void>();
  @Output() focusIn = new EventEmitter<void>();

  constructor(
    ngxEditorService: NgxEditorService,
    private sharedService: SharedService,
  ) {
    this.config = ngxEditorService.config;
  }

  get toolbar(): Toolbar {
    return this.config.menu?.toolbar;
  }

  writeValue(value: Record<string, any> | string | null): void {
    if (!this.editorInitialized) {
      return;
    }

    this.updateContent(value);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  private parse(value: Record<string, any> | string): ProsemirrorNode {
    if (!value) {
      return null;
    }

    let contentJson = null;

    if (typeof value === 'string') {
      contentJson = toDoc(value, this.config.schema);
    } else {
      contentJson = value;
    }

    const { schema } = this.config;
    return schema.nodeFromJSON(contentJson);
  }

  private updateContent(value: Record<string, any> | string): void {
    try {
      const { state } = this.view;
      const { tr, doc } = state;

      const newDoc = this.parse(value);
      tr.replaceWith(0, state.doc.content.size, newDoc)
        .setMeta('PREVENT_ONCHANGE', true);

      // don't emit if both content is same
      if (doc !== null && doc.eq(tr.doc)) {
        return;
      }

      if (!tr.docChanged) {
        return;
      }

      this.view.dispatch(tr);
    } catch (err) {
      console.error('Unable to update document.', err);
    }
  }

  private handleTransactions(tr: Transaction): void {
    const { state } = this.view.state.applyTransaction(tr);
    this.view.updateState(state);

    if (!tr.docChanged || !this.onChange || tr.getMeta('PREVENT_ONCHANGE')) {
      return;
    }

    const json = state.doc.toJSON();
    this.onChange(json);
  }

  private createUpdateWatcherPlugin(): Plugin {
    const plugin = new Plugin({
      key: new PluginKey('ngx-update-watcher'),
      view: () => {
        return {
          update: (view: EditorView) => this.sharedService.plugin.update.next(view),
          destroy: () => this.sharedService.plugin.destroy.next()
{'fraction_non_alphanumeric': 0.13518834492715578, 'fraction_numerical': 0.000262501640635254, 'mean_word_length': 3.4457409568261377, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 31, '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': '5080772', 'n_tokens_mistral': 2379, 'n_tokens_neox': 2321, 'n_words': 556}