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.
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'));
});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. |
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.
- 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
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.'
}
]
}
};- 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
- 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
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
}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>
`
})- 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
{formName}: The form name{timestamp}: Current timestamp{fieldName}: Any form field value (replacefieldNamewith actual field name)
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
})- 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
- 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
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'
};
}
}Forms are accessible at: {baseRoute}{formRoute}
For example, with baseRoute: '/forms' and form route: '/contact':
- POST
/forms/contact
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
});// 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"
]
}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
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
}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
- nodemailer: For email handler (install with
npm install nodemailer) - googleapis: For Google Sheets handler (install with
npm install googleapis)
- 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
This plugin provides tag index and individual tag pages.
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
}
};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: []
}
}
}The plugin uses the following hooks to maintain existing functionality:
-
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)
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
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:
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:
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
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
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.
- UttoriWiki
UttoriWiki is a fast, simple, wiki knowledge base.
- UttoriWikiViewModel :
object - UttoriWikiDocument :
object - UttoriWikiDocumentAttachment :
object - UttoriWikiDocumentMetaData :
object
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)
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'), () => { ... });Kind: instance property of UttoriWiki
Kind: instance property of UttoriWiki
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. |
Redirects to the homepage.
Kind: instance property of UttoriWiki
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
Registers plugins with the Event Dispatcher.
Kind: instance method of UttoriWiki
| Param | Type | Description |
|---|---|---|
| config | UttoriWikiConfig |
A configuration object. |
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()
}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. |
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. |
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. |
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. |
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 |
To run the test suite, first install the dependencies, then run npm test:
npm install
DEBUG=Uttori* npm test- Matthew Callis - author of UttoriWiki