viddo/atom-textual-velocity

View on GitHub
lib/epics/previewNoteEpic.js

Summary

Maintainability
A
2 hrs
Test Coverage
/* @flow */

import path from "path";
import { merge } from "rxjs";
import {
  debounceTime,
  distinctUntilChanged,
  filter,
  finalize,
  map
} from "rxjs/operators";
import * as A from "../actions";
import previewEditor from "../previewEditor";
import takeUntilDispose from "../takeUntilDispose";
import { showWarningNotification } from "../showWarningNotification";

import type { Action } from "../actions";
import type { State } from "../../flow-types/State";
import type { PreviewEditor } from "../../flow-types/PreviewEditor";

const HAS_FILE_EXT_REGEX = /\.\w{1,5}$/;

export default function previewNoteEpic(
  action$: rxjs$Observable<Action>,
  state$: reduxRxjs$StateObservable<State>
) {
  let preview: PreviewEditor | null = null;
  const destroyPreview = () => {
    if (preview) {
      preview.destroy();
      preview = null;
    }
  };

  return merge(
    // Open/close preview based on selectedNote state
    action$.pipe(
      debounceTime(25),
      map(() => state$.value),
      distinctUntilChanged((a: State, b: State) => {
        return (
          (a.selectedNote && a.selectedNote.filename) ===
          (b.selectedNote && b.selectedNote.filename)
        );
      }),
      filter(() => {
        const state = state$.value;
        if (!state.selectedNote) {
          destroyPreview();
          return false;
        }

        const { filename } = state.selectedNote;
        const notePath = path.join(state.dir, filename);

        // if there already exists a text-editor, use that instead of preview
        if (existsOpenTextEditorForPath(notePath)) {
          destroyPreview();
          atom.workspace.open(notePath, {
            activatePane: false,
            searchAllPanes: true
          });
        } else {
          // no text editor for selected note, show preview
          const note = state.notes[filename];
          if (note) {
            const searchRegexps = state.sifterResult.tokens.map(t => t.regex);
            if (!preview) {
              preview = previewEditor();
            }
            preview
              .openPreview(notePath, note.content, searchRegexps)
              .catch(error => {
                showWarningNotification("Failed to preview note", error);
              });
          }
        }

        return false;
      })
    ),

    // Open on specific open-note action
    action$.pipe(
      filter(action => {
        if (action.type === A.OPEN_NOTE) {
          const state = state$.value;
          let filename;

          if (state.selectedNote) {
            filename = state.selectedNote.filename;
          } else {
            filename = state.queryOriginal.trim() || "untitled";

            if (!HAS_FILE_EXT_REGEX.test(filename)) {
              const ext = atom.config
                .get("textual-velocity.defaultExt")
                .replace(/^\./, ""); // avoid double dots next to extension, i.e. untitled..txt => untitled.txt
              filename = `${filename}.${ext}`;
            }
          }

          atom.workspace
            .open(path.join(state.dir, filename))
            .then(destroyPreview);
        }

        return false;
      })
    )
  ).pipe(takeUntilDispose(action$), finalize(destroyPreview));
}

function existsOpenTextEditorForPath(notePath: string) {
  // until https://github.com/eslint/eslint/issues/12117 is resolved:
  // eslint-disable-next-line no-unused-vars
  for (let textEditor of atom.workspace.getTextEditors()) {
    if (textEditor.getPath() === notePath) {
      return true;
    }
  }

  return false;
}