diff --git a/crates/app/src/public/html/journals/app.lisp b/crates/app/src/public/html/journals/app.lisp
index 98779b7..39c6699 100644
--- a/crates/app/src/public/html/journals/app.lisp
+++ b/crates/app/src/public/html/journals/app.lisp
@@ -393,10 +393,6 @@
(script ("id" "editor_content") ("type" "text/markdown") (text "{{ note.content|remove_script_tags|safe }}"))
(script
(text "setTimeout(async () => {
- if (!document.getElementById(\"preview_tab\").shadowRoot) {
- document.getElementById(\"preview_tab\").attachShadow({ mode: \"open\" });
- }
-
document.getElementById(\"editor_tab\").innerHTML = \"\";
globalThis.editor = CodeMirror(document.getElementById(\"editor_tab\"), {
value: document.getElementById(\"editor_content\").innerHTML,
@@ -451,8 +447,7 @@
).text();
const preview_token = window.crypto.randomUUID();
- document.getElementById(\"preview_tab\").shadowRoot.innerHTML = `${res}`;
trigger(\"atto::hooks::tabs:switch\", [\"preview\"]);
diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs
index 7117f69..b7bdd77 100644
--- a/crates/core/src/database/auth.rs
+++ b/crates/core/src/database/auth.rs
@@ -3,6 +3,7 @@ use oiseau::cache::Cache;
use crate::model::auth::UserConnections;
use crate::model::moderation::AuditLogEntry;
use crate::model::oauth::AuthGrant;
+use crate::model::permissions::SecondaryPermission;
use crate::model::{
Error, Result,
auth::{Token, User, UserSettings},
@@ -46,6 +47,7 @@ impl DataManager {
grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(),
invite_code: get!(x->21(i64)) as usize,
+ secondary_permissions: SecondaryPermission::from_bits(get!(x->7(i32)) as u32).unwrap(),
}
}
@@ -224,7 +226,8 @@ impl DataManager {
&"",
&serde_json::to_string(&data.grants).unwrap(),
&serde_json::to_string(&data.associated).unwrap(),
- &(data.invite_code as i64)
+ &(data.invite_code as i64),
+ &(SecondaryPermission::DEFAULT.bits() as i32),
]
);
diff --git a/crates/core/src/database/drivers/sql/create_users.sql b/crates/core/src/database/drivers/sql/create_users.sql
index fa95be5..467d00a 100644
--- a/crates/core/src/database/drivers/sql/create_users.sql
+++ b/crates/core/src/database/drivers/sql/create_users.sql
@@ -19,5 +19,6 @@ CREATE TABLE IF NOT EXISTS users (
connections TEXT NOT NULL,
stripe_id TEXT NOT NULL,
grants TEXT NOT NULL,
- associated TEXT NOT NULL
+ associated TEXT NOT NULL,
+ secondary_permissions INT NOT NULL
)
diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs
index f1ed465..64ebc62 100644
--- a/crates/core/src/model/auth.rs
+++ b/crates/core/src/model/auth.rs
@@ -1,6 +1,9 @@
use std::collections::HashMap;
-use super::{oauth::AuthGrant, permissions::FinePermission};
+use super::{
+ oauth::AuthGrant,
+ permissions::{FinePermission, SecondaryPermission},
+};
use serde::{Deserialize, Serialize};
use totp_rs::TOTP;
use tetratto_shared::{
@@ -52,6 +55,9 @@ pub struct User {
/// The ID of the [`InviteCode`] this user provided during registration.
#[serde(default)]
pub invite_code: usize,
+ /// Secondary permissions because the regular permissions struct ran out of possible bits.
+ #[serde(default)]
+ pub secondary_permissions: SecondaryPermission,
}
pub type UserConnections =
@@ -287,6 +293,7 @@ impl User {
grants: Vec::new(),
associated: Vec::new(),
invite_code: 0,
+ secondary_permissions: SecondaryPermission::DEFAULT,
}
}
diff --git a/crates/core/src/model/permissions.rs b/crates/core/src/model/permissions.rs
index 9cd6dcb..1584083 100644
--- a/crates/core/src/model/permissions.rs
+++ b/crates/core/src/model/permissions.rs
@@ -44,86 +44,110 @@ bitflags! {
}
}
-impl Serialize for FinePermission {
- fn serialize(&self, serializer: S) -> Result
- where
- S: serde::Serializer,
- {
- serializer.serialize_u32(self.bits())
- }
+macro_rules! user_permission {
+ ($struct:ident, $visitor:ident, $banned_check:ident) => {
+ impl Serialize for $struct {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_u32(self.bits())
+ }
+ }
+
+ struct $visitor;
+ impl Visitor<'_> for $visitor {
+ type Value = $struct;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("u32")
+ }
+
+ fn visit_u32(self, value: u32) -> Result
+ where
+ E: DeError,
+ {
+ if let Some(permission) = $struct::from_bits(value) {
+ Ok(permission)
+ } else {
+ Ok($struct::from_bits_retain(value))
+ }
+ }
+
+ fn visit_i32(self, value: i32) -> Result
+ where
+ E: DeError,
+ {
+ if let Some(permission) = $struct::from_bits(value as u32) {
+ Ok(permission)
+ } else {
+ Ok($struct::from_bits_retain(value as u32))
+ }
+ }
+
+ fn visit_u64(self, value: u64) -> Result
+ where
+ E: DeError,
+ {
+ if let Some(permission) = $struct::from_bits(value as u32) {
+ Ok(permission)
+ } else {
+ Ok($struct::from_bits_retain(value as u32))
+ }
+ }
+ }
+
+ impl<'de> Deserialize<'de> for $struct {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ deserializer.deserialize_any($visitor)
+ }
+ }
+
+ impl $struct {
+ /// Join two permissions into a single `u32`.
+ pub fn join(lhs: $struct, rhs: $struct) -> Self {
+ lhs | rhs
+ }
+
+ /// Check if the given `input` contains the given permission.
+ pub fn check(self, permission: $struct) -> bool {
+ if (self & $struct::ADMINISTRATOR) == $struct::ADMINISTRATOR {
+ // has administrator permission, meaning everything else is automatically true
+ return true;
+ } else if self.$banned_check() {
+ // has banned permission, meaning everything else is automatically false
+ return false;
+ }
+
+ (self & permission) == permission
+ }
+
+ /// Sink for checking if the permission is banned.
+ pub fn sink(&self) -> bool {
+ false
+ }
+ }
+
+ impl Default for $struct {
+ fn default() -> Self {
+ Self::DEFAULT
+ }
+ }
+ };
}
-struct FinePermissionVisitor;
-impl Visitor<'_> for FinePermissionVisitor {
- type Value = FinePermission;
-
- fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
- formatter.write_str("u32")
- }
-
- fn visit_u32(self, value: u32) -> Result
- where
- E: DeError,
- {
- if let Some(permission) = FinePermission::from_bits(value) {
- Ok(permission)
- } else {
- Ok(FinePermission::from_bits_retain(value))
- }
- }
-
- fn visit_i32(self, value: i32) -> Result
- where
- E: DeError,
- {
- if let Some(permission) = FinePermission::from_bits(value as u32) {
- Ok(permission)
- } else {
- Ok(FinePermission::from_bits_retain(value as u32))
- }
- }
-
- fn visit_u64(self, value: u64) -> Result
- where
- E: DeError,
- {
- if let Some(permission) = FinePermission::from_bits(value as u32) {
- Ok(permission)
- } else {
- Ok(FinePermission::from_bits_retain(value as u32))
- }
- }
-}
-
-impl<'de> Deserialize<'de> for FinePermission {
- fn deserialize(deserializer: D) -> Result
- where
- D: Deserializer<'de>,
- {
- deserializer.deserialize_any(FinePermissionVisitor)
- }
-}
+user_permission!(FinePermission, FinePermissionVisitor, check_banned);
impl FinePermission {
- /// Join two [`FinePermission`]s into a single `u32`.
- pub fn join(lhs: FinePermission, rhs: FinePermission) -> FinePermission {
- lhs | rhs
+ /// Check if the given permission qualifies as "Banned" status.
+ pub fn check_banned(self) -> bool {
+ (self & FinePermission::BANNED) == FinePermission::BANNED
}
- /// Check if the given `input` contains the given [`FinePermission`].
- pub fn check(self, permission: FinePermission) -> bool {
- if (self & FinePermission::ADMINISTRATOR) == FinePermission::ADMINISTRATOR {
- // has administrator permission, meaning everything else is automatically true
- return true;
- } else if self.check_banned() {
- // has banned permission, meaning everything else is automatically false
- return false;
- }
-
- (self & permission) == permission
- }
-
- /// Check if the given [`FinePermission`] qualifies as "Helper" status.
+ /// Check if the given permission qualifies as "Helper" status.
pub fn check_helper(self) -> bool {
self.check(FinePermission::MANAGE_COMMUNITIES)
&& self.check(FinePermission::MANAGE_POSTS)
@@ -133,24 +157,26 @@ impl FinePermission {
&& self.check(FinePermission::VIEW_AUDIT_LOG)
}
- /// Check if the given [`FinePermission`] qualifies as "Manager" status.
+ /// Check if the given permission qualifies as "Manager" status.
pub fn check_manager(self) -> bool {
self.check_helper() && self.check(FinePermission::MANAGE_USERS)
}
- /// Check if the given [`FinePermission`] qualifies as "Administrator" status.
+ /// Check if the given permission qualifies as "Administrator" status.
pub fn check_admin(self) -> bool {
self.check_manager() && self.check(FinePermission::ADMINISTRATOR)
}
+}
- /// Check if the given [`FinePermission`] qualifies as "Banned" status.
- pub fn check_banned(self) -> bool {
- (self & FinePermission::BANNED) == FinePermission::BANNED
+bitflags! {
+ /// Fine-grained permissions built using bitwise operations. Second permission value.
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+ pub struct SecondaryPermission: u32 {
+ const DEFAULT = 1 << 0;
+ const ADMINISTRATOR = 1 << 1;
+
+ const _ = !0;
}
}
-impl Default for FinePermission {
- fn default() -> Self {
- Self::DEFAULT
- }
-}
+user_permission!(SecondaryPermission, SecondaryPermissionVisitor, sink);
diff --git a/crates/shared/src/markdown.rs b/crates/shared/src/markdown.rs
index c4b51da..1ae665b 100644
--- a/crates/shared/src/markdown.rs
+++ b/crates/shared/src/markdown.rs
@@ -14,8 +14,9 @@ pub fn render_markdown(input: &str) -> String {
},
parse: ParseOptions {
constructs: Constructs {
- gfm_autolink_literal: true,
- ..Default::default()
+ math_flow: true,
+ math_text: true,
+ ..Constructs::gfm()
},
gfm_strikethrough_single_tilde: false,
math_text_single_dollar: false,