403Webshell
Server IP : 34.87.99.72  /  Your IP : 216.73.217.31
Web Server : Apache/2.4.46 (Unix) OpenSSL/1.1.1n
System : Linux zlock-bitnami-lamp-prod-v2-vm 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64
User : bitnami ( 1000)
PHP Version : 7.4.18
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : OFF
Directory :  /opt/bitnami/apache/htdocs/app/node_modules/tiptap-extensions/src/plugins/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/bitnami/apache/htdocs/app/node_modules/tiptap-extensions/src/plugins/Suggestions.js
import { Plugin, PluginKey } from 'prosemirror-state'
import { Decoration, DecorationSet } from 'prosemirror-view'
import { insertText } from 'tiptap-commands'

function getTextBetween(node, from, to, blockSeparator, inlineSeparator, leafText = '\0') {
  let text = ''
  let blockSeparated = true
  let inlineNode = null
  node.content.nodesBetween(from, to, (innerNode, pos) => {
    if (innerNode.isText) {
      if (inlineNode) {
        inlineNode = null
        return
      }
      text += innerNode.text.slice(Math.max(from, pos) - pos, to - pos)
      blockSeparated = !blockSeparator
    } else if (innerNode.isLeaf && leafText) {
      text += leafText
      blockSeparated = !blockSeparator
    } else if (innerNode.isInline && !innerNode.isLeaf) {
      text += inlineSeparator
      if (innerNode.textContent) {
        text += innerNode.textContent
        inlineNode = innerNode
      }
      text += inlineSeparator
      blockSeparated = !blockSeparated
    } else if (!blockSeparated && innerNode.isBlock) {
      text += blockSeparator
      blockSeparated = true
    }
  }, 0)
  return text
}

// Create a matcher that matches when a specific character is typed. Useful for @mentions and #tags.
function triggerCharacter({
  char = '@',
  allowSpaces = false,
  startOfLine = false,
}) {

  return $position => {
    // cancel if top level node
    if ($position.depth <= 0) {
      return false
    }

    // Matching expressions used for later
    const escapedChar = `\\${char}`
    const suffix = new RegExp(`\\s${escapedChar}$`)
    const prefix = startOfLine ? '^' : ''
    const regexp = allowSpaces
      ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
      : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm')

    // Lookup the boundaries of the current node
    const textFrom = $position.before()
    const textTo = $position.end()
    const text = getTextBetween($position.doc, textFrom, textTo, '\0', '\0')

    let match = regexp.exec(text)
    let position
    while (match !== null) {
      // JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
      // or the line beginning
      const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)

      if (/^[\s\0]?$/.test(matchPrefix)) {
        // The absolute position of the match in the document
        const from = match.index + $position.start()
        let to = from + match[0].length

        // Edge case handling; if spaces are allowed and we're directly in between
        // two triggers
        if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
            match[0] += ' '
            to += 1
        }

        // If the $position is located within the matched substring, return that range
        if (from < $position.pos && to >= $position.pos) {
          position = {
            range: {
              from,
              to,
            },
            query: match[0].slice(char.length),
            text: match[0],
          }
        }
      }

      match = regexp.exec(text)
    }

    return position
  }
}

export default function SuggestionsPlugin({
  matcher = {
    char: '@',
    allowSpaces: false,
    startOfLine: false,
  },
  appendText = null,
  suggestionClass = 'suggestion',
  command = () => false,
  items = [],
  onEnter = () => false,
  onChange = () => false,
  onExit = () => false,
  onKeyDown = () => false,
  onFilter = (searchItems, query) => {
    if (!query) {
      return searchItems
    }

    return searchItems
      .filter(item => JSON.stringify(item).toLowerCase().includes(query.toLowerCase()))
  },
}) {
  return new Plugin({
    key: new PluginKey('suggestions'),

    view() {
      return {
        update: async (view, prevState) => {
          const prev = this.key.getState(prevState)
          const next = this.key.getState(view.state)

          // See how the state changed
          const moved = prev.active && next.active && prev.range.from !== next.range.from
          const started = !prev.active && next.active
          const stopped = prev.active && !next.active
          const changed = !started && !stopped && prev.query !== next.query
          const handleStart = started || moved
          const handleChange = changed && !moved
          const handleExit = stopped || moved

          // Cancel when suggestion isn't active
          if (!handleStart && !handleChange && !handleExit) {
            return
          }

          const state = handleExit ? prev : next
          const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`)

          // build a virtual node for popper.js or tippy.js
          // this can be used for building popups without a DOM node
          const virtualNode = decorationNode ? {
            getBoundingClientRect() {
              return decorationNode.getBoundingClientRect()
            },
            clientWidth: decorationNode.clientWidth,
            clientHeight: decorationNode.clientHeight,
          } : null

          const props = {
            view,
            range: state.range,
            query: state.query,
            text: state.text,
            decorationNode,
            virtualNode,
            items: (handleChange || handleStart)
              ? await onFilter(Array.isArray(items) ? items : await items(), state.query)
              : [],
            command: ({ range, attrs }) => {
              command({
                range,
                attrs,
                schema: view.state.schema,
              })(view.state, view.dispatch, view)

              if (appendText) {
                insertText(appendText)(view.state, view.dispatch, view)
              }
            },
          }

          // Trigger the hooks when necessary
          if (handleExit) {
            onExit(props)
          }

          if (handleChange) {
            onChange(props)
          }

          if (handleStart) {
            onEnter(props)
          }
        },
      }
    },

    state: {

      // Initialize the plugin's internal state.
      init() {
        return {
          active: false,
          range: {},
          query: null,
          text: null,
        }
      },

      // Apply changes to the plugin state from a view transaction.
      apply(tr, prev) {
        const { selection } = tr
        const next = { ...prev }

        // We can only be suggesting if there is no selection
        if (selection.from === selection.to) {
          // Reset active state if we just left the previous suggestion range
          if (selection.from < prev.range.from || selection.from > prev.range.to) {
            next.active = false
          }

          // Try to match against where our cursor currently is
          const $position = selection.$from
          const match = triggerCharacter(matcher)($position)
          const decorationId = (Math.random() + 1).toString(36).substr(2, 5)

          // If we found a match, update the current state to show it
          if (match) {
            next.active = true
            next.decorationId = prev.decorationId ? prev.decorationId : decorationId
            next.range = match.range
            next.query = match.query
            next.text = match.text
          } else {
            next.active = false
          }
        } else {
          next.active = false
        }

        // Make sure to empty the range if suggestion is inactive
        if (!next.active) {
          next.decorationId = null
          next.range = {}
          next.query = null
          next.text = null
        }

        return next
      },
    },

    props: {

      // Call the keydown hook if suggestion is active.
      handleKeyDown(view, event) {
        const { active, range } = this.getState(view.state)

        if (!active) return false

        return onKeyDown({ view, event, range })
      },

      // Setup decorator on the currently active suggestion.
      decorations(editorState) {
        const { active, range, decorationId } = this.getState(editorState)

        if (!active) return null

        return DecorationSet.create(editorState.doc, [
          Decoration.inline(range.from, range.to, {
            nodeName: 'span',
            class: suggestionClass,
            'data-decoration-id': decorationId,
          }),
        ])
      },
    },
  })
}

Youez - 2016 - github.com/yon3zu
LinuXploit