docs: only removing the psql and redis-cli dependencies from README.md + trailing whitespace characters
This commit is contained in:
parent
9dc83a65cf
commit
521fea2530
1 changed files with 100 additions and 129 deletions
229
README.md
229
README.md
|
|
@ -10,92 +10,91 @@
|
||||||
<p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p>
|
<p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p>
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
* [Introduction](#introduction)
|
||||||
- [Introduction](#introduction)
|
* [Overview](#overview)
|
||||||
- [Overview](#overview)
|
* [Foundation](#foundation)
|
||||||
- [Foundation](#foundation)
|
* [Backend](#backend)
|
||||||
- [Backend](#backend)
|
* [Frontend](#frontend)
|
||||||
- [Frontend](#frontend)
|
* [Storage](#storage)
|
||||||
- [Storage](#storage)
|
* [Screenshots](#screenshots)
|
||||||
- [Screenshots](#screenshots)
|
* [Getting started](#getting-started)
|
||||||
- [Getting started](#getting-started)
|
* [Dependencies](#dependencies)
|
||||||
- [Dependencies](#dependencies)
|
* [Start the application](#start-the-application)
|
||||||
- [Start the application](#start-the-application)
|
* [Running tests](#running-tests)
|
||||||
- [Running tests](#running-tests)
|
* [Clients](#clients)
|
||||||
- [Clients](#clients)
|
* [Service container](#service-container)
|
||||||
- [Service container](#service-container)
|
* [Dependency injection](#dependency-injection)
|
||||||
- [Dependency injection](#dependency-injection)
|
* [Test dependencies](#test-dependencies)
|
||||||
- [Test dependencies](#test-dependencies)
|
* [Configuration](#configuration)
|
||||||
- [Configuration](#configuration)
|
* [Environment overrides](#environment-overrides)
|
||||||
- [Environment overrides](#environment-overrides)
|
* [Environments](#environments)
|
||||||
- [Environments](#environments)
|
* [Database](#database)
|
||||||
- [Database](#database)
|
* [Auto-migrations](#auto-migrations)
|
||||||
- [Auto-migrations](#auto-migrations)
|
* [Separate test database](#separate-test-database)
|
||||||
- [Separate test database](#separate-test-database)
|
* [ORM](#orm)
|
||||||
- [ORM](#orm)
|
* [Entity types](#entity-types)
|
||||||
- [Entity types](#entity-types)
|
* [New entity type](#new-entity-type)
|
||||||
- [New entity type](#new-entity-type)
|
* [Sessions](#sessions)
|
||||||
- [Sessions](#sessions)
|
* [Encryption](#encryption)
|
||||||
- [Encryption](#encryption)
|
* [Authentication](#authentication)
|
||||||
- [Authentication](#authentication)
|
* [Login / Logout](#login--logout)
|
||||||
- [Login / Logout](#login--logout)
|
* [Forgot password](#forgot-password)
|
||||||
- [Forgot password](#forgot-password)
|
* [Registration](#registration)
|
||||||
- [Registration](#registration)
|
* [Authenticated user](#authenticated-user)
|
||||||
- [Authenticated user](#authenticated-user)
|
* [Middleware](#middleware)
|
||||||
- [Middleware](#middleware)
|
* [Email verification](#email-verification)
|
||||||
- [Email verification](#email-verification)
|
* [Routes](#routes)
|
||||||
- [Routes](#routes)
|
* [Custom middleware](#custom-middleware)
|
||||||
- [Custom middleware](#custom-middleware)
|
* [Controller / Dependencies](#controller--dependencies)
|
||||||
- [Controller / Dependencies](#controller--dependencies)
|
* [Patterns](#patterns)
|
||||||
- [Patterns](#patterns)
|
* [Errors](#errors)
|
||||||
- [Errors](#errors)
|
* [Testing](#testing)
|
||||||
- [Testing](#testing)
|
* [HTTP server](#http-server)
|
||||||
- [HTTP server](#http-server)
|
* [Request / Request helpers](#request--response-helpers)
|
||||||
- [Request / Request helpers](#request--response-helpers)
|
* [Goquery](#goquery)
|
||||||
- [Goquery](#goquery)
|
* [Controller](#controller)
|
||||||
- [Controller](#controller)
|
* [Page](#page)
|
||||||
- [Page](#page)
|
* [Flash messaging](#flash-messaging)
|
||||||
- [Flash messaging](#flash-messaging)
|
* [Pager](#pager)
|
||||||
- [Pager](#pager)
|
* [CSRF](#csrf)
|
||||||
- [CSRF](#csrf)
|
* [Automatic template parsing](#automatic-template-parsing)
|
||||||
- [Automatic template parsing](#automatic-template-parsing)
|
* [Cached responses](#cached-responses)
|
||||||
- [Cached responses](#cached-responses)
|
* [Cache tags](#cache-tags)
|
||||||
- [Cache tags](#cache-tags)
|
* [Cache middleware](#cache-middleware)
|
||||||
- [Cache middleware](#cache-middleware)
|
* [Data](#data)
|
||||||
- [Data](#data)
|
* [Forms](#forms)
|
||||||
- [Forms](#forms)
|
* [Submission processing](#submission-processing)
|
||||||
- [Submission processing](#submission-processing)
|
* [Inline validation](#inline-validation)
|
||||||
- [Inline validation](#inline-validation)
|
* [Headers](#headers)
|
||||||
- [Headers](#headers)
|
* [Status code](#status-code)
|
||||||
- [Status code](#status-code)
|
* [Metatags](#metatags)
|
||||||
- [Metatags](#metatags)
|
* [URL and link generation](#url-and-link-generation)
|
||||||
- [URL and link generation](#url-and-link-generation)
|
* [HTMX support](#htmx-support)
|
||||||
- [HTMX support](#htmx-support)
|
* [Rendering the page](#rendering-the-page)
|
||||||
- [Rendering the page](#rendering-the-page)
|
* [Template renderer](#template-renderer)
|
||||||
- [Template renderer](#template-renderer)
|
* [Custom functions](#custom-functions)
|
||||||
- [Custom functions](#custom-functions)
|
* [Caching](#caching)
|
||||||
- [Caching](#caching)
|
* [Hot-reload for development](#hot-reload-for-development)
|
||||||
- [Hot-reload for development](#hot-reload-for-development)
|
* [File configuration](#file-configuration)
|
||||||
- [File configuration](#file-configuration)
|
* [Funcmap](#funcmap)
|
||||||
- [Funcmap](#funcmap)
|
* [Cache](#cache)
|
||||||
- [Cache](#cache)
|
* [Set data](#set-data)
|
||||||
- [Set data](#set-data)
|
* [Get data](#get-data)
|
||||||
- [Get data](#get-data)
|
* [Flush data](#flush-data)
|
||||||
- [Flush data](#flush-data)
|
* [Flush tags](#flush-tags)
|
||||||
- [Flush tags](#flush-tags)
|
* [Tasks](#tasks)
|
||||||
- [Tasks](#tasks)
|
* [Queues](#queues)
|
||||||
- [Queues](#queues)
|
* [Scheduled tasks](#scheduled-tasks)
|
||||||
- [Scheduled tasks](#scheduled-tasks)
|
* [Worker](#worker)
|
||||||
- [Worker](#worker)
|
* [Monitoring](#monitoring)
|
||||||
- [Monitoring](#monitoring)
|
* [Static files](#static-files)
|
||||||
- [Static files](#static-files)
|
* [Cache control headers](#cache-control-headers)
|
||||||
- [Cache control headers](#cache-control-headers)
|
* [Cache-buster](#cache-buster)
|
||||||
- [Cache-buster](#cache-buster)
|
* [Email](#email)
|
||||||
- [Email](#email)
|
* [HTTPS](#https)
|
||||||
- [HTTPS](#https)
|
* [Logging](#logging)
|
||||||
- [Logging](#logging)
|
* [Roadmap](#roadmap)
|
||||||
- [Roadmap](#roadmap)
|
* [Credits](#credits)
|
||||||
- [Credits](#credits)
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
|
|
@ -149,9 +148,9 @@ Go server-side rendered HTML combined with the projects below enable you to crea
|
||||||
|
|
||||||
Ensure the following are installed on your system:
|
Ensure the following are installed on your system:
|
||||||
|
|
||||||
- [Go](https://go.dev/)
|
- [Go](https://go.dev/)
|
||||||
- [Docker](https://www.docker.com/)
|
- [Docker](https://www.docker.com/)
|
||||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
### Start the application
|
### Start the application
|
||||||
|
|
||||||
|
|
@ -202,7 +201,7 @@ A new container can be created and initialized via `services.NewContainer()`. It
|
||||||
### Dependency injection
|
### Dependency injection
|
||||||
|
|
||||||
The container exists to faciliate easy dependency-injection both for services within the container as well as areas of your application that require any of these dependencies. For example, the container is passed to and stored within the `Controller`
|
The container exists to faciliate easy dependency-injection both for services within the container as well as areas of your application that require any of these dependencies. For example, the container is passed to and stored within the `Controller`
|
||||||
so that the controller and the route using it have full, easy access to all services.
|
so that the controller and the route using it have full, easy access to all services.
|
||||||
|
|
||||||
### Test dependencies
|
### Test dependencies
|
||||||
|
|
||||||
|
|
@ -277,7 +276,6 @@ Ent relies on code-generation for the entities you create to provide robust, typ
|
||||||
### Entity types
|
### Entity types
|
||||||
|
|
||||||
The two included entity types are:
|
The two included entity types are:
|
||||||
|
|
||||||
- User
|
- User
|
||||||
- PasswordToken
|
- PasswordToken
|
||||||
|
|
||||||
|
|
@ -395,8 +393,8 @@ A `middleware` package is included which you can easily add to along with the cu
|
||||||
|
|
||||||
The `Controller`, which is described in a section below, serves two purposes for routes:
|
The `Controller`, which is described in a section below, serves two purposes for routes:
|
||||||
|
|
||||||
1. It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
|
1) It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
|
||||||
2. It stores a pointer to the `Container`, making all _Services_ available within your route
|
2) It stores a pointer to the `Container`, making all _Services_ available within your route
|
||||||
|
|
||||||
While using the `Controller` is not required for your routes, it will certainly make development easier.
|
While using the `Controller` is not required for your routes, it will certainly make development easier.
|
||||||
|
|
||||||
|
|
@ -522,7 +520,6 @@ Flash messaging requires that [sessions](#sessions) and the session middleware a
|
||||||
#### Creating messages
|
#### Creating messages
|
||||||
|
|
||||||
There are four types of messages, and each can be created as follows:
|
There are four types of messages, and each can be created as follows:
|
||||||
|
|
||||||
- Success: `msg.Success(ctx echo.Context, message string)`
|
- Success: `msg.Success(ctx echo.Context, message string)`
|
||||||
- Info: `msg.Info(ctx echo.Context, message string)`
|
- Info: `msg.Info(ctx echo.Context, message string)`
|
||||||
- Warning: `msg.Warning(ctx echo.Context, message string)`
|
- Warning: `msg.Warning(ctx echo.Context, message string)`
|
||||||
|
|
@ -576,10 +573,10 @@ page.Layout = "main"
|
||||||
|
|
||||||
That alone will result in the following templates being parsed and executed when the `Page` is rendered:
|
That alone will result in the following templates being parsed and executed when the `Page` is rendered:
|
||||||
|
|
||||||
1. `layouts/main.gohtml` as the base template
|
1) `layouts/main.gohtml` as the base template
|
||||||
2. `pages/home.gohtml` to provide the `content` template for the layout
|
2) `pages/home.gohtml` to provide the `content` template for the layout
|
||||||
3. All template files located within the `components` directory
|
3) All template files located within the `components` directory
|
||||||
4. The entire [funcmap](#funcmap)
|
4) The entire [funcmap](#funcmap)
|
||||||
|
|
||||||
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
|
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
|
||||||
|
|
||||||
|
|
@ -600,9 +597,8 @@ You can use the [cache client](#cache) on the `Container` to easily [flush cache
|
||||||
Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
|
Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
|
||||||
|
|
||||||
The cache is bypassed if the requests meet any of the following criteria:
|
The cache is bypassed if the requests meet any of the following criteria:
|
||||||
|
1) Is not a GET request
|
||||||
1. Is not a GET request
|
2) Is made by an authenticated user
|
||||||
2. Is made by an authenticated user
|
|
||||||
|
|
||||||
Cached pages are looked up for a key that matches the exact, full URL of the given request.
|
Cached pages are looked up for a key that matches the exact, full URL of the given request.
|
||||||
|
|
||||||
|
|
@ -640,14 +636,12 @@ Form submission processing is made extremely simple by leveraging functionality
|
||||||
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
||||||
|
|
||||||
Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end:
|
Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var form ContactForm
|
var form ContactForm
|
||||||
ctx.Set(context.FormKey, &form)
|
ctx.Set(context.FormKey, &form)
|
||||||
```
|
```
|
||||||
|
|
||||||
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := ctx.Bind(&form); err != nil {
|
||||||
// Something went wrong...
|
// Something went wrong...
|
||||||
|
|
@ -655,7 +649,6 @@ if err := ctx.Bind(&form); err != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
|
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := form.Submission.Process(ctx, form); err != nil {
|
||||||
// Something went wrong...
|
// Something went wrong...
|
||||||
|
|
@ -663,7 +656,6 @@ if err := form.Submission.Process(ctx, form); err != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
Check if the form submission has any validation errors:
|
Check if the form submission has any validation errors:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if !form.Submission.HasErrors() {
|
if !form.Submission.HasErrors() {
|
||||||
// All good, now execute something!
|
// All good, now execute something!
|
||||||
|
|
@ -671,7 +663,6 @@ if !form.Submission.HasErrors() {
|
||||||
```
|
```
|
||||||
|
|
||||||
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
|
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if form.Submission.HasErrors() {
|
if form.Submission.HasErrors() {
|
||||||
return c.Get(ctx)
|
return c.Get(ctx)
|
||||||
|
|
@ -679,7 +670,6 @@ if form.Submission.HasErrors() {
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
page := controller.NewPage(ctx)
|
page := controller.NewPage(ctx)
|
||||||
page.Form = ContactForm{}
|
page.Form = ContactForm{}
|
||||||
|
|
@ -690,15 +680,8 @@ if form := ctx.Get(context.FormKey); form != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
And finally, your template:
|
And finally, your template:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input
|
<input id="email" name="email" type="email" class="input" value="{{.Form.Email}}">
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
class="input"
|
|
||||||
value="{{.Form.Email}}"
|
|
||||||
/>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Inline validation
|
#### Inline validation
|
||||||
|
|
@ -710,14 +693,11 @@ While [validator](https://github.com/go-playground/validator) is a great package
|
||||||
To provide the inline validation in your template, there are two things that need to be done.
|
To provide the inline validation in your template, there are two things that need to be done.
|
||||||
|
|
||||||
First, include a status class on the element so it will highlight green or red based on the validation:
|
First, include a status class on the element so it will highlight green or red based on the validation:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input id="email" name="email" type="email" class="input
|
<input id="email" name="email" type="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
|
||||||
{{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Second, render the error messages, if there are any for a given field:
|
Second, render the error messages, if there are any for a given field:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
|
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
|
||||||
```
|
```
|
||||||
|
|
@ -765,14 +745,12 @@ A _component_ template is included to render metatags in `core.gohtml` which can
|
||||||
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
|
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
|
||||||
|
|
||||||
As an example, if you have route such as:
|
As an example, if you have route such as:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
profile := Profile{Controller: ctr}
|
profile := Profile{Controller: ctr}
|
||||||
e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
|
e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
|
||||||
```
|
```
|
||||||
|
|
||||||
And you want to generate a URL in the template, you can:
|
And you want to generate a URL in the template, you can:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{call .ToURL "user_profile" 1}
|
{{call .ToURL "user_profile" 1}
|
||||||
```
|
```
|
||||||
|
|
@ -786,11 +764,9 @@ There is also a helper function provided in the [funcmap](#funcmap) to generate
|
||||||
```
|
```
|
||||||
|
|
||||||
Will generate:
|
Will generate:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<a href="/user/profile/1" class="is-active extra-class">Profile</a>
|
<a href="/user/profile/1" class="is-active extra-class">Profile</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
|
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
|
||||||
|
|
||||||
### HTMX support
|
### HTMX support
|
||||||
|
|
@ -798,7 +774,6 @@ Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` clas
|
||||||
[HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
[HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
||||||
|
|
||||||
Many examples of its usage are available in the included examples:
|
Many examples of its usage are available in the included examples:
|
||||||
|
|
||||||
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
|
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
|
||||||
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
|
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
|
||||||
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
|
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
|
||||||
|
|
@ -871,7 +846,6 @@ buf, err = c.TemplateRenderer.
|
||||||
```
|
```
|
||||||
|
|
||||||
This will do the following:
|
This will do the following:
|
||||||
|
|
||||||
- [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once
|
- [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once
|
||||||
- Set the _base template file_ as `main`
|
- Set the _base template file_ as `main`
|
||||||
- Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml`
|
- Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml`
|
||||||
|
|
@ -1065,7 +1039,6 @@ err := c.Tasks.
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, this task will be:
|
In this example, this task will be:
|
||||||
|
|
||||||
- Assigned a task type of `my_task`
|
- Assigned a task type of `my_task`
|
||||||
- The task worker will be sent `taskData` as the payload
|
- The task worker will be sent `taskData` as the payload
|
||||||
- Put in to the `critical` queue
|
- Put in to the `critical` queue
|
||||||
|
|
@ -1163,15 +1136,13 @@ The cache max-life is controlled by the configuration at `Config.Cache.Expiratio
|
||||||
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
|
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
|
||||||
|
|
||||||
For example, to render a file located in `static/picture.png`, you would use:
|
For example, to render a file located in `static/picture.png`, you would use:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="{{File "picture.png"}}"/>
|
<img src="{{File "picture.png"}}"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
Which would result in:
|
Which would result in:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="/files/picture.png?v=9fhe73kaf3" />
|
<img src="/files/picture.png?v=9fhe73kaf3"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `9fhe73kaf3` is the randomly-generated cache-buster.
|
Where `9fhe73kaf3` is the randomly-generated cache-buster.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue