<commit_before>import { appConfigFactory } from '@/app-config'
import { message } from '@/_helpers/browser-api'
import { isContainChinese, isContainEnglish } from '@/_helpers/lang-check'
import { createAppConfigStream } from '@/_helpers/config-manager'
import * as selection from '@/_helpers/selection'
import { MsgType, PostMsgType, PostMsgSelection, MsgSelection } from '@/typings/message'

// import { Observable, fromEvent, timer, merge, of, asyncScheduler } from 'rxjs'
// import { map, mapTo, scan, filter, take, switchMap, buffer, debounceTime, observeOn, share, distinctUntilChanged } from 'rxjs/operators'
import { Observable } from 'rxjs/Observable'
import { fromEvent } from 'rxjs/observable/fromEvent'
import { timer } from 'rxjs/observable/timer'
import { merge } from 'rxjs/observable/merge'
import { of } from 'rxjs/observable/of'
import { map } from 'rxjs/operators/map'
import { delay } from 'rxjs/operators/delay'
import { mapTo } from 'rxjs/operators/mapTo'
import { scan } from 'rxjs/operators/scan'
import { filter } from 'rxjs/operators/filter'
import { take } from 'rxjs/operators/take'
import { switchMap } from 'rxjs/operators/switchMap'
import { buffer } from 'rxjs/operators/buffer'
import { debounceTime } from 'rxjs/operators/debounceTime'
import { share } from 'rxjs/operators/share'
import { distinctUntilChanged } from 'rxjs/operators/distinctUntilChanged'

message.addListener(MsgType.__PreloadSelection__, (data, sender) => {
  return Promise.resolve(selection.getSelectionInfo())
})

/** Pass through message from iframes */
window.addEventListener('message', ({ data, source }: { data: PostMsgSelection, source: Window | null }) => {
  if (data.type !== PostMsgType.Selection) { return }

  // get the souce iframe
  const iframe = Array.from(document.querySelectorAll('iframe'))
    .find(({ contentWindow }) => contentWindow === source)
  if (!iframe) { return }

  const { selectionInfo, mouseX, mouseY, ctrlKey, dbClick } = data
  const { left, top } = iframe.getBoundingClientRect()

  sendMessage(
    mouseX + left,
    mouseY + top,
    dbClick,
    ctrlKey,
    selectionInfo
  )
})

let config = appConfigFactory()
let isCtrlPressed = false
let clickPeriodCount = 0
let lastMousedownTarget: EventTarget | null

const isCtrlPressed$$ = share<boolean>()(isKeyPressed(isCtrlKey))

const validCtrlPressed$$ = isCtrlPressed$$.pipe(
  filter(isCtrlPressed => config.active && isCtrlPressed),
  share(),
)

const tripleCtrlPressed$ = validCtrlPressed$$.pipe(
  buffer(debounceTime(500)(validCtrlPressed$$)),
  filter(group => group.length >= 3),
)

merge(
  fromEvent<MouseEvent>(window, 'mousedown', { capture: true }),
  fromEvent<TouchEvent>(window, 'touchstart', { capture: true }),
).subscribe(({ target }) => {
  lastMousedownTarget = target
})

const validMouseup$$ = merge(
  fromEvent<MouseEvent>(window, 'mouseup', { capture: true }),
  fromEvent<TouchEvent>(window, 'touchend', { capture: true }).pipe(map(e => e.changedTouches[0])),
).pipe(
  filter(({ target }) => {
    if (!config.active || window.name === 'saladict-frame') {
      return false
    }

    if (target) {
      if (typeof target['className'] === 'string' && target['className'].startsWith('saladict-')) {
        return false
      }
    }

    return true
  }),
  // if user click on a selected text,
  // getSelection would reture the text before the highlight disappears
  // delay to wait for selection get cleared
  delay(10),
  share(),
)

const clickPeriodCount$ = merge(
  mapTo(true)(validMouseup$$),
  switchMap(() => timer(config.doubleClickDelay).pipe(take(1), mapTo(false)))(validMouseup$$)
).pipe(
  scan((sum: number, flag: boolean) => flag ? sum + 1 : 0, 0)
)

createAppConfigStream().subscribe(newConfig => config = newConfig)

isCtrlPressed$$.subscribe(flag => isCtrlPressed = flag)

isKeyPressed(isEscapeKey).subscribe(flag => {
  if (flag) {
    message.self.send({ type: MsgType.EscapeKey })
  }
})

clickPeriodCount$.subscribe(count => clickPeriodCount = count)

tripleCtrlPressed$.subscribe(() => {
  message.self.send({ type: MsgType.TripleCtrl })
})

let lastText: string
let lastContext: string
validMouseup$$.subscribe(({ clientX, clientY }) => {
  if (config.noTypeField && isTypeField(lastMousedownTarget)) {
    sendEmptyMessage()
    return
  }

  const text = selection.getSelectionText()
  if (
    text && (
      (config.language.english && isContainEnglish(text) && !isContainChinese(text)) ||
      (config.language.chinese && isContainChinese(text))
    )
  ) {
    const context = selection.getSelectionSentence()
    if (text === lastText && context === lastContext && clickPeriodCount < 2) {
      // Ignore this rule if it is a double click.
      // Same selection. This could be caused by other widget on the page
      // that uses preventDefault which stops selection being cleared when clicked.
      // Ignore it so that the panel won't follow.
      return
    }
    lastContext = context

    sendMessage(
      clientX,
      clientY,
      clickPeriodCount >= 2,
      isCtrlPressed,
      {
        text: selection.getSelectionText(),
        context,
        title: window.pageTitle || document.title,
        url: window.pageURL || document.URL,<commit_msg>fix(selection): ctrl key detection #124 #122 #114<commit_after>import { appConfigFactory } from '@/app-config'
import { message } from '@/_helpers/browser-api'
import { isContainChinese, isContainEnglish } from '@/_helpers/lang-check'
import { createAppConfigStream } from '@/_helpers/config-manager'
import * as selection from '@/_helpers/selection'
import { MsgType, PostMsgType, PostMsgSelection, MsgSelection } from '@/typings/message'

// import { Observable, fromEvent, timer, merge, of, asyncScheduler } from 'rxjs'
// import { map, mapTo, scan, filter, take, switchMap, buffer, debounceTime, observeOn, share, distinctUntilChanged } from 'rxjs/operators'
import { Observable } from 'rxjs/Observable'
import { fromEvent } from 'rxjs/observable/fromEvent'
import { timer } from 'rxjs/observable/timer'
import { merge } from 'rxjs/observable/merge'
import { of } from 'rxjs/observable/of'
import { map } from 'rxjs/operators/map'
import { delay } from 'rxjs/operators/delay'
import { mapTo } from 'rxjs/operators/mapTo'
import { scan } from 'rxjs/operators/scan'
import { filter } from 'rxjs/operators/filter'
import { take } from 'rxjs/operators/take'
import { switchMap } from 'rxjs/operators/switchMap'
import { buffer } from 'rxjs/operators/buffer'
import { debounceTime } from 'rxjs/operators/debounceTime'
import { share } from 'rxjs/operators/share'
import { distinctUntilChanged } from 'rxjs/operators/distinctUntilChanged'

message.addListener(MsgType.__PreloadSelection__, (data, sender) => {
  return Promise.resolve(selection.getSelectionInfo())
})

/** Pass through message from iframes */
window.addEventListener('message', ({ data, source }: { data: PostMsgSelection, source: Window | null }) => {
  if (data.type !== PostMsgType.Selection) { return }

  // get the souce iframe
  const iframe = Array.from(document.querySelectorAll('iframe'))
    .find(({ contentWindow }) => contentWindow === source)
  if (!iframe) { return }

  const { selectionInfo, mouseX, mouseY, ctrlKey, dbClick } = data
  const { left, top } = iframe.getBoundingClientRect()

  sendMessage(
    mouseX + left,
    mouseY + top,
    dbClick,
    ctrlKey,
    selectionInfo
  )
})

let config = appConfigFactory()
let clickPeriodCount = 0
let lastMousedownTarget: EventTarget | null

const validCtrlPressed$$ = isKeyPressed(isCtrlKey).pipe(
  filter(isCtrlPressed => config.active && isCtrlPressed),
  share(),
)

const tripleCtrlPressed$ = validCtrlPressed$$.pipe(
  buffer(debounceTime(500)(validCtrlPressed$$)),
  filter(group => group.length >= 3),
)

merge(
  fromEvent<MouseEvent>(window, 'mousedown', { capture: true }),
  fromEvent<TouchEvent>(window, 'touchstart', { capture: true }),
).subscribe(({ target }) => {
  lastMousedownTarget = target
})

const validMouseup$$ = merge(
  fromEvent<MouseEvent>(window, 'mouseup', { capture: true }),
  fromEvent<TouchEvent>(window, 'touchend', { capture: true }).pipe(map(e => e.changedTouches[0])),
).pipe(
  filter(({ target }) => {
    if (!config.active || window.name === 'saladict-frame') {
      return false
    }

    if (target) {
      if (typeof target['className'] === 'string' && target['className'].startsWith('saladict-')) {
        return false
      }
    }

    return true
  }),
  // if user click on a selected text,
  // getSelection would reture the text before the highlight disappears
  // delay to wait for selection get cleared
  delay(10),
  share(),
)

const clickPeriodCount$ = merge(
  mapTo(true)(validMouseup$$),
  switchMap(() => timer(config.doubleClickDelay).pipe(take(1), mapTo(false)))(validMouseup$$)
).pipe(
  scan((sum: number, flag: boolean) => flag ? sum + 1 : 0, 0)
)

createAppConfigStream().subscribe(newConfig => config = newConfig)

isKeyPressed(isEscapeKey).subscribe(flag => {
  if (flag) {
    message.self.send({ type: MsgType.EscapeKey })
  }
})

clickPeriodCount$.subscribe(count => clickPeriodCount = count)

tripleCtrlPressed$.subscribe(() => {
  message.self.send({ type: MsgType.TripleCtrl })
})

let lastText: string
let lastContext: string
validMouseup$$.subscribe(event => {
  if (config.noTypeField && isTypeField(lastMousedownTarget)) {
    sendEmptyMessage()
    return
  }

  const text = selection.getSelectionText()
  if (
    text && (
      (config.language.english && isContainEnglish(text) && !isContainChinese(text)) ||
      (config.language.chinese && isContainChinese(text))
    )
  ) {
    const context = selection.getSelectionSentence()
    if (text === lastText && context === lastContext && clickPeriodCount < 2) {
      // Ignore this rule if it is a double click.
      // Same selection. This could be caused by other widget on the page
      // that uses preventDefault which stops selection being cleared when clicked.
      // Ignore it so that the panel won't follow.
      return
    }
    lastContext = context

    sendMessage(
      event.clientX,
      event.clientY,
      clickPeriodCount >= 2,
      Boolean(event['metaKey'] || event['ctrlKey']),
      {
        text: selection.getSelectionText(),
        context,
        title: window.pageTitle || document.title,
        url: window.pageURL || document.URL,
{'fraction_non_alphanumeric': 0.137217140105922, 'fraction_numerical': 0.0035628310062590274, 'mean_word_length': 4.141584158415841, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 47, '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': '624262', 'n_tokens_mistral': 3223, 'n_tokens_neox': 3168, 'n_words': 793}