@uttori/wiki
v7.1.0
Published
Uttori Wiki is a wiki functionality module for Uttori.
Maintainers
Readme
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:
- @uttori/storage-provider-json-file
- @uttori/search-provider-lunr
- @uttori/plugin-renderer-replacer
- @uttori/plugin-renderer-markdown-it
- @uttori/plugin-upload-multer
- @uttori/plugin-generator-sitemap
- @uttori/plugin-analytics-json-file
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 (replacefieldNamewith 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
- Create a Google Cloud Project and enable the Google Sheets API
- Create a service account and download the credentials JSON file
- Share your Google Sheet with the service account email
- 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
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))
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)
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)
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:
buildMetadata(document, path, robots)- Purpose: Builds metadata for view models
- Usage: Creates metadata for tag index and tag detail pages
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:
GET /{route}(default:GET /tags)- Handler:
tagIndex - Purpose: Displays the tag index page with all available tags
- Template:
tags
- Handler:
GET /{route}/:tag(default:GET /tags/:tag)- Handler:
tag - Purpose: Displays all documents with a specific tag
- Template:
tag
- Handler:
Templates Required
The plugin expects the following templates to exist in your theme:
tags- Tag index page template- Variables:
title,config,session,taggedDocuments,meta,basePath,flash
- Variables:
tag- Individual tag page template- Variables:
title,config,session,taggedDocuments,meta,basePath,flash
- Variables:
Migration from Core
When migrating from the core tag functionality:
Remove from config.js:
ignoreTagspropertyroutes.tagspropertytitles.tagspropertytagIndexRouteandtagRoutepropertiesrouteMiddleware.tagIndexandrouteMiddleware.tagproperties
Remove from wiki.js:
tagIndexmethodtagmethodgetTaggedDocumentsmethod- Tag route binding in
bindRoutes
Add plugin to configuration:
- Import
TagRoutesPlugin - Add to
pluginsarray - Configure with
'uttori-plugin-tag-routes'key
- Import
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
Typedefs
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. |
- UttoriWiki
- new UttoriWiki(config, server)
- .config : UttoriWikiConfig
- .hooks : module:@uttori/event-dispatcher~EventDispatcher
- .home
- .homepageRedirect : module:express~RequestHandler
- .search
- .edit
- .delete
- .save
- .saveNew
- .create
- .detail
- .preview
- .historyIndex
- .historyDetail
- .historyRestore
- .notFound
- .saveValid
- .registerPlugins(config)
- .validateConfig(config)
- .buildMetadata(document, [path], [robots]) ⇒ Promise.<UttoriWikiDocumentMetaData>
- .bindRoutes(server)
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. |
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 || document.content.slice(0, 160) |
| 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 testContributors
- Matthew Callis - author of UttoriWiki
