Create project and documents #1
9 changed files with 213 additions and 54 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1858,6 +1858,7 @@ dependencies = [
|
|||
"memo-map",
|
||||
"self_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ dotenvy = "0.15.7"
|
|||
env_logger = "0.11.3"
|
||||
fjall = "0.6.5"
|
||||
free-icons = "0.7.0"
|
||||
minijinja = { version = "1.0.14", features = ["loader"] }
|
||||
minijinja = { version = "1.0.14", features = ["loader", "json", "builtins"] }
|
||||
minijinja-autoreload = "1.0.14"
|
||||
model_derive = { path = "./model_derive" }
|
||||
rand = "0.8.5"
|
||||
|
|
|
@ -7,18 +7,6 @@ import { nord } from '@milkdown/theme-nord'
|
|||
import { listener, listenerCtx } from '@milkdown/plugin-listener';
|
||||
import '@milkdown/theme-nord/style.css'
|
||||
|
||||
const markdown =
|
||||
`# Milkdown Vanilla Commonmark
|
||||
|
||||
> You're scared of a world where you're needed.
|
||||
|
||||
This is a demo for using Milkdown with **Vanilla Typescript**.
|
||||
|
||||
- A list
|
||||
- [ ] Something to do
|
||||
- [x] Something done`
|
||||
|
||||
|
||||
function configureListItem(ctx: Ctx) {
|
||||
ctx.set(listItemBlockConfig.key, {
|
||||
renderLabel: (label: string, listType, checked?: boolean) => {
|
||||
|
@ -35,16 +23,20 @@ function configureListItem(ctx: Ctx) {
|
|||
})
|
||||
}
|
||||
|
||||
function createEditor(rootId, fieldId, content) {
|
||||
Editor
|
||||
.make()
|
||||
.config(ctx => {
|
||||
ctx.set(rootCtx, "#editor")
|
||||
ctx.set(defaultValueCtx, markdown)
|
||||
ctx.set(rootCtx, rootId)
|
||||
ctx.set(defaultValueCtx, content)
|
||||
|
||||
const listener = ctx.get(listenerCtx);
|
||||
listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
|
||||
if (markdown !== prevMarkdown) {
|
||||
console.log(markdown);
|
||||
console.log(fieldId);
|
||||
document.getElementById(fieldId).value = markdown;
|
||||
console.log("updated");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -55,3 +47,6 @@ Editor
|
|||
.use(listener)
|
||||
.use(listItemBlockComponent)
|
||||
.create();
|
||||
}
|
||||
|
||||
window.createEditor = createEditor;
|
||||
|
|
|
@ -45,8 +45,8 @@ pub async fn create_document_page(
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateDocumentSubmission {
|
||||
project_id: Uuid,
|
||||
title: String,
|
||||
pub project_id: Uuid,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
pub async fn create_document_submit(
|
||||
|
@ -117,6 +117,7 @@ pub async fn edit_document_page(
|
|||
}
|
||||
};
|
||||
|
||||
dbg!(&document);
|
||||
let values = context! {
|
||||
user => user,
|
||||
document => document,
|
||||
|
@ -124,3 +125,46 @@ pub async fn edit_document_page(
|
|||
|
||||
ctx.render_resp("documents/edit_document.html", values)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EditDocumentSubmission {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
|
||||
pub async fn edit_document_submit(
|
||||
State(ctx): State<Context>,
|
||||
auth_session: AuthSession<Context>,
|
||||
Path((document_id,)): Path<(Uuid,)>,
|
||||
form: Form<EditDocumentSubmission>,
|
||||
) -> Response {
|
||||
let user = match auth_session.user {
|
||||
Some(user) => user,
|
||||
None => return Redirect::to("/login").into_response(),
|
||||
};
|
||||
|
||||
let mut document = match ModelPermission::user_document(&ctx.kv_handles, user.id, document_id) {
|
||||
Ok(Some(document)) => document,
|
||||
Ok(None) => return Redirect::to("/documents").into_response(),
|
||||
Err(err) => {
|
||||
error!(?err, "failed to load document");
|
||||
return internal_server_error();
|
||||
}
|
||||
};
|
||||
|
||||
let new_document = Document {
|
||||
id: document.id,
|
||||
project_id: document.id,
|
||||
title: form.title.to_owned(),
|
||||
content: form.content.to_owned(),
|
||||
};
|
||||
|
||||
if let Err(err) = new_document.save(&ctx.kv_handles) {
|
||||
error!(?err, "failed to save document");
|
||||
return internal_server_error();
|
||||
}
|
||||
info!(?new_document, "document updated");
|
||||
|
||||
Redirect::to("/documents").into_response()
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use tower_sessions::SessionManagerLayer;
|
|||
use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::{config::CommandLineOptions, context::Context, handler::{documents::{create_document_page, create_document_submit, documents_page, edit_document_page}, home::home_page, login::logout, login_page, login_submit, projects::{create_project_page, create_project_submit, projects_page}}, kv::KvHandle, logging::setup_logging, templates::make_template_loader};
|
||||
use crate::{config::CommandLineOptions, context::Context, handler::{documents::{create_document_page, create_document_submit, documents_page, edit_document_page, edit_document_submit}, home::home_page, login::logout, login_page, login_submit, projects::{create_project_page, create_project_submit, projects_page}}, kv::KvHandle, logging::setup_logging, templates::make_template_loader};
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
dotenvy::dotenv()?;
|
||||
|
@ -50,6 +50,7 @@ pub async fn run() -> Result<()> {
|
|||
.route("/documents/new", get(create_document_page))
|
||||
.route("/documents/new", post(create_document_submit))
|
||||
.route("/documents/edit/:id", get(edit_document_page))
|
||||
.route("/documents/edit/:id", post(edit_document_submit))
|
||||
.layer(trace_layer)
|
||||
.layer(session_layer)
|
||||
.layer(auth_layer)
|
||||
|
|
|
@ -31819,15 +31819,6 @@
|
|||
};
|
||||
|
||||
// frontend/main.ts
|
||||
var markdown = `# Milkdown Vanilla Commonmark
|
||||
|
||||
> You're scared of a world where you're needed.
|
||||
|
||||
This is a demo for using Milkdown with **Vanilla Typescript**.
|
||||
|
||||
- A list
|
||||
- [ ] Something to do
|
||||
- [x] Something done`;
|
||||
function configureListItem(ctx) {
|
||||
ctx.set(listItemBlockConfig.key, {
|
||||
renderLabel: (label, listType, checked) => {
|
||||
|
@ -31842,14 +31833,20 @@ This is a demo for using Milkdown with **Vanilla Typescript**.
|
|||
}
|
||||
});
|
||||
}
|
||||
function createEditor(rootId, fieldId, content3) {
|
||||
Oe.make().config((ctx) => {
|
||||
ctx.set(ce, "#editor");
|
||||
ctx.set(re, markdown);
|
||||
ctx.set(ce, rootId);
|
||||
ctx.set(re, content3);
|
||||
const listener = ctx.get(h4);
|
||||
listener.markdownUpdated((ctx2, markdown2, prevMarkdown) => {
|
||||
if (markdown2 !== prevMarkdown) {
|
||||
console.log(markdown2);
|
||||
listener.markdownUpdated((ctx2, markdown, prevMarkdown) => {
|
||||
if (markdown !== prevMarkdown) {
|
||||
console.log(markdown);
|
||||
console.log(fieldId);
|
||||
document.getElementById(fieldId).value = markdown;
|
||||
console.log("updated");
|
||||
}
|
||||
});
|
||||
}).config(configureListItem).use(cr).use(wt).use(c5).use(U5).use(listItemBlockComponent).create();
|
||||
}
|
||||
window.createEditor = createEditor;
|
||||
})();
|
||||
|
|
|
@ -1166,6 +1166,23 @@ select {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
min-height: 3rem;
|
||||
flex-shrink: 1;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
line-height: 2;
|
||||
border-radius: var(--rounded-btn, 0.5rem);
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.btm-nav > * .label {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
|
@ -1607,6 +1624,38 @@ select {
|
|||
}
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
box-shadow: none;
|
||||
border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
outline-offset: 2px;
|
||||
outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
}
|
||||
|
||||
.textarea-disabled,
|
||||
.textarea:disabled,
|
||||
.textarea[disabled] {
|
||||
cursor: not-allowed;
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
color: var(--fallback-bc,oklch(var(--bc)/0.4));
|
||||
}
|
||||
|
||||
.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder {
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
|
||||
--tw-placeholder-opacity: 0.2;
|
||||
}
|
||||
|
||||
.textarea-disabled::placeholder,
|
||||
.textarea:disabled::placeholder,
|
||||
.textarea[disabled]::placeholder {
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
|
||||
--tw-placeholder-opacity: 0.2;
|
||||
}
|
||||
|
||||
@keyframes toast-pop {
|
||||
0% {
|
||||
transform: scale(0.9);
|
||||
|
@ -2239,6 +2288,10 @@ select {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
@ -2255,6 +2308,11 @@ select {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.h-max {
|
||||
height: -moz-max-content;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.min-h-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
@ -2287,6 +2345,11 @@ select {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.w-max {
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
@ -2404,6 +2467,16 @@ select {
|
|||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-black {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-gray-400 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-accent {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
|
||||
|
|
48
templates/documents/edit_document.html
Normal file
48
templates/documents/edit_document.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full bg-white">
|
||||
{% include "head.html" %}
|
||||
<body class="h-full">
|
||||
|
||||
<div class="h-full">
|
||||
{% set current_page = "documents" %}
|
||||
{% include "components/sidebar.html" %}
|
||||
|
||||
<main class="pl-72 bg-gray-50 h-full">
|
||||
<form action="/documents/edit/{{ document.id }}" method="POST">
|
||||
<div class="navbar bg-accent text-accent-content">
|
||||
<div class="navbar-start gap-2">
|
||||
<a class="btn" href="/documents">
|
||||
{{ "arrow-left"|heroicon("w-6 h-6")|safe }} Back
|
||||
</a>
|
||||
<button class="btn" onClick="saveDocument()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-8 py-8 flex flex-col gap-y-4">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
Title
|
||||
<input type="text" id="title" name="title" class="grow" value="{{ document.title }}" />
|
||||
</label>
|
||||
|
||||
<div id="editor-{{document.id}}" class="prose bg-white"></div>
|
||||
|
||||
<textarea id="content-{{ document.id }}" name="content" class="hidden">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
|
||||
<script type="text/javascript" src="/static/main.js"></script>
|
||||
<script type="text/javascript">
|
||||
console.log("hi");
|
||||
window.createEditor("#editor-{{document.id}}", "content-{{document.id}}", {{document.content | tojson }});
|
||||
console.log({{document.content | tojson}});
|
||||
console.log("hi2");
|
||||
function saveDocument() {
|
||||
console.log("saving");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -26,7 +26,7 @@
|
|||
{% for document in documents %}
|
||||
<div class="card w-96 bg-base-100 shadow-md border-2 border-solid">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ document.name }}</h2>
|
||||
<h2 class="card-title">{{ document.title }}</h2>
|
||||
<div class="card-actions justify-end">
|
||||
<a href="/documents/edit/{{ document.id }}" class="btn btn-primary">Open</a>
|
||||
|
||||
|
|
Loading…
Reference in a new issue