V2 #1
|
@ -866,13 +866,13 @@ type RethinkDbData (conn : Net.IConnection, config : DataConfig, log : ILogger<R
|
||||||
member _.startUp () = backgroundTask {
|
member _.startUp () = backgroundTask {
|
||||||
let! dbs = rethink<string list> { dbList; result; withRetryOnce conn }
|
let! dbs = rethink<string list> { dbList; result; withRetryOnce conn }
|
||||||
if not (dbs |> List.contains config.Database) then
|
if not (dbs |> List.contains config.Database) then
|
||||||
log.LogInformation($"Creating database {config.Database}...")
|
log.LogInformation $"Creating database {config.Database}..."
|
||||||
do! rethink { dbCreate config.Database; write; withRetryOnce; ignoreResult conn }
|
do! rethink { dbCreate config.Database; write; withRetryOnce; ignoreResult conn }
|
||||||
|
|
||||||
let! tables = rethink<string list> { tableList; result; withRetryOnce conn }
|
let! tables = rethink<string list> { tableList; result; withRetryOnce conn }
|
||||||
for tbl in Table.all do
|
for tbl in Table.all do
|
||||||
if not (tables |> List.contains tbl) then
|
if not (tables |> List.contains tbl) then
|
||||||
log.LogInformation($"Creating table {tbl}...")
|
log.LogInformation $"Creating table {tbl}..."
|
||||||
do! rethink { tableCreate tbl; write; withRetryOnce; ignoreResult conn }
|
do! rethink { tableCreate tbl; write; withRetryOnce; ignoreResult conn }
|
||||||
|
|
||||||
do! ensureIndexes Table.Category [ "webLogId" ]
|
do! ensureIndexes Table.Category [ "webLogId" ]
|
||||||
|
|
|
@ -4,6 +4,7 @@ open System
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
|
open Microsoft.Extensions.Logging
|
||||||
open MyWebLog
|
open MyWebLog
|
||||||
open MyWebLog.ViewModels
|
open MyWebLog.ViewModels
|
||||||
|
|
||||||
|
@ -235,7 +236,7 @@ module private SqliteHelpers =
|
||||||
|
|
||||||
|
|
||||||
/// SQLite myWebLog data implementation
|
/// SQLite myWebLog data implementation
|
||||||
type SQLiteData (conn : SqliteConnection) =
|
type SQLiteData (conn : SqliteConnection, log : ILogger<SQLiteData>) =
|
||||||
|
|
||||||
/// Add parameters for category INSERT or UPDATE statements
|
/// Add parameters for category INSERT or UPDATE statements
|
||||||
let addCategoryParameters (cmd : SqliteCommand) (cat : Category) =
|
let addCategoryParameters (cmd : SqliteCommand) (cat : Category) =
|
||||||
|
@ -1800,6 +1801,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "theme"
|
let! exists = tableExists "theme"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating theme tables..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE theme (
|
"""CREATE TABLE theme (
|
||||||
|
@ -1825,6 +1827,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "web_log"
|
let! exists = tableExists "web_log"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating web log tables..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE web_log (
|
"""CREATE TABLE web_log (
|
||||||
|
@ -1870,6 +1873,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "category"
|
let! exists = tableExists "category"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating category table..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE category (
|
"""CREATE TABLE category (
|
||||||
|
@ -1883,6 +1887,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "web_log_user"
|
let! exists = tableExists "web_log_user"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating user table..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE web_log_user (
|
"""CREATE TABLE web_log_user (
|
||||||
|
@ -1900,6 +1905,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "page"
|
let! exists = tableExists "page"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating page tables..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE page (
|
"""CREATE TABLE page (
|
||||||
|
@ -1937,6 +1943,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "post"
|
let! exists = tableExists "post"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating post tables..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE post (
|
"""CREATE TABLE post (
|
||||||
|
@ -1998,6 +2005,7 @@ type SQLiteData (conn : SqliteConnection) =
|
||||||
|
|
||||||
let! exists = tableExists "tag_map"
|
let! exists = tableExists "tag_map"
|
||||||
if not exists then
|
if not exists then
|
||||||
|
log.LogInformation "Creating tag map tables..."
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
"""CREATE TABLE tag_map (
|
"""CREATE TABLE tag_map (
|
||||||
|
|
|
@ -38,19 +38,23 @@ module DataImplementation =
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
|
|
||||||
/// Get the configured data implementation
|
/// Get the configured data implementation
|
||||||
let get (sp : IServiceProvider) : IData option =
|
let get (sp : IServiceProvider) : IData =
|
||||||
let config = sp.GetRequiredService<IConfiguration> ()
|
let config = sp.GetRequiredService<IConfiguration> ()
|
||||||
if (config.GetSection "RethinkDB").Exists () then
|
if (config.GetConnectionString >> isNull >> not) "SQLite" then
|
||||||
|
let conn = new SqliteConnection (config.GetConnectionString "SQLite")
|
||||||
|
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
upcast SQLiteData (conn, sp.GetRequiredService<ILogger<SQLiteData>> ())
|
||||||
|
elif (config.GetSection "RethinkDB").Exists () then
|
||||||
Json.all () |> Seq.iter Converter.Serializer.Converters.Add
|
Json.all () |> Seq.iter Converter.Serializer.Converters.Add
|
||||||
let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB")
|
let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB")
|
||||||
let conn = rethinkCfg.CreateConnectionAsync () |> Async.AwaitTask |> Async.RunSynchronously
|
let conn = rethinkCfg.CreateConnectionAsync () |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
Some (upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ()))
|
upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ())
|
||||||
elif (config.GetConnectionString >> isNull >> not) "SQLite" then
|
|
||||||
let conn = new SqliteConnection (config.GetConnectionString "SQLite")
|
|
||||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
|
||||||
Some (upcast SQLiteData conn)
|
|
||||||
else
|
else
|
||||||
None
|
let log = sp.GetRequiredService<ILogger<SQLiteData>> ()
|
||||||
|
log.LogInformation "Using default SQLite database myweblog.db"
|
||||||
|
let conn = new SqliteConnection ("Data Source=./myweblog.db;Cache=Shared")
|
||||||
|
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
|
upcast SQLiteData (conn, log)
|
||||||
|
|
||||||
|
|
||||||
open Giraffe
|
open Giraffe
|
||||||
|
@ -78,39 +82,37 @@ let rec main args =
|
||||||
let _ = builder.Services.AddAuthorization ()
|
let _ = builder.Services.AddAuthorization ()
|
||||||
let _ = builder.Services.AddAntiforgery ()
|
let _ = builder.Services.AddAntiforgery ()
|
||||||
|
|
||||||
let sp = builder.Services.BuildServiceProvider ()
|
let sp = builder.Services.BuildServiceProvider ()
|
||||||
match DataImplementation.get sp with
|
let data = DataImplementation.get sp
|
||||||
| Some data ->
|
|
||||||
task {
|
|
||||||
do! data.startUp ()
|
|
||||||
do! WebLogCache.fill data
|
|
||||||
do! ThemeAssetCache.fill data
|
|
||||||
} |> Async.AwaitTask |> Async.RunSynchronously
|
|
||||||
|
|
||||||
// Define distributed cache implementation based on data implementation
|
task {
|
||||||
match data with
|
do! data.startUp ()
|
||||||
| :? RethinkDbData as rethink ->
|
do! WebLogCache.fill data
|
||||||
// A RethinkDB connection is designed to work as a singleton
|
do! ThemeAssetCache.fill data
|
||||||
builder.Services.AddSingleton<IData> data |> ignore
|
} |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
builder.Services.AddDistributedRethinkDBCache (fun opts ->
|
|
||||||
opts.TableName <- "Session"
|
// Define distributed cache implementation based on data implementation
|
||||||
opts.Connection <- rethink.Conn)
|
match data with
|
||||||
|> ignore
|
| :? RethinkDbData as rethink ->
|
||||||
| :? SQLiteData ->
|
// A RethinkDB connection is designed to work as a singleton
|
||||||
// ADO.NET connections are designed to work as per-request instantiation
|
builder.Services.AddSingleton<IData> data |> ignore
|
||||||
let cfg = sp.GetRequiredService<IConfiguration> ()
|
builder.Services.AddDistributedRethinkDBCache (fun opts ->
|
||||||
builder.Services.AddScoped<SqliteConnection> (fun sp ->
|
opts.TableName <- "Session"
|
||||||
let conn = new SqliteConnection (cfg.GetConnectionString "SQLite")
|
opts.Connection <- rethink.Conn)
|
||||||
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
|> ignore
|
||||||
conn)
|
| :? SQLiteData as sql ->
|
||||||
|> ignore
|
// ADO.NET connections are designed to work as per-request instantiation
|
||||||
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
let cfg = sp.GetRequiredService<IConfiguration> ()
|
||||||
// Use SQLite for caching as well
|
builder.Services.AddScoped<SqliteConnection> (fun sp ->
|
||||||
let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"
|
let conn = new SqliteConnection (sql.Conn.ConnectionString)
|
||||||
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
|
||||||
| _ -> ()
|
conn)
|
||||||
| None ->
|
|> ignore
|
||||||
invalidOp "There is no data configuration present; please add a RethinkDB section or LiteDB connection string"
|
builder.Services.AddScoped<IData, SQLiteData> () |> ignore
|
||||||
|
// Use SQLite for caching as well
|
||||||
|
let cachePath = Option.ofObj (cfg.GetConnectionString "SQLiteCachePath") |> Option.defaultValue "./session.db"
|
||||||
|
builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) |> ignore
|
||||||
|
| _ -> ()
|
||||||
|
|
||||||
let _ = builder.Services.AddSession(fun opts ->
|
let _ = builder.Services.AddSession(fun opts ->
|
||||||
opts.IdleTimeout <- TimeSpan.FromMinutes 60
|
opts.IdleTimeout <- TimeSpan.FromMinutes 60
|
||||||
|
|
54
src/admin-theme/_layout.liquid
Normal file
54
src/admin-theme/_layout.liquid
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
||||||
|
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarText">
|
||||||
|
{% if logged_on -%}
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
||||||
|
{{ "admin/pages" | nav_link: "Pages" }}
|
||||||
|
{{ "admin/posts" | nav_link: "Posts" }}
|
||||||
|
{{ "admin/categories" | nav_link: "Categories" }}
|
||||||
|
{{ "admin/settings" | nav_link: "Settings" }}
|
||||||
|
</ul>
|
||||||
|
{%- endif %}
|
||||||
|
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
||||||
|
{% if logged_on -%}
|
||||||
|
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
||||||
|
{{ "user/log-off" | nav_link: "Log Off" }}
|
||||||
|
{%- else -%}
|
||||||
|
{{ "user/log-on" | nav_link: "Log On" }}
|
||||||
|
{%- endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class="mx-3 mt-3">
|
||||||
|
<div class="messages mt-2" id="msgContainer">
|
||||||
|
{% for msg in messages %}
|
||||||
|
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
||||||
|
{{ msg.message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
{% if msg.detail %}
|
||||||
|
<hr>
|
||||||
|
{{ msg.detail.value }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{{ content }}
|
||||||
|
</main>
|
||||||
|
<footer class="position-fixed bottom-0 w-100">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 text-end">
|
||||||
|
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
|
@ -1,63 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{{ page_title | escape }} « Admin « {{ web_log.name | escape }}</title>
|
<title>{{ page_title | strip_html }} « Admin « {{ web_log.name | strip_html }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
{% include_template "_layout" %}
|
||||||
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
|
||||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarText">
|
|
||||||
{% if logged_on -%}
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
|
||||||
{{ "admin/pages" | nav_link: "Pages" }}
|
|
||||||
{{ "admin/posts" | nav_link: "Posts" }}
|
|
||||||
{{ "admin/categories" | nav_link: "Categories" }}
|
|
||||||
{{ "admin/settings" | nav_link: "Settings" }}
|
|
||||||
</ul>
|
|
||||||
{%- endif %}
|
|
||||||
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
|
||||||
{% if logged_on -%}
|
|
||||||
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
|
||||||
{{ "user/log-off" | nav_link: "Log Off" }}
|
|
||||||
{%- else -%}
|
|
||||||
{{ "user/log-on" | nav_link: "Log On" }}
|
|
||||||
{%- endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main class="mx-3 mt-3">
|
|
||||||
<div class="messages mt-2" id="msgContainer">
|
|
||||||
{% for msg in messages %}
|
|
||||||
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
|
||||||
{{ msg.message }}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
{% if msg.detail %}
|
|
||||||
<hr>
|
|
||||||
{{ msg.detail.value }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{{ content }}
|
|
||||||
</main>
|
|
||||||
<footer class="position-fixed bottom-0 w-100">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 text-end">
|
|
||||||
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<script>Admin.dismissSuccesses()</script>
|
<script>Admin.dismissSuccesses()</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -3,66 +3,13 @@
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="generator" content="{{ generator }}">
|
<meta name="generator" content="{{ generator }}">
|
||||||
<title>{{ page_title | escape }} « Admin « {{ web_log.name | escape }}</title>
|
<title>{{ page_title | strip_html }} « Admin « {{ web_log.name | strip_html }}</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="{{ "themes/admin/admin.css" | relative_link }}">
|
<link rel="stylesheet" href="{{ "themes/admin/admin.css" | relative_link }}">
|
||||||
</head>
|
</head>
|
||||||
<body hx-boost="true">
|
<body hx-boost="true">
|
||||||
<header>
|
{% include_template "_layout" %}
|
||||||
<nav class="navbar navbar-dark bg-dark navbar-expand-md justify-content-start px-2 position-fixed top-0 w-100">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="{{ "" | relative_link }}" hx-boost="false">{{ web_log.name }}</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
|
|
||||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarText">
|
|
||||||
{% if logged_on -%}
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
{{ "admin/dashboard" | nav_link: "Dashboard" }}
|
|
||||||
{{ "admin/pages" | nav_link: "Pages" }}
|
|
||||||
{{ "admin/posts" | nav_link: "Posts" }}
|
|
||||||
{{ "admin/categories" | nav_link: "Categories" }}
|
|
||||||
{{ "admin/settings" | nav_link: "Settings" }}
|
|
||||||
</ul>
|
|
||||||
{%- endif %}
|
|
||||||
<ul class="navbar-nav flex-grow-1 justify-content-end">
|
|
||||||
{% if logged_on -%}
|
|
||||||
{{ "admin/user/edit" | nav_link: "Edit User" }}
|
|
||||||
{{ "user/log-off" | nav_link: "Log Off" }}
|
|
||||||
{%- else -%}
|
|
||||||
{{ "user/log-on" | nav_link: "Log On" }}
|
|
||||||
{%- endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main class="mx-3 mt-3">
|
|
||||||
<div class="messages mt-2" id="msgContainer">
|
|
||||||
{% for msg in messages %}
|
|
||||||
<div role="alert" class="alert alert-{{ msg.level }} alert-dismissible fade show">
|
|
||||||
{{ msg.message }}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
{% if msg.detail %}
|
|
||||||
<hr>
|
|
||||||
{{ msg.detail.value }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{{ content }}
|
|
||||||
</main>
|
|
||||||
<footer class="position-fixed bottom-0 w-100">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 text-end">
|
|
||||||
<img src="{{ "themes/admin/logo-light.png" | relative_link }}" alt="myWebLog" width="120" height="34">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||||
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
|
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{% if model.subtitle %}
|
{%- if is_category or is_tag %}
|
||||||
<h2>{{ model.subtitle.value }}</h2>
|
<h1 class="index-title">{{ page_title }}</h1>
|
||||||
{% endif %}
|
{%- if is_category %}
|
||||||
<section class="container" aria-label="The posts for the page">
|
{%- assign cat = categories | where: "slug", slug | first -%}
|
||||||
|
{%- if cat.description %}<h4 class="text-muted">{{ cat.description.value }}</h4>{% endif -%}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
<section class="container mt-3" aria-label="The posts for the page">
|
||||||
{% for post in model.posts %}
|
{% for post in model.posts %}
|
||||||
<article>
|
<article>
|
||||||
<h1>
|
<h1>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
<title>{{ page_title | escape }}{% if page_title %} « {% endif %}{{ web_log.name | escape }}</title>
|
<title>{{ page_title | strip_html }}{% if page_title %} « {% endif %}{{ web_log.name | strip_html }}</title>
|
||||||
{% page_head -%}
|
{% page_head -%}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarText">
|
<div class="collapse navbar-collapse" id="navbarText">
|
||||||
{% if web_log.subtitle -%}
|
{% if web_log.subtitle -%}
|
||||||
<span class="navbar-text">{{ web_log.subtitle.value | escape }}</span>
|
<span class="navbar-text">{{ web_log.subtitle.value }}</span>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% unless web_log.default_page == "posts" %}{{ "page/1" | nav_link: "Posts" }}{% endunless %}
|
{% unless web_log.default_page == "posts" %}{{ "page/1" | nav_link: "Posts" }}{% endunless %}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user