[Project] Initial Commit

This commit is contained in:
Robert von Burg 2021-07-11 20:36:03 +02:00
commit d2dd19801a
282 changed files with 15858 additions and 0 deletions

33
.github/workflows/publish-site.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: hugo publish
on:
push:
branches:
- develop
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build-deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.80.0'
- name: Build
run: hugo --minify
- name: Commit the generated website and push to git repository
run: |
git config --global user.email "dev@strolch.li"
git config --global user.name "Strolch GitHub Actions Build"
git status
git add publish/.
git commit -am "[AUTO] New version of website"
git push origin HEAD:develop

48
.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
# Build files
out
target
gen
bin
# IntelliJ files
.idea
*.iml
# Eclipse files
.settings
.classpath
.project
.externalToolBuilders
.metadata
maven-eclipse.xml
# Vim files
*.swp
# Mac files
.DS_Store
# Windows files
Thumbs.db
# Package Files #
*.jar
*.war
*.ear
*.zip
/nbactions.xml
# PlatformIO specific IDE files
.pioenvs
.piolibdeps
.pio
# Microsoft Visual Studio Code IDE files
.vscode
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
# Netbeans IDE files
/nbproject/private/
/nbproject/

1
CNAME Normal file
View File

@ -0,0 +1 @@
www.strolch.li

66
README.md Normal file
View File

@ -0,0 +1,66 @@
# Strolch website
## GoHugo
This website is created with the static site generator [Hugo](https://gohugo.io/).
### Theme
Uses the [Hugo-theme-learn](https://learn.netlify.app/en/).
Special layout components are explained on [learn.netlify.app/en/shortcodes](https://learn.netlify.app/en/shortcodes/notice/).
### Additional tools
#### Gallery image view
https://www.liwen.id.au/heg/#gallery-usage
https://github.com/liwenyip/hugo-easy-gallery/
Example use:
```
{{< gallery >}}
{{< figure link="/assets/... .png" caption="" caption-position="center" caption-effect="fade" >}}
{{< figure link="/assets/... .png" caption="" caption-position="center" caption-effect="fade" >}}
{{< figure link="/assets/... .png" caption="" caption-position="center" caption-effect="fade" >}}
{{< /gallery >}}
{{< load-photoswipe >}}
```
The last line only must be added once per page if multiple galleries are used.
### Text format
All pages are separate md-files inside the [content](content/) directory. The formatting
of the text needs to use the rules specified in [commonmark.org](https://spec.commonmark.org/0.29/).
### Test locally
To test the site locally, first install Hugo as described on ["Install Hugo"](https://gohugo.io/getting-started/installing/).
Example, for Mac:
```
brew install hugo
```
To run the site, open the terminal in the directory with the sources of this site and run the following command:
```
cd strolch.li
hugo serve
```
The website is now available on [localhost:1313](http://localhost:1313/).
### Build and run on GitHub Pages
Using a GitHub Action, the site is published on each commit into the main branch of this repository.
***The "docs" directory is auto-generated by this GitHub Action so should never be manually touched. This directory is pushed to GitHub Pages.***
This process is described here:
* [Automate your GitHub Pages Deployment using Hugo and Actions](https://medium.com/@asishrs/automate-your-github-pages-deployment-using-hugo-and-actions-518b959a51f9)
* [Deploy Hugo project to GitHub Pages with GitHub Actions](https://discourse.gohugo.io/t/deploy-hugo-project-to-github-pages-with-github-actions/20725)

110
config.toml Normal file
View File

@ -0,0 +1,110 @@
baseURL = "https://strolch.li/"
languageCode = "en-us"
title = "Strolch"
theme = "hugo-theme-learn"
publishDir = "publish"
refLinksErrorLevel = "WARNING"
[markup.goldmark.renderer]
unsafe = true
# For search functionality
[outputs]
home = ["HTML", "RSS", "JSON"]
[params]
# Prefix URL to edit current page. Will display an "Edit this page" button on top right hand corner of every page.
# Useful to give opportunity to people to create merge request for your doc.
# See the config.toml file from this documentation site to have an example.
editURL = "https://github.com/strolch-li/strolch/tree/develop/li.strolch.website"
# Author of the site, will be used in meta information
author = "Strolch"
# Description of the site, will be used in meta information
description = "Strolch is a parameterized framework for use on servers and IoT "
# Shows a checkmark for visited pages on the menu
showVisitedLinks = false
# Disable search function. It will hide search bar
disableSearch = false
# Javascript and CSS cache are automatically busted when new version of site is generated.
# Set this to true to disable this behavior (some proxies don't handle well this optimization)
disableAssetsBusting = false
# Set this to true to disable copy-to-clipboard button for inline code.
disableInlineCopyToClipBoard = false
# A title for shortcuts in menu is set by default. Set this to true to disable it.
disableShortcutsTitle = false
# If set to false, a Home button will appear below the search bar on the menu.
# It is redirecting to the landing page of the current language if specified. (Default is "/")
disableLandingPageButton = false
# When using mulitlingual website, disable the switch language button.
disableLanguageSwitchingButton = false
# Hide breadcrumbs in the header and only show the current page title
disableBreadcrumb = false
# If set to true, prevents Hugo from including the mermaid module if not needed (will reduce load times and traffic)
disableMermaid = false
# Specifies the remote location of the mermaid js
customMermaidURL = "https://unpkg.com/mermaid@8.8.0/dist/mermaid.min.js"
# Hide Next and Previous page buttons normally displayed full height beside content
disableNextPrev = false
# Order sections in menu by "weight" or "title". Default to "weight"
ordersectionsby = "weight"
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = "green"
# Provide a list of custom css files to load relative from the `static/` folder in the site root.
# custom_css = ["css/foo.css", "css/bar.css"]
# Change the title separator. Default to "::".
titleSeparator = " - "
[imaging]
# Default resample filter used for resizing. Default is Box,
# a simple and fast averaging filter appropriate for downscaling.
# See https://github.com/disintegration/imaging
resampleFilter = "box"
# Default JPEG quality setting. Default is 75.
quality = 75
# Anchor used when cropping pictures.
# Default is "smart" which does Smart Cropping, using https://github.com/muesli/smartcrop
# Smart Cropping is content aware and tries to find the best crop for each image.
# Valid values are Smart, Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight
anchor = "smart"
# Default background color.
# Hugo will preserve transparency for target formats that supports it,
# but will fall back to this color for JPEG.
# Expects a standard HEX color string with 3 or 6 digits.
# See https://www.google.com/search?q=color+picker
bgColor = "#ffffff"
[imaging.exif]
# Regexp matching the fields you want to Exclude from the (massive) set of Exif info
# available. As we cache this info to disk, this is for performance and
# disk space reasons more than anything.
# If you want it all, put ".*" in this config setting.
# Note that if neither this or ExcludeFields is set, Hugo will return a small
# default set.
includeFields = ""
# Regexp matching the Exif fields you want to exclude. This may be easier to use
# than IncludeFields above, depending on what you want.
excludeFields = ""
# Hugo extracts the "photo taken" date/time into .Date by default.
# Set this to true to turn it off.
disableDate = false
# Hugo extracts the "photo taken where" (GPS latitude and longitude) into
# .Long and .Lat. Set this to true to turn it off.
disableLatLong = false
[[menu.shortcuts]]
name = "<i class='fas fa-tags'></i> Tags"
url = "/tags"
weight = 5
[[menu.shortcuts]]
name = "<i class='fab fa-github'></i> GitHub project"
identifier = "github"
url = "https://github.com/strolch-li"
weight = 10

82
content/_index.md Normal file
View File

@ -0,0 +1,82 @@
---
title: Strolch Overview
---
## Strolch Overview
Strolch is framework for developing Software. It's main features are:
* Complete persisted data model:
* Parameters and values by time
* Resources, Orders with arbitrary parameter grouping
* Activity/Action hierarchy with arbitrary depth
* Policies for delegation
* JSON as well as XML transformation
* Locator API
* Transactions with pessimistic locking and optional read-locking
* Search API
* Component based
* Deeply integrated privilege handling
* Fully in-memory
* Persisted auditing, versioning, operations log
* DAOs for file system or PostgreSQL, easily extended
* Execution framework
* Service / Command oriented
* Reporting API configured by Resource objects
* REST API for data access
* WebComponents UI for
* Inspector
* Users
* Roles
* Operations Log
* Login Screen
* Jobs
* runs on plain old Java SE
## Strolch Intro
It is a different framework to Spring and other similar type of Java
frameworks, as the model is defined as an abstract model, where you
always have the same three types of objects: Resources, Orders and
Activities. The fields are mapped as Parameter objects, of which the
important primitives are available.
The nice part about this framework is, that you can be up and ready in
a matter of minutes, and start building your project immediately in
that you open your favourite XML editor and start modelling your data.
Once your data is defined, you write your business logic in the form
of Services, Commands and Searches. There are many predefined services
and commands to manipulate the object model, so that you write your own
services when you need to enforce special business rules.
Through the use of Policy objects, you decouple algorithms from your
object model, so that at runtime you can change the behaviour, or
easily implement different behaviour depending on your use-case. For
instance you might have a simple billing service which performs a few
preparatory steps, and then calls the configured billing policy to
execute the billing depending on the customer, the warehouse, etc.
And of course persistence is as simple as configuring the persistence
handler, pointing to your RDBMS and then setting the mode to CACHED.
For you as a developer there is no more thinking in terms of SQL etc.,
as this is completely hidden from the developer. There is even a simple
file persistence layer if you are running IoT devices.
The runtime can be just about anything. Usually it is run inside an
Apache Tomcat instance as a webapp, as a WEB UI has been required for
all current Strolch projects. You could just as well use a main class.
Accessing the Strolch Agent remotely is usually done through REST.
Strolch is being actively developed, and customers constantly give us
reasons to improve and extend the framework. There is a Polymer
Inspector component which makes it easy to see and manipulate the
actual data. The new Search API makes it really easy to query your data.
Yes, Strolch is different, but the concept has come out of the planning
and execution segment, and has been refined over the years until it has
become what it is today.
## API
Check out the API page to see how to use Strolch.
[**More to motivation etc**](/history/).

184
content/api/_index.md Normal file
View File

@ -0,0 +1,184 @@
---
title: 'API'
weight: 10
---
## Overview
The Strolch API revolves around the StrolchTransaction object. The main
concept is to implement your use cases in Service implementations. You
open a transaction using the openTx(String)-method and then perform the
use case by adding your Command instances to the transaction.
Transactions are opened on a StrolchRealm. The realms are used to
separate mandates in a single runtime instance of Strolch. Each realm
has its own ResourceMap, OrderMap, ActivityMap instances from which the
TX retrieves the elements.
## Model
The Strolch model is implemented in the project li.strolch.model.
The Strolch model consists of three root level elements: Resource,
Order and Activity. Each element has at least the following attributes:
* Id → the element's id
* Name → the element's name
* Type → the element's type
Each root element can have any number of ParameterBag instances on it,
which in turn can have any number of Parameters on it. Accessing these
objects is always done by their IDs. Strolch root elements are always
stored in the respective ElementMaps in their Strolch realm. Thus
accessing a certain parameter from a Resource would look like this:
```java
try (StrolchTransaction tx = openTx(realmName)) {
Resource resource = tx.getResourceBy("TestType", "MyTestResource");
Date date = resource.getDate("@bag01", "@param6");
logger.info("@param6 date is " + date);
}
```
XML Presentation of Strolch's top level elements of Resources:
```xml
<!-- Resource instance -->
<Resource Id="MyTestResource" Name="Test Name" Type="TestType">
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param7" Name="StringList Param" Type="StringList" Value="Hello;World" />
<Parameter Id="@param6" Name="Date Param" Type="Date" Value="2012-11-30T18:12:05.628+01:00" />
<Parameter Id="@param5" Name="String Param" Type="String" Value="Strolch" />
</ParameterBag>
<ParameterBag Id="@bag02" Name="Test Bag" Type="TestBag">
<Parameter Id="@param4" Name="Long Param" Type="Long" Value="4453234566" />
<Parameter Id="@param3" Name="Integer Param" Type="Integer" Value="77" />
<Parameter Id="@param2" Name="Float Param" Type="Float" Value="44.3" />
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true" />
</ParameterBag>
<TimedState Id="@integerState" Name="Integer State" Type="IntegerState">
<Value Time="0" Value="1" />
<Value Time="1" Value="2" />
<Value Time="2" Value="3" />
</TimedState>
</Resource>
```
XML Presentation of Strolch's top level elements of Orders:
```xml
<!-- Order instance -->
<Order Id="MyTestOrder" Name="Test Name" Type="TestType" Date="2013-11-20T07:42:57.699Z" State="CREATED">
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param7" Name="StringList Param" Type="StringList" Value="Hello;World" />
<Parameter Id="@param6" Name="Date Param" Type="Date" Value="2012-11-30T18:12:05.628+01:00" />
<Parameter Id="@param5" Name="String Param" Type="String" Value="Strolch" />
</ParameterBag>
<ParameterBag Id="@bag02" Name="Test Bag" Type="TestBag">
<Parameter Id="@param4" Name="Long Param" Type="Long" Value="4453234566" />
<Parameter Id="@param3" Name="Integer Param" Type="Integer" Value="77" />
<Parameter Id="@param2" Name="Float Param" Type="Float" Value="44.3" />
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true" />
</ParameterBag>
</Order>
```
XML Presentation of Strolch's top level elements of Activities:
```xml
<!-- Activity instance -->
<Activity Id="bicycleProduction" Name="Bicycle Production" Type="Series">
<Activity Id="componentProduction" Name="Production of components" Type="Series">
<Action Id="consumeGears" Name="Gears"
ResourceId="gears" ResourceType="Article" Type="Consume">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT0S" />
</ParameterBag>
</Action>
<Activity Id="frameProduction" Name="Production frame" Type="Series">
<Action Id="produce" Name="Production frame"
ResourceId="frameProduction" ResourceType="Machine" Type="Use">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
</ParameterBag>
</Action>
<Action Id="toStock" Name="Frame ToStock"
ResourceId="frame" ResourceType="Article" Type="Produce">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
</ParameterBag>
</Action>
</Activity>
<Activity Id="brakeProduction" Type="Series" Name="Herstellen Bremsen" TimeOrdering="Series">
<Action Id="produce" Name="Production saddle"
ResourceId="saddleProduction" ResourceType="Machine" Type="Use">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
</ParameterBag>
</Action>
<Action Id="toStock" Name="Saddle ToStock"
ResourceId="frame" ResourceType="Article" Type="Produce">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
</ParameterBag>
</Action>
</Activity>
</Activity>
<Action Id="assembly" Name="Bicycle assemble"
ResourceId="bicycleAssembly" ResourceType="Assembly" Type="Use">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT5M" />
</ParameterBag>
</Action>
<Action Id="toStock" Name="Bicycle to stock"
ResourceId="bicycle" ResourceType="Product" Type="Produce">
<ParameterBag Id="objectives" Name="Production goals" Type="Objectives">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="1" />
<Parameter Id="duration" Name="Duration" Type="Duration" Value="PT1M" />
</ParameterBag>
</Action>
</Activity>
```
## Realms
Strolch realms implement the multi-client capability which is thus baked right
into the Strolch runtime. When configuring a Strolch runtime, realms are
configured and for each realm the data store mode is set. Each realm has its
own persistence configuration and can thus run in one of the 4 modes that the
Strolch agent implements:
* EMPTY
This is a transient data store mode, where no model changes are persisted,
but they are only kept in memory. When the Strolch agent is started, this
realm stays empty as no data is loaded.
* TRANSIENT
This is the same as EMPTY, but with the difference that when the Strolch
agent is started, an XML file is parsed and the in memory realm is populated
with the elements parsed from that XML file.
* CACHED
In this mode, all data is stored in memory, and any changes made are written
back to the persistence layer. This allows for fast in-memory quries, but
makes sure no data is lost when the agent is restarted.
Strolch Realms are also responsible for opening Transactions, as these are
bound to the persistence layer configured for this realm. At runtime, a realm
is then accessed from the ComponentContainer:
```java
ComponentContainer container = getAgent().getContainer();
StrolchRealm realm = container.getRealm(StrolchConstants.DEFAULT_REALM);
try(StrolchTransaction tx = realm.openTx()) {
Resource resource = tx.getResourceBy("TestType", "MyTestResource");
...
}
```

View File

@ -0,0 +1,127 @@
---
title: 'Development'
weight: 90
---
## Prerequisites
To start developing Strolch you need an installed:
* Java JDK 11
* Apache Maven 3.x
## Building Strolch
Setting up Strolch is just a few lines:
```shell
git clone https://github.com/4treesCH/strolch.git
cd strolch
mvn clean install -DskipTests
```
{{% notice tip %}}
Note: To run the tests you will need to configure the PostgreSQL Databases. See
the README in the module.
{{% /notice %}}
After running the Maven build, you will have a full build of all Strolch
projects. Now you can start modifying the projects, and add your own features,
or, far more interesting, start developing your projects using the Strolch
agent.
## Creating a Strolch App
To create your own Strolch App, you can use Maven's archetype generation. There
are two versions, one is a simple Java App which you can use to directly access
the Strolch runtime, and the second is to create a Java Web App, which is the
usual way to run Strolch runtimes.
{{% notice tip %}}
Note: you need to have installed Strolch to your local maven repo, otherwise the
archetype won't be available.
{{% /notice %}}
### Creating a Java Strolch Web App
The following shows the maven command to create the new maven project. Note that you should replace the placeholders in the brackets:
```shell
mvn archetype:generate \
-DarchetypeGroupId=li.strolch \
-DarchetypeArtifactId=li.strolch.mvn.archetype.webapp \
-DarchetypeVersion=1.6.0-SNAPSHOT \
-DgroupId=<my.groupid> \
-DartifactId=<my-artifactId> \
-Dversion=<my.version> \
-DappName="<my app name>"
```
#### Install the web dependencies
The Strolch Web App uses [NodeJS v11.x](https://nodejs.org/download/release/v11.15.0/) to build the web dependencies. Please
download the relevant platform's package, unpack it, and add the `bin` directory
to your path variable.
Once NodeJS is installed, then you can prepare the web dependencies:
```shell
cd src/main/webapp/
npm install gulp -g
npm install
gulp
```
{{% notice tip %}} Note: Whenever the bower.json is changed then you should
again call npm install inside the webapp folder. {{% /notice %}}
#### Building the WAR
Building the WAR uses the `package` maven goal, but to have the optimized WAR
use the `release` profile:
```shell
mvn clean package -Prelease
```
Happy coding =))
### Creating a simple Java Strolch App
The following shows the maven command to create the new maven project. Note that
you should replace the placeholders in the brackets:
```shell
mvn archetype:generate \
-DarchetypeGroupId=li.strolch \
-DarchetypeArtifactId=li.strolch.mvn.archetype.main \
-DarchetypeVersion=1.6.0-SNAPSHOT \
-DgroupId=<my.groupid> \
-DartifactId=<my-artifactId> \
-Dversion=<my.version> \
-DappName="<my app name>"
```
You change into the directory of the new project and then build the project by calling:
```shell
cd <my-artifactId>
mvn clean package
```
Start the program using:
```shell
mvn exec:java
```
Happy coding =))
## Tools used
The following tools are used to develop Strolch and Strolch-based projects:
* [IntelliJ](https://www.jetbrains.com/idea/download/#section=linux "{target='_blank'}")
* [Apache Maven](https://maven.apache.org/ "{target='_blank'}")
* [Git SCM](http://git-scm.com/ "{target='_blank'}")

View File

@ -0,0 +1,14 @@
---
title: 'Documentation'
weight: 20
---
## Overview
Strolch's documentation has only just begun, but as more and more details of
the implementation in Strolch are fixed, more documentation can be created and
will be available here.
Currently we have the following topics of discussion:
{{% children %}}

View File

@ -0,0 +1,56 @@
---
title: 'Architecture'
weight: 10
---
## Architecture
### Birds View
A Strolch agent's architecture can be seen as a simple three-tier architecture.
The presentation layer is mostly a web frontend, where the communication with
the agent is done via REST API calls.
The agent itself implements the business logic using Services, Commands,
Queries etc.
The agent can communicate with other 3rd systems using any API, where it is
preferred to use JSON over REST.
The agent can use a standard RDBMS as a storage system, where currently DAOs
have been implemented only for PostgreSQL. Should it be required, then any
JDBC cabable RDBMS can be used, as no PostgreSQL specific SQL commands are used.
The following diagram helps visualize this:
![Strolch Architecture Birds View](/assets/images/Strolch-Bird-View.svg)
### Squirrel View
The following diagram shows a more detailed view of the architecture of a
Strolch Agent:
![Strolch Architecture Squirrel View](/assets/images/Strolch-Squirrel-View.svg)
A Strolch agent consists of the following main parts:
* REST Endpoints → expose an API to access the Strolch agent outside of the
Java VM
* Services and Commands → implements business logic
* Searches → implements specific queries against the Strolch model
* Components → Implements additional logic, which is best implement as a
component. E.g. active components which have threads, etc.
* Realms → implements multi-tenant capabilities
In addition to the main parts, Strolch contains inherit capabilities, which
gives Strolch unique features when compared to other Java Frameworks:
* Policies → Policies allow injecting different algorithms. All root elements
can store Policy definitions, so that a service can delegate to a Policy and
thus behave differently, depending on the element being accessed.
* Transactions → Transactions handle locking/unlocking of objects, updating the
model and are the central API for the developer.
* Privileges → Strolch is user agnostic and any action, i.e. Service, Query,
etc. is authorized against the authenticated user.
* Observers → modifications to the model are propagated to listeners using the
observer pattern.
* Audits → Every access (read, modify) of the model are audited
* Versioning → modifications to objects are versioned and thus can be rolled
back at a later time.

View File

@ -0,0 +1,113 @@
---
title: 'Components'
weight: 60
---
## Components
A Strolch agent can be easily extended with arbitrary components. An agent
is basically a container for classes extending StrolchComponent with a life
cycle. These classes mostly implement an interface which describes the
operations that are supported by the component.
The following represents a list of the most used components:
* `RealmHandler`: li.strolch.agent.impl.DefaultRealmHandler
* `PrivilegeHandler`: li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler
* `EnumHandler`: li.strolch.runtime.query.enums.DefaultEnumHandler
* `PolicyHandler`: li.strolch.policy.DefaultPolicyHandler
* `ServiceHandler`: li.strolch.service.api.DefaultServiceHandler
* `StrolchSessionHandler`: li.strolch.rest.DefaultStrolchSessionHandler
* `PersistenceHandler`: multiple implementations
* `PostInitializer`: project specific implementation
* `MailHandler`: li.strolch.handler.mail.SmtpMailHandler
A component has a life-cycle, which is governed by the Agent's own life-cycle.
The life-cycle is as follows:
```
setup -> initialize -> start <-> stop -> destroy
```
The setup step is used to instantiate the component, the initialize step is
used to validate configuration parameters, and the run step is used to start
the component, i.e. start threads, etc. The stop step stops these threads and
also allows the component to be started again. The destroy step destroys the
instance and makes it unusable anymore, i.e. shutdown of the agent.
Each component has its own configuration parameters. A component is
registered in the `StrolchConfiguration.xml` file with a
* name
* api class name
* implementation class name
* configuration parameters
* any required dependencies
The dependencies is an important feature as the dependencies of a component
are always started before the actual component.
By example of the `MailHandler` we shall show how a strolch component would
be implemented.
First define an interface:
```java
public interface MailHandler {
void sendMail(String subject, String text, String recipient);
}
```
Then implement a concrete `MailHandler`:
```java
public class SmtpMailHandler extends StrolchComponent implements MailHandler {
// instance fields with configuration properties to send the mail
public SmtpMailHandler(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
public void initialize(ComponentConfiguration configuration) throws Exception {
// store any properties needed from the configuration
super.initialize(configuration);
}
@Override
public void sendMail(String subject, String text, String recipient) {
// send the e-mail using SMTP, or store in stack to send by thread
}
}
```
Now that the component is written, it must be registered on the component, so
that it is loaded when the agent is started. For this the
`StrolchConfiguration.xml` file must be modified to include a component
element:
```xml
<StrolchConfiguration>
<env id="dev">
...
<Component>
<name>MailHandler</name>
<api>li.strolch.handler.mail.MailHandler</api>
<impl>li.strolch.handler.mail.SmtpMailHandler</impl>
<Properties>
<username>test</username>
<password>test</password>
<hostName>localhost</hostName>
...
</Properties>
</Component>
...
</env>
</StrolchConfiguration>
```
Now when the agent is started, the component can be retrieved and used.
E.g from inside a Service:
```java
MailHandler mailHandler = getComponent(MailHandler.class);
mailHandler.sendMail("My Subject", "Hello World", "test@test.ch");
```

View File

@ -0,0 +1,18 @@
---
title: 'Do and Don''t'
weight: 30
---
## This page discusses things you should and shouldn't do when using Strolch
The following is a simple list of do's and don'ts:
* 1 `Service` per use-case, should mostly delegate to `Commands`.
* `Commands` implement use-cases or parts of it, and are thus reusable.
* Subclass `ResourceSearch`, `OrderSearch` and `ActivitySearch` when implementing use-case specific search - this allows privilege checking.
* One Transaction at a time - no TX inside of another TX.
* Commands are added to TXs and performed on close: `tx.addCommand(Command)` and then `tx.commitOnClose()`
* Use `tx.flush()` if you need to perform first some work and then as late as possible call `tx.commitOnClose()`
* Only access `ElementMaps` if really no other way, mostly use `tx.get*By()`, `tx.findBy()` and searches - if a specific get is missing, then add the method to `StrolchTransaction` and send a pull request!
* Use `tx.stream*()` methods to iterate over all elements, if you don't want to use a search.
* Don't write logic in REST API beans. Delegate to other services, making your code reusable!
* Transform to JSON using the `StrolchElementToJsonVisitor`.
* References between objects is done by adding a `ParameterBag` with the id `relations` to the object and then `StringParameters` with the value being the ID, the UOM set to the type of element being referenced and the Interpretation set to the class type being referenced.

View File

@ -0,0 +1,305 @@
---
title: 'Model'
weight: 20
---
## Model
Before we dive into the entire model, let's show an example and how it would
be modelled in Strolch and use in Strolch:
![Strolch model example](/assets/images/strolch-model-example.png)
A possible model would look as follows:
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<StrolchModel xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd">
<Resource Id="Product" Name="Product Template" Type="Template">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Value=""/>
<Parameter Id="color" Name="Color" Type="String" Value=""/>
<Parameter Id="form" Name="Form" Type="String" Value=""/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="articles" Name="Articles" Type="StringList" Interpretation="Resource-Ref" Uom="Article" Value=""/>
</ParameterBag>
</Resource>
<Resource Id="Article" Name="Article Template" Type="Template">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Value=""/>
<Parameter Id="barcode" Name="Barcode" Type="String" Value=""/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="product" Name="Product" Type="String" Interpretation="Resource-Ref" Uom="Product" Value=""/>
</ParameterBag>
</Resource>
<Resource Id="Customer" Name="Customer Template" Type="Template">
<ParameterBag Id="address" Name="Address" Type="Address">
<Parameter Id="street" Name="Street" Type="String" Value=""/>
<Parameter Id="zip" Name="Zip" Type="String" Value=""/>
<Parameter Id="city" Name="City" Type="String" Value=""/>
<Parameter Id="country" Name="Country" Type="String" Value=""/>
</ParameterBag>
</Resource>
<Order Id="Order" Name="Order" Type="Template">
<ParameterBag Id="quantities" Name="Quantities per Article Id" Type="Quantities">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="0"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="articles" Name="Articles" Type="StringList" Interpretation="Resource-Ref" Uom="Article" Value=""/>
<Parameter Id="customer" Name="Customer" Type="String" Interpretation="Resource-Ref" Uom="Customer" Value=""/>
</ParameterBag>
</Order>
</StrolchModel>
```
Let's go through this model:
* In the above model we see that the id and name fields are always on the
element, and thus aren't added as parameters. Further most elements have a
parameters ParameterBag, with one or more parameters, modelling the fields.
* Note that in this example the Type of all the elements is Template. Strolch
has API support to create a clone of these elements, so that they have a
unique ID, and the proper type for persistence.
* The Product element has three parameters: description, color and form. In
this case they are all of type String. Further the relations ParameterBag
defines the relationships, i.e. the product knows its articles. Note how
the relation is first defined in a relations ParameterBag and that the
Parameter has Interpretation="Resource-Ref" Uom="Product" attributes.
Strolch has API support for this, making it trivial to retrieve a dependency.
* The Article element has two parameters description and barcode. Further it
has a reference to its Product.
* The Order element doesn't model the date and state fields as parameters, as
these are inherently part of an Order element. The Order does define two
references to customer and articles. A special case is the quantities
ParameterBag. This bag of parameters is used to store the per article
quantity for this order. With ParameterBags, you can eliminate the use of
simple aggregate classes, as is commonly used in object-oriented programming.
* The Customer element models a address ParameterBag to store the address of
a customer. Using a separate bag allows for further more direct fields to
stored on the default parameters bag.
Now that we have a basic understanding of te model, it is of far more interest
in how to create and interact with these elements at runtime. The following
listing will perform simple operations:
```java
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, false)) {
/*
* create a new product
*/
Resource dafalgan = tx.getResourceTemplate("Product", true);
dafalgan.setName("Dafalgan 100mg");
dafalgan.getParameter("description", true).setValue("Dafalgan is for pain.");
dafalgan.getParameter("color", true).setValue("Yellow");
dafalgan.getParameter("form", true).setValue("flat");
StringListParameter articlesP = dafalgan.getRelationsParam("articles", true);
/*
* create articles
*/
Resource dafalgan1 = tx.getResourceTemplate("Article", true);
dafalgan1.setName("Dafalgan 100mg 10 pce");
dafalgan1.getParameter("description", true).setValue("This is pack with 10 pieces.");
dafalgan1.getParameter("barcode", true).setValue("654654");
Resource dafalgan2 = tx.getResourceTemplate("Article", true);
dafalgan2.setName("Dafalgan 100mg 20 pce");
dafalgan2.getParameter("description", true).setValue("This is pack with 20 pieces.");
dafalgan2.getParameter("barcode", true).setValue("654655");
/*
* add reference to product
*/
dafalgan1.getRelationParam("product").setValue(dafalgan.getId());
articlesP.addValue(dafalgan1.getId());
dafalgan2.getRelationParam("product").setValue(dafalgan.getId());
articlesP.addValue(dafalgan2.getId());
/*
* create a new customer
*/
Resource customer1 = tx.getResourceTemplate("Customer", true);
customer1.setName("John Doe");
// set address
ParameterBag addressBag = customer1.getParameterBag("address", true);
addressBag.getParameter("street", true).setValue("Main Str. 1");
addressBag.getParameter("zip", true).setValue("1234");
addressBag.getParameter("city", true).setValue("Hometown");
addressBag.getParameter("country", true).setValue("Switzerland");
/*
* create a new order
*/
Order order = tx.getOrderTemplate("Order", true);
order.setName("Order for " + customer1.getName());
order.setDate(LocalDate.of(2021, 2, 1));
order.setState(State.PLANNED);
// store reference to customer
order.getRelationParam("customer", true).setValue(customer1.getId());
StringListParameter orderArticlesP = order.getRelationsParam("articles", true);
ParameterBag quantitiesBag = order.getParameterBag("quantities", true);
FloatParameter quantityT = quantitiesBag.removeParameter("quantity");
// order quantity of 20 for Dafalgan 1
FloatParameter q1P = quantityT.getClone();
q1P.setId(dafalgan1.getId());
q1P.setValue(20);
quantitiesBag.addParameter(q1P);
orderArticlesP.addValue(dafalgan1.getId());
// set order quantity of 10 for Dafalgan 2
FloatParameter q2P = quantityT.getClone();
orderArticlesP.addValue(dafalgan2.getId());
q2P.setId(dafalgan2.getId());
q2P.setValue(20);
quantitiesBag.addParameter(q2P);
// keep IDs for later use
dafalganId = dafalgan.getId();
dafalgan1Id = dafalgan1.getId();
dafalgan2Id = dafalgan2.getId();
customerId = customer1.getId();
orderId = order.getId();
/*
* persist
*/
tx.add(dafalgan);
tx.add(dafalgan1);
tx.add(dafalgan2);
tx.add(customer1);
tx.add(order);
// commit
tx.commitOnClose();
}
```
```java
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true)) {
// get order
Order order = tx.getOrderBy("Order", orderId, true);
assertNotNull(orderId);
assertEquals("Order for John Doe", order.getName());
// get customer
Resource customer = tx.getResourceByRelation(order, "customer", true);
assertNotNull(customer);
assertEquals("John Doe", customer.getName());
// get articles
List<Resource> articles = tx.getResourcesByRelation(order, "articles", true);
assertEquals(2, articles.size());
// get products
List<Resource> products = articles.stream().map(a -> tx.getResourceByRelation(a, "product", true))
.distinct().collect(Collectors.toList());
assertEquals(1, products.size());
// search for all orders in state PLANNED and with customer
List<Order> orders = new OrderSearch().types("Order").stateIsIn(State.PLANNED)
.where(ExpressionsSupport.relationParam("customer").isEqualTo(customerId)).search(tx).toList();
assertEquals(1, orders.size());
}
```
{{% notice tip %}}
Note: Checkout [example-model.xml](https://github.com/4treesCH/strolch/blob/develop/li.strolch.service/src/test/resources/transienttest/data/example-model.xml "{target='_blank'}") and [SimpleModelTest.java](https://github.com/4treesCH/strolch/blob/develop/li.strolch.service/src/test/java/li/strolch/service/SimpleModelTest.java "{target='_blank'}") for these examples.
{{% /notice %}}
{{% notice tip %}}
There is an XML Schema which defines the model in XML: [StrolchModel-1.6.xsd](/assets/xsd/StrolchModel-1.6.xsd "{target='_blank'}")
{{% /notice %}}
Here is an example of all the possible elements in Strolch:
```xml
<StrolchModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd"
xsi:schemaLocation="https://strolch.li/xsd/StrolchModel-1.6.xsd StrolchModel-1.6.xsd">
<IncludeFile file="Include1.xml"/>
<Order Id="@test1" Name="Test Order" Type="Order">
<Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
<Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
</Policies>
</Order>
<Resource Id="MyTestResource" Name="Test Name" Type="TestType">
<Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
<ParameterBag Id="@bag01" Name="Test Bag 01" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<ParameterBag Id="@bag02" Name="Test Bag 02" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<TimedState Id="@booleanState" Name="Boolean State" Type="Boolean">
<Value Time="1970-01-01T00:02:00.000+01:00" Value="false"/>
</TimedState>
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
<Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
</Policies>
</Resource>
<Activity Id="activity_1" Name="Activity" Type="parentType" TimeOrdering="Series">
<Version Version="0" CreatedBy="test" CreatedAt="2012-11-30T18:12:05.628+01:00" Deleted="false"/>
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
<Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
</Policies>
<Action Id="action_1" Name="Action 1" ResourceId="dummyId" ResourceType="dummyType" State="Created" Type="Use">
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
<Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
</Policies>
<ValueChange StateId="dummyId" Time="2012-11-30T18:12:05.628+01:00" Value="5" Type="Integer"/>
<ValueChange StateId="dummyId" Time="2012-11-30T18:12:06.628+01:00" Value="6" Type="Integer"/>
</Action>
<Activity Id="child_activity" Name="Child Activity" Type="childType" TimeOrdering="Series">
<ParameterBag Id="@bag01" Name="Test Bag" Type="TestBag">
<Parameter Id="@param1" Name="Boolean Param" Type="Boolean" Value="true"/>
</ParameterBag>
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning"/>
<Policy Type="ConfirmationPolicy" Value="key:NoConfirmation"/>
</Policies>
<Action Id="action_2" Name="Action 2" ResourceId="dummyId" ResourceType="dummyType" State="Planned"
Type="Use">
<ValueChange StateId="dummyId" Time="2012-11-30T18:12:05.628+01:00" Value="5" Type="Integer"/>
<ValueChange StateId="dummyId" Time="2012-11-30T18:12:06.628+01:00" Value="6" Type="Integer"/>
</Action>
<Action Id="action_3" Name="Action 3" ResourceId="dummyId" ResourceType="dummyType" State="Created"
Type="Use"/>
</Activity>
</Activity>
</StrolchModel>
```

View File

@ -0,0 +1,62 @@
---
title: 'Observers'
weight: 110
---
## Observers
All changes done in a Strolch transaction are recorded and then propagated to
any registered observers.
The observer feature is opt-in and is configured for each realm. In the
`StrolchConfiguration.xml` file enable observers by adding the
`enableObserverUpdates` property per realm:
```xml
<StrolchConfiguration>
<env id="dev">
...
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<realms>defaultRealm, otherRealm</realms>
<enableObserverUpdates>true</enableObserverUpdates>
<dataStoreMode>TRANSIENT</dataStoreMode>
<dataStoreFile>StrolchModel.xml</dataStoreFile>
<enableObserverUpdates.otherRealm>true</enableObserverUpdates.otherRealm>
<dataStoreMode.otherRealm>TRANSIENT</dataStoreMode.otherRealm>
<dataStoreFile.otherRealm>StrolchModel.xml</dataStoreFile.otherRealm>
</Properties>
</Component>
</env>
...
</StrolchConfiguration>
```
Registering for updates is done by registering an Observer on the
ObserverHandler of the realm itself:
```java
ObserverHandler observerHandler = container.getRealm(StrolchConstants.DEFAULT_REALM).getObserverHandler();
observerHandler.registerObserver(Tags.RESOURCE, new Observer() {
@Override
public void update(String key, List<StrolchRootElement> elements) {
logger.info(elements.size() + " resources were updated!");
}
@Override
public void remove(String key, List<StrolchRootElement> elements) {
logger.info(elements.size() + " resources were removed!");
}
@Override
public void add(String key, List<StrolchRootElement> elements) {
logger.info(elements.size() + " resources were added!");
}
});
```

View File

@ -0,0 +1,103 @@
---
title: 'Policies'
weight: 100
---
## Policies
Policies are an integral part when writing business logic in Strolch. In many
cases it would suffice to write all such logic in `Services` and `Commands`, but
as soon as behaviour can change, depending on the element being accessed, then
this would quickly lead to many if/else blocks.
Since writing large if/else blocks is not maintanable in the long run, Strolch
offers a different approach. All Strolch elements can store Policy definitions.
This is a simple key/value store where the key defines the type of policy, and
the value references the policy to use.
Currently there are two ways to reference a policy in Strolch, either via a key
which defines a further lookup in the `PolicyHandler`, or directly as the name
of the class to instantiate.
Using policies in Strolch gives the additional possibility of easily changing
the behaviour at runtime, as a Service and/or Command would delegate the
behaviour to the currently configured policy on the releveant element.
Policies are implemented by defining an abstract class and extends
StrolchPolicy. This abstract class then defines the API of the actual policy. A
concrete class then extends this abstract class and implements the concrete
methods.
Policies are registered on Resources, Orders, Activities and Actions. The
following shows defining two policies on a Resource, a PlanningPolicy, an
ExecutionPolicy in XML:
```xml
<Resource ...>
...
<Policies>
<Policy Type="PlanningPolicy" Value="key:SimplePlanning" />
<Policy Type="ExecutionPolicy" Value="java:li.strolch.policytest.TestSimulatedExecutionPolicy" />
</Policies>
</Resource>
```
{{% notice tip %}} Note how the PlanningPolicy has a value of key:SimplePlanning
and the ExecutionPolicy defines a reference to an actual class. {{% /notice %}}
To use the PolicyHandler, it must be configured in the StrolchConfiguration.xml
as follows:
```xml
<Component>
<name>PolicyHandler</name>
<api>li.strolch.policy.PolicyHandler</api>
<impl>li.strolch.policy.DefaultPolicyHandler</impl>
<Properties>
<readPolicyFile>true</readPolicyFile>
<policyConfigFile>StrolchPolicies.xml</policyConfigFile>
</Properties>
</Component>
```
And this policy handler implementation requires a file where the lookups for the
policies is defined, e.g.:
```xml
<StrolchPolicies>
<PolicyType Type="PlanningPolicy"
Api="li.strolch.policytest.TestPlanningPolicy">
<Policy Key="SimplePlanning"
Class="li.strolch.policytest.TestSimplePlanningPolicy"/>
</PolicyType>
<PolicyType Type="ConfirmationPolicy"
Api="li.strolch.policytest.TestConfirmationPolicy">
<Policy Key="NoConfirmation"
Class="li.strolch.policytest.TestNoConfirmationPolicy"/>
</PolicyType>
</StrolchPolicies>
```
Now at runtime we can access the policies:
```java
PolicyHandler policyHandler=getComponent(PolicyHandler.class);
try(StrolchTransaction tx=openTx()){
Resource res=tx.getResourceBy("TestType","MyTestResource");
PolicyDefs policyDefs=res.getPolicyDefs();
PolicyDef planningPolicyDef=policyDefs.getPolicyDef("PlanningPolicy");
PlanningPolicy planningPolicy=policyHandler.getPolicy(planningPolicyDef,tx);
planningPolicy.plan(...);
PolicyDef executionPolicyDef=res.getPolicyDefs().getPolicyDef("ExecutionPolicy");
ExecutionPolicy executionPolicy=policyHandler.getPolicy(executionPolicyDef,tx);
executionPolicy.execute(...);
}
```

View File

@ -0,0 +1,119 @@
---
title: 'Privileges'
weight: 140
---
## Privileges
No framework is complete without user management and privilege validation. The
basic form would be Users and Roles, and then validating that an authenticated
user has a given role. In Strolch we go a step further: A User has roles
assigned, and each role has a set of Privileges. The privileges can overlap, a
validation is performed to make sure that the one role doesn't deny and another
role allows a specific action.
As explained in
the [Privilege Configuration](/documentation/runtime-configuration.md) section,
users are defined in the `PrivilegeUsers.xml` file, and roles are defined in the
`PrivilegeRoles.xml` file.
Let's assume the following user and role definition:
```xml
<Users>
<User userId="1" username="jill" password="$PBKDF2WithHmacSHA512,10000,256$61646d696e$cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344">
<Firstname>Jill</Firstname>
<Lastname>Someone</Lastname>
<State>ENABLED</State>
<Locale>en-GB</Locale>
<Roles>
<Role>AppUser</Role>
</Roles>
<Properties>
<Property name="realm" value="execution" />
</Properties>
</User>
</Users>
```
```xml
<Roles>
<Role name="AppUser">
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
</Roles>
```
This configuration contains one user and one role. The user `jill` has the role
`AppUser` and the role `AppUser` has three privileges assigned.
Note how the user's password is configured similar to a unix password
definition: Using the dollar sign & first the hashing algorithm is configured (
algorithm, iterations, key length), then the salt, followed by the password
hash.
{{% notice tip %}}
Note: The password can also still be saved using two separate fields: a salt and
password field. This configuration will be immediately changed to the unix form,
so won't be described further here.
{{% /notice %}}
Further a user always has a firstname and last name. Optionally a locale can be
set, otherwise the system locale is used. The user's state must be defined as
one of `NEW`, `ENABLED`, `DISABLED`, `EXPIRED` or `SYSTEM`. A user can only
authenticate/login with the state `ENABLED`. A user can have any number of
properties, which can then be used at runtime. A user can also reference any
number of roles, the assigned privilege can overlap, a global configuration mode
defines how duplicate privileges are handled.
Roles have a name and any number of `Privilege` definitions. A Privilege has a
name, which in many cases is the name of Java class/interface on which an action
is being invoked. The `policy` value defines which policy to use when evaluating
the privilege access. The privilege definition is defined in the
`PrivilegeConfig.xml` and is the name of a class to call for privilege validation.
Further the privilege definitions can have a `AllAllowed` boolean flag, or any
number of Allow or Deny values. How these values are interpreted is defined in
the policy implementation. A policy takes three input parameters:
* `PrivilegeContext` → supplied by privilege and gives access to the Certificate,
thus identifying the user for which privilege access is to be validated.
* `IPrivilege` → Contains the privilege values: `AllAllowed`, `Allow` and `Deny`
* `Restrictable` → An interface from which the privilege name is retrieved, and
the associated value. The value is an object, and is cast to the relevant
input in the concrete privilege policy.
The following privilege policies are already implemented:
* `DefaultPrivilege` → simple policy where the passed `Restrictable` is expected to
return a String value, which is compared with allow and deny values.
* Internal: `RoleAccessPrivilege` → policy used for the internal privileges
`PrivilegeGetRole`, `PrivilegeAddRole`, `PrivilegeModifyRole` or `PrivilegeModifyRole`
* Internal: `UserAccessPrivilege` → policy used for the internal privileges
`PrivilegeGetUser`, `PrivilegeAddUser`, `PrivilegeRemoveUser`, `PrivilegeModifyUser`,
`PrivilegeAddRoleToUser`, `PrivilegeRemoveRoleFromUser`, `PrivilegeSetUserState`,
`PrivilegeSetUserLocale` or `PrivilegeSetUserPassword`
* Internal: `UserAccessWithSameOrganisationPrivilege` → Same as the
`UserAccessPrivilege` but expects the authenticated user to have a property
`organisation` and validates that the user being modified is in the same
organisation.
* Internal: `UsernameFromCertificatePrivilege` → This policy expects a
`Restrictable` to return the `certificate` of another authenticated user and is
used when modifying an authenticated user, i.e. killing a session, or
modifying its current state, e.g. locale etc.
* Internal: `UsernameFromCertificateWithSameOrganisationPrivilege` → Same as
`UsernameFromCertificatePrivilege` but expects the authenticated user to have a
property `organisation` and validates that the user being modified is in the
same organisation.
{{% notice tip %}}
Note: As a rule, the sequence is `AllAllowed → Allow → Deny → default deny`
{{% /notice %}}

View File

@ -0,0 +1,115 @@
---
title: 'Queries'
weight: 89
---
## Queries
{{% notice warning %}}
The Query API is deprecated and the search API should be used instead.
{{% /notice %}}
As is custom for every framework, querying the model must be possible. Strolch
queries are implemented using the `StrolchQuery` interface and one of its concrete
implementations: `ResourceQuery`, `OrderQuery`, `ActivityQuery`.
A Strolch element always has two identifiers: `Type` and `Id`. The type is important
as it classifies an element. So if a car and a house would be modelled in
Strolch, then those would both be a `Resource`, but one of type `Car` and the other
of type `House`. Both would have different parameters.
Thus one of the inputs for every query is it's type, which is defined as the
navigation. It is said that we navigate to the Cars, or Houses. Thus when
instantiating a ResourceQuery, pass the navigation to the type of Resource as
well. Same applies for Orders and Activities.
Further input for a StrolchQuery are the selections. These selections get
translated into RDBMS `WHERE` clauses. Selections support boolean operations thus
allowing for complex querying.
StrolchQueries also support Ordering and object transformation. Following
classes provide the most used scenarios:
* OrderById
* OrderByName
* OrderByParameter
* *ToDomVisitor
* *ToSaxVisitor
* *ToJsonVisitor
* *ToFlatJsonVisitor
Example: Query all resources of type Car:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<Resource> query = ResourceQuery.query("Car");
query.withAny();
List<Resource> cars = tx.doQuery(query);
}
```
Example: Query all resources of type Car, order by Name and transform to JSON:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<JsonObject> query = ResourceQuery.query("Car", new ResourceToJsonVisitor(),
new OrderByName());
query.withAny();
List<JsonObject> cars = tx.doQuery(query);
}
```
the previous example can also be written as follows:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<JsonObject> query = new ResourceQuery<>();
query.setNavigation(new StrolchTypeNavigation("Car"));
query.setResourceVisitor(new ResourceToJsonVisitor());
query.withAny();
List<JsonObject> cars = tx.doQuery(query);
}
```
Example: Query all resources of type Car with color blue:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<Resource> query = ResourceQuery.query("Car");
query.with(ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()));
List<Resource> cars = tx.doQuery(query);
}
```
Example: Query all resources of type Car which are not blue:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<Resource> query = ResourceQuery.query("Car");
query.not(ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()));
List<Resource> cars = tx.doQuery(query);
}
```
Example: Query all resources of type Car with color blue or yellow:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<Resource> query = ResourceQuery.query("Car");
query.or().with(
ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()),
ParameterSelection.stringSelection("parameters", "color", "yellow", StringMatchMode.es()));
List<Resource> cars = tx.doQuery(query);
}
```
Example: Query all resources of type Car with color blue or yellow owned by Jill:
```java
try (StrolchTransaction tx = openTx()) {
ResourceQuery<Resource> query = ResourceQuery.query("Car");
StringParameterSelection owner = ParameterSelection.stringSelection("parameters", "owner", "Jill", StringMatchMode.es());
OrSelection colors = new OrSelection().with(
ParameterSelection.stringSelection("parameters", "color", "blue", StringMatchMode.es()),
ParameterSelection.stringSelection("parameters", "color", "yellow", StringMatchMode.es()));
query.and().with(owner, colors);
List<Resource> cars = tx.doQuery(query);
}
```

View File

@ -0,0 +1,118 @@
---
title: 'Realms'
weight: 50
---
## Realms
Realms implement multi-tenant capabilities. A Strolch agent can have an
arbitrary number of realms configured and each realm has its own persistence
configuration, allowing to separate mandates completely.
A realm can run in one of the following modes:
* EMPTY
This is a transient data store mode, where no model changes are
persisted - they are only kept in memory. When the Strolch agent is
started, this realm is empty as no data is loaded.
* TRANSIENT
This is the same as EMPTY, but with the difference that when the Strolch
agent is started, a model file is parsed and the in-memory realm is
populated with the elements parsed from the model file.
* CACHED
In this mode, all data is stored in-memory, and any changes made are
written back to the persistence layer. This allows for fast in-memory
qeuries, but makes sure no data is lost when the agent is restarted.
Realms are mostly hidden from a developer as a `StrolchTransaction` exposes
all important operations needed to access Strolch objects. A developer will
however need to configure the realms for their specific project. If the
project only requires one realm, then the `defaultRealm` can be used, where the
developer only is required to configure the mode and any relevant model file.
If the mode is `CACHED`, then the `PersistenceHandler` component is required to be
configured, so that the DAOs know how to access the underlying database.
The configuration in the `StrolchConfiguration.xml` file is as follows:
```xml
<StrolchConfiguration>
<env id="dev">
...
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<!-- if CACHED: -->
<!--depends>PersistenceHandler</depends-->
<Properties>
<dataStoreMode>EMPTY|TRANSIENT|CACHED</dataStoreMode>
<dataStoreFile>StrolchModel.xml</dataStoreFile>
</Properties>
</Component>
...
</env>
</StrolchConfiguration>
```
## Multi-Realm
A multi-realm configuration would be as follows.
{{% notice tip %}}
Note how the defaultRealm is still enabled, and has its configuration as
before. Further the PostgreSQL PersistenceHandler is configured to show how the
realms are connected to the persistence handler:
{{% /notice %}}
```xml
<StrolchConfiguration>
<env id="dev">
...
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<depends>PersistenceHandler</depends>
<Properties>
<realms>defaultRealm, cachedRealm</realms>
<dataStoreMode>TRANSIENT</dataStoreMode>
<dataStoreFile>DefaultRealm.xml</dataStoreFile>
<dataStoreMode.cachedRealm>CACHED</dataStoreMode.cachedRealm>
<dataStoreMode.emptyRealm>EMPTY</dataStoreMode.emptyRealm>
</Properties>
</Component>
<Component>
<name>PersistenceHandler</name>
<api>li.strolch.persistence.api.PersistenceHandler</api>
<impl>li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler</impl>
<Properties>
<allowSchemaCreation>true</allowSchemaCreation>
<allowSchemaDrop>true</allowSchemaDrop>
<db.url.cachedRealm>jdbc:postgresql://localhost/testdb2</db.url.cachedRealm>
<db.username.cachedRealm>testuser2</db.username.cachedRealm>
<db.password.cachedRealm>test</db.password.cachedRealm>
<db.pool.maximumPoolSize.cachedRealm>1</db.pool.maximumPoolSize.cachedRealm>
</Properties>
</Component>
...
</env>
</StrolchConfiguration>
```
## Access realm
Accessing a realm is done in multiple ways. Important is to note, that a user
should use the `StrolchTransaction` object, instead of accessing the Realm directly.
Opening a transaction is done from a `Service` by calling one of the
`openTx()`-methods. Nevertheless, the realm can be accessed as follows:
```java
ComponentContainer container = getAgent().getContainer();
StrolchRealm realm = container.getRealm(StrolchConstants.DEFAULT_REALM);
try(StrolchTransaction tx = realm.openTx()) {
Resource resource = tx.getResourceBy("TestType", "MyTestResource");
...
}
```

View File

@ -0,0 +1,309 @@
---
title: 'Reports'
weight: 130
---
## Reports
Since Strolch has a generic model, it was rather straight forward to create a
simple API for writing reports. In Strolch a report is defined by using its own
model, i.e. a Report is a `Resource` of type `Report`.
A report consists of the following parts:
* policy definition, thus allowing extensions
* basic configuration like base object type, order direction, etc.
* column definitions
* joins
* ordering definition
* filters
An example of a report is as follows:
```xml
<Resource Id="stockReport" Name="Stock Report" Type="Report">
<ParameterBag Id="parameters" Name="parameters" Type="Parameters">
<Parameter Id="objectType" Index="20" Hidden="false" Name="Object Type"
Type="String" Interpretation="Resource-Ref" Uom="Player"
Value="Player"/>
<Parameter Id="descending" Name="Descending order" Type="Boolean"
Value="true"/>
</ParameterBag>
<ParameterBag Id="ordering" Name="Ordering" Type="Ordering">
<Parameter Id="name" Name="Name" Type="String"
Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
</ParameterBag>
<ParameterBag Id="noTeamFilter" Name="Filter" Type="Filter">
<Parameter Id="policy" Name="Filter Policy" Type="String"
Interpretation="ReportFilterPolicy" Uom="key:Equals"
Value="!"/>
<Parameter Id="fieldRef" Name="Field reference" Type="String"
Interpretation="Resource-Ref" Uom="Slot"
Value="Bags/relations/team"/>
</ParameterBag>
<ParameterBag Id="columns" Name="Display Columns" Type="Display">
<Parameter Id="name" Name="Player" Type="String"
Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
<Parameter Id="birthDate" Name="Birth date" Type="String"
Interpretation="Resource-Ref" Uom="Player"
Value="Bags/parameters/birthDate"/>
<Parameter Id="team" Name="Team" Type="String"
Interpretation="Resource-Ref" Uom="Team" Value="$name"/>
</ParameterBag>
<ParameterBag Id="joins" Name="Joins" Type="Joins">
<Parameter Id="Team" Index="10" Hidden="false" Name="Team" Type="String"
Interpretation="Resource-Ref" Uom="Team" Value="Player"/>
</ParameterBag>
<Policies>
<Policy Type="ReportPolicy"
Value="java:li.strolch.report.policy.GenericReport"/>
</Policies>
</Resource>
```
This report
* shows all Resources of type player (parameter `objectType`) → marks the object
type to be show in the filter criteria (default), and that its sorting index
is at 20.
* orders the report by player's name (parameter bag `ordering`)
* filters out all players with no team assigned (parameter bag `noTeamFilter`)
* defines three columns: Player, Birth date, Team (paramger bag `columns`)
* joins in the resource of type `Team`
* Uses the `GenericReport` class to generate the report
## GenericReport
The default generic report implemented in Strolch has the following features and
options:
### Parameters
The parameters bag can contain the following parameters:
* `objectType` → the base type of object to get the input for the report. This
means that the `Interpretation` is set to one of:
* `Resource-Ref`
* `Order-Ref`
* `Activity-Ref`
and that the `UOM` and `value` of the parameter is set to the type of element with
which to retrieve the elements from the strolch model.
* `descending` → boolean flag to define if sorting is in descending order
* `allowMissingColumns` → flag to define if no exception should be thrown if a
column is missing
* `dateRangeSel` → defines a lookup parameter to use as a date range selector.
This requires input when executing the report
{{% notice warning %}}
Note: that the attributes Hidden and Index define the
visibility and sorting index as filter criteria respectively.
{{% /notice %}}
### Lookups
Many of the features of the generic report rely on looking up a value on the
referenced element. The following shows the ways that a lookup can be performed:
* `$id` → lookup the ID of the element
* `$name` → lookup the name of the element
* `$type` → lookup the type of the element
* `$date` → lookup the date of the element (only possible on `Order`
and `Activity` elements)
* `$state` → lookup the state of the element (only possible on `Order`
and `Activity` elements)
* `Bags/<bag_id>/<param_id>` → a lookup on the selected element by bag ID and
parameter ID
* `$search:<parent_ref_id>:Bags/<bag_id>/<param_id>` → searches for a parameter
with the given bag and parameter, and if it does not exist, looks for the
parent with the given parent_ref_id on the element. This allows a recursive
search up a tree of elements which all have the same parameter referencing a
parent. relations bag
{{% notice warning %}} Note: these definitions are set as the value of a
Parameter, and the Interpretation and UOM of the parameter is used to find the
element on which to perform the lookup. I.e. the following definition:
{{% /notice %}}
```xml
<Parameter Id="name" Name="Player" Type="String" Interpretation="Resource-Ref" Uom="Player" Value="$name"/>
```
defines that we want to lookup the name of the resource of type Player.
### Ordering
Ordering, i.e sorting is done by adding the parameter bag with the id `ordering`
and each parameter defines a column to order by. The sequence of the ordering is
defined by the `index` value assigned to each parameter.
### Filtering
Filtering use additional Strolch Policies which implement the operator function.
I.e. performing an equals, etc. The following `ReportFilterPolicy` are available
and should be added in your `StrolchPolicies.xml` file:
```xml
<StrolchPolicies>
...
<PolicyType Type="ReportFilterPolicy" Api="li.strolch.report.policy.ReportFilterPolicy">
<Policy Key="GreaterThan" Class="li.strolch.report.policy.GreaterThanReportFilter"/>
<Policy Key="LessThan" Class="li.strolch.report.policy.LessThanReportFilter"/>
<Policy Key="Equals" Class="li.strolch.report.policy.EqualsReportFilter"/>
<Policy Key="Contains" Class="li.strolch.report.policy.ContainsReportFilter"/>
<Policy Key="IsIn" Class="li.strolch.report.policy.IsInReportFilter"/>
<Policy Key="ValueRef" Class="li.strolch.report.policy.ValueRefReportFilter"/>
</PolicyType>
...
</StrolchPolicies>
```
From this we can see that we can perform a `GreaterThan`, `LessThan` and `Equals`
filtering. These filters can also be negated by prefixing the filter value with
an exclamation mark (!).
A special case for the filter values are filters on dates. If you are filtering
on a date, then you can use the special operator `now()`. This filter will use the
current date and time and will add/subtract the ISO8601 period passed as an
argument to the operator.
The following shows examples of these filters:
```xml
<ParameterBag Id="minQtyFilter" Name="Filter" Type="Filter">
<Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:GreaterThan" Value="10"/>
<Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="Bags/parameters/quantity"/>
</ParameterBag>
<ParameterBag Id="notEmptyFilter" Name="Filter" Type="Filter">
<Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:Equals" Value="!"/>
<Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="Team" Value="Bags/relations/team"/>
</ParameterBag>
<ParameterBag Id="threeMonthsAgoFilter" Name="Filter" Type="Filter">
<Parameter Id="policy" Name="Filter Policy" Type="String" Interpretation="ReportFilterPolicy" Uom="key:LessThan" Value="now(-P3M)"/>
<Parameter Id="fieldRef" Name="Field reference" Type="String" Interpretation="Resource-Ref" Uom="FromStock" Value="$date"/>
</ParameterBag>
```
{{% notice tip %}}
Note: One parameter defines which policy gets used and the `key:<name>` value
references a policy defined in the `StrolchPolicies.xml` file. Further the lookup
is defined in the `fieldRef` parameter.
{{% /notice %}}
### Joins
To add columns from data which is not on the element denoted by the base object
type, we can join further elements. This is done by adding the parameter bag
`joins` and then each parameter references an element to join. The joining is done
as follows:
* The `Intepretation` and `UOM` define which object we want to join, i.e. resource
of type foo
* The value of the parameter defines the type of element on which to find the
reference
* The join ordering is not relevant, as the tree is traversed accordingly
* At least one join must reference the base object type
* The lookup of the join is done by finding a parameter with any ID, which has
the same `Interpretation` and `UOM` as the join definition
* The attributes `Hidden` and `Index` define the visibility and sorting index as
filter criteria respectively.
Thus the following:
```xml
<ParameterBag Id="joins" Name="Joins" Type="Joins">
<Parameter Id="Team" Index="10" Hidden="false" Name="Team" Type="String" Interpretation="Resource-Ref" Uom="Team" Value="Player"/>
<Parameter Id="Country" Index="5" Hidden="false" Name="Team" Type="String" Interpretation="Resource-Ref" Uom="Country" Value="Team"/>
</ParameterBag>
```
Performs two joins: First we join a resource of type `Team` by finding the
relevant parameter on the `Player` resource, and then we lookup a resource of type
`Country` on the previously joined `Team` resource.
### Execution of Reports
To execute a reports, we must instantiate the Report and can then directly
generate a JsonObject stream, which we can then pipe to a browser, file, etc.:
```java
Stream<JsonObject> jsonObjectStream = new Report(tx, reportId).doReportAsJson();
```
If you prefer a CSV report:
```java
try (CSVPrinter csvP = new CSVPrinter(new OutputStreamWriter(out),
CSVFormat.DEFAULT.withHeader(headers).withDelimiter(';'))) {
// do report without AsJson, and then iterating each row and sending to a CSV writer
report.doReport().forEach(row -> {
try {
csvP.printRecord(row.valueStream().collect(Collectors.toList())); // add to CSV
} catch (Exception e) {
logger.error("Could not write CSV row", e);
}
});
}
```
### Filter Criteria
Predefining filters is a good start, but in some case you only want a portion of
the actual filtered data. For instance if you make a stock report, you might
only want one location. This information is dynamic and thus not stored on the
report definition.
To perform these dynamic filterings, one would call the `filter()`-method on the
report, passing the type of element to be filtered, and to which element IDs to
reduce the report data to. The following reduces the report to only return the
rows with the `product01` Product and `location02` Location elements:
```java
new Report(tx, "stockReport")
.filter("Product", "product01")
.filter("Location", "location02")
.doReportAsJson()
```
It is possible to find the possible filter criteria dynamically using the
generateFilterCriteria() method.
### Date Range Filtering
The last option to filter dynamically is using a date range selector. Define the
dateRangeSel lookup parameter, and then set the date range on the instantiated
report:
Model the report in XML:
```xml
<ParameterBag Id="parameters" Name="parameters" Type="Parameters">
...
<Parameter Id="dateRangeSel" Name="Date Range Selector" Type="String" Interpretation="Resource-Ref" Uom="Product" Value="Bags/parameters/expirationDate"/>
...
</ParameterBag>
```
And now call the report in Java:
```java
Date from = new Date(LocalDate.of(2016, 1, 1).toEpochDay() * 86400000);
Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
List<JsonObject> result = new Report(tx, "stockReport") //
.filter("Product", "product01") //
.dateRange(dateRange) //
.doReportAsJson()
```
{{% notice tip %}}
Note: See the [GenericReportTest](https://github.com/4treesCH/strolch/blob/develop/li.strolch.service/src/test/java/li/strolch/report/GenericReportTest.java) for examples.
{{% /notice %}}

View File

@ -0,0 +1,218 @@
---
title: 'Runtime Configuration'
weight: 40
---
## Runtime Configuration
A Strolch runtime configuration comprises two parts: a configuration part, and
a model part. The configuration are files located in the `..config/` folder,
and the model are files located in the `../data` folder.
In an absolute minimal configuration, the Strolch runtime requires the
following folder structure:
* `../config/`
* `../StrolchConfiguration.xml` → configures the Strolch agent
* `../PrivilegeConfig.xml` → configures user management
* `../PrivilegeUsers.xml` → contains the users in an XML based user management file
* `../PrivilegeRoles.xml` → contains the roles and privileges in an XML based user management
## StrolchConfiguration.xml
The StrolchConfiguration.xml file configures the Strolch agent. The StrolchConfiguration.xml defines the following:
* `<StrolchConfiguration>` root element
* `<env id="xxx">` different environments with the possibility of having a
global environment for configuration valid in multiple environments.
* `<Runtime>` element which defines the agents name and a few other
properties e.g. `locale` and `verbose`:
* `<applicationName>` the agent's name
* `<Properties>`
* `<locale>` the agent's internal locale for log messages etc.
* `<verbose>` the logging level for some internal logging. (Logging is
mostly done using log4j over slf4j)
* `<Component>` elements for each component used in the agent. A component
is configured by defining the following child elements:
* `<name>` the name of the component, use when defining dependencies
between components. The name is mostly set to the simple name of the
interface of the component
* `<api>` the full class name to the interface of the component. During
runtime this interface will be used to access the component e.g.:
`ServiceHandler svcHandler = agent.getContainer().getComponent(ServiceHandler.class);`
* `<impl>` the full class name of the concrete implementation of the
component. During initialization this class will be instantiated and
registered under the component name and interface. This class must
extend the class li.strolch.agent.api.StrolchComponent
* `<depends>` any number of these elements, where the content is the name
of another component, on which this component depends. Depending
components are initialized and started after the component they depend
on and are stopped and destroyed before
* `<Properties>`
* `<...>` any number of properties which the component requires. The
element's name will be the key with which the value can be accessed at
runtime.
{{% notice warning %}}
When a property is missing, and the component has a hard coded default value, then when the component is initialized, the use of the default value and its key is logged. This makes it easy to see which new properties can be configured. Should the component not define a default value, then the component will thrown an exception on initialization. In this case it can be a good moment to read the JavaDoc (or source code) for the component in question to see how it is configured.
{{% /notice %}}
## Privilege Configuration
In Strolch authentication and authorization is baked in. To open a transaction,
and thus access the Strolch model, a Certificate object is required, which
means the user has been authenticated and possibly authorized.
The PrivilegeConfig.xml defines the following:
* `<Privilege>` root element
* `<Container>` configures the individual Privilege components
* `<Parameters>` base configuration properties for Privilege
* `<EncryptionHandler>` configures the hashing algorithms and other
encryption specific configuration
* `<PersistenceHandler>` configures the persistence of the roles and users
* `<UserChallengeHandler>` configures a challenge handler so that a user
can reset their password. The default challenge handler is the
`li.strolch.privilege.handler.MailUserChallengeHandler` which sends a
challenge to the user's defined e-mail address.
* `<SsoHandler>` the SSO Handler is used to implement a SingleSignOn and
can be used to start a session using a LDAP token, etc. There is no
default implementation as this is project specific.
* `<Policies>` configures the available privilege policies at runtime, the
name is referenced from the model file
The `PrivilegeUsers.xml` and `PrivilegeRoles.xml` define the users and roles
and is used when in `PrvilegeConfig.xml` the `PersistenceHandler` is set to
`ch.eitchnet.privilege.handler.XmlPersistenceHandler`:
* `<Users>` configures all users
* `<User>` configures a specific user
* `<Firstname>` configures a user's first name
* `<Lastname>` configure a user's last name
* `<State>` configures the user's state, see `li.strolch.privilege.model.UserState`
* `<Locale>` configure the user's locale
* `<Roles>` configures the user's roles
* `<Role>` adds a role to the user
* `<Properties>` configures user specific properties. What properties
are used is not specified and is dependent on the concrete agent
* `<Property>` defines a single property
* `<Roles>` configures all roles
* `<Role>` configures a specific role
* `<Privilege>` configures a specific privilege for this role
* `<AllAllowed>` if set to true, then defines that all values
associated with this privilege are allowed
* `<Allow>` defines one allowed value for this privilege
* `<Deny>` defines one denied value for this privilege
## Implementing a StrolchComponent
Implementing a strolch component requires an interface, which defines the
component's API and a concrete class which implements the interface and
extends the class `StrolchComponent`.
The StrolchComponent class adds the state model to the class, which
transitions as follows:
`UNDEFINED => SETUP => INITIALIZED => STARTED <=> STOPPED => DESTROYED`
Components can switch between `STARTED` and `STOPPED`, but once `DESTROYED` no
further state change is possible. The component's state is changed by changes
to the agent's lifecycle.
A component's state is changed by a call to the appropriate method on the
component, override the methods as necessary. Note that it is good practice
that the `initialize()`-method is used to get all the configuration properties,
and that they should there be evaluated and that the method so return quickly.
The `start()`-method is called after the agent's initialization and should be
where additional threads are started. Correctly implementing these methods
allows to quickly detect a wrongly configured agent, which might take longer
to start for whatever reason.
The following shows a basic implementation of a component on the basis of a
post initializer (a component which performs some actions in its
`start()`-method which should be done after everything else is started in the
agent).
```java
public class SimplePostInitializer
extends StrolchComponent
implements PostInitializer {
public SimplePostInitializer(ComponentContainer container,
String componentName) {
super(container, componentName);
}
@Override
public void initialize(ComponentConfiguration configuration) {
// do some initialization, validate configuration values, etc.
// now call super, to update state
super.initialize(configuration);
}
@Override
public void start() {
// start any threads, or perform long running start work
// now call super, to update state
super.start();
}
@Override
public void stop() {
// stop threads and timers, but be ready to start again
// now call super, to update state
super.stop();
}
@Override
public void destroy() {
// destroy this component, release all resources and don't worry about
// being called to start again now call super, to update state
super.destroy();
}
}
```
The new component would then be registered in the `StrolchConfiguration.xml`
as follows:
```xml
<StrolchConfiguration>
<env id="...">
...
<Component>
<name>SimplePostInitializer</name>
<api>li.strolch.agent.api.PostInitializer</api>
<impl>li.strolch.documentation.SimplePostInitializer</impl>
</Component>
...
</env>
</StrolchConfiguration>
```
And can be access at runtime using:
```java
PostInitializer postInitializer = getContainer().getComponent(PostInitializer.class);
```
## Starting the agent
When a Strolch runtime is started, then the root path to the runtime configuration must be passed. In Java this is done by calling:
```java
StrolchAgent agent = new StrolchAgent();
agent.setup(environment, rootPath);
agent.initialize();
agent.start();
```
In Servlet 3.0 applications one would implement the
`javax.servlet.ServletContextListener` interface, add the `@WebListener`
annotation to the class and in the `contextInitialized()`-method start Strolch:
```java
String realPath = sce.getServletContext().getRealPath("/WEB-INF");
String environment = StrolchEnvironment.getEnvironmentFromEnvProperties(pathF);
this.agent = new StrolchAgent();
this.agent.setup(environment, new File(realPath));
this.agent.initialize();
this.agent.start();
```

View File

@ -0,0 +1,99 @@
---
title: 'Searches'
weight: 80
---
## Searches
As is custom for every framework, querying, or searching, the model must be
possible. Strolch searches are implemented using the `StrolchSearch` class and
one of its concrete implementations: `ResourceSearch`, `OrderSearch`,
`ActivitySearch`.
A Strolch element always has two identifiers: `Type` and `Id`. The type is
important as it classifies an element. So if a car and a house would be modelled
in Strolch, then those would both be a `Resource`, but one of type `Car`
and the other of type `House`. Both would have different parameters. Thus when
searching for objects, the first thing to do is define the type of object being
searched.
The Strolch search API is very expressive and offers multiple ways to perform
the same search. The search API consists of three components: The search
classes, the search expressions and the search predicates. The concept was taken
from the [Apache Camel](https://camel.apache.org/) project.
There are four main search classes:
* RootElementSearch - search for any of Resource, Order or Activity elements
* ResourceSearch - search for Resources
* OrderSearch - search for Orders
* ActivitySearch - search for Activities
No search is useful without a `where` clause, which are called search
expressions. When writing a search, there are multiple ways to add such where
clauses. Either
* override the `define()`-method in your sub class and add the where clauses by
calling the `where()` method, or
* define special methods on the class e.g. `byColor()` which also calls the
`where()`-method to add a search expression, or
* directly call the `where()`-method after instantiating a search.
When extending the class, then the search expressions are available as methods
on the super class, otherwise you can statically import them from
[ExpressionsSupport](https://github.com/4treesCH/strolch/blob/develop/li.strolch.agent/src/main/java/li/strolch/search/ExpressionsSupport.java)
.
And of course a where clause needs operators, which are called search
predicates. Just as search expressions are available in sub classes, so are
search predicates and can also be statically imported through
[PredicatesSupport](https://github.com/4treesCH/strolch/blob/develop/li.strolch.agent/src/main/java/li/strolch/search/PredicatesSupport.java)
.
Examples of search expressions with search predicates follow:
```java
ResourceSearch search=new ResourceSearch();
// predicate either as parameter, or chained
search.where(id().isEqualTo("myId"));
search.where(id(isEqualTo("myId")));
// negating
search.where(id(isEqualTo("myId")).not());
search.where(param("bagId","paramId").isIn(Arrays.asList("red","blue","green")));
search.where(paramNull("bagId","paramId")));
// boolean operations
search.where(id(isEqualTo("myId")) //
.or(name(isEqualTo("myName"))));
```
Note how the predicates can be chained to the search expression, or passed as a
parameter to the expression.
In addition to using predefined search search expressions, one can also just
pass a lambda expression which performs a custom filter:
```java
personSearch.where(person -> person.getName().length() == 3);
```
See
the [StrolchSearchTest](https://github.com/4treesCH/strolch/blob/develop/li.strolch.agent/src/test/java/li/strolch/search/StrolchSearchTest.java)
for many ways in which you can implement tests.
{{% notice tip %}} Note that strolch searches requires privileges, thus when you
use a strolch search, add it to the role of the user in `PrivilegeRoles.xml`:
{{% /notice %}}
```xml
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<Allow>internal
</Allow> <!-- internal used for when the search is done in an internal service -->
<Allow>li.strolch.bookshop.search.BookSearch</Allow>
</Privilege>
```

View File

@ -0,0 +1,117 @@
---
title: 'Services and Commands'
weight: 70
---
## Services and Commands
`Services` are written to implement a specific use-case. `Commands` are written to
implement re-usable parts of a use-case. The use-case can be abstract
e.g., `AddResourceService` or very specific e.g. `CreatePatientService`.
Should the use-case be re-usable in different scenarios, then commands should
implement the logic, and the services should then execute the commands. E.g.
The `CreatePatientService` would use a `CreatePatientResourceCommand` and then
use an `AddResourceCommand` in a single transaction, so that the task of
creating the actual Patient Resource can be re-used somewhere else.
Services extend the abstract class `AbstractService` and then implement the
method `internalDoService(ServiceArgument)`. AbstractService defines generic
template arguments with which the concrete service can define a specific
input ServiceArgument class and output ServiceResult class.
The AbstractService class has multiple helper methods:
* `openTx():StrolchTransaction` - to open a transaction
* `runPrivileged()` - to perform a `SystemUserAction`
* `getComponent():V` - to retrieve a specific StrolchComponent
there are more - check the JavaDocs.
Commands extend the `Command` class and then implement the method `doCommand()`.
Commands have helper methods:
* `tx()` - to get the current transaction
* `getPolicy()` - to retrieve a `StrolchPolicy` instance
* `runPrivileged()` - to perform a `SystemUserAction`
there are more - check the JavaDocs.
The following code snippets shows how a Service and Command are used to
perform the task of adding a new Order. Note how:
* the Service opens the transaction
* adds the command to the TX
* calls `tx.commitOnClose()`
* the command validates its input
* locks the object
* performs the work
* and implements an undo
AddOrderService:
```java
public class AddOrderService extends AbstractService<AddOrderService.AddOrderArg, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
protected ServiceResult internalDoService(AddOrderArg arg) {
try (StrolchTransaction tx = openTx(arg.realm)) {
AddOrderCommand command = new AddOrderCommand(getContainer(), tx);
command.setOrder(arg.order);
tx.addCommand(command);
tx.commitOnClose();
}
return ServiceResult.success();
}
public static class AddOrderArg extends ServiceArgument {
public Order order;
}
}
```
AddOrderCommand:
```java
public class AddOrderCommand extends Command {
private Order order;
public AddOrderCommand(ComponentContainer container, StrolchTransaction tx) {
super(container, tx);
}
public void setOrder(Order order) {
this.order = order;
}
@Override
public void validate() {
DBC.PRE.assertNotNull("Order may not be null!", this.order);
}
@Override
public void doCommand() {
tx().lock(this.order);
OrderMap orderMap = tx().getOrderMap();
if (orderMap.hasElement(tx(), this.order.getType(), this.order.getId())) {
String msg = MessageFormat.format("The Order {0} already exists!", this.order.getLocator());
throw new StrolchException(msg);
}
orderMap.add(tx(), this.order);
}
@Override
public void undo() {
if (this.order != null && tx().isRollingBack()) {
OrderMap orderMap = tx().getOrderMap();
if (orderMap.hasElement(tx(), this.order.getType(), this.order.getId()))
orderMap.remove(tx(), this.order);
}
}
}
```

View File

@ -0,0 +1,100 @@
---
title: 'Transactions'
weight: 90
---
## Transactions
Strolch Transactions play a central role in a Strolch agent. A transaction is
opened for a realm, and grants access to the model of the agent. Transactions
are implemented as a Java `try-with-resources` by implementing
the `AutoCloseable`
interface. This makes it trivial to understand the scope of a transaction.
Transactions handle the following:
* Opening and closing database connections
* Releasing locks to strolch elements, if `tx.lock(StrolchRootElement)` or
`tx.lock(Locator)` was called
* Performing Commands by executing them in the added order, and validating them
first.
* Exception handling
* Auditing
* Updating observers
When a transaction is opened, it is by default read-only, i.e. does not perform
any commands when it is closed. Should the TX perform commands, then it is
important to call `tx.commitOnClose()`, but only at the end of the work, so that
exception handling can properly work if something goes wrong.
`StrolchTransaction` offers a myriad of methods:
* find element by its `Locator`
* get methods for elements by type and id, or using a `StringParameter` or
`StringListParameter` references
* methods to add, update or remove elements
* assert privilege access
* get a new element by its template
* check if an element exists by type and id
* get streams for elements
* add commands for execution
Transactions are opened by accessing the realm, but there are convenience
methods depending on the use-case:
* In Services: by calling one of the `openTx()`-methods
* In Commands: Transactions are already open, use method `tx()` to get instance.
* REST API: `RestfulStrolchComponent.openTx()`
{{% notice warning %}}
Note: don't open a new TX inside a TX for the same realm!
{{% /notice %}}
Important is to always open the transaction as a `try-with-resource` block and to
define if the TX should commit, or not:
```java
try (StrolchTransaction tx = openTx(...)) {
// read lock our object
Locator ferrariLoc = Resource.locatorFor("Car", "ferrari");
tx.lock(ferrariLoc);
// find a car by locator
Resource ferrari = tx.findElement(ferrariLoc);
// get a car by ID
Resource opel = tx.getResourceBy("Car", "opel", true);
// modify ball
opel.setName("Opel Corsa");
tx.update(opel);
// get by string reference
StringParameter ownerP = ferrari.getParameter("relations", "owner", true);
Resource owner = tx.getResourceBy(ownerP, true);
// get by string list reference
StringListParameter previousOwnersP = opel.getParameter("relations", "previousOwners", true);
List<Resource> previousOwners = tx.getResourcesBy(previousOwnersP, true);
// check resource exists
if (tx.hasResource("Car", "audi")) {
Resource audi = tx.getResourceBy("Car", "audi", true);
// assert has privilege to remove a car
tx.assertHasPrivilege(Operation.REMOVE, audi);
// remove the car
tx.remove(audi);
}
// iterate all cars
tx.streamResources("Car").forEach(car -> {
logger.info("Car: " + car.getId());
});
// commit if TX was changed
if (tx.needsCommit())
tx.commitOnClose();
}
```

View File

@ -0,0 +1,65 @@
---
title: 'Versioning'
weight: 120
---
## Versioning
One of Strolch's features that sets it apart from other frameworks, is that
versioning is baked into Strolch's fabric. The feature is opt-in, as it is not
required in all projects, but it only needs enabling, for all modifications to
objects to be versioned, so that rollbacks can be done when needed.
The feature is enabled for each realm. In the `StrolchConfiguration.xml` file
enable it by adding the `enableVersioning` propery per realm:
```xml
<StrolchConfiguration>
<env id="dev">
...
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<realms>defaultRealm, otherRealm</realms>
<enableVersioning>true</enableVersioning>
<dataStoreMode>TRANSIENT</dataStoreMode>
<dataStoreFile>StrolchModel.xml</dataStoreFile>
<enableVersioning.otherRealm>true</enableVersioning.otherRealm>
<dataStoreMode.otherRealm>TRANSIENT</dataStoreMode.otherRealm>
<dataStoreFile.otherRealm>StrolchModel.xml</dataStoreFile.otherRealm>
</Properties>
</Component>
</env>
...
</StrolchConfiguration>
```
Once versioning is enabled, versioning is handled automatically. The API for versioning is implemented on the ElementMaps.
Example: Revert to previous version of a Resource:
```java
Resource res = tx.getResourceBy("TestType", "MyTestResource");
ResourceMap resourceMap = tx.getResourceMap();
Resource previousVersion = resourceMap.revertToVersion(tx, res);
// or
Resource previousVersion = resourceMap.revertToVersion(tx, "TestType", "MyTestResource", 1);
```
Example: Retrieve all versions of a Resource:
```java
List<Resource> versions = resourceMap.getVersionsFor(tx, "TestType", "MyTestResource");
```
{{% notice tip %}}
Note: When reverting to a previous version, it is important to remember, that
any references on an element to other elements will also be restored. As long as
the relationship is to the same element, then this is not an issue, but should
the relationship have changed, then it this must be handled and the user
performing a revert be allowed to decided which element to reference in the
reverted version.
{{% /notice %}}

View File

@ -0,0 +1,14 @@
---
title: 'Download'
weight: 80
---
## Download
Strolch is
on [Maven central](https://mvnrepository.com/artifact/li.strolch/li.strolch.agent)
, but if the latest version is not there, then build it locally. A guide can be
found on the [development](/development) page.
Strolch is also built on [Jenkins](https://ci.4trees.ch/), so you can see if the
latest version passes all tests.

37
content/history/_index.md Normal file
View File

@ -0,0 +1,37 @@
---
title: 'History'
weight: 10
---
## Overview
Strolch is an open source component based software agent written in Java and can be compared, in a light sense, with the Java EE stack: Strolch takes care of persistence, implements Services for use cases, Commands as re-usable algorithms and has a parameterized data model.
Strolch has an intrinsic understanding for mandates, which are called realms so that a single agent can be used to implement applications with multiple users/customers for instance in SaaS environments.
The parameterized data model consists of three top level objects, Resources, Orders and Activities. These objects can have any number of ParameterBags which in turn can have any number of Parameters on them. This allows for a very dynamic modelling of data structures including modification at run time. Multiple ready to use Parameter types are already implemented which handle the primitive types in Java including ListParameters for collections of these primitive types.
One of the main features of the Strolch agent, is that persistence is handled transparently and the user must not be worried about databases and the likes. Currently there are two implementations for persisting the Strolch model, a PostgreSQL and an XML file persistence. Currently both persistence layers persist the data by converting to XML and storing it into the database. The XML file persistence stores each object in its own file.
The agent itself has a small memory footprint and requires very few components to start. For the agent to be useful it needs additional functionality which is implemented in StrolchComponents. Each component is registered via its Java interface on the agent and is bound to the life cycle of the agent. When the agent is started, these components can be retrieved and used to perform any number of functionalities. This is the preferred way to extend the Strolch agent. There are a number of components already implemented, e.g. the ServiceHandler which executes Services in a controlled fashion and can validate authorized access to these services.
No software product is complete without a system for authentication and authorization. Strolch implements this by using the Privilege framework which has been written by Robert von Burg. The standard ServiceHandler detects the existence of the PrivilegeHandler and then validates that the user has authorization to perform the service. This framework is implemented as its own Strolch component, thus can be retrieved at any time during execution to perform fine grained and special authorization validation.
## Motivation
A question often asked is why create Strolch. What are its benefits in contrast to using Java SE with an OR-Mapper like Hibernate, or using Java EE on JBoss or Glassfish? Especially since many of the features existing in those stacks needed to be re-created in Strolch.
The first answer to this question is that those systems are often overly complicated and bloated. Java SE with Hibernate certainly is a viable option when it comes to being light-weightier but Hibernate, even though it is supposed to, often fails to truly help remove the need to really understand an RDBMS. Often enough Hibernate will just get in the way of the most important part of development: writing the business code. Being an OR-Mapper which is supposed to implement all the nitty-gritty details of an RDBMS system, Hibernate, and JPA for that matter, still often has the developer go back to understanding these details.
Strolch tries a different approach to persistence. Instead of writing pojos/entities, Strolch's model has the concept that each element's attributes are part of a composition pattern: each attribute is its own object and thus can be dynamically changed at runtime, but also makes persistence of such an element generic. Instead of having fixed attributes for a concrete class, these parameters are stored in a map and are accessed through the parameter's ID.
Assigning an ID to an attribute for accessing of course brings its own downsides, i.e. the parameter might simply not be there, when being accessed. This is certainly an issue that the developer must handle, when implementing a project using Strolch, but allows the developer to not need to worry about persistence, as this is generically handled.
Since the persistence is generically handled, and Strolch stays lightweight on its requirements at runtime, the developer can quickly get down to what is important for business value: Writing the business logic and the presentation layer. Here too Strolch tries to help the developer by bringing in concepts which are easy to follow: Use cases are implemented as Services, and re-usable business logic is put into Commands.
There will be reasons against using Strolch, as there will be against using the Java EE stack, or an OR-Mapper or even the Java ecosystem for that fact. Important is to note, that the concepts behind Strolch are nothing new, but have been implemented in at least two previous proprietary products. Since those products are not accessible to the public, it was decided that a re-implementation might be of use to the programming community at large.
Currently, there is at least one company using Strolch in a commercial project which helps drive Strolch's development and further motivates its existence.
Strolch is an open source project and licensed under the Apache License 2.0.
##Technology
Strolch is written in Java and is programmed against the JDK 8. Strolch runs on any JRE 8 compliant environment. Strolch is tested on the Oracle JRE 8.

24
content/plc/_index.md Normal file
View File

@ -0,0 +1,24 @@
---
title: 'PLC'
weight: 20
---
## Overview
Using Strolch as a PLC has certain advantages and disadvantages. The following
is a list of advantages:
* Same programming model and language for server and PLC
* PLC has the same privilege handling as in Strolch
* Simulating down to the PLC level is easily possible for easier testing of
server logic
Of course using the Java language as a PLC has its limitations, we have manage
to use it for customers and are satisfied with the result. What follows is a
description in how to set up your own Strolch based PLC.
Checkout the code at [GitHub](https://github.com/4treesCH/strolch-plc)
Currently, we have the following topics of discussion:
{{% children %}}

View File

@ -0,0 +1,35 @@
---
title: 'Architecture'
weight: 50
---
## Architecture
### Overview
![Strolch PLC Architecture Overview](/assets/images/Strolch-PLC-Architecture-Overview.png)
The Strolch PLC architecture sees the Strolch Agent as the server, managing
logical devices, i.e. multiple sensors and actors together and thus deciding on
further steps. With this architecture multiple PLCs can be combined together in
one agent for flow control.
### PLC Architecture
![Strolch PLC Architecture](/assets/images/Strolch-PLC-Architecture.png)
On the agent side the two main classes are the `PlcGwServerHandler` and the
`PlcGwService`
The `PlcGwServerHandler` handles connections from remote PLCs over WebSockets and
sends the requests to these PLCs. A `PlcGwService` instance will be notified and
can then decide on an action. In an execution model with Activities, the
`PlcNotificationListener` interface can be implemented, or the `PlcExecutionPolicy`
can be directly extended.
On the PLC side, the `PlcGwClientHandler` is optional if no agent is required. The
`PlcHandler` initializes the model and connections. The `Plc` class is Strolch
agnostic and manages the connections and notifies `PlcListener` instances on
changes coming from the underlying connections. The `PlcService` implementations
implement business logic, and can also be notified on updates from connections.

View File

@ -0,0 +1,539 @@
---
title: 'Example Set-Up'
weight: 90
---
## Example Set-Up
This example setup describes the movement of containers over conveyors. The
conveyors have motors which can be started and stopped by a GPIO output pin
controlled on a Raspberry Pi and each conveyor has a light barrier to detect the
occupancy of a container and the Raspberry Pi detects this on GPIO input pins.
Further at each conveyor location is a barcode reader to read the ID of a
container.
The general idea is that the PLC notifies a Strolch agent of changes, and only
turns conveyors on, when the agent gives the command. Thus the agent handles
business logic and the PLC controls the I/Os.
![Strolch PLC Example](/assets/images/Strolch-Plc-Example.png)
## New Project
1. First create a new Strolch Web project using the Strolch Maven archetype
2. Now add the following Maven dependencies:
```xml
<project>
<properties>
<strolch.version>1.6.0-SNAPSHOT</strolch.version>
<strolch.plc.version>0.1.0-SNAPSHOT</strolch.plc.version>
</properties>
<dependencyManagement>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.bom</artifactId>
<type>pom</type>
<version>${strolch.version}</version>
<scope>import</scope>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-bom</artifactId>
<type>pom</type>
<version>${strolch.plc.version}</version>
<scope>import</scope>
</dependency>
</dependencyManagement>
<dependencies>
<!-- PLC -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-core</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-rest</artifactId>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>strolch-plc-gw-client</artifactId>
</dependency>
</dependencies>
</project>
```
3. Add a bower dependency: `"strolch-wc-plc": "4treesCH/strolch-wc-plc#^0.3.4"`
to `src/main/webapp/bower.json`
After adding the dependency, run `gulp` in the webapp directory. Gulp should
have been installed through the instructions from
the [development page](/development).
4. Now we need to add the PLC web views to our new project. This is added in
the `src/main/webapp/app/src/c-app.html` file. Add the following:
```html
<!-- HTML Imports -->
<link rel="import" href="../bower_components/strolch-wc-plc/strolch-wc-plc-connections.html">
<link rel="import" href="../bower_components/strolch-wc-plc/strolch-wc-plc-logical-devices.html">
<!-- Change default-page to plcLogicalDevices -->
<c-app-routing id="appRouting"
login-page="login"
default-page="plcLogicalDevices"
auth-valid="[[authTokenValid]]"
page="{{page}}"
route-tail="{{routeTail}}"
use-hash-as-path></c-app-routing>
<!-- Add the new pages in the iron-pages element: -->
<template is="dom-if" if="[[equal(page, 'plcConnections')]]" restamp>
<strolch-wc-plc-connections id="plcConnections"
base-path="../"
base-rest-path="[[baseRestPath]]"
route="{{subroute}}"></strolch-wc-plc-connections>
</template>
<template is="dom-if" if="[[equal(page, 'plcLogicalDevices')]]" restamp>
<strolch-wc-plc-logical-devices id="plcLogicalDevices"
base-path="../"
base-rest-path="[[baseRestPath]]"
base-ws-path="[[baseWsPath]]"
route="{{subroute}}"></strolch-wc-plc-logical-devices>
</template>
// add a new property to the WebSocket path for observing changes on the PLC
wsObserverPath: {
type: String,
value: function () {
return CustomWeb.baseWsPath + "/plc/observer";
}
}
```
5. Don't forget to add the PLC Rest classes to your `ResourceConfig`
```java
@ApplicationPath("rest")
public class RestfulApplication extends ResourceConfig {
public RestfulApplication() {
...
// strolch plc services
packages(PlcConnectionsResource.class.getPackage().getName());
...
}
}
```
6. Now we need to configure the PLC's runtime by
modifying `runtime/StrolchConfiguration.xml` and adding the following:
```xml
<StrolchConfiguration>
<env id="dev">
<!--
This component configures the PlcHandler by
loading the PlcConnections, PlcAddresses and PlcTelegrams
-->
<Component>
<name>PlcHandler</name>
<api>li.strolch.plc.core.PlcHandler</api>
<impl>li.strolch.plc.core.DefaultPlcHandler</impl>
<depends>RealmHandler</depends>
<Properties>
<!-- The component handling the low level connections -->
<plcClass>li.strolch.plc.core.hw.DefaultPlc</plcClass>
</Properties>
</Component>
<!--
This component handles registrations of the PlcServices, i.e. your PLC business logic
-->
<Component>
<name>PlcServiceInitializer</name>
<api>li.strolch.plc.core.PlcServiceInitializer</api>
<impl>li.strolch.plc.example.CustomPlcServiceInitializer</impl>
<depends>PlcHandler</depends>
<Properties>
</Properties>
</Component>
<!--
This component notifies a Strolch agent of changes on the PLC
only if you have a Strolch server with a configured
li.strolch.plc.gw.server.PlcServerWebSocketEndpoint ready to accept connections
-->
<Component>
<name>PlcGwClientHandler</name>
<api>li.strolch.plc.gw.client.PlcGwClientHandler</api>
<impl>li.strolch.plc.gw.client.PlcGwClientHandler</impl>
<depends>PlcHandler</depends>
<depends>PlcServiceInitializer</depends>
<Properties>
<plcId>plc-01</plcId>
<gwUsername>plc-01</gwUsername>
<gwPassword>plc-01</gwPassword>
<gwServerUrl>ws://localhost:8080/agent/websocket/strolch/plc
</gwServerUrl>
</Properties>
</Component>
</env>
</StrolchConfiguration>
```
7. Now we add the custom classes we just declared.
**PlcServiceInitializer**
```java
import java.util.ArrayList;
import java.util.List;
import li.strolch.plc.example.services.*;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
import li.strolch.plc.core.PlcServiceInitializer;
public class CustomPlcServiceInitializer extends PlcServiceInitializer {
public CustomPlcServiceInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
@Override
protected List<PlcService> getPlcServices(PlcHandler plcHandler) {
ArrayList<PlcService> plcServices = new ArrayList<>();
StartupPlcService startupPlcService = new StartupPlcService(plcHandler);
ConveyorPlcService conveyorPlcService = new ConveyorPlcService(plcHandler);
plcServices.add(conveyorPlcService);
plcServices.add(startupPlcService);
return plcServices;
}
}
```
**PlcPostInitializer**
```java
import li.strolch.agent.api.ComponentContainer;
import li.strolch.plc.core.PlcPostInitializer;
public class CustomPostInitializer extends PlcPostInitializer {
public CustomPostInitializer(ComponentContainer container, String componentName) {
super(container, componentName);
}
// override the initialize(), start(), stop() and destroy() methods as needed
}
```
8. In the `CustomPlcServiceInitializer` we added two PlcServices, for which the
code is missing. The following are simple examples:
**StartupPlcService**
```java
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
public class StartupPlcService extends PlcService {
public static final String PLC = "PLC";
public static final String STARTED = "Started";
public static final String STOPPED = "Stopped";
public StartupPlcService(PlcHandler plcHandler) {
super(plcHandler);
}
@Override
public void start(StrolchTransaction tx) {
send(PLC, STARTED);
super.start(tx);
}
@Override
public void stop() {
send(PLC, STOPPED);
super.stop();
}
}
```
**ConveyorPlcService**
```java
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import li.strolch.plc.core.PlcHandler;
import li.strolch.plc.core.PlcService;
import li.strolch.plc.model.PlcAddress;
public class ConveyorPlcService extends PlcService {
public static final int BOX_TRANSFER_DURATION = 30;
private static final String R_CONVEYOR_01 = "Conveyor01";
private static final String A_START_BUTTON = "StartButton";
private static final String T_MOTOR_ON = "MotorOn";
private static final String T_MOTOR_OFF = "MotorOff";
private static final String A_BOX_DETECTED = "BoxDetected";
private boolean motorOn;
private ScheduledFuture<?> motorStopTask;
public ConveyorPlcService(PlcHandler plcHandler) {
super(plcHandler);
}
@Override
public void handleNotification(PlcAddress address, Object value) {
String resource = address.resource;
String action = address.action;
if (!resource.equals("Conveyor01"))
throw new IllegalStateException("Unexpected resource " + resource);
boolean active = (boolean) value;
if (action.equals(A_START_BUTTON)) {
if (active) {
logger.info("Start button pressed. Starting motors...");
send(R_CONVEYOR_01, T_MOTOR_ON);
this.motorOn = true;
scheduleStopTask();
}
} else if (action.equals(A_BOX_DETECTED)) {
if (active && this.motorOn) {
logger.info("Container detected, refreshing stop task...");
scheduleStopTask();
}
} else {
logger.info("Unhandled notification " + address.toKeyAddress());
}
}
private void scheduleStopTask() {
if (this.motorStopTask != null)
this.motorStopTask.cancel(false);
this.motorStopTask = schedule(this::stopMotor, BOX_TRANSFER_DURATION, TimeUnit.SECONDS);
}
private void stopMotor() {
send(R_CONVEYOR_01, T_MOTOR_OFF);
}
@Override
public void register() {
this.plcHandler.register(R_CONVEYOR_01, A_START_BUTTON, this);
this.plcHandler.register(R_CONVEYOR_01, A_BOX_DETECTED, this);
super.register();
}
@Override
public void unregister() {
this.plcHandler.unregister(R_CONVEYOR_01, A_START_BUTTON, this);
this.plcHandler.unregister(R_CONVEYOR_01, A_BOX_DETECTED, this);
super.unregister();
}
}
```
9. Now the last part is to add the model, i.e. PlcConnections, PlcAddresses and
PlcTelegrams. To have less configuration files and make it easier to
reconfigure at runtime, this data is stored in normal Strolch `Resources`.
In this example we will use simple Raspberry Pi GPIOs. For convenience, and
also when sharing I/O definitions with external partners, it is easier to use
a CSV file to define the I/Os and then use the `PlcAddressGenerator` to
generate and validate the model.
For this purpose in this example, we will use one conveyor with 2 inputs and
1 output. The CSV file should have the following content:
```csv
Description,Type,SubType,Device,Pin,Resource,Action1,Action2,Connection,DeviceId
Material Flow,Group,,,,,,,,MaterialFlow
Conveyor 1,Input,Pin,,4,Conveyor,Occupied,,raspiBcmGpioInput
Conveyor 1,Input,Pin,,17,Conveyor,BoxDetected,,raspiBcmGpioInput
Conveyor 1,Output,Pin,,18,Conveyor,MotorOn,MotorOff,raspiBcmGpioOutput
```
The CSV headers are as follows:
* Description → a simple description for this PlcAddress
* Type →
* Group → Must be the first line and generates a PlcLogicalDevice, all
succeeding lines are grouped to this device. Add additional to group
further devices
* Input → defines a boolean input
* Output → defines a boolean output
* Virtual → defines a virtual address which has no corresponding hardware
connection. Used for internal communication.
* DataLogicScanner → defines an address to read barcodes from a DataLogic
Scanner. The actions must be left empty as the keys Barcode (address), On
and Off (telegrams) will be generated.
* SubType →
* For Input and Output types →
* DevPin, DevPin0 → Generates the address as `<Connection>.<Device>.<Pin>`.
DevPin0 decrements the Device and Pin values by one.
* Pin → Generates the address as `<Connection>.<Pin>`.
* For Virtual types →
* Boolean
* String
* Device → Device number
* Pin → The pin number on the device
* Resource → The resource ID with which to notify the agent
* Action1 → The action ID
* Action2 → The second action ID if required
* Connection → The ID of the PlcConnection with which this I/O is attached
* DeviceId → For type Group: Set the ID of this PlcLogicalDevice being generated
When you use this file as input for the `PlcAddressGenerator`, then it will
generate PlcLogicalDevice, PlcAddress and PlcTelegram elements:
```xml
<StrolchModel>
<Resource Id="D_MaterialFlow" Name="MaterialFlow" Type="PlcLogicalDevice">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String"
Value="Material Flow"/>
<Parameter Id="group" Name="Group" Type="String"
Value="01 Material Flow"/>
<Parameter Id="index" Name="Index" Type="Integer" Value="10"/>
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Relations">
<Parameter Id="addresses" Name="Addresses" Type="StringList"
Interpretation="Resource-Ref" Uom="PlcAddress"
Value="A_Conveyor-Occupied, A_Conveyor-BoxDetected, A_Conveyor-MotorOn"/>
<Parameter Id="telegrams" Name="Telegrams" Type="StringList"
Interpretation="Resource-Ref" Uom="PlcTelegram"
Value="T_Conveyor-MotorOn, T_Conveyor-MotorOff"/>
</ParameterBag>
</Resource>
<Resource Id="A_Conveyor-Occupied" Name="Conveyor - Occupied"
Type="PlcAddress">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String"
Index="5"
Value="Conveyor 1"/>
<Parameter Id="address" Name="HW Address" Type="String"
Interpretation="PlcConnection" Index="10"
Value="raspiBcmGpioInput.4"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress"
Type="String"
Index="20" Value="Conveyor"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String"
Index="30" Value="Occupied"/>
<Parameter Id="index" Name="Index" Type="Integer" Index="40"
Value="10"/>
<Parameter Id="value" Name="Value" Type="Boolean" Index="100"
Value="false"/>
</ParameterBag>
</Resource>
<Resource Id="T_Conveyor-MotorOn" Name="Conveyor - MotorOn"
Type="PlcTelegram">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String"
Index="5"
Value="Conveyor 1"/>
<Parameter Id="address" Name="HW Address" Type="String"
Interpretation="PlcConnection" Index="10"
Value="raspiBcmGpioOutput.18"/>
<Parameter Id="resource" Name="Resource ID for PlcAddress"
Type="String"
Index="20" Value="Conveyor"/>
<Parameter Id="action" Name="Action ID for PlcAddress" Type="String"
Index="30" Value="MotorOn"/>
<Parameter Id="index" Name="Index" Type="Integer" Index="40"
Value="10"/>
<Parameter Id="value" Name="Value" Type="Boolean" Index="100"
Value="true"/>
</ParameterBag>
</Resource>
</StrolchModel>
```
The PlcLogicalDevice references the PlcAddress and PlcTelegram objects, and is
then used in the UI for grouping.
The PlcAddress is used to store the current value and defines the keys with
which the agent will be notified
The PlcTelegram is used to store default values to send, for specific keys. E.g.
The action `On` would send true, and `Off` would send false. This is semantics, and
is defined in each project depending on the hardware.
10. Copy the
file [plc-state.xml](https://github.com/4treesCH/strolch-plc/blob/develop/example/plc-state.xml)
to your runtime and reference it by use of
a `<IncludeFile file="plc-state.xml" />` element. Modify the `PlcId` to be the
same as the one you defined in the `StrolchConfiguration.xml`.
11. Now that we have a model, the `PlcConnections` are to be defined. In the
previous example we used a Raspberry Pi's GPIOs. This needs to be defined as
a `PlcConnection`:
```xml
<StrolchModel>
<Resource Id="raspiBcmGpioOutput" Name="Raspi BCM GPIO Output"
Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String"
Value="li.strolch.plc.core.hw.gpio.RaspiBcmGpioOutputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String"
Interpretation="Enumeration" Uom="ConnectionState"
Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String"
Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean"
Value="false"/>
<Parameter Id="bcmOutputPins" Name="BCM Output Pins"
Type="IntegerList" Value="27"/>
</ParameterBag>
</Resource>
<Resource Id="raspiBcmGpioInput" Name="Raspi BCM GPIO Input"
Type="PlcConnection">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="className" Name="Connection Class" Type="String"
Value="li.strolch.plc.core.hw.gpio.RaspiBcmGpioInputConnection"/>
<Parameter Id="state" Name="Connection State" Type="String"
Interpretation="Enumeration" Uom="ConnectionState"
Value="Disconnected"/>
<Parameter Id="stateMsg" Name="Connection State Msg" Type="String"
Interpretation="Enumeration" Uom="ConnectionState"
Value=""/>
<Parameter Id="inverted" Name="Inverted" Type="Boolean"
Value="true"/>
<Parameter Id="bcmInputPins" Name="BCM Input Pins"
Type="IntegerList" Value="4"/>
</ParameterBag>
</Resource>
</StrolchModel>
```
See [strolch-plc-example-connections.xml](https://github.com/4treesCH/strolch-plc/blob/develop/example/strolch-plc-example-connections.xml) for further examples.

View File

@ -0,0 +1,31 @@
---
title: 'Tutorial'
weight: 40
---
## Let's build a bookshop!
In this tutorial we will build a book store using Strolch. This book store will
be without a UI, but we will do everything using REST APIs, which should make it
easy to add a UI later using whatever framework suits one most.
The book store will have the following features:
* The store owner can add, update and remove books
* The store owner can edit the stock quantity
* Users can view a list of books
* Users can add books to a virtual cart
* Users can create and verify an account using an e-mail address
* Users can submit an order for the books in their cart
* The store owner can see the orders by state (pending, preparing, sent)
* The store owner can update the state of an order (preparing, sent)
* Notify the user when the order is sent
The code to the book can be downloaded
from [GitHub](https://github.com/4treesCH/strolch-bookshop) and will be updated
as this tutorial is updated.
The tutorial consists of the following parts:
{{% children %}}

View File

@ -0,0 +1,805 @@
---
title: 'Configuration'
weight: 10
---
## Configuration
Let's start by creating a new Apache Maven project. We'll need a POM with the
proper dependencies. We expect you to be familiar with Apache Maven, so we'll
just show you a working POM file:
**pom.xml**
```xml
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>li.strolch</groupId>
<artifactId>strolch-bookshop</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>strolch-bookshop</name>
<description>Bookshop built on Strolch</description>
<inceptionYear>2017</inceptionYear>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
<buildTimestamp>${maven.build.timestamp}</buildTimestamp>
<jdk.version>1.8</jdk.version>
<jersey.version>2.25.1</jersey.version>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<petitparser.version>2.1.0</petitparser.version>
<hikaricp.version>2.7.1</hikaricp.version>
<postgresql.version>42.1.4</postgresql.version>
<gson.version>2.8.2</gson.version>
<annotation.version>1.3.1</annotation.version>
<javaxmail.version>1.6.0</javaxmail.version>
<serverlet.version>3.1.0</serverlet.version>
<jaxrs.api.version>2.1</jaxrs.api.version>
<junit.version>4.12</junit.version>
<hamcrest.version>1.3</hamcrest.version>
<mockito.version>2.0.8-beta</mockito.version>
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
<maven-source-plugin.version>3.0.1</maven-source-plugin.version>
<maven-jar-plugin.version>3.0.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.1.0</maven-war-plugin.version>
<strolch.version>1.6.0-SNAPSHOT</strolch.version>
<warFinalName>bookshop</warFinalName>
<m2eclipse.wtp.contextRoot>${warFinalName}</m2eclipse.wtp.contextRoot>
</properties>
<dependencies>
<!-- base -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- strolch -->
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.utils</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.privilege</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.model</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.agent</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.rest</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.service</artifactId>
<version>${strolch.version}</version>
</dependency>
<dependency>
<groupId>li.strolch</groupId>
<artifactId>li.strolch.testbase</artifactId>
<version>${strolch.version}</version>
<scope>test</scope>
</dependency>
<!-- utils -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- web -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${serverlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${jaxrs.api.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<!-- filter properties files, and copy the rest -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${warFinalName}</warName>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<!-- active when building on eitch's machines -->
<profile>
<id>m2e.eitchpc</id>
<activation>
<property>
<name>user.name</name>
<value>eitch</value>
</property>
<os>
<family>unix</family>
</os>
</activation>
<properties>
<strolch.env>dev.eitchpc</strolch.env>
</properties>
</profile>
</profiles>
</project>
```
Now we need the rest of the directory structure:
```text
../strolch-bookshop/
- src/main/java/
- li/strolch/bookshop/
- <!-- java classes -->
- src/main/resources/
- ENV.properties
- appVersion.properties
- logback.xml
- src/main/webapp/WEB-INF/
- StrolchBootstrap.xml
- runtime
- config/
- PrivilegeConfig.xml
- PrivilegeRoles.xml
- PrivilegeUsers.xml
- StrolchConfiguration.xml
- StrolchPolicies.xml
- data/
- StrolchModel.xsd
- defaultModel.xml
- templates.xml
- temp/
```
A few notes to the resource files:
* The `ENV.properties` file is filtered by maven and the environment to load is
written in it using the environment variable strolch.env.
* The `appVersion.properties` file is also filtered by maven and allows to reflect
on the version of this app at runtime.
* The `logback.xml` file configures logging using SLF4j and Logback.
The `StrolchBootstrap.xml` file is used to configure Strolch's environment and
root directory. For a webapp it can be annoying to store Strolch's configuration
inside the webapp, which is why we can define an absolute path where the
configuration is kept. In the following example we keep it in the root of the
sources:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<StrolchBootstrap>
<env id="dev.eitchpc" default="true">
<root>/home/eitch/src/git/strolch-bookshop/runtime</root>
<environment>dev</environment>
</env>
</StrolchBootstrap>
```
Here we define two environments, but the both redefine the environment to dev.
This is because we want this app to start on two different machines with
different user home directories. See the profiles in the POM as to how these
environments are activated using a environment property strolch.env.
In this next step we'll create Strolch's configuration at the location we
defined in the StrolchBootstrap.xml file. Strolch's configuration contains of
three directories: config, data and temp. config contains static files which
usually aren't changed, data contains model files in XML format and temp is used
at runtime for any temporary files, e.g. storing active sessions.
The configuration as well as the model has been described on Strolch's
documentation web page, we'll just provide you with the files for the bookshop:
**PrivilegeConfig.xml**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Privilege>
<Container>
<Parameters>
<!-- parameters for the container itself -->
<Parameter name="secretKey" value="45f251ce-d51f-4624-990a-8dcd5b181f0e"/>
<Parameter name="secretSalt" value="4770a32d-1512-4891-9a63-362504932500"/>
<Parameter name="persistSessions" value="true"/>
<Parameter name="autoPersistOnUserChangesData" value="false"/>
<Parameter name="privilegeConflictResolution" value="MERGE"/>
</Parameters>
<EncryptionHandler class="li.strolch.privilege.handler.DefaultEncryptionHandler">
<Parameters>
<Parameter name="hashAlgorithm" value="PBKDF2WithHmacSHA512"/>
<Parameter name="hashIterations" value="10000"/>
<Parameter name="hashKeyLength" value="256"/>
</Parameters>
</EncryptionHandler>
<PersistenceHandler class="li.strolch.privilege.handler.XmlPersistenceHandler">
<Parameters>
<Parameter name="usersXmlFile" value="PrivilegeUsers.xml"/>
<Parameter name="rolesXmlFile" value="PrivilegeRoles.xml"/>
</Parameters>
</PersistenceHandler>
<UserChallengeHandler class="li.strolch.privilege.handler.MailUserChallengeHandler">
</UserChallengeHandler>
</Container>
<Policies>
<Policy name="DefaultPrivilege" class="li.strolch.privilege.policy.DefaultPrivilege"/>
<Policy name="RoleAccessPrivilege" class="li.strolch.privilege.policy.RoleAccessPrivilege"/>
<Policy name="UserAccessPrivilege" class="li.strolch.privilege.policy.UserAccessPrivilege"/>
<Policy name="UserSessionAccessPrivilege" class="li.strolch.privilege.policy.UsernameFromCertificatePrivilege"/>
</Policies>
</Privilege>
```
**PrivilegeRoles.xml**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Roles>
<Role name="User">
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
<Allow>li.strolch.bookshop.search.BookSearch</Allow>
</Privilege>
<Privilege name="GetResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
<Role name="UserPrivileges">
<Privilege name="PrivilegeSetUserLocale" policy="UserAccessPrivilege" />
<Privilege name="PrivilegeSetUserPassword" policy="UserAccessPrivilege" />
</Role>
<!--
Internal
-->
<Role name="StrolchAdmin">
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeSetUserPassword" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
<Role name="agent">
<Privilege name="li.strolch.privilege.handler.SystemAction" policy="DefaultPrivilege">
<Allow>li.strolch.runtime.privilege.StrolchSystemAction</Allow>
<Allow>li.strolch.runtime.privilege.StrolchSystemActionWithResult</Allow>
<Allow>li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer</Allow>
</Privilege>
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="GetActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="AddActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="UpdateActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveResource" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveOrder" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="RemoveActivity" policy="ModelPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeAction" policy="DefaultPrivilege">
<Allow>Persist</Allow>
<Allow>PersistSessions</Allow>
<Allow>GetCertificates</Allow>
</Privilege>
<Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeModifyUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
<Privilege name="PrivilegeGetUser" policy="UserAccessPrivilege">
<AllAllowed>true</AllAllowed>
</Privilege>
</Role>
</Roles>
```
**PrivilegeUsers.xml**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Users>
<User userId="U10" username="jill" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918">
<Firstname>Jill</Firstname>
<Lastname>Someone</Lastname>
<State>ENABLED</State>
<Locale>en-GB</Locale>
<Roles>
<Role>User</Role>
<Role>UserPrivileges</Role>
</Roles>
<Properties>
<Property name="email" value="eitch+jill@eitchnet.ch" />
</Properties>
</User>
<User userId="U01" username="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918">
<Firstname>Jill</Firstname>
<Lastname>Someone</Lastname>
<State>ENABLED</State>
<Locale>en-GB</Locale>
<Roles>
<Role>StrolchAdmin</Role>
<Role>UserPrivileges</Role>
</Roles>
<Properties>
<Property name="email" value="eitch+admin@eitchnet.ch" />
</Properties>
</User>
<!--
Internal
-->
<User userId="S01" username="agent">
<State>SYSTEM</State>
<Roles>
<Role>agent</Role>
</Roles>
</User>
</Users>
```
**StrolchConfiguration.xml**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<StrolchConfiguration>
<env id="global">
<Runtime>
<applicationName>Bookshop</applicationName>
<Properties>
<locale>en</locale>
<verbose>true</verbose>
</Properties>
</Runtime>
<Component>
<name>PrivilegeHandler</name>
<api>li.strolch.runtime.privilege.PrivilegeHandler</api>
<impl>li.strolch.runtime.privilege.DefaultStrolchPrivilegeHandler</impl>
<Properties>
<privilegeConfigFile>PrivilegeConfig.xml</privilegeConfigFile>
</Properties>
</Component>
<Component>
<name>RealmHandler</name>
<api>li.strolch.agent.api.RealmHandler</api>
<impl>li.strolch.agent.impl.DefaultRealmHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<realms>defaultRealm</realms>
<dataStoreMode>TRANSIENT</dataStoreMode>
<dataStoreFile>defaultModel.xml</dataStoreFile>
<enableObserverUpdates>true</enableObserverUpdates>
</Properties>
</Component>
<Component>
<name>ServiceHandler</name>
<api>li.strolch.service.api.ServiceHandler</api>
<impl>li.strolch.service.api.DefaultServiceHandler</impl>
<depends>RealmHandler</depends>
<depends>PrivilegeHandler</depends>
<Properties>
<verbose>true</verbose>
</Properties>
</Component>
<Component>
<name>PolicyHandler</name>
<api>li.strolch.policy.PolicyHandler</api>
<impl>li.strolch.policy.DefaultPolicyHandler</impl>
<Properties>
<readPolicyFile>true</readPolicyFile>
</Properties>
</Component>
<Component>
<name>ExecutionHandler</name>
<api>li.strolch.execution.ExecutionHandler</api>
<impl>li.strolch.execution.EventBasedExecutionHandler</impl>
<depends>RealmHandler</depends>
<depends>PrivilegeHandler</depends>
</Component>
<Component>
<name>RestfulHandler</name>
<api>li.strolch.rest.RestfulStrolchComponent</api>
<impl>li.strolch.rest.RestfulStrolchComponent</impl>
<depends>SessionHandler</depends>
<Properties>
<secureCookie>false</secureCookie>
<restLogging>false</restLogging>
<restLoggingEntity>false</restLoggingEntity>
<restTracing>ALL</restTracing>
</Properties>
</Component>
<Component>
<name>SessionHandler</name>
<api>li.strolch.rest.StrolchSessionHandler</api>
<impl>li.strolch.rest.DefaultStrolchSessionHandler</impl>
<depends>PrivilegeHandler</depends>
<Properties>
<session.ttl.minutes>30</session.ttl.minutes>
<session.reload>true</session.reload>
</Properties>
</Component>
<Component>
<name>MailHandler</name>
<api>li.strolch.handler.mail.MailHandler</api>
<impl>li.strolch.handler.mail.SmtpMailHandler</impl>
<Properties>
<fromAddr>relayer@eitchnet.ch</fromAddr>
<fromName>Susi</fromName>
<overrideRecipients><![CDATA[IPSC Test <eitch@eitchnet.ch>]]></overrideRecipients>
<recipientWhitelist>eitch@eitchnet.ch</recipientWhitelist>
<username>test</username>
<password>test</password>
<auth>true</auth>
<startTls>true</startTls>
<host>smtp.gmail.com</host>
<port>587</port>
</Properties>
</Component>
</env>
<env id="dev">
<!-- overrides go here -->
</env>
</StrolchConfiguration>
```
**StrolchPolicies.xml**
```xml
<StrolchPolicies>
<PolicyType Type="ExecutionPolicy" Api="li.strolch.execution.policy.ExecutionPolicy">
<Policy Key="DurationExecution" Class="li.strolch.execution.policy.DurationExecution" />
<Policy Key="ReservationExection" Class="li.strolch.execution.policy.ReservationExecution" />
</PolicyType>
<PolicyType Type="ConfirmationPolicy" Api="li.strolch.execution.policy.ConfirmationPolicy">
<Policy Key="DefaultConfirmation" Class="li.strolch.execution.policy.ConfirmationPolicy" />
</PolicyType>
<PolicyType Type="ActivityArchivalPolicy" Api="li.strolch.execution.policy.ActivityArchivalPolicy">
<Policy Key="DefaultActivityArchival" Class="li.strolch.execution.policy.ActivityArchivalPolicy" />
</PolicyType>
</StrolchPolicies>
```
A few notes on the configuration:
* Note how there are three users. Jill is a user with currently no privileges as
it's role definition is empty. Admin can do everything, and the agent user is
a system user which can also do everything.
* There is one realm defined in the `RealmHandler` component which references
the
`defaultModel.xml` file in the `data` directory. This file then includes the
currently still empty `templates.xml` file.
* We have defined a `global` environment, but are using the `dev` environment.
The dev environment includes the definitions in the global environment.
* In `PrivilegeConfig.xml` we have enabled persistence of sessions, so you will
be needing the unlimited JCE libraries for your JVM.
When you restart the server, you don't need to log back in, if your session is
still alive.
* In `PrivilegeRoles.xml` there seems to be a lot of boilerplate. One thing about
a highly configurable system is that sometimes the configuration is bigger. In
this case we have opted to have the configuration shown and not use default
values which you don't see, so that privilege access is clearly seen.
Your project is now ready to be imported into your favourite IDE. We have used
both IntelliJ and Eclipse so this is up to you.
Now that we have a configuration, it is time to have Strolch started when the
WAR is deployed and started. In your IDE create a new class as follows:
**StartupListener.java**
```java
package li.strolch.bookshop.web;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.InputStream;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.agent.api.StrolchBootstrapper;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WebListener
public class StartupListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(StartupListener.class);
private static final String APP_NAME = "Bookshop";
private StrolchAgent agent;
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("Starting " + APP_NAME + "...");
long start = System.currentTimeMillis();
try {
String boostrapFileName = "/WEB-INF/" + StrolchBootstrapper.FILE_BOOTSTRAP;
InputStream bootstrapFile = sce.getServletContext().getResourceAsStream(boostrapFileName);
StrolchBootstrapper bootstrapper = new StrolchBootstrapper(StartupListener.class);
this.agent = bootstrapper.setupByBoostrapFile(StartupListener.class, bootstrapFile);
this.agent.initialize();
this.agent.start();
} catch (Throwable e) {
logger.error("Failed to start " + APP_NAME + " due to: " + e.getMessage(), e);
throw e;
}
long took = System.currentTimeMillis() - start;
logger.info("Started " + APP_NAME + " in " + (StringHelper.formatMillisecondsDuration(took)));
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (this.agent != null) {
logger.info("Destroying " + APP_NAME + "...");
try {
this.agent.stop();
this.agent.destroy();
} catch (Throwable e) {
logger.error("Failed to stop " + APP_NAME + " due to: " + e.getMessage(), e);
throw e;
}
}
logger.info("Destroyed " + APP_NAME);
}
}
```
Now configure your IDE to start the web project, and then once it has started,
you should see the following in the logs:
```text
Bookshop:dev All 8 Strolch Components started. Took 44ms. Strolch is now ready to be used. Have fun =))
```
This log tells us the name of the app as defined in the StrolchConfiguration.xml
file as well as which environment was loaded. Further we can see that 8
components were configured and started.
This concludes the initial setup of a new Strolch project. We can now go ahead
and start building the business logic.

View File

@ -0,0 +1,560 @@
---
title: 'CRUD Book'
weight: 30
---
## Preparation
Since Books are central to the bookshop, we'll first create the CRUD REST API
for them. The API will be as follows:
```text
GET ../rest/books?query=,offset=,limit=
GET ../rest/books/{id}
POST ../rest/books
PUT ../rest/books/{id}
DELETE ../rest/books/{id}
```
Thus corresponding with querying, getting, creating, updating and removing of
books. So let's go ahead and add these REST APIs to our project.
Our project is using JAX-RS 2.0 as the API and Jersey 2.x as the implementation,
thus first we need to configure JAX-RS. Thus create the following class:
```java
@ApplicationPath("rest")
public class RestfulApplication extends ResourceConfig {
public RestfulApplication() {
// add strolch resources
register(AuthenticationService.class);
register(ModelQuery.class);
register(Inspector.class);
// add project resources by package name
packages(BooksResource.class.getPackage().getName());
// filters
register(AuthenticationRequestFilter.class, Priorities.AUTHENTICATION);
register(AccessControlResponseFilter.class);
register(AuthenticationResponseFilter.class);
register(HttpCacheResponseFilter.class);
// log exceptions and return them as plain text to the caller
register(StrolchRestfulExceptionMapper.class);
// the JSON generated is in UTF-8
register(CharsetResponseFilter.class);
RestfulStrolchComponent restfulComponent = RestfulStrolchComponent.getInstance();
if (restfulComponent.isRestLogging()) {
register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
Level.SEVERE, LoggingFeature.Verbosity.PAYLOAD_ANY, Integer.MAX_VALUE));
property(ServerProperties.TRACING, "ALL");
property(ServerProperties.TRACING_THRESHOLD, "TRACE");
}
}
}
```
As we add new resources they will be automatically since we register the entire package.
Now add the books resource class:
```java
@Path("books")
public class BooksResource {
}
```
## Search
The first service we'll add is to query, or search for the existing books. The
API defines three parameters, with which the result can be controlled. The
method can be defined as follows:
```java
@Path("books")
public class BooksResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response query(@Context HttpServletRequest request,
@QueryParam("query") String queryS,
@QueryParam("offset") String offsetS,
@QueryParam("limit") String limitS) {
// TODO
}
}
```
To fill this method we need a few things. First let's define a constants class where we keep String constants which we used in the model file:
```java
public class BookShopConstants {
public static final String TYPE_BOOK = "Book";
}
```
As this tutorial progresses, more and more constants will be added here. This
class helps with two issues: Through the constants we can easily reason over
where certain fields, and types are used and of course String literals in code
are a rather bad thing.
In Strolch there are multiple way to access objects. The old way was using
Queries, the new search API is much more fluent and easier to read and write.
The search API, as well as the deprecated query API allows us to implement
privilege validation and thus one should create corresponding classes for each
type of search. Book entities are Resources, thus we will be creating a
`ResourceSearch`. The search is for Resources of type Book thus the resulting
search looks as follows:
```java
public class BooksSearch<U> extends ResourceSearch<U> {
public BookSearch() {
types(TYPE_BOOK);
}
public BookSearch stringQuery(String value) {
if (isEmpty(value))
return this;
// split by spaces
value = value.trim();
String[] values = value.split(" ");
// add where clauses for id, name and description
where(id().containsIgnoreCase(values) //
.or(name().containsIgnoreCase(values)) //
.or(param(BAG_PARAMETERS, PARAM_DESCRIPTION).containsIgnoreCase(values)));
return this;
}
}
```
Note how we added a special `stringQuery(String)`-method. This method defines
where a search string entered by the user will be used to match a book. In this
case for `id`, `name` and the `description` parameter.
So that our users can call this query, we must give them this as a privilege.
This is done by adding the full class name to the `PrivilegeRoles.xml` file as
follows:
```xml
...
<Role name="User">
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
<Allow>internal</Allow>
<Allow>li.strolch.bookshop.search.BookSearch</Allow>
</Privilege>
</Role>
...
```
{{% notice tip %}} Note: The `internal` allow value is a special privilege which
is used internally when a service or something performs internal queries. This
means that a service can perform a query for object to which the user might not
have access, but without which the service could not be completed. We will use
this in a later stage. {{% /notice %}}
Now we have all parts we need to implement the query method. The method will
include opening a transaction, instantiating the search, executing the search,
and returning the result:
```java
@Path("books")
public class BooksResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response query(@Context HttpServletRequest request,
@QueryParam("query") String queryS,
@QueryParam("offset") String offsetS,
@QueryParam("limit") String limitS) {
// this is an authenticated method call, thus we can get the certificate from the request:
Certificate cert = (Certificate) request
.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
int offset = StringHelper.isNotEmpty(offsetS) ? Integer.parseInt(offsetS) : 0;
int limit = StringHelper.isNotEmpty(limitS) ? Integer.parseInt(limitS) : 0;
// open the TX with the certificate, using this class as context
Paging<Resource> paging;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance()
.openTx(cert, getClass())) {
// perform a book search
paging = new BookSearch() //
.stringQuery(queryS) //
.search(tx) //
.orderByName(false) //
.toPaging(offset, limit);
}
ResourceVisitor<JsonObject> visitor = new StrolchRootElementToJsonVisitor()
.flat().asResourceVisitor();
return ResponseUtil.toResponse(paging, e -> e.accept(visitor));
}
}
```
{{% notice tip %}} Note: We automatically transform the Resource objects to JSON
using the `StrolchElementToJsonVisitor`. By calling the method `.flat()`-method we have a
more compact JSON format. Paging is handled by a util class.
{{% /notice %}}
The helper class `ResponseUtil` takes care of creating the `JsonObject` and the
proper page. As a rule we use the format where we return two fields: `msg` is a
dash if all is ok, otherwise an error message will be present. Data is always in
the `data` field. This is just a personal taste, and can be changed to one's own
taste.
## Get
We have all we need now to implement the GET method:
```java
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@Context HttpServletRequest request, @PathParam("id") String id) {
// this is an authenticated method call, thus we can get the certificate from the request:
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
// open the TX with the certificate, using this class as context
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) {
// get the book
Resource book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, id);
if (book == null)
return ResponseUtil.toResponse(Status.NOT_FOUND, "Book " + id + " does not exist!");
// transform to JSON
JsonObject bookJ = book.accept(new StrolchRootElementToJsonVisitor().flat());
// return
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, bookJ);
}
}
```
{{% notice tip %}} Note how we simply retrieve the book as a Resource from the
TX. This is a good moment to familiarize yourself with the API of the
`StrolchTransaction`. There are methods to retrieve elements, and also perform
searches. We will use more of these methods later. {{% /notice %}}
Further it can be noted that a simple retrieval isn't validated against the
user's privileges, the user is authenticated, which is enough for the moment.
## Create
To create a new book we need to implement a `Service`. This service will be called
`CreateBookService`. A Service always has a `ServiceArgument` and a `ServiceResult`.
Our service will use the `JsonServiceArgument` and the `JsonServiceResult`. The
implementation of the POST method is as follows:
```java
@POST
@Produces(MediaType.APPLICATION_JSON)
public Response create(@Context HttpServletRequest request, String data) {
// this is an authenticated method call, thus we can get the certificate from the request:
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
// parse data to JSON
JsonObject jsonData = JsonParser.parseString(data).getAsJsonObject();
// instantiate the service with the argument
CreateBookService svc = new CreateBookService();
JsonServiceArgument arg = svc.getArgumentInstance();
arg.jsonElement = jsonData;
// perform the service
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
JsonServiceResult result = serviceHandler.doService(cert, svc, arg);
// return depending on the result state
if (result.isOk())
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, result.getResult());
return ResponseUtil.toResponse(result);
}
```
{{% notice tip %}}
Note: We return the created object again as JSON in its own data field.
{{% /notice %}}
The service is implemented as follows:
```java
public class CreateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> {
@Override
protected JsonServiceResult getResultInstance() {
return new JsonServiceResult();
}
@Override
public JsonServiceArgument getArgumentInstance() {
return new JsonServiceArgument();
}
@Override
protected JsonServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
// open a new transaction, using the realm from the argument, or the certificate
Resource book;
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
// get a new book "instance" from the template
book = tx.getResourceTemplate(BookShopConstants.TYPE_BOOK);
// map all values from the JSON object into the new book element
book.accept(new FromFlatJsonVisitor(arg.jsonElement.getAsJsonObject()).ignoreBag(BAG_RELATIONS));
// save changes
tx.add(book);
// notify the TX that it should commit on close
tx.commitOnClose();
}
// map the return value to JSON
JsonObject result = book.accept(new StrolchElementToJsonVisitor().flat());
// and return the result
return new JsonServiceResult(result);
}
}
```
{{% notice tip %}}
Note: For the authenticated user to be able to perform this service, we must add it to their privileges:
{{% /notice %}}
```xml
...
<Role name="User">
...
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
<Allow>li.strolch.bookshop.service.CreateBookService</Allow>
</Privilege>
...
</Role>
...
```
## Update
Updating of a book is basically the same as the creation, we just use PUT,
verify that the book exists and give the user the privilege.
**PUT Method:**
```java
@PUT
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response update(@Context HttpServletRequest request, @PathParam("id") String id, String data) {
// this is an authenticated method call, thus we can get the certificate from the request:
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
// parse data to JSON
JsonObject jsonData = JsonParser.parseString(data).getAsJsonObject();
// instantiate the service with the argument
UpdateBookService svc = new UpdateBookService();
JsonServiceArgument arg = svc.getArgumentInstance();
arg.objectId = id;
arg.jsonElement = jsonData;
// perform the service
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
JsonServiceResult result = serviceHandler.doService(cert, svc, arg);
// return depending on the result state
if (result.isOk())
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, result.getResult());
return ResponseUtil.toResponse(result);
}
```
**Update Service:**
```java
public class UpdateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> {
@Override
protected JsonServiceResult getResultInstance() {
return new JsonServiceResult();
}
@Override
public JsonServiceArgument getArgumentInstance() {
return new JsonServiceArgument();
}
@Override
protected JsonServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
// verify same book
DBC.PRE.assertEquals("ObjectId and given Id must be same!", arg.objectId,
arg.jsonElement.getAsJsonObject().get(Json.ID).getAsString());
// open a new transaction, using the realm from the argument, or the certificate
Resource book;
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
// get the existing book
book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, arg.objectId, true);
// map all values from the JSON object into the new book element
book.accept(new FromFlatJsonVisitor(arg.jsonElement.getAsJsonObject()).ignoreBag(BAG_RELATIONS));
// save changes
tx.update(book);
// notify the TX that it should commit on close
tx.commitOnClose();
}
// map the return value to JSON
JsonObject result = book.accept(new StrolchElementToJsonVisitor().flat());
// and return the result
return new JsonServiceResult(result);
}
}
```
**Privilege**
```xml
...
<Role name="User">
...
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
...
<Allow>li.strolch.bookshop.service.UpdateBookService</Allow>
...
</Privilege>
...
</Role>
...
```
## Remove
To remove a book, we need a DELETE method, a remove service and the associated
privilege.
**DELETE Method:**
```java
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response update(@Context HttpServletRequest request, @PathParam("id") String id) {
// this is an authenticated method call, thus we can get the certificate from the request:
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
// instantiate the service with the argument
RemoveBookService svc = new RemoveBookService();
StringServiceArgument arg = svc.getArgumentInstance();
arg.value = id;
// perform the service
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
ServiceResult result = serviceHandler.doService(cert, svc, arg);
// return depending on the result state
return ResponseUtil.toResponse(result);
}
```
**Remove Service:**
```java
public class RemoveBookService extends AbstractService<StringServiceArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public StringServiceArgument getArgumentInstance() {
return new StringServiceArgument();
}
@Override
protected ServiceResult internalDoService(StringServiceArgument arg) throws Exception {
// open a new transaction, using the realm from the argument, or the certificate
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
// get the existing book
Resource book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, arg.value, true);
// save changes
tx.remove(book);
// notify the TX that it should commit on close
tx.commitOnClose();
}
// and return the result
return ServiceResult.success();
}
}
```
**Privilege:**
```xml
...
<Role name="User">
...
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
...
<Allow>li.strolch.bookshop.service.RemoveBookService</Allow>
...
</Privilege>
...
</Role>
...
```
## Notes
One should now see a pattern emerge:
* The REST API delegates to the Services, or Searches, with the exception of the
retrieval of a single object by id.
* Services should do initial validation of the input. Not much validation was
done here, but more could be done.
* Commands are reusable objects to perform recurring work.
* Searches and Services are privileged actions for which a user must have the
privilege to perform the action.
The book services are quite simple, but as more requirements arise, it should be
easy to implement them in the service layer. Thus should a service be required
to be performed by an integration layer, then they can simply call the services,
since the input is defined and validation is done there (i.e. NOT in the REST
API).
This concludes the CRUD of books.

203
content/tutorial/model.md Normal file
View File

@ -0,0 +1,203 @@
---
title: 'Model'
weight: 20
---
## Model
Looking back at our functionality, we can list the following entities that need
to be modelled (We'll go into detail further down):
* Book → books can be orderd
* UserCart → we want to store the cart of the user
* Account → we need to know where to send the orders
* PurchaseOrder → we need to know what was ordered and keep track of its state
* FromStock → we want to use activities to implement the process of an order
In Strolch we model entities by defining the element as a template. Thus in the
`templates.xml` file we can add the templates with the following content:
**Book**
```xml
<Resource Id="Book" Name="Book Template" Type="Template">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="description" Name="Description" Type="String" Value=""/>
<Parameter Id="quantity" Name="Quantity in Stock" Type="Integer" Value="0"/>
</ParameterBag>
</Resource>
```
**Account**
```xml
<Resource Id="Account" Name="Account Template" Type="Template">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="user" Name="User" Type="String" Value=""/>
<Parameter Id="firstName" Name="First Name" Type="String" Value=""/>
<Parameter Id="lastName" Name="Last Name" Type="String" Value=""/>
<Parameter Id="email" Name="E-Mail" Type="String" Value=""/>
</ParameterBag>
<ParameterBag Name="Address" Id="address" Type="Address">
<Parameter Id="phone" Name="Telephone Number" Type="String" Value=""/>
<Parameter Id="street" Name="Street" Type="String" Value=""/>
<Parameter Id="city" Name="City" Type="String" Value=""/>
<Parameter Id="zip" Name="Postal Code" Type="String" Value=""/>
<Parameter Id="country" Name="Country" Type="String" Value=""/>
</ParameterBag>
</Resource>
```
**UserCart**
```xml
<Resource Id="UserCart" Name="UserCart Template" Type="Template">
<ParameterBag Id="books" Name="Books" Type="Book">
<!-- Parameter Id="bookId" Name="Book reference" Type="Float" Value="0" / -->
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="account" Name="Account" Type="String"
Interpretation="Resource-Ref" Uom="Account" Value=""/>
</ParameterBag>
</Resource>
```
**PurchaseOrder**
```xml
<Order Id="PurchaseOrder" Name="PurchaseOrder Template" Type="Template"
State="Created">
<ParameterBag Id="books" Name="Books" Type="Book">
<!-- Parameter Id="bookId" Name="Book reference" Type="Float" Value="0" / -->
</ParameterBag>
<ParameterBag Id="relations" Name="Relations" Type="Parameters">
<Parameter Id="account" Name="Account" Type="String"
Interpretation="Resource-Ref" Uom="Account" Value=""/>
</ParameterBag>
</Order>
```
**FromStock**
```xml
<Activity Id="FromStock" Name="From Stock Template" Type="FromStock"
TimeOrdering="Series">
<ParameterBag Name="objectives" Id="Objectives" Type="Objectives">
<Parameter Name="Duration" Id="duration" Value="PT1MS" Type="Duration"/>
</ParameterBag>
<Action Id="validate" Name="Validation of order" Type="Use"
ResourceType="Validation" ResourceId="validation"/>
<!-- for each book we do a consume, i.e. reduce the stock quantity -->
<Action Id="Consume" Name="Consume Template for book" Type="Template">
<ParameterBag Id="parameters" Name="Parameters" Type="Parameters">
<Parameter Id="quantity" Name="Quantity" Type="Float" Value="0"/>
</ParameterBag>
</Action>
<Action Id="package" Name="Packaging of PurchaseOrder" Type="Use"
ResourceType="Packaging" ResourceId="packaging"/>
<Action Id="send" Name="Sending of package" Type="Use" ResourceType="Sending"
ResourceId="sending"/>
</Activity>
```
Let's explain a few things:
* The `Book` entity is a `Resource` object and only contains the description and the
current quantity in stock.
* The `Account` entity is a Resource and contains the address and further details
of the user, and with the `user` parameter the username is defined, thus
referencing the real user.
* The `UserCart` entity is a Resource and has a reference to the `account` Resource.
{{% notice tip %}}
Note how the reference is done using a StringParameter, where `Interpretation`,
`UOM` and the `value` is set in a specific manner.
{{% /notice %}}
* The `UserCart` entity is a Resource and references `books` using a special
ParameterBag with the type set to `Book`, the actual type of the book entity.
Each Parameter is of type Float and the ID of the parameter is the ID of the
book, and the value is the quantity that the user would like to purchase.
There will only be one cart per user/account.
* The `PurchaseOrder` entity is an `Order` object, and is basically a copy of the
UserCart entity. This is the confirmed purchase order for the contents of a
cart, and can then be used for reports on how much of which book was sold.
* The `FromStock` entity is an `Activity` object and defines the process we will go
through when delivering a purchase to a user.
{{% notice tip %}}
Note how the activity has a
ParameterBag `objectives` with a `duration` parameter. This defines for
this activity how long each `Action` should execute. This can be overridden in
each Action and can help to plan how much effort goes into the delivering of
each PurchaseOrder.
{{% /notice %}}
{{% notice tip %}}
Further note how the activity has three special actions (`validate`, `package` and
`send`) on which a `ResourceType` and `ResourceId` are defined. Actions are always
performed on a Resource, as the referenced Resource defines the behaviour of
the action through defined `Policy` objects.
{{% /notice %}}
* For each book which will be purchased, an Action will be created of type
`Consume`. In the template this is defined by a template `Action` with the id
`Consume` and will later be changed accordingly.
Since we are referencing resources from actions in the activity, we need to add
these as well, but not as templates. They can be added to the `defaultModel.xml`
file:
```xml
<Resource Id="validation" Name="Validation Resource" Type="Validation">
<Policies>
<Policy Type="ExecutionPolicy" Value="key:ValidationExecution" />
<Policy Type="ConfirmationPolicy" Value="key:DefaultConfirmation" />
</Policies>
</Resource>
<Resource Id="packaging" Name="Packaging Resource" Type="Packaging">
<Policies>
<Policy Type="ExecutionPolicy" Value="key:PackagingExecution" />
<Policy Type="ConfirmationPolicy" Value="key:DefaultConfirmation" />
</Policies>
</Resource>
<Resource Id="sending" Name="Sending Resource" Type="Sending">
<Policies>
<Policy Type="ExecutionPolicy" Value="key:SendingExecution" />
<Policy Type="ConfirmationPolicy" Value="key:DefaultConfirmation" />
</Policies>
</Resource>
```
What should now be noted by these three new Resources is that they have Policy
definitions:
* `ExecutionPolicy` → defines how an action on this resource is executed by
referencing an ExecutionPolicy implementation.
* `ConfirmationPolicy` → defines behaviour to be performed on every state change
of an action being performed on this resource by referencing an
ConfirmationPolicy implementation.
Currently these resources reference policies which don't exist. We will resolve
this issue later, when we implement the execution of the activity.
This concludes the model definition. In the next step we'll start creating
services and commands for our model.

View File

@ -0,0 +1,16 @@
{{/* https://discourse.gohugo.io/t/how-can-i-add-rel-nofollow-or-target-blank-or-rel-external-or-other-similar-attributes-to-specific-links-in-my-posts-in-markdown/32131/7 */}}
{{- $parsedTitle := dict -}}
{{- with .Title -}}
{{- $parsedTitle = partial "functions/parse-title-attribute.html" . -}}
{{- end -}}
<a href="{{ .Destination | safeURL }}"
{{- with $parsedTitle.title }} title="{{ . }}"{{- end -}}
{{- with $parsedTitle.attributes -}}
{{- range $k, $v := . -}}
{{- printf " %s=%q" $k $v | safeHTMLAttr -}}
{{- end -}}
{{- end -}}
>{{ .Text | safeHTML }}</a>
{{- /* Strip trailing space. */ -}}

View File

@ -0,0 +1,23 @@
{{/* Split .Title into two parts, title and attributes. */}}
{{- $parts := split . "{" -}}
{{- $parts = apply $parts "trim" "." " " -}}
{{- $parts = apply $parts "trim" "." "}" -}}
{{/* Extract title into a string. */}}
{{- $title := index $parts 0 -}}
{{/* Extract attributes into a dictionary. */}}
{{- $temp := index $parts 1 -}}
{{- $temp = split $temp "'" -}}
{{- $temp = first (sub (len $temp) 1) $temp -}}
{{- $temp = apply $temp "replace" "." "=" "" -}}
{{- $temp = apply $temp "trim" "." " " -}}
{{- $attributes := dict -}}
{{- if $temp -}}
{{- range (seq 0 2 (sub (len $temp) 1)) -}}
{{- $attributes = merge $attributes (dict (index $temp . ) (index $temp (add 1 .))) -}}
{{- end -}}
{{- end -}}
{{- $parsedTitle := dict "title" $title "attributes" $attributes -}}
{{- return $parsedTitle -}}

View File

@ -0,0 +1,28 @@
<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>
{{- if .Get "link" -}}
<a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
{{- end -}}
<img src="{{ .Get "src" }}"
{{- if or (.Get "alt") (.Get "caption") }}
alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
{{- end -}}
{{- with .Get "width" }} width="{{ . }}"{{ end -}}
{{- with .Get "height" }} height="{{ . }}"{{ end -}}
/><!-- Closing img tag -->
{{- if .Get "link" }}</a>{{ end -}}
{{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
<figcaption>
{{ with (.Get "title") -}}
<h4>{{ . }}</h4>
{{- end -}}
{{- if or (.Get "caption") (.Get "attr") -}}<p>
{{- .Get "caption" | markdownify -}}
{{- with .Get "attrlink" }}
<a href="{{ . }}">
{{- end -}}
{{- .Get "attr" | markdownify -}}
{{- if .Get "attrlink" }}</a>{{ end }}</p>
{{- end }}
</figcaption>
{{- end }}
</figure>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<svg width="256px" height="108px" viewBox="0 0 256 108" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M152.983963,37.213516 C147.387032,37.213516 143.218557,39.9617169 143.218557,46.5756347 C143.218557,51.5588676 145.965589,55.0189589 152.682374,55.0189589 C158.37516,55.0189589 162.242046,51.6640731 162.242046,46.3698995 C162.242046,40.3696804 158.781954,37.213516 152.983963,37.213516 L152.983963,37.213516 L152.983963,37.213516 Z M141.793607,83.9153973 C140.469187,85.5402374 139.149443,87.2679452 139.149443,89.3054247 C139.149443,93.3722009 144.334904,94.5960913 151.458484,94.5960913 C157.359342,94.5960913 165.397041,94.1822831 165.397041,88.6964018 C165.397041,85.435032 161.530155,85.2339726 156.643945,84.9277078 L141.793607,83.9153973 L141.793607,83.9153973 Z M171.906922,37.5209498 C173.73516,39.8611872 175.670941,43.1178813 175.670941,47.7971872 C175.670941,59.089242 166.819653,65.7008219 154.004457,65.7008219 C150.745425,65.7008219 147.794995,65.2951963 145.965589,64.7867032 L142.607196,70.1778995 L152.576,70.7869224 C170.178046,71.9091142 180.551306,72.4187763 180.551306,85.9435251 C180.551306,97.6458813 170.278575,104.255123 152.576,104.255123 C134.162703,104.255123 127.14316,99.5758174 127.14316,91.5392877 C127.14316,86.9605114 129.178301,84.5244201 132.738922,81.1613516 C129.38053,79.7422466 128.263014,77.1997808 128.263014,74.4515799 C128.263014,72.2118721 129.38053,70.1778995 131.214612,68.2432877 C133.045187,66.3133516 135.07916,64.3775708 137.521096,62.1402009 C132.536694,59.6982648 128.771507,54.4075982 128.771507,46.8783927 C128.771507,35.1807123 136.50411,27.1465205 152.066338,27.1465205 C156.441717,27.1465205 159.088219,27.5486393 161.428457,28.1635068 L181.267872,28.1635068 L181.267872,36.8078904 L171.906922,37.5209498 L171.906922,37.5209498 Z" fill="#2F2707"></path>
<path d="M199.165662,19.0340091 C193.365333,19.0340091 190.009279,15.6744475 190.009279,9.87294977 C190.009279,4.0796347 193.365333,0.92347032 199.165662,0.92347032 C205.066521,0.92347032 208.423744,4.0796347 208.423744,9.87294977 C208.423744,15.6744475 205.066521,19.0340091 199.165662,19.0340091 L199.165662,19.0340091 L199.165662,19.0340091 Z M186.039525,80.1712511 L186.039525,72.1382283 L191.229662,71.4286758 C192.654612,71.2229406 192.85684,70.9190137 192.85684,69.3900274 L192.85684,39.4801096 C192.85684,38.3637626 192.552913,37.6483653 191.53242,37.3456073 L186.039525,35.4109954 L187.15821,27.1734064 L208.219178,27.1734064 L208.219178,69.3900274 C208.219178,71.0207123 208.31737,71.2229406 209.847525,71.4286758 L215.036493,72.1382283 L215.036493,80.1712511 L186.039525,80.1712511 L186.039525,80.1712511 Z" fill="#2F2707"></path>
<path d="M255.267068,76.2272146 C250.890521,78.3617169 244.482338,80.2951598 238.68084,80.2951598 C226.575196,80.2951598 221.998758,75.4171324 221.998758,63.9158356 L221.998758,37.2626119 C221.998758,36.653589 221.998758,36.2456256 221.181662,36.2456256 L214.061589,36.2456256 L214.061589,27.1909406 C223.016913,26.1716164 226.575196,21.6945388 227.693881,10.6058813 L237.359927,10.6058813 L237.359927,25.0564384 C237.359927,25.7659909 237.359927,26.0734247 238.174685,26.0734247 L252.517699,26.0734247 L252.517699,36.2456256 L237.359927,36.2456256 L237.359927,60.5586119 C237.359927,66.5611689 238.786046,68.8990685 244.276603,68.8990685 C247.12884,68.8990685 250.075763,68.188347 252.517699,67.2730594 L255.267068,76.2272146" fill="#2F2707"></path>
<path d="M104.528658,49.5295708 L58.0126393,3.01705936 C55.3357443,0.337826484 50.990758,0.337826484 48.3103562,3.01705936 L38.6513242,12.6760913 L50.9042557,24.9290228 C53.7518174,23.9669772 57.0166941,24.6122374 59.2867945,26.8823379 C61.5674155,29.1664658 62.208,32.4593973 61.2225753,35.3163105 L73.0313059,47.1250411 C75.8882192,46.1407854 79.1846575,46.777863 81.4664475,49.0631598 C84.6553425,52.2508858 84.6553425,57.4176438 81.4664475,60.6077078 C78.2763836,63.7977717 73.1096256,63.7977717 69.9183927,60.6077078 C67.5197078,58.2066849 66.9270502,54.6834703 68.141589,51.7283653 L57.1289132,40.7156895 L57.1277443,69.6962922 C57.9050959,70.0820457 58.6391963,70.5952146 59.2867945,71.2404749 C62.4756895,74.4282009 62.4756895,79.5949589 59.2867945,82.7873607 C56.0967306,85.9762557 50.9276347,85.9762557 47.7422466,82.7873607 C44.5533516,79.5949589 44.5533516,74.4282009 47.7422466,71.2404749 C48.5301187,70.4537717 49.4418995,69.8587763 50.4144658,69.4589954 L50.4144658,40.2083653 C49.4418995,39.8109224 48.5312877,39.2194338 47.7422466,38.4268858 C45.3260274,36.0141735 44.7450594,32.4687489 45.9829772,29.5019543 L33.9053881,17.4220274 L2.01059361,49.314484 C-0.669808219,51.9960548 -0.669808219,56.3410411 2.01059361,59.020274 L48.5266119,105.533954 C51.2046758,108.213187 55.5484932,108.213187 58.2300639,105.533954 L104.528658,59.2365297 C107.20789,56.5561279 107.20789,52.2088037 104.528658,49.5295708" fill="#DE4C36"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<linearGradient x1="0%" y1="50%" x2="99.7986577%" y2="50%" id="linearGradient-1">
<stop stop-color="#F97A12" offset="26%"></stop>
<stop stop-color="#B07B58" offset="46%"></stop>
<stop stop-color="#577BAE" offset="72%"></stop>
<stop stop-color="#1E7CE5" offset="91%"></stop>
<stop stop-color="#087CFA" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0.344827586%" y1="34.5396825%" x2="121.182266%" y2="77.6190476%" id="linearGradient-2">
<stop stop-color="#F97A12" offset="0%"></stop>
<stop stop-color="#CB7A3E" offset="7%"></stop>
<stop stop-color="#9E7B6A" offset="15%"></stop>
<stop stop-color="#757B91" offset="24%"></stop>
<stop stop-color="#537BB1" offset="33%"></stop>
<stop stop-color="#387CCC" offset="43%"></stop>
<stop stop-color="#237CE0" offset="54%"></stop>
<stop stop-color="#147CEF" offset="66%"></stop>
<stop stop-color="#0B7CF7" offset="79%"></stop>
<stop stop-color="#087CFA" offset="100%"></stop>
</linearGradient>
<linearGradient x1="78.1690141%" y1="97.7493606%" x2="30.1056338%" y2="-6.62404092%" id="linearGradient-3">
<stop stop-color="#FE315D" offset="0%"></stop>
<stop stop-color="#CB417E" offset="8%"></stop>
<stop stop-color="#9E4E9B" offset="16%"></stop>
<stop stop-color="#755BB4" offset="25%"></stop>
<stop stop-color="#5365CA" offset="34%"></stop>
<stop stop-color="#386DDB" offset="44%"></stop>
<stop stop-color="#2374E9" offset="54%"></stop>
<stop stop-color="#1478F3" offset="66%"></stop>
<stop stop-color="#0B7BF8" offset="79%"></stop>
<stop stop-color="#087CFA" offset="100%"></stop>
</linearGradient>
<linearGradient x1="20%" y1="24.0865385%" x2="103.421589%" y2="132.676282%" id="linearGradient-4">
<stop stop-color="#FE315D" offset="0%"></stop>
<stop stop-color="#F63462" offset="4%"></stop>
<stop stop-color="#DF3A71" offset="10%"></stop>
<stop stop-color="#C24383" offset="17%"></stop>
<stop stop-color="#AD4A91" offset="29%"></stop>
<stop stop-color="#755BB4" offset="55%"></stop>
<stop stop-color="#1D76ED" offset="92%"></stop>
<stop stop-color="#087CFA" offset="100%"></stop>
</linearGradient>
</defs>
<g>
<polygon fill="url(#linearGradient-1)" points="64.8 199.6 2.8 150.8 33.6 93.6 122 128"></polygon>
<polygon fill="url(#linearGradient-2)" points="256 68.4 251.2 216.4 152.8 256 93.6 217.6 180 128 142.4 44.8 176.4 4"></polygon>
<polygon fill="url(#linearGradient-3)" points="256 68.4 178 160.4 142.4 44.8 176.4 4"></polygon>
<polygon fill="url(#linearGradient-4)" points="123.2 212.4 20.8 249.6 37.2 192 58.4 120.8 0 101.2 37.2 0 117.2 9.6 196.4 100"></polygon>
<rect fill="#000000" x="48" y="48" width="160" height="160"></rect>
<path d="M63.2,178 L123.2,178 L123.2,188 L63.2,188 L63.2,178 Z M106,80.8 L106,68.8 L73.2,68.8 L73.2,80.8 L82.4,80.8 L82.4,122.8 L73.2,122.8 L73.2,135.2 L106,135.2 L106,122.8 L96.8,122.8 L96.8,80.8 L106,80.8 Z M138,136 C133.433351,136.205984 128.889763,135.242193 124.8,133.2 C121.453966,131.354025 118.472002,128.914237 116,126 L125.2,115.6 C126.861272,117.42513 128.742265,119.03741 130.8,120.4 C132.73613,121.535124 134.957474,122.09046 137.2,122 C139.666127,122.188479 142.064692,121.139107 143.6,119.2 C145.452667,116.813942 146.312461,113.804666 146,110.8 L146,68 L160.8,68 L160.8,111.2 C160.951789,114.737935 160.408195,118.271293 159.2,121.6 C158.144579,124.977032 156.212191,128.013641 153.6,130.4 C149.066749,133.765567 143.638763,135.714074 138,136 Z" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="https://strolch.li/xsd/StrolchModel-1.6.xsd"
xmlns="https://strolch.li/xsd/StrolchModel-1.6.xsd"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:annotation>
<xs:documentation>This is Version 1.6.x of the StrolchModel XSD.</xs:documentation>
</xs:annotation>
<xs:element name="StrolchModel" type="StrolchModelType"/>
<xs:complexType name="StrolchModelType">
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:choice>
<xs:element type="IncludeFileType" name="IncludeFile" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="OrderType" name="Order" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="ResourceType" name="Resource" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="ActivityType" name="Activity" maxOccurs="unbounded" minOccurs="0"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="IncludeFileType">
<xs:attribute type="xs:string" name="file"/>
</xs:complexType>
<xs:complexType name="VersionType">
<xs:attribute type="xs:int" name="Version" use="required"/>
<xs:attribute type="xs:string" name="CreatedBy" use="required"/>
<xs:attribute type="xs:string" name="UpdatedBy" use="required"/>
<xs:attribute type="xs:dateTime" name="Created" use="required"/>
<xs:attribute type="xs:dateTime" name="Updated" use="required"/>
<xs:attribute type="xs:string" name="Deleted" use="required"/>
</xs:complexType>
<xs:complexType name="OrderType">
<xs:sequence>
<xs:element type="VersionType" name="Version" maxOccurs="1" minOccurs="0"/>
<xs:element type="ParameterBagType" name="ParameterBag" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="PoliciesType" name="Policies" maxOccurs="1" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Type" use="required"/>
<xs:attribute type="xs:dateTime" name="Date" use="optional"/>
<xs:attribute type="StateType" name="State" use="optional"/>
</xs:complexType>
<xs:complexType name="ResourceType">
<xs:sequence>
<xs:element type="VersionType" name="Version" maxOccurs="1" minOccurs="0"/>
<xs:element type="ParameterBagType" name="ParameterBag" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="TimedStateType" name="TimedState" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="PoliciesType" name="Policies" maxOccurs="1" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Type" use="required"/>
</xs:complexType>
<xs:complexType name="ActivityType">
<xs:sequence>
<xs:element type="VersionType" name="Version" maxOccurs="1" minOccurs="0"/>
<xs:element type="ParameterBagType" name="ParameterBag" maxOccurs="unbounded" minOccurs="0"/>
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:choice>
<xs:element type="ActionType" name="Action" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="ActivityType" name="Activity" maxOccurs="unbounded" minOccurs="0"/>
</xs:choice>
</xs:sequence>
<xs:element type="PoliciesType" name="Policies" maxOccurs="1" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Type" use="required"/>
<xs:attribute type="TimeOrderingType" name="TimeOrdering" use="required"/>
</xs:complexType>
<xs:complexType name="ActionType">
<xs:sequence>
<xs:element type="ParameterBagType" name="ParameterBag" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="PoliciesType" name="Policies" maxOccurs="1" minOccurs="0"/>
<xs:element type="ValueChangeType" name="ValueChange" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="ResourceId" use="optional"/>
<xs:attribute type="xs:string" name="ResourceType" use="optional"/>
<xs:attribute type="StateType" name="State" use="optional"/>
<xs:attribute type="xs:string" name="Type" use="required"/>
</xs:complexType>
<xs:complexType name="ParameterBagType">
<xs:sequence>
<xs:element type="ParameterType" name="Parameter" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Type" use="required"/>
</xs:complexType>
<xs:complexType name="ParameterType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="ParameterValueType" name="Type" use="required"/>
<xs:attribute type="xs:string" name="Value" use="optional"/>
<xs:attribute type="xs:string" name="Interpretation" use="optional"/>
<xs:attribute type="xs:string" name="Uom" use="optional"/>
<xs:attribute type="xs:boolean" name="Hidden" use="optional"/>
<xs:attribute type="xs:int" name="Index" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PoliciesType">
<xs:sequence>
<xs:element type="PolicyType" name="Policy" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PolicyType">
<xs:attribute type="xs:string" name="Type" use="required"/>
<xs:attribute type="xs:string" name="Value" use="required"/>
</xs:complexType>
<xs:complexType name="TimedStateType">
<xs:sequence>
<xs:element type="ValueType" name="Value" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Id" use="required"/>
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="TimedStateTypeType" name="Type" use="required"/>
<xs:attribute type="xs:string" name="Interpretation" use="optional"/>
<xs:attribute type="xs:string" name="Uom" use="optional"/>
<xs:attribute type="xs:boolean" name="Hidden" use="optional"/>
<xs:attribute type="xs:int" name="Index" use="optional"/>
</xs:complexType>
<xs:complexType name="ValueType">
<xs:attribute type="xs:dateTime" name="Time" use="required"/>
<xs:attribute type="xs:string" name="Value" use="required"/>
</xs:complexType>
<xs:complexType name="ValueChangeType">
<xs:attribute type="xs:string" name="StateId" use="optional"/>
<xs:attribute type="xs:dateTime" name="Time" use="required"/>
<xs:attribute type="xs:string" name="Value" use="required"/>
<xs:attribute type="TimedStateTypeType" name="Type" use="required"/>
</xs:complexType>
<xs:simpleType name="StateType">
<xs:restriction base="xs:string">
<xs:enumeration value="Created"/>
<xs:enumeration value="Planning"/>
<xs:enumeration value="Planned"/>
<xs:enumeration value="Execution"/>
<xs:enumeration value="Stopped"/>
<xs:enumeration value="Warning"/>
<xs:enumeration value="Error"/>
<xs:enumeration value="Executed"/>
<xs:enumeration value="Closed"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="TimeOrderingType">
<xs:restriction base="xs:string">
<xs:enumeration value="Series"/>
<xs:enumeration value="Parallel"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ParameterValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="Boolean"/>
<xs:enumeration value="String"/>
<xs:enumeration value="Text"/>
<xs:enumeration value="Integer"/>
<xs:enumeration value="Long"/>
<xs:enumeration value="Float"/>
<xs:enumeration value="Date"/>
<xs:enumeration value="Duration"/>
<xs:enumeration value="StringList"/>
<xs:enumeration value="IntegerList"/>
<xs:enumeration value="FloatList"/>
<xs:enumeration value="LongList"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="TimedStateTypeType">
<xs:restriction base="xs:string">
<xs:enumeration value="Boolean"/>
<xs:enumeration value="Integer"/>
<xs:enumeration value="Float"/>
<xs:enumeration value="Long"/>
<xs:enumeration value="FloatList"/>
<xs:enumeration value="StringSet"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

View File

@ -0,0 +1 @@
google-site-verification: google052dbec2d053a4e1.html

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -0,0 +1,226 @@
# Changelog
## v2.5.0 (01/06/2020)
#### New features
- [**feature**] Allow custom css files to be loaded by configuration [#376](https://github.com/matcornic/hugo-theme-learn/pull/376)
- [**feature**] Added flag to turn off image featherlight [#333](https://github.com/matcornic/hugo-theme-learn/pull/333)
#### Bug Fixes
- [**bug**] fix null pointer when no params on image url [#383](https://github.com/matcornic/hugo-theme-learn/pull/383)
- [**bug**] can't give argument to non-function not .Params.hidden [#403](https://github.com/matcornic/hugo-theme-learn/pull/403)
- [**bug**] Fix .File.UniqueID on zero object warning [#372](https://github.com/matcornic/hugo-theme-learn/pull/372)
- [**bug**] fix select option color in Chrome [#345](https://github.com/matcornic/hugo-theme-learn/pull/345)
- [**bug**] remove unneeded spaces from HTML [#320](https://github.com/matcornic/hugo-theme-learn/pull/320)
- [**bug**] Avoid displaying hidden sections in menu [#354](https://github.com/matcornic/hugo-theme-learn/pull/354)
#### Enhancements
- [**enhancement**] Updated theme installation documentation link. [#390](https://github.com/matcornic/hugo-theme-learn/pull/390)
- [**enhancement**] [ImgBot] Optimize images [#366](https://github.com/matcornic/hugo-theme-learn/pull/366)
- [**enhancement**] fix extra spaces in the description [#339](https://github.com/matcornic/hugo-theme-learn/pull/339)
- [**enhancement**] change to minified 'autocomplete.js' for improved performance [#321](https://github.com/matcornic/hugo-theme-learn/pull/321)
#### Internationalisation
- [**i18n**] Fix: typos [#343](https://github.com/matcornic/hugo-theme-learn/pull/343)
- [**i18n**] Fix: typo [#342](https://github.com/matcornic/hugo-theme-learn/pull/342)
- [**i18n**] Incorrect english in notice documentation [#336](https://github.com/matcornic/hugo-theme-learn/pull/336)
- [**i18n**] Add Chinese language for this theme [#349](https://github.com/matcornic/hugo-theme-learn/pull/349)
- [**i18n**] Add jp and zh cn [#335](https://github.com/matcornic/hugo-theme-learn/pull/335)
- [**i18n**] Added Russian translation [#332](https://github.com/matcornic/hugo-theme-learn/pull/332)
#### Theme Meta
- [**meta**] Fix various linting warnings [#405](https://github.com/matcornic/hugo-theme-learn/pull/405)
- [**meta**] Netlify build on 0.66.0 [#377](https://github.com/matcornic/hugo-theme-learn/pull/377)
- [**meta**] add inteliver documentation showcase [#331](https://github.com/matcornic/hugo-theme-learn/pull/331)
---
## v2.4.0 (04/09/2019)
#### Bug Fixes
- [**bug**] correct GitHub capitalization [#293](https://github.com/matcornic/hugo-theme-learn/pull/293)
- [**bug**] fix 'locate' typo [#285](https://github.com/matcornic/hugo-theme-learn/pull/285)
- [**bug**] Remove reference to the unused horsey CSS [#284](https://github.com/matcornic/hugo-theme-learn/pull/284)
- [**bug**] Fix invalid HTML in 404 template. [#271](https://github.com/matcornic/hugo-theme-learn/pull/271)
#### Enhancements
- [**enhancement**] Delete html5shiv-printshiv.min.js [#319](https://github.com/matcornic/hugo-theme-learn/pull/319)
- [**enhancement**] remove html5shiv [#315](https://github.com/matcornic/hugo-theme-learn/pull/315)
- [**enhancement**] Fix deprecated .Hugo, .UniqueID and .URL references [#303](https://github.com/matcornic/hugo-theme-learn/pull/303)
- [**enhancement**] Fix type lines 364 and 369 [#304](https://github.com/matcornic/hugo-theme-learn/pull/304)
- [**enhancement**] remove unneeded type="text/css" [#298](https://github.com/matcornic/hugo-theme-learn/pull/298)
- [**enhancement**] remove unneeded type="text/css" [#297](https://github.com/matcornic/hugo-theme-learn/pull/297)
- [**enhancement**] HTTPS links in footer [#295](https://github.com/matcornic/hugo-theme-learn/pull/295)
- [**enhancement**] remove unneeded type="text/css" [#292](https://github.com/matcornic/hugo-theme-learn/pull/292)
- [**enhancement**] Make shortcodes & example site compatible with Hugo v0.55 [#281](https://github.com/matcornic/hugo-theme-learn/pull/281)
#### Internationalisation
- [**i18n**] Added German and Arabic translation [#312](https://github.com/matcornic/hugo-theme-learn/pull/312)
#### Theme Meta
- [**meta**] Added Tshark.dev as example site [#318](https://github.com/matcornic/hugo-theme-learn/pull/318)
---
## v2.3.0 (16/04/2019)
#### New features
- [**feature**] Added support for tags [#196](https://github.com/matcornic/hugo-theme-learn/pull/196)
#### Bug Fixes
- [**bug**] Fix issue where "children" shortcode only shows top level. [#252](https://github.com/matcornic/hugo-theme-learn/pull/252)
- [**bug**] Fix translation when using a custom baseURL [#234](https://github.com/matcornic/hugo-theme-learn/pull/234)
- [**bug**] Preventing left/right arrow key navigation in textareas [#241](https://github.com/matcornic/hugo-theme-learn/pull/241)
- [**bug**] Update menu.html to include the check icon [#229](https://github.com/matcornic/hugo-theme-learn/pull/229)
#### Enhancements
- [**enhancement**] Render the ```mermaid blocks into graphs [#226](https://github.com/matcornic/hugo-theme-learn/pull/226)
- [**enhancement**] Remove oudated versions of jquery & modernizr libraries [#259](https://github.com/matcornic/hugo-theme-learn/pull/259)
- [**enhancement**] Various Updates [#237](https://github.com/matcornic/hugo-theme-learn/pull/237)
- [**enhancement**] Use style instead of width and height in logo [#250](https://github.com/matcornic/hugo-theme-learn/pull/250)
- [**enhancement**] [ImgBot] Optimize images [#222](https://github.com/matcornic/hugo-theme-learn/pull/222)
- [**enhancement**] remove duplicate icon and fix incorrect favicon type [#227](https://github.com/matcornic/hugo-theme-learn/pull/227)
- [**enhancement**] HTTPS links in exampleSite menu-footer [#223](https://github.com/matcornic/hugo-theme-learn/pull/223)
#### Internationalisation
- [**i18n**] Dutch i18n [#239](https://github.com/matcornic/hugo-theme-learn/pull/239)
#### Theme Meta
- [**meta**] Add .editorconfig [#224](https://github.com/matcornic/hugo-theme-learn/pull/224)
---
## v2.2.0 (28/01/2019)
#### New features
- [**feature**] Sitewide param to enable or disable next/previous page buttons and breadcrumbs [#184](https://github.com/matcornic/hugo-theme-learn/pull/184)
#### Bug Fixes
- [**bug**] Fix baseurl used by search to load json data [#177](https://github.com/matcornic/hugo-theme-learn/pull/177)
- [**bug**] Updated CSS link to fontawesome library [#186](https://github.com/matcornic/hugo-theme-learn/pull/186)
- [**bug**] Close / Cancel search icon not showing in input box [#215](https://github.com/matcornic/hugo-theme-learn/pull/215)
- [**bug**] Prevent left and right keydown events while in input fields [#219](https://github.com/matcornic/hugo-theme-learn/pull/219)
- [**bug**] xss fix [#182](https://github.com/matcornic/hugo-theme-learn/pull/182)
- [**bug**] Fix error in blockquote documentation fixes #165 [#190](https://github.com/matcornic/hugo-theme-learn/pull/190)
#### Enhancements
- [**enhancement**] Update mermaid.js to a499296 [#199](https://github.com/matcornic/hugo-theme-learn/pull/199)
- [**enhancement**] Update Font Awesome to 5.0.6 [#129](https://github.com/matcornic/hugo-theme-learn/pull/129)
- [**enhancement**] Update 404.html alttext [#161](https://github.com/matcornic/hugo-theme-learn/pull/161)
- [**enhancement**] Remove CSS source map metadata [#167](https://github.com/matcornic/hugo-theme-learn/pull/167)
- [**enhancement**] Load github images in examplesite via https instead of http [#180](https://github.com/matcornic/hugo-theme-learn/pull/180)
- [**enhancement**] Load main site logo via BaseUrl [#185](https://github.com/matcornic/hugo-theme-learn/pull/185)
- [**enhancement**] HTTPS links in examplesite sidebar [#200](https://github.com/matcornic/hugo-theme-learn/pull/200)
- [**enhancement**] Use correct input type for search [#205](https://github.com/matcornic/hugo-theme-learn/pull/205)
- [**enhancement**] HTTPS link to learn.getgrav.org [#207](https://github.com/matcornic/hugo-theme-learn/pull/207)
- [**enhancement**] Update html5shiv-printshiv.min.js [#208](https://github.com/matcornic/hugo-theme-learn/pull/208)
- [**enhancement**] Remove whitespace from clippy.svg [#211](https://github.com/matcornic/hugo-theme-learn/pull/211)
- [**enhancement**] Upgrade fontawesome to 5.6.3 [#218](https://github.com/matcornic/hugo-theme-learn/pull/218)
- [**enhancement**] fix clickable nodes style in mermaid [#169](https://github.com/matcornic/hugo-theme-learn/pull/169)
#### Internationalisation
- [**i18n**] French language correction [#157](https://github.com/matcornic/hugo-theme-learn/pull/157)
- [**i18n**] French language correction [#158](https://github.com/matcornic/hugo-theme-learn/pull/158)
- [**i18n**] Add indonesian translation [#159](https://github.com/matcornic/hugo-theme-learn/pull/159)
- [**i18n**] Add Turkish i18n config file [#175](https://github.com/matcornic/hugo-theme-learn/pull/175)
#### Theme Meta
- [**meta**] Fix wercker builds [#178](https://github.com/matcornic/hugo-theme-learn/pull/178)
- [**meta**] Declare netlify buildsteps in repo file rather than in webui [#217](https://github.com/matcornic/hugo-theme-learn/pull/217)
---
## v2.1.0 (10/08/2018)
#### Internationalisation
- [**i18n**] Clean up the English language phrasing [#146](https://github.com/matcornic/hugo-theme-learn/pull/146)
- [**i18n**] Updated _index.en.md for typo "names" [#150](https://github.com/matcornic/hugo-theme-learn/pull/150)
#### Theme Meta
- [**meta**] Add license scan report and status [#151](https://github.com/matcornic/hugo-theme-learn/pull/151)
#### Uncategorised
- [**closed**] Different viewport [#122](https://github.com/matcornic/hugo-theme-learn/pull/122)
- [**closed**] TranslationBaseName replaced for Name on archetypes template [#145](https://github.com/matcornic/hugo-theme-learn/pull/145)
- [**closed**] Improved variant of #119, as per request [#124](https://github.com/matcornic/hugo-theme-learn/pull/124)
- [**closed**] role="" is invalid [#121](https://github.com/matcornic/hugo-theme-learn/pull/121)
- [**closed**] Related to previous PR [#120](https://github.com/matcornic/hugo-theme-learn/pull/120)
- [**closed**] Issue #111 for _index.fr.md [#117](https://github.com/matcornic/hugo-theme-learn/pull/117)
- [**closed**] Issue #111 for _index.en.md [#116](https://github.com/matcornic/hugo-theme-learn/pull/116)
- [**closed**] Issue #111 for theme-green.css [#114](https://github.com/matcornic/hugo-theme-learn/pull/114)
- [**closed**] Issue #111 for theme-red.css [#113](https://github.com/matcornic/hugo-theme-learn/pull/113)
- [**closed**] Issue #111 for theme-blue.css [#112](https://github.com/matcornic/hugo-theme-learn/pull/112)
- [**closed**] Issue #111 for theme-mine.css [#115](https://github.com/matcornic/hugo-theme-learn/pull/115)
- [**closed**] fix #77 : no wrap images that already wrapped [#118](https://github.com/matcornic/hugo-theme-learn/pull/118)
- [**closed**] doc: Keep icons aligned [#110](https://github.com/matcornic/hugo-theme-learn/pull/110)
- [**closed**] Add Portuguese translation [#109](https://github.com/matcornic/hugo-theme-learn/pull/109)
- [**closed**] Use `relURL` where possible [#102](https://github.com/matcornic/hugo-theme-learn/pull/102)
- [**closed**] Bug fix in sidebar menu and children description generation [#105](https://github.com/matcornic/hugo-theme-learn/pull/105)
- [**closed**] fix some typo [#104](https://github.com/matcornic/hugo-theme-learn/pull/104)
- [**closed**] Added a `menuTitle` attribute to partials/menu.html [#90](https://github.com/matcornic/hugo-theme-learn/pull/90)
- [**closed**] allowing comments system [#86](https://github.com/matcornic/hugo-theme-learn/pull/86)
- [**closed**] Add spanish translation [#85](https://github.com/matcornic/hugo-theme-learn/pull/85)
- [**closed**] Replace horsey with Pixabay's autocomplete [#75](https://github.com/matcornic/hugo-theme-learn/pull/75)
- [**closed**] Added info about 'draft:true' [#74](https://github.com/matcornic/hugo-theme-learn/pull/74)
- [**closed**] Remove white space from `align` parameter [#63](https://github.com/matcornic/hugo-theme-learn/pull/63)
---
## v2.0.0 (20/08/2017)
#### Uncategorised
- [**closed**] V2 [#56](https://github.com/matcornic/hugo-theme-learn/pull/56)
---
## v1.1.0 (22/07/2017)
#### Bug Fixes
- [**bug**] Fix sticky header jumpiness [#45](https://github.com/matcornic/hugo-theme-learn/pull/45)
#### Uncategorised
- [**closed**] Fix anchor scrolling that hides behind top nav bar [#46](https://github.com/matcornic/hugo-theme-learn/pull/46)
- [**closed**] feat: Change the default code color [#43](https://github.com/matcornic/hugo-theme-learn/pull/43)
- [**closed**] Use index pages + automatic navigation arrows [#36](https://github.com/matcornic/hugo-theme-learn/pull/36)
---
## v1.0.0 (25/03/2017)
#### Uncategorised
- [**closed**] Search [#18](https://github.com/matcornic/hugo-theme-learn/pull/18)
- [**closed**] Extracted menu footer content to separate partial file [#35](https://github.com/matcornic/hugo-theme-learn/pull/35)
- [**closed**] feat: style Hugo figure shortcode [#33](https://github.com/matcornic/hugo-theme-learn/pull/33)
- [**closed**] Fix URL for 'Edit this page' on Windows [#27](https://github.com/matcornic/hugo-theme-learn/pull/27)
- [**closed**] Revert "Menu generation using Content file instead directories" [#10](https://github.com/matcornic/hugo-theme-learn/pull/10)
- [**closed**] (#25) use .Site.BaseURL to lcoate static assets [#26](https://github.com/matcornic/hugo-theme-learn/pull/26)
- [**closed**] Menu ordering and definition in config.toml [#8](https://github.com/matcornic/hugo-theme-learn/pull/8)
- [**closed**] Menu generation using Content file instead directories [#5](https://github.com/matcornic/hugo-theme-learn/pull/5)
- [**closed**] Add Checkmark On visited Links [#4](https://github.com/matcornic/hugo-theme-learn/pull/4)
- [**closed**] doc: Add theme installation instructions [#1](https://github.com/matcornic/hugo-theme-learn/pull/1)

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Grav
Copyright (c) 2016 MATHIEU CORNIC
Copyright (c) 2017 Valere JEANTET
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,97 @@
# Hugo Learn Theme
This repository contains a theme for [Hugo](https://gohugo.io/), based on great [Grav Learn Theme](https://learn.getgrav.org/).
Visit the [theme documentation](https://learn.netlify.com/en/) to see what is going on. It is actually built with this theme.
[![wercker status](https://app.wercker.com/status/233466a2be73fcea400e7dc02ef6adf9/s/master "wercker status")](https://app.wercker.com/project/byKey/233466a2be73fcea400e7dc02ef6adf9)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn?ref=badge_shield)
## Main features
- Automatic Search
- Multilingual mode
- Unlimited menu levels
- Automatic next/prev buttons to navigate through menu entries
- Image resizing, shadow…
- Attachments files
- List child pages
- Mermaid diagram (flowchart, sequence, gantt)
- Customizable look and feel and themes variants
- Buttons, Tip/Note/Info/Warning boxes, Expand
## Installation
Navigate to your themes folder in your Hugo site and use the following commands:
```shell
cd themes/
git clone https://github.com/matcornic/hugo-theme-learn.git
```
Check that your Hugo version is minimum `0.25` with `hugo version`.
![Overview](https://github.com/matcornic/hugo-theme-learn/raw/master/images/tn.png)
## Usage
- [Visit the documentation](https://learn.netlify.com/en/)
## Download old versions (prior to 2.0.0)
If you need old version for compatibility purpose, either download [theme source code from releases](https://github.com/matcornic/hugo-theme-learn/releases) or use the right git tag. For example, with `1.1.0`
- Direct download way: https://github.com/matcornic/hugo-theme-learn/archive/1.1.0.zip
- Git way:
```shell
cd themes/hugo-theme-learn
git checkout tags/1.1.0
```
For both solutions, the documentation is available at https://github.com/matcornic/hugo-theme-learn/releases/download/1.1.0/hugo-learn-doc-1.1.0.zip
## Credits
Many thanks to [@vjeantet](https://github.com/vjeantet/) for the fork [docdock](https://github.com/vjeantet/hugo-theme-docdock). The v2 of this theme is mainly based on his work !
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmatcornic%2Fhugo-theme-learn?ref=badge_large)
## Releasing
Somewhat work-in-progress steps to release with [gren](https://github.com/github-tools/github-release-notes)
- Check all MRs assigned to the milestone are closed or pushed back to another release
- Close the milestone
- Check merged MRs on the milestone have a tag (Bug, Enhancement, etc.)
- Tag and push the repo
```shell
git tag <tag>
git push origin <tag>
```
- Generate CHANGELOG.md with _gren_
```shell
gren changelog --override --generate --tags=all
```
- Fix the date for the current release in CHANGELOG.md
- Add the changelog to git and update the tag
```shell
git add CHANGELOG.md
git commit -m "Ship tag <tag>"
git push origin master
git tag -f <tag>
git push --force origin <tag>
```
- Generate release with _gren_
```shell
gren release -t <tag>
```

View File

@ -0,0 +1,13 @@
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
chapter = true
pre = "<b>X. </b>"
+++
### Chapter X
# Some Chapter title
Lorem Ipsum.

View File

@ -0,0 +1,7 @@
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
+++
Lorem Ipsum.

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 MATHIEU CORNIC
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,112 @@
baseURL = "/"
languageCode = "en-US"
defaultContentLanguage = "en"
title = "Hugo Learn Documentation"
theme = "hugo-theme-learn"
themesdir = "../.."
metaDataFormat = "yaml"
defaultContentLanguageInSubdir= true
[params]
editURL = "https://github.com/matcornic/hugo-theme-learn/edit/master/exampleSite/content/"
description = "Documentation for Hugo Learn Theme"
author = "Mathieu Cornic"
showVisitedLinks = true
disableBreadcrumb = false
disableNextPrev = false
disableLandingPageButton = true
disableMermaid = false
customMermaidURL = "https://unpkg.com/mermaid@8.8.0/dist/mermaid.min.js"
titleSeparator = "::"
[outputs]
home = [ "HTML", "RSS", "JSON"]
[Languages]
[Languages.en]
title = "Documentation for Hugo Learn Theme"
weight = 1
languageName = "English"
landingPageURL = "/en"
landingPageName = "<i class='fas fa-home'></i> Home"
[[Languages.en.menu.shortcuts]]
name = "<i class='fab fa-fw fa-github'></i> GitHub repo"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-fw fa-camera'></i> Showcases"
url = "showcase"
weight = 11
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bookmark'></i> Hugo Documentation"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bullhorn'></i> Credits"
url = "/credits"
weight = 30
[Languages.fr]
title = "Documentation du thème Hugo Learn"
weight = 2
languageName = "Français"
landingPageURL = "/fr"
landingPageName = "<i class='fas fa-home'></i> Accueil"
[[Languages.fr.menu.shortcuts]]
name = "<i class='fab fa-fw fa-github'></i> Repo GitHub"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-fw fa-camera'></i> Vitrine"
url = "/showcase"
weight = 11
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bookmark'></i> Documentation Hugo"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bullhorn'></i> Crédits"
url = "/credits"
weight = 30
[Languages.zh]
title = "Hugo 主题的 Learn 文档"
weight = 3
languageName = "简体中文"
landingPageURL = "/zh"
landingPageName = "<i class='fas fa-home'></i> 家"
[[Languages.zh.menu.shortcuts]]
name = "<i class='fab fa-fw fa-github'></i> GitHub 仓库"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.zh.menu.shortcuts]]
name = "<i class='fas fa-fw fa-camera'></i> 展示区"
url = "/showcase"
weight = 11
[[Languages.zh.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bookmark'></i> Hugo 文档"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.zh.menu.shortcuts]]
name = "<i class='fas fa-fw fa-bullhorn'></i> 鸣谢"
url = "/credits"
weight = 30

View File

@ -0,0 +1,41 @@
---
title: "Learn Theme for Hugo"
---
# Hugo learn theme
[Hugo-theme-learn](http://github.com/matcornic/hugo-theme-learn) is a theme for [Hugo](https://gohugo.io/), a fast and modern static website engine written in Go. Where Hugo is often used for blogs, this multilingual-ready theme is **fully designed for documentation**.
This theme is a partial porting of the [Learn theme](http://learn.getgrav.org/) of [Grav](https://getgrav.org/), a modern flat-file CMS written in PHP.
{{% notice tip %}}Learn theme works with a _page tree structure_ to organize content : All contents are pages, which belong to other pages. [read more about this]({{%relref "cont/pages/_index.md"%}})
{{% /notice %}}
## Main features
* [Automatic Search]({{%relref "basics/configuration/_index.md#activate-search" %}})
* [Multilingual mode]({{%relref "cont/i18n/_index.md" %}})
* **Unlimited menu levels**
* **Automatic next/prev buttons to navigate through menu entries**
* [Image resizing, shadow...]({{%relref "cont/markdown.en.md#images" %}})
* [Attachments files]({{%relref "shortcodes/attachments.en.md" %}})
* [List child pages]({{%relref "shortcodes/children/_index.md" %}})
* [Mermaid diagram]({{%relref "shortcodes/mermaid.en.md" %}}) (flowchart, sequence, gantt)
* [Customizable look and feel and themes variants]({{%relref "basics/style-customization/_index.md"%}})
* [Buttons]({{%relref "shortcodes/button.en.md" %}}), [Tip/Note/Info/Warning boxes]({{%relref "shortcodes/notice.en.md" %}}), [Expand]({{%relref "shortcodes/expand.en.md" %}})
![Screenshot](https://github.com/matcornic/hugo-theme-learn/raw/master/images/screenshot.png?width=40pc&classes=shadow)
## Contribute to this documentation
Feel free to update this content, just click the **Edit this page** link displayed on top right of each page, and pullrequest it
{{% notice info %}}
Your modification will be deployed automatically when merged.
{{% /notice %}}
## Documentation website
This current documentation has been statically generated with Hugo with a simple command : `hugo -t hugo-theme-learn` -- source code is [available here at GitHub](https://github.com/matcornic/hugo-theme-learn)
{{% notice note %}}
Automatically published and hosted thanks to [Netlify](https://www.netlify.com/). Read more about [Automated HUGO deployments with Netlify](https://www.netlify.com/blog/2015/07/30/hosting-hugo-on-netlifyinsanely-fast-deploys/)
{{% /notice %}}

View File

@ -0,0 +1,43 @@
---
title: "Learn Theme for Hugo"
---
# Thème Hugo learn
[Hugo-theme-learn](http://github.com/matcornic/hugo-theme-learn) est un thème pour [Hugo](https://gohugo.io/), un générateur de site statique, rapide et modern, écrit en Go. Tandis que Hugo est souvent utilisé pour des blogs, ce thème multi-langue est **entièrement conçu pour la documentation**.
Ce thème est un portage partiel du [thème Learn](http://learn.getgrav.org/) de [Grav](https://getgrav.org/), un CMS modern écrit en PHP.
{{% notice tip %}}Le thème Learn fonctionne grâce à la structure de page aborescentes pour organiser le contenu: tous les contenus sont des pages qui appartiennent à d'autres pages. [Plus d'infos]({{%relref "cont/pages/_index.md"%}})
{{% /notice %}}
## Fonctionnalités principales
* [Recherche automatique]({{%relref "basics/configuration/_index.md#activer-recherche" %}})
* [Mode multi-langue]({{%relref "cont/i18n/_index.md" %}})
* **Nombre de niveau infini dans le menu**
* **Boutons suivant/précédent automatiquement générés pour naviguer entre les items du menu**
* [Taille d'image, ombres...]({{%relref "cont/markdown.fr.md#images" %}})
* [Fichiers joints]({{%relref "shortcodes/attachments.fr.md" %}})
* [Lister les pages filles]({{%relref "shortcodes/children/_index.md" %}})
* [Diagrammes Mermaid]({{%relref "shortcodes/mermaid.fr.md" %}}) (flowchart, sequence, gantt)
* [Style configurable and variantes de couleurs]({{%relref "basics/style-customization/_index.md"%}})
* [Boutons]({{%relref "shortcodes/button.fr.md" %}}), [Messages Astuce/Note/Info/Attention]({{%relref "shortcodes/notice.fr.md" %}}), [Expand]({{%relref "shortcodes/expand.fr.md" %}})
![Screenshot](https://github.com/matcornic/hugo-theme-learn/raw/master/images/screenshot.png?width=40pc&classes=shadow)
## Contribuer à cette documentation
N'hésitez pas à mettre à jour ce contenu en cliquant sur le lien **Modifier cette page** en haut de chaque page, et créer la Pull Request associée.
{{% notice info %}}
Votre modification sera déployée automatiquement quand elle sera mergée.
{{% /notice %}}
## Site de documentation
Cette documentation statique a été générée avec Hugo avec une simple commande : `hugo -t hugo-theme-learn` -- le code source est [disponible sur Github](https://github.com/matcornic/hugo-theme-learn)
{{% notice note %}}
Le site est auomatiquement publié et hébergé par [Netlify](https://www.netlify.com/). Plus d'infos sur le [déploiement de site Hugo avec Netlify](https://www.netlify.com/blog/2015/07/30/hosting-hugo-on-netlifyinsanely-fast-deploys/)(En anglais)
{{% /notice %}}

View File

@ -0,0 +1,12 @@
---
title: Basics
weight: 5
pre: "<b>1. </b>"
chapter: true
---
### Chapter 1
# Basics
Discover what this Hugo theme is all about and the core-concepts behind it.

View File

@ -0,0 +1,12 @@
---
title: Démarrage
weight: 5
pre: "<b>1. </b>"
chapter: true
---
### Chapitre 1
# Démarrage
Découvrez comment utiliser ce thème Hugo et apprenez-en les concepts

View File

@ -0,0 +1,12 @@
---
title: 基础
weight: 5
pre: "<b>1. </b>"
chapter: true
---
### 章节 1
# 基础
了解该 Hugo 主题的特点以及背后的核心概念。

View File

@ -0,0 +1,112 @@
---
date: 2016-04-09T16:50:16+02:00
title: Configuration
weight: 20
---
## Global site parameters
On top of [Hugo global configuration](https://gohugo.io/overview/configuration/), **Hugo-theme-learn** lets you define the following parameters in your `config.toml` (here, values are default).
Note that some of these parameters are explained in details in other sections of this documentation.
```toml
[params]
# Prefix URL to edit current page. Will display an "Edit this page" button on top right hand corner of every page.
# Useful to give opportunity to people to create merge request for your doc.
# See the config.toml file from this documentation site to have an example.
editURL = ""
# Author of the site, will be used in meta information
author = ""
# Description of the site, will be used in meta information
description = ""
# Shows a checkmark for visited pages on the menu
showVisitedLinks = false
# Disable search function. It will hide search bar
disableSearch = false
# Javascript and CSS cache are automatically busted when new version of site is generated.
# Set this to true to disable this behavior (some proxies don't handle well this optimization)
disableAssetsBusting = false
# Set this to true to disable copy-to-clipboard button for inline code.
disableInlineCopyToClipBoard = false
# A title for shortcuts in menu is set by default. Set this to true to disable it.
disableShortcutsTitle = false
# If set to false, a Home button will appear below the search bar on the menu.
# It is redirecting to the landing page of the current language if specified. (Default is "/")
disableLandingPageButton = true
# When using mulitlingual website, disable the switch language button.
disableLanguageSwitchingButton = false
# Hide breadcrumbs in the header and only show the current page title
disableBreadcrumb = true
# If set to true, prevents Hugo from including the mermaid module if not needed (will reduce load times and traffic)
disableMermaid = false
# Specifies the remote location of the mermaid js
customMermaidURL = "https://unpkg.com/mermaid@8.8.0/dist/mermaid.min.js"
# Hide Next and Previous page buttons normally displayed full height beside content
disableNextPrev = true
# Order sections in menu by "weight" or "title". Default to "weight"
ordersectionsby = "weight"
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = ""
# Provide a list of custom css files to load relative from the `static/` folder in the site root.
custom_css = ["css/foo.css", "css/bar.css"]
# Change the title separator. Default to "::".
titleSeparator = "-"
```
## Activate search
If not already present, add the follow lines in the same `config.toml` file.
```toml
[outputs]
home = [ "HTML", "RSS", "JSON"]
```
Learn theme uses the last improvement available in hugo version 20+ to generate a json index file ready to be consumed by lunr.js javascript search engine.
> Hugo generate lunrjs index.json at the root of public folder.
> When you build the site with `hugo server`, hugo generates it internally and of course it doesnt show up in the filesystem
## Mermaid
The mermaid configuration parameters can also be set on a specific page. In this case, the global parameter would be overwritten by the local one.
> Example:
>
> Mermaid is globally disabled. By default it won't be loaded by any page.
> On page "Architecture" you need a class diagram. You can set the mermaid parameters locally to only load mermaid on this page (not on the others).
You also can disable mermaid for specific pages while globally enabled.
## Home Button Configuration
If the `disableLandingPage` option is set to `false`, an Home button will appear
on the left menu. It is an alternative for clicking on the logo. To edit the
appearance, you will have to configure two parameters for the defined languages:
```toml
[Lanugages]
[Lanugages.en]
...
landingPageURL = "/en"
landingPageName = "<i class='fas fa-home'></i> Redirect to Home"
...
[Lanugages.fr]
...
landingPageURL = "/fr"
landingPageName = "<i class='fas fa-home'></i> Accueil"
...
```
If those params are not configured for a specific language, they will get their
default values:
```toml
landingPageURL = "/"
landingPageName = "<i class='fas fa-home'></i> Home"
```
The home button is going to looks like this:
![Default Home Button](/en/basics/configuration/images/home_button_defaults.jpg?width=100%)

View File

@ -0,0 +1,110 @@
---
date: 2016-04-09T16:50:16+02:00
title: Configuration
weight: 20
---
## Paramètres globaux du site
En plus de la [configuration globale d'Hugo](https://gohugo.io/overview/configuration/), **Hugo-theme-learn** vous permet de définir les paramètres suivant dans votre fichier `config.toml` (ci-dessous sont affichées les valeurs par défaut).
Notez que certains de ces paramètres sont expliqués en détails dans d'autres sections de cette documentation.
```toml
[params]
# L'URL préfixe pour éditer la page courante. Ce paramètre affichera un bouton "Modifier cette page" on haut de de chacune des pages.
# Pratique pour donner les possibilité à vos utilisateurs de créer une merge request pour votre doc.
# Allez voir le fichier config.toml de cette documentation pour avoir un exemple.
editURL = ""
# Autheur du site, est utilisé dans les informations meta
author = ""
# Description du site, est utilisé dans les informations meta
description = ""
# Affiche une icône lorsque la page a été visitée
showVisitedLinks = false
# Désactive la fonction de recherche. Une valeur à true cache la barre de recherche.
disableSearch = false
# Par défaut, le cache Javascript et CSS est automatiquement vidé lorsqu'une nouvelle version du site est générée.
# Utilisez ce paramètre lorsque vous voulez désactiver ce comportement (c'est parfois incompatible avec certains proxys)
disableAssetsBusting = false
# Utilisez ce paramètre pour désactiver le bouton copy-to-clipboard pour le code formatté sur une ligne.
disableInlineCopyToClipBoard = false
# Un titre est défini par défaut lorsque vous utilisez un raccourci dans le menu. Utilisez ce paramètre pour le cacher.
disableShortcutsTitle = false
# S'il est réglé sur faux, un bouton Accueil apparaîtra sous la barre de recherche dans le menu.
# Il redirige vers la page d'accueil de la langue actuelle si cela est spécifié. (La valeur par défaut est "/")
disableLandingPageButton = true
# Si défini à true, empêche Hugo d'inclure le module "mermaid" s'il n'est pas nécessaire (réduira les temps de chargement et le trafic)
disableMermaid = false
# Spécifie l'emplacement distant du mermaid js
customMermaidURL = "https://unpkg.com/mermaid@8.8.0/dist/mermaid.min.js"
# Quand vous utilisez un site multi-langue, utilisez ce paramètre pour désactiver le bouton de changement de langue.
disableLanguageSwitchingButton = false
# Ordonne les sections dans menu par poids ("weight") ou titre ("title"). Défaut à "weight"
ordersectionsby = "weight"
# Utilisez ce paramètre pour modifier le schéma de couleur du site. Les valeurs par défaut sont "red", "blue", "green".
themeVariant = ""
# Fournissez une liste de fichiers css personnalisés à charger par rapport depuis le dossier `static/` à la racine du site.
custom_css = ["css/foo.css", "css/bar.css"]
```
## Activer la recherche {#activer-recherche}
Si ce n'est pas déjà présent, ajoutez les lignes suivantes dans le fichier `config.toml`.
```toml
[outputs]
home = [ "HTML", "RSS", "JSON"]
```
Le thème *Learn* utilise les dernières améliorations d'Hugo pour générer un fichier d'index JSON, prêt à être consommé par le moteur de recherche lunr.js.
> Hugo génère lunrjs index.json à la racine du dossier `public`.
> Quand vous générez le site avec `hugo server`, Hugo génère le fichier en mémoire, il n'est donc pas disponible sur le disque.
## Mermaid
Les paramètres de configuration du mermaid peuvent également être définis sur une page spécifique. Dans ce cas, le paramètre global sera écrasé par le paramètre local.
> Exemple:
>
> Mermaid est globalement handicapé. Par défaut, elle ne sera chargée par aucune page.
> À la page "Architecture", vous avez besoin d'un diagramme de classe. Vous pouvez régler les paramètres de mermaid localement pour ne charger que la sirène sur cette page (pas sur les autres).
Vous pouvez également désactiver mermaid pour des pages spécifiques tout en l'activant globalement.
<<<<<<< HEAD
## Configuration du bouton Accueil
Si l'option `disableLandingPage` est définie sur `false`, un bouton
"Accueil" apparaîtra dans le menu de gauche. C'est une alternative pour cliquer
sur le logo. Pour modifier le vous devrez configurer deux paramètres pour les
langues définies :
```toml
[Lanugages]
[Lanugages.en]
...
landingPageURL = "/en"
landingPageName = "<i class='fas fa-home'></i> Redirect to Home"
...
[Lanugages.fr]
...
landingPageURL = "/fr"
landingPageName = "<i class='fas fa-home'></i> Accueil"
...
```
Si ces paramètres ne sont pas configurés pour une langue spécifique, ils
obtiendront leur valeurs par défaut:
```toml
landingPageURL = "/"
landingPageName = "<i class='fas fa-home'></i> Home"
```
Le bouton d'accueil va ressembler à ceci:
![Default Home Button](/en/basics/configuration/images/home_button_defaults.jpg?width=100%)
=======
>>>>>>> 023fe7ef2b4c45fe66ac932d9e25d09f30b74a4e

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -0,0 +1,102 @@
---
title: Installation
weight: 15
---
The following steps are here to help you initialize your new website. If you don't know Hugo at all, we strongly suggest you learn more about it by following this [great documentation for beginners](https://gohugo.io/overview/quickstart/).
## Create your project
Hugo provides a `new` command to create a new website.
```
hugo new site <new_project>
```
## Install the theme
Install the **Hugo-theme-learn** theme by following [this documentation](https://gohugo.io/getting-started/quick-start/#step-3-add-a-theme)
This theme's repository is: https://github.com/matcornic/hugo-theme-learn.git
Alternatively, you can [download the theme as .zip](https://github.com/matcornic/hugo-theme-learn/archive/master.zip) file and extract it in the `themes` directory
## Basic configuration
When building the website, you can set a theme by using `--theme` option. However, we suggest you modify the configuration file (`config.toml`) and set the theme as the default. You can also add the `[outputs]` section to enable the search functionality.
```toml
# Change the default theme to be use when building the site with Hugo
theme = "hugo-theme-learn"
# For search functionality
[outputs]
home = [ "HTML", "RSS", "JSON"]
```
## Create your first chapter page
Chapters are pages that contain other child pages. It has a special layout style and usually just contains a _chapter name_, the _title_ and a _brief abstract_ of the section.
```
### Chapter 1
# Basics
Discover what this Hugo theme is all about and the core concepts behind it.
```
renders as
![A Chapter](/en/basics/installation/images/chapter.png?classes=shadow&width=60pc)
**Hugo-theme-learn** provides archetypes to create skeletons for your website. Begin by creating your first chapter page with the following command
```
hugo new --kind chapter basics/_index.md
```
By opening the given file, you should see the property `chapter=true` on top, meaning this page is a _chapter_.
By default all chapters and pages are created as a draft. If you want to render these pages, remove the property `draft: true` from the metadata.
## Create your first content pages
Then, create content pages inside the previously created chapter. Here are two ways to create content in the chapter:
```
hugo new basics/first-content.md
hugo new basics/second-content/_index.md
```
Feel free to edit thoses files by adding some sample content and replacing the `title` value in the beginning of the files.
## Launching the website locally
Launch by using the following command:
```
hugo serve
```
Go to `http://localhost:1313`
You should notice three things:
1. You have a left-side **Basics** menu, containing two submenus with names equal to the `title` properties in the previously created files.
2. The home page explains how to customize it by following the instructions.
3. When you run `hugo serve`, when the contents of the files change, the page automatically refreshes with the changes. Neat!
## Build the website
When your site is ready to deploy, run the following command:
```
hugo
```
A `public` folder will be generated, containing all static content and assets for your website. It can now be deployed on any web server.
{{% notice note %}}
This website can be automatically published and hosted with [Netlify](https://www.netlify.com/) (Read more about [Automated HUGO deployments with Netlify](https://www.netlify.com/blog/2015/07/30/hosting-hugo-on-netlifyinsanely-fast-deploys/)). Alternatively, you can use [Github pages](https://gohugo.io/hosting-and-deployment/hosting-on-github/)
{{% /notice %}}

View File

@ -0,0 +1,100 @@
---
title: Installation
weight: 15
---
Les étapes suivantes sont là pour vous aider à initialiser votre site. Si vous ne connaissez pas du tout Hugo, il est fortement conseillé de vous entrainer en suivant ce [super tuto pour débutants](https://gohugo.io/overview/quickstart/).
## Créer votre projet
Hugo fournit une commande `new` pour créer un nouveau site.
```
hugo new site <new_project>
```
## Installer le thème
Installer le thème **Hugo-theme-learn** en suivant [cette documentation](https://gohugo.io/themes/installing/)
Le repo du thème est : https://github.com/matcornic/hugo-theme-learn.git
Sinon, vous pouvez [télécharger le thème sous forme d'un fichier .zip](https://github.com/matcornic/hugo-theme-learn/archive/master.zip) et extrayez le dans votre dossier de thèmes.
## Configuration simple
Lorsque vous générez votre site, vous pouvez définir un thème en utilisant l'option `--theme`. Il est conseillé de modifier votre fichier de configuration `config.toml` and définir votre thème par défaut. En passant, ajoutez les prérequis à l'utilisation de la fonctionnalité de recherche.
```toml
# Modifiez le thème pour qu'il soit utilisé par défaut à chaque génération de site.
theme = "hugo-theme-learn"
# Pour la fonctionnalité de recherche
[outputs]
home = [ "HTML", "RSS", "JSON"]
```
## Créer votre première page chapitre
Les *chapitres* sont des pages contenant d'autre pages filles. Elles ont un affichage spécial et contiennent habituellement juste un _nom_ de chapitre, le _titre_ et un _résumé_ de la section.
```
### Chapitre 1
# Démarrage
Découvrez comment utiliser ce thème Hugo et apprenez en les concepts
```
s'affiche comme
![Un chapitre](/en/basics/installation/images/chapter.png?classes=shadow&width=60pc)
**Hugo-theme-learn** fournit des archétypes pour créer des squelettes pour votre site. Commencez par créer votre premier chapitre avec la commande suivante:
```
hugo new --kind chapter basics/_index.md
```
En ouvrant le fichier généré, vous devriez voir la propriété `chapter=true` en haut, paramètre quit définit que le page est un _chapitre_.
## Créer votre première page
Puis, créez votre premier page dans le chapitre précédent. Pour ce faire, il existe deux possibilités :
```
hugo new basics/first-content.md
hugo new basics/second-content/_index.md
```
N'hésitez pas à éditer ces fichiers en ajoutant des exemple de contenu et en remplaçant le paramètre `title` au début du fichier.
## Lancer le site localement
Lancez la commande suivante :
```
hugo serve
```
Se rendre sur `http://localhost:1313`
Vous devriez voir trois choses:
1. Vous avez un menu **Basics** à gauche, qui contient deux sous-menu avec des noms égal au paramètre `title` des fichiers précédemment générés.
2. La page d'accueil vous explique comment la modifier. Suivez les instructions.
3. Avec la commande `hugo serve`, la page se rafraichit automatiquement à chaque fois que vous sauvegardez. Super !
## Générez le site
Quand votre site est prêt à être déployé, lancez la commande suivante:
```
hugo
```
Un dossier `public` a été généré. Il contient tout le contenu statique et les ressources nécessaires pour votre site. Votre site peut maintenant être déployé en utilisant n'importe quel serveur !
{{% notice note %}}
Ce site peut être automatiquement publié et hébergé avec [Netlify](https://www.netlify.com/) ([Plus d'infos](https://www.netlify.com/blog/2015/07/30/hosting-hugo-on-netlifyinsanely-fast-deploys/)). Sinon, vous pouvez utiliser les [Github pages](https://gohugo.io/hosting-and-deployment/hosting-on-github/)
{{% /notice %}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,11 @@
---
title: Requirements
weight: 10
disableToc: true
---
Thanks to the simplicity of Hugo, this page is as empty as this theme needs requirements.
Just download latest version of [Hugo binary (> 0.25)](https://gohugo.io/getting-started/installing/) for your OS (Windows, Linux, Mac) : it's that simple.
![Magic](/en/basics/requirements/images/magic.gif?classes=shadow)

View File

@ -0,0 +1,11 @@
---
title: Prérequis
weight: 10
disableToc: true
---
Grâce à la simplicité d'Hugo, cette page est vide car il n'y a quasi pas de prérequis pour utiliser le thème.
Téléchargez la dernière version du [binaire Hugo (> 0.25)](https://gohugo.io/getting-started/installing/) pour votre Système d'exploitation (Windows, Linux, Mac) : et c'est tout !
![Magic](/en/basics/requirements/images/magic.gif?classes=shadow)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,194 @@
---
date: 2016-04-09T16:50:16+02:00
title: Style customization
weight: 25
---
**Hugo-theme-learn** has been built to be as configurable as possible by defining multiple [partials](https://gohugo.io/templates/partials/)
In `themes/hugo-theme-learn/layouts/partials/`, you will find all the partials defined for this theme. If you need to overwrite something, don't change the code directly. Instead [follow this page](https://gohugo.io/themes/customizing/). You'd create a new partial in the `layouts/partials` folder of your local project. This partial will have the priority.
This theme defines the following partials :
- *header*: the header of the content page (contains the breadcrumbs). _Not meant to be overwritten_
- *custom-header*: custom headers in page. Meant to be overwritten when adding CSS imports. Don't forget to include `style` HTML tag directive in your file
- *footer*: the footer of the content page (contains the arrows). _Not meant to be overwritten_
- *custom-footer*: custom footer in page. Meant to be overwritten when adding Javacript. Don't forget to include `javascript` HTML tag directive in your file
- *favicon*: the favicon
- *logo*: the logo, on top left hand corner.
- *meta*: HTML meta tags, if you want to change default behavior
- *menu*: left menu. _Not meant to be overwritten_
- *menu-footer*: footer of the the left menu
- *search*: search box
- *toc*: table of contents
## Change the logo
Create a new file in `layouts/partials/` named `logo.html`. Then write any HTML you want.
You could use an `img` HTML tag and reference an image created under the *static* folder, or you could paste a SVG definition !
{{% notice note %}}
The size of the logo will adapt automatically
{{% /notice %}}
## Change the favicon
If your favicon is a png, just drop off your image in your local `static/images/` folder and name it `favicon.png`
If you need to change this default behavior, create a new file in `layouts/partials/` named `favicon.html`. Then write something like this:
```html
<link rel="shortcut icon" href="/images/favicon.png" type="image/x-icon" />
```
## Change default colors {#theme-variant}
**Hugo Learn theme** let you choose between 3 native color scheme variants, but feel free to add one yourself ! Default color scheme is based on [Grav Learn Theme](https://learn.getgrav.org/).
### Red variant
```toml
[params]
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = "red"
```
![Red variant](/en/basics/style-customization/images/red-variant.png?width=60pc)
### Blue variant
```toml
[params]
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = "blue"
```
![Blue variant](/en/basics/style-customization/images/blue-variant.png?width=60pc)
### Green variant
```toml
[params]
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = "green"
```
![Green variant](/en/basics/style-customization/images/green-variant.png?width=60pc)
### 'Yours variant
First, create a new CSS file in your local `static/css` folder prefixed by `theme` (e.g. with _mine_ theme `static/css/theme-mine.css`). Copy the following content and modify colors in CSS variables.
```css
:root{
--MAIN-TEXT-color:#323232; /* Color of text by default */
--MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */
--MAIN-LINK-color:#1C90F3; /* Color of links */
--MAIN-LINK-HOVER-color:#167ad0; /* Color of hovered links */
--MAIN-ANCHOR-color: #1C90F3; /* color of anchors on titles */
--MENU-HEADER-BG-color:#1C90F3; /* Background color of menu header */
--MENU-HEADER-BORDER-color:#33a1ff; /*Color of menu header border */
--MENU-SEARCH-BG-color:#167ad0; /* Search field background color (by default borders + icons) */
--MENU-SEARCH-BOX-color: #33a1ff; /* Override search field border color */
--MENU-SEARCH-BOX-ICONS-color: #a1d2fd; /* Override search field icons color */
--MENU-SECTIONS-ACTIVE-BG-color:#20272b; /* Background color of the active section and its childs */
--MENU-SECTIONS-BG-color:#252c31; /* Background color of other sections */
--MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */
--MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */
--MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */
--MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */
--MENU-VISITED-color: #33a1ff; /* Color of 'page visited' icons in menu */
--MENU-SECTION-HR-color: #20272b; /* Color of <hr> separator in menu */
}
body {
color: var(--MAIN-TEXT-color) !important;
}
textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus {
border-color: none;
box-shadow: none;
}
h2, h3, h4, h5 {
color: var(--MAIN-TITLES-TEXT-color) !important;
}
a {
color: var(--MAIN-LINK-color);
}
.anchor {
color: var(--MAIN-ANCHOR-color);
}
a:hover {
color: var(--MAIN-LINK-HOVER-color);
}
#sidebar ul li.visited > a .read-icon {
color: var(--MENU-VISITED-color);
}
#body a.highlight:after {
display: block;
content: "";
height: 1px;
width: 0%;
-webkit-transition: width 0.5s ease;
-moz-transition: width 0.5s ease;
-ms-transition: width 0.5s ease;
transition: width 0.5s ease;
background-color: var(--MAIN-LINK-HOVER-color);
}
#sidebar {
background-color: var(--MENU-SECTIONS-BG-color);
}
#sidebar #header-wrapper {
background: var(--MENU-HEADER-BG-color);
color: var(--MENU-SEARCH-BOX-color);
border-color: var(--MENU-HEADER-BORDER-color);
}
#sidebar .searchbox {
border-color: var(--MENU-SEARCH-BOX-color);
background: var(--MENU-SEARCH-BG-color);
}
#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active {
background: var(--MENU-SECTIONS-ACTIVE-BG-color);
}
#sidebar .searchbox * {
color: var(--MENU-SEARCH-BOX-ICONS-color);
}
#sidebar a {
color: var(--MENU-SECTIONS-LINK-color);
}
#sidebar a:hover {
color: var(--MENU-SECTIONS-LINK-HOVER-color);
}
#sidebar ul li.active > a {
background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color);
color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important;
}
#sidebar hr {
border-color: var(--MENU-SECTION-HR-color);
}
```
Then, set the `themeVariant` value with the name of your custom theme file. That's it !
```toml
[params]
# Change default color scheme with a variant one. Can be "red", "blue", "green".
themeVariant = "mine"
```

View File

@ -0,0 +1,194 @@
---
date: 2016-04-09T16:50:16+02:00
title: Personnalisation du style
weight: 25
---
**Hugo-theme-learn** a été conçu pour être aussi configurable que possible en définissant plusieurs [partials](https://gohugo.io/templates/partials/)
Dans `themes/hugo-theme-learn/layouts/partials/`, vous pourrez trouver tous les *partials* définis pour ce thème. Si vous avez besoin d'écraser quelque chose, ne modifiez pas le code directement. A la place, [suivez cette page](https://gohugo.io/themes/customizing/). Vous créerez alors un nouveau *partial* dans le dossier `layouts/partials` de votre site local. Ce *partial* aura la priorité.
Ce thème définit les *partials* suivant :
- *header*: l'en-tête de la page page (contient le fil d'Ariane). _Pas voué à être écrasé_
- *custom-header*: En-tête personnalisé. Voué à être écrasé quand vous ajoutez des imports CSS. N'oubliez pas d'inclure la balise HTML `style` dans votre fichier
- *footer*: le pied-de-page de la page (contains les flèches). _Pas voué à être écrasé_
- *custom-footer*: Pied-de-page personnalisé. Voué à être écrasé quand vous ajoutez du Javascript. N'oubliez pas d'inclure la balise HTML `javascript` dans votre fichier
- *favicon*: le favicon
- *logo*: le logo, affiché un haut à gauche.
- *meta*: les balises HTML meta, que vous pouvez écraser sans problème.
- *menu*: Le menu à gauche. _Pas voué à être écrasé_
- *menu-footer*: Le pied-de-page du menu
- *search*: le champ de recherche
- *toc*: le sommaire
## Changer le logo
Créez un nouveau fichier dans `layouts/partials/`, nommé `logo.html`. Puis, écrivez le code HTML voulu.
Vous pourriez utiliser une balise HTML `img` et référencer une image créée dans le dossier *static*, voire même y coller un code SVG !
{{% notice note %}}
La taille du logo va s'adapter automatiquement
{{% /notice %}}
## Changer le favicon
Si votre favicon est un png, déposez votre image dans votre dossier local `static/images/` et nommez le `favicon.png`
Si vous avez besoin de changer ce comportement par défaut, créer un nouveau fichier dans `layouts/partials/` et nommez le `favicon.html`. Puis ajoutez quelque chose comme:
```html
<link rel="shortcut icon" href="/images/favicon.png" type="image/x-icon" />
```
## Changer les couleurs par défaut {#theme-variant}
**Hugo Learn theme** vous permet de choisir nativement entre 3 schéma de couleurs, mais n'hésitez pas à en ajouter d'autres ! Les couleurs par défaut sont celles de [Grav Learn Theme](https://learn.getgrav.org/).
### Variante rouge
```toml
[params]
# Modifier le schéma de couleur par défaut. Peut être "red", "blue", "green".
themeVariant = "red"
```
![Variante rouge](/en/basics/style-customization/images/red-variant.png?width=60pc)
### Variante bleue
```toml
[params]
# Modifier le schéma de couleur par défaut. Peut être "red", "blue", "green".
themeVariant = "blue"
```
![Variante bleue](/en/basics/style-customization/images/blue-variant.png?width=60pc)
### Variante verte
```toml
[params]
# Modifier le schéma de couleur par défaut. Peut être "red", "blue", "green".
themeVariant = "green"
```
![Variante verte](/en/basics/style-customization/images/green-variant.png?width=60pc)
### Votre variante
Premièrement, créez un nouveau fichier CSS dans votre dossier `static/css`, préfixé par `theme` (ex: avec le theme_lemien_ `static/css/theme-lemien.css`). Copiez le contenu suivant et modifiez les couleurs dans les variables CSS.
```css
:root{
--MAIN-TEXT-color:#323232; /* Color of text by default */
--MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */
--MAIN-LINK-color:#1C90F3; /* Color of links */
--MAIN-LINK-HOVER-color:#167ad0; /* Color of hovered links */
--MAIN-ANCHOR-color: #1C90F3; /* color of anchors on titles */
--MENU-HEADER-BG-color:#1C90F3; /* Background color of menu header */
--MENU-HEADER-BORDER-color:#33a1ff; /*Color of menu header border */
--MENU-SEARCH-BG-color:#167ad0; /* Search field background color (by default borders + icons) */
--MENU-SEARCH-BOX-color: #33a1ff; /* Override search field border color */
--MENU-SEARCH-BOX-ICONS-color: #a1d2fd; /* Override search field icons color */
--MENU-SECTIONS-ACTIVE-BG-color:#20272b; /* Background color of the active section and its childs */
--MENU-SECTIONS-BG-color:#252c31; /* Background color of other sections */
--MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */
--MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */
--MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */
--MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */
--MENU-VISITED-color: #33a1ff; /* Color of 'page visited' icons in menu */
--MENU-SECTION-HR-color: #20272b; /* Color of <hr> separator in menu */
}
body {
color: var(--MAIN-TEXT-color) !important;
}
textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus {
border-color: none;
box-shadow: none;
}
h2, h3, h4, h5 {
color: var(--MAIN-TITLES-TEXT-color) !important;
}
a {
color: var(--MAIN-LINK-color);
}
.anchor {
color: var(--MAIN-ANCHOR-color);
}
a:hover {
color: var(--MAIN-LINK-HOVER-color);
}
#sidebar ul li.visited > a .read-icon {
color: var(--MENU-VISITED-color);
}
#body a.highlight:after {
display: block;
content: "";
height: 1px;
width: 0%;
-webkit-transition: width 0.5s ease;
-moz-transition: width 0.5s ease;
-ms-transition: width 0.5s ease;
transition: width 0.5s ease;
background-color: var(--MAIN-LINK-HOVER-color);
}
#sidebar {
background-color: var(--MENU-SECTIONS-BG-color);
}
#sidebar #header-wrapper {
background: var(--MENU-HEADER-BG-color);
color: var(--MENU-SEARCH-BOX-color);
border-color: var(--MENU-HEADER-BORDER-color);
}
#sidebar .searchbox {
border-color: var(--MENU-SEARCH-BOX-color);
background: var(--MENU-SEARCH-BG-color);
}
#sidebar ul.topics > li.parent, #sidebar ul.topics > li.active {
background: var(--MENU-SECTIONS-ACTIVE-BG-color);
}
#sidebar .searchbox * {
color: var(--MENU-SEARCH-BOX-ICONS-color);
}
#sidebar a {
color: var(--MENU-SECTIONS-LINK-color);
}
#sidebar a:hover {
color: var(--MENU-SECTIONS-LINK-HOVER-color);
}
#sidebar ul li.active > a {
background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color);
color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important;
}
#sidebar hr {
border-color: var(--MENU-SECTION-HR-color);
}
```
Puis, configurez le paramètre `themeVariant` avec le nom de votre variante. C'est tout !
```toml
[params]
# Modifier le schéma de couleur par défaut. Peut être "red", "blue", "green".
themeVariant = "lemien"
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

View File

@ -0,0 +1,12 @@
---
title: Content
weight: 10
chapter: true
pre: "<b>2. </b>"
---
### Chapter 2
# Content
Find out how to create and organize your content quickly and intuitively.

View File

@ -0,0 +1,12 @@
---
title: Contenu
weight: 10
chapter: true
pre: "<b>2. </b>"
---
### Chapitre 2
# Contenu
Découvrez comment créer et organiser votre contenu facilement et intuitivement.

View File

@ -0,0 +1,57 @@
---
title: Archetypes
weight: 10
---
Using the command: `hugo new [relative new content path]`, you can start a content file with the date and title automatically set. While this is a welcome feature, active writers need more: [archetypes](https://gohugo.io/content/archetypes/).
It is pre-configured skeleton pages with default front matter. Please refer to the documentation for types of page to understand the differences.
## Chapter {#archetypes-chapter}
To create a Chapter page, run the following commands
```
hugo new --kind chapter <name>/_index.md
```
It will create a page with predefined Front-Matter:
```markdown
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
chapter = true
pre = "<b>X. </b>"
+++
### Chapter X
# Some Chapter title
Lorem Ipsum.
```
## Default
To create a default page, run either one of the following commands
```
# Either
hugo new <chapter>/<name>/_index.md
# Or
hugo new <chapter>/<name>.md
```
It will create a page with predefined Front-Matter:
```markdown
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
+++
Lorem Ipsum.
```

View File

@ -0,0 +1,57 @@
---
title: Archétypes
weight: 10
---
En utilisant la commande: `hugo new [chemin vers nouveau contenu]`, vous pouvez créer un nouveau fichier avec la date et le title automatiquement initialisé. Même si c'est une fonctionnalité intéressante, elle reste limitée pour les auteurs actifs qui ont besoin de mieux : les [archetypes](https://gohugo.io/content/archetypes/).
Les archétypes sont des squelettes de pages préconfigurées avec un Front Matter par défaut. Merci de vous référer à la documentation pour connaitre les différents types de page.
## Chapitre {#archetypes-chapter}
Pour créer un chapitre, lancez les commandes suivantes
```
hugo new --kind chapter <name>/_index.md
```
Cela crééra une page avec le Front Matter suivant:
```markdown
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
chapter = true
pre = "<b>X. </b>"
+++
### Chapter X
# Some Chapter title
Lorem Ipsum.
```
## Défaut
Pour créer une page classique, lancer l'une des deux commandes suivantes
```
# Soit
hugo new <chapter>/<name>/_index.md
# Ou
hugo new <chapter>/<name>.md
```
Cela crééra une page avec le Front Matter suivant:
```markdown
+++
title = "{{ replace .Name "-" " " | title }}"
date = {{ .Date }}
weight = 5
+++
Lorem Ipsum.
```

View File

@ -0,0 +1,78 @@
---
date: 2016-04-09T16:50:16+02:00
title: Multilingual and i18n
weight: 30
---
**Learn theme** is fully compatible with Hugo multilingual mode.
It provides:
- Translation strings for default values (English and French). Feel free to contribute !
- Automatic menu generation from multilingual content
- In-browser language switching
![I18n menu](/en/cont/i18n/images/i18n-menu.gif)
## Basic configuration
After learning [how Hugo handle multilingual websites](https://gohugo.io/content-management/multilingual), define your languages in your `config.toml` file.
For example with current French and English website.
```toml
# English is the default language
defaultContentLanguage = "en"
# Force to have /en/my-page and /fr/my-page routes, even for default language.
defaultContentLanguageInSubdir= true
[Languages]
[Languages.en]
title = "Documentation for Hugo Learn Theme"
weight = 1
languageName = "English"
[Languages.fr]
title = "Documentation du thème Hugo Learn"
weight = 2
languageName = "Français"
```
Then, for each new page, append the *id* of the language to the file.
- Single file `my-page.md` is split in two files:
- in English: `my-page.en.md`
- in French: `my-page.fr.md`
- Single file `_index.md` is split in two files:
- in English: `_index.en.md`
- in French: `_index.fr.md`
{{% notice info %}}
Be aware that only translated pages are displayed in menu. It's not replaced with default language content.
{{% /notice %}}
{{% notice tip %}}
Use [slug](https://gohugo.io/content-management/multilingual/#translate-your-content) Front Matter parameter to translate urls too.
{{% /notice %}}
## Overwrite translation strings
Translations strings are used for common default values used in the theme (*Edit this page* button, *Search placeholder* and so on). Translations are available in french and english but you may use another language or want to override default values.
To override these values, create a new file in your local i18n folder `i18n/<idlanguage>.toml` and inspire yourself from the theme `themes/hugo-theme-learn/i18n/en.toml`
By the way, as these translations could be used by other people, please take the time to propose a translation by [making a PR](https://github.com/matcornic/hugo-theme-learn/pulls) to the theme !
## Disable language switching
Switching the language in the browser is a great feature, but for some reasons you may want to disable it.
Just set `disableLanguageSwitchingButton=true` in your `config.toml`
```toml
[params]
# When using mulitlingual website, disable the switch language button.
disableLanguageSwitchingButton = true
```
![I18n menu](/en/cont/i18n/images/i18n-menu.gif)

View File

@ -0,0 +1,78 @@
---
date: 2016-04-09T16:50:16+02:00
title: Multi-langue et i18n
weight: 30
---
**Learn** est complètement compatible avec le mode multi-langue d'Hugo.
Il fournit :
- Des *translation strings* pour les valeurs par défaut utilisées par le thème (Anglais et Français). N'hésitez pas à contribuer !
- Génération automatique du menu avec le contenu multi-langue
- Modification de la langue dans le navigateur
![I18n menu](/en/cont/i18n/images/i18n-menu.gif)
## Configuration simple
Après avoir appris [comment Hugo gère les sites multi-langue](https://gohugo.io/content-management/multilingual), définissez vos langues dans votre fichier `config.toml`.
Par exemple, pour ce site, avec du contenu en français et en anglais.
```toml
# Anglais est la langue par défaut
defaultContentLanguage = "en"
# Force d'avoir /en/ma-page et /fr/ma-page routes, même avec la langue par défaut.
defaultContentLanguageInSubdir= true
[Languages]
[Languages.en]
title = "Documentation for Hugo Learn Theme"
weight = 1
languageName = "English"
[Languages.fr]
title = "Documentation du thème Hugo Learn"
weight = 2
languageName = "Français"
```
Puis, pour chaque nouvelle page, ajoutez *l'id* de la langue du fichier.
- Le fichier `my-page.md` est découpé en deux fichiers :
- en anglais : `my-page.en.md`
- en français : `my-page.fr.md`
- Le fichier `_index.md` est découpé en deux fichiers :
- en anglais: `_index.en.md`
- en français: `_index.fr.md`
{{% notice info %}}
Attention, seulement les pages traduites sont affichées dans le menu. Le contenu n'est pas remplacé par les pages de la langue par défaut.
{{% /notice %}}
{{% notice tip %}}
Utilisez le paramètre du Front Matter [slug](https://gohugo.io/content-management/multilingual/#translate-your-content) pour traduire également les URLs.
{{% /notice %}}
## Surcharger les *translation strings*
Les *Translations strings* sont utilisées comme valeurs par défaut dans le thème (Bouton *Modifier la page*, Element de subsitution *Recherche*, etc.). Les traductions sont disponibles en français et en anglais mais vous pouvez utiliser n'importe quelle autre langue et surcharger avec vos propres valeurs.
Pour surcharger ces valeurs, créer un nouveau fichier dans votre dossier i18n local `i18n/<idlanguage>.toml` et inspirez vous du thème `themes/hugo-theme-learn/i18n/en.toml`
D'ailleurs, ces traductions pour servir à tout le monde, donc svp prenez le temps de [proposer une Pull Request](https://github.com/matcornic/hugo-theme-learn/pulls) !
## Désactiver le changement de langue
Vous pouvez changer de langue directement dans le navigateur. C'est une super fonctionnalité, mais vous avez peut-être besoin de la désactiver.
Pour ce faire, ajouter le paramètre `disableLanguageSwitchingButton=true` dans votre `config.toml`
```toml
[params]
# Quand vous utilisez un site en multi-langue, désactive le bouton de changment de langue.
disableLanguageSwitchingButton = true
```
![I18n menu](/en/cont/i18n/images/i18n-menu.gif)

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,41 @@
---
title: Icons and logos
weight: 27
---
The Learn theme for Hugo loads the [**Font Awesome**](https://fontawesome.com) library, allowing you to easily display any icon or logo available in the Font Awesome free collection.
## Finding an icon
Browse through the available icons in the [Font Awesome Gallery](https://fontawesome.com/icons?d=gallery&m=free). Notice that the **free** filter is enabled, as only the free icons are available by default.
Once on the Font Awesome page for a specific icon, for example the page for the [heart](https://fontawesome.com/icons/heart?style=solid), copy the HTML reference and paste into the markdown content.
The HTML to include the heart icon is:
```
<i class="fas fa-heart"></i>
```
## Including in markdown
Paste the `<i>` HTML into markup and Font Awesome will load the relevant icon.
```
Built with <i class="fas fa-heart"></i> from Grav and Hugo
```
Which appears as
Built with <i class="fas fa-heart"></i> from Grav and Hugo
## Customising icons
Font Awesome provides many ways to modify the icon
* Change colour (by default the icon will inherit the parent colour)
* Increase or decrease size
* Rotate
* Combine with other icons
Check the full documentation on [web fonts with CSS](https://fontawesome.com/how-to-use/web-fonts-with-css) for more.

View File

@ -0,0 +1,692 @@
---
date: 2016-04-09T16:50:16+02:00
title: Markdown syntax
weight: 15
---
{{% notice note %}}
This page is a shameful copy of the great [Grav original page](http://learn.getgrav.org/content/markdown).
Only difference is information about image customization ([resizing]({{< relref "#resizing-image" >}}), [add CSS classes]({{< relref "#add-css-classes" >}})...)
{{% /notice%}}
Let's face it: Writing content for the Web is tiresome. WYSIWYG editors help alleviate this task, but they generally result in horrible code, or worse yet, ugly web pages.
**Markdown** is a better way to write **HTML**, without all the complexities and ugliness that usually accompanies it.
Some of the key benefits are:
1. Markdown is simple to learn, with minimal extra characters so it's also quicker to write content.
2. Less chance of errors when writing in markdown.
3. Produces valid XHTML output.
4. Keeps the content and the visual display separate, so you cannot mess up the look of your site.
5. Write in any text editor or Markdown application you like.
6. Markdown is a joy to use!
John Gruber, the author of Markdown, puts it like this:
> The overriding design goal for Markdowns formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like its been marked up with tags or formatting instructions. While Markdowns syntax has been influenced by several existing text-to-HTML filters, the single biggest source of inspiration for Markdowns syntax is the format of plain text email.
> -- John Gruber
Grav ships with built-in support for [Markdown](http://daringfireball.net/projects/markdown/) and [Markdown Extra](https://michelf.ca/projects/php-markdown/extra/). You must enable **Markdown Extra** in your `system.yaml` configuration file
Without further delay, let us go over the main elements of Markdown and what the resulting HTML looks like:
{{% notice info %}}
<i class="fas fa-bookmark"></i> Bookmark this page for easy future reference!
{{% /notice %}}
## Headings
Headings from `h1` through `h6` are constructed with a `#` for each level:
```markdown
# h1 Heading
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
```
Renders to:
<!-- markdownlint-disable MD025 -->
# h1 Heading
<!-- markdownlint-enable MD025 -->
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
HTML:
```html
<h1>h1 Heading</h1>
<h2>h2 Heading</h2>
<h3>h3 Heading</h3>
<h4>h4 Heading</h4>
<h5>h5 Heading</h5>
<h6>h6 Heading</h6>
```
## Comments
Comments should be HTML compatible
```html
<!--
This is a comment
-->
```
Comment below should **NOT** be seen:
<!--
This is a comment
-->
## Horizontal Rules
The HTML `<hr>` element is for creating a "thematic break" between paragraph-level elements. In markdown, you can create a `<hr>` with any of the following:
* `___`: three consecutive underscores
* `---`: three consecutive dashes
* `***`: three consecutive asterisks
renders to:
___
## Body Copy
Body copy written as normal, plain text will be wrapped with `<p></p>` tags in the rendered HTML.
So this body copy:
```markdown
Lorem ipsum dolor sit amet, graecis denique ei vel, at duo primis mandamus. Et legere ocurreret pri, animal tacimates complectitur ad cum. Cu eum inermis inimicus efficiendi. Labore officiis his ex, soluta officiis concludaturque ei qui, vide sensibus vim ad.
```
renders to this HTML:
```html
<p>Lorem ipsum dolor sit amet, graecis denique ei vel, at duo primis mandamus. Et legere ocurreret pri, animal tacimates complectitur ad cum. Cu eum inermis inimicus efficiendi. Labore officiis his ex, soluta officiis concludaturque ei qui, vide sensibus vim ad.</p>
```
## Emphasis
### Bold
For emphasizing a snippet of text with a heavier font-weight.
The following snippet of text is **rendered as bold text**.
```markdown
**rendered as bold text**
```
renders to:
<!-- markdownlint-disable MD036 -->
**rendered as bold text**
<!-- markdownlint-enable MD036 -->
and this HTML
```html
<strong>rendered as bold text</strong>
```
### Italics
For emphasizing a snippet of text with italics.
The following snippet of text is _rendered as italicized text_.
```markdown
_rendered as italicized text_
```
renders to:
<!-- markdownlint-disable MD036 -->
_rendered as italicized text_
<!-- markdownlint-enable MD036 -->
and this HTML:
```html
<em>rendered as italicized text</em>
```
### Strikethrough
In GFM (GitHub flavored Markdown) you can do strikethroughs.
```markdown
~~Strike through this text.~~
```
Which renders to:
~~Strike through this text.~~
HTML:
```html
<del>Strike through this text.</del>
```
## Blockquotes
For quoting blocks of content from another source within your document.
Add `>` before any text you want to quote.
```markdown
> **Fusion Drive** combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.
```
Renders to:
> **Fusion Drive** combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.
and this HTML:
```html
<blockquote>
<p><strong>Fusion Drive</strong> combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.</p>
</blockquote>
```
Blockquotes can also be nested:
```markdown
> Donec massa lacus, ultricies a ullamcorper in, fermentum sed augue. Nunc augue augue, aliquam non hendrerit ac, commodo vel nisi.
>
> > Sed adipiscing elit vitae augue consectetur a gravida nunc vehicula. Donec auctor odio non est accumsan facilisis. Aliquam id turpis in dolor tincidunt mollis ac eu diam.
>
> Mauris sit amet ligula egestas, feugiat metus tincidunt, luctus libero. Donec congue finibus tempor. Vestibulum aliquet sollicitudin erat, ut aliquet purus posuere luctus.
```
Renders to:
> Donec massa lacus, ultricies a ullamcorper in, fermentum sed augue. Nunc augue augue, aliquam non hendrerit ac, commodo vel nisi.
>
> > Sed adipiscing elit vitae augue consectetur a gravida nunc vehicula. Donec auctor odio non est accumsan facilisis. Aliquam id turpis in dolor tincidunt mollis ac eu diam.
>
> Mauris sit amet ligula egestas, feugiat metus tincidunt, luctus libero. Donec congue finibus tempor. Vestibulum aliquet sollicitudin erat, ut aliquet purus posuere luctus.
## Notices
{{% notice note %}}
The old mechanism for notices overriding the block quote syntax (`>>>`) has been deprecated. Notices are now handled via a dedicated plugin called [Markdown Notices](https://github.com/getgrav/grav-plugin-markdown-notices)
{{% /notice %}}
## Lists
### Unordered
A list of items in which the order of the items does not explicitly matter.
You may use any of the following symbols to denote bullets for each list item:
```markdown
* valid bullet
- valid bullet
+ valid bullet
```
For example
```markdown
+ Lorem ipsum dolor sit amet
+ Consectetur adipiscing elit
+ Integer molestie lorem at massa
+ Facilisis in pretium nisl aliquet
+ Nulla volutpat aliquam velit
- Phasellus iaculis neque
- Purus sodales ultricies
- Vestibulum laoreet porttitor sem
- Ac tristique libero volutpat at
+ Faucibus porta lacus fringilla vel
+ Aenean sit amet erat nunc
+ Eget porttitor lorem
```
Renders to:
<!-- markdownlint-disable MD004 -->
+ Lorem ipsum dolor sit amet
+ Consectetur adipiscing elit
+ Integer molestie lorem at massa
+ Facilisis in pretium nisl aliquet
+ Nulla volutpat aliquam velit
- Phasellus iaculis neque
- Purus sodales ultricies
- Vestibulum laoreet porttitor sem
- Ac tristique libero volutpat at
+ Faucibus porta lacus fringilla vel
+ Aenean sit amet erat nunc
+ Eget porttitor lorem
<!-- markdownlint-enable MD004 -->
And this HTML
```html
<ul>
<li>Lorem ipsum dolor sit amet</li>
<li>Consectetur adipiscing elit</li>
<li>Integer molestie lorem at massa</li>
<li>Facilisis in pretium nisl aliquet</li>
<li>Nulla volutpat aliquam velit
<ul>
<li>Phasellus iaculis neque</li>
<li>Purus sodales ultricies</li>
<li>Vestibulum laoreet porttitor sem</li>
<li>Ac tristique libero volutpat at</li>
</ul>
</li>
<li>Faucibus porta lacus fringilla vel</li>
<li>Aenean sit amet erat nunc</li>
<li>Eget porttitor lorem</li>
</ul>
```
### Ordered
A list of items in which the order of items does explicitly matter.
```markdown
1. Lorem ipsum dolor sit amet
4. Consectetur adipiscing elit
2. Integer molestie lorem at massa
8. Facilisis in pretium nisl aliquet
4. Nulla volutpat aliquam velit
99. Faucibus porta lacus fringilla vel
21. Aenean sit amet erat nunc
6. Eget porttitor lorem
```
Renders to:
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. Facilisis in pretium nisl aliquet
5. Nulla volutpat aliquam velit
6. Faucibus porta lacus fringilla vel
7. Aenean sit amet erat nunc
8. Eget porttitor lorem
And this HTML:
```html
<ol>
<li>Lorem ipsum dolor sit amet</li>
<li>Consectetur adipiscing elit</li>
<li>Integer molestie lorem at massa</li>
<li>Facilisis in pretium nisl aliquet</li>
<li>Nulla volutpat aliquam velit</li>
<li>Faucibus porta lacus fringilla vel</li>
<li>Aenean sit amet erat nunc</li>
<li>Eget porttitor lorem</li>
</ol>
```
**TIP**: If you just use `1.` for each number, Markdown will automatically number each item. For example:
```markdown
1. Lorem ipsum dolor sit amet
1. Consectetur adipiscing elit
1. Integer molestie lorem at massa
1. Facilisis in pretium nisl aliquet
1. Nulla volutpat aliquam velit
1. Faucibus porta lacus fringilla vel
1. Aenean sit amet erat nunc
1. Eget porttitor lorem
```
Renders to:
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. Facilisis in pretium nisl aliquet
5. Nulla volutpat aliquam velit
6. Faucibus porta lacus fringilla vel
7. Aenean sit amet erat nunc
8. Eget porttitor lorem
## Code
### Inline code
Wrap inline snippets of code with `` ` ``.
```markdown
In this example, `<section></section>` should be wrapped as **code**.
```
Renders to:
In this example, `<section></section>` should be wrapped as **code**.
HTML:
```html
<p>In this example, <code>&lt;section&gt;&lt;/section&gt;</code> should be wrapped as <strong>code</strong>.</p>
```
### Indented code
Or indent several lines of code by at least two spaces, as in:
```markdown
// Some comments
line 1 of code
line 2 of code
line 3 of code
```
Renders to:
<!-- markdownlint-disable MD046 -->
// Some comments
line 1 of code
line 2 of code
line 3 of code
<!-- markdownlint-enable MD046 -->
HTML:
```html
<pre>
<code>
// Some comments
line 1 of code
line 2 of code
line 3 of code
</code>
</pre>
```
### Block code "fences"
Use "fences" ```` ``` ```` to block in multiple lines of code.
```markdown
Sample text here...
```
HTML:
```html
<pre>
<code>Sample text here...</code>
</pre>
```
### Syntax highlighting
GFM, or "GitHub Flavored Markdown" also supports syntax highlighting. To activate it, simply add the file extension of the language you want to use directly after the first code "fence", ` ```js `, and syntax highlighting will automatically be applied in the rendered HTML.
See [Code Highlighting]({{< ref "syntaxhighlight.md" >}}) for additional documentation.
For example, to apply syntax highlighting to JavaScript code:
```plaintext
```js
grunt.initConfig({
assemble: {
options: {
assets: 'docs/assets',
data: 'src/data/*.{json,yml}',
helpers: 'src/custom-helpers.js',
partials: ['src/partials/**/*.{hbs,md}']
},
pages: {
options: {
layout: 'default.hbs'
},
files: {
'./': ['src/templates/pages/index.hbs']
}
}
}
};
```
```
Renders to:
```js
grunt.initConfig({
assemble: {
options: {
assets: 'docs/assets',
data: 'src/data/*.{json,yml}',
helpers: 'src/custom-helpers.js',
partials: ['src/partials/**/*.{hbs,md}']
},
pages: {
options: {
layout: 'default.hbs'
},
files: {
'./': ['src/templates/pages/index.hbs']
}
}
}
};
```
## Tables
Tables are created by adding pipes as dividers between each cell, and by adding a line of dashes (also separated by bars) beneath the header. Note that the pipes do not need to be vertically aligned.
```markdown
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
```
Renders to:
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
And this HTML:
```html
<table>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
<tr>
<td>data</td>
<td>path to data files to supply the data that will be passed into templates.</td>
</tr>
<tr>
<td>engine</td>
<td>engine to be used for processing templates. Handlebars is the default.</td>
</tr>
<tr>
<td>ext</td>
<td>extension to be used for dest files.</td>
</tr>
</table>
```
### Right aligned text
Adding a colon on the right side of the dashes below any heading will right align text for that column.
```markdown
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
```
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
### Basic link
```markdown
[Assemble](http://assemble.io)
```
Renders to (hover over the link, there is no tooltip):
[Assemble](http://assemble.io)
HTML:
```html
<a href="http://assemble.io">Assemble</a>
```
### Add a tooltip
```markdown
[Upstage](https://github.com/upstage/ "Visit Upstage!")
```
Renders to (hover over the link, there should be a tooltip):
[Upstage](https://github.com/upstage/ "Visit Upstage!")
HTML:
```html
<a href="https://github.com/upstage/" title="Visit Upstage!">Upstage</a>
```
### Named Anchors
Named anchors enable you to jump to the specified anchor point on the same page. For example, each of these chapters:
```markdown
# Table of Contents
* [Chapter 1](#chapter-1)
* [Chapter 2](#chapter-2)
* [Chapter 3](#chapter-3)
```
will jump to these sections:
```markdown
## Chapter 1 <a id="chapter-1"></a>
Content for chapter one.
## Chapter 2 <a id="chapter-2"></a>
Content for chapter one.
## Chapter 3 <a id="chapter-3"></a>
Content for chapter one.
```
**NOTE** that specific placement of the anchor tag seems to be arbitrary. They are placed inline here since it seems to be unobtrusive, and it works.
## Images {#images}
Images have a similar syntax to links but include a preceding exclamation point.
```markdown
![Minion](https://octodex.github.com/images/minion.png)
```
![Minion](https://octodex.github.com/images/minion.png)
or
```markdown
![Alt text](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
```
![Alt text](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
Like links, Images also have a footnote style syntax
### Alternative usage : note images
```markdown
![Alt text][id]
```
![Alt text][id]
With a reference later in the document defining the URL location:
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
### Resizing image
Add HTTP parameters `width` and/or `height` to the link image to resize the image. Values are CSS values (default is `auto`).
```markdown
![Minion](https://octodex.github.com/images/minion.png?width=20pc)
```
![Minion](https://octodex.github.com/images/minion.png?width=20pc)
```markdown
![Minion](https://octodex.github.com/images/minion.png?height=50px)
```
![Minion](https://octodex.github.com/images/minion.png?height=50px)
```markdown
![Minion](https://octodex.github.com/images/minion.png?height=50px&width=300px)
```
![Minion](https://octodex.github.com/images/minion.png?height=50px&width=300px)
### Add CSS classes
Add a HTTP `classes` parameter to the link image to add CSS classes. `shadow`and `border` are available but you could define other ones.
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=shadow)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=shadow)
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=border)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=border)
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=border,shadow)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=border,shadow)
### Lightbox
Add a HTTP `featherlight` parameter to the link image to disable lightbox. By default lightbox is enabled using the featherlight.js plugin. You can disable this by defining `featherlight` to `false`.
```markdown
![Minion](https://octodex.github.com/images/minion.png?featherlight=false)
```

View File

@ -0,0 +1,666 @@
---
date: 2016-04-09T16:50:16+02:00
title: Syntaxe Markdown
weight: 15
---
{{% notice note %}}
Cette page est une copie de la [doc de Grav](http://learn.getgrav.org/content/markdown).
La seule différence porte sur la personalisation des images ([taille]({{< relref "#resizing-image" >}}), [ajout de classes CSS]({{< relref "#add-css-classes" >}})...)
Pour des raisons évidentes, cette page n'a pas été traduites en français 😁
{{% /notice%}}
Let's face it: Writing content for the Web is tiresome. WYSIWYG editors help alleviate this task, but they generally result in horrible code, or worse yet, ugly web pages.
**Markdown** is a better way to write **HTML**, without all the complexities and ugliness that usually accompanies it.
Some of the key benefits are:
1. Markdown is simple to learn, with minimal extra characters so it's also quicker to write content.
2. Less chance of errors when writing in markdown.
3. Produces valid XHTML output.
4. Keeps the content and the visual display separate, so you cannot mess up the look of your site.
5. Write in any text editor or Markdown application you like.
6. Markdown is a joy to use!
John Gruber, the author of Markdown, puts it like this:
> The overriding design goal for Markdowns formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like its been marked up with tags or formatting instructions. While Markdowns syntax has been influenced by several existing text-to-HTML filters, the single biggest source of inspiration for Markdowns syntax is the format of plain text email.
> -- <cite>John Gruber</cite>
Grav ships with built-in support for [Markdown](http://daringfireball.net/projects/markdown/) and [Markdown Extra](https://michelf.ca/projects/php-markdown/extra/). You must enable **Markdown Extra** in your `system.yaml` configuration file
Without further delay, let us go over the main elements of Markdown and what the resulting HTML looks like:
{{% notice info %}}
<i class="fas fa-bookmark"></i> Bookmark this page for easy future reference!
{{% /notice %}}
## Headings
Headings from `h1` through `h6` are constructed with a `#` for each level:
```markdown
# h1 Heading
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
```
Renders to:
# h1 Heading
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
HTML:
```html
<h1>h1 Heading</h1>
<h2>h2 Heading</h2>
<h3>h3 Heading</h3>
<h4>h4 Heading</h4>
<h5>h5 Heading</h5>
<h6>h6 Heading</h6>
```
## Comments
Comments should be HTML compatible
```html
<!--
This is a comment
-->
```
Comment below should **NOT** be seen:
<!--
This is a comment
-->
## Horizontal Rules
The HTML `<hr>` element is for creating a "thematic break" between paragraph-level elements. In markdown, you can create a `<hr>` with any of the following:
* `___`: three consecutive underscores
* `---`: three consecutive dashes
* `***`: three consecutive asterisks
renders to:
___
---
***
## Body Copy
Body copy written as normal, plain text will be wrapped with `<p></p>` tags in the rendered HTML.
So this body copy:
```markdown
Lorem ipsum dolor sit amet, graecis denique ei vel, at duo primis mandamus. Et legere ocurreret pri, animal tacimates complectitur ad cum. Cu eum inermis inimicus efficiendi. Labore officiis his ex, soluta officiis concludaturque ei qui, vide sensibus vim ad.
```
renders to this HTML:
```html
<p>Lorem ipsum dolor sit amet, graecis denique ei vel, at duo primis mandamus. Et legere ocurreret pri, animal tacimates complectitur ad cum. Cu eum inermis inimicus efficiendi. Labore officiis his ex, soluta officiis concludaturque ei qui, vide sensibus vim ad.</p>
```
## Emphasis
### Bold
For emphasizing a snippet of text with a heavier font-weight.
The following snippet of text is **rendered as bold text**.
```markdown
**rendered as bold text**
```
renders to:
**rendered as bold text**
and this HTML
```html
<strong>rendered as bold text</strong>
```
### Italics
For emphasizing a snippet of text with italics.
The following snippet of text is _rendered as italicized text_.
```markdown
_rendered as italicized text_
```
renders to:
_rendered as italicized text_
and this HTML:
```html
<em>rendered as italicized text</em>
```
### strikethrough
In GFM (GitHub flavored Markdown) you can do strikethroughs.
```markdown
~~Strike through this text.~~
```
Which renders to:
~~Strike through this text.~~
HTML:
```html
<del>Strike through this text.</del>
```
## Blockquotes
For quoting blocks of content from another source within your document.
Add `>` before any text you want to quote.
```markdown
> **Fusion Drive** combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.
```
Renders to:
> **Fusion Drive** combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.
and this HTML:
```html
<blockquote>
<p><strong>Fusion Drive</strong> combines a hard drive with a flash storage (solid-state drive) and presents it as a single logical volume with the space of both drives combined.</p>
</blockquote>
```
Blockquotes can also be nested:
```markdown
> Donec massa lacus, ultricies a ullamcorper in, fermentum sed augue. Nunc augue augue, aliquam non hendrerit ac, commodo vel nisi.
>
> > Sed adipiscing elit vitae augue consectetur a gravida nunc vehicula. Donec auctor odio non est accumsan facilisis. Aliquam id turpis in dolor tincidunt mollis ac eu diam.
>
> Mauris sit amet ligula egestas, feugiat metus tincidunt, luctus libero. Donec congue finibus tempor. Vestibulum aliquet sollicitudin erat, ut aliquet purus posuere luctus.
```
Renders to:
> Donec massa lacus, ultricies a ullamcorper in, fermentum sed augue. Nunc augue augue, aliquam non hendrerit ac, commodo vel nisi.
>
> > Sed adipiscing elit vitae augue consectetur a gravida nunc vehicula. Donec auctor odio non est accumsan facilisis. Aliquam id turpis in dolor tincidunt mollis ac eu diam.
>
> Mauris sit amet ligula egestas, feugiat metus tincidunt, luctus libero. Donec congue finibus tempor. Vestibulum aliquet sollicitudin erat, ut aliquet purus posuere luctus.
## Notices
{{% notice note %}}
The old mechanism for notices overriding the block quote syntax (`>>>`) has been deprecated. Notices are now handled via a dedicated plugin called [Markdown Notices](https://github.com/getgrav/grav-plugin-markdown-notices)
{{% /notice %}}
## Lists
### Unordered
A list of items in which the order of the items does not explicitly matter.
You may use any of the following symbols to denote bullets for each list item:
```markdown
* valid bullet
- valid bullet
+ valid bullet
```
For example
```markdown
+ Lorem ipsum dolor sit amet
+ Consectetur adipiscing elit
+ Integer molestie lorem at massa
+ Facilisis in pretium nisl aliquet
+ Nulla volutpat aliquam velit
- Phasellus iaculis neque
- Purus sodales ultricies
- Vestibulum laoreet porttitor sem
- Ac tristique libero volutpat at
+ Faucibus porta lacus fringilla vel
+ Aenean sit amet erat nunc
+ Eget porttitor lorem
```
Renders to:
+ Lorem ipsum dolor sit amet
+ Consectetur adipiscing elit
+ Integer molestie lorem at massa
+ Facilisis in pretium nisl aliquet
+ Nulla volutpat aliquam velit
- Phasellus iaculis neque
- Purus sodales ultricies
- Vestibulum laoreet porttitor sem
- Ac tristique libero volutpat at
+ Faucibus porta lacus fringilla vel
+ Aenean sit amet erat nunc
+ Eget porttitor lorem
And this HTML
```html
<ul>
<li>Lorem ipsum dolor sit amet</li>
<li>Consectetur adipiscing elit</li>
<li>Integer molestie lorem at massa</li>
<li>Facilisis in pretium nisl aliquet</li>
<li>Nulla volutpat aliquam velit
<ul>
<li>Phasellus iaculis neque</li>
<li>Purus sodales ultricies</li>
<li>Vestibulum laoreet porttitor sem</li>
<li>Ac tristique libero volutpat at</li>
</ul>
</li>
<li>Faucibus porta lacus fringilla vel</li>
<li>Aenean sit amet erat nunc</li>
<li>Eget porttitor lorem</li>
</ul>
```
### Ordered
A list of items in which the order of items does explicitly matter.
```markdown
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. Facilisis in pretium nisl aliquet
5. Nulla volutpat aliquam velit
6. Faucibus porta lacus fringilla vel
7. Aenean sit amet erat nunc
8. Eget porttitor lorem
```
Renders to:
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. Facilisis in pretium nisl aliquet
5. Nulla volutpat aliquam velit
6. Faucibus porta lacus fringilla vel
7. Aenean sit amet erat nunc
8. Eget porttitor lorem
And this HTML:
```html
<ol>
<li>Lorem ipsum dolor sit amet</li>
<li>Consectetur adipiscing elit</li>
<li>Integer molestie lorem at massa</li>
<li>Facilisis in pretium nisl aliquet</li>
<li>Nulla volutpat aliquam velit</li>
<li>Faucibus porta lacus fringilla vel</li>
<li>Aenean sit amet erat nunc</li>
<li>Eget porttitor lorem</li>
</ol>
```
**TIP**: If you just use `1.` for each number, Markdown will automatically number each item. For example:
```markdown
1. Lorem ipsum dolor sit amet
1. Consectetur adipiscing elit
1. Integer molestie lorem at massa
1. Facilisis in pretium nisl aliquet
1. Nulla volutpat aliquam velit
1. Faucibus porta lacus fringilla vel
1. Aenean sit amet erat nunc
1. Eget porttitor lorem
```
Renders to:
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. Facilisis in pretium nisl aliquet
5. Nulla volutpat aliquam velit
6. Faucibus porta lacus fringilla vel
7. Aenean sit amet erat nunc
8. Eget porttitor lorem
## Code
### Inline code
Wrap inline snippets of code with `` ` ``.
```markdown
In this example, `<section></section>` should be wrapped as **code**.
```
Renders to:
In this example, `<section></section>` should be wrapped with **code**.
HTML:
```html
<p>In this example, <code>&lt;section&gt;&lt;/section&gt;</code> should be wrapped with <strong>code</strong>.</p>
```
### Indented code
Or indent several lines of code by at least four spaces, as in:
<pre>
// Some comments
line 1 of code
line 2 of code
line 3 of code
</pre>
Renders to:
// Some comments
line 1 of code
line 2 of code
line 3 of code
HTML:
```html
<pre>
<code>
// Some comments
line 1 of code
line 2 of code
line 3 of code
</code>
</pre>
```
### Block code "fences"
Use "fences" ```` ``` ```` to block in multiple lines of code.
<pre>
``` markup
Sample text here...
```
</pre>
```
Sample text here...
```
HTML:
```html
<pre>
<code>Sample text here...</code>
</pre>
```
### Syntax highlighting
GFM, or "GitHub Flavored Markdown" also supports syntax highlighting. To activate it, simply add the file extension of the language you want to use directly after the first code "fence", ` ```js `, and syntax highlighting will automatically be applied in the rendered HTML. For example, to apply syntax highlighting to JavaScript code:
<pre>
```js
grunt.initConfig({
assemble: {
options: {
assets: 'docs/assets',
data: 'src/data/*.{json,yml}',
helpers: 'src/custom-helpers.js',
partials: ['src/partials/**/*.{hbs,md}']
},
pages: {
options: {
layout: 'default.hbs'
},
files: {
'./': ['src/templates/pages/index.hbs']
}
}
}
};
```
</pre>
Renders to:
```js
grunt.initConfig({
assemble: {
options: {
assets: 'docs/assets',
data: 'src/data/*.{json,yml}',
helpers: 'src/custom-helpers.js',
partials: ['src/partials/**/*.{hbs,md}']
},
pages: {
options: {
layout: 'default.hbs'
},
files: {
'./': ['src/templates/pages/index.hbs']
}
}
}
};
```
## Tables
Tables are created by adding pipes as dividers between each cell, and by adding a line of dashes (also separated by bars) beneath the header. Note that the pipes do not need to be vertically aligned.
```markdown
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
```
Renders to:
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
And this HTML:
```html
<table>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
<tr>
<td>data</td>
<td>path to data files to supply the data that will be passed into templates.</td>
</tr>
<tr>
<td>engine</td>
<td>engine to be used for processing templates. Handlebars is the default.</td>
</tr>
<tr>
<td>ext</td>
<td>extension to be used for dest files.</td>
</tr>
</table>
```
### Right aligned text
Adding a colon on the right side of the dashes below any heading will right align text for that column.
```markdown
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
```
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
### Basic link
```markdown
[Assemble](http://assemble.io)
```
Renders to (hover over the link, there is no tooltip):
[Assemble](http://assemble.io)
HTML:
```html
<a href="http://assemble.io">Assemble</a>
```
### Add a title
```markdown
[Upstage](https://github.com/upstage/ "Visit Upstage!")
```
Renders to (hover over the link, there should be a tooltip):
[Upstage](https://github.com/upstage/ "Visit Upstage!")
HTML:
```html
<a href="https://github.com/upstage/" title="Visit Upstage!">Upstage</a>
```
### Named Anchors
Named anchors enable you to jump to the specified anchor point on the same page. For example, each of these chapters:
```markdown
# Table of Contents
* [Chapter 1](#chapter-1)
* [Chapter 2](#chapter-2)
* [Chapter 3](#chapter-3)
```
will jump to these sections:
```markdown
## Chapter 1 <a id="chapter-1"></a>
Content for chapter one.
## Chapter 2 <a id="chapter-2"></a>
Content for chapter one.
## Chapter 3 <a id="chapter-3"></a>
Content for chapter one.
```
**NOTE** that specific placement of the anchor tag seems to be arbitrary. They are placed inline here since it seems to be unobtrusive, and it works.
## Images {#images}
Images have a similar syntax to links but include a preceding exclamation point.
```markdown
![Minion](https://octodex.github.com/images/minion.png)
```
![Minion](https://octodex.github.com/images/minion.png)
or
```markdown
![Alt text](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
```
![Alt text](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
Like links, Images also have a footnote style syntax
### Alternative usage : note images
```markdown
![Alt text][id]
```
![Alt text][id]
With a reference later in the document defining the URL location:
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
### Resizing image
Add HTTP parameters `width` and/or `height` to the link image to resize the image. Values are CSS values (default is `auto`).
```markdown
![Minion](https://octodex.github.com/images/minion.png?width=20pc)
```
![Minion](https://octodex.github.com/images/minion.png?width=20pc)
```markdown
![Minion](https://octodex.github.com/images/minion.png?height=50px)
```
![Minion](https://octodex.github.com/images/minion.png?height=50px)
```markdown
![Minion](https://octodex.github.com/images/minion.png?height=50px&width=300px)
```
![Minion](https://octodex.github.com/images/minion.png?height=50px&width=300px)
### Add CSS classes
Add a HTTP `classes` parameter to the link image to add CSS classes. `shadow`and `border` are available but you could define other ones.
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=shadow)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=shadow)
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=border)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=border)
```markdown
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?classes=border,shadow)
```
![stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg?width=40pc&classes=border,shadow)

View File

@ -0,0 +1,109 @@
---
date: 2016-04-09T16:50:16+02:00
title: Menu extra shortcuts
weight: 25
---
You can define additional menu entries or shortcuts in the navigation menu without any link to content.
## Basic configuration
Edit the website configuration `config.toml` and add a `[[menu.shortcuts]]` entry for each link your want to add.
Example from the current website:
[[menu.shortcuts]]
name = "<i class='fab fa-github'></i> Github repo"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Showcases"
url = "/showcase"
weight = 11
[[menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Hugo Documentation"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Credits"
url = "/credits"
weight = 30
By default, shortcuts are preceded by a title. This title can be disabled by setting `disableShortcutsTitle=true`.
However, if you want to keep the title but change its value, it can be overriden by changing your local i18n translation string configuration.
For example, in your local `i18n/en.toml` file, add the following content
[Shortcuts-Title]
other = "<Your value>"
Read more about [hugo menu](https://gohugo.io/extras/menus/) and [hugo i18n translation strings](https://gohugo.io/content-management/multilingual/#translation-of-strings)
## Configuration for Multilingual mode {#i18n}
When using a multilingual website, you can set different menus for each language. In the `config.toml` file, prefix your menu configuration by `Languages.<language-id>`.
Example from the current website:
[Languages]
[Languages.en]
title = "Documentation for Hugo Learn Theme"
weight = 1
languageName = "English"
[[Languages.en.menu.shortcuts]]
name = "<i class='fab fa-github'></i> Github repo"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Showcases"
url = "/showcase"
weight = 11
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Hugo Documentation"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Credits"
url = "/credits"
weight = 30
[Languages.fr]
title = "Documentation du thème Hugo Learn"
weight = 2
languageName = "Français"
[[Languages.fr.menu.shortcuts]]
name = "<i class='fab fa-github'></i> Repo Github"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Vitrine"
url = "/showcase"
weight = 11
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Documentation Hugo"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Crédits"
url = "/credits"
weight = 30
Read more about [hugo menu](https://gohugo.io/extras/menus/) and [hugo multilingual menus](https://gohugo.io/content-management/multilingual/#menus)

View File

@ -0,0 +1,109 @@
---
date: 2016-04-09T16:50:16+02:00
title: Raccourcis du menu
weight: 25
---
Vous pouvez définir des entrées ou raccourcis supplémentaires dans le menu sans avoir besoin d'être lié à un contenu du site.
## Configuration simple
Editez le fichier de configuration `config.toml` et ajoutez une entrée `[[menu.shortcuts]]` pour chaque lien que vous voulez ajouter.
Exemple pour ce site:
[[menu.shortcuts]]
name = "<i class='fab fa-github'></i> Github repo"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Showcases"
url = "/showcase"
weight = 11
[[menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Hugo Documentation"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Credits"
url = "/credits"
weight = 30
Par défaut, les raccourcis sont précédés par un titre. Ce titre peut être désactivé en ajouter le paramètre `disableShortcutsTitle=true` dans la section `params` de votre `config.toml`.
Cependant, si vous voulez garder le titre mais changer sa valeur, vous pouvez modifier votre configuration multilangue locale en changeant les *translation string*.
Par exemple, dans votre fichier local `i18n/en.toml`, ajouter le contenu
[Shortcuts-Title]
other = "<Votre valeur>"
Plus d'infos sur [les menus Hugo](https://gohugo.io/extras/menus/) et sur [les translations strings](https://gohugo.io/content-management/multilingual/#translation-of-strings)
## Configuration pour le mode multi-langue {#i18n}
Quand vous utilisez un site multi-langue, vous pouvez avoir des menus différents pour chaque langage. Dans le fichier de configuration `config.toml`, préfixez votre configuration par `Languages.<language-id>`.
Par exemple, avec ce site :
[Languages]
[Languages.en]
title = "Documentation for Hugo Learn Theme"
weight = 1
languageName = "English"
[[Languages.en.menu.shortcuts]]
name = "<i class='fab fa-github'></i> Github repo"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Showcases"
url = "/showcase"
weight = 11
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Hugo Documentation"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.en.menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Credits"
url = "/credits"
weight = 30
[Languages.fr]
title = "Documentation du thème Hugo Learn"
weight = 2
languageName = "Français"
[[Languages.fr.menu.shortcuts]]
name = "<i class='fab fa-github'></i> Repo Github"
identifier = "ds"
url = "https://github.com/matcornic/hugo-theme-learn"
weight = 10
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-camera'></i> Vitrine"
url = "/showcase"
weight = 11
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-bookmark'></i> Documentation Hugo"
identifier = "hugodoc"
url = "https://gohugo.io/"
weight = 20
[[Languages.fr.menu.shortcuts]]
name = "<i class='fas fa-bullhorn'></i> Crédits"
url = "/credits"
weight = 30
Plus d'infos sur [les menus Hugo](https://gohugo.io/extras/menus/) et les [menus multi-langue Hugo](https://gohugo.io/content-management/multilingual/#menus)

View File

@ -0,0 +1,166 @@
---
date: 2016-04-09T16:50:16+02:00
title: Pages organization
weight: 5
---
In **Hugo**, pages are the core of your site. Once it is configured, pages are definitely the added value to your documentation site.
## Folders
Organize your site like [any other Hugo project](https://gohugo.io/content/organization/). Typically, you will have a *content* folder with all your pages.
content
├── level-one
│ ├── level-two
│ │ ├── level-three
│ │ │ ├── level-four
│ │ │ │ ├── _index.md <-- /level-one/level-two/level-three/level-four
│ │ │ │ ├── page-4-a.md <-- /level-one/level-two/level-three/level-four/page-4-a
│ │ │ │ ├── page-4-b.md <-- /level-one/level-two/level-three/level-four/page-4-b
│ │ │ │ └── page-4-c.md <-- /level-one/level-two/level-three/level-four/page-4-c
│ │ │ ├── _index.md <-- /level-one/level-two/level-three
│ │ │ ├── page-3-a.md <-- /level-one/level-two/level-three/page-3-a
│ │ │ ├── page-3-b.md <-- /level-one/level-two/level-three/page-3-b
│ │ │ └── page-3-c.md <-- /level-one/level-two/level-three/page-3-c
│ │ ├── _index.md <-- /level-one/level-two
│ │ ├── page-2-a.md <-- /level-one/level-two/page-2-a
│ │ ├── page-2-b.md <-- /level-one/level-two/page-2-b
│ │ └── page-2-c.md <-- /level-one/level-two/page-2-c
│ ├── _index.md <-- /level-one
│ ├── page-1-a.md <-- /level-one/page-1-a
│ ├── page-1-b.md <-- /level-one/page-1-b
│ └── page-1-c.md <-- /level-one/page-1-c
├── _index.md <-- /
└── page-top.md <-- /page-top
{{% notice note %}}
`_index.md` is required in each folder, its your “folder home page”
{{% /notice %}}
## Types
**Hugo-theme-learn** defines two types of pages. *Default* and *Chapter*. Both can be used at any level of the documentation, the only difference being layout display.
A **Chapter** displays a page meant to be used as introduction for a set of child pages. Commonly, it contains a simple title and a catch line to define content that can be found under it.
You can define any HTML as prefix for the menu. In the example below, it's just a number but that could be an [icon](https://fortawesome.github.io/Font-Awesome/).
![Chapter page](/en/cont/pages/images/pages-chapter.png?width=50pc)
```markdown
+++
title = "Basics"
chapter = true
weight = 5
pre = "<b>1. </b>"
+++
### Chapter 1
# Basics
Discover what this Hugo theme is all about and the core-concepts behind it.
```
To tell **Hugo-theme-learn** to consider a page as a chapter, set `chapter=true` in the Front Matter of the page.
A **Default** page is any other content page.
![Default page](/en/cont/pages/images/pages-default.png?width=50pc)
```toml
+++
title = "Installation"
weight = 15
+++
```
The following steps are here to help you initialize your new website. If you don't know Hugo at all, we strongly suggest you to train by following this [great documentation for beginners](https://gohugo.io/overview/quickstart/).
## Create your project
Hugo provides a `new` command to create a new website.
```
hugo new site <new_project>
```
**Hugo-theme-learn** provides [archetypes]({{< relref "cont/archetypes.en.md" >}}) to help you create this kind of pages.
## Front Matter configuration
Each Hugo page has to define a [Front Matter](https://gohugo.io/content/front-matter/) in *yaml*, *toml* or *json*.
**Hugo-theme-learn** uses the following parameters on top of Hugo ones :
```toml
+++
# Table of content (toc) is enabled by default. Set this parameter to true to disable it.
# Note: Toc is always disabled for chapter pages
disableToc = "false"
# If set, this will be used for the page's menu entry (instead of the `title` attribute)
menuTitle = ""
# The title of the page in menu will be prefixed by this HTML content
pre = ""
# The title of the page in menu will be postfixed by this HTML content
post = ""
# Set the page as a chapter, changing the way it's displayed
chapter = false
# Hide a menu entry by setting this to true
hidden = false
# Display name of this page modifier. If set, it will be displayed in the footer.
LastModifierDisplayName = ""
# Email of this page modifier. If set with LastModifierDisplayName, it will be displayed in the footer
LastModifierEmail = ""
+++
```
### Add icon to a menu entry
In the page frontmatter, add a `pre` param to insert any HTML code before the menu label. The example below uses the Github icon.
```toml
+++
title = "Github repo"
pre = "<i class='fab fa-github'></i> "
+++
```
![Title with icon](/en/cont/pages/images/frontmatter-icon.png)
### Ordering sibling menu/page entries
Hugo provides a [flexible way](https://gohugo.io/content/ordering/) to handle order for your pages.
The simplest way is to set `weight` parameter to a number.
```toml
+++
title = "My page"
weight = 5
+++
```
### Using a custom title for menu entries
By default, **Hugo-theme-learn** will use a page's `title` attribute for the menu item (or `linkTitle` if defined).
But a page's title has to be descriptive on its own while the menu is a hierarchy.
We've added the `menuTitle` parameter for that purpose:
For example (for a page named `content/install/linux.md`):
```toml
+++
title = "Install on Linux"
menuTitle = "Linux"
+++
```
## Homepage
To configure your home page, you basically have three choices:
1. Create an `_index.md` document in `content` folder and fill the file with *Markdown content*
2. Create an `index.html` file in the `static` folder and fill the file with *HTML content*
3. Configure your server to automatically redirect home page to one your documentation page

View File

@ -0,0 +1,146 @@
---
date: 2016-04-09T16:50:16+02:00
title: Organisation des pages
weight: 5
---
Dans **Hugo**, les pages sont le cœur de votre site. Une fois configurées, les pages sont la valeur ajoutée de votre site de documentation.
## Dossiers
Organisez votre site comme n'importe quel autre [projet Hugo](https://gohugo.io/content/organization/). Typiquement, vous allez avoir un dossier *content* avec vos pages.
content
├── niveau-un
│ ├── niveau-deux
│ │ ├── niveau-trois
│ │ │ ├── niveau-quatre
│ │ │ │ ├── _index.md <-- /niveau-un/niveau-deux/niveau-trois/niveau-quatre
│ │ │ │ ├── page-4-a.md <-- /niveau-un/niveau-deux/niveau-trois/niveau-quatre/page-4-a
│ │ │ │ ├── page-4-b.md <-- /niveau-un/niveau-deux/niveau-trois/niveau-quatre/page-4-b
│ │ │ │ └── page-4-c.md <-- /niveau-un/niveau-deux/niveau-trois/niveau-quatre/page-4-c
│ │ │ ├── _index.md <-- /niveau-un/niveau-deux/niveau-trois
│ │ │ ├── page-3-a.md <-- /niveau-un/niveau-deux/niveau-trois/page-3-a
│ │ │ ├── page-3-b.md <-- /niveau-un/niveau-deux/niveau-trois/page-3-b
│ │ │ └── page-3-c.md <-- /niveau-un/niveau-deux/niveau-trois/page-3-c
│ │ ├── _index.md <-- /niveau-un/niveau-deux
│ │ ├── page-2-a.md <-- /niveau-un/niveau-deux/page-2-a
│ │ ├── page-2-b.md <-- /niveau-un/niveau-deux/page-2-b
│ │ └── page-2-c.md <-- /niveau-un/niveau-deux/page-2-c
│ ├── _index.md <-- /niveau-un
│ ├── page-1-a.md <-- /niveau-un/page-1-a
│ ├── page-1-b.md <-- /niveau-un/page-1-b
│ └── page-1-c.md <-- /niveau-un/page-1-c
├── _index.md <-- /
└── premiere-page.md <-- /premiere-page
{{% notice note %}}
Le fichier `_index.md` est obligatoire dans chaque dossier, c'est en quelque sorte votre page d'accueil pour le dossier.
{{% /notice %}}
## Types
**Hugo-theme-learn** définit deux types de pages. *Défaut* et *Chapitre*. Les deux sont utilisables à n'importe quel niveau du site, la seule différence est dans l'affichage.
Un **Chapitre** affiche une page vouée à être une introduction pour un ensemble de pages filles. Habituellement, il va seulement contenir un titre et un résumé de la section.
Vous pouvez définir n'importe quel contenu HTML comme préfixe de l'entrée du menu. Dans l'exemple ci-dessous, c'est juste un nombre mais vous pourriez utiliser une [icône](https://fortawesome.github.io/Font-Awesome/).
![Page Chapitre](/en/cont/pages/images/pages-chapter.png?width=50pc)
```markdown
+++
title = "Démarrage"
weight = 5
pre = "<b>1. </b>"
chapter = true
+++
### Chapitre 1
# Démarrage
Découvrez comment utiliser ce thème Hugo et apprenez en les concepts
```
Pour dire à **Hugo-theme-learn** de considérer la page comme un chapitre, configure `chapter=true` dans le Front Matter de la page.
Une page **Défaut** est n'importe quelle autre page.
![Page défaut](/en/cont/pages/images/pages-default.png?width=50pc)
+++
title = "Installation"
weight = 15
+++
The following steps are here to help you initialize your new website. If you don't know Hugo at all, we strongly suggest you to train by following this [great documentation for beginners](https://gohugo.io/overview/quickstart/).
## Create your project
Hugo provides a `new` command to create a new website.
```
hugo new site <new_project>
```
**Hugo-theme-learn** fournit des [archétypes]({{< relref "cont/archetypes.fr.md" >}}) pour vous aider à créer ce type de pages.
## Configuration des Front Matter
Chaque page Hugo doit définir un [Front Matter](https://gohugo.io/content/front-matter/) dans le format *yaml*, *toml* ou *json*.
**Hugo-theme-learn** utilise les paramètres suivant en plus de ceux définis par Hugo:
```toml
+++
# Le Sommaire (table of content = toc) est activé par défaut. Modifier ce paramètre à true pour le désactiver.
# Note: Le sommaire est toujours désactivé pour les chapitres
disableToc = "false"
# Le titre de la page dans le menu sera préfixé par ce contentu HTML
pre = ""
# Le titre de la page dans le menu sera suffixé par ce contentu HTML
post = ""
# Modifier le type de la page pour changer l'affichage
chapter = false
# Cache la page du menu
hidden = false
# Nom de la personne qui a modifié la page. Quand configuré, sera affiché dans le pied de page.
LastModifierDisplayName = ""
# Email de la personne qui a modifié la page. Quand configuré, sera affiché dans le pied de page.
LastModifierEmail = ""
+++
```
### Ajouter une icône à une entrée du menu
Dans le Front Matter, ajouter un paramètre `pre` pour insérer du code HTML qui s'affichera avant le label du menu. L'exemple ci-dessous utilise l'icône de Github.
```toml
+++
title = "Repo Github"
pre = "<i class='fab fa-github'></i> "
+++
```
![Titre avec icône](/en/cont/pages/images/frontmatter-icon.png)
### Ordonner les entrées dans le menu
Hugo permet de modifier facilement [l'ordre des menu](https://gohugo.io/content/ordering/).
La manière la plus simple est de configurer le paramètre `weight` avec un nombre.
```toml
+++
title = "Ma page"
weight = 5
+++
```
## Page d'accueil
Pour configurer votre page d'accueil, vous avez trois choix:
1. Créer une page `_index.md` dans le dossier `content` et remplissez le fichier avec du *contenu Markdown*
2. Créer une page `index.html` dans le dossier `static` et remplissez le fichier avec du *contenu HTML*
3. Configurez votre serveur pour automatiquement rediriger la page d'accueil vers l'une de vos pages.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@ -0,0 +1,89 @@
---
date: 2020-06-01T13:31:12+01:00
title: Code highlighting
weight: 16
---
Learn theme uses [highlight.js](https://highlightjs.org/) to provide code syntax highlighting.
## Markdown syntax
Wrap the code block with three backticks and the name of the language. Highlight will try to auto detect the language if one is not provided.
<!-- markdownlint-disable MD046 -->
```plaintext
```json
[
{
"title": "apples",
"count": [12000, 20000],
"description": {"text": "...", "sensitive": false}
},
{
"title": "oranges",
"count": [17500, null],
"description": {"text": "...", "sensitive": false}
}
]
```
```
<!-- markdownlint-disable MD046 -->
Renders to:
```json
[
{
"title": "apples",
"count": [12000, 20000],
"description": {"text": "...", "sensitive": false}
},
{
"title": "oranges",
"count": [17500, null],
"description": {"text": "...", "sensitive": false}
}
]
```
## Supported languages
Learn theme ships with its own version of highlight.js to support offline browsing. The included package supports 38 common languages, as described on the [highlight.js download page](https://highlightjs.org/download/).
## Identifying failed language detection
Highlight will write a warning to the browser console if a requested language was not found. For example, the following code block references an imaginary language `foo`. An error will be output to the console on this page.
```plaintext
```foo
bar
```
```
```nohighlight
Could not find the language 'foo', did you forget to load/include a language module?(anonymous) @ highlight.pack.js
```
## Supporting additional languages
To support languages other than the 38 common languages included in the default highlight.js you will need to download your own version of highlight.js and add it to your site content.
### Download custom highlight.js
Visit [https://highlightjs.org/download/](https://highlightjs.org/download/) and select your desired language support. Note that more languages means greater package size.
### Add custom highlight.js to static resources
Inside the zip archive downloaded from highlight.js extract the file named `highlight.pack.js`. Move this file to the **new** location
```nohighlight
static/js/highlight.pack.js
```
**Do not** replace the existing file at `themes/hugo-theme-learn/static/js/highlight.pack.js`.
Including the file in the correct path will override the theme default highlight.pack.js and prevent issues caused in the future if the theme default file is updated.
## Further usage information
See [https://highlightjs.org/usage/](https://highlightjs.org/usage/)

View File

@ -0,0 +1,39 @@
---
date: 2018-11-29T08:41:44+01:00
title: Tags
weight: 40
tags: ["documentation", "tutorial"]
---
*Learn theme* support one default taxonomy of gohugo: the *tag* feature.
## Configuration
Just add tags to any page:
```markdown
---
date: 2018-11-29T08:41:44+01:00
title: Theme tutorial
weight: 15
tags: ["tutorial", "theme"]
---
```
## Behavior
The tags are displayed at the top of the page, in their insertion order.
Each tag is a link to a *Taxonomy* page displaying all the articles with the given tag.
## List all the tags
In the `config.toml` file you can add a shortcut to display all the tags
```toml
[[menu.shortcuts]]
name = "<i class='fas fa-tags'></i> Tags"
url = "/tags"
weight = 30
```

View File

@ -0,0 +1,40 @@
---
date: 2018-11-29T08:41:44+01:00
title: Tags
weight: 40
tags: ["documentation", "tutorial"]
---
Le *thème Learn* supporte une des taxonomy par défaut de GoHugo : les tags.
## Configuration
Il suffit d'ajouter un tableau de tags sur la page :
```markdown
---
date: 2018-11-29T08:41:44+01:00
title: Tutoriel pour le thème
weight: 15
tags: ["tutoriel", "theme"]
---
```
## Comportement
Les tags sont affichés en haut de la page, dans l'ordre dans lequel ils ont été saisis.
Chaque tag est un lien vers une page *Taxonomy*, qui affiche tous les article avec ce tag.
## Liste des tags
Il est possible de rajouter un raccourci dans le fichier `config.toml` afin d'afficher une page listant tous les tags
```toml
[[menu.shortcuts]]
name = "<i class='fas fa-tags'></i> Tags"
url = "/tags"
weight = 30
```

View File

@ -0,0 +1,28 @@
---
title: Credits
disableToc: true
---
## Contributors
Thanks to them <i class="fas fa-heart"></i> for making Open Source Software a better place !
{{% ghcontributors "https://api.github.com/repos/matcornic/hugo-theme-learn/contributors?per_page=100" %}}
And a special thanks to [@vjeantet](https://github.com/vjeantet) for his work on [docdock](https://github.com/vjeantet/hugo-theme-docdock), a fork of hugo-theme-learn. v2.0.0 of this theme is inspired by his work.
## Packages and libraries
* [mermaid](https://knsv.github.io/mermaid) - generation of diagram and flowchart from text in a similar manner as markdown
* [font awesome](http://fontawesome.io/) - the iconic font and CSS framework
* [jQuery](https://jquery.com) - The Write Less, Do More, JavaScript Library
* [lunr](https://lunrjs.com) - Lunr enables you to provide a great search experience without the need for external, server-side, search services...
* [horsey](https://bevacqua.github.io/horsey/) - Progressive and customizable autocomplete component
* [clipboard.js](https://zenorocha.github.io/clipboard.js) - copy text to clipboard
* [highlight.js](https://highlightjs.org) - Javascript syntax highlighter
* [modernizr](https://modernizr.com) - A JavaScript toolkit that allows web developers to use new CSS3 and HTML5 features while maintaining a fine level of control over browsers that don't support
## Tooling
* [Netlify](https://www.netlify.com) - Continuous deployement and hosting of this documentation
* [Hugo](https://gohugo.io/)

View File

@ -0,0 +1,28 @@
---
title: Crédits
disableToc: true
---
## Contributeurs
Merci à eux <i class="fas fa-heart"></i> de rendre le monde Open Source meilleur !
{{% ghcontributors "https://api.github.com/repos/matcornic/hugo-theme-learn/contributors?per_page=100" %}}
Et un grand merci à [@vjeantet](https://github.com/vjeantet) pour son travail sur [docdock](https://github.com/vjeantet/hugo-theme-docdock), un fork de _hugo-theme-learn_. La v2.0.0 du thème est en grande partie inspirée de son travail.
## Packages et librairies
* [mermaid](https://knsv.github.io/mermaid) - géneration de diagrames et graphiques à partir de texte similaire à Markdown
* [font awesome](http://fontawesome.io/) - Le framework de polices iconiques
* [jQuery](https://jquery.com) - La plus connue des librairies Javascript
* [lunr](https://lunrjs.com) - Lunr fournit des fonctions de recherche sans service externe
* [horsey](https://bevacqua.github.io/horsey/) - Autocomplétion de composants (utiliser pour les suggestions de recherche)
* [clipboard.js](https://zenorocha.github.io/clipboard.js) - Copier le texte dans le presse-papier
* [highlight.js](https://highlightjs.org) - Mise en valeur de syntaxes
* [modernizr](https://modernizr.com) - Une boite à outil Javascript qui permet aux développeurs d'utiliser les dernières fonctionnalités de CSS et HTML5, même sur de vieux navigateurs.
## Outils
* [Netlify](https://www.netlify.com) - Déploiement continue et hébergement de cette documentation
* [Hugo](https://gohugo.io/)

View File

@ -0,0 +1,16 @@
---
date: 2016-04-09T16:50:16+02:00
title: Shortcodes
pre: "<b>3. </b>"
weight: 15
---
Hugo uses Markdown for its simple content format. However, there are a lot of things that Markdown doesnt support well. You could use pure HTML to expand possibilities.
But this happens to be a bad idea. Everyone uses Markdown because it's pure and simple to read even non-rendered. You should avoid HTML to keep it as simple as possible.
To avoid this limitations, Hugo created [shortcodes](https://gohugo.io/extras/shortcodes/). A shortcode is a simple snippet inside a page.
**Hugo-theme-learn** provides multiple shortcodes on top of existing ones.
{{%children style="h2" description="true" %}}

View File

@ -0,0 +1,16 @@
---
date: 2016-04-09T16:50:16+02:00
title: Shortcodes
pre: "<b>3. </b>"
weight: 15
---
Hugo utilise Markdown pour son format simple. Cependant, il y a beaucoup de chose que Markdown ne supporte pas bien. On pourrait utiliser du HTML pur pour améliorer les capacité du Markdown.
Mais c'est probablement une mauvaise idée. Tout le monde utilise le Markdown parce que c'est pur et simple à lire même lorsqu'il est affiché en texte brut. Vous devez éviter le HTML autant que possible pour garder le contenu simple.
Cependant, pour éviter les limitations, Hugo a créé les [shortcodes](https://gohugo.io/extras/shortcodes/). Un shortcode est un bout de code (*snippet*) dans une page.
**Hugo-theme-learn** fournit de multiple shortcodes en plus de ceux existant.
{{%children style="h2" description="true" %}}

Some files were not shown because too many files have changed in this diff Show More