Merge branch 'develop' of https://codeberg.org/calckey/calckey into mfm-warn

This commit is contained in:
Freeplay 2023-05-12 17:28:07 -04:00
commit 5dedd6b706
30 changed files with 309 additions and 599 deletions

38
.config/devenv.yml Normal file
View file

@ -0,0 +1,38 @@
url: http://localhost:3000
port: 3000
db:
host: 127.0.0.1
port: 5432
db: calckey
user: calckey
pass: calckey
redis:
host: localhost
port: 6379
family: 4
#sonic:
# host: localhost
# port: 1491
# auth: SecretPassword
# collection: notes
# bucket: default
#elasticsearch:
# host: localhost
# port: 9200
# ssl: false
# user:
# pass:
id: 'aid'
reservedUsernames:
- root
- admin
- administrator
- me
- system

1
.gitignore vendored
View file

@ -22,6 +22,7 @@ coverage
# config
/.config/*
!/.config/example.yml
!/.config/devenv.yml
!/.config/docker_example.env
!/.config/helm_values_example.yml

View file

@ -19,7 +19,6 @@
- MFM button
- Personal notes for all accounts
- Fully revamp non-logged-in screen
- Classic mode make instance icon bring up new context menu
- Lookup/details for post/file/instance
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
@ -118,6 +117,13 @@
- Sonic search
- Popular color schemes, including Nord, Gruvbox, and Catppuccin
- Non-nyaify cat mode
- Post imports from other Calckey/Misskey/Mastodon/Pleroma/Akkoma instances
- Improve Classic mode
- Proper Helm/Kubernetes config
- Multiple boost visibilities
- Improve system emails
- Mod mail
- Focus trapping and button labels
## Implemented (remote)

22
docs/development.md Normal file
View file

@ -0,0 +1,22 @@
# 🌎 Calckey Developer Docs
## Nix Dev Environment
The Calckey repo comes with a Nix-based shell environment to help make development as easy as possible!
Please note, however, that this environment will not work on Windows outside of a WSL2 environment.
### Prerequisites
- Installed the [Nix Package Manager](https://nixos.org/download.html)
- Installed [direnv](https://direnv.net/docs/installation.html) and added its hook to your shell.
Once the repo is cloned to your computer, follow these next few steps inside the Calckey folder:
- Run `direnv allow`. This will build the environment and install all needed tools.
- Run `install-deps`, then `prepare-config`, to install the node dependencies and prepare the needed config files.
- In a second terminal, run `devenv up`. This will spawn a **Redis** server, a **Postgres** server, and the **Calckey** server in dev mode.
- Once you see the Calckey banner printed in your second terminal, run `migrate` in the first.
- Once migrations finish, open http://localhost:3000 in your web browser.
- You should now see the admin user creation screen!
Note: When you want to restart a dev server, all you need to do is run `devenv up`, no other steps are necessary.

View file

@ -32,6 +32,8 @@
# Add additional packages to our environment
packages = [
pkgs.nodePackages.pnpm
pkgs.python3
];
# No need to warn on a new version, we'll update as needed.
devenv.warnOnNewVersion = false;
@ -43,6 +45,18 @@
# Enable stable Rust for the backend
languages.rust.enable = true;
languages.rust.version = "stable";
processes = {
dev-server.exec = "pnpm run dev";
};
scripts = {
build.exec = "pnpm run build";
clean.exec = "pnpm run clean";
clear-state.exec = "rm -rf .devenv/state/redis .devenv/state/postgres";
format.exec = "pnpm run format";
install-deps.exec = "pnpm install";
migrate.exec = "pnpm run migrate";
prepare-config.exec = "cp .config/devenv.yml .config/default.yml";
};
services = {
postgres = {
enable = true;

View file

@ -517,8 +517,8 @@ dayOverDayChanges: "Veränderung zu Gestern"
appearance: "Aussehen"
clientSettings: "Client-Einstellungen"
accountSettings: "Nutzerkonto-Einstellungen"
promotion: "Werbung"
promote: "Werbung schalten"
promotion: "geworben"
promote: "Werben"
numberOfDays: "Anzahl der Tage"
hideThisNote: "Diesen Beitrag verstecken"
showFeaturedNotesInTimeline: "Beliebte Beiträge in der Timeline anzeigen"
@ -852,7 +852,7 @@ gallery: "Bilder-Galerie"
recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge"
shareWithNote: "Mit Beitrag teilen"
ads: "Werbung"
ads: "Werbeanzeigen"
expiration: "Frist"
memo: "Merkzettel"
priority: "Priorität"
@ -1034,7 +1034,7 @@ _accountDelete:
inProgress: "Löschung in Bearbeitung"
_ad:
back: "Zurück"
reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
reduceFrequencyOfThisAd: "Diese Werbeanzeige weniger anzeigen"
_forgotPassword:
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese\
\ wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
@ -1174,6 +1174,10 @@ _mfm:
\ befindet."
foreground: Vordergrundfarbe
background: Hintergrundfarbe
positionDescription: Verschieben Sie Inhalte um einen bestimmten Betrag
position: Position
scale: Maßstab
scaleDescription: Skaliere den Inhalt um einen bestimmten Betrag.
_instanceTicker:
none: "Nie anzeigen"
remote: "Für Nutzer eines anderen Servers anzeigen"
@ -1989,3 +1993,51 @@ userSaysSomethingReasonReply: '{name} hat auf einen Beitrag geantwortet der {rea
beinhaltet'
userSaysSomethingReasonRenote: '{name} hat einen Beitrag geteilt der {reason} beinhaltet'
userSaysSomethingReasonQuote: '{name} hat einen Beitrag zitiert der {reason} beinhaltet'
seperateRenoteQuote: Getrennte Boost- und Zitat-Schaltflächen
showAds: Anzeigen anzeigen
splash: Begrüßungsbildschirm
customSplashIconsDescription: URLs für benutzerdefinierte Splash-Screen-Symbole, die
durch Zeilenumbrüche getrennt sind und nach dem Zufallsprinzip jedes Mal angezeigt
werden, wenn ein Benutzer die Seite lädt/neu lädt. Bitte stelle sicher, dass die
Bilder unter einer statischen URL stehen, vorzugsweise alle in der Größe 192x192.
sendPushNotificationReadMessageCaption: Eine Benachrichtigung mit dem Text "{emptyPushNotificationMessage}"
wird für kurze Zeit angezeigt. Dies kann ggf. den Akkuverbrauch Ihres Geräts erhöhen.
customSplashIcons: Benutzerdefinierte Begrüßungsbildschirmsymbole (URLs)
adminCustomCssWarn: Diese Einstellung sollte nur verwendet werden, wenn Sie wissen,
was sie tut. Die Eingabe falscher Werte kann dazu führen, dass ALLE Clients nicht
mehr normal funktionieren. Bitte stellen Sie sicher, dass Ihr CSS ordnungsgemäß
funktioniert, indem Sie es in Ihren Benutzereinstellungen testen.
customMOTD: Benutzerdefinierte Meldung des Tages (Begrüßungsbildschirmmeldungen)
allowedInstancesDescription: Hosts von Instanzen, die für den Verbund auf die Whitelist
gesetzt werden sollen, jeweils durch eine neue Zeile getrennt (gilt nur im privaten
Modus).
migration: Migration
updateAvailable: Es könnte eine Aktualisierung verfügbar sein!
showAdminUpdates: Anzeigen, dass eine neue Calckey-Version verfügbar ist (nur Administrator)
customMOTDDescription: Benutzerdefinierte Meldungen für die Meldung des Tages (Begrüßungsbildschirm),
die durch Zeilenumbrüche getrennt sind und nach dem Zufallsprinzip jedes Mal angezeigt
werden, wenn ein Benutzer die Seite (neu) lädt.
recommendedInstancesDescription: Empfohlene Instanzen, die durch Zeilenumbrüche getrennt
sind, werden in der empfohlenen Zeitachse angezeigt. Fügen Sie NICHT "https://"
hinzu, sondern NUR die Domain.
sendModMail: Moderationshinweis senden
moveFromDescription: 'Dadurch wird ein Alias Ihres alten Nutzerkontos festgelegt,
sodass Sie von ihrem bisherigen Konto zu diesem Nutzerkonto wechseln können. Tun
Sie dies, BEVOR Sie von Ihrem bisherigen Nutzerkonto hierhin wechseln. Bitte geben
Sie den Namen des Nutzerkontos wie folgt ein: person@server.xyz'
preventAiLearning: KI gestütztes bot-scraping unterdrücken
preventAiLearningDescription: Fordern Sie KI-Sprachmodelle von Drittanbietern auf,
die von Ihnen hochgeladenen Inhalte, wie z. B. Beiträge und Bilder, nicht zu untersuchen.
license: Genehmigung
indexPosts: Gelistete Beiträge
migrationConfirm: "Sind Sie absolut sicher, dass Sie Ihr Nutzerkonto zu diesem {account}\
\ umziehen möchten? Sobald Sie dies bestätigt haben, kann dies nicht mehr rückgängig\
\ gemacht werden und Ihr Nutzerkonto kann nicht mehr von ihnen genutzt werden.\n\
Stellen Sie außerdem sicher, dass Sie dieses Nutzerkonto als das Konto festgelegt\
\ haben, von dem Sie umziehen."
noteId: Beitrags-ID
customKaTeXMacro: Individuelle KaTeX Makros
enableCustomKaTeXMacro: Individuelle KaTeX-Makros aktivieren
replayTutorial: Wiederhole die Benutzeranleitung
apps: Apps
caption: Automatische Untertitelung

View file

@ -1085,7 +1085,7 @@ apps: "Apps"
sendModMail: "Send Moderation Notice"
preventAiLearning: "Prevent AI bot scraping"
preventAiLearningDescription: "Request third-party AI language models not to study content you upload, such as posts and images."
pwa: "Install PWA"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing\

View file

@ -975,6 +975,7 @@ customKaTeXMacroDescription: "数式入力を楽にするためのマクロを
enableCustomKaTeXMacro: "カスタムKaTeXマクロを有効にする"
preventAiLearning: "AIによる学習を防止"
preventAiLearningDescription: "投稿したート、添付した画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。"
pwa: "PWAをインストール"
_sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"

View file

@ -1921,7 +1921,7 @@ sendErrorReportsDescription: "Gdy ta opcja jest włączona, szczegółowe inform
\ Calckey.\nZawrze to informacje takie jak wersja twojego systemu operacyjnego,\
\ przeglądarki, Twoja aktywność na Calckey itd."
privateModeInfo: Jeśli włączone, tylko dopuszczone instancje będą mogły federować
z Twoją instancją. Wszystkie posty będą ukryte przed publiką.
z Twoją instancją. Wszystkie posty będą jedynie widoczne na Twojej instancji.
oneHour: Godzina
oneDay: Dzień
oneWeek: Tydzień
@ -2012,3 +2012,4 @@ silencedInstancesDescription: Wymień nazwy domenowe instancji, które chcesz wy
są obserwowane. To nie będzie miało wpływu na zablokowane instancje.
cannotUploadBecauseExceedsFileSizeLimit: Ten plik nie mógł być przesłany, ponieważ
jego wielkość przekracza dozwolony limit.
sendModMail: Wyślij Powiadomienie Moderacyjne

View file

@ -1,12 +1,12 @@
{
"name": "calckey",
"version": "14.0.0-dev8",
"version": "14.0.0-dev9",
"codename": "aqua",
"repository": {
"type": "git",
"url": "https://codeberg.org/calckey/calckey.git"
},
"packageManager": "pnpm@8.4.0",
"packageManager": "pnpm@8.5.0",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",

View file

@ -1,5 +1,5 @@
export class PreventAiLarning1683682889948 {
name = 'PreventAiLarning1683682889948'
export class PreventAiLearning1683682889948 {
name = 'PreventAiLearning1683682889948'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLearning" boolean NOT NULL DEFAULT true`);

View file

@ -1,4 +1,4 @@
html, body {
html {
background-color: var(--bg);
color: var(--fg);
}

View file

@ -9,6 +9,7 @@
},
"devDependencies": {
"@discordapp/twemoji": "14.0.2",
"@khmyznikov/pwa-install": "^0.2.0",
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0",

View file

@ -144,6 +144,10 @@ export default defineComponent({
padding: var(--x-padding);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(20px));
margin-inline: -12px;
padding-inline: 12px;
mask: linear-gradient(to right, transparent, black 12px calc(100% - 12px), transparent);
-webkit-mask: linear-gradient(to right, transparent, black 12px calc(100% - 12px), transparent);
> .title {
margin: 0;

View file

@ -58,13 +58,12 @@ defineExpose({
.giivymft {
&.noGap {
> .notes {
background: var(--panel);
background: var(--panel) !important;
border-radius: var(--radius);
}
}
&:not(.noGap) {
> .notes {
background: var(--bg);
.qtqtichx {
background: var(--panel);
border-radius: var(--radius);

View file

@ -180,6 +180,13 @@
primary
show-only-to-register
/>
<pwa-install />
<MkButton
primary
inline
@click="installPwa"
>{{ i18n.ts.pwa }}</MkButton
>
</div>
</Transition>
</div>
@ -201,6 +208,7 @@ import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
import { $i } from "@/account";
import { instance } from "@/instance";
import "@khmyznikov/pwa-install";
const isLocalTimelineAvailable =
!instance.disableLocalTimeline ||
@ -243,6 +251,11 @@ const tutorial = computed({
},
});
function installPwa(ev: MouseEvent) {
const pwaInstall = document.getElementsByTagName("pwa-install")[0];
pwaInstall.showDialog();
}
function close(res) {
tutorial.value = -1;
dialog.close();

View file

@ -158,7 +158,7 @@ definePageMetadata(
}
> .tl {
background: var(--bg);
background: none;
border-radius: var(--radius);
overflow: clip;
}

View file

@ -218,7 +218,9 @@ definePageMetadata(
}
.fcuexfpr {
background: var(--bg);
#calckey_app > :not(.wallpaper) & {
background: var(--bg);
}
> .note {
> .main {

View file

@ -391,7 +391,7 @@ onMounted(() => {
}
> .tl {
background: var(--bg);
background: none;
border-radius: var(--radius);
overflow: clip;
}

View file

@ -125,7 +125,7 @@ definePageMetadata(
}
> .tl {
background: var(--bg);
background: none;
border-radius: var(--radius);
overflow: clip;
}

View file

@ -4,6 +4,7 @@ import { host } from "@/config";
import * as os from "@/os";
import XTutorial from "../components/MkTutorialDialog.vue";
import { i18n } from "@/i18n";
import "@khmyznikov/pwa-install";
export function openHelpMenu_(ev: MouseEvent) {
os.popupMenu(
@ -24,6 +25,14 @@ export function openHelpMenu_(ev: MouseEvent) {
icon: "ph-lightbulb ph-bold ph-lg",
to: "/about-calckey",
},
{
type: "button",
text: i18n.ts.pwa,
icon: "ph-plus-square ph-bold ph-lg",
action: () => {
document.getElementsByTagName("pwa-install")[0].showDialog();
}
},
{
type: "button",
text: i18n.ts.apps,

View file

@ -40,7 +40,6 @@
html {
touch-action: manipulation;
background-color: var(--bg);
background-attachment: fixed;
background-size: cover;
background-position: center;

View file

@ -105,6 +105,7 @@
<i class="icon ph-pencil ph-bold ph-lg ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.note }}</span>
</button>
<pwa-install />
<button
v-tooltip.noDelay.right="i18n.ts.help"
class="item _button help"
@ -132,6 +133,7 @@ import { openAccountMenu as openAccountMenu_ } from "@/account";
import { openHelpMenu_ } from "@/scripts/helpMenu";
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
import "@khmyznikov/pwa-install";
const menu = toRef(defaultStore.state, "menu");
const otherMenuItemIndicated = computed(() => {

View file

@ -1,5 +1,5 @@
<template>
<header class="mvcprjjd" :class="{ iconOnly }">
<header class="mvcprjjd sidebar" :class="{ iconOnly }">
<div class="body">
<div class="top">
<div
@ -122,6 +122,7 @@
<i class="icon ph-pencil ph-bold ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.note }}</span>
</button>
<pwa-install />
<button
v-tooltip.noDelay.right="i18n.ts.help"
class="item _button help"
@ -150,6 +151,7 @@ import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
import { instance } from "@/instance";
import { version } from "@/config";
import "@khmyznikov/pwa-install";
const isEmpty = (x: string | null) => x == null || x === "";
@ -236,23 +238,22 @@ function more(ev: MouseEvent) {
.mvcprjjd {
$nav-width: 250px;
$nav-icon-only-width: 80px;
flex: 0 0 $nav-width;
width: $nav-width;
box-sizing: border-box;
> .body {
position: fixed;
position: sticky;
top: 0;
left: 0;
z-index: 1001;
width: $nav-icon-only-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
overflow: auto;
overflow-x: clip;
background: var(--navBg);
#calckey_app > :not(.wallpaper) & {
background: var(--navBg);
}
contain: strict;
display: flex;
flex-direction: column;
@ -260,17 +261,15 @@ function more(ev: MouseEvent) {
&:not(.iconOnly) {
> .body {
margin-left: -200px;
padding-left: 200px;
box-sizing: content-box;
width: $nav-width;
> .top {
position: sticky;
top: 0;
position: relative;
z-index: 1;
padding: 2rem 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .banner {
position: absolute;
top: 0;
@ -298,12 +297,7 @@ function more(ev: MouseEvent) {
}
> .bottom {
position: sticky;
bottom: 0;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .post {
position: relative;
@ -474,13 +468,7 @@ function more(ev: MouseEvent) {
width: $nav-icon-only-width;
> .top {
position: sticky;
top: 0;
z-index: 1;
padding: 2rem 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .account {
display: block;
@ -497,12 +485,7 @@ function more(ev: MouseEvent) {
}
> .bottom {
position: sticky;
bottom: 0;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .post {
display: block;

View file

@ -1,409 +0,0 @@
<template>
<div class="npcljfve" :class="{ iconOnly }">
<button
v-click-anime
class="item _button account"
@click="openAccountMenu"
>
<MkAvatar :user="$i" class="avatar" disableLink /><MkAcct
class="text"
:user="$i"
/>
</button>
<div class="post" data-cy-open-post-form @click="post">
<MkButton class="button" gradate full rounded>
<i class="ph-pencil ph-bold ph-lg ph-fw ph-lg"></i
><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span>
</MkButton>
</div>
<div class="divider"></div>
<MkA
v-click-anime
class="item index"
active-class="active"
to="/"
exact
>
<i class="ph-house ph-bold ph-lg ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component
:is="navbarItemDef[item].to ? 'MkA' : 'button'"
v-else-if="
navbarItemDef[item] && navbarItemDef[item].show !== false
"
v-click-anime
class="item _button"
:class="item"
active-class="active"
:to="navbarItemDef[item].to"
v-on="
navbarItemDef[item].action
? { click: navbarItemDef[item].action }
: {}
"
>
<i class="ph-fw ph-lg" :class="navbarItemDef[item].icon"></i
><span class="text">{{ $ts[navbarItemDef[item].title] }}</span>
<span v-if="navbarItemDef[item].indicated" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</component>
</template>
<div class="divider"></div>
<MkA
v-if="$i.isAdmin || $i.isModerator"
v-click-anime
class="item"
active-class="active"
to="/admin"
:behavior="settingsWindowed ? 'modalWindow' : null"
>
<i class="ph-door ph-bold ph-lg ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="ph-dots-three-outline ph-bold ph-lg ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.more }}</span>
<span v-if="otherNavItemIndicated" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</button>
<MkA
v-click-anime
class="item"
active-class="active"
to="/settings"
:behavior="settingsWindowed ? 'modalWindow' : null"
>
<i class="ph-gear-six ph-bold ph-lg ph-fw ph-lg"></i
><span class="text">{{ i18n.ts.settings }}</span>
</MkA>
<div class="divider"></div>
<div class="about">
<MkA v-click-anime class="link" @click="openInstanceMenu">
<img
:src="
$instance.iconUrl ||
$instance.faviconUrl ||
'/favicon.ico'
"
class="_ghost"
/>
</MkA>
</div>
<!--<MisskeyLogo class="misskey"/>-->
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from "vue";
import { host } from "@/config";
import { search } from "@/scripts/search";
import * as os from "@/os";
import { navbarItemDef } from "@/navbar";
import { openAccountMenu } from "@/account";
import MkButton from "@/components/MkButton.vue";
import { StickySidebar } from "@/scripts/sticky-sidebar";
//import MisskeyLogo from '@assets/client/misskey.svg';
import { i18n } from "@/i18n";
export default defineComponent({
components: {
MkButton,
//MisskeyLogo,
},
data() {
return {
host: host,
accounts: [],
connection: null,
navbarItemDef: navbarItemDef,
iconOnly: false,
settingsWindowed: false,
i18n,
};
},
computed: {
menu(): string[] {
return this.$store.state.menu;
},
otherNavItemIndicated(): boolean {
for (const def in this.navbarItemDef) {
if (this.menu.includes(def)) continue;
if (this.navbarItemDef[def].indicated) return true;
}
return false;
},
},
watch: {
"$store.reactiveState.menuDisplay.value"() {
this.calcViewState();
},
iconOnly() {
this.$nextTick(() => {
this.$emit("change-view-mode");
});
},
},
created() {
window.addEventListener("resize", this.calcViewState);
this.calcViewState();
},
mounted() {
const sticky = new StickySidebar(this.$el.parentElement, 16);
window.addEventListener(
"scroll",
() => {
sticky.calc(window.scrollY);
},
{ passive: true }
);
},
methods: {
calcViewState() {
this.iconOnly =
window.innerWidth <= 1400 ||
this.$store.state.menuDisplay === "sideIcon";
this.settingsWindowed = window.innerWidth > 1400;
},
post() {
os.post();
},
search() {
search();
},
more(ev) {
os.popup(
defineAsyncComponent(
() => import("@/components/MkLaunchPad.vue")
),
{
src: ev.currentTarget ?? ev.target,
},
{},
"closed"
);
},
openAccountMenu: (ev) => {
openAccountMenu(
{
withExtraOperation: true,
},
ev
);
},
},
});
function openInstanceMenu(ev: MouseEvent) {
os.popupMenu(
[
{
text: instance.name ?? host,
type: "label",
},
{
type: "link",
text: i18n.ts.instanceInfo,
icon: "ph-info ph-bold ph-lg",
to: "/about",
},
null,
{
type: "parent",
text: i18n.ts.help,
icon: "ph-question ph-bold ph-lg",
children: [
{
type: "link",
to: "/mfm-cheat-sheet",
text: i18n.ts._mfm.cheatSheet,
icon: "ph-code ph-bold ph-lg",
},
{
type: "link",
to: "/scratchpad",
text: i18n.ts.scratchpad,
icon: "ph-terminal-window ph-bold ph-lg",
},
{
type: "link",
to: "/api-console",
text: "API Console",
icon: "ph-terminal-window ph-bold ph-lg",
},
null,
{
text: i18n.ts.document,
icon: "ph-question ph-bold ph-lg",
action: () => {
window.open(
"https://misskey-hub.net/help.html",
"_blank"
);
},
},
],
},
{
type: "link",
text: i18n.ts.aboutMisskey,
to: "/about-calckey",
},
],
ev.currentTarget ?? ev.target,
{
align: "left",
}
);
}
</script>
<style lang="scss" scoped>
.npcljfve {
$ui-font-size: 1em; // TODO:
$nav-icon-only-width: 78px; // TODO:
$avatar-size: 32px;
$avatar-margin: 8px;
padding: 0 16px;
box-sizing: border-box;
width: 260px;
&.iconOnly {
flex: 0 0 $nav-icon-only-width;
width: $nav-icon-only-width !important;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
}
> .post {
> .button {
width: 46px;
height: 46px;
padding: 0;
margin-inline: 0 !important;
}
}
> .item {
padding-left: 0;
width: 100%;
text-align: center;
font-size: $ui-font-size * 1.1;
line-height: 3.7rem;
> i,
> .avatar {
margin-right: 0;
}
> i {
left: 10px;
}
> .text {
display: none;
}
}
}
> .divider {
margin: 10px 0;
border-top: solid 0.5px var(--divider);
}
> .post {
position: sticky;
top: 0;
z-index: 1;
padding: 16px 0;
background: var(--bg);
> .button {
min-width: 0;
}
}
> .about {
fill: currentColor;
padding: 8px 0 16px 0;
text-align: center;
> .link {
display: block;
width: 32px;
margin: 0 auto;
img {
display: block;
width: 100%;
}
}
}
> .item {
position: relative;
display: block;
font-size: $ui-font-size;
line-height: 2.6rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
> i {
width: 32px;
justify-content: center;
}
> i,
> .avatar {
margin-right: $avatar-margin;
}
> .avatar {
width: $avatar-size;
height: $avatar-size;
vertical-align: middle;
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
&:hover {
text-decoration: none;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
}
}
</style>

View file

@ -13,9 +13,7 @@
class="columns"
:class="{ fullView, withGlobalHeader: showMenuOnTop }"
>
<div v-if="!showMenuOnTop" class="sidebar">
<XSidebar />
</div>
<XSidebar v-if="!showMenuOnTop"/>
<div v-else ref="widgetsLeft" class="widgets left">
<XWidgets
:place="'left'"
@ -57,7 +55,7 @@
<script lang="ts" setup>
import { defineAsyncComponent, markRaw, ref, onMounted, provide } from "vue";
import XSidebar from "./classic.sidebar.vue";
import XSidebar from "./_common_/navbar.vue";
import XCommon from "./_common_/common.vue";
import type { ComputedRef } from "vue";
import type { PageMetadata } from "@/scripts/page-metadata";
@ -72,7 +70,7 @@ import {
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
const XHeaderMenu = defineAsyncComponent(() => import("./classic.header.vue"));
const XWidgets = defineAsyncComponent(() => import("./classic.widgets.vue"));
const XWidgets = defineAsyncComponent(() => import("./universal.widgets.vue"));
const DESKTOP_THRESHOLD = 1100;
@ -101,7 +99,7 @@ provide("shouldSpacerMin", true);
function attachSticky(el) {
const sticky = new StickySidebar(
el,
defaultStore.state.menuDisplay === "top" ? 0 : 16,
defaultStore.state.menuDisplay === 0,
defaultStore.state.menuDisplay === "top" ? 60 : 0
); // TODO: 60px
window.addEventListener(
@ -236,9 +234,18 @@ onMounted(() => {
min-height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
--navBg: transparent;
--X14: var(--acrylicBg);
&.wallpaper {
background: var(--wallpaperOverlay);
//backdrop-filter: var(--blur, blur(4px));
:deep(main) {
background: var(--acrylicBg) !important;
backdrop-filter: blur(12px);
}
:deep(.tl), :deep(.notes) {
background: none !important;
}
}
> .columns {
@ -250,7 +257,7 @@ onMounted(() => {
&.fullView {
margin: 0;
> .sidebar {
> :deep(.sidebar) {
display: none;
}
@ -266,6 +273,50 @@ onMounted(() => {
}
}
> :deep(.sidebar) {
margin-left: -200px;
padding-left: 200px;
box-sizing: content-box;
.banner {
pointer-events: none;
mask: radial-gradient(farthest-side at top, hsl(0, 0%, 0%) 0%,
hsla(0, 0%, 0%, 0.987) 0.3%,
hsla(0, 0%, 0%, 0.951) 1.4%,
hsla(0, 0%, 0%, 0.896) 3.2%,
hsla(0, 0%, 0%, 0.825) 5.8%,
hsla(0, 0%, 0%, 0.741) 9.3%,
hsla(0, 0%, 0%, 0.648) 13.6%,
hsla(0, 0%, 0%, 0.55) 18.9%,
hsla(0, 0%, 0%, 0.45) 25.1%,
hsla(0, 0%, 0%, 0.352) 32.4%,
hsla(0, 0%, 0%, 0.259) 40.7%,
hsla(0, 0%, 0%, 0.175) 50.2%,
hsla(0, 0%, 0%, 0.104) 60.8%,
hsla(0, 0%, 0%, 0.049) 72.6%,
hsla(0, 0%, 0%, 0.013) 85.7%,
hsla(0, 0%, 0%, 0) 100%) !important;
-webkit-mask: radial-gradient(farthest-side at top, hsl(0, 0%, 0%) 0%,
hsla(0, 0%, 0%, 0.987) 0.3%,
hsla(0, 0%, 0%, 0.951) 1.4%,
hsla(0, 0%, 0%, 0.896) 3.2%,
hsla(0, 0%, 0%, 0.825) 5.8%,
hsla(0, 0%, 0%, 0.741) 9.3%,
hsla(0, 0%, 0%, 0.648) 13.6%,
hsla(0, 0%, 0%, 0.55) 18.9%,
hsla(0, 0%, 0%, 0.45) 25.1%,
hsla(0, 0%, 0%, 0.352) 32.4%,
hsla(0, 0%, 0%, 0.259) 40.7%,
hsla(0, 0%, 0%, 0.175) 50.2%,
hsla(0, 0%, 0%, 0.104) 60.8%,
hsla(0, 0%, 0%, 0.049) 72.6%,
hsla(0, 0%, 0%, 0.013) 85.7%,
hsla(0, 0%, 0%, 0) 100%) !important;
width: 125% !important;
left: -12.5% !important;
height: 125% !important;
}
}
> .main {
min-width: 0;
width: 750px;
@ -282,7 +333,6 @@ onMounted(() => {
> .widgets {
//--panelBorder: none;
width: 300px;
margin-top: 16px;
@media (max-width: $widgets-hide-threshold) {
display: none;
@ -293,10 +343,6 @@ onMounted(() => {
}
}
> .sidebar {
margin-top: 16px;
}
&.withGlobalHeader {
> .main {
margin-top: 0;
@ -314,7 +360,7 @@ onMounted(() => {
@media (max-width: 850px) {
margin: 0;
> .sidebar {
> :deep(.sidebar) {
border-right: solid 0.5px var(--divider);
}

View file

@ -1,127 +0,0 @@
<template>
<div class="ddiqwdnk">
<MkAd class="a" :prefer="['widget']" />
<XWidgets
class="widgets"
:edit="editMode"
:widgets="
$store.reactiveState.widgets.value.filter(
(w) => w.place === place
)
"
@add-widget="addWidget"
@remove-widget="removeWidget"
@update-widget="updateWidget"
@update-widgets="updateWidgets"
@exit="editMode = false"
/>
<button
v-if="editMode"
class="_textButton edit"
style="font-size: 0.9em"
@click="editMode = false"
>
<i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.editWidgetsExit }}
</button>
<button
v-else
class="_textButton edit"
style="font-size: 0.9em"
@click="editMode = true"
>
<i class="ph-pencil ph-bold ph-lg"></i> {{ i18n.ts.editWidgets }}
</button>
</div>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
import XWidgets from "@/components/MkWidgets.vue";
import { i18n } from "@/i18n";
export default defineComponent({
components: {
XWidgets,
},
props: {
place: {
type: String,
},
},
emits: ["mounted"],
data() {
return {
editMode: false,
i18n,
};
},
mounted() {
this.$emit("mounted", this.$el);
},
methods: {
addWidget(widget) {
this.$store.set("widgets", [
{
...widget,
place: this.place,
},
...this.$store.state.widgets,
]);
},
removeWidget(widget) {
this.$store.set(
"widgets",
this.$store.state.widgets.filter((w) => w.id !== widget.id)
);
},
updateWidget({ id, data }) {
this.$store.set(
"widgets",
this.$store.state.widgets.map((w) =>
w.id === id
? {
...w,
data,
}
: w
)
);
},
updateWidgets(widgets) {
this.$store.set("widgets", [
...this.$store.state.widgets.filter(
(w) => w.place !== this.place
),
...widgets,
]);
},
},
});
</script>
<style lang="scss" scoped>
.ddiqwdnk {
position: sticky;
height: min-content;
box-sizing: border-box;
padding-bottom: 8px;
> .widgets,
> .a {
width: 300px;
}
> .edit {
display: block;
margin: 16px auto;
}
}
</style>

View file

@ -323,6 +323,8 @@ function onDrop(ev) {
overflow: hidden;
contain: strict;
background: var(--bg);
&.draghover {
&:after {
content: "";

View file

@ -464,13 +464,11 @@ console.log(mainRouter.currentRoute.value.name);
> .contents {
width: 100%;
min-width: 0;
background: var(--bg);
}
> .widgets {
padding: 0 var(--margin);
border-left: solid 0.5px var(--divider);
background: var(--bg);
@media (max-width: $widgets-hide-threshold) {
display: none;

View file

@ -659,6 +659,9 @@ importers:
'@discordapp/twemoji':
specifier: 14.0.2
version: 14.0.2
'@khmyznikov/pwa-install':
specifier: ^0.2.0
version: 0.2.0
'@phosphor-icons/web':
specifier: ^2.0.3
version: 2.0.3
@ -2094,6 +2097,13 @@ packages:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
/@khmyznikov/pwa-install@0.2.0:
resolution: {integrity: sha512-Lz8lvCf47/DCVzXgLyCGt46HDpie2U/lga++vOgVwil3B8bSMj8fL1+a/8oBhDikHjtLKinTCMf0jZvgW2xCZQ==}
dependencies:
'@lit/localize': 0.11.4
lit: 2.7.4
dev: true
/@koa/cors@3.4.3:
resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==}
engines: {node: '>= 8.0.0'}
@ -2127,6 +2137,23 @@ packages:
resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
dev: true
/@lit-labs/ssr-dom-shim@1.1.1:
resolution: {integrity: sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==}
dev: true
/@lit/localize@0.11.4:
resolution: {integrity: sha512-RRIwIX2tAm3+DuEndoXSJrFjGrAK5cb5IXo5K6jcJ6sbgD829B8rSqHC5MaKVUmXTVLIR1bk5IZOZDf9wFereA==}
dependencies:
'@lit/reactive-element': 1.6.1
lit: 2.7.4
dev: true
/@lit/reactive-element@1.6.1:
resolution: {integrity: sha512-va15kYZr7KZNNPZdxONGQzpUr+4sxVu7V/VG7a8mRfPPXUyhEYj5RzXCQmGrlP3tAh0L3HHm5AjBMFYRqlM9SA==}
dependencies:
'@lit-labs/ssr-dom-shim': 1.1.1
dev: true
/@mapbox/node-pre-gyp@1.0.10:
resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==}
hasBin: true
@ -3582,6 +3609,10 @@ packages:
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
dev: true
/@types/trusted-types@2.0.3:
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
dev: true
/@types/undertaker-registry@1.0.1:
resolution: {integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==}
dev: true
@ -10073,6 +10104,28 @@ packages:
wrap-ansi: 7.0.0
dev: true
/lit-element@3.3.2:
resolution: {integrity: sha512-xXAeVWKGr4/njq0rGC9dethMnYCq5hpKYrgQZYTzawt9YQhMiXfD+T1RgrdY3NamOxwq2aXlb0vOI6e29CKgVQ==}
dependencies:
'@lit-labs/ssr-dom-shim': 1.1.1
'@lit/reactive-element': 1.6.1
lit-html: 2.7.4
dev: true
/lit-html@2.7.4:
resolution: {integrity: sha512-/Jw+FBpeEN+z8X6PJva5n7+0MzCVAH2yypN99qHYYkq8bI+j7I39GH+68Z/MZD6rGKDK9RpzBw7CocfmHfq6+g==}
dependencies:
'@types/trusted-types': 2.0.3
dev: true
/lit@2.7.4:
resolution: {integrity: sha512-cgD7xrZoYr21mbrkZIuIrj98YTMw/snJPg52deWVV4A8icLyNHI3bF70xsJeAgwTuiq5Kkd+ZR8gybSJDCPB7g==}
dependencies:
'@lit/reactive-element': 1.6.1
lit-element: 3.3.2
lit-html: 2.7.4
dev: true
/load-json-file@1.1.0:
resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==}
engines: {node: '>=0.10.0'}