Compare commits

...

10 Commits

Author SHA1 Message Date
Natty d049f3c82f
Created a project for the MMM parser 2023-10-01 10:46:26 +02:00
Natty a0bdab23e7
Frontend: Fixed document titles
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-01 01:25:20 +02:00
Natty b4e01fee63
Frontend: Alt text indicators 2023-10-01 01:05:18 +02:00
Natty 36c3507d84
Frontend: Replaced the refreshing text with a spinner
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 23:03:48 +02:00
Natty 2525b3a50a
Frontend: Graceful shutdown of backend 2023-09-30 23:02:39 +02:00
Natty 3837980e50
Frontend: Fixed imports (???)
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 22:40:58 +02:00
Natty 5c8db9b243
Frontend: Cleaned up poll code
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 22:16:27 +02:00
Natty 98c5bcb4c4
Frontend: Different icon for already-renoted buttons
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 21:31:00 +02:00
Natty df275de905
Frontend: Fixed a wrong renote cancel condition 2023-09-30 21:16:56 +02:00
Natty dde74dd56b
Frontend: Fixed renote button
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 21:03:16 +02:00
16 changed files with 189 additions and 51 deletions

8
Cargo.lock generated
View File

@ -1603,6 +1603,14 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "mmm_parser"
version = "0.2.0"
dependencies = [
"nom",
"thiserror",
]
[[package]]
name = "nom"
version = "7.1.3"

View File

@ -14,6 +14,7 @@ members = [
"fe_calckey",
"magnetar_common",
"magnetar_sdk",
"magnetar_mmm_parser",
"core"
]
@ -36,6 +37,7 @@ hyper = "0.14"
js-sys = "0.3"
log = "0.4"
miette = "5.9"
nom = "7"
percent-encoding = "2.2"
redis = "0.23"
reqwest = "0.11"

View File

@ -8,7 +8,7 @@
<meta name="theme-color" content="{% if theme_color %} {{ theme_color }} {% else %} #31748f {% endif %}">
<meta name="theme-color-orig" content="{% if theme_color %} {{ theme_color }} {% else %} #31748f {% endif %}">
<meta name="twitter:card" content="summary">
<meta name="og:site_name" content="{{ instance_name }}">
<meta property="og:site_name" content="{{ instance_name }}">
<title>{{ title | capitalize }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico?{{ timestamp }}">
@ -29,10 +29,10 @@
<meta name="robots" content="noindex">
{% endif %}
<meta name="og:title" content="{{ title | capitalize }}">
<meta name="og:description" content="{% if description %}{{ description }}{% else %}Magnetar, an open source social media platform{% endif %}">
<meta name="og:image" content="{% if image %}{{ image }}{% else %}/static-assets/favicon.png{% endif %}">
<meta name="og:image:alt" content="{% if image_alt %}{{ image_alt }}{% else %}The instance's logo{% endif %}">
<meta property="og:title" content="{{ title | capitalize }}">
<meta property="og:description" content="{% if description %}{{ description }}{% else %}Magnetar, an open source social media platform{% endif %}">
<meta property="og:image" content="{% if image %}{{ image }}{% else %}/static-assets/favicon.png{% endif %}">
<meta property="og:image:alt" content="{% if image_alt %}{{ image_alt }}{% else %}The instance's logo{% endif %}">
<style>
{{ style_css | safe }}

View File

@ -163,6 +163,7 @@ export type Note = {
url?: string;
updatedAt?: DateString;
isHidden?: boolean;
hasRenotedBefore?: boolean;
};
export type NoteReaction = {

View File

@ -1,5 +1,9 @@
<template>
<button ref="thumbnail" class="zdjebgpv">
<button
ref="thumbnail"
class="zdjebgpv"
:class="{ 'no-alt': !file.comment }"
>
<ImgWithBlurhash
v-if="isThumbnailAvailable"
:hash="file.blurhash"
@ -92,6 +96,10 @@ const isThumbnailAvailable = computed(() => {
padding: 0;
cursor: pointer;
&.no-alt {
border: 2px solid orangered;
}
> .icon-sub {
position: absolute;
width: 30%;

View File

@ -27,19 +27,38 @@
/>
<div v-if="image.type === 'image/gif'" class="gif">GIF</div>
</a>
<div class="_image_controls">
<button
v-if="!image.comment"
v-tooltip="i18n.ts.noAltText"
class="_button _image_control_btn _image_control_warn"
@click="noAltTextPopup"
>
<i class="ph-file-x ph-bold ph-lg"></i>
</button>
<button
v-if="image.comment"
v-tooltip="i18n.ts.altText"
class="_button _image_control_btn"
@click="altTextPopup"
>
<i class="ph-file-text ph-bold ph-lg"></i>
</button>
<button
v-tooltip="i18n.ts.hide"
class="_button hide"
class="_button _image_control_btn"
@click="hide = true"
>
<i class="ph-eye-slash ph-bold ph-lg"></i>
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { watch } from "vue";
import type * as misskey from "calckey-js";
import * as os from "@/os";
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
import { defaultStore } from "@/store";
@ -52,6 +71,26 @@ const props = defineProps<{
let hide = $ref(true);
function noAltTextPopup(event: MouseEvent) {
os.alert({
type: "warning",
title: i18n.ts.noAltText,
text: i18n.ts.noAltTextDescription,
});
event.stopPropagation();
}
function altTextPopup(event: MouseEvent) {
os.alert({
type: "info",
title: i18n.ts.altText,
text: props.image.comment,
});
event.stopPropagation();
}
const url =
props.raw || defaultStore.state.loadRawImages
? props.image.url
@ -110,9 +149,15 @@ watch(
position: relative;
background: var(--bg);
> .hide {
display: block;
> ._image_controls {
display: flex;
position: absolute;
gap: 6px;
top: 12px;
right: 12px;
> ._image_control_btn {
display: block;
border-radius: 6px;
background-color: var(--accentedBg);
-webkit-backdrop-filter: var(--blur, blur(15px));
@ -121,14 +166,20 @@ watch(
font-size: 0.8em;
padding: 6px 8px;
text-align: center;
top: 12px;
right: 12px;
border: 2px solid transparent;
> i {
display: block;
}
}
> ._image_control_warn {
color: #f6b195;
border: 2px solid #fa895c;
box-sizing: border-box;
}
}
> a {
display: block;
cursor: zoom-in;

View File

@ -42,7 +42,13 @@
</span>
<span v-if="!isLocal">
<span> · </span>
<a @click.stop="refresh">{{ i18n.ts.reload }}</a>
<a v-if="!pollRefreshing" @click.stop="refresh">{{
i18n.ts.reload
}}</a>
<i
v-else
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
</span>
<span v-if="isVoted"> · {{ i18n.ts._poll.voted }}</span>
<span v-else-if="closed"> · {{ i18n.ts._poll.closed }}</span>
@ -61,10 +67,11 @@ import { i18n } from "@/i18n";
import { useInterval } from "@/scripts/use-interval";
const props = defineProps<{
note: misskey.entities.Note;
note: misskey.entities.Note & Required<Pick<misskey.entities.Note, "poll">>;
readOnly?: boolean;
}>();
const pollRefreshing = ref(false);
const remaining = ref(-1);
const total = computed(() => sum(props.note.poll.choices.map((x) => x.votes)));
@ -100,7 +107,7 @@ if (props.note.poll.expiresAt) {
const tick = () => {
remaining.value = Math.floor(
Math.max(
new Date(props.note.poll.expiresAt).getTime() - Date.now(),
new Date(props.note.poll.expiresAt!).getTime() - Date.now(),
0
) / 1000
);
@ -117,13 +124,27 @@ if (props.note.poll.expiresAt) {
async function refresh() {
if (!props.note.uri) return;
const obj = await os.apiWithDialog("ap/show", { uri: props.note.uri });
if (obj.type === "Note" && obj.object.poll) {
pollRefreshing.value = true;
os.api("ap/show", { uri: props.note.uri })
.then((obj) => {
if (obj && obj.type === "Note" && obj.object.poll) {
props.note.poll = obj.object.poll;
}
})
.catch((err) => {
os.alert({
type: "error",
text: err.message + "\n" + (err as any).id,
});
})
.finally(() => {
pollRefreshing.value = false;
});
}
const vote = async (id) => {
const vote = async (id: number) => {
pleaseLogin();
if (props.readOnly || closed.value || isVoted.value) return;

View File

@ -7,7 +7,13 @@
:class="{ renoted: hasRenotedBefore }"
@click="renote(false, $event)"
>
<i class="ph-repeat ph-bold ph-lg"></i>
<i
class="ph-bold ph-lg"
:class="{
'ph-repeat': !hasRenotedBefore,
'ph-repeat-once': hasRenotedBefore,
}"
></i>
<p v-if="count > 0 && !detailedView" class="count">{{ count }}</p>
</button>
<button v-else class="eddddedb _button">
@ -36,6 +42,8 @@ const props = defineProps<{
const buttonRef = ref<HTMLElement>();
const hasRenotedBefore = ref(props.note.hasRenotedBefore ?? false);
const canRenote = computed(
() =>
["public", "home"].includes(props.note.visibility) ||
@ -68,14 +76,6 @@ useTooltip(buttonRef, async (showing) => {
const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
pleaseLogin();
const renotes = await os.api("notes/renotes", {
noteId: props.note.id,
userId: $i.id,
limit: 1,
});
const hasRenotedBefore = renotes.length > 0;
let buttonActions: Array<MenuItem> = [];
if (props.note.visibility === "public") {
@ -88,7 +88,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "public",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -115,7 +115,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "home",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -143,7 +143,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
visibility: "specified",
visibleUserIds: props.note.visibleUserIds,
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -168,7 +168,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "followers",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -206,7 +206,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
localOnly: true,
}
);
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -236,7 +236,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
});
}
if (hasRenotedBefore) {
if (hasRenotedBefore.value) {
buttonActions.push({
text: i18n.ts.unrenote,
icon: "ph-trash ph-bold ph-lg",
@ -245,7 +245,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
os.api("notes/unrenote", {
noteId: props.note.id,
});
hasRenotedBefore = false;
hasRenotedBefore.value = false;
},
});
}

View File

@ -15,6 +15,6 @@ export const lang = localStorage.getItem("lang");
export const langs = _LANGS_;
export const locale = JSON.parse(localStorage.getItem("locale"));
export const version = _VERSION_;
export const instanceName = siteName === "Calckey" ? host : siteName;
export const instanceName = siteName === "Magnetar" ? host : siteName;
export const ui = localStorage.getItem("ui");
export const debug = localStorage.getItem("debug") === "true";

View File

@ -145,11 +145,10 @@
</template>
<script lang="ts" setup>
import { computed, defineComponent, inject, watch } from "vue";
import { computed, watch } from "vue";
import MkButton from "@/components/MkButton.vue";
import * as os from "@/os";
import MkContainer from "@/components/MkContainer.vue";
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkGalleryPostPreview from "@/components/MkGalleryPostPreview.vue";
import MkFollowButton from "@/components/MkFollowButton.vue";

View File

@ -599,6 +599,8 @@ troubleshooting: "Poradce při potížích"
whatIsNew: "Zobrazit změny"
translate: "Přeložit"
hide: "Skrýt"
noAltText: "Chybějící popis obrázku"
noAltTextDescription: "Tento obrázek nemá alt text. Popis obrázku je užitečný pro zrakově-postižené uživatele a poskytuje lepší kontext pro všechny."
smartphone: "Telefon"
tablet: "Tablet"
auto: "Auto"

View File

@ -942,6 +942,9 @@ deleteAccountConfirm: "This will irreversibly delete your account. Proceed?"
incorrectPassword: "Incorrect password."
voteConfirm: "Confirm your vote for \"{choice}\"?"
hide: "Hide"
altText: "Alt text"
noAltText: "No alt text"
noAltTextDescription: "This image does not have alt text. Alt text is useful for vision impaired users, as well as for everyone else for context."
leaveGroup: "Leave group"
leaveGroupConfirm: "Are you sure you want to leave \"{name}\"?"
useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"

View File

@ -21,8 +21,10 @@ use std::sync::Arc;
use std::time::Duration;
use tera::Tera;
use thiserror::Error;
use tokio::signal;
use tower_http::services::ServeFile;
use tower_http::trace::TraceLayer;
use tracing::error;
use tracing::log::info;
use tracing_subscriber::EnvFilter;
@ -117,6 +119,33 @@ async fn main() -> miette::Result<()> {
info!("Serving on: {addr}");
axum::Server::bind(&addr)
.serve(app.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(|e| miette!("Error running server: {}", e))
}
async fn shutdown_signal() {
let ctrl_c = async {
if let Err(e) = signal::ctrl_c().await {
error!("Ctrl+C signal handler error: {}", e);
}
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("SIGTERM handler error")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
info!("Shutting down...");
}

View File

@ -0,0 +1,9 @@
[package]
name = "mmm_parser"
version.workspace = true
edition.workspace = true
license = "MIT OR Apache-2.0"
[dependencies]
thiserror = { workspace = true }
nom = { workspace = true }

View File

@ -0,0 +1,5 @@
# MMM
Magnetar {marinated, modified} Markdown?
#TODO: Finish docs

View File