Skip to content

uttori/uttori-wiki

Repository files navigation

view on npm npm module downloads Coverage Status

Uttori Wiki

UttoriWiki is a fast, simple, blog / wiki / knowledge base / generic website built around Express.js using the Uttori set of components allowing specific chunks of functionality be changed or swapped out to fit specific needs.

Why yet another knowledge management / note taking app? I wanted to have something that functioned as a wiki or blog or similar small app that I could reuse components for and keep extensible without having to rewrite everything or learn a new framework.

Because of that, UttoriWiki is plugin based. Search and Storage engines are fully configurable. The format of the data is also up to you: Markdown, Wikitext, Creole, AsciiDoc, Textile, reStructuredText, BBCode, Pendown, etc. Markdown is the default and best supported.

Nothing is prescribed. Don't want to write in Markdown? You don't need to! Don't want to store files on disk? Choose a database storage engine! Already running a bunch of external dependencies and want to plug into those? You can most likely do it!

Rendering happens in a pipeline making it easy to render to Markdown, then filter content out and manipulate the content like removing tags or replacing text with emojis.

Configuration

Please see src/config.js or the config doc for all options. Below is an example configuration using some plugins:

import { Plugin: StorageProvider } from '@uttori/storage-provider-json-file';
import { Plugin: SearchProvider } from '@uttori/search-provider-lunr';

import AnalyticsPlugin from '@uttori/plugin-analytics-json-file';
import MarkdownItRenderer from '@uttori/plugin-renderer-markdown-it';
import ReplacerRenderer from '@uttori/plugin-renderer-replacer';
import MulterUpload from '@uttori/plugin-upload-multer';
import SitemapGenerator from '@uttori/plugin-generator-sitemap';
import { AddQueryOutputToViewModel } from '@uttori/wiki';

const config = {
  homePage: 'home-page',
  ignoreSlugs: ['home-page'],
  excerptLength: 400,
  publicUrl: 'http://127.0.0.1:8000/wiki',
  themePath: path.join(__dirname, 'theme'),
  publicPath: path.join(__dirname, 'public'),
  useDeleteKey: false,
  deleteKey: process.env.DELETE_KEY || '',
  useEditKey: false,
  editKey: process.env.EDIT_KEY || '',
  publicHistory: true,
  allowedDocumentKeys: [],

  // Plugins
  plugins: [
    StorageProvider,
    SearchProvider,
    AnalyticsPlugin,
    MarkdownItRenderer,
    ReplacerRenderer,
    MulterUpload,
    SitemapGenerator,
  ],

  // Use the JSON to Disk Storage Provider
  [StorageProvider.configKey]: {
    // Path in which to store content (markdown files, etc.)
    contentDirectory: `${__dirname}/content`,

    // Path in which to store content history (markdown files, etc.)
    historyDirectory: `${__dirname}/content/history`,

    // File Extension
    extension: 'json',
  },

  // Use the Lunr Search Provider
  [SearchProvider.configKey]: {
    // Optional Lunr locale
    lunr_locales: [],

    // Ignore Slugs
    ignoreSlugs: ['home-page'],
  },

  // Plugin: Analytics with JSON Files
  [AnalyticsPlugin.configKey]: {
    events: {
      getPopularDocuments: ['popular-documents'],
      updateDocument: ['document-save', 'document-delete'],
      validateConfig: ['validate-config'],
    },

    // Directory files will be uploaded to.
    directory: `${__dirname}/data`,

    // Name of the JSON file.
    name: 'visits',

    // File extension to use for the JSON file.
    extension: 'json',
  },

  // Plugin: Markdown rendering with MarkdownIt
  [MarkdownItRenderer.configKey]: {
    events: {
      renderContent: ['render-content'],
      renderCollection: ['render-search-results'],
      validateConfig: ['validate-config'],
    },


    // Uttori Specific Configuration
    uttori: {
      // Prefix for relative URLs, useful when the Express app is not at root.
      baseUrl: '',

      // Safe List, if a domain is not in this list, it is set to 'external nofollow noreferrer'.
      allowedExternalDomains: [
        'my-site.org',
      ],

      // Open external domains in a new window.
      openNewWindow: true,

      // Table of Contents
      toc: {
        // The opening DOM tag for the TOC container.
        openingTag: '<nav class="table-of-contents">',

        // The closing DOM tag for the TOC container.
        closingTag: '</nav>',

        // Slugify options for convering content to anchor links.
        slugify: {
          lower: true,
        },
      },
    },
  },

  // Plugin: Replace text
  [ReplacerRenderer.configKey]: {
    events: {
      renderContent: ['render-content'],
      renderCollection: ['render-search-results'],
      validateConfig: ['validate-config'],
    },

    // Rules for text replace
    rules: [
      {
        test: /bunny|rabbit/gm,
        output: '🐰',
      },
    ],
  },

  // Plugin: Multer Upload
  [MulterUpload.configKey]: {
    events: {
      bindRoutes: ['bind-routes'],
      validateConfig: ['validate-config'],
    },

    // Directory files will be uploaded to
    directory: `${__dirname}/uploads`,

    // URL to POST files to
    route: '/upload',

    // URL to GET uploads from
    publicRoute: '/uploads',
  },

  // Plugin: Sitemap Generator
  [SitemapGenerator.configKey]: {
    events: {
      callback: ['document-save', 'document-delete'],
      validateConfig: ['validate-config'],
    },

    // Sitemap URL (ie https://wiki.domain.tld)
    base_url: 'https://wiki.domain.tld',

    // Location where the XML sitemap will be written to.
    directory: `${__dirname}/themes/default/public`,

    urls: [
      {
        url: '/',
        lastmod: new Date().toISOString(),
        priority: '1.00',
      },
      {
        url: '/tags',
        lastmod: new Date().toISOString(),
        priority: '0.90',
      },
      {
        url: '/new',
        lastmod: new Date().toISOString(),
        priority: '0.70',
      },
    ],
  },

  // Plugin: View Model Related Documents
  [AddQueryOutputToViewModel.configKey]: {
    events: {
      callback: [
        'view-model-home',
        'view-model-edit',
        'view-model-new',
        'view-model-search',
        'view-model-tag',
        'view-model-tag-index',
        'view-model-detail',
      ],
    },
    queries: {
      'view-model-home' : [
        {
          key: 'tags',
          query: `SELECT tags FROM documents WHERE slug NOT_IN ("${ignoreSlugs.join('", "')}") ORDER BY id ASC LIMIT -1`,
          format: (tags) => [...new Set(tags.flatMap((t) => t.tags))].filter(Boolean).sort((a, b) => a.localeCompare(b)),
          fallback: [],
        },
        {
          key: 'documents',
          query: `SELECT * FROM documents WHERE slug NOT_IN ("${ignoreSlugs.join('", "')}") ORDER BY id ASC LIMIT -1`,
          fallback: [],
        },
        {
          key: 'popularDocuments',
          fallback: [],
          format: (results) => results.map((result) => result.slug),
          queryFunction: async (target, context) => {
            const ignoreSlugs = ['home-page'];
            const [popular] = await context.hooks.fetch('popular-documents', { limit: 5 }, context);
            const slugs = `"${popular.map(({ slug }) => slug).join('", "')}"`;
            const query = `SELECT 'slug', 'title' FROM documents WHERE slug NOT_IN (${ignoreSlugs}) AND slug IN (${slugs}) ORDER BY updateDate DESC LIMIT 5`;
            const [results] = await context.hooks.fetch('storage-query', query);
            return [results];
          },
        }
      ],
    },
  },

  // Middleware Configuration in the form of ['function', 'param1', 'param2', ...]
  middleware: [
    ['disable', 'x-powered-by'],
    ['enable', 'view cache'],
    ['set', 'views', path.join(`${__dirname}/themes/`, 'default', 'templates')],

    // EJS Specific Setup
    ['use', layouts],
    ['set', 'layout extractScripts', true],
    ['set', 'layout extractStyles', true],
    // If you use the `.ejs` extension use the below:
    // ['set', 'view engine', 'ejs'],
    // I prefer using `.html` templates:
    ['set', 'view engine', 'html'],
    ['engine', 'html', ejs.renderFile],
  ],

  redirects: [
    {
      route: '/:year/:slug',
      target: '/:slug',
      status: 301,
      appendQueryString: true,
    },
  ],

  // Override route handlers
  homeRoute: (request, response, next) => { ... },
  tagIndexRoute: (request, response, next) => { ... },
  tagRoute: (request, response, next) => { ... },
  searchRoute: (request, response, next) => { ... },
  editRoute: (request, response, next) => { ... },
  deleteRoute: (request, response, next) => { ... },
  saveRoute: (request, response, next) => { ... },
  saveNewRoute: (request, response, next) => { ... },
  newRoute: (request, response, next) => { ... },
  detailRoute: (request, response, next) => { ... },
  previewRoute: (request, response, next) => { ... },
  historyIndexRoute: (request, response, next) => { ... },
  historyDetailRoute: (request, response, next) => { ... },
  historyRestoreRoute: (request, response, next) => { ... },
  notFoundRoute: (request, response, next) => { ... },
  saveValidRoute: (request, response, next) => { ... },

  // Custom per route middleware, in the order they should be used
  routeMiddleware: {
    home: [],
    tagIndex: [],
    tag: [],
    search: [],
    notFound: [],
    create: [],
    saveNew: [],
    preview: [],
    edit: [],
    delete: [],
    historyIndex: [],
    historyDetail: [],
    historyRestore: [],
    save: [],
    detail: [],
  },
};

export default config;

Use in an example Express.js app:

// Server
import express from 'express';

// Reference the Uttori Wiki middleware
import { wiki as middleware } from '@uttori/wiki';

// Pull in our custom config, example above
import config from './config.js';

// Initilize Your app
const app = express();

// Setup the app
app.set('port', process.env.PORT || 8000);
app.set('ip', process.env.IP || '127.0.0.1');

// Setup Express
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));

// Setup the wiki, could also mount under a sub directory path with other applications
app.use('/', middleware(config));

// Listen for connections
app.listen(app.get('port'), app.get('ip'), () => {
  console.log('✔ listening at %s:%d', app.get('ip'), app.get('port'));
});

Events

The following events are avaliable to hook into through plugins and are used in the methods below:

Name Type Returns Description
bind-routes dispatch Called after the default routes are bound to the server.
document-delete dispatch Called when a document is about to be deleted.
document-save filter Uttori Document Called when a document is about to be saved.
render-content filter HTML Content Called when content is being prepared to be shown.
render-search-results filter Array of Uttori Documents Called when search results have been collected and is being prepared to be shown.
validate-config dispatch Called after initial configuration validation.
validate-invalid dispatch Called when a document is found invalid (spam?).
validate-valid dispatch Called when a document is found to be valid.
validate-save validate Boolean Called before saving a document to validate the document.
view-model-detail filter View Model Called when rendering the detail page just before being shown.
view-model-edit filter View Model Called when rendering the edit page just before being shown.
view-model-error-404 filter View Model Called when rendering a 404 Not Found error page just before being shown.
view-model-history-detail filter View Model Called when rendering a history detail page just before being shown.
view-model-history-index filter View Model Called when rendering a history index page just before being shown.
view-model-history-restore filter View Model Called when rendering a history restore page just before being shown.
view-model-home filter View Model Called when rendering the home page just before being shown.
view-model-metadata filter View Model Called after the initial view model metadata is setup.
view-model-new filter View Model Called when rendering the new document page just before being shown.
view-model-search filter View Model Called when rendering a search result page just before being shown.
view-model-tag-index filter View Model Called when rendering the tag index page just before being shown.
view-model-tag filter View Model Called when rendering a tag detail page just before being shown.

Included Plugins

Form Handler Plugin

A flexible form handling plugin for Uttori Wiki that allows you to easily define multiple forms through configuration objects and handle submissions with configurable handlers.

Features

  • Multiple Form Support: Define multiple forms with different configurations
  • Flexible Field Types: Support for text, email, textarea, number, url, and custom validation
  • Configurable Handlers: Default console logging, email sending, Google Sheets integration
  • JSON and Form Data: Accepts both JSON and form-encoded data
  • Validation: Built-in validation with custom regex support
  • Middleware Support: Add custom middleware for authentication, rate limiting, etc.
  • Error Handling: Comprehensive error handling and validation

Configuration

Add the plugin to your Uttori Wiki configuration:

import FormHandler from './src/plugins/form-handler.js';
import EmailHandler from './src/plugins/form-handlers/email-handler.js';
import GoogleDocsHandler from './src/plugins/form-handlers/google-docs-handler.js';

const config = {
  // ... other config
  plugins: [
    FormHandler,
    // ... other plugins
  ],
  [FormHandler.configKe]: {
    baseRoute: '/forms', // Optional: base route for all forms
    forms: [
      {
        name: 'contact',
        route: '/contact',
        fields: [
          {
            name: 'name',
            type: 'text',
            required: true,
            label: 'Full Name',
            placeholder: 'Enter your full name'
          },
          {
            name: 'email',
            type: 'email',
            required: true,
            label: 'Email Address',
            placeholder: 'Enter your email address'
          },
          {
            name: 'message',
            type: 'textarea',
            required: true,
            label: 'Message',
            placeholder: 'Enter your message'
          }
        ],
        handler: EmailHandler.create({
          host: 'smtp.gmail.com',
          port: 587,
          secure: false,
          user: '[email protected]',
          pass: 'your-app-password',
          from: '[email protected]',
          to: '[email protected]',
          subject: 'Contact Form Submission from {name}',
          template: `
            <h2>New Contact Form Submission</h2>
            <p><strong>Name:</strong> {name}</p>
            <p><strong>Email:</strong> {email}</p>
            <p><strong>Message:</strong></p>
            <p>{message}</p>
            <hr>
            <p><em>Submitted at: {timestamp}</em></p>
          `
        }),
        successMessage: 'Thank you for your message! We will get back to you soon.',
        errorMessage: 'There was an error submitting your message. Please try again.'
      }
    ]
  }
};

Form Configuration

Form Object Properties

  • name (string, required): Unique identifier for the form
  • route (string, required): URL route for form submission (relative to baseRoute)
  • fields (array, required): Array of field configurations
  • handler (function, optional): Custom handler function for form processing
  • successMessage (string, required): Success message to return
  • errorMessage (string, required): Error message to return
  • middleware (array, optional): Custom Express middleware for the form route

Field Object Properties

  • name (string, required): Field name (used as form data key)
  • type (string, required): Field type (text, email, textarea, number, url)
  • required (boolean, optional): Whether the field is required
  • label (string, optional): Display label for the field
  • placeholder (string, optional): Placeholder text for the field
  • validation (function, optional): Custom validation function
  • errorMessage (string, optional): Custom error message for validation

Built-in Handlers

Default Handler (Console Logging)

If no custom handler is provided, the form data will be logged to the console:

{
  name: 'feedback',
  route: '/feedback',
  fields: [
    { name: 'rating', type: 'number', required: true },
    { name: 'comment', type: 'textarea', required: false }
  ]
  // No handler - uses default console.log
}

Email Handler

Send form submissions via email using nodemailer:

import EmailHandler from './src/plugins/form-handlers/email-handler.js';

// In your form configuration
handler: EmailHandler.create({
  transportOptions: { ... },
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Contact Form Submission from {name}',
  template: `
    <h2>New Contact Form Submission</h2>
    <p><strong>Name:</strong> {name}</p>
    <p><strong>Email:</strong> {email}</p>
    <p><strong>Message:</strong></p>
    <p>{message}</p>
  `
})
Email Handler Configuration
  • transportOptions.host (string, required): SMTP host
  • transportOptions.port (number, required): SMTP port
  • transportOptions.secure (boolean, optional): Whether to use SSL/TLS
  • transportOptions.auth.user (string, required): SMTP username
  • transportOptions.auth.pass (string, required): SMTP password
  • from (string, required): Email address to send from
  • to (string, required): Email address to send to
  • subject (string, required): Email subject template
  • template (string, optional): Email body HTML template
Email Template Variables
  • {formName}: The form name
  • {timestamp}: Current timestamp
  • {fieldName}: Any form field value (replace fieldName with actual field name)

Google Sheets Handler

Write form submissions to Google Sheets:

import GoogleDocsHandler from './src/plugins/form-handlers/google-docs-handler.js';

// In your form configuration
handler: GoogleDocsHandler.create({
  credentialsPath: './google-credentials.json',
  spreadsheetId: 'your-spreadsheet-id',
  sheetName: 'Form Submissions',
  headers: ['name', 'email', 'message'],
  appendTimestamp: true
})
Google Sheets Handler Configuration
  • credentialsPath (string, required): Path to Google service account credentials JSON file
  • spreadsheetId (string, required): Google Sheets spreadsheet ID
  • sheetName (string, required): Name of the sheet to write to
  • headers (array, optional): Custom headers for the spreadsheet
  • appendTimestamp (boolean, optional): Whether to append timestamp to each row
Setting up Google Sheets
  1. Create a Google Cloud Project and enable the Google Sheets API
  2. Create a service account and download the credentials JSON file
  3. Share your Google Sheet with the service account email
  4. Use GoogleDocsHandler.setupHeaders(config) to initialize the sheet headers

Custom Handlers

You can create custom handlers by providing a function that accepts form data, form config, request, and response:

{
  name: 'custom-form',
  route: '/custom',
  fields: [
    { name: 'data', type: 'text', required: true }
  ],
  handler: async (formData, formConfig, req, res) => {
    // Custom processing logic
    console.log('Custom handler processing:', formData);

    // Save to database, send to API, etc.
    await saveToDatabase(formData);

    return {
      message: 'Data processed successfully',
      id: 'some-id'
    };
  }
}

API Endpoints

Forms are accessible at: {baseRoute}{formRoute}

For example, with baseRoute: '/forms' and form route: '/contact':

  • POST /forms/contact

Request/Response Format

Request

Accepts both JSON and form-encoded data:

// JSON
fetch('/forms/contact', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'John Doe',
    email: '[email protected]',
    message: 'Hello world!'
  })
});

// Form data
const formData = new FormData();
formData.append('name', 'John Doe');
formData.append('email', '[email protected]');
formData.append('message', 'Hello world!');

fetch('/forms/contact', {
  method: 'POST',
  body: formData
});

Response

// Success
{
  "success": true,
  "message": "Thank you for your message! We will get back to you soon.",
  "data": {
    "messageId": "email-message-id",
    "message": "Email sent successfully"
  }
}

// Error
{
  "success": false,
  "message": "There was an error submitting your message. Please try again.",
  "errors": [
    "Field \"email\" must be a valid email address"
  ]
}

Validation

The plugin provides built-in validation for:

  • Required fields: Checks if required fields are present and not empty
  • Email format: Validates email addresses using regex
  • Number format: Validates numeric values
  • URL format: Validates URLs
  • Custom regex: Supports custom validation patterns

Middleware Support

Add custom middleware for authentication, rate limiting, etc.:

{
  name: 'admin-form',
  route: '/admin/feedback',
  fields: [
    { name: 'feedback', type: 'textarea', required: true }
  ],
  middleware: [
    // Authentication middleware
    (req, res, next) => {
      if (!req.session || !req.session.user) {
        return res.status(401).json({ error: 'Authentication required' });
      }
      next();
    },
    // Rate limiting middleware
    rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 5 // limit each IP to 5 requests per windowMs
    })
  ],
  handler: customHandler
}

Error Handling

The plugin handles various error scenarios:

  • Validation errors: Returns 400 with validation details
  • Handler errors: Returns 500 with error message
  • Configuration errors: Throws during plugin registration
  • Missing fields: Validates required fields
  • Invalid data types: Validates field types and formats

Dependencies

  • nodemailer: For email handler (install with npm install nodemailer)
  • googleapis: For Google Sheets handler (install with npm install googleapis)

Security Considerations

  • Validate all input data
  • Use HTTPS in production
  • Implement rate limiting for public forms
  • Sanitize email templates to prevent injection
  • Secure Google credentials file
  • Use environment variables for sensitive configuration

Tag Routes Plugin

This plugin provides tag index and individual tag pages.

Installation

Add the plugin to your wiki configuration:

import TagRoutesPlugin from './plugins/tag-routes.js';

const config = {
  plugins: [
    TagRoutesPlugin,
    // ... other plugins
  ],
  'uttori-plugin-tag-routes': {
    // plugin configuration
  }
};

Configuration

The plugin accepts the following configuration options:

{
  'uttori-plugin-tag-routes': {
    route: 'tags',                    // Route path for tag pages (default: 'tags')
    title: 'Tags',                    // Default title for tag pages (default: 'Tags')
    ignoreTags: [],                   // Tags to ignore when generating the tags page (default: [])
    limit: 1024,                      // Max documents per tag (default: 1024)
    titles: {},                       // Custom titles for specific tags (default: {})
    tagIndexRoute: undefined,         // Custom tag index route handler (default: undefined)
    tagRoute: undefined,              // Custom tag detail route handler (default: undefined)
    routeMiddleware: {                // Middleware for tag routes
      tagIndex: [],
      tag: []
    }
  }
}

Required Hooks

The plugin uses the following hooks to maintain existing functionality:

Core Hooks Used

  1. bind-routes (dispatch)

    • Purpose: Registers tag routes with the Express server
    • Usage: Plugin listens to this hook to add its routes
    • Implementation: context.hooks.on('bind-routes', TagRoutesPlugin.bindRoutes(plugin))
  2. storage-query (fetch)

    • Purpose: Queries the storage system for documents
    • Usage: Used in getTaggedDocuments() to find documents with specific tags
    • Implementation: await this.context.hooks.fetch('storage-query', query, this.context)
  3. view-model-tag-index (filter)

    • Purpose: Allows modification of the tag index view model
    • Usage: Applied to the view model before rendering the tag index page
    • Implementation: await this.context.hooks.filter('view-model-tag-index', viewModel, this.context)
  4. view-model-tag (filter)

    • Purpose: Allows modification of the individual tag view model
    • Usage: Applied to the view model before rendering individual tag pages
    • Implementation: await this.context.hooks.filter('view-model-tag', viewModel, this.context)

Context Methods Used

The plugin relies on the following methods from the wiki context:

  1. buildMetadata(document, path, robots)

    • Purpose: Builds metadata for view models
    • Usage: Creates metadata for tag index and tag detail pages
  2. config.ignoreSlugs

    • Purpose: List of slugs to exclude from tag queries
    • Usage: Used in storage queries to filter out ignored documents

Routes Provided

The plugin registers the following routes:

  1. GET /{route} (default: GET /tags)

    • Handler: tagIndex
    • Purpose: Displays the tag index page with all available tags
    • Template: tags
  2. GET /{route}/:tag (default: GET /tags/:tag)

    • Handler: tag
    • Purpose: Displays all documents with a specific tag
    • Template: tag

Templates Required

The plugin expects the following templates to exist in your theme:

  1. tags - Tag index page template

    • Variables: title, config, session, taggedDocuments, meta, basePath, flash
  2. tag - Individual tag page template

    • Variables: title, config, session, taggedDocuments, meta, basePath, flash

Migration from Core

When migrating from the core tag functionality:

  1. Remove from config.js:

    • ignoreTags property
    • routes.tags property
    • titles.tags property
    • tagIndexRoute and tagRoute properties
    • routeMiddleware.tagIndex and routeMiddleware.tag properties
  2. Remove from wiki.js:

    • tagIndex method
    • tag method
    • getTaggedDocuments method
    • Tag route binding in bindRoutes
  3. Add plugin to configuration:

    • Import TagRoutesPlugin
    • Add to plugins array
    • Configure with 'uttori-plugin-tag-routes' key

Backward Compatibility

The plugin maintains full backward compatibility with existing functionality:

  • All existing hooks continue to work
  • Template variables remain the same
  • Route structure is preserved (configurable)
  • Custom route handlers are supported
  • Middleware support is maintained

Example Usage

import UttoriWiki from './src/wiki.js';
import TagRoutesPlugin from './src/plugins/tag-routes.js';

const config = {
  plugins: [TagRoutesPlugin],
  'uttori-plugin-tag-routes': {
    route: 'categories',              // Use 'categories' instead of 'tags'
    title: 'Categories',              // Custom title
    ignoreTags: ['private', 'draft'], // Ignore these tags
    limit: 50,                        // Limit to 50 documents per tag
    titles: {                         // Custom titles for specific tags
      'javascript': 'JavaScript',
      'nodejs': 'Node.js'
    }
  }
};

const wiki = new UttoriWiki(config, server);

This will create routes at /categories and /categories/:tag with the specified configuration.


API Reference

Classes

UttoriWiki

UttoriWiki is a fast, simple, wiki knowledge base.

Typedefs

UttoriWikiViewModel : object
UttoriWikiDocument : object
UttoriWikiDocumentAttachment : object
UttoriWikiDocumentMetaData : object

UttoriWiki

UttoriWiki is a fast, simple, wiki knowledge base.

Kind: global class
Properties

Name Type Description
config UttoriWikiConfig The configuration object.
hooks module:@uttori/event-dispatcher~EventDispatcher The hook / event dispatching object.

new UttoriWiki(config, server)

Creates an instance of UttoriWiki.

Param Type Description
config UttoriWikiConfig A configuration object.
server module:express~Application The Express server instance.

Example (Init UttoriWiki)

const server = express();
const wiki = new UttoriWiki(config, server);
server.listen(server.get('port'), server.get('ip'), () => { ... });

uttoriWiki.config : UttoriWikiConfig

Kind: instance property of UttoriWiki

uttoriWiki.hooks : module:@uttori/event-dispatcher~EventDispatcher

Kind: instance property of UttoriWiki

uttoriWiki.home

Renders the homepage with the home template.

Hooks:

  • filter - render-content - Passes in the home-page content.
  • filter - view-model-home - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.homepageRedirect : module:express~RequestHandler

Redirects to the homepage.

Kind: instance property of UttoriWiki

uttoriWiki.search

Renders the search page using the search template.

Hooks:

  • filter - render-search-results - Passes in the search results.
  • filter - view-model-search - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request.<{}, {}, {}, {s: string}> The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.edit

Renders the edit page using the edit template.

Hooks:

  • filter - view-model-edit - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.delete

Attempts to delete a document and redirect to the homepage. If the config useDeleteKey value is true, the key is verified before deleting.

Hooks:

  • dispatch - document-delete - Passes in the document beind deleted.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.save

Attempts to update an existing document and redirects to the detail view of that document when successful.

Hooks:

  • validate - validate-save - Passes in the request.
  • dispatch - validate-invalid - Passes in the request.
  • dispatch - validate-valid - Passes in the request.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request.<SaveParams, {}, UttoriWikiDocument> The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.saveNew

Attempts to save a new document and redirects to the detail view of that document when successful.

Hooks:

  • validate - validate-save - Passes in the request.
  • dispatch - validate-invalid - Passes in the request.
  • dispatch - validate-valid - Passes in the request.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request.<SaveParams, {}, UttoriWikiDocument> The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.create

Renders the creation page using the edit template.

Hooks:

  • filter - view-model-new - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.detail

Renders the detail page using the detail template.

Hooks:

  • fetch - storage-get - Get the requested content from the storage.
  • filter - render-content - Passes in the document content.
  • filter - view-model-detail - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.preview

Renders the a preview of the passed in content. Sets the X-Robots-Tag header to noindex.

Hooks:

  • render-content - render-content - Passes in the request body content.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.historyIndex

Renders the history index page using the history_index template. Sets the X-Robots-Tag header to noindex.

Hooks:

  • filter - view-model-history-index - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.historyDetail

Renders the history detail page using the detail template. Sets the X-Robots-Tag header to noindex.

Hooks:

  • render-content - render-content - Passes in the document content.
  • filter - view-model-history-index - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.historyRestore

Renders the history restore page using the edit template. Sets the X-Robots-Tag header to noindex.

Hooks:

  • filter - view-model-history-restore - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.notFound

Renders the 404 Not Found page using the 404 template. Sets the X-Robots-Tag header to noindex.

Hooks:

  • filter - view-model-error-404 - Passes in the viewModel.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.saveValid

Handles saving documents, and changing the slug of documents, then redirecting to the document.

title, excerpt, and content will default to a blank string tags is expected to be a comma delimited string in the request body, "tag-1,tag-2" slug will be converted to lowercase and will use request.body.slug and fall back to request.params.slug.

Hooks:

  • filter - document-save - Passes in the document.

Kind: instance property of UttoriWiki

Param Type Description
request module:express~Request.<SaveParams, {}, UttoriWikiDocument> The Express Request object.
response module:express~Response The Express Response object.
next module:express~NextFunction The Express Next function.

uttoriWiki.registerPlugins(config)

Registers plugins with the Event Dispatcher.

Kind: instance method of UttoriWiki

Param Type Description
config UttoriWikiConfig A configuration object.

uttoriWiki.validateConfig(config)

Validates the config.

Hooks:

  • dispatch - validate-config - Passes in the config object.

Kind: instance method of UttoriWiki

Param Type Description
config UttoriWikiConfig A configuration object.

uttoriWiki.buildMetadata(document, [path], [robots]) ⇒ Promise.<UttoriWikiDocumentMetaData>

Builds the metadata for the view model.

Hooks:

  • filter - render-content - Passes in the meta description.

Kind: instance method of UttoriWiki
Returns: Promise.<UttoriWikiDocumentMetaData> - Metadata object.

Param Type Description
document Partial.<UttoriWikiDocument> A UttoriWikiDocument.
[path] string The URL path to build meta data for with leading slash.
[robots] string A meta robots tag value.

Example

const metadata = await wiki.buildMetadata(document, '/private-document-path', 'no-index');
 {
  canonical,   // `${this.config.publicUrl}/private-document-path`
  robots,      // 'no-index'
  title,       // document.title
  description, // document.excerpt || document.content.slice(0, 160)
  modified,    // new Date(document.updateDate).toISOString()
  published,   // new Date(document.createDate).toISOString()
}

uttoriWiki.bindRoutes(server)

Bind the routes to the server. Routes are bound in the order of Home, Tags, Search, Not Found Placeholder, Document, Plugins, Not Found - Catch All

Hooks:

  • dispatch - bind-routes - Passes in the server instance.

Kind: instance method of UttoriWiki

Param Type Description
server module:express~Application The Express server instance.

UttoriWikiViewModel : object

Kind: global typedef
Properties

Name Type Description
title string The document title to be used anywhere a title may be needed.
config UttoriWikiConfig The configuration object.
meta UttoriWikiDocumentMetaData The metadata object.
basePath string The base path of the request.
[document] UttoriWikiDocument The document object.
[session] module:express-session~Session The Express session object.
[flash] boolean | object | Array.<string> The flash object.
[taggedDocuments] Array.<UttoriWikiDocument> | Record.<string, Array.<UttoriWikiDocument>> An array of documents that are tagged with the document.
[searchTerm] string The search term to be used in the search results.
[searchResults] Array.<UttoriWikiDocument> An array of search results.
[slug] string The slug of the document.
[action] string The action to be used in the form.
[revision] string The revision of the document.
[historyByDay] Record.<string, Array.<string>> An object of history by day.
[currentDocument] UttoriWikiDocument The current version of the document for comparison.
[diffs] Record.<string, string> An object containing HTML table diffs for changed fields.

UttoriWikiDocument : object

Kind: global typedef
Properties

Name Type Description
slug string The document slug to be used in the URL and as a unique ID.
title string The document title to be used anywhere a title may be needed.
[image] string An image to represent the document in Open Graph or elsewhere.
[excerpt] string A succinct deescription of the document, think meta description.
content string All text content for the doucment.
[html] string All rendered HTML content for the doucment that will be presented to the user.
createDate number The Unix timestamp of the creation date of the document.
updateDate number The Unix timestamp of the last update date to the document.
tags string | Array.<string> A collection of tags that represent the document.
[redirects] string | Array.<string> An array of slug like strings that will redirect to this document. Useful for renaming and keeping links valid or for short form WikiLinks.
[layout] string The layout to use when rendering the document.
[attachments] Array.<UttoriWikiDocumentAttachment> An array of attachments to the document with name being a display name, path being the path to the file, and type being the MIME type of the file. Useful for storing files like PDFs, images, etc.

UttoriWikiDocumentAttachment : object

Kind: global typedef
Properties

Name Type Description
name string The display name of the attachment.
path string The path to the attachment.
type string The MIME type of the attachment.
[skip] boolean Whether to skip the attachment. Used to control whether to index the attachment.

UttoriWikiDocumentMetaData : object

Kind: global typedef
Properties

Name Type Description
canonical string ${this.config.publicUrl}/private-document-path
robots string 'no-index'
title string document.title
description string document.excerpt
modified string new Date(document.updateDate).toISOString()
published string new Date(document.createDate).toISOString()
image string OpenGraph Image

Tests

To run the test suite, first install the dependencies, then run npm test:

npm install
DEBUG=Uttori* npm test

Contributors

License

MIT

About

Uttori Wiki is a wiki module for the Uttori system.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •