VOOZH about

URL: https://deepwiki.com/calevans/staticforge/8.4-interactive-features

⇱ Interactive Features | calevans/staticforge | DeepWiki


Loading...
Last indexed: 11 February 2026 (5f6a2a)
Menu

Interactive Features

Purpose and Scope

This document covers StaticForge's interactive features that enable user engagement on static sites: Forms and Search. These features allow content authors to embed contact forms, feedback forms, and site-wide search functionality using shortcode syntax in content files.

The Forms feature processes shortcodes like {{ form('contact') }} during the RENDER event, replacing them with fully-functional HTML forms that submit to external providers with optional spam protection. The Search feature builds a JSON index during the POST_RENDER event and generates client-side search interfaces.

For navigation and structural features (menus, categories, TOC), see Navigation & Structure Features. For content rendering, see Content Renderers.


Forms Feature Architecture

The Forms feature enables embedding of configurable HTML forms in content pages using Twig function syntax. Forms are defined in siteconfig.yaml with field specifications, validation rules, and submission endpoints.

System Component Diagram


Sources: src/Features/Forms/Feature.php1-59 siteconfig.yaml30-59

Event Lifecycle Integration

The Forms feature registers a single event listener on the RENDER event at priority 50, executing during content transformation before template application.


Sources: src/Features/Forms/Feature.php22-24 src/Features/Forms/Feature.php55-58

Configuration Schema

Forms are defined in the forms: section of siteconfig.yaml. Each form has a unique identifier used in shortcode references.

Configuration KeyRequiredTypePurpose
templateNostringCustom Twig template (defaults to _form.html.twig)
provider_urlYesstringBase URL for form submission endpoint
form_idYesstringUnique identifier appended to provider URL
challenge_urlNostringAltcha spam protection challenge endpoint
submit_textYesstringButton text
success_messageYesstringMessage displayed on successful submission
error_messageYesstringMessage displayed on submission error
fieldsYesarrayField definitions (see below)

Field Definition Schema

Each field in the fields: array requires:

Field PropertyRequiredTypeValid Values
nameYesstringField identifier (HTML name attribute)
labelYesstringHuman-readable label
typeYesstringtext, email, textarea, tel, url, number
requiredNobooleanValidation constraint
placeholderNostringField placeholder text
maxlengthNointegerMaximum character count
rowsNointegerTextarea height (type=textarea only)

Example Configuration


Sources: siteconfig.yaml30-59 siteconfig.yaml.example67-106

Usage in Content

Forms are embedded in content files using Twig function syntax:


The form() function accepts the form identifier defined in siteconfig.yaml. During the RENDER event, FormsService replaces this shortcode with the complete HTML form including:

  • Form fields with proper HTML5 validation attributes
  • AJAX submission handling (no page reload)
  • Optional Altcha spam protection challenge
  • Success/error message display logic

Sources: src/Features/Forms/Feature.php55-58

Implementation Details

The Forms feature implements ConfigurableFeatureInterface, declaring its required configuration:


This enables the audit:config command to validate that siteconfig.yaml contains a forms: section before the build executes.

Registration Flow

src/Features/Forms/Feature.php36-45

  1. Feature registers with EventManager during bootstrap
  2. Retrieves logger and Twig engine from Container
  3. Instantiates FormsService with dependencies
  4. Logs registration confirmation

Render Event Handler

src/Features/Forms/Feature.php55-58

The handleRender() method delegates all processing to FormsService::processForms(), which:

  1. Scans rendered_content for {{ form('identifier') }} patterns
  2. Loads form configuration from site_config['forms'][identifier]
  3. Renders form template with configuration data
  4. Replaces shortcode with rendered HTML
  5. Returns updated parameters

Sources: src/Features/Forms/Feature.php1-59


Search Feature Architecture

The Search feature builds a JSON search index during site generation and provides client-side search functionality. Pages are parsed into sections based on headers, enabling search results to link directly to relevant content sections.

System Component Diagram


Sources: src/Features/Search/Services/SearchIndexService.php1-239 siteconfig.yaml77-84

Event Lifecycle Integration

The Search feature uses a two-phase indexing strategy:

Phase 1: Collection (POST_RENDER)

During each file's POST_RENDER event, SearchIndexService::collectPage() extracts searchable content and stores it in memory.

Phase 2: Persistence (POST_LOOP)

After all files are processed, SearchIndexService::buildIndex() writes the accumulated documents to public/search.json.


Sources: src/Features/Search/Services/SearchIndexService.php27-78 src/Features/Search/Services/SearchIndexService.php166-189

Configuration Schema

Search is configured in the search: section of siteconfig.yaml:

Configuration KeyTypeDefaultPurpose
enabledbooleanfalseMaster toggle for search feature
enginestringminisearchSearch library: minisearch or fuse
separatorstring|Separator in breadcrumb navigation
exclude_pathsarray[]URL paths to exclude (e.g., /tags/, /categories/)
exclude_content_inarray[]Content directories to exclude entirely

Search Engine Selection

  • minisearch: Faster exact matches and prefix search, smaller bundle size
  • fuse: Better fuzzy matching with typo tolerance, larger bundle

Example Configuration


Sources: siteconfig.yaml77-84 siteconfig.yaml.example49-66

Search Index Structure

The public/search.json file contains an array of document objects, where each document represents a searchable section:

Document Schema

FieldTypeDescription
idintegerUnique document identifier (auto-incrementing)
titlestringSection title (header text or page title)
textstringSearchable content (max 5000 chars, whitespace normalized)
urlstringFull URL including anchor for direct section linking
tagsstringSpace-separated tag list from frontmatter
categorystringCategory slug from frontmatter

Example Document


Sources: src/Features/Search/Services/SearchIndexService.php65-74

Content Parsing Strategy

SearchIndexService::parseHtmlSections() splits rendered HTML into searchable sections based on header elements (<h1> through <h6>).

Parsing Algorithm

  1. Load HTML into DOMDocument with UTF-8 encoding
  2. Remove <script>, <style>, and <head> elements
  3. Query all header elements and text nodes
  4. Group text content under the most recent header
  5. Extract header id attribute for anchor linking
  6. Create separate document for each section with content

Section Splitting Logic


This approach enables search results to link directly to the relevant section (e.g., #installation) rather than just the page top.

Sources: src/Features/Search/Services/SearchIndexService.php86-157

Exclusion Logic

Pages can be excluded from the search index through multiple mechanisms:

1. Frontmatter Exclusion

Set search_index: false in a file's frontmatter:


2. Path-Based Exclusion

Configure exclude_paths in siteconfig.yaml to exclude URL patterns:


Exclusion uses prefix matching - any URL starting with an excluded path is skipped.

3. Content Directory Exclusion

Use exclude_content_in to exclude entire content subdirectories:


Exclusion Evaluation Flow


Sources: src/Features/Search/Services/SearchIndexService.php191-225

Implementation Details

URL Calculation

The search index stores absolute URLs constructed from SITE_BASE_URL and the output path:

src/Features/Search/Services/SearchIndexService.php227-238

SITE_BASE_URL (https://example.com) + 
output_path (/docs/guide.html) →
URL (https://example.com/docs/guide.html#section-id)

Document ID Generation

The SearchIndexService maintains an internal counter ($idCounter) initialized to 1. Each section added to the index receives a sequential ID, ensuring uniqueness across the entire site.

Text Normalization

Content text undergoes normalization during parsing:

  1. Whitespace collapsed to single spaces
  2. Header text excluded from section body
  3. Truncated to 5000 characters per section
  4. UTF-8 encoded via DOMDocument XML declaration

Tag Processing

Tags from frontmatter are converted to a space-separated string for search indexing:


This allows search engines to match tag terms as regular text.

Sources: src/Features/Search/Services/SearchIndexService.php47-52 src/Features/Search/Services/SearchIndexService.php68

Testing Coverage

The Search feature includes comprehensive unit tests validating core indexing behavior:

Test Cases

TestFilePurpose
testCollectPageAddsDocumenttests/Unit/Features/Search/SearchIndexServiceTest.php:45-71Verifies document creation with title, text, URL, tags
testSkipsPageWithSearchIndexFalsetests/Unit/Features/Search/SearchIndexServiceTest.php:73-95Validates frontmatter exclusion
testSkipsExcludedPathstests/Unit/Features/Search/SearchIndexServiceTest.php:97-117Confirms path-based filtering

Sources: tests/Unit/Features/Search/SearchIndexServiceTest.php1-118


Comparison: Forms vs Search

Both features operate during the rendering pipeline but serve different purposes and use different event integration patterns:

AspectForms FeatureSearch Feature
PurposeEmbed interactive forms in contentBuild searchable content index
Event PhaseRENDER (during content transformation)POST_RENDER + POST_LOOP (data collection)
OutputHTML form markup in pageSeparate search.json file
Configurationforms: in siteconfig.yamlsearch: in siteconfig.yaml
Content IntegrationTwig function {{ form('name') }}Automatic (no shortcode required)
Client-SideAJAX submission + optional AltchaJavaScript search UI with chosen engine
Exclusion ControlN/A (explicit opt-in via shortcode)Frontmatter + path configuration

Sources: src/Features/Forms/Feature.php22-24 src/Features/Search/Services/SearchIndexService.php1-239