Display news, improve news form
This commit is contained in:
parent
e434b89e9e
commit
9564c37899
@ -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>
|
||||||
|
16
assets/templates/news.html
Normal file
16
assets/templates/news.html
Normal 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 %}
|
@ -1,3 +1,3 @@
|
|||||||
import "./admin/ow-admin";
|
import "./admin/ow-admin";
|
||||||
import "./admin/ow-articles";
|
|
||||||
import "./admin/news-article";
|
import "./admin/article-form";
|
||||||
|
@ -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() {
|
||||||
|
@ -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() {
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -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() {
|
@ -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>
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
});
|
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
76
client/src/shared/date-time.js
Normal file
76
client/src/shared/date-time.js
Normal 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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user