mirror of
https://github.com/evennia/evennia.git
synced 2026-04-03 14:37:17 +02:00
Refactoring Concept/Component documentation. Still not done
This commit is contained in:
parent
7114aea912
commit
c7ec3dfad3
42 changed files with 745 additions and 1275 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
```
|
||||
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
|
||||
│Client├─┼──►│Session├───►│Account├──►│Object│
|
||||
│Client├─┼──►│Session├───►│Account├──►│Object│
|
||||
└──────┘ │ └───────┘ └───────┘ └──────┘
|
||||
^
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
# Bootstrap Components and Utilities
|
||||
|
||||
Bootstrap provides many utilities and components you can use when customizing Evennia's web
|
||||
presence. We'll go over a few examples here that you might find useful.
|
||||
> Please take a look at either [the basic web tutorial](../Howtos/Beginner-Tutorial/Part5/Add-a-simple-new-web-page.md) or
|
||||
>[the web character view tutorial](../Howtos/Web-Character-View-Tutorial.md)
|
||||
> to get a feel for how to add pages to Evennia's website to test these examples.
|
||||
|
||||
## General Styling
|
||||
Bootstrap provides base styles for your site. These can be customized through CSS, but the default
|
||||
styles are intended to provide a consistent, clean look for sites.
|
||||
|
||||
### Color
|
||||
Most elements can be styled with default colors. [Take a look at the documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors
|
||||
- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color
|
||||
or background color.
|
||||
|
||||
### Borders
|
||||
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth
|
||||
info, please [read the documentation on borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
|
||||
```
|
||||
<span class="border border-dark"></span>
|
||||
```
|
||||
You can also easily round corners just by adding a class.
|
||||
```
|
||||
<img src="..." class="rounded" />
|
||||
```
|
||||
|
||||
### Spacing
|
||||
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might
|
||||
like to add margins or padding through CSS itself - however these classes are used in the default
|
||||
Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to
|
||||
learn more.
|
||||
|
||||
***
|
||||
## Components
|
||||
|
||||
### Buttons
|
||||
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use -
|
||||
button styling can be added to `<button>`, `<a>`, and `<input>` elements.
|
||||
```
|
||||
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
|
||||
<button class="btn btn-primary" type="submit">Me too!</button>
|
||||
<input class="btn btn-primary" type="button" value="Button">
|
||||
<input class="btn btn-primary" type="submit" value="Also a Button">
|
||||
<input class="btn btn-primary" type="reset" value="Button as Well">
|
||||
```
|
||||
### Cards
|
||||
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements
|
||||
that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database
|
||||
Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options -
|
||||
the following is a simple example, but read the documentation or look at the site's source for more.
|
||||
```
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Card title</h4>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
|
||||
<p class="card-text">Fancy, isn't it?</p>
|
||||
<a href="#" class="card-link">Card link</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Jumbotron
|
||||
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an
|
||||
image or tagline for your game. They can flow with the rest of your content or take up the full
|
||||
width of the page - Evennia's base site uses the former.
|
||||
```
|
||||
<div class="jumbotron jumbotron-fluid">
|
||||
<div class="container">
|
||||
<h1 class="display-3">Full Width Jumbotron</h1>
|
||||
<p class="lead">Look at the source of the default Evennia page for a regular Jumbotron</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Forms
|
||||
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap.
|
||||
For a more in-depth look at how to use forms and their styles in your own Evennia site, please read
|
||||
over [the web character gen tutorial.](../Howtos/Web-Character-Generation.md)
|
||||
|
|
@ -41,7 +41,6 @@ Batch-Processors.md
|
|||
Batch-Code-Processor.md
|
||||
Batch-Command-Processor.md
|
||||
Inputfuncs.md
|
||||
Outputfuncs.md
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -77,5 +76,5 @@ Webclient.md
|
|||
Web-Admin.md
|
||||
Webserver.md
|
||||
Web-API.md
|
||||
Bootstrap-Components-and-Utilities.md
|
||||
Web-Bootstrap-Framework.md
|
||||
```
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Default Commands
|
||||
|
||||
The full set of default Evennia commands currently contains 88 commands in 9 source
|
||||
files. Our policy for adding default commands is outlined [here](../Concepts/Using-MUX-as-a-Standard.md). The
|
||||
files. Our policy for adding default commands is outlined [here](../Coding/Default-Command-Syntax.md). The
|
||||
[Commands](./Commands.md) documentation explains how Commands work as well as how to make new or customize
|
||||
existing ones.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
# The Inline Function Parser
|
||||
|
||||
The [FuncParser](evennia.utils.funcparser.FuncParser) extracts and executes
|
||||
'inline functions'
|
||||
embedded in a string on the form `$funcname(args, kwargs)`. Under the hood, this will
|
||||
lead to a call to a Python function you control. The inline function call will be replaced by
|
||||
the return from the function.
|
||||
The [FuncParser](evennia.utils.funcparser.FuncParser) extracts and executes 'inline functions' embedded in a string on the form `$funcname(args, kwargs)`. Under the hood, this will lead to a call to a Python function you control. The inline function call will be replaced by the return from the function.
|
||||
|
||||
```python
|
||||
from evennia.utils.funcparser import FuncParser
|
||||
|
|
@ -378,7 +374,7 @@ The `caller` is required, it's the the object to do the access-check for. The `a
|
|||
These are used to implement actor-stance emoting. They are used by the
|
||||
[DefaultObject.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method
|
||||
by default. You can read a lot more about this on the page
|
||||
[Change messages per receiver](../Concepts/Change-Messages-Per-Receiver.md).
|
||||
[Change messages per receiver](../Concepts/Change-Message-Per-Receiver.md).
|
||||
|
||||
On the parser side, all these inline functions require extra kwargs be passed into the parser
|
||||
(done by `msg_contents` by default):
|
||||
|
|
|
|||
|
|
@ -17,23 +17,18 @@ An Evennia Object is, by definition, a Python class that includes [evennia.obje
|
|||
┌────────────┐
|
||||
Evennia│ │ObjectParent│
|
||||
library│ └──────▲─────┘
|
||||
│ │
|
||||
┌─────────────┐ │ ┌──────┐ │
|
||||
│DefaultObject◄────────────────────┼────┤Object├──────┤
|
||||
└──────▲──────┘ │ └──────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────┐ │ ┌─────────┐ │
|
||||
├────────┤DefaultCharacter◄─┼────┤Character├───┤
|
||||
│ └────────────────┘ │ └─────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────┐ │ ┌────┐ │
|
||||
├────────┤DefaultRoom ◄─┼────┤Room├────────┤
|
||||
│ └────────────────┘ │ └────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────┐ │ ┌────┐ │
|
||||
└────────┤DefaultExit ◄─┼────┤Exit├────────┘
|
||||
└────────────────┘ │ └────┘
|
||||
│
|
||||
│Game-dir
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# Outputfuncs
|
||||
|
||||
TODO. For now info about outputfuncs are found in [OOB](../Concepts/OOB.md).
|
||||
|
|
@ -1,38 +1,43 @@
|
|||
# Permissions
|
||||
|
||||
A *permission* is simply a text string stored in the handler `permissions` on `Objects`
|
||||
and `Accounts`. Think of it as a specialized sort of [Tag](./Tags.md) - one specifically dedicated
|
||||
to access checking. They are thus often tightly coupled to [Locks](./Locks.md).
|
||||
Permission strings are not case-sensitive, so "Builder" is the same as "builder"
|
||||
etc.
|
||||
A *permission* is simply a text string stored in the handler `permissions` on `Objects` and `Accounts`. Think of it as a specialized sort of [Tag](./Tags.md) - one specifically dedicated to access checking. They are thus often tightly coupled to [Locks](./Locks.md). Permission strings are not case-sensitive, so "Builder" is the same as "builder" etc.
|
||||
|
||||
Permissions are used as a convenient way to structure access levels and
|
||||
hierarchies. It is set by the `perm` command and checked by the
|
||||
`PermissionHandler.check` method as well as by the specially the `perm()` and
|
||||
`pperm()` [lock functions](./Locks.md).
|
||||
Permissions are used as a convenient way to structure access levels and hierarchies. It is set by the `perm` command and checked by the `PermissionHandler.check` method as well as by the specially the `perm()` and `pperm()` [lock functions](./Locks.md).
|
||||
|
||||
All new accounts are given a default set of permissions defined by
|
||||
`settings.PERMISSION_ACCOUNT_DEFAULT`.
|
||||
All new accounts are given a default set of permissions defined by `settings.PERMISSION_ACCOUNT_DEFAULT`.
|
||||
|
||||
## The super user
|
||||
|
||||
There are strictly speaking two types of users in Evennia, the *super user* and everyone else. The
|
||||
superuser is the first user you create, object `#1`. This is the all-powerful server-owner account.
|
||||
Technically the superuser not only has access to everything, it *bypasses* the permission checks
|
||||
entirely.
|
||||
|
||||
This makes the superuser impossible to lock out, but makes it unsuitable to actually play-
|
||||
test the game's locks and restrictions with (see `quell` below). Usually there is no need to have
|
||||
but one superuser.
|
||||
|
||||
## Managing Permissions
|
||||
|
||||
In-game, you use the `perm` command to add and remove permissions
|
||||
j
|
||||
perm/account Tommy = Builders
|
||||
perm/account/del Tommy = Builders
|
||||
|
||||
Note the use of the `/account` switch. It means you assign the permission to the
|
||||
[Accounts](./Accounts.md) Tommy instead of any [Character](./Objects.md) that also
|
||||
happens to be named "Tommy".
|
||||
> perm/account Tommy = Builders
|
||||
> perm/account/del Tommy = Builders
|
||||
|
||||
There can be reasons for putting permissions on Objects (especially NPCS), but
|
||||
for granting powers to players, you should usually put the permission on the
|
||||
`Account` - this guarantees that they are kept, *regardless*
|
||||
of which Character they are currently puppeting. This is especially important to
|
||||
remember when assigning permissions from the *hierarchy tree* (see below), as an
|
||||
Account's permissions will overrule that of its character. So to be sure to
|
||||
avoid confusion you should generally put hierarchy permissions on the Account,
|
||||
not on their Characters (but see also [quelling](#quelling)).
|
||||
Note the use of the `/account` switch. It means you assign the permission to the [Accounts](./Accounts.md) Tommy instead of any [Character](./Objects.md) that also happens to be named "Tommy". If you don't want to use `/account`, you can also prefix the name with `*` to indicate an Account is sought:
|
||||
|
||||
> perm *Tommy = Builders
|
||||
|
||||
There can be reasons for putting permissions on Objects (especially NPCS), but for granting powers to players, you should usually put the permission on the `Account` - this guarantees that they are kept, *regardless* of which Character they are currently puppeting.
|
||||
|
||||
This is especially important to remember when assigning permissions from the *hierarchy tree* (see below), as an Account's permissions will overrule that of its character. So to be sure to avoid confusion you should generally put hierarchy permissions on the Account, not on their Characters/puppets.
|
||||
|
||||
If you _do_ want to start using the permissions on your _puppet_, you use `quell`
|
||||
|
||||
> quell
|
||||
> unquell
|
||||
|
||||
This drops to the permissions on the puppeted object, and then back to your Account-permissions again. Quelling is useful if you want to try something "as" someone else. It's also useful for superusers since this makes them susceptible to locks (so they can test things).
|
||||
|
||||
In code, you add/remove Permissions via the `PermissionHandler`, which sits on all
|
||||
typeclassed entities as the property `.permissions`:
|
||||
|
|
@ -44,7 +49,6 @@ typeclassed entities as the property `.permissions`:
|
|||
obj.permissions.remove("Blacksmith")
|
||||
```
|
||||
|
||||
|
||||
## The permission hierarchy
|
||||
|
||||
Selected permission strings can be organized in a *permission hierarchy* by editing the tuple
|
||||
|
|
@ -57,35 +61,19 @@ Selected permission strings can be organized in a *permission hierarchy* by edit
|
|||
Admin # can administrate accounts
|
||||
Developer # like superuser but affected by locks (highest)
|
||||
|
||||
(Besides being case-insensitive, hierarchical permissions also understand the
|
||||
plural form, so you could use `Developers` and `Developer` interchangeably).
|
||||
(Besides being case-insensitive, hierarchical permissions also understand the plural form, so you could use `Developers` and `Developer` interchangeably).
|
||||
|
||||
> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is
|
||||
set. The Guest is is never part of `settings.PERMISSION_HIERARCHY`.
|
||||
> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is set. The Guest is is never part of `settings.PERMISSION_HIERARCHY`.
|
||||
|
||||
When checking a hierarchical permission (using one of the methods to follow),
|
||||
you will pass checks for your level and all *below* you. That is, even if the
|
||||
check explicitly checks for "Builder" level access, you will actually pass if you have
|
||||
one of "Builder", "Admin" or "Developer". By contrast, if you check for a
|
||||
non-hierarchical permission, like "Blacksmith" you *must* have exactly
|
||||
that permission to pass.
|
||||
When checking a hierarchical permission (using one of the methods to follow), you will pass checks for your level and all *below* you. That is, even if the check explicitly checks for "Builder" level access, you will actually pass if you have one of "Builder", "Admin" or "Developer". By contrast, if you check for a non-hierarchical permission, like "Blacksmith" you *must* have exactly that permission to pass.
|
||||
|
||||
## Checking permissions
|
||||
|
||||
It's important to note that you check for the permission of a *puppeted*
|
||||
[Object](./Objects.md) (like a Character), the check will always first use the
|
||||
permissions of any `Account` connected to that Object before checking for
|
||||
permissions on the Object. In the case of hierarchical permissions (Admins,
|
||||
Builders etc), the Account permission will always be used (this stops an Account
|
||||
from escalating their permission by puppeting a high-level Character). If the
|
||||
permission looked for is not in the hierarchy, an exact match is required, first
|
||||
on the Account and if not found there (or if no Account is connected), then on
|
||||
the Object itself.
|
||||
It's important to note that you check for the permission of a *puppeted* [Object](./Objects.md) (like a Character), the check will always first use the permissions of any `Account` connected to that Object before checking for permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the Account permission will always be used (this stops an Account from escalating their permission by puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact match is required, first on the Account and if not found there (or if no Account is connected), then on the Object itself.
|
||||
|
||||
### Checking with obj.permissions.check()
|
||||
|
||||
The simplest way to check if an entity has a permission is to check its
|
||||
_PermissionHandler_, stored as `.permissions` on all typeclassed entities.
|
||||
The simplest way to check if an entity has a permission is to check its _PermissionHandler_, stored as `.permissions` on all typeclassed entities.
|
||||
|
||||
if obj.permissions.check("Builder"):
|
||||
# allow builder to do stuff
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ The effect of this is that you can fully `reload` the Server and have players st
|
|||
```
|
||||
Internet│ ┌──────────┐ ┌─┐ ┌─┐ ┌─────────┐
|
||||
│ │Portal │ │S│ ┌───┐ │S│ │Server │
|
||||
P │ │ ├─┤e├───┤AMP├───┤e├─┤ │
|
||||
l ──┼──┤ Telnet │ │s│ │ │ │s│ │ │
|
||||
a │ │ Webclient├─┤s├───┤ ├───┤s├─┤ Game │
|
||||
y ──┼──┤ SSH │ │i│ │ │ │i│ │ Database│
|
||||
e │ │ ... ├─┤o├───┤ ├───┤o├─┤ │
|
||||
r ──┼──┤ │ │n│ │ │ │n│ │ │
|
||||
P │ │ │ │e│ │AMP│ │e│ │ │
|
||||
l ──┼──┤ Telnet ├─┤s├───┤ ├───┤s├─┤ │
|
||||
a │ │ Webclient│ │s│ │ │ │s│ │ Game │
|
||||
y ──┼──┤ SSH ├─┤i├───┤ ├───┤i├─┤ Database│
|
||||
e │ │ ... │ │o│ │ │ │o│ │ │
|
||||
r ──┼──┤ ├─┤n├───┤ ├───┤n├─┤ │
|
||||
s │ │ │ │s│ └───┘ │s│ │ │
|
||||
│ └──────────┘ └─┘ └─┘ └─────────┘
|
||||
│Evennia
|
||||
│Evennia
|
||||
```
|
||||
|
||||
The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. This allows the two programs to communicate seamlessly on the same machine.
|
||||
|
|
@ -186,7 +186,7 @@ server reboot (assuming the Portal is not stopped at the same time, obviously).
|
|||
Both the Portal and Server each have a *sessionhandler* to manage the connections. These handlers
|
||||
are global entities contain all methods for relaying data across the AMP bridge. All types of
|
||||
Sessions hold a reference to their respective Sessionhandler (the property is called
|
||||
`sessionhandler`) so they can relay data. See [protocols](../Concepts/Custom-Protocols.md) for more info
|
||||
`sessionhandler`) so they can relay data. See [protocols](../Concepts/Protocols.md) for more info
|
||||
on building new protocols.
|
||||
|
||||
To get all Sessions in the game (i.e. all currently connected clients), you access the server-side
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
# Evennia REST API
|
||||
|
||||
Evennia makes its database accessible via a REST API found on
|
||||
[http://localhost:4001/api](http://localhost:4001/api) if running locally with
|
||||
default setup. The API allows you to retrieve, edit and create resources from
|
||||
outside the game, for example with your own custom client or game editor.
|
||||
|
||||
While you can view and learn about the api in the web browser, it is really
|
||||
[http://localhost:4001/api](http://localhost:4001/api) if running locally with default setup. The API allows you to retrieve, edit and create resources from outside the game, for example with your own custom client or game editor. While you can view and learn about the api in the web browser, it is really
|
||||
meant to be accessed in code, by other programs.
|
||||
|
||||
The API is using [Django Rest Framework][drf]. This automates the process
|
||||
|
|
@ -55,16 +51,10 @@ To list a specific type of object:
|
|||
"previous": null,
|
||||
"results" : [{"db_key": "A rusty longsword", "id": 57, "db_location": 213, ...}]}
|
||||
|
||||
In the above example, it now displays the objects inside the "results" array,
|
||||
while it has a "count" value for the number of total objects, and "next" and
|
||||
"previous" links for the next and previous page, if any. This is called
|
||||
[pagination][pagination], and the link displays "limit" and "offset" as query
|
||||
parameters that can be added to the url to control the output.
|
||||
In the above example, it now displays the objects inside the "results" array, while it has a "count" value for the number of total objects, and "next" and "previous" links for the next and previous page, if any. This is called [pagination][pagination], and the link displays "limit" and "offset" as query parameters that can be added to the url to control the output.
|
||||
|
||||
|
||||
Other query parameters can be defined as [filters][filters] which allow you to
|
||||
further narrow the results. For example, to only get accounts with developer
|
||||
permissions:
|
||||
Other query parameters can be defined as [filters][filters] which allow you to further narrow the results. For example, to only get accounts with developer permissions:
|
||||
|
||||
>>> response = requests.get("https://www.mygame.com/api/accounts/?permission=developer",
|
||||
auth=("MyUserName", "password123"))
|
||||
|
|
@ -83,11 +73,7 @@ Now suppose that you want to use the API to create an [Object](./Objects.md):
|
|||
{"db_key": "A shiny sword", "id": 214, "db_location": None, ...}
|
||||
|
||||
|
||||
Here we made a HTTP POST request to the `/api/objects` endpoint with the `db_key`
|
||||
we wanted. We got back info for the newly created object. You can now make
|
||||
another request with PUT (replace everything) or PATCH (replace only what you
|
||||
provide). By providing the id to the endpoint (`/api/objects/214`),
|
||||
we make sure to update the right sword:
|
||||
Here we made a HTTP POST request to the `/api/objects` endpoint with the `db_key` we wanted. We got back info for the newly created object. You can now make another request with PUT (replace everything) or PATCH (replace only what you provide). By providing the id to the endpoint (`/api/objects/214`), we make sure to update the right sword:
|
||||
|
||||
>>> data = {"db_key": "An even SHINIER sword", "db_location": 50}
|
||||
>>> response = requests.put("https://www.mygame.com/api/objects/214",
|
||||
|
|
|
|||
164
docs/source/Components/Web-Bootstrap-Framework.md
Normal file
164
docs/source/Components/Web-Bootstrap-Framework.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# Bootstrap frontend framework
|
||||
|
||||
Evennia's default web page uses a framework called [Bootstrap](https://getbootstrap.com/). This framework is in use across the internet - you'll probably start to recognize its influence once you learn some of the common design patterns. This switch is great for web developers, perhaps like yourself, because instead of wondering about setting up different grid systems or what custom class another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by default, and comes with some default styles that Evennia has lightly overrode to keep some of the same colors and styles you're used to from the previous design.
|
||||
|
||||
e, a brief overview of Bootstrap follows. For more in-depth info, please
|
||||
read [the documentation](https://getbootstrap.com/docs/4.0/getting-started/introduction/).
|
||||
|
||||
## Grid system
|
||||
|
||||
Other than the basic styling Bootstrap includes, it also includes [a built in layout and grid system](https://getbootstrap.com/docs/4.0/layout/overview/).
|
||||
|
||||
### The container
|
||||
|
||||
The first part of the grid system is [the container](https://getbootstrap.com/docs/4.0/layout/overview/#containers).
|
||||
|
||||
The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and
|
||||
full-width. Fixed-width containers take up a certain max-width of the page - they're useful for limiting the width on Desktop or Tablet platforms, instead of making the content span the width of the page.
|
||||
|
||||
```
|
||||
<div class="container">
|
||||
<!--- Your content here -->
|
||||
</div>
|
||||
```
|
||||
Full width containers take up the maximum width available to them - they'll span across a wide-
|
||||
screen desktop or a smaller screen phone, edge-to-edge.
|
||||
```
|
||||
<div class="container-fluid">
|
||||
<!--- This content will span the whole page -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### The grid
|
||||
|
||||
The second part of the layout system is [the grid](https://getbootstrap.com/docs/4.0/layout/grid/).
|
||||
|
||||
This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of elements depending on the size of the screen, without writing any media queries. We'll briefly go over it - to learn more, please read the docs or look at the source code for Evennia's home page in your browser.
|
||||
|
||||
> Important! Grid elements should be in a .container or .container-fluid. This will center the
|
||||
contents of your site.
|
||||
|
||||
Bootstrap's grid system allows you to create rows and columns by applying classes based on breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If you'd like to know more about these breakpoints, please [take a look at the documentation for
|
||||
them.](https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints)
|
||||
|
||||
To use the grid system, first create a container for your content, then add your rows and columns like so:
|
||||
```
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
1 of 3
|
||||
</div>
|
||||
<div class="col">
|
||||
2 of 3
|
||||
</div>
|
||||
<div class="col">
|
||||
3 of 3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
This layout would create three equal-width columns.
|
||||
|
||||
To specify your sizes - for instance, Evennia's default site has three columns on desktop and
|
||||
tablet, but reflows to single-column on smaller screens. Try it out!
|
||||
```
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-md-6 col-lg-3">
|
||||
1 of 4
|
||||
</div>
|
||||
<div class="col col-md-6 col-lg-3">
|
||||
2 of 4
|
||||
</div>
|
||||
<div class="col col-md-6 col-lg-3">
|
||||
3 of 4
|
||||
</div>
|
||||
<div class="col col-md-6 col-lg-3">
|
||||
4 of 4
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on
|
||||
anything smaller.
|
||||
|
||||
To learn more about Bootstrap's grid, please [take a look at the
|
||||
docs](https://getbootstrap.com/docs/4.0/layout/grid/)
|
||||
I
|
||||
## General Styling elements
|
||||
|
||||
Bootstrap provides base styles for your site. These can be customized through CSS, but the default
|
||||
styles are intended to provide a consistent, clean look for sites.
|
||||
|
||||
### Color
|
||||
Most elements can be styled with default colors. [Take a look at the documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors
|
||||
- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color
|
||||
or background color.
|
||||
|
||||
### Borders
|
||||
|
||||
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth
|
||||
info, please [read the documentation on borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
|
||||
```
|
||||
<span class="border border-dark"></span>
|
||||
```
|
||||
You can also easily round corners just by adding a class.
|
||||
```
|
||||
<img src="..." class="rounded" />
|
||||
```
|
||||
|
||||
### Spacing
|
||||
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might like to add margins or padding through CSS itself - however these classes are used in the default Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to
|
||||
learn more.
|
||||
|
||||
### Buttons
|
||||
|
||||
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use - button styling can be added to `<button>`, `<a>`, and `<input>` elements.
|
||||
```
|
||||
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
|
||||
<button class="btn btn-primary" type="submit">Me too!</button>
|
||||
<input class="btn btn-primary" type="button" value="Button">
|
||||
<input class="btn btn-primary" type="submit" value="Also a Button">
|
||||
<input class="btn btn-primary" type="reset" value="Button as Well">
|
||||
```
|
||||
|
||||
### Cards
|
||||
|
||||
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements
|
||||
that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database
|
||||
Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options -
|
||||
the following is a simple example, but read the documentation or look at the site's source for more.
|
||||
```
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Card title</h4>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
|
||||
<p class="card-text">Fancy, isn't it?</p>
|
||||
<a href="#" class="card-link">Card link</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Jumbotron
|
||||
|
||||
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an
|
||||
image or tagline for your game. They can flow with the rest of your content or take up the full
|
||||
width of the page - Evennia's base site uses the former.
|
||||
```
|
||||
<div class="jumbotron jumbotron-fluid">
|
||||
<div class="container">
|
||||
<h1 class="display-3">Full Width Jumbotron</h1>
|
||||
<p class="lead">Look at the source of the default Evennia page for a regular Jumbotron</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Forms
|
||||
|
||||
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap.
|
||||
For a more in-depth look at how to use forms and their styles in your own Evennia site, please read
|
||||
over [the web character gen tutorial.](../Howtos/Web-Character-Generation.md)
|
||||
|
||||
## Further reading
|
||||
|
||||
Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting- started/introduction/) or read one of our other web tutorials.
|
||||
|
|
@ -1,20 +1,10 @@
|
|||
# Game website
|
||||
|
||||
When Evennia starts it will also start a [Webserver](./Webserver.md) as part of the
|
||||
[Server](./Portal-And-Server.md) process. This uses [Django](https://docs.djangoproject.com)
|
||||
to present a simple but functional default game website. With the default setup,
|
||||
open your browser to [localhost:4001](http://localhost:4001) or [127.0.0.1:4001](http://127.0.0.1:4001)
|
||||
to see it.
|
||||
When Evennia starts it will also start a [Webserver](./Webserver.md) as part of the [Server](./Portal-And-Server.md) process. This uses [Django](https://docs.djangoproject.com) to present a simple but functional default game website. With the default setup, open your browser to [localhost:4001](http://localhost:4001) or [127.0.0.1:4001](http://127.0.0.1:4001) to see it.
|
||||
|
||||
The website allows existing players to log in using an account-name and
|
||||
password they previously used to register with the game. If a user logs in with
|
||||
the [Webclient](./Webclient.md) they will also log into the website and vice-versa.
|
||||
So if you are logged into the website, opening the webclient will automatically
|
||||
log you into the game as that account.
|
||||
The website allows existing players to log in using an account-name and password they previously used to register with the game. If a user logs in with the [Webclient](./Webclient.md) they will also log into the website and vice-versa. So if you are logged into the website, opening the webclient will automatically log you into the game as that account.
|
||||
|
||||
The default website shows a "Welcome!" page with a few links to useful
|
||||
resources. It also shows some statistics about how many players are currently
|
||||
connected.
|
||||
The default website shows a "Welcome!" page with a few links to useful resources. It also shows some statistics about how many players are currently connected.
|
||||
|
||||
In the top menu you can find
|
||||
- _Home_ - Get back to front page.
|
||||
|
|
@ -41,10 +31,7 @@ In the top menu you can find
|
|||
|
||||
## Modifying the default Website
|
||||
|
||||
You can modify and override all aspects of the web site from your game dir.
|
||||
You'll mostly be doing so in your settings file
|
||||
(`mygame/server/conf/settings.py` and in the gamedir's `web/folder`
|
||||
(`mygame/web/` if your game folder is `mygame/`).
|
||||
You can modify and override all aspects of the web site from your game dir. You'll mostly be doing so in your settings file (`mygame/server/conf/settings.py` and in the gamedir's `web/folder` (`mygame/web/` if your game folder is `mygame/`).
|
||||
|
||||
> When testing your modifications, it's a good idea to add `DEBUG = True` to
|
||||
> your settings file. This will give you nice informative tracebacks directly
|
||||
|
|
@ -52,8 +39,7 @@ You'll mostly be doing so in your settings file
|
|||
> DEBUG mode leaks memory (for retaining debug info) and is *not* safe to use
|
||||
> for a production game!
|
||||
|
||||
As explained on the [Webserver](./Webserver.md) page, the process for getting a web
|
||||
page is
|
||||
As explained on the [Webserver](./Webserver.md) page, the process for getting a web page is
|
||||
|
||||
1. Web browser sends HTTP request to server with an URL
|
||||
2. `urls.py` uses regex to match that URL to a _view_ (a Python function or callable class).
|
||||
|
|
@ -65,8 +51,7 @@ page is
|
|||
the HTML page requires static resources are requested, the browser will
|
||||
fetch those separately before displaying it to the user.
|
||||
|
||||
If you look at the [evennia/web/](github:develop/evennia/web) directory you'll find the following
|
||||
structure (leaving out stuff not relevant to the website):
|
||||
If you look at the [evennia/web/](github:develop/evennia/web) directory you'll find the following structure (leaving out stuff not relevant to the website):
|
||||
|
||||
```
|
||||
evennia/web/
|
||||
|
|
@ -91,8 +76,7 @@ structure (leaving out stuff not relevant to the website):
|
|||
|
||||
```
|
||||
|
||||
The top-level `web/urls.py` file 'includes' the `web/website/urls.py` file -
|
||||
that way all the website-related url-handling is kept in the same place.
|
||||
The top-level `web/urls.py` file 'includes' the `web/website/urls.py` file - that way all the website-related url-handling is kept in the same place.
|
||||
|
||||
This is the layout of the `mygame/web/` folder relevant for the website:
|
||||
|
||||
|
|
@ -124,18 +108,11 @@ This is the layout of the `mygame/web/` folder relevant for the website:
|
|||
|
||||
```
|
||||
|
||||
As you can see, the `mygame/web/` folder is a copy of the `evennia/web/` folder
|
||||
structure except the `mygame` folders are mostly empty.
|
||||
As you can see, the `mygame/web/` folder is a copy of the `evennia/web/` folder structure except the `mygame` folders are mostly empty.
|
||||
|
||||
For static- and template-files, Evennia will _first_
|
||||
look in `mygame/static` and `mygame/templates` before going to the default
|
||||
locations in `evennia/web/`. So override these resources, you just need to put
|
||||
a file with the same name in the right spot under `mygame/web/` (and then
|
||||
reload the server). Easiest is often to copy the original over and modify it.
|
||||
For static- and template-files, Evennia will _first_ look in `mygame/static` and `mygame/templates` before going to the default locations in `evennia/web/`. So override these resources, you just need to put a file with the same name in the right spot under `mygame/web/` (and then reload the server). Easiest is often to copy the original over and modify it.
|
||||
|
||||
Overridden views (Python modules) also need an additional tweak to the
|
||||
`website/urls.py` file - you must make sure to repoint the url to the new
|
||||
version rather than it using the original.
|
||||
Overridden views (Python modules) also need an additional tweak to the `website/urls.py` file - you must make sure to repoint the url to the new version rather than it using the original.
|
||||
|
||||
## Examples of commom web changes
|
||||
|
||||
|
|
@ -178,53 +155,26 @@ documents - they contain a special templating language marked with `{% ... %}` a
|
|||
|
||||
Some important things to know:
|
||||
|
||||
- `{% extends "base.html" %}` - This is equivalent to a Python
|
||||
`from othermodule import *` statement, but for templates. It allows a given template
|
||||
to use everything from the imported (extended) template, but also to override anything
|
||||
it wants to change. This makes it easy to keep all pages looking the same and avoids
|
||||
a lot of boiler plate.
|
||||
- `{% block blockname %}...{% endblock %}` - Blocks are inheritable, named pieces of code
|
||||
that are modified in one place and then used elsewhere. This works a bit in reverse to
|
||||
normal inheritance, because it's commonly in such a way that `base.html` defines an empty
|
||||
block, let's say `contents`: `{% block contents %}{% endblock %}` but makes sure to put
|
||||
that _in the right place_, say in the main body, next to the sidebar etc. Then each page
|
||||
does `{% extends "base.html %"}` and makes their own `{% block contents} <actual content> {% endblock %}`.
|
||||
Their `contents` block will now override the empty one in `base.html` and appear in the right
|
||||
place in the document, without the extending template having to specifying everything else
|
||||
- `{% extends "base.html" %}` - This is equivalent to a Python `from othermodule import *` statement, but for templates. It allows a given template to use everything from the imported (extended) template, but also to override anything it wants to change. This makes it easy to keep all pages looking the same and avoids a lot of boiler plate.
|
||||
- `{% block blockname %}...{% endblock %}` - Blocks are inheritable, named pieces of code that are modified in one place and then used elsewhere. This works a bit in reverse to normal inheritance, because it's commonly in such a way that `base.html` defines an empty block, let's say `contents`: `{% block contents %}{% endblock %}` but makes sure to put that _in the right place_, say in the main body, next to the sidebar etc. Then each page does `{% extends "base.html %"}` and makes their own `{% block contents} <actual content> {% endblock %}`. Their `contents` block will now override the empty one in `base.html` and appear in the right place in the document, without the extending template having to specifying everything else
|
||||
around it!
|
||||
- `{{ ... }}` are 'slots' usually embedded inside HTML tags or content. They reference a
|
||||
_context_ (basically a dict) that the Python _view_ makes available to it.
|
||||
Keys on the context are accessed with dot-notation, so if you provide a
|
||||
context `{"stats": {"hp": 10, "mp": 5}}` to your template, you could access
|
||||
that as `{{ stats.hp }}` to display `10` at that location to display `10` at
|
||||
that location.
|
||||
- `{{ ... }}` are 'slots' usually embedded inside HTML tags or content. They reference a _context_ (basically a dict) that the Python _view_ makes available to it. Keys on the context are accessed with dot-notation, so if you provide a context `{"stats": {"hp": 10, "mp": 5}}` to your template, you could access that as `{{ stats.hp }}` to display `10` at that location to display `10` at that location.
|
||||
|
||||
This allows for template inheritance (making it easier to make all
|
||||
pages look the same without rewriting the same thing over and over)
|
||||
This allows for template inheritance (making it easier to make all pages look the same without rewriting the same thing over and over)
|
||||
|
||||
There's a lot more information to be found in the [Django template language documentation](https://docs.djangoproject.com/en/3.2/ref/templates/language/).
|
||||
|
||||
### Change webpage colors and styling
|
||||
|
||||
You can tweak the [CSS](https://en.wikipedia.org/wiki/Cascading_Style_Sheets) of the entire
|
||||
website. If you investigate the `evennia/web/templates/website/base.html` file you'll see that we
|
||||
use the [Bootstrap
|
||||
4](https://getbootstrap.com/docs/4.6/getting-started/introduction/) toolkit.
|
||||
You can tweak the [CSS](https://en.wikipedia.org/wiki/Cascading_Style_Sheets) of the entire website. If you investigate the `evennia/web/templates/website/base.html` file you'll see that we use the [Bootstrap 4](https://getbootstrap.com/docs/4.6/getting-started/introduction/) toolkit.
|
||||
|
||||
Much structural HTML functionality is actually coming from bootstrap, so you
|
||||
will often be able to just add bootstrap CSS classes to elements in the HTML
|
||||
file to get various effects like text-centering or similar.
|
||||
Much structural HTML functionality is actually coming from bootstrap, so you will often be able to just add bootstrap CSS classes to elements in the HTML file to get various effects like text-centering or similar.
|
||||
|
||||
The website's custom CSS is found in
|
||||
`evennia/web/static/website/css/website.css` but we also look for a (currently
|
||||
empty) `custom.css` in the same location. You can override either, but it may
|
||||
be easier to revert your changes if you only add things to `custom.css`.
|
||||
The website's custom CSS is found in `evennia/web/static/website/css/website.css` but we also look for a (currently empty) `custom.css` in the same location. You can override either, but it may be easier to revert your changes if you only add things to `custom.css`.
|
||||
|
||||
Copy the CSS file you want to modify to the corresponding location in `mygame/web`.
|
||||
Modify it and reload the server to see your changes.
|
||||
Copy the CSS file you want to modify to the corresponding location in `mygame/web`. Modify it and reload the server to see your changes.
|
||||
|
||||
You can also apply static files without reloading, but running this in the
|
||||
terminal:
|
||||
You can also apply static files without reloading, but running this in the terminal:
|
||||
|
||||
evennia collectstatic --no-input
|
||||
|
||||
|
|
@ -233,8 +183,7 @@ terminal:
|
|||
> Note that before you see new CSS files applied you may need to refresh your
|
||||
> browser without cache (Ctrl-F5 in Firefox, for example).
|
||||
|
||||
As an example, add/copy `custom.css` to `mygame/web/static/website/css/` and
|
||||
add the following:
|
||||
As an example, add/copy `custom.css` to `mygame/web/static/website/css/` and add the following:
|
||||
|
||||
|
||||
```css
|
||||
|
|
@ -258,8 +207,7 @@ Reload and your website now has a red theme!
|
|||
|
||||
### Change front page functionality
|
||||
|
||||
The logic is all in the view. To find where the index-page view is found, we
|
||||
look in `evennia/web/website/urls.py`. Here we find the following line:
|
||||
The logic is all in the view. To find where the index-page view is found, we look in `evennia/web/website/urls.py`. Here we find the following line:
|
||||
|
||||
```python
|
||||
# in evennia/web/website/urls.py
|
||||
|
|
@ -275,8 +223,7 @@ The first `""` is the empty url - root - what you get if you just enter `localho
|
|||
with no extra path. As expected, this leads to the index page. By looking at the imports
|
||||
we find the view is in in `evennia/web/website/views/index.py`.
|
||||
|
||||
Copy this file to the corresponding location in `mygame/web`. Then tweak your `mygame/web/website/urls.py`
|
||||
file to point to the new file:
|
||||
Copy this file to the corresponding location in `mygame/web`. Then tweak your `mygame/web/website/urls.py` file to point to the new file:
|
||||
|
||||
```python
|
||||
# in mygame/web/website/urls.py
|
||||
|
|
@ -293,45 +240,27 @@ urlpatterns = [
|
|||
|
||||
```
|
||||
|
||||
So we just import `index` from the new location and point to it. After a reload
|
||||
the front page will now redirect to use your copy rather than the original.
|
||||
So we just import `index` from the new location and point to it. After a reload the front page will now redirect to use your copy rather than the original.
|
||||
|
||||
The frontpage view is a class `EvenniaIndexView`. This is a [Django class-based view](https://docs.djangoproject.com/en/3.2/topics/class-based-views/).
|
||||
It's a little less visible what happens in a class-based view than in a function (since
|
||||
the class implements a lot of functionality as methods), but it's powerful and
|
||||
much easier to extend/modify.
|
||||
The frontpage view is a class `EvenniaIndexView`. This is a [Django class-based view](https://docs.djangoproject.com/en/3.2/topics/class-based-views/). It's a little less visible what happens in a class-based view than in a function (since the class implements a lot of functionality as methods), but it's powerful and much easier to extend/modify.
|
||||
|
||||
The class property `template_name` sets the location of the template used under
|
||||
the `templates/` folder. So `website/index.html` points to
|
||||
`web/templates/website/index.html` (as we already explored above.
|
||||
The class property `template_name` sets the location of the template used under the `templates/` folder. So `website/index.html` points to `web/templates/website/index.html` (as we already explored above.
|
||||
|
||||
The `get_context_data` is a convenient method for providing the context for the
|
||||
template. In the index-page's case we want the game stats (number of recent
|
||||
players etc). These are then made available to use in `{{ ... }}` slots in the
|
||||
template as described in the previous section.
|
||||
The `get_context_data` is a convenient method for providing the context for the template. In the index-page's case we want the game stats (number of recent players etc). These are then made available to use in `{{ ... }}` slots in the template as described in the previous section.
|
||||
|
||||
### Change other website pages
|
||||
|
||||
The other sub pages are handled in the same way - copy the template or static
|
||||
resource to the right place, or copy the view and repoint your `website/urls.py` to
|
||||
your copy. Just remember to reload.
|
||||
The other sub pages are handled in the same way - copy the template or static resource to the right place, or copy the view and repoint your `website/urls.py` to your copy. Just remember to reload.
|
||||
|
||||
## Adding a new web page
|
||||
|
||||
### Using Flat Pages
|
||||
|
||||
The absolutely simplest way to add a new web page is to use the `Flat Pages`
|
||||
app available in the [Web Admin](./Web-Admin.md). The page will appear with the same
|
||||
styling as the rest of the site.
|
||||
The absolutely simplest way to add a new web page is to use the `Flat Pages` app available in the [Web Admin](./Web-Admin.md). The page will appear with the same styling as the rest of the site.
|
||||
|
||||
For the `Flat pages` module to work you must first set up a _Site_ (or
|
||||
domain) to use. You only need to this once.
|
||||
For the `Flat pages` module to work you must first set up a _Site_ (or domain) to use. You only need to this once.
|
||||
|
||||
- Go to the Web admin and select `Sites`. If your
|
||||
game is at `mygreatgame.com`, that's the domain you need to add. For local
|
||||
experimentation, add the domain `localhost:4001`. Note the `id` of the domain
|
||||
(look at the url when you click on the new domain, if it's for example
|
||||
`http://localhost:4001/admin/sites/site/2/change/`, then the id is `2`).
|
||||
- Go to the Web admin and select `Sites`. If your game is at `mygreatgame.com`, that's the domain you need to add. For local experimentation, add the domain `localhost:4001`. Note the `id` of the domain (look at the url when you click on the new domain, if it's for example `http://localhost:4001/admin/sites/site/2/change/`, then the id is `2`).
|
||||
- Now add the line `SITE_ID = <id>` to your settings file.
|
||||
|
||||
Next you create new pages easily.
|
||||
|
|
@ -348,17 +277,13 @@ You can now go to `localhost:4001/test/` and see your new page!
|
|||
|
||||
### Add Custom new page
|
||||
|
||||
The `Flat Pages` page doesn't allow for (much) dynamic content and customization. For
|
||||
this you need to add the needed components yourself.
|
||||
The `Flat Pages` page doesn't allow for (much) dynamic content and customization. For this you need to add the needed components yourself.
|
||||
|
||||
Let's see how to make a `/test/` page from scratch.
|
||||
|
||||
- Add a new `test.html` file under `mygame/web/templates/website/`. Easiest is to base
|
||||
this off an existing file. Make sure to `{% extend base.html %}` if you want to
|
||||
get the same styling as the rest of your site.
|
||||
- Add a new `test.html` file under `mygame/web/templates/website/`. Easiest is to base this off an existing file. Make sure to `{% extend base.html %}` if you want to get the same styling as the rest of your site.
|
||||
- Add a new view `testview.py` under `mygame/web/website/views/` (don't name it `test.py` or
|
||||
Django/Evennia will think it contains unit tests). Add a view there to process
|
||||
your page. This is a minimal view to start from (read much more [in the Django docs](https://docs.djangoproject.com/en/3.2/topics/class-based-views/)):
|
||||
Django/Evennia will think it contains unit tests). Add a view there to process your page. This is a minimal view to start from (read much more [in the Django docs](https://docs.djangoproject.com/en/3.2/topics/class-based-views/)):
|
||||
|
||||
```python
|
||||
# mygame/web/website/views/testview.py
|
||||
|
|
@ -416,5 +341,4 @@ The form is then linked into the view-class by adding `form_class = MyFormClass`
|
|||
the view (next to `template_name`).
|
||||
|
||||
There are several example forms in `evennia/web/website/forms.py`. It's also a good
|
||||
idea to read [Building a form in Django](https://docs.djangoproject.com/en/3.2/topics/forms/#building-a-form-in-django)
|
||||
on the Django website - it covers all you need.
|
||||
idea to read [Building a form in Django](https://docs.djangoproject.com/en/3.2/topics/forms/#building-a-form-in-django) on the Django website - it covers all you need.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue