question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

.isActive() and nested nodes

See original GitHub issue

Currently .isActive checks to see if the node is active at any point in a nested tree of nodes, this is slightly problematic with nested list of different types. So ,for example, I have a toolbar with both ul and ol buttons and use isActive to set the active state of the button, if you nest a ul within an ol both are technically active and so both buttons show as active.

Would it be possible to have a depth argument or ‘isActiveTop()’ so that we can check if only is the top level node matches?

Additionally isActive('paragraph') returns true with most other node types as they almost all nest a paragraph within them (list, tables, blockquotes ect.), could there be a exclusive argument or a isActiveOnly() option?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
samwilliscommented, Feb 12, 2021

Having put some more thought into this I have made a new helper that solves this problem, a depth arg would not solve it as its hard to know how ‘deep’ to test and its ambiguous how that would work on a multi node selection.

This helper, lastActiveNodes, finds the last (furthest from the root document) matching types in your selection (ignoring nesting), you can give it either a list of args similar to isActive or a group name, calling it like below. It will return a list of node type names that are the last ones active in the selection, if the selection is ‘empty’ (just the cursor) the returned list will only have at most one name. This lets you build a UI for lists that works the same as most word processors.

lastActiveNodes(editor.state, [{type: 'bulletList'}, {type: 'orderedList'}])
lastActiveNodes(editor.state, 'list')
// And types with attributes - not that you would nest headings but you get the idea
lastActiveNodes(editor.state, [
  {type: 'heading', attributes: { level: 1 }, key: 'h1'},
  {type: 'heading', attributes: { level: 2 }, key: 'h2'},
  {type: 'heading', attributes: { level: 3 }, key: 'h3'},
]))

If you are checking for arguments of the same node type (for example for headings) you can provide an additional key for each type that will be returned if it matches.

In action:

https://user-images.githubusercontent.com/31130/107784283-28391c80-6d43-11eb-96f6-d9bd9a77198b.mov

I think the inverse, a firstActiveNodes, will solve the isActive('paragraph') use case and so that’s on my todo list…

The code:

import { EditorState } from 'prosemirror-state'
import { NodeType, Node } from 'prosemirror-model'
import objectIncludes from '@tiptap/core/src/utilities/objectIncludes'
import getNodeType from '@tiptap/core/src/helpers/getNodeType'
import { AnyObject } from '@tiptap/core/src/types'


export interface LastActiveNodesItemOption {
  type: NodeType | string | null;
  attributes?: AnyObject;
  key?: string;
}

export default function lastActiveNodes(
  state: EditorState,
  typesOrGroup: LastActiveNodesItemOption[] | string
): string[] {
  const { from, to, empty } = state.selection
  let types: LastActiveNodesItemOption[]

  if (typeof typesOrGroup === 'string') {
    // types is a name of a node group
    types = Object.entries(state.schema.nodes)
      .filter(([name, nodeType]) => (nodeType as any).groups.includes(typesOrGroup))
      .map(([name, nodeType]) => {
        return {
          type: (nodeType as NodeType),
        }
      })
  } else {
    // types is a list of LastActiveNodeItemOption
    types = typesOrGroup;
    for (const item of types) {
      item.type = item.type ? getNodeType(item.type, state.schema) : null
    }
  }

  let lastNode: Node | null = null;
  let lastMatchedType: LastActiveNodesItemOption | null = null;
  const matchedTypes: Set<LastActiveNodesItemOption> = new Set();
  const notFoundTypes = new Set(types)

  state.doc.nodesBetween(from, to, (node, pos, parent) => {
    if (notFoundTypes.size == 0) return false;
    if (!node.isText) {
      const matchedType = types.filter((item) => {
        if (!item.type) {
          return true
        }
        if (typeof item.type === 'string') return false; // Typeguard, shouldn't happen
        return node.type.name === item.type.name
      })
      .find(item => {
        if (!item.attributes) return true;
        return objectIncludes(node.attrs, item.attributes)
      })
      if (matchedType) {
        if (lastMatchedType && lastNode && (lastNode !== parent)) {
          notFoundTypes.delete(lastMatchedType);
          matchedTypes.add(lastMatchedType);
        }
        lastMatchedType = matchedType;
      }
      lastNode = node;
    }
  })

  if (lastMatchedType) {
    matchedTypes.add(lastMatchedType);
  }

  return [...matchedTypes.values()].map((item) => {
    if (item.key) {
      return item.key
    } else if (typeof item.type === 'string') {
      return item.type;
    } else if (item.type?.name) {
      return item.type.name
    } else {
      return ''
    }
  })

}

0reactions
philippkuehncommented, Dec 16, 2021

Currently, I don’t have plans to add this to the core. But thanks again for the code. I’m sure it will help some people 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

Running hql query for nested element returns "unexpected ...
I'm using grails 2.3.4; I have no problem accessing the information p.owner.contract.isActive in my grails code; A product always has an owner ...
Read more >
Node | Sceneform (1.15.0) - Google Developers
Nested Classes; Public Constructors; Public Methods; Protected Methods ... Returns true if the node is active. ... public final boolean isActive ().
Read more >
NavLink - React Router: Declarative Routing for React.js
A function to add extra logic for determining whether the link is active. This should be used if you want to do more...
Read more >
Class - No Magic Documentation
Returns the value of the 'Nested Classifier' containment reference list. java.util. ... boolean, hasNestedClassifier() ... boolean, isActive().
Read more >
Migrating to React Router v6: A complete guide
For nested routes, developers had to find the entire route because there ... In React Router v5, we use useHistory() for handling navigation ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found