How to check if a selection contains a mark decoration?

Hi all,

Based on the official decoration example, I want to toggle the underline by shortcuts.

According to the topic “How to remove a decoration mark from state”, I have add the removeUnderline StateEffect and the filter on the update method of underlineField.

But what confuse me is I don’t know how to toggle the addUnderline and the removeUnderline effect in the underline Command.

export function toggleUnderline(view: EditorView) {
  const { state, dispatch } = view;
  let effects: StateEffect<unknown>[] = state.selection.ranges
    .filter((r) => !r.empty)
    .map(({ from, to }) => {
      // ???
      // when return the removeUnderline effect
      return addUnderline.of({ from, to })
    });

  if (!effects.length) return false;
  if (!state.field(underlineField, false)) {
    effects.push(StateEffect.appendConfig.of([underlineField, underlineTheme]));
  }
  dispatch({ effects });
  return true;
}

Is there anyway to check if a selection contains a mark decoration?

Any hint will be grateful.:pleading_face:

You can iterate over decorations in a given part of the document with the between method on the decoration set.

1 Like

Thank you. That’s really helpful.

I finished it with some improvement on boundary check. :partying_face:

// the defined effect
const Effect = {
  add: StateEffect.define<{ from: number; to: number }>(),
  remove: StateEffect.define<{ from: number; to: number }>(),
};

// the filter part
if (e.is(Effect.remove)) {
  underlines = underlines.update({
    filter: (from, to, value) => {
      let shouldRemove =
        from === e.value.from &&
        to === e.value.to &&
        value.spec.class === 'cm-underline';
      return !shouldRemove;
    },
  });
}

// the command
export function toggleUnderline(view: EditorView) {
  const { state, dispatch } = view;
  const decoSet = state.field(stateField, false),
    effects: StateEffect<unknown>[] = [];

  if (!decoSet) effects.push(StateEffect.appendConfig.of([stateField, theme]));

  state.selection.ranges
    .filter((r) => !r.empty)
    .forEach(({ from, to }) => {
      // whatever, add the Effect.add effect first
      effects.push(Effect.add.of({ from, to }));
      // iterate over decorations
      decoSet?.between(from, to, (decoFrom, decoTo) => {
        // for side decorations, do nothing
        if (from === decoTo || to === decoFrom) return;
        // for partly or fully contained decorations, do the actions below
        effects.push(Effect.remove.of({ from, to }));
        effects.push(Effect.remove.of({ from: decoFrom, to: decoTo }));
        if (decoFrom < from) effects.push(Effect.add.of({ from: decoFrom, to: from }));
        if (decoTo > to) effects.push(Effect.add.of({ from: to, to: decoTo }));
      });
    });

  if (!effects.length) return false;
  dispatch({ effects });
  return true;
}
2 Likes

Would love a “dispose” or “revert” method to create a reverse transaction instead of hand-writing one.