<commit_before>import { ElementRef } from '@angular/core';
import { wrapIn, setBlockType, chainCommands, newlineInCode, toggleMark, baseKeymap } from 'prosemirror-commands';
import { blockQuoteRule, orderedListRule, bulletListRule, codeBlockRule, headingRule,
  inputRules, allInputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { MenuBarEditorView, icons, MenuItem, Dropdown, DropdownSubmenu,
  wrapItem, blockTypeItem, joinUpItem, liftItem} from 'prosemirror-menu';
import { wrapInList, splitListItem, liftListItem, sinkListItem } from 'prosemirror-schema-list';
import { EditorState, Plugin, Selection } from 'prosemirror-state';
import { Mark, MarkType, Node } from 'prosemirror-model';

export class ProseMirrorImage {
  constructor(public name: string,
              public src: string,
              public title: string,
              public alt: string) {}
}

export class ProseMirrorMarkdownEditor {

  view: MenuBarEditorView;
  savedState: EditorState;

  constructor(private el: ElementRef,
              private value: string,
              private images: ProseMirrorImage[],
              private setModel: Function,
              private promptForLink: Function) {
    this.savedState = EditorState.create(this.stateConfig());
    this.view = new MenuBarEditorView(el.nativeElement, this.viewProps(this.savedState));
  }

  update(images: ProseMirrorImage[]) {
    this.images = images;
    let state = this.view.editor.state.reconfigure(this.stateConfig());
    this.view.update(this.viewProps(state));
  }

  resetEditor() {
    this.view.updateState(this.savedState);
  }

  setSavedState() {
    this.savedState = this.view.editor.state;
  }

  destroy() {
    this.view.editor.destroy();
  }

  isSelectionEmpty() {
    return this.view.editor.state.selection.empty;
  }

  viewProps(state: any) {
    return {
      state,
      onAction: (action) => {
        this.view.updateState(this.view.editor.state.applyAction(action));
        this.setModel(defaultMarkdownSerializer.serialize(this.view.editor.state.doc));
      }
    };
  }

  stateConfig() {
    return {
      doc: defaultMarkdownParser.parse(this.value ? this.value : ''),
      plugins: this.buildMenuItemsPlugin()
    };
  }

  createLinkItem(url, title) {
    if (this.markActive(this.view.editor.state, schema.marks.link)) {
      // can't see how to edit mark, only toggle. So toggle off to toggle back on with new attrs
      toggleMark(schema.marks.link)(this.view.editor.state, this.view.props.onAction);
    }
    toggleMark(schema.marks.link, {
      href: url,
      title
    })(this.view.editor.state, this.view.props.onAction);
  }

  linkItem(markType) {
    return this.markItem(markType, {
      title: 'Add or remove link',
      icon: icons.link,
      run: (state, onAction, view) => {
        if (this.markActive(state, markType)) {
          let linkMark = this.selectAroundMark(markType, state.doc, state.selection.anchor, onAction);
          this.promptForLink(linkMark.attrs.href, linkMark.attrs.title);
          return true;
        } else {
          this.promptForLink();
        }
      }
    });
  }

  selectAroundMark(markType: MarkType, doc: Node, pos: number, onAction): Mark {
    let $pos = doc.resolve(pos),
      parent = $pos.parent;

    let start = parent.childAfter($pos.parentOffset);
    if (!start.node) {
      return null;
    }

    let targetMark = start.node.marks.find(mark => mark.type.name === markType.name);
    if (!targetMark) {
      return null;
    }

    let startIndex = $pos.index(),
      startPos = $pos.start() + start.offset;
    while (startIndex > 0 && targetMark.isInSet(parent.child(startIndex - 1).marks)) {
      startPos -= parent.child(--startIndex).nodeSize;
    }
    let endIndex = $pos.indexAfter(),
      endPos = startPos + start.node.nodeSize;
    while (endPos < parent.childCount && targetMark.isInSet(parent.child(endIndex).marks)) {
      endPos += parent.child(endIndex++).nodeSize;
    }
    //return {from: startPos, to: endPos};
    let selection = Selection.between(doc.resolve(startPos), doc.resolve(endPos));
    onAction(selection.action());

    return targetMark;<commit_msg>fixes link node selection when cursor at end of line<commit_after>import { ElementRef } from '@angular/core';
import { wrapIn, setBlockType, chainCommands, newlineInCode, toggleMark, baseKeymap } from 'prosemirror-commands';
import { blockQuoteRule, orderedListRule, bulletListRule, codeBlockRule, headingRule,
  inputRules, allInputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
import { schema, defaultMarkdownParser, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { MenuBarEditorView, icons, MenuItem, Dropdown, DropdownSubmenu,
  wrapItem, blockTypeItem, joinUpItem, liftItem} from 'prosemirror-menu';
import { wrapInList, splitListItem, liftListItem, sinkListItem } from 'prosemirror-schema-list';
import { EditorState, Plugin, Selection } from 'prosemirror-state';
import { Mark, MarkType, Node } from 'prosemirror-model';

export class ProseMirrorImage {
  constructor(public name: string,
              public src: string,
              public title: string,
              public alt: string) {}
}

export class ProseMirrorMarkdownEditor {

  view: MenuBarEditorView;
  savedState: EditorState;

  constructor(private el: ElementRef,
              private value: string,
              private images: ProseMirrorImage[],
              private setModel: Function,
              private promptForLink: Function) {
    this.savedState = EditorState.create(this.stateConfig());
    this.view = new MenuBarEditorView(el.nativeElement, this.viewProps(this.savedState));
  }

  update(images: ProseMirrorImage[]) {
    this.images = images;
    let state = this.view.editor.state.reconfigure(this.stateConfig());
    this.view.update(this.viewProps(state));
  }

  resetEditor() {
    this.view.updateState(this.savedState);
  }

  setSavedState() {
    this.savedState = this.view.editor.state;
  }

  destroy() {
    this.view.editor.destroy();
  }

  isSelectionEmpty() {
    return this.view.editor.state.selection.empty;
  }

  viewProps(state: any) {
    return {
      state,
      onAction: (action) => {
        this.view.updateState(this.view.editor.state.applyAction(action));
        this.setModel(defaultMarkdownSerializer.serialize(this.view.editor.state.doc));
      }
    };
  }

  stateConfig() {
    return {
      doc: defaultMarkdownParser.parse(this.value ? this.value : ''),
      plugins: this.buildMenuItemsPlugin()
    };
  }

  createLinkItem(url, title) {
    if (this.markActive(this.view.editor.state, schema.marks.link)) {
      // can't see how to edit mark, only toggle. So toggle off to toggle back on with new attrs
      toggleMark(schema.marks.link)(this.view.editor.state, this.view.props.onAction);
    }
    toggleMark(schema.marks.link, {
      href: url,
      title
    })(this.view.editor.state, this.view.props.onAction);
  }

  linkItem(markType) {
    return this.markItem(markType, {
      title: 'Add or remove link',
      icon: icons.link,
      run: (state, onAction, view) => {
        if (this.markActive(state, markType)) {
          let linkMark = this.selectAroundMark(markType, state.doc, state.selection.anchor, onAction);
          if (linkMark) {
            this.promptForLink(linkMark.attrs.href, linkMark.attrs.title);
            return true;
          }
        } else {
          this.promptForLink();
        }
      }
    });
  }

  selectAroundMark(markType: MarkType, doc: Node, pos: number, onAction): Mark {
    let $pos = doc.resolve(pos),
      parent = $pos.parent;

    let start = parent.childAfter($pos.parentOffset);
    if (!start.node || start.node.marks.length === 0) {
      // happens if the cursor is at the end of the line or the end of the node, use nodeAt pos - 1 to find node marks
      start.node = parent.nodeAt($pos.parentOffset - 1);
      if (!start.node) {
        return null;
      }
    }

    let targetMark = start.node.marks.find(mark => mark.type.name === markType.name);
    if (!targetMark) {
      return null;
    }

    let startIndex = $pos.index(),
      startPos = $pos.start() + start.offset;
    while (startIndex > 0 && targetMark.isInSet(parent.child(startIndex - 1).marks)) {
      startPos -= parent.child(--startIndex).nodeSize;
    }
    let endIndex = $pos.indexAfter(),
      endPos = startPos + start.node.nodeSize;
    while (endPos < parent.childCount && targetMark.isInSet(parent.child(endIndex).marks)) {
      endPos += parent.child(endIndex++).nodeSize;
    }

    let selection = Selection.between(doc.resolve(startPos), doc.resolve(endPos));
    onAction(selection.action());

    return targetMark;
{'fraction_non_alphanumeric': 0.12459200900393923, 'fraction_numerical': 0.0007878446820483962, 'mean_word_length': 3.1855864342910976, 'pattern_counts': {'":': 0, '<': 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': '1082200', 'n_tokens_mistral': 2746, 'n_tokens_neox': 2680, 'n_words': 569}