add: taken slug check
This commit is contained in:
parent
a33ee961fe
commit
d80368e6c2
5 changed files with 85 additions and 7 deletions
|
@ -137,3 +137,34 @@ globalThis.tab_preview = async () => {
|
||||||
document.getElementById("editor_tab_button").classList.add("camo");
|
document.getElementById("editor_tab_button").classList.add("camo");
|
||||||
document.getElementById("preview_tab_button").classList.remove("camo");
|
document.getElementById("preview_tab_button").classList.remove("camo");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let exists_timeout = null;
|
||||||
|
globalThis.check_exists_input = (e) => {
|
||||||
|
if (exists_timeout) {
|
||||||
|
clearTimeout(exists_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
exists_timeout = setTimeout(async () => {
|
||||||
|
if (e.target.value.length < 2 || e.target.value.length > 32) {
|
||||||
|
e.target.setCustomValidity("");
|
||||||
|
e.target.removeAttribute("data-invalid");
|
||||||
|
e.target.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = (
|
||||||
|
await (await fetch(`/api/v1/entries/${e.target.value}`)).json()
|
||||||
|
).payload;
|
||||||
|
|
||||||
|
console.log(exists);
|
||||||
|
if (exists) {
|
||||||
|
e.target.setCustomValidity("Slug is already in use");
|
||||||
|
e.target.setAttribute("data-invalid", "true");
|
||||||
|
} else {
|
||||||
|
e.target.setCustomValidity("");
|
||||||
|
e.target.removeAttribute("data-invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.reportValidity();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
|
@ -158,7 +158,7 @@ video {
|
||||||
|
|
||||||
/* button */
|
/* button */
|
||||||
.button {
|
.button {
|
||||||
--h: 35.2px;
|
--h: 36px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -171,11 +171,11 @@ video {
|
||||||
border: none;
|
border: none;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
height: var(--h);
|
height: var(--h);
|
||||||
min-height: var(--h);
|
line-height: var(--h);
|
||||||
max-height: var(--h);
|
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
|
@ -193,17 +193,19 @@ video {
|
||||||
|
|
||||||
/* input */
|
/* input */
|
||||||
input {
|
input {
|
||||||
--h: 35.2px;
|
--h: 36px;
|
||||||
padding: var(--pad-2) var(--pad-4);
|
padding: var(--pad-2) var(--pad-4);
|
||||||
background: var(--color-raised);
|
background: var(--color-raised);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
transition: background 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
border 0.15s;
|
||||||
height: var(--h);
|
height: var(--h);
|
||||||
min-height: var(--h);
|
line-height: var(--h);
|
||||||
max-height: var(--h);
|
border-left: solid 0px transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
|
@ -212,6 +214,11 @@ input:focus {
|
||||||
background: var(--color-super-raised);
|
background: var(--color-super-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:user-invalid,
|
||||||
|
input[data-invalid] {
|
||||||
|
border-left: inset 5px var(--color-red);
|
||||||
|
}
|
||||||
|
|
||||||
/* typo */
|
/* typo */
|
||||||
p {
|
p {
|
||||||
margin-bottom: var(--pad-4);
|
margin-bottom: var(--pad-4);
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
("type" "text")
|
("type" "text")
|
||||||
("minlength" "2")
|
("minlength" "2")
|
||||||
("name" "new_slug")
|
("name" "new_slug")
|
||||||
|
("oninput" "check_exists_input(event)")
|
||||||
("placeholder" "New url")))
|
("placeholder" "New url")))
|
||||||
(div
|
(div
|
||||||
("class" "w-full flex justify-between gap-2")
|
("class" "w-full flex justify-between gap-2")
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
("minlength" "2")
|
("minlength" "2")
|
||||||
("maxlength" "32")
|
("maxlength" "32")
|
||||||
("name" "slug")
|
("name" "slug")
|
||||||
|
("oninput" "check_exists_input(event)")
|
||||||
("placeholder" "Custom url"))))
|
("placeholder" "Custom url"))))
|
||||||
(text "{{ components::footer() }}")
|
(text "{{ components::footer() }}")
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ pub fn routes() -> Router {
|
||||||
.route("/api/v1/render", post(render_request))
|
.route("/api/v1/render", post(render_request))
|
||||||
.route("/api/v1/entries", post(create_request))
|
.route("/api/v1/entries", post(create_request))
|
||||||
.route("/api/v1/entries/{slug}", post(edit_request))
|
.route("/api/v1/entries/{slug}", post(edit_request))
|
||||||
|
.route("/api/v1/entries/{slug}", get(exists_request))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_context(data: &DataClient, build_code: &str) -> Context {
|
fn default_context(data: &DataClient, build_code: &str) -> Context {
|
||||||
|
@ -178,6 +179,25 @@ async fn render_request(Json(req): Json<RenderMarkdown>) -> impl IntoResponse {
|
||||||
crate::markdown::render_markdown(&req.content)
|
crate::markdown::render_markdown(&req.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn exists_request(
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(slug): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let (ref data, _, _) = *data.read().await;
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: data
|
||||||
|
.query(&SimplifiedQuery {
|
||||||
|
query: AppDataSelectQuery::KeyIs(format!("entries('{}')", slug)),
|
||||||
|
mode: AppDataSelectMode::One(0),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_ok(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CreateEntry {
|
struct CreateEntry {
|
||||||
content: String,
|
content: String,
|
||||||
|
@ -206,6 +226,14 @@ async fn create_request(
|
||||||
return Json(Error::DataTooLong("slug".to_string()).into());
|
return Json(Error::DataTooLong("slug".to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.content.len() < 2 {
|
||||||
|
return Json(Error::DataTooShort("content".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.content.len() > 150_000 {
|
||||||
|
return Json(Error::DataTooLong("content".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
// check for existing
|
// check for existing
|
||||||
if data
|
if data
|
||||||
.query(&SimplifiedQuery {
|
.query(&SimplifiedQuery {
|
||||||
|
@ -274,6 +302,16 @@ async fn edit_request(
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let (ref data, _, _) = *data.read().await;
|
let (ref data, _, _) = *data.read().await;
|
||||||
|
|
||||||
|
// check content length
|
||||||
|
if req.content.len() < 2 {
|
||||||
|
return Json(Error::DataTooShort("content".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.content.len() > 150_000 {
|
||||||
|
return Json(Error::DataTooLong("content".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
let (id, mut entry) = match data
|
let (id, mut entry) = match data
|
||||||
.query(&SimplifiedQuery {
|
.query(&SimplifiedQuery {
|
||||||
query: AppDataSelectQuery::KeyIs(format!("entries('{}')", slug)),
|
query: AppDataSelectQuery::KeyIs(format!("entries('{}')", slug)),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue