Progress on item sharing
This commit is contained in:
parent
2c9b2d4eb7
commit
77663735a4
10 changed files with 212 additions and 31 deletions
160
assets/static/css/guest.css
Normal file
160
assets/static/css/guest.css
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/* Global variables. */
|
||||||
|
:root,
|
||||||
|
::backdrop {
|
||||||
|
/* Set sans-serif & mono fonts */
|
||||||
|
--sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
|
||||||
|
"Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica,
|
||||||
|
"Helvetica Neue", sans-serif;
|
||||||
|
--mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||||
|
--standard-border-radius: 5px;
|
||||||
|
|
||||||
|
/* Default (light) theme */
|
||||||
|
--bg: #fff;
|
||||||
|
--primary-color: #2b5797;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
|
||||||
|
/* Box sizing rules */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent font size inflation */
|
||||||
|
html {
|
||||||
|
-moz-text-size-adjust: none;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default margin in favour of better control in authored CSS */
|
||||||
|
body, h1, h2, h3, h4, p,
|
||||||
|
figure, blockquote, dl, dd {
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
|
||||||
|
ul[role='list'],
|
||||||
|
ol[role='list'] {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set core body defaults */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set shorter line heights on headings and interactive elements */
|
||||||
|
h1, h2, h3, h4,
|
||||||
|
input, label {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Balance text wrapping on headings */
|
||||||
|
h1, h2,
|
||||||
|
h3, h4 {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A elements that don't have a class get default styles */
|
||||||
|
a:not([class]) {
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inherit fonts for inputs and buttons */
|
||||||
|
input, button,
|
||||||
|
textarea, select {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anything that has been anchored to should have extra scroll margin */
|
||||||
|
:target {
|
||||||
|
scroll-margin-block: 5ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,h2,h3,h4,h5,h6, form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
/* End reset */
|
||||||
|
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: var(--sans-font), sans-serif;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
overflow-x: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 32px;
|
||||||
|
float: left;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-top-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
height: 50px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-title {
|
||||||
|
height: 50px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-title h5 {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-content {
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-detail dt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-large {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large */
|
||||||
|
@media (min-width:992px) {
|
||||||
|
.show-large {
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
#app-title {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -104,11 +104,6 @@ textarea, select {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure textareas without a rows attribute are not tiny */
|
|
||||||
/*textarea:not([rows]) {
|
|
||||||
min-height: 10em;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/* Anything that has been anchored to should have extra scroll margin */
|
/* Anything that has been anchored to should have extra scroll margin */
|
||||||
:target {
|
:target {
|
||||||
scroll-margin-block: 5ex;
|
scroll-margin-block: 5ex;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="manifest" href="/static/manifest.json">
|
<link rel="manifest" href="/static/manifest.json">
|
||||||
<link rel="stylesheet" href="/static/bootstrap-icons/font/bootstrap-icons.min.css" />
|
<link rel="stylesheet" href="/static/bootstrap-icons/font/bootstrap-icons.min.css" />
|
||||||
<link rel="stylesheet" href="/static/css/main.css" />
|
<link rel="stylesheet" href="/static/css/guest.css" />
|
||||||
<link rel="icon" type="image/x-icon" href="/static/img/brainminder-icon.svg">
|
<link rel="icon" type="image/x-icon" href="/static/img/brainminder-icon.svg">
|
||||||
|
|
||||||
{{block "page:meta" . }}
|
{{block "page:meta" . }}
|
||||||
|
@ -15,8 +15,15 @@
|
||||||
}}
|
}}
|
||||||
</head>
|
</head>
|
||||||
<body class="guest">
|
<body class="guest">
|
||||||
Title
|
<div id="page-top-bar">
|
||||||
<div id="full-main-container">
|
<div id="app-title">
|
||||||
|
<img src="/static/img/brainminder.svg" alt="BrainMinder" class="logo" /><h5 class="show-large">BrainMinder</h5>
|
||||||
|
</div>
|
||||||
|
<div id="page-title-container">
|
||||||
|
<div id="page-title">Shared item : {{ .itemShare.ItemTitle}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="guest-main-container">
|
||||||
<div id="page-content">{{template "page:content" .}}</div>
|
<div id="page-content">{{template "page:content" .}}</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<p style="margin-right: 6px">
|
<p style="margin-right: 6px">
|
||||||
<label for="share-token">Share Url</label>
|
<label for="share-token">Share Url</label>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<span class="prefix">{{ .baseUrl }}/item/share/</span>
|
<span class="prefix">{{ .publicBaseUrl }}/item/share/</span>
|
||||||
<input name="Token" id="share-token" type="url" readonly="readonly" value="{{ .shareToken }}" />
|
<input name="Token" id="share-token" type="url" readonly="readonly" value="{{ .shareToken }}" />
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<p style="margin-right: 6px">
|
<p style="margin-right: 6px">
|
||||||
<label for="share-token">Share Url</label>
|
<label for="share-token">Share Url</label>
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<span class="prefix">{{ .baseUrl }}/item/share/</span>
|
<span class="prefix">{{ .publicBaseUrl }}/item/share/</span>
|
||||||
<input name="Token" id="share-token" type="url" readonly="readonly" value="{{ .itemShare.Token }}" />
|
<input name="Token" id="share-token" type="url" readonly="readonly" value="{{ .itemShare.Token }}" />
|
||||||
</div>
|
</div>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{{define "page:content"}}
|
{{define "page:content"}}
|
||||||
Shared item FOUND
|
<dl class="item-detail">
|
||||||
{{ .itemShare.ItemTitle}}
|
<dt>Summary</dt>
|
||||||
|
<dd>{{ .itemShare.ItemSummaryRendered | safeHTML}}</dd>
|
||||||
|
<dt>Description</dt>
|
||||||
|
<dd>{{ .itemShare.ItemDescriptionRendered | safeHTML}}</dd>
|
||||||
|
</dl>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
publicBaseURL string
|
||||||
baseURL string
|
baseURL string
|
||||||
httpPort int
|
httpPort int
|
||||||
cookie struct {
|
cookie struct {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"brainminder.speedtech.it/internal/password"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/justinas/nosurf"
|
"github.com/justinas/nosurf"
|
||||||
|
@ -993,7 +994,7 @@ func (app *application) itemShare(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data := app.newTemplateData(r)
|
data := app.newTemplateData(r)
|
||||||
data["item"] = item
|
data["item"] = item
|
||||||
data["baseUrl"] = app.config.baseURL
|
data["publicBaseUrl"] = app.config.publicBaseURL
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
|
@ -1023,10 +1024,14 @@ func (app *application) itemShare(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashedPassword := ""
|
||||||
|
if itemShareFromForm.Password != "" {
|
||||||
|
hashedPassword, _ = password.Hash(itemShareFromForm.Password)
|
||||||
|
}
|
||||||
itemShare := &models.ItemShare{
|
itemShare := &models.ItemShare{
|
||||||
ItemId: itemId,
|
ItemId: itemId,
|
||||||
Token: itemShareFromForm.Token,
|
Token: itemShareFromForm.Token,
|
||||||
Password: itemShareFromForm.Password,
|
Password: hashedPassword,
|
||||||
PermissionEdit: itemShareFromForm.PermissionEdit,
|
PermissionEdit: itemShareFromForm.PermissionEdit,
|
||||||
StartDatetime: itemShareFromForm.StartDatetime,
|
StartDatetime: itemShareFromForm.StartDatetime,
|
||||||
EndDatetime: itemShareFromForm.EndDatetime,
|
EndDatetime: itemShareFromForm.EndDatetime,
|
||||||
|
@ -1065,7 +1070,7 @@ func (app *application) itemShareEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data := app.newTemplateData(r)
|
data := app.newTemplateData(r)
|
||||||
data["itemShare"] = itemShare
|
data["itemShare"] = itemShare
|
||||||
data["baseUrl"] = app.config.baseURL
|
data["publicBaseUrl"] = app.config.publicBaseURL
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
|
@ -1093,9 +1098,13 @@ func (app *application) itemShareEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashedPassword := ""
|
||||||
|
if itemShareFromForm.Password != "" {
|
||||||
|
hashedPassword, _ = password.Hash(itemShareFromForm.Password)
|
||||||
|
}
|
||||||
itemShare := &models.ItemShare{
|
itemShare := &models.ItemShare{
|
||||||
Token: itemShareFromForm.Token,
|
Token: itemShareFromForm.Token,
|
||||||
Password: itemShareFromForm.Password,
|
Password: hashedPassword,
|
||||||
PermissionEdit: itemShareFromForm.PermissionEdit,
|
PermissionEdit: itemShareFromForm.PermissionEdit,
|
||||||
StartDatetime: itemShareFromForm.StartDatetime,
|
StartDatetime: itemShareFromForm.StartDatetime,
|
||||||
EndDatetime: itemShareFromForm.EndDatetime,
|
EndDatetime: itemShareFromForm.EndDatetime,
|
||||||
|
|
|
@ -33,7 +33,8 @@ func main() {
|
||||||
func run(logger *slog.Logger) error {
|
func run(logger *slog.Logger) error {
|
||||||
var cfg config
|
var cfg config
|
||||||
|
|
||||||
cfg.baseURL = env.GetString("BASE_URL", "http://localhost:4445")
|
cfg.publicBaseURL = env.GetString("PUBLIC_BASE_URL", "http://localhost:4445")
|
||||||
|
cfg.baseURL = env.GetString("BASE_URL", "http://localhost")
|
||||||
cfg.httpPort = env.GetInt("HTTP_PORT", 4445)
|
cfg.httpPort = env.GetInt("HTTP_PORT", 4445)
|
||||||
cfg.cookie.secretKey = env.GetString("COOKIE_SECRET_KEY", `CHANGE_THIS_COOKIE_KEY`)
|
cfg.cookie.secretKey = env.GetString("COOKIE_SECRET_KEY", `CHANGE_THIS_COOKIE_KEY`)
|
||||||
cfg.db.dsn = env.GetString("DB_DSN", "db.sqlite")
|
cfg.db.dsn = env.GetString("DB_DSN", "db.sqlite")
|
||||||
|
|
|
@ -19,6 +19,8 @@ type ItemShare struct {
|
||||||
EndDatetime string `db:"end_datetime"`
|
EndDatetime string `db:"end_datetime"`
|
||||||
Password string `db:"password"`
|
Password string `db:"password"`
|
||||||
ItemTitle string `db:"item_title"`
|
ItemTitle string `db:"item_title"`
|
||||||
|
ItemSummaryRendered string `db:"item_summary_rendered"`
|
||||||
|
ItemDescriptionRendered string `db:"item_description_rendered"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (model *ItemShareModel) One(id int64) (*ItemShare, bool, error) {
|
func (model *ItemShareModel) One(id int64) (*ItemShare, bool, error) {
|
||||||
|
@ -27,7 +29,8 @@ func (model *ItemShareModel) One(id int64) (*ItemShare, bool, error) {
|
||||||
|
|
||||||
var row ItemShare
|
var row ItemShare
|
||||||
|
|
||||||
query := `SELECT bmi.title AS item_title, bmis.* FROM bm_item_shares bmis
|
query := `SELECT bmi.title AS item_title, bmi.summary_rendered as item_summary_rendered, bmi.description_rendered as item_description_rendered,
|
||||||
|
bmis.* FROM bm_item_shares bmis
|
||||||
INNER JOIN bm_item bmi ON bmis.item_id=bmi.id WHERE bmis.id = $1`
|
INNER JOIN bm_item bmi ON bmis.item_id=bmi.id WHERE bmis.id = $1`
|
||||||
|
|
||||||
err := model.DB.GetContext(ctx, &row, query, id)
|
err := model.DB.GetContext(ctx, &row, query, id)
|
||||||
|
@ -44,7 +47,8 @@ func (model *ItemShareModel) OneByToken(token string) (*ItemShare, bool, error)
|
||||||
|
|
||||||
var row ItemShare
|
var row ItemShare
|
||||||
|
|
||||||
query := `SELECT bmi.title AS item_title, bmis.* FROM bm_item_shares bmis
|
query := `SELECT bmi.title AS item_title, bmi.summary_rendered as item_summary_rendered, bmi.description_rendered as item_description_rendered,
|
||||||
|
bmis.* FROM bm_item_shares bmis
|
||||||
INNER JOIN bm_item bmi ON bmis.item_id=bmi.id WHERE bmis.token = $1`
|
INNER JOIN bm_item bmi ON bmis.item_id=bmi.id WHERE bmis.token = $1`
|
||||||
|
|
||||||
err := model.DB.GetContext(ctx, &row, query, token)
|
err := model.DB.GetContext(ctx, &row, query, token)
|
||||||
|
@ -78,7 +82,7 @@ func (model *ItemShareModel) Update(ItemShare *ItemShare) error {
|
||||||
ctx, cancel := database.GetContext()
|
ctx, cancel := database.GetContext()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
query := `UPDATE bm_item_shares SET token=:token, permission_edit=:permission_edit, start_datetime=:start_datetime, end_datetime=:end_datetime WHERE id = :id`
|
query := `UPDATE bm_item_shares SET token=:token, permission_edit=:permission_edit, start_datetime=:start_datetime, end_datetime=:end_datetime, password=:password WHERE id = :id`
|
||||||
_, err := model.DB.NamedExecContext(ctx, query, ItemShare)
|
_, err := model.DB.NamedExecContext(ctx, query, ItemShare)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue