add: journal.css special note
This commit is contained in:
parent
f0d1a1e8e4
commit
dc50f3a8af
8 changed files with 125 additions and 13 deletions
|
@ -1250,3 +1250,32 @@ details.accordion .inner {
|
||||||
.CodeMirror-focused .CodeMirror-placeholder {
|
.CodeMirror-focused .CodeMirror-placeholder {
|
||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-color: var(--color-super-lowered) !important;
|
||||||
|
background-color: var(--color-lowered) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hints {
|
||||||
|
background: var(--color-raised) !important;
|
||||||
|
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
|
||||||
|
var(--color-shadow);
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
padding: var(--pad-1) !important;
|
||||||
|
border-color: var(--color-super-lowered) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hints li {
|
||||||
|
color: var(--color-text-raised) !important;
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: calc(var(--pad-1) / 2) var(--pad-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hints li.CodeMirror-hint-active {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
color: var(--color-text-primary) !important;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,11 @@
|
||||||
; redirect to journal homepage
|
; redirect to journal homepage
|
||||||
(meta ("http-equiv" "refresh") ("content" "0; url=/@{{ user.username }}/{{ journal.title }}"))
|
(meta ("http-equiv" "refresh") ("content" "0; url=/@{{ user.username }}/{{ journal.title }}"))
|
||||||
(text "{%- endif %} {%- endif %}")
|
(text "{%- endif %} {%- endif %}")
|
||||||
|
|
||||||
|
(text "{% if view_mode and journal -%}")
|
||||||
|
; add journal css
|
||||||
|
(link ("rel" "stylesheet") ("data-turbo-temporary" "true") ("href" "/api/v1/journals/{{ journal.id }}/journal.css?v=tetratto-{{ random_cache_breaker }}"))
|
||||||
|
(text "{%- endif %}")
|
||||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"journals\") }}")
|
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"journals\") }}")
|
||||||
(text "{% if not view_mode -%}")
|
(text "{% if not view_mode -%}")
|
||||||
(nav
|
(nav
|
||||||
|
@ -73,7 +78,7 @@
|
||||||
(b (text "{{ note.title }}"))
|
(b (text "{{ note.title }}"))
|
||||||
(text "{%- endif %}"))
|
(text "{%- endif %}"))
|
||||||
|
|
||||||
(text "{% if user and user.id == journal.owner -%}")
|
(text "{% if user and user.id == journal.owner and (not note or note.title != \"journal.css\") -%}")
|
||||||
(div
|
(div
|
||||||
("class" "pillmenu")
|
("class" "pillmenu")
|
||||||
(a
|
(a
|
||||||
|
@ -181,10 +186,36 @@
|
||||||
; import codemirror
|
; import codemirror
|
||||||
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js") ("data-turbo-temporary" "true"))
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js") ("data-turbo-temporary" "true"))
|
||||||
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js") ("data-turbo-temporary" "true"))
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js") ("data-turbo-temporary" "true"))
|
||||||
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js") ("data-turbo-temporary" "true"))
|
|
||||||
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css") ("data-turbo-temporary" "true"))
|
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css") ("data-turbo-temporary" "true"))
|
||||||
|
|
||||||
|
(text "{% if note.title == \"journal.css\" -%}")
|
||||||
|
; css editor
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/edit/closebrackets.js") ("data-turbo-temporary" "true"))
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/hint/css-hint.js") ("data-turbo-temporary" "true"))
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/hint/show-hint.js") ("data-turbo-temporary" "true"))
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/lint/css-lint.js") ("data-turbo-temporary" "true"))
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/css/css.js") ("data-turbo-temporary" "true"))
|
||||||
|
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/addon/hint/show-hint.css") ("data-turbo-temporary" "true"))
|
||||||
|
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/addon/lint/lint.css") ("data-turbo-temporary" "true"))
|
||||||
|
|
||||||
|
(style
|
||||||
|
(text ".CodeMirror {
|
||||||
|
font-family: monospace !important;
|
||||||
|
font-size: 16px;
|
||||||
|
border: solid 1px var(--color-super-lowered);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-line {
|
||||||
|
padding-left: 5px !important;
|
||||||
|
}"))
|
||||||
|
(text "{% else %}")
|
||||||
|
; markdown editor
|
||||||
|
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js") ("data-turbo-temporary" "true"))
|
||||||
|
(text "{%- endif %}")
|
||||||
|
|
||||||
; tab bar
|
; tab bar
|
||||||
|
(text "{% if note.title != \"journal.css\" -%}")
|
||||||
(div
|
(div
|
||||||
("class" "pillmenu")
|
("class" "pillmenu")
|
||||||
(a
|
(a
|
||||||
|
@ -199,6 +230,7 @@
|
||||||
("data-tab-button" "preview")
|
("data-tab-button" "preview")
|
||||||
("data-turbo" "false")
|
("data-turbo" "false")
|
||||||
(str (text "journals:label.preview_pane"))))
|
(str (text "journals:label.preview_pane"))))
|
||||||
|
(text "{%- endif %}")
|
||||||
|
|
||||||
; tabs
|
; tabs
|
||||||
(div
|
(div
|
||||||
|
@ -222,10 +254,15 @@
|
||||||
(script ("id" "editor_content") ("type" "text/markdown") (text "{{ note.content|remove_script_tags|safe }}"))
|
(script ("id" "editor_content") ("type" "text/markdown") (text "{{ note.content|remove_script_tags|safe }}"))
|
||||||
(script
|
(script
|
||||||
(text "setTimeout(() => {
|
(text "setTimeout(() => {
|
||||||
|
if (!document.getElementById(\"preview_tab\").shadowRoot) {
|
||||||
|
document.getElementById(\"preview_tab\").attachShadow({ mode: \"open\" });
|
||||||
|
}
|
||||||
|
|
||||||
globalThis.editor = CodeMirror(document.getElementById(\"editor_tab\"), {
|
globalThis.editor = CodeMirror(document.getElementById(\"editor_tab\"), {
|
||||||
value: document.getElementById(\"editor_content\").innerHTML,
|
value: document.getElementById(\"editor_content\").innerHTML,
|
||||||
mode: \"markdown\",
|
mode: \"{% if note.title == 'journal.css' -%} css {%- else -%} markdown {%- endif %}\",
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
|
lineNumbers: \"{{ note.title }}\" === \"journal.css\",
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
viewportMargin: Number.POSITIVE_INFINITY,
|
viewportMargin: Number.POSITIVE_INFINITY,
|
||||||
|
@ -233,7 +270,8 @@
|
||||||
highlightFormatting: false,
|
highlightFormatting: false,
|
||||||
fencedCodeBlockHighlighting: false,
|
fencedCodeBlockHighlighting: false,
|
||||||
xml: false,
|
xml: false,
|
||||||
smartIndent: false,
|
smartIndent: true,
|
||||||
|
indentUnit: 4,
|
||||||
placeholder: `# {{ note.title }}`,
|
placeholder: `# {{ note.title }}`,
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Home: \"goLineLeft\",
|
Home: \"goLineLeft\",
|
||||||
|
@ -244,6 +282,15 @@
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
editor.on(\"keydown\", (cm, e) => {
|
||||||
|
if (e.key.length > 1) {
|
||||||
|
// ignore all keys that aren't a letter
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.showHint(cm, CodeMirror.hint.css);
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelector(\"[data-tab-button=editor]\").addEventListener(\"click\", async (e) => {
|
document.querySelector(\"[data-tab-button=editor]\").addEventListener(\"click\", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
trigger(\"atto::hooks::tabs:switch\", [\"editor\"]);
|
trigger(\"atto::hooks::tabs:switch\", [\"editor\"]);
|
||||||
|
@ -263,7 +310,10 @@
|
||||||
})
|
})
|
||||||
).text();
|
).text();
|
||||||
|
|
||||||
document.getElementById(\"preview_tab\").innerHTML = res;
|
const preview_token = window.crypto.randomUUID();
|
||||||
|
document.getElementById(\"preview_tab\").shadowRoot.innerHTML = `${res}<style>
|
||||||
|
@import url(\"/api/v1/journals/{{ journal.id }}/journal.css?v=preview-${preview_token}\");
|
||||||
|
</style>`;
|
||||||
trigger(\"atto::hooks::tabs:switch\", [\"preview\"]);
|
trigger(\"atto::hooks::tabs:switch\", [\"preview\"]);
|
||||||
});
|
});
|
||||||
}, 150);"))
|
}, 150);"))
|
||||||
|
@ -360,7 +410,7 @@
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title,
|
title,
|
||||||
content: `# ${title}`,
|
content: title === \"journal.css\" ? `/* ${title} */\\n` : `# ${title}`,
|
||||||
journal: \"{{ selected_journal }}\",
|
journal: \"{{ selected_journal }}\",
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -564,14 +564,14 @@
|
||||||
(li
|
(li
|
||||||
(text "Use custom CSS on your profile"))
|
(text "Use custom CSS on your profile"))
|
||||||
(li
|
(li
|
||||||
(text "Ability to use community emojis outside of
|
(text "Use community emojis outside of
|
||||||
their community"))
|
their community"))
|
||||||
(li
|
(li
|
||||||
(text "Ability to upload and use gif emojis"))
|
(text "Upload and use gif emojis"))
|
||||||
(li
|
(li
|
||||||
(text "Create infinite stack timelines"))
|
(text "Create infinite stack timelines"))
|
||||||
(li
|
(li
|
||||||
(text "Ability to upload images to posts"))
|
(text "Upload images to posts"))
|
||||||
(li
|
(li
|
||||||
(text "Save infinite post drafts"))
|
(text "Save infinite post drafts"))
|
||||||
(li
|
(li
|
||||||
|
@ -579,7 +579,7 @@
|
||||||
(li
|
(li
|
||||||
(text "Ability to create forges"))
|
(text "Ability to create forges"))
|
||||||
(li
|
(li
|
||||||
(text "Ability to create more than 1 app"))
|
(text "Create more than 1 app"))
|
||||||
(li
|
(li
|
||||||
(text "Create up to 10 stack blocks"))
|
(text "Create up to 10 stack blocks"))
|
||||||
(li
|
(li
|
||||||
|
@ -587,7 +587,9 @@
|
||||||
(li
|
(li
|
||||||
(text "Increased proxied image size"))
|
(text "Increased proxied image size"))
|
||||||
(li
|
(li
|
||||||
(text "Create infinite journals")))
|
(text "Create infinite journals"))
|
||||||
|
(li
|
||||||
|
(text "Create infinite notes in each journal")))
|
||||||
(a
|
(a
|
||||||
("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}")
|
("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}")
|
||||||
("class" "button")
|
("class" "button")
|
||||||
|
|
|
@ -49,6 +49,20 @@ pub async fn get_request(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_css_request(
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
|
||||||
|
let note = match data.get_note_by_journal_title(id, "journal.css").await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return ([("Content-Type", "text/plain")], format!("/* {e} */")),
|
||||||
|
};
|
||||||
|
|
||||||
|
([("Content-Type", "text/css")], note.content)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
pub async fn list_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadJournals) {
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadJournals) {
|
||||||
|
|
|
@ -551,6 +551,7 @@ pub fn routes() -> Router {
|
||||||
.route("/journals", post(journals::create_request))
|
.route("/journals", post(journals::create_request))
|
||||||
.route("/journals/{id}", get(journals::get_request))
|
.route("/journals/{id}", get(journals::get_request))
|
||||||
.route("/journals/{id}", delete(journals::delete_request))
|
.route("/journals/{id}", delete(journals::delete_request))
|
||||||
|
.route("/journals/{id}/journal.css", get(journals::get_css_request))
|
||||||
.route("/journals/{id}/title", post(journals::update_title_request))
|
.route("/journals/{id}/title", post(journals::update_title_request))
|
||||||
.route(
|
.route(
|
||||||
"/journals/{id}/privacy",
|
"/journals/{id}/privacy",
|
||||||
|
|
|
@ -116,7 +116,7 @@ pub async fn view_request(
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we don't have a selected journal, we shouldn't be here probably
|
// if we don't have a selected journal, we shouldn't be here probably
|
||||||
if selected_journal.is_empty() {
|
if selected_journal.is_empty() | (selected_note == "journal.css") {
|
||||||
return Err(Html(
|
return Err(Html(
|
||||||
render_error(Error::NotAllowed, &jar, &data, &user).await,
|
render_error(Error::NotAllowed, &jar, &data, &user).await,
|
||||||
));
|
));
|
||||||
|
|
|
@ -70,7 +70,7 @@ impl DataManager {
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAXIMUM_FREE_JOURNALS: usize = 15;
|
const MAXIMUM_FREE_JOURNALS: usize = 5;
|
||||||
|
|
||||||
/// Create a new journal in the database.
|
/// Create a new journal in the database.
|
||||||
///
|
///
|
||||||
|
|
|
@ -65,6 +65,8 @@ impl DataManager {
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAXIMUM_FREE_NOTES_PER_JOURNAL: usize = 10;
|
||||||
|
|
||||||
/// Create a new note in the database.
|
/// Create a new note in the database.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -85,6 +87,20 @@ impl DataManager {
|
||||||
|
|
||||||
data.title = data.title.replace(" ", "_").to_lowercase();
|
data.title = data.title.replace(" ", "_").to_lowercase();
|
||||||
|
|
||||||
|
// check number of notes
|
||||||
|
let owner = self.get_user_by_id(data.owner).await?;
|
||||||
|
|
||||||
|
if !owner.permissions.check(FinePermission::SUPPORTER) {
|
||||||
|
let journals = self.get_notes_by_journal(data.owner).await?;
|
||||||
|
|
||||||
|
if journals.len() >= Self::MAXIMUM_FREE_NOTES_PER_JOURNAL {
|
||||||
|
return Err(Error::MiscError(
|
||||||
|
"You already have the maximum number of notes you can have in this journal"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check name
|
// check name
|
||||||
let regex = regex::RegexBuilder::new(NAME_REGEX)
|
let regex = regex::RegexBuilder::new(NAME_REGEX)
|
||||||
.multi_line(true)
|
.multi_line(true)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue