tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

plugin.ts (5790B)


      1 import {type EditorView, type EditorProps} from "prosemirror-view"
      2 import {EditorState, EditorStateConfig} from "./state"
      3 import {Transaction} from "./transaction"
      4 
      5 /// This is the type passed to the [`Plugin`](#state.Plugin)
      6 /// constructor. It provides a definition for a plugin.
      7 export interface PluginSpec<PluginState> {
      8  /// The [view props](#view.EditorProps) added by this plugin. Props
      9  /// that are functions will be bound to have the plugin instance as
     10  /// their `this` binding.
     11  props?: EditorProps<Plugin<PluginState>>
     12 
     13  /// Allows a plugin to define a [state field](#state.StateField), an
     14  /// extra slot in the state object in which it can keep its own data.
     15  state?: StateField<PluginState>
     16 
     17  /// Can be used to make this a keyed plugin. You can have only one
     18  /// plugin with a given key in a given state, but it is possible to
     19  /// access the plugin's configuration and state through the key,
     20  /// without having access to the plugin instance object.
     21  key?: PluginKey
     22 
     23  /// When the plugin needs to interact with the editor view, or
     24  /// set something up in the DOM, use this field. The function
     25  /// will be called when the plugin's state is associated with an
     26  /// editor view.
     27  view?: (view: EditorView) => PluginView
     28 
     29  /// When present, this will be called before a transaction is
     30  /// applied by the state, allowing the plugin to cancel it (by
     31  /// returning false).
     32  filterTransaction?: (tr: Transaction, state: EditorState) => boolean
     33 
     34  /// Allows the plugin to append another transaction to be applied
     35  /// after the given array of transactions. When another plugin
     36  /// appends a transaction after this was called, it is called again
     37  /// with the new state and new transactions—but only the new
     38  /// transactions, i.e. it won't be passed transactions that it
     39  /// already saw.
     40  appendTransaction?: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => Transaction | null | undefined
     41 
     42  /// Additional properties are allowed on plugin specs, which can be
     43  /// read via [`Plugin.spec`](#state.Plugin.spec).
     44  [key: string]: any
     45 }
     46 
     47 /// A stateful object that can be installed in an editor by a
     48 /// [plugin](#state.PluginSpec.view).
     49 export type PluginView = {
     50  /// Called whenever the view's state is updated.
     51  update?: (view: EditorView, prevState: EditorState) => void
     52 
     53  /// Called when the view is destroyed or receives a state
     54  /// with different plugins.
     55  destroy?: () => void
     56 }
     57 
     58 function bindProps(obj: {[prop: string]: any}, self: any, target: {[prop: string]: any}) {
     59  for (let prop in obj) {
     60    let val = obj[prop]
     61    if (val instanceof Function) val = val.bind(self)
     62    else if (prop == "handleDOMEvents") val = bindProps(val, self, {})
     63    target[prop] = val
     64  }
     65  return target
     66 }
     67 
     68 /// Plugins bundle functionality that can be added to an editor.
     69 /// They are part of the [editor state](#state.EditorState) and
     70 /// may influence that state and the view that contains it.
     71 export class Plugin<PluginState = any> {
     72  /// Create a plugin.
     73  constructor(
     74    /// The plugin's [spec object](#state.PluginSpec).
     75    readonly spec: PluginSpec<PluginState>
     76  ) {
     77    if (spec.props) bindProps(spec.props, this, this.props)
     78    this.key = spec.key ? spec.key.key : createKey("plugin")
     79  }
     80 
     81  /// The [props](#view.EditorProps) exported by this plugin.
     82  readonly props: EditorProps<Plugin<PluginState>> = {}
     83 
     84  /// @internal
     85  key: string
     86 
     87  /// Extract the plugin's state field from an editor state.
     88  getState(state: EditorState): PluginState | undefined { return (state as any)[this.key] }
     89 }
     90 
     91 /// A plugin spec may provide a state field (under its
     92 /// [`state`](#state.PluginSpec.state) property) of this type, which
     93 /// describes the state it wants to keep. Functions provided here are
     94 /// always called with the plugin instance as their `this` binding.
     95 export interface StateField<T> {
     96  /// Initialize the value of the field. `config` will be the object
     97  /// passed to [`EditorState.create`](#state.EditorState^create). Note
     98  /// that `instance` is a half-initialized state instance, and will
     99  /// not have values for plugin fields initialized after this one.
    100  init: (config: EditorStateConfig, instance: EditorState) => T
    101 
    102  /// Apply the given transaction to this state field, producing a new
    103  /// field value. Note that the `newState` argument is again a partially
    104  /// constructed state does not yet contain the state from plugins
    105  /// coming after this one.
    106  apply: (tr: Transaction, value: T, oldState: EditorState, newState: EditorState) => T
    107 
    108  /// Convert this field to JSON. Optional, can be left off to disable
    109  /// JSON serialization for the field.
    110  toJSON?: (value: T) => any
    111 
    112  /// Deserialize the JSON representation of this field. Note that the
    113  /// `state` argument is again a half-initialized state.
    114  fromJSON?: (config: EditorStateConfig, value: any, state: EditorState) => T
    115 }
    116 
    117 const keys = Object.create(null)
    118 
    119 function createKey(name: string) {
    120  if (name in keys) return name + "$" + ++keys[name]
    121  keys[name] = 0
    122  return name + "$"
    123 }
    124 
    125 /// A key is used to [tag](#state.PluginSpec.key) plugins in a way
    126 /// that makes it possible to find them, given an editor state.
    127 /// Assigning a key does mean only one plugin of that type can be
    128 /// active in a state.
    129 export class PluginKey<PluginState = any> {
    130  /// @internal
    131  key: string
    132 
    133  /// Create a plugin key.
    134  constructor(name = "key") { this.key = createKey(name) }
    135 
    136  /// Get the active plugin with this key, if any, from an editor
    137  /// state.
    138  get(state: EditorState): Plugin<PluginState> | undefined { return state.config.pluginsByKey[this.key] }
    139 
    140  /// Get the plugin's state from an editor state.
    141  getState(state: EditorState): PluginState | undefined { return (state as any)[this.key] }
    142 }