Skip to content
wiki.fftac.org

Manuscript Workstation - Source Excerpt 11

Back to Manuscript Workstation

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js

const visibleCount = this.applyThumbFilter(query);
      const totalLabel = `${total} result${total === 1 ? '' : 's'}`;
      const thumbLabel = `${visibleCount} thumbnail${visibleCount === 1 ? '' : 's'}`;
      this.setSearchStatus(`${totalLabel} in the manuscript index. ${thumbLabel} matched locally in the folio rail.`);
    }

    performSearch(query = '') {
      const normalizedQuery = `${query || ''}`.trim();
      if (!normalizedQuery || !config.searchUrl || typeof window.fetch !== 'function') {
        this.clearSearchResults();
        return;
      }

      if (this.searchAbortController) {
        this.searchAbortController.abort();
      }

      const requestUrl = createUrlOr(config.searchUrl, null, { context: 'workstation-search-url' });
      if (!requestUrl) {
        this.clearSearchResults();
        this.setSearchStatus('Manuscript search is temporarily unavailable.');
        return;
      }

      requestUrl.searchParams.set('q', normalizedQuery);
      requestUrl.searchParams.set('limit', '8');

      this.searchAbortController = new AbortController();

      runWorkstationAction(
        async () => {
          const response = await window.fetch(requestUrl.toString(), {
          credentials: 'same-origin',
          headers: config.restNonce ? { 'X-WP-Nonce': `${config.restNonce}` } : {},
          signal: this.searchAbortController.signal,
          });

          if (!response.ok) {
            throw new Error(`Search request failed with ${response.status}`);
          }

          const payload = await response.json();

          if (this.lastSearchQuery !== normalizedQuery) {
            return;
          }

          this.renderSearchResults(normalizedQuery, payload);

          if (!Array.isArray(payload.results) || !payload.results.length) {
            const visibleCount = this.applyThumbFilter(normalizedQuery);
            if (visibleCount > 0) {
              this.setSearchStatus(`No deep manuscript hits yet. ${visibleCount} thumbnail${visibleCount === 1 ? '' : 's'} still matched locally.`);
            } else {
              this.setSearchStatus(`No manuscript matches for "${normalizedQuery}".`);
            }
          }
        },
        {
          context: 'workstation-search',
          shouldIgnoreError: (error) => error && error.name === 'AbortError',
          onError: () => {
            this.clearSearchResults();
            this.setSearchStatus('Manuscript search is temporarily unavailable.');
          },
        }
      );
    }

    bindSearch() {
      if (!this.searchInput) {
        return;
      }

      const runSearch = () => {
        const query = `${this.searchInput.value || ''}`.trim();
        this.lastSearchQuery = query;
        this.requestViewerStateSync();

        if (this.searchClear) {
          this.searchClear.hidden = query === '';
        }

        window.clearTimeout(this.searchTimer);

        if (!query) {
          if (this.searchAbortController) {
            this.searchAbortController.abort();
          }

          this.applyThumbFilter('');
          this.clearSearchResults();
          this.setSearchStatus(this.getSearchIdleCopy());
          return;
        }

        const visibleCount = this.applyThumbFilter(query);
        if (query.length < 2) {
          this.clearSearchResults();
          this.setSearchStatus(
            `${visibleCount} thumbnail${visibleCount === 1 ? '' : 's'} matched locally. Keep typing to search summaries, keywords, and transcript text across the codex.`
          );
          return;
        }

        this.setSearchStatus('Searching the manuscript index...');
        this.searchTimer = window.setTimeout(() => {
          this.performSearch(query);
        }, 180);
      };

      this.runSearch = runSearch;
      this.searchInput.addEventListener('input', runSearch);
      this.searchInput.addEventListener('search', runSearch);

      this.searchInput.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter' || !this.searchResults || this.searchResults.hidden) {
          return;
        }

        const firstResult = this.searchResults.querySelector('[data-workstation-search-link]');
        if (!(firstResult instanceof HTMLElement)) {
          return;
        }

        const href = `${firstResult.getAttribute('href') || ''}`.trim();
        if (!href) {
          return;
        }

        event.preventDefault();
        const targetSequence = Number.parseInt(`${firstResult.dataset.pageSequence || ''}`.trim(), 10) || 0;
        const direction = targetSequence && this.currentSequence && targetSequence < this.currentSequence ? 'prev' : 'next';
        this.closeSearchModal();
        this.navigateToPage(href, direction);
      });

      if (this.searchClear) {
        this.searchClear.addEventListener('click', () => {
          this.searchInput.value = '';
          runSearch();
          this.searchInput.focus();
        });
      }

      if (this.searchResults) {
        this.searchResults.addEventListener('click', (event) => {
          const target = event.target;
          if (!(target instanceof HTMLElement)) {
            return;
          }

          const link = target.closest('[data-workstation-search-link]');
          if (!(link instanceof HTMLElement)) {
            return;
          }

          if (
            event.defaultPrevented ||
            event.button !== 0 ||
            event.metaKey ||
            event.ctrlKey ||
            event.shiftKey ||
            event.altKey
          ) {
            return;
          }

          const href = `${link.getAttribute('href') || ''}`.trim();
          if (!href) {
            return;
          }

          event.preventDefault();
          const targetSequence = Number.parseInt(`${link.dataset.pageSequence || ''}`.trim(), 10) || 0;
          const direction = targetSequence && this.currentSequence && targetSequence < this.currentSequence ? 'prev' : 'next';
          this.closeSearchModal();
          this.navigateToPage(href, direction);
        });
      }

      const seededQuery = `${this.initialViewerState.searchQuery || ''}`.trim();
      if (seededQuery) {
        this.searchInput.value = seededQuery;
        runSearch();
      } else if (this.searchClear) {
        this.searchClear.hidden = true;
      }
    }

    bindBeforeUnload() {
      window.addEventListener('beforeunload', (event) => {
        if (!this.canEdit || !this.dirtyFolios.size) {
          return;
        }

        event.preventDefault();
        event.returnValue = '';
      });
    }

    bindViewport() {
      this.viewport.addEventListener('dragstart', (event) => {
        event.preventDefault();
      });

      this.viewport.addEventListener(
        'wheel',
        (event) => {
          event.preventDefault();
          const nextScale = this.state.scale + (event.deltaY < 0 ? 0.18 : -0.18);
          this.zoomTo(nextScale, event.clientX, event.clientY);
        },
        { passive: false }
      );

      this.viewport.addEventListener('dblclick', (event) => {
        if (this.state.scale <= this.getFitScale(this.state.fitMode) + 0.01) {
          this.zoomTo(Math.max(2, this.state.scale + 1), event.clientX, event.clientY);
        } else {
          this.fitTo(this.getResetFitMode());
        }
      });

      this.viewport.addEventListener('pointerdown', (event) => {
        if (event.button !== 0) {
          return;
        }

        const target = event.target;
        const edgeLink =
          target instanceof HTMLElement ? target.closest('[data-workstation-prev], [data-workstation-next]') : null;
        if (
          !(target instanceof HTMLElement) ||
          target.closest('[data-workstation-crop-handle]') ||
          (!edgeLink && target.closest('a, button'))
        ) {
          return;
        }

        event.preventDefault();
        const lockedTurnDirection = edgeLink ? this.getLinkDirection(edgeLink) : 'none';
        this.suppressViewportClick = false;
        this.viewportDrag = {
          pointerId: event.pointerId,
          startX: event.clientX,
          startY: event.clientY,
          startPanX: this.state.panX,
          startPanY: this.state.panY,
          activated: false,
          mode: lockedTurnDirection !== 'none' ? 'turn' : 'pending',
          turnDistance: 0,
          turnDirection: lockedTurnDirection !== 'none' ? lockedTurnDirection : 'none',
          lockedTurnDirection,
        };
        this.viewport.classList.add('is-dragging');
        this.resetDragTurnOffset();
        if (lockedTurnDirection !== 'none') {
          this.setEdgeHint(lockedTurnDirection);
        }

        setPointerCapture(this.viewport, event.pointerId);
      });