Merge branch 'develop' of https://codeberg.org/calckey/calckey into notifications
This commit is contained in:
commit
3a92e77c6c
36 changed files with 494 additions and 314 deletions
35
README.md
35
README.md
|
@ -82,8 +82,8 @@ If you have access to a server that supports one of the sources below, I recomme
|
|||
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommend)
|
||||
- Web Proxy (one of the following)
|
||||
- 🍀 Nginx (recommended)
|
||||
- 🪶 Apache
|
||||
- 🦦 Caddy
|
||||
- 🪶 Apache
|
||||
|
||||
### 😗 Optional dependencies
|
||||
|
||||
|
@ -107,7 +107,8 @@ git clone --depth 1 https://codeberg.org/calckey/calckey.git
|
|||
cd calckey/
|
||||
```
|
||||
|
||||
By default, you're on the development branch. Run `git checkout beta` or `git checkout main` to switch to the Beta/Main branches.
|
||||
> **Note**
|
||||
> By default, you're on the main branch. Run `git checkout beta` or `git checkout develop` to switch to the Beta/Develop branches.
|
||||
|
||||
## 📩 Install dependencies
|
||||
|
||||
|
@ -128,11 +129,18 @@ npm i -g pm2
|
|||
pm2 install pm2-logrotate
|
||||
```
|
||||
|
||||
[`pm2-logrotate`](https://github.com/keymetrics/pm2-logrotate/blob/master/README.md) ensures that log files don't infinitely gather size, as Calckey produces a lot of logs.
|
||||
> **Note**
|
||||
> [`pm2-logrotate`](https://github.com/keymetrics/pm2-logrotate/blob/master/README.md) ensures that log files don't infinitely gather size, as Calckey produces a lot of logs.
|
||||
|
||||
## 🐘 Create database
|
||||
|
||||
Assuming you set up PostgreSQL correctly, all you have to run is:
|
||||
In PostgreSQL (`psql`), run the following command:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE calckey WITH encoding = 'UTF8';
|
||||
```
|
||||
|
||||
or run the following from the command line:
|
||||
|
||||
```sh
|
||||
psql postgres -c "create database calckey with encoding = 'UTF8';"
|
||||
|
@ -144,7 +152,8 @@ In Calckey's directory, fill out the `db` section of `.config/default.yml` with
|
|||
|
||||
Follow sonic's [installation guide](https://github.com/valeriansaliou/sonic#installation)
|
||||
|
||||
If you use IPv4: in Sonic's directory, edit the `config.cfg` file to change `inet` to `"0.0.0.0:1491"`.
|
||||
> **Note**
|
||||
> If you use IPv4: in Sonic's directory, edit the `config.cfg` file to change `inet` to `"0.0.0.0:1491"`.
|
||||
|
||||
In Calckey's directory, fill out the `sonic` section of `.config/default.yml` with the correct information.
|
||||
|
||||
|
@ -177,13 +186,6 @@ For migrating from Misskey v13, Misskey v12, and Foundkey, read [this document](
|
|||
- Run `sudo ln -s ./calckey.nginx.conf ../sites-enabled/calckey.nginx.conf`
|
||||
- Run `sudo nginx -t` to validate that the config is valid, then restart the NGINX service.
|
||||
|
||||
### 🪶 Apache
|
||||
|
||||
- Run `sudo cp ./calckey.apache.conf /etc/apache2/sites-available/ && cd /etc/apache2/sites-available/`
|
||||
- Edit `calckey.apache.conf` to reflect your instance properly
|
||||
- Run `sudo a2ensite calckey.apache` to enable the site
|
||||
- Run `sudo service apache2 restart` to reload apache2 configuration
|
||||
|
||||
### 🦦 Caddy
|
||||
|
||||
- Add the following block to your `Caddyfile`, replacing `example.tld` with your own domain:
|
||||
|
@ -194,6 +196,15 @@ example.tld {
|
|||
```
|
||||
- Reload your caddy configuration
|
||||
|
||||
### 🪶 Apache
|
||||
|
||||
> **Warning**
|
||||
> Apache has some known problems with Calckey. Only use it if you have to.
|
||||
|
||||
- Run `sudo cp ./calckey.apache.conf /etc/apache2/sites-available/ && cd /etc/apache2/sites-available/`
|
||||
- Edit `calckey.apache.conf` to reflect your instance properly
|
||||
- Run `sudo a2ensite calckey.apache` to enable the site
|
||||
- Run `sudo service apache2 restart` to reload apache2 configuration
|
||||
## 🚀 Build and launch!
|
||||
|
||||
### 🐢 NodeJS + pm2
|
||||
|
|
|
@ -1290,7 +1290,7 @@ loadRawImages: Carregar les imatges originals en comptes de mostrar les miniatur
|
|||
noteFavoritesCount: Nombre de notes afegides a favorits
|
||||
useSystemFont: Fes servir la font per defecte del sistema
|
||||
contact: Contacte
|
||||
clips: Clips
|
||||
clips: Retalls
|
||||
experimentalFeatures: Característiques experimentals
|
||||
developer: Desenvolupador
|
||||
makeExplorableDescription: Si desactives aquesta funció el teu compte no sortirà a
|
||||
|
@ -1496,7 +1496,7 @@ gallery: Galeria
|
|||
popularPosts: Pàgines populars
|
||||
shareWithNote: Comparteix amb una publicació
|
||||
expiration: Data límit
|
||||
memo: Memo
|
||||
memo: Recordatori
|
||||
priority: Prioritat
|
||||
high: Alta
|
||||
middle: Mitjana
|
||||
|
@ -1537,7 +1537,7 @@ incorrectPassword: Contrasenya incorrecta.
|
|||
clickToFinishEmailVerification: Feu clic a [{ok}] per completar la verificació del
|
||||
correu electrònic.
|
||||
overridedDeviceKind: Tipus de dispositiu
|
||||
smartphone: Smartphone
|
||||
smartphone: Telèfon intel·ligent
|
||||
tablet: Tauleta
|
||||
auto: Automàtic
|
||||
recentNHours: Últimes {n} hores
|
||||
|
@ -1625,7 +1625,7 @@ customKaTeXMacroDescription: "Configura macros per escriure expressions matemàt
|
|||
objectStorageRegion: Regió
|
||||
objectStoragePrefix: Prefix
|
||||
objectStoragePrefixDesc: Els fitxers es guardaran dins de carpetes amb aquest prefix.
|
||||
objectStorageEndpoint: Endpoint
|
||||
objectStorageEndpoint: Extrem
|
||||
newNoteRecived: Hi han notes noves
|
||||
sounds: Sons
|
||||
listen: Escoltar
|
||||
|
@ -1704,7 +1704,9 @@ oneWeek: Una setmana
|
|||
reflectMayTakeTime: Pot trigar una mica a reflectir-se.
|
||||
thereIsUnresolvedAbuseReportWarning: Hi ha informes sense resoldre.
|
||||
driveCapOverrideLabel: Canvieu la capacitat del disc per a aquest usuari
|
||||
isSystemAccount: Un compte creat i operat automàticament pel sistema.
|
||||
isSystemAccount: Aquest compte és creat i operat automàticament pel sistema. Si us
|
||||
plau, no modereu, editeu, suprimiu o modifiqueu aquest compte de cap forma, o podria
|
||||
trencar el vostre servidor.
|
||||
typeToConfirm: Introduïu {x} per confirmar
|
||||
deleteAccount: Suprimeix el compte
|
||||
document: Documentació
|
||||
|
@ -1746,7 +1748,7 @@ reverse: Revés
|
|||
objectStorageBucket: Cubell
|
||||
objectStorageBucketDesc: Si us plau específica el nom del cubell que faràs servir
|
||||
al teu proveïdor.
|
||||
clip: Clip
|
||||
clip: Retall
|
||||
createNew: Crear una nova
|
||||
optional: Opcional
|
||||
jumpToSpecifiedDate: Vés a una data concreta
|
||||
|
@ -2056,3 +2058,5 @@ newer: Més nou
|
|||
older: Més antic
|
||||
silencedWarning: S'està mostrant aquesta pàgina per què aquest usuari és d'un servidor
|
||||
que l'administrador a silenciat, així que pot ser spam.
|
||||
jumpToPrevious: Vés a l'anterior
|
||||
cw: Avís de contingut
|
||||
|
|
|
@ -1171,6 +1171,8 @@ _mfm:
|
|||
sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt."
|
||||
rotate: "Drehen"
|
||||
rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel."
|
||||
fade: "Ein-/Ausblenden"
|
||||
fadeDescription: "Blended Inhalt ein and aus."
|
||||
plain: "Schlicht"
|
||||
plainDescription: "Deaktiviert jegliche MFM-Syntax, die sich innerhalb dieses MFM-Effekts\
|
||||
\ befindet."
|
||||
|
|
|
@ -57,7 +57,7 @@ sendMessage: "Send a message"
|
|||
copyUsername: "Copy username"
|
||||
searchUser: "Search for a user"
|
||||
reply: "Reply"
|
||||
jumpToReply: "Jump to Reply"
|
||||
jumpToPrevious: "Jump to previous"
|
||||
loadMore: "Load more"
|
||||
showMore: "Show more"
|
||||
newer: "newer"
|
||||
|
@ -69,6 +69,7 @@ followRequestAccepted: "Follow request accepted"
|
|||
mention: "Mention"
|
||||
mentions: "Mentions"
|
||||
directNotes: "Direct messages"
|
||||
cw: "Content warning"
|
||||
importAndExport: "Import/Export Data"
|
||||
import: "Import"
|
||||
export: "Export"
|
||||
|
@ -971,7 +972,7 @@ driveCapOverrideLabel: "Change the drive capacity for this user"
|
|||
driveCapOverrideCaption: "Reset the capacity to default by inputting a value of 0\
|
||||
\ or lower."
|
||||
requireAdminForView: "You must log in with an administrator account to view this."
|
||||
isSystemAccount: "An account created and automatically operated by the system."
|
||||
isSystemAccount: "This account is created and automatically operated by the system. Please do not moderate, edit, delete, or otherwise tamper with this account, or it may break your server."
|
||||
typeToConfirm: "Please enter {x} to confirm"
|
||||
deleteAccount: "Delete account"
|
||||
document: "Documentation"
|
||||
|
@ -1266,6 +1267,8 @@ _mfm:
|
|||
sparkleDescription: "Gives content a sparkling particle effect."
|
||||
rotate: "Rotate"
|
||||
rotateDescription: "Turns content by a specified angle."
|
||||
fade: "Fade"
|
||||
fadeDescription: "Fades content in and out."
|
||||
position: "Position"
|
||||
positionDescription: "Move content by a specified amount."
|
||||
scale: "Scale"
|
||||
|
|
|
@ -1077,6 +1077,8 @@ _mfm:
|
|||
sparkle: "Paillettes"
|
||||
sparkleDescription: "Ajoute un effet scintillant au contenu."
|
||||
rotate: "Pivoter"
|
||||
fade: "Apparaître/Disparaître"
|
||||
fadeDescription: "Fait apparaître et disparaître le contenu."
|
||||
plainDescription: Désactiver les effets de tous les MFM contenus dans cet effet
|
||||
MFM.
|
||||
rotateDescription: Pivoter le contenu d'un angle spécifique.
|
||||
|
|
|
@ -910,6 +910,8 @@ _mfm:
|
|||
fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
|
||||
rainbow: "Arcobaleno"
|
||||
rotate: "Ruota"
|
||||
fade: "Dissolvenza"
|
||||
fadeDescription: "Dissolvenza in entrata e in uscita del contenuto."
|
||||
_instanceTicker:
|
||||
none: "Nascondi"
|
||||
remote: "Mostra solo per gli/le utenti remotə"
|
||||
|
|
|
@ -13,7 +13,7 @@ password: "Wachtwoord"
|
|||
forgotPassword: "Wachtwoord vergeten"
|
||||
fetchingAsApObject: "Ophalen vanuit de Fediverse"
|
||||
ok: "Ok"
|
||||
gotIt: "Begrepen"
|
||||
gotIt: "Begrepen!"
|
||||
cancel: "Annuleren"
|
||||
enterUsername: "Voer een gebruikersnaam in"
|
||||
renotedBy: "Hergedeeld door {user}"
|
||||
|
@ -47,12 +47,12 @@ copyContent: "Kopiëren inhoud"
|
|||
copyLink: "Kopiëren link"
|
||||
delete: "Verwijderen"
|
||||
deleteAndEdit: "Verwijderen en bewerken"
|
||||
deleteAndEditConfirm: "Weet je zeker dat je deze notitie wilt verwijderen en dan bewerken?
|
||||
Je verliest alle reacties, herdelingen en antwoorden erop."
|
||||
deleteAndEditConfirm: "Weet je zeker dat je deze post wilt verwijderen en dan bewerken?
|
||||
Je verliest alle reacties, boosts en antwoorden erop."
|
||||
addToList: "Aan lijst toevoegen"
|
||||
sendMessage: "Verstuur bericht"
|
||||
copyUsername: "Kopiëren gebruikersnaam "
|
||||
searchUser: "Zoeken een gebruiker"
|
||||
copyUsername: "Gebruikersnaam kopiëren"
|
||||
searchUser: "Zoek een gebruiker"
|
||||
reply: "Antwoord"
|
||||
loadMore: "Laad meer"
|
||||
showMore: "Toon meer"
|
||||
|
@ -68,7 +68,7 @@ export: "Export"
|
|||
files: "Bestanden"
|
||||
download: "Downloaden"
|
||||
driveFileDeleteConfirm: "Weet je zeker dat je het bestand \"{name}\" wilt verwijderen?
|
||||
Notities met dit bestand als bijlage worden ook verwijderd."
|
||||
Posts met dit bestand als bijlage worden ook verwijderd."
|
||||
unfollowConfirm: "Weet je zeker dat je {name} wilt ontvolgen?"
|
||||
exportRequested: "Je hebt een export aangevraagd. Dit kan een tijdje duren. Het wordt
|
||||
toegevoegd aan je Drive zodra het is voltooid."
|
||||
|
@ -101,13 +101,13 @@ followRequests: "Volgverzoeken"
|
|||
unfollow: "Ontvolgen"
|
||||
followRequestPending: "Wachten op goedkeuring volgverzoek"
|
||||
enterEmoji: "Voer een emoji in"
|
||||
renote: "Herdelen"
|
||||
renote: "Boost"
|
||||
unrenote: "Stop herdelen"
|
||||
renoted: "Herdeeld"
|
||||
cantRenote: "Dit bericht kan niet worden herdeeld"
|
||||
cantReRenote: "Een herdeling kan niet worden herdeeld"
|
||||
renoted: "Boosted."
|
||||
cantRenote: "Dit bericht kan niet worden geboost."
|
||||
cantReRenote: "Een boost kan niet worden geboost."
|
||||
quote: "Quote"
|
||||
pinnedNote: "Vastgemaakte notitie"
|
||||
pinnedNote: "Vastgemaakte post"
|
||||
pinned: "Vastmaken aan profielpagina"
|
||||
you: "Jij"
|
||||
clickToShow: "Klik om te bekijken"
|
||||
|
@ -116,7 +116,7 @@ add: "Toevoegen"
|
|||
reaction: "Reacties"
|
||||
reactionSettingDescription2: "Sleep om opnieuw te ordenen, Klik om te verwijderen,
|
||||
Druk op \"+\" om toe te voegen"
|
||||
rememberNoteVisibility: "Vergeet niet de notitie zichtbaarheidsinstellingen"
|
||||
rememberNoteVisibility: "Onthoud post zichtbaarheidsinstellingen"
|
||||
attachCancel: "Verwijder bijlage"
|
||||
markAsSensitive: "Markeren als NSFW"
|
||||
unmarkAsSensitive: "Geen NSFW"
|
||||
|
@ -139,8 +139,8 @@ flagAsCat: "Markeer dit account als een kat."
|
|||
flagAsCatDescription: "Zet deze vlag aan als je wilt aangeven dat dit account een
|
||||
kat is."
|
||||
flagShowTimelineReplies: "Toon antwoorden op de tijdlijn"
|
||||
flagShowTimelineRepliesDescription: "Als je dit vlag aanzet, toont de tijdlijn ook
|
||||
antwoorden op andere en niet alleen jouw eigen notities."
|
||||
flagShowTimelineRepliesDescription: "Als je deze vlag aanzet, toont de tijdlijn ook
|
||||
antwoorden op andere en niet alleen jouw eigen post."
|
||||
autoAcceptFollowed: "Accepteer verzoeken om jezelf te volgen vanzelf als je de verzoeker
|
||||
al volgt"
|
||||
addAccount: "Account toevoegen"
|
||||
|
@ -203,8 +203,8 @@ mutedUsers: "Gedempte gebruikers"
|
|||
blockedUsers: "Geblokkeerde gebruikers"
|
||||
noUsers: "Er zijn geen gebruikers."
|
||||
editProfile: "Bewerk Profiel"
|
||||
noteDeleteConfirm: "Ben je zeker dat je dit bericht wil verwijderen?"
|
||||
pinLimitExceeded: "Je kunt geen berichten meer vastprikken"
|
||||
noteDeleteConfirm: "Ben je zeker dat je deze post wil verwijderen?"
|
||||
pinLimitExceeded: "Je kunt geen posts meer vastprikken"
|
||||
intro: "Installatie van Calckey geëindigd! Maak nu een beheerder aan."
|
||||
done: "Klaar"
|
||||
processing: "Bezig met verwerken"
|
||||
|
@ -499,3 +499,23 @@ manageGroups: Beheer groepen
|
|||
subscribePushNotification: Pushmeldingen inschakelen
|
||||
unsubscribePushNotification: Pushmeldingen uitschakelen
|
||||
pushNotificationAlreadySubscribed: Pushmeldingen zijn al ingeschakeld
|
||||
antennaSource: Antenne bron
|
||||
antennaKeywords: Trefwoorden om naar te luisteren
|
||||
antennaExcludeKeywords: Trefwoorden om te negeren
|
||||
driveCapacityPerRemoteAccount: Schijfruimte per externe gebruiker
|
||||
backgroundImageUrl: Achtergrondafbeelding URL
|
||||
basicInfo: Basis informatie
|
||||
pinnedUsers: Vastgezette gebruikers
|
||||
pinnedPages: Vastgezette Pagina's
|
||||
driveCapacityPerLocalAccount: Schijfruimte per lokale gebruiker
|
||||
iconUrl: Icoon URL
|
||||
bannerUrl: Banner afbeelding URL
|
||||
manageAntennas: Beheer Antennes
|
||||
name: Naam
|
||||
notifyAntenna: Meld nieuwe posts
|
||||
withFileAntenna: Alleen posts met bestanden
|
||||
enableServiceworker: Schakel pushmeldingen voor je browser in
|
||||
renoteUnmute: Ontdemp boosts
|
||||
jumpToPrevious: Spring naar vorige
|
||||
caseSensitive: Hoofdlettergevoelig
|
||||
cw: Inhoudswaarschuwing
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "calckey",
|
||||
"version": "14.0.0-dev14",
|
||||
"version": "14.0.0-dev18",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://codeberg.org/calckey/calckey.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.5.0",
|
||||
"packageManager": "pnpm@8.5.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
"koa-send": "5.0.1",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "7.0.2",
|
||||
"mfm-js": "0.23.2",
|
||||
"mfm-js": "0.23.3",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.4-lts.1",
|
||||
"native-utils": "link:native-utils",
|
||||
|
|
|
@ -9,6 +9,9 @@ export function nyaize(text: string): string {
|
|||
.replace(/(?<=n)a/gi, (x) => (x === "A" ? "YA" : "ya"))
|
||||
.replace(/(?<=morn)ing/gi, (x) => (x === "ING" ? "YAN" : "yan"))
|
||||
.replace(/(?<=every)one/gi, (x) => (x === "ONE" ? "NYAN" : "nyan"))
|
||||
.replace(/non(?=[bcdfghjklmnpqrstvwxyz])/gi, (x) =>
|
||||
x === "NON" ? "NYAN" : "nyan",
|
||||
)
|
||||
// ko-KR
|
||||
.replace(/[나-낳]/g, (match) =>
|
||||
String.fromCharCode(
|
||||
|
|
|
@ -38,6 +38,7 @@ export const paramDef = {
|
|||
type: "object",
|
||||
properties: {
|
||||
noteId: { type: "string", format: "misskey:id" },
|
||||
userId: { type: "string", format: "misskey:id" },
|
||||
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: "string", format: "misskey:id" },
|
||||
untilId: { type: "string", format: "misskey:id" },
|
||||
|
@ -52,13 +53,19 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
throw err;
|
||||
});
|
||||
|
||||
const query = makePaginationQuery(
|
||||
let query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
)
|
||||
.andWhere("note.renoteId = :renoteId", { renoteId: note.id })
|
||||
.innerJoinAndSelect("note.user", "user")
|
||||
.innerJoinAndSelect("note.user", "user");
|
||||
|
||||
if (ps.userId) {
|
||||
query.andWhere("user.id = :userId", { userId: ps.userId });
|
||||
}
|
||||
|
||||
query
|
||||
.leftJoinAndSelect("user.avatar", "avatar")
|
||||
.leftJoinAndSelect("user.banner", "banner")
|
||||
.leftJoinAndSelect("note.reply", "reply")
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as fs from "node:fs";
|
|||
import * as http from "node:http";
|
||||
import Koa from "koa";
|
||||
import Router from "@koa/router";
|
||||
import cors from "@koa/cors";
|
||||
import mount from "koa-mount";
|
||||
import koaLogger from "koa-logger";
|
||||
import * as slow from "koa-slow";
|
||||
|
@ -41,6 +42,12 @@ app.proxy = true;
|
|||
|
||||
app.use(removeTrailingSlash());
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: "*",
|
||||
}),
|
||||
);
|
||||
|
||||
if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
|
||||
// Logger
|
||||
app.use(
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
"start-server-and-test": "1.15.2",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"swiper": "^8.4.5",
|
||||
"swiper": "9.3.2",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.146.0",
|
||||
|
|
|
@ -111,11 +111,12 @@ function onMousedown(evt: MouseEvent): void {
|
|||
z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため
|
||||
display: block;
|
||||
min-width: 100px;
|
||||
min-height: 35px;
|
||||
width: max-content;
|
||||
padding: 8px 16px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
font-size: max(12px, 1em);
|
||||
box-shadow: none;
|
||||
text-decoration: none;
|
||||
background: var(--buttonBg);
|
||||
|
@ -193,7 +194,7 @@ function onMousedown(evt: MouseEvent): void {
|
|||
|
||||
&.mini {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.9em;
|
||||
font-size: max(12px, 0.9em);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<button
|
||||
class="kpoogebi _button"
|
||||
v-if="$i != null && $i.id != user.id"
|
||||
class="kpoogebi _button follow-button"
|
||||
:class="{
|
||||
wait,
|
||||
active: isFollowing || hasPendingFollowRequestFromYou,
|
||||
|
@ -10,40 +11,43 @@
|
|||
}"
|
||||
:disabled="wait"
|
||||
@click="onClick"
|
||||
:aria-label="`${state} ${user.name || user.username}`"
|
||||
>
|
||||
<template v-if="!wait">
|
||||
<template v-if="isBlocking">
|
||||
<span v-if="full">{{ i18n.ts.blocked }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.blocked) }}</span
|
||||
><i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="hasPendingFollowRequestFromYou && user.isLocked"
|
||||
>
|
||||
<span v-if="full">{{ i18n.ts.followRequestPending }}</span
|
||||
<span v-if="full">{{
|
||||
(state = i18n.ts.followRequestPending)
|
||||
}}</span
|
||||
><i class="ph-hourglass-medium ph-bold ph-lg"></i>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"
|
||||
>
|
||||
<!-- つまりリモートフォローの場合。 -->
|
||||
<span v-if="full">{{ i18n.ts.processing }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.processing) }}</span
|
||||
><i class="ph-circle-notch ph-bold ph-lg fa-pulse"></i>
|
||||
</template>
|
||||
<template v-else-if="isFollowing">
|
||||
<span v-if="full">{{ i18n.ts.unfollow }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.unfollow) }}</span
|
||||
><i class="ph-minus ph-bold ph-lg"></i>
|
||||
</template>
|
||||
<template v-else-if="!isFollowing && user.isLocked">
|
||||
<span v-if="full">{{ i18n.ts.followRequest }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.followRequest) }}</span
|
||||
><i class="ph-plus ph-bold ph-lg"></i>
|
||||
</template>
|
||||
<template v-else-if="!isFollowing && !user.isLocked">
|
||||
<span v-if="full">{{ i18n.ts.follow }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.follow) }}</span
|
||||
><i class="ph-plus ph-bold ph-lg"></i>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="full">{{ i18n.ts.processing }}</span
|
||||
<span v-if="full">{{ (state = i18n.ts.processing) }}</span
|
||||
><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
|
||||
</template>
|
||||
</button>
|
||||
|
@ -55,6 +59,7 @@ import type * as Misskey from "calckey-js";
|
|||
import * as os from "@/os";
|
||||
import { stream } from "@/stream";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/account";
|
||||
|
||||
const emit = defineEmits(["refresh"]);
|
||||
const props = withDefaults(
|
||||
|
@ -71,6 +76,8 @@ const props = withDefaults(
|
|||
|
||||
const isBlocking = computed(() => props.user.isBlocking);
|
||||
|
||||
let state = $ref(i18n.ts.processing);
|
||||
|
||||
let isFollowing = $ref(props.user.isFollowing);
|
||||
let hasPendingFollowRequestFromYou = $ref(
|
||||
props.user.hasPendingFollowRequestFromYou
|
||||
|
@ -155,7 +162,7 @@ onBeforeUnmount(() => {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kpoogebi {
|
||||
.follow-button {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
@ -164,13 +171,15 @@ onBeforeUnmount(() => {
|
|||
color: var(--accent);
|
||||
border: solid 1px var(--accent);
|
||||
padding: 0;
|
||||
height: 31px;
|
||||
font-size: 16px;
|
||||
border-radius: 32px;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border-radius: 100px;
|
||||
background: var(--bg);
|
||||
|
||||
&.full {
|
||||
padding: 0 8px 0 12px;
|
||||
padding: 0.2em 0.7em;
|
||||
width: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
@ -207,7 +216,7 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
color: var(--fgOnAccent);
|
||||
background: var(--accent);
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
:aria-label="accessibleLabel"
|
||||
v-if="!muted.muted"
|
||||
v-show="!isDeleted"
|
||||
ref="el"
|
||||
|
@ -85,6 +86,7 @@
|
|||
:parentId="appearNote.parentId"
|
||||
@push="(e) => router.push(notePage(e))"
|
||||
@focusfooter="footerEl.focus()"
|
||||
@expanded="(e) => setPostExpanded(e)"
|
||||
></MkSubNoteContent>
|
||||
<div v-if="translating || translation" class="translation">
|
||||
<MkLoading v-if="translating" mini />
|
||||
|
@ -472,6 +474,39 @@ function readPromo() {
|
|||
isDeleted.value = true;
|
||||
}
|
||||
|
||||
let postIsExpanded = ref(false);
|
||||
|
||||
function setPostExpanded(val: boolean) {
|
||||
postIsExpanded.value = val;
|
||||
}
|
||||
|
||||
const accessibleLabel = computed(() => {
|
||||
let label = `${props.note.user.username}; `;
|
||||
if (props.note.renote) {
|
||||
label += `${i18n.t("renoted")} ${props.note.renote.user.username}; `;
|
||||
if (props.note.renote.cw) {
|
||||
label += `${i18n.t("cw")}: ${props.note.renote.cw}; `;
|
||||
if (postIsExpanded.value) {
|
||||
label += `${props.note.renote.text}; `;
|
||||
}
|
||||
} else {
|
||||
label += `${props.note.renote.text}; `;
|
||||
}
|
||||
} else {
|
||||
if (props.note.cw) {
|
||||
label += `${i18n.t("cw")}: ${props.note.cw}; `;
|
||||
if (postIsExpanded.value) {
|
||||
label += `${props.note.text}; `;
|
||||
}
|
||||
} else {
|
||||
label += `${props.note.text}; `;
|
||||
}
|
||||
}
|
||||
const date = new Date(props.note.createdAt);
|
||||
label += `${date.toLocaleTimeString()}`;
|
||||
return label;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
focus,
|
||||
blur,
|
||||
|
|
|
@ -35,38 +35,30 @@
|
|||
|
||||
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
||||
<option value="replies">
|
||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||
<template v-if="appearNote.repliesCount > 0">
|
||||
<span class="count">{{ appearNote.repliesCount }}</span>
|
||||
</template>
|
||||
<!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> -->
|
||||
<span v-if="appearNote.repliesCount > 0" class="count">{{
|
||||
appearNote.repliesCount
|
||||
}}</span>
|
||||
{{ i18n.ts._notification._types.reply }}
|
||||
</option>
|
||||
<option value="renotes">
|
||||
<i class="ph-repeat ph-bold ph-lg"></i>
|
||||
<template v-if="appearNote.renoteCount > 0">
|
||||
<span class="count">{{ appearNote.renoteCount }}</span>
|
||||
</template>
|
||||
<option value="renotes" v-if="appearNote.renoteCount > 0">
|
||||
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
|
||||
<span class="count">{{ appearNote.renoteCount }}</span>
|
||||
{{ i18n.ts._notification._types.renote }}
|
||||
</option>
|
||||
<option value="quotes">
|
||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||
<template v-if="directQuotes?.length > 0">
|
||||
<span class="count">{{ directQuotes.length }}</span>
|
||||
</template>
|
||||
{{ i18n.ts._notification._types.quote }}
|
||||
</option>
|
||||
<option value="reactions">
|
||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
||||
<template v-if="reactionsCount > 0">
|
||||
<span class="count">{{ reactionsCount }}</span>
|
||||
</template>
|
||||
<option value="reactions" v-if="reactionsCount > 0">
|
||||
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
|
||||
<span class="count">{{ reactionsCount }}</span>
|
||||
{{ i18n.ts.reaction }}
|
||||
</option>
|
||||
<option value="clips">
|
||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||
<template v-if="clips?.length > 0">
|
||||
<span class="count">{{ clips.length }}</span>
|
||||
</template>
|
||||
<option value="quotes" v-if="directQuotes?.length > 0">
|
||||
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
|
||||
<span class="count">{{ directQuotes.length }}</span>
|
||||
{{ i18n.ts._notification._types.quote }}
|
||||
</option>
|
||||
<option value="clips" v-if="clips?.length > 0">
|
||||
<!-- <i class="ph-paperclip ph-bold ph-lg"></i> -->
|
||||
<span class="count">{{ clips.length }}</span>
|
||||
{{ i18n.ts.clips }}
|
||||
</option>
|
||||
</MkTab>
|
||||
|
@ -518,11 +510,15 @@ onUnmounted(() => {
|
|||
overflow: clip;
|
||||
outline: none;
|
||||
scroll-margin-top: calc(var(--stickyTop) + 20vh);
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--divider);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.article {
|
||||
cursor: unset;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
&:first-of-type {
|
||||
&:first-child {
|
||||
padding-top: 28px;
|
||||
}
|
||||
}
|
||||
|
@ -632,7 +628,7 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
> :deep(.note-container) {
|
||||
padding: 6px 0 0 0;
|
||||
padding: 12px 0 0 0;
|
||||
> .header > .body {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
@ -642,7 +638,7 @@ onUnmounted(() => {
|
|||
> :deep(.reacted-users > *) {
|
||||
padding-inline: 16px !important;
|
||||
}
|
||||
> .chips {
|
||||
> :deep(.underline) {
|
||||
padding-left: 16px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
<article
|
||||
v-if="!muted.muted || muted.what === 'reply'"
|
||||
ref="el"
|
||||
v-size="{ max: [450, 500] }"
|
||||
|
@ -150,7 +150,7 @@
|
|||
></MkA>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</article>
|
||||
<div v-else class="muted" @click="muted.muted = false">
|
||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||
<template #name>
|
||||
|
|
|
@ -69,11 +69,11 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
|||
|
||||
const renotes = await os.api("notes/renotes", {
|
||||
noteId: props.note.id,
|
||||
limit: 11,
|
||||
userId: $i.id,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
const users = renotes.map((x) => x.user.id);
|
||||
const hasRenotedBefore = users.includes($i.id);
|
||||
const hasRenotedBefore = renotes.length > 0;
|
||||
|
||||
let buttonActions: Array<MenuItem> = [];
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
!note.replyId
|
||||
"
|
||||
:to="`/notes/${note.renoteId}`"
|
||||
v-tooltip="i18n.ts.jumpToReply"
|
||||
v-tooltip="i18n.ts.jumpToPrevious"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
|
@ -54,11 +54,12 @@
|
|||
v-model="showContent"
|
||||
:note="note"
|
||||
v-on:keydown="focusFooter"
|
||||
v-on:update:model-value="(val) => emit('expanded', val)"
|
||||
/>
|
||||
<div
|
||||
class="body"
|
||||
v-bind="{
|
||||
'aria-hidden': !showContent ? 'true' : null,
|
||||
'aria-hidden': note.cw && !showContent ? 'true' : null,
|
||||
tabindex: !showContent ? '-1' : null,
|
||||
}"
|
||||
>
|
||||
|
@ -70,7 +71,7 @@
|
|||
v-if="!detailed && note.replyId"
|
||||
:to="`#${note.replyId}`"
|
||||
behavior="browser"
|
||||
v-tooltip="i18n.ts.jumpToReply"
|
||||
v-tooltip="i18n.ts.jumpToPrevious"
|
||||
class="reply-icon"
|
||||
@click.stop
|
||||
>
|
||||
|
@ -190,6 +191,7 @@ const props = defineProps<{
|
|||
const emit = defineEmits<{
|
||||
(ev: "push", v): void;
|
||||
(ev: "focusfooter"): void;
|
||||
(ev: "expanded", v): void;
|
||||
}>();
|
||||
|
||||
const cwButton = ref<HTMLElement>();
|
||||
|
|
|
@ -16,9 +16,10 @@ export default defineComponent({
|
|||
return h(
|
||||
"div",
|
||||
{
|
||||
class: ["pxhvhrfw",
|
||||
class: [
|
||||
"pxhvhrfw",
|
||||
{ chips: this.style === "chips" },
|
||||
{ underline: this.style === "underline" }
|
||||
{ underline: this.style === "underline" },
|
||||
],
|
||||
role: "tablist",
|
||||
},
|
||||
|
@ -86,9 +87,14 @@ export default defineComponent({
|
|||
> .icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.chips, &.underline {
|
||||
&.chips,
|
||||
&.underline {
|
||||
padding: 12px 32px;
|
||||
font-size: 0.85em;
|
||||
overflow-x: auto;
|
||||
|
@ -124,6 +130,7 @@ export default defineComponent({
|
|||
&.underline {
|
||||
padding-block: 0 !important;
|
||||
margin-bottom: -1px;
|
||||
border-radius: 0;
|
||||
button {
|
||||
background: none !important;
|
||||
border-radius: 0 !important;
|
||||
|
|
|
@ -299,6 +299,15 @@ const props = withDefaults(
|
|||
filter: hue-rotate(360deg) contrast(150%) saturate(150%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mfm-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -3,31 +3,32 @@
|
|||
v-if="show"
|
||||
ref="el"
|
||||
class="fdidabkb"
|
||||
:class="{ slim: narrow, thin: thin_ }"
|
||||
:class="{ thin: thin_, tabs: tabs?.length > 0 }"
|
||||
:style="{ background: bg }"
|
||||
@click="onClick"
|
||||
>
|
||||
<button
|
||||
v-if="props.displayBackButton"
|
||||
class="_button button icon backButton"
|
||||
@click.stop="goBack()"
|
||||
@touchstart="preventDrag"
|
||||
v-tooltip.noDelay="i18n.ts.goBack"
|
||||
>
|
||||
<i class="ph-caret-left ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<div v-if="narrow" class="buttons left" @click="openAccountMenu">
|
||||
<MkAvatar
|
||||
v-if="props.displayMyAvatar && $i"
|
||||
class="avatar"
|
||||
:user="$i"
|
||||
:disable-preview="true"
|
||||
disableLink
|
||||
/>
|
||||
</div>
|
||||
<template v-if="metadata">
|
||||
<div class="left">
|
||||
<div class="buttons">
|
||||
<button
|
||||
v-if="props.displayBackButton"
|
||||
class="_button button icon backButton"
|
||||
@click.stop="goBack()"
|
||||
@touchstart="preventDrag"
|
||||
v-tooltip.noDelay="i18n.ts.goBack"
|
||||
>
|
||||
<i class="ph-caret-left ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<MkAvatar
|
||||
v-if="narrow && props.displayMyAvatar && $i"
|
||||
class="avatar button"
|
||||
:user="$i"
|
||||
:disable-preview="true"
|
||||
disableLink
|
||||
@click.stop="openAccountMenu"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!hideTitle"
|
||||
v-if="!hideTitle && metadata"
|
||||
class="titleContainer"
|
||||
@click="showTabsPopup"
|
||||
>
|
||||
|
@ -66,7 +67,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav ref="tabsEl" v-if="hasTabs" class="tabs">
|
||||
</div>
|
||||
<template v-if="metadata">
|
||||
<nav
|
||||
ref="tabsEl"
|
||||
v-if="hasTabs"
|
||||
class="tabs"
|
||||
:class="{ collapse: hasTabs && tabs.length > 3 }"
|
||||
>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:ref="(el) => (tabRefs[tab.key] = el)"
|
||||
|
@ -85,6 +93,20 @@
|
|||
</nav>
|
||||
</template>
|
||||
<div class="buttons right">
|
||||
<template v-if="metadata.avatar">
|
||||
<MkFollowButton
|
||||
v-if="narrow"
|
||||
:user="metadata.avatar"
|
||||
:full="false"
|
||||
class="fullButton"
|
||||
></MkFollowButton>
|
||||
<MkFollowButton
|
||||
v-else
|
||||
:user="metadata.avatar"
|
||||
:full="true"
|
||||
class="fullButton"
|
||||
></MkFollowButton>
|
||||
</template>
|
||||
<template v-for="action in actions">
|
||||
<button
|
||||
v-tooltip.noDelay="action.text"
|
||||
|
@ -112,7 +134,7 @@ import {
|
|||
nextTick,
|
||||
reactive,
|
||||
} from "vue";
|
||||
import tinycolor from "tinycolor2";
|
||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||
import { popupMenu } from "@/os";
|
||||
import { scrollToTop } from "@/scripts/scroll";
|
||||
import { globalEvents } from "@/events";
|
||||
|
@ -223,25 +245,9 @@ function goBack(): void {
|
|||
window.history.back();
|
||||
}
|
||||
|
||||
const calcBg = () => {
|
||||
const rawBg = metadata?.bg || "var(--bg)";
|
||||
const tinyBg = tinycolor(
|
||||
rawBg.startsWith("var(")
|
||||
? getComputedStyle(document.documentElement).getPropertyValue(
|
||||
rawBg.slice(4, -1)
|
||||
)
|
||||
: rawBg
|
||||
);
|
||||
tinyBg.setAlpha(0.85);
|
||||
bg.value = tinyBg.toRgbString();
|
||||
};
|
||||
|
||||
let ro: ResizeObserver | null;
|
||||
|
||||
onMounted(() => {
|
||||
calcBg();
|
||||
globalEvents.on("themeChanged", calcBg);
|
||||
|
||||
watch(
|
||||
() => [props.tab, props.tabs],
|
||||
() => {
|
||||
|
@ -251,17 +257,15 @@ onMounted(() => {
|
|||
// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
|
||||
// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
|
||||
const tabSizeX = tabEl.scrollWidth + 20; // + the tab's padding
|
||||
tabEl.style = `--width: ${tabSizeX}px`;
|
||||
if (props.tabs.length > 3) {
|
||||
tabEl.style = `--width: ${tabSizeX}px`;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const parentRect = tabsEl.getBoundingClientRect();
|
||||
const rect = tabEl.getBoundingClientRect();
|
||||
const left =
|
||||
rect.left - parentRect.left + tabsEl?.scrollLeft;
|
||||
tabHighlightEl.style.width = tabSizeX + "px";
|
||||
tabHighlightEl.style.transform = `translateX(${left}px)`;
|
||||
tabHighlightEl.style.transform = `translateX(${tabEl.offsetLeft}px)`;
|
||||
window.requestAnimationFrame(() => {
|
||||
tabsEl?.scrollTo({
|
||||
left: left - 60,
|
||||
left: tabEl.offsetLeft - 60,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
|
@ -286,7 +290,6 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
globalEvents.off("themeChanged", calcBg);
|
||||
if (ro) ro.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
@ -295,94 +298,96 @@ onUnmounted(() => {
|
|||
.fdidabkb {
|
||||
--height: 55px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
height: var(--height);
|
||||
padding-inline: 24px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@media (max-width: 500px) {
|
||||
padding-inline: 12p;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
> .left {
|
||||
min-width: unset !important;
|
||||
max-width: 40%;
|
||||
}
|
||||
> .left,
|
||||
> .right {
|
||||
flex: unset !important;
|
||||
}
|
||||
&:not(.tabs) {
|
||||
> .left {
|
||||
width: 0 !important;
|
||||
flex-grow: 1 !important;
|
||||
max-width: unset !important;
|
||||
}
|
||||
}
|
||||
&.tabs {
|
||||
> .left {
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.buttons ~ .titleContainer > .title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
z-index: -1;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--bg);
|
||||
opacity: 0.85;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
&.thin {
|
||||
--height: 45px;
|
||||
|
||||
> .buttons {
|
||||
.buttons {
|
||||
> .button {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.slim {
|
||||
> .titleContainer {
|
||||
flex: 1;
|
||||
margin: 0 auto;
|
||||
|
||||
> *:first-child {
|
||||
margin-left: auto;
|
||||
> .left {
|
||||
display: flex;
|
||||
> .buttons {
|
||||
&:not(:empty) {
|
||||
margin-left: calc(0px - var(--margin));
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
> .tabs {
|
||||
padding-inline: 12px;
|
||||
mask: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 10px 80%,
|
||||
transparent
|
||||
);
|
||||
-webkit-mask: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 10px 80%,
|
||||
transparent
|
||||
);
|
||||
margin-left: -10px;
|
||||
padding-left: 22px;
|
||||
scrollbar-width: none;
|
||||
&::before {
|
||||
content: unset;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
&::after {
|
||||
// Force right padding
|
||||
content: "";
|
||||
display: inline-block;
|
||||
min-width: 20%;
|
||||
> .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-left: var(--margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
.buttons {
|
||||
--margin: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--height);
|
||||
margin: 0 var(--margin);
|
||||
|
||||
&.left {
|
||||
margin-right: auto;
|
||||
|
||||
> .avatar {
|
||||
$size: 32px;
|
||||
display: inline-block;
|
||||
width: $size;
|
||||
height: $size;
|
||||
vertical-align: bottom;
|
||||
margin: 0 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
// margin-right: calc(0px - var(--margin));
|
||||
// margin-left: var(--margin);
|
||||
> .button:last-child {
|
||||
margin-right: calc(0px - var(--margin));
|
||||
}
|
||||
}
|
||||
|
||||
> .button/*, @at-root .backButton*/ {
|
||||
|
@ -412,85 +417,117 @@ onUnmounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
> .backButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
> .titleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 400px;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
margin-left: 24px;
|
||||
margin-right: 1rem;
|
||||
|
||||
> .avatar {
|
||||
$size: 32px;
|
||||
display: inline-block;
|
||||
width: $size;
|
||||
height: $size;
|
||||
vertical-align: bottom;
|
||||
margin: 0 8px;
|
||||
pointer-events: none;
|
||||
> .left {
|
||||
> .backButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
transform: translate(0em);
|
||||
}
|
||||
|
||||
> .title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
> .titleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 400px;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
margin-right: 1rem;
|
||||
|
||||
> .subtitle {
|
||||
opacity: 0.6;
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
> .avatar {
|
||||
$size: 32px;
|
||||
display: inline-block;
|
||||
width: $size;
|
||||
height: $size;
|
||||
vertical-align: bottom;
|
||||
margin: 0 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
|
||||
&.activeTab {
|
||||
text-align: center;
|
||||
> .subtitle {
|
||||
opacity: 0.6;
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
> .chevron {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
&.activeTab {
|
||||
text-align: center;
|
||||
|
||||
> .chevron {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .left,
|
||||
> .right {
|
||||
flex-basis: 100%;
|
||||
flex-shrink: 9999;
|
||||
overflow: hidden;
|
||||
}
|
||||
> .left {
|
||||
min-width: 20%;
|
||||
margin-left: -10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
> .right {
|
||||
// margin-left: auto;
|
||||
min-width: max-content;
|
||||
margin-right: -10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
contain: strict;
|
||||
contain: content;
|
||||
display: flex;
|
||||
padding-inline: 20px;
|
||||
margin-inline: -20px;
|
||||
mask: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 20px calc(100% - 20px),
|
||||
transparent
|
||||
);
|
||||
-webkit-mask: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 20px calc(100% - 20px),
|
||||
transparent
|
||||
);
|
||||
scrollbar-width: none;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 40%;
|
||||
border-left: 1px solid var(--divider);
|
||||
margin-right: 1em;
|
||||
margin-left: 10px;
|
||||
vertical-align: -1px;
|
||||
&.collapse {
|
||||
--width: 38px;
|
||||
> .tab {
|
||||
width: 38px;
|
||||
min-width: 38px !important;
|
||||
&:not(.active) > .title {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tab {
|
||||
|
@ -499,12 +536,12 @@ onUnmounted(() => {
|
|||
position: relative;
|
||||
border-inline: 10px solid transparent;
|
||||
height: 100%;
|
||||
min-width: max-content;
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
width: 38px;
|
||||
--width: 38px;
|
||||
overflow: hidden;
|
||||
transition: color 0.2s, opacity 0.2s, width 0.2s;
|
||||
transition: color 0.2s, opacity 0.2s, width 0.2s, min-width 0.2s;
|
||||
--width: max-content;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
|
@ -515,9 +552,7 @@ onUnmounted(() => {
|
|||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
width: var(--width);
|
||||
}
|
||||
&:not(.active) > .title {
|
||||
opacity: 0;
|
||||
min-width: var(--width) !important;
|
||||
}
|
||||
|
||||
> .icon + .title {
|
||||
|
@ -527,7 +562,6 @@ onUnmounted(() => {
|
|||
transition: opacity 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
> .highlight {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
|
|
@ -156,6 +156,14 @@ export default defineComponent({
|
|||
}
|
||||
return h(MkSparkle, {}, genEl(token.children));
|
||||
}
|
||||
case "fade": {
|
||||
const direction = token.props.args.out
|
||||
? "alternate-reverse"
|
||||
: "alternate";
|
||||
const speed = validTime(token.props.args.speed) || "1.5s";
|
||||
style = `animation: mfm-fade ${speed} linear infinite; animation-direction: ${direction};`;
|
||||
break;
|
||||
}
|
||||
case "flip": {
|
||||
const transform =
|
||||
token.props.args.h && token.props.args.v
|
||||
|
|
|
@ -353,6 +353,18 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.fade }}</div>
|
||||
<div class="content">
|
||||
<p>{{ i18n.ts._mfm.fadeDescription }}</p>
|
||||
<div class="preview">
|
||||
<Mfm :text="preview_fade" />
|
||||
<MkTextarea v-model="preview_fade"
|
||||
><span>MFM</span></MkTextarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.position }}</div>
|
||||
<div class="content">
|
||||
|
@ -479,6 +491,7 @@ let preview_bg = $ref("$[bg.color=ff0000 Background color]");
|
|||
let preview_plain = $ref(
|
||||
"<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>"
|
||||
);
|
||||
let preview_fade = $ref("$[fade 🍮] $[fade.out 🍮] $[fade.speed=5s 🍮]");
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts._mfm.cheatSheet,
|
||||
|
|
|
@ -27,11 +27,10 @@
|
|||
v-if="!showNext && hasNext"
|
||||
class="load next"
|
||||
@click="showNext = true"
|
||||
v-tooltip="
|
||||
`${i18n.ts.loadMore} (${i18n.ts.newer})`
|
||||
"
|
||||
><i class="ph-caret-up ph-bold ph-lg"></i
|
||||
></MkButton>
|
||||
>
|
||||
<i class="ph-caret-up ph-bold ph-lg"></i>
|
||||
{{ `${i18n.ts.loadMore} (${i18n.ts.newer})` }}
|
||||
</MkButton>
|
||||
<div class="note _gap">
|
||||
<MkRemoteCaution
|
||||
v-if="note.user.host != null"
|
||||
|
@ -47,11 +46,10 @@
|
|||
v-if="!showPrev && hasPrev"
|
||||
class="load prev"
|
||||
@click="showPrev = true"
|
||||
v-tooltip="
|
||||
`${i18n.ts.loadMore} (${i18n.ts.older})`
|
||||
"
|
||||
><i class="ph-caret-down ph-bold ph-lg"></i
|
||||
></MkButton>
|
||||
>
|
||||
<i class="ph-caret-down ph-bold ph-lg"></i>
|
||||
{{ `${i18n.ts.loadMore} (${i18n.ts.older})` }}
|
||||
</MkButton>
|
||||
</div>
|
||||
|
||||
<div v-if="showPrev" class="_gap">
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<MkInfo
|
||||
v-if="user.username.includes('.')"
|
||||
class="_formBlock"
|
||||
warn
|
||||
>{{ i18n.ts.isSystemAccount }}</MkInfo
|
||||
>
|
||||
|
||||
|
|
|
@ -135,7 +135,6 @@
|
|||
<div class="follow-container">
|
||||
<div class="actions">
|
||||
<MkFollowButton
|
||||
v-if="$i != null && $i.id != user.id"
|
||||
:user="user"
|
||||
@refresh="emit('refresh')"
|
||||
:inline="true"
|
||||
|
|
|
@ -199,6 +199,11 @@ export const routes = [
|
|||
name: "api",
|
||||
component: page(() => import("./pages/settings/api.vue")),
|
||||
},
|
||||
{
|
||||
path: "/apps",
|
||||
name: "apps",
|
||||
component: page(() => import("./pages/settings/apps.vue")),
|
||||
},
|
||||
{
|
||||
path: "/webhook/edit/:webhookId",
|
||||
name: "webhook",
|
||||
|
|
|
@ -9,6 +9,7 @@ const animatedMfm = [
|
|||
"jump",
|
||||
"bounce",
|
||||
"rainbow",
|
||||
"fade",
|
||||
];
|
||||
|
||||
export function extractMfmWithAnimation(nodes: mfm.MfmNode[]): string[] {
|
||||
|
|
|
@ -19,4 +19,5 @@ export const MFM_TAGS = [
|
|||
"rainbow",
|
||||
"sparkle",
|
||||
"rotate",
|
||||
"fade",
|
||||
];
|
||||
|
|
|
@ -103,7 +103,7 @@ body::-webkit-scrollbar-thumb {
|
|||
}
|
||||
|
||||
html._themeChanging_ {
|
||||
&, * {
|
||||
&, *, ::before, ::after {
|
||||
transition: background 1s ease, border 1s ease !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -402,7 +402,6 @@ function more(ev: MouseEvent) {
|
|||
position: relative;
|
||||
width: 32px;
|
||||
margin-right: 8px;
|
||||
transform: translateY(0.15em);
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
|
@ -524,7 +523,6 @@ function more(ev: MouseEvent) {
|
|||
> .icon {
|
||||
position: relative;
|
||||
color: var(--fgOnAccent);
|
||||
transform: translate(0.15em, 0em);
|
||||
}
|
||||
|
||||
> .text {
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
|
||||
<div v-if="isMobile" class="buttons">
|
||||
<button
|
||||
:aria-label="i18n.t('menu')"
|
||||
class="button nav _button"
|
||||
@click="drawerMenuShowing = true"
|
||||
>
|
||||
|
@ -119,10 +120,15 @@
|
|||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
</button>
|
||||
<button class="button home _button" @click="mainRouter.push('/')">
|
||||
<button
|
||||
:aria-label="i18n.t('home')"
|
||||
class="button home _button"
|
||||
@click="mainRouter.push('/')"
|
||||
>
|
||||
<i class="ph-house ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button
|
||||
:aria-label="i18n.t('notifications')"
|
||||
class="button notifications _button"
|
||||
@click="mainRouter.push('/my/notifications')"
|
||||
>
|
||||
|
@ -131,7 +137,11 @@
|
|||
><i class="ph-circle ph-fill"></i
|
||||
></span>
|
||||
</button>
|
||||
<button class="button post _button" @click="os.post()">
|
||||
<button
|
||||
:aria-label="i18n.t('note')"
|
||||
class="button post _button"
|
||||
@click="os.post()"
|
||||
>
|
||||
<i class="ph-pencil ph-bold ph-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
<div v-if="isMobile" class="buttons">
|
||||
<button
|
||||
:aria-label="i18n.t('menu')"
|
||||
class="button nav _button"
|
||||
@click="drawerMenuShowing = true"
|
||||
>
|
||||
|
@ -47,6 +48,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
:aria-label="i18n.t('home')"
|
||||
class="button home _button"
|
||||
@click="
|
||||
mainRouter.currentRoute.value.name === 'index'
|
||||
|
@ -63,6 +65,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
:aria-label="i18n.t('notifications')"
|
||||
class="button notifications _button"
|
||||
@click="
|
||||
mainRouter.push('/my/notifications');
|
||||
|
@ -80,6 +83,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
:aria-label="i18n.t('messaging')"
|
||||
class="button messaging _button"
|
||||
@click="
|
||||
mainRouter.push('/my/messaging');
|
||||
|
@ -99,6 +103,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
:aria-label="i18n.t('_deck._columns.widgets')"
|
||||
class="button widget _button"
|
||||
@click="widgetsShowing = true"
|
||||
>
|
||||
|
@ -111,6 +116,7 @@
|
|||
<button
|
||||
v-if="isMobile && mainRouter.currentRoute.value.name === 'index'"
|
||||
ref="postButton"
|
||||
:aria-label="i18n.t('note')"
|
||||
class="postButton button post _button"
|
||||
@click="os.post()"
|
||||
>
|
||||
|
@ -122,6 +128,7 @@
|
|||
"
|
||||
ref="postButton"
|
||||
class="postButton button post _button"
|
||||
:aria-label="i18n.t('startMessaging')"
|
||||
@click="messagingStart"
|
||||
>
|
||||
<i class="ph-user-plus ph-bold ph-lg"></i>
|
||||
|
|
|
@ -264,8 +264,8 @@ importers:
|
|||
specifier: 7.0.2
|
||||
version: 7.0.2(@types/koa@2.13.5)(ejs@3.1.8)(pug@3.0.2)
|
||||
mfm-js:
|
||||
specifier: 0.23.2
|
||||
version: 0.23.2
|
||||
specifier: 0.23.3
|
||||
version: 0.23.3
|
||||
mime-types:
|
||||
specifier: 2.1.35
|
||||
version: 2.1.35
|
||||
|
@ -840,8 +840,8 @@ importers:
|
|||
specifier: 2.1.0
|
||||
version: 2.1.0
|
||||
swiper:
|
||||
specifier: ^8.4.5
|
||||
version: 8.4.5
|
||||
specifier: 9.3.2
|
||||
version: 9.3.2
|
||||
syuilo-password-strength:
|
||||
specifier: 0.0.1
|
||||
version: 0.0.1
|
||||
|
@ -6411,12 +6411,6 @@ packages:
|
|||
domhandler: 5.0.3
|
||||
entities: 4.4.0
|
||||
|
||||
/dom7@4.0.4:
|
||||
resolution: {integrity: sha512-DSSgBzQ4rJWQp1u6o+3FVwMNnT5bzQbMb+o31TjYYeRi05uAcpF8koxdfzeoe5ElzPmua7W7N28YJhF7iEKqIw==}
|
||||
dependencies:
|
||||
ssr-window: 4.0.2
|
||||
dev: true
|
||||
|
||||
/domelementtype@1.3.1:
|
||||
resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
|
||||
dev: false
|
||||
|
@ -10424,17 +10418,10 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mfm-js@0.23.2:
|
||||
resolution: {integrity: sha512-lfYvsMr6FIYbt0ZDL+nY+GWWqmcXpe9jrYLBLy5vvQHwGfPALpx43uNHj8hZsakgM82hPMo/zdx0e9tj+4Z4IA==}
|
||||
dependencies:
|
||||
twemoji-parser: 14.0.0
|
||||
dev: false
|
||||
|
||||
/mfm-js@0.23.3:
|
||||
resolution: {integrity: sha512-o8scYmbey6rMUmWAlT3k3ntt6khaCLdxlmHhAWV5wTTMj2OK1atQvZfRUq0SIVm1Jig08qlZg/ps71xUqrScNA==}
|
||||
dependencies:
|
||||
twemoji-parser: 14.0.0
|
||||
dev: true
|
||||
|
||||
/micromatch@3.1.10:
|
||||
resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}
|
||||
|
@ -13594,12 +13581,10 @@ packages:
|
|||
webpack: 5.75.0(@swc/core@1.3.50)(webpack-cli@5.0.1)
|
||||
dev: true
|
||||
|
||||
/swiper@8.4.5:
|
||||
resolution: {integrity: sha512-zveyEFBBv4q1sVkbJHnuH4xCtarKieavJ4SxP0QEHvdpPLJRuD7j/Xg38IVVLbp7Db6qrPsLUePvxohYx39Agw==}
|
||||
/swiper@9.3.2:
|
||||
resolution: {integrity: sha512-Kj9Z4kXRmJR3YT/Wj+XLWj8P6IcRt+WG38uL8M3/Wny7+6sV0TlP9vnE1X+Co9c7VzNooojWGnFa+Wf/9+CUMA==}
|
||||
engines: {node: '>= 4.7.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
dom7: 4.0.4
|
||||
ssr-window: 4.0.2
|
||||
dev: true
|
||||
|
||||
|
|
Loading…
Reference in a new issue