Display news, improve news form

This commit is contained in:
Adrian Woźniak 2022-07-13 17:25:51 +02:00
parent e434b89e9e
commit 9564c37899
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
13 changed files with 153 additions and 68 deletions

View File

@ -4,12 +4,13 @@
<ow-articles> <ow-articles>
{% for article in news %} {% for article in news %}
<news-article <news-article
article-id="{{article.id}}"
article-title="{{article.title}}" article-title="{{article.title}}"
status="{{article.status.as_str()}}" status="{{article.status.as_str()}}"
published-at="{{article.published_at}}" published-at="{{article.published_at}}"
created-at="{{article.created_at}}" created-at="{{article.created_at}}"
> >
{{article.body}} {{article.body|safe}}
</news-article> </news-article>
{% endfor %} {% endfor %}
</ow-articles> </ow-articles>

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<ow-articles>
{% for article in news %}
<news-article
article-id="{{article.id}}"
article-title="{{article.title}}"
status="{{article.status.as_str()}}"
published-at="{{article.published_at}}"
created-at="{{article.created_at}}"
>
{{article.body|safe}}
</news-article>
{% endfor %}
</ow-articles>
{% endblock %}

View File

@ -1,3 +1,3 @@
import "./admin/ow-admin"; import "./admin/ow-admin";
import "./admin/ow-articles";
import "./admin/news-article"; import "./admin/article-form";

View File

@ -1,5 +1,4 @@
import { Component, FORM_STYLE } from "../shared"; import { Component, FORM_STYLE } from "../shared";
import "../shared/rich-text-editor";
customElements.define('article-form', class extends Component { customElements.define('article-form', class extends Component {
constructor() { constructor() {

View File

@ -1,5 +1,4 @@
import { Component } from "../shared"; import { Component } from "../shared";
import "./ow-articles";
customElements.define('ow-admin', class extends Component { customElements.define('ow-admin', class extends Component {
constructor() { constructor() {

View File

@ -8,6 +8,9 @@ import "./price/price-view";
import "./price/price-input"; import "./price/price-input";
import "./register-form.js"; import "./register-form.js";
import "./business-items"; import "./business-items";
import "./news/ow-articles";
import "./news/news-article";
import "./shared/rich-text-editor";
import { fireFbReady } from "./shared.js"; import { fireFbReady } from "./shared.js";

View File

@ -1,8 +1,9 @@
import { Component } from "../shared"; import { Component } from "../shared";
import "../shared/date-time";
customElements.define('news-article', class extends Component { customElements.define('news-article', class extends Component {
static get observedAttributes() { static get observedAttributes() {
return ["article-title", "status", "body", "created-at", "published-at"] return ["article-id", "article-title", "status", "body", "created-at", "published-at"]
} }
constructor() { constructor() {
@ -42,11 +43,13 @@ customElements.define('news-article', class extends Component {
<section id="time"> <section id="time">
<div class="time"> <div class="time">
<span>Created at:</span> <span>Created at:</span>
<span id="created_at"></span> <date-time id="created_at" hide-date="false" hide-time="false">
</date-time>
</div> </div>
<div class="time"> <div class="time">
<span>Published at:</span> <span>Published at:</span>
<span id="published_at"></span> <date-time id="published_at" hide-date="false" hide-time="false">
</date-time>
</div> </div>
</section> </section>
<section id="body"> <section id="body">
@ -56,6 +59,15 @@ customElements.define('news-article', class extends Component {
`); `);
} }
get article_id() {
const id = parseInt(this.getAttribute('article-id'));
return isNaN(id) ? null : id;
}
set article_id(v) {
this.setAttribute('article-id', v);
}
get article_title() { get article_title() {
return this.getAttribute('article-title'); return this.getAttribute('article-title');
} }
@ -79,8 +91,7 @@ customElements.define('news-article', class extends Component {
} }
set created_at(v) { set created_at(v) {
this.setAttribute('created-at', v); this.shadowRoot.querySelector('#created_at').datetime = v;
this.shadowRoot.querySelector('#created_at').textContent = v;
} }
get published_at() { get published_at() {
@ -88,7 +99,6 @@ customElements.define('news-article', class extends Component {
} }
set published_at(v) { set published_at(v) {
this.setAttribute('published-at', v); this.shadowRoot.querySelector('#published_at').datetime = v;
this.shadowRoot.querySelector('#published_at').textContent = v;
} }
}); });

View File

@ -1,5 +1,4 @@
import { Component } from "../shared" import { Component } from "../shared"
import "./article-form";
customElements.define('ow-articles', class extends Component { customElements.define('ow-articles', class extends Component {
constructor() { constructor() {

View File

@ -1,16 +0,0 @@
import { Component } from "./shared";
import "./ow-news/news-article";
customElements.define("ow-news", class extends Component {
constructor() {
super(`
<style>
:host { display: block; }
</style>
<article>
<slot></slot>
</article>
`);
}
});

View File

@ -1,37 +0,0 @@
import { Component } from "../shared";
customElements.define('news-article', class extends Component {
static get observedAttributes() {
return ["title", "body", "published-at"]
}
constructor() {
super(`
<style>
:host { display: block; }
</style>
<article>
<h1 id="title"></h1>
<section id="body"></section>
</article>
`);
}
get title() {
return this.getAttribute('title');
}
set title(v) {
this.setAttribute('title', v);
this.shadowRoot.querySelector('#title').textContent = v;
}
get body() {
return this.getAttribute('body');
}
set body(v) {
this.setAttribute('body', v);
this.shadowRoot.querySelector('#body').textContent = v;
}
});

View File

@ -0,0 +1,76 @@
import { Component } from "../shared";
customElements.define('date-time', class extends Component {
static get observedAttributes() {
return ['datetime', "hide-date", "hide-time"]
}
constructor() {
super(`
<style>
:host { display: block; }
section { display: flex; justify-content: flex-start; }
:host([hide-time = "true"]) #time { display: none; }
:host([hide-time = "false"]) #time { margin-left: 8px; }
:host([hide-date = "true"]) #date { display: none; }
</style>
<section>
<div id="date"></div>
<div id="time"></div>
</section>
`);
}
get datetime() {
return Date.parse(this.getAttribute('datetime'));
}
set datetime(v) {
this.setAttribute('datetime', v);
this.#format();
}
get hide_date() {
return this.getAttribute('hide-date') === 'true';
}
set hide_date(v) {
this.setAttribute('hide-date', v === true || v === 'true' ? 'true' : 'false');
this.#format();
}
get hide_time() {
return this.getAttribute('hide-time') === 'true';
}
set hide_time(v) {
this.setAttribute('hide-time', v === true || v === 'true' ? 'true' : 'false');
this.#format();
}
#format() {
{
const el = this.shadowRoot.querySelector('#date');
if (!this.hide_date)
el.textContent = new Date().toLocaleDateString(navigator.language || navigator.userLanguage, {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric"
});
else
el.textContent = '';
}
{
const el = this.shadowRoot.querySelector('#time');
if (!this.hide_date)
el.textContent = new Date().toLocaleDateString(navigator.language || navigator.userLanguage, {
hour: "numeric",
minute: "numeric",
date: "short"
}).split(' ')[1];
else
el.textContent = '';
}
}
});

View File

@ -216,6 +216,7 @@ customElements.define('rich-text-editor', class extends Component {
const img = new Image(); const img = new Image();
img.src = reader.result || ''; img.src = reader.result || '';
el.appendChild(img); el.appendChild(img);
this.#emitChange();
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}); });

View File

@ -126,7 +126,7 @@ struct AccountTemplate {
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> Result<HttpResponse> { async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> Result<HttpResponse> {
let pool = db.into_inner(); let pool = db.into_inner();
let mut t = crate::ok_or_internal!(pool.begin().await); let mut t = crate::ok_or_internal!(pool.begin().await);
let record = match id.identity() { let account = match id.identity() {
Some(id) => queries::account_by_id(&mut t, id).await, Some(id) => queries::account_by_id(&mut t, id).await,
_ => { _ => {
id.forget(); id.forget();
@ -136,7 +136,7 @@ async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> Result<HttpRespon
t.commit().await.ok(); t.commit().await.ok();
Ok(HttpResponse::Ok().body( Ok(HttpResponse::Ok().body(
AccountTemplate { AccountTemplate {
account: record, account,
error: None, error: None,
page: Page::Account, page: Page::Account,
} }
@ -547,6 +547,39 @@ async fn upload(
} }
} }
#[derive(Template)]
#[template(path = "news.html")]
pub struct NewsTemplate {
account: Option<db::Account>,
error: Option<String>,
page: Page,
news: Vec<db::NewsArticle>,
}
#[get("/news")]
async fn news(id: Identity, db: Data<PgPool>) -> Result<HttpResponse> {
let pool = db.into_inner();
let mut t = crate::ok_or_internal!(pool.begin().await);
let account = match id.identity() {
Some(id) => queries::account_by_id(&mut t, id).await,
_ => {
id.forget();
None
}
};
let news = queries::published_news(&mut t).await.unwrap_or_default();
Ok(HttpResponse::Ok().content_type("text/html").body(
NewsTemplate {
account,
error: None,
page: Page::News,
news,
}
.render()
.unwrap(),
))
}
pub fn configure(config: &mut ServiceConfig) { pub fn configure(config: &mut ServiceConfig) {
std::fs::create_dir_all("./uploads").expect("Failed to create ./uploads directory"); std::fs::create_dir_all("./uploads").expect("Failed to create ./uploads directory");
@ -562,6 +595,7 @@ pub fn configure(config: &mut ServiceConfig) {
) )
.service(index) .service(index)
.service(account_page) .service(account_page)
.service(news)
.service(register) .service(register)
.service(logout) .service(logout)
.service(login) .service(login)