Merge remote-tracking branch 'upstream/develop' into Dockerfile-optimization
This commit is contained in:
commit
21a59dc497
|
@ -11,10 +11,5 @@ pipeline:
|
||||||
password:
|
password:
|
||||||
# Secret 'docker_password' needs to be set in the CI settings
|
# Secret 'docker_password' needs to be set in the CI settings
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
when:
|
|
||||||
# Push new version of tag latest if new push on main-branch
|
|
||||||
event: push
|
|
||||||
branch: main
|
|
||||||
|
|
||||||
depends_on:
|
branch: main
|
||||||
- prSecurityCheck
|
|
||||||
|
|
|
@ -17,5 +17,3 @@ pipeline:
|
||||||
event: tag
|
event: tag
|
||||||
tag: v*
|
tag: v*
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- prSecurityCheck
|
|
||||||
|
|
10286
CHANGELOG.md
10286
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -11,7 +11,7 @@
|
||||||
[![translate-badge](https://hosted.weblate.org/widgets/calckey/-/svg-badge.svg)](https://hosted.weblate.org/engage/calckey/)
|
[![translate-badge](https://hosted.weblate.org/widgets/calckey/-/svg-badge.svg)](https://hosted.weblate.org/engage/calckey/)
|
||||||
[![docker badge](https://img.shields.io/docker/pulls/thatonecalculator/calckey?logo=docker)](https://hub.docker.com/r/thatonecalculator/calckey)
|
[![docker badge](https://img.shields.io/docker/pulls/thatonecalculator/calckey?logo=docker)](https://hub.docker.com/r/thatonecalculator/calckey)
|
||||||
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](./CODE_OF_CONDUCT.md)
|
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](./CODE_OF_CONDUCT.md)
|
||||||
[![lavaforge badge](https://custom-icon-badges.demolab.com/badge/hosted%20on-lavaforge-FF8066.svg?logo=lavaforge&logoColor=white)](https://codeberg.org/calckey/calckey/)
|
[![Codeberg badge](https://custom-icon-badges.demolab.com/badge/hosted%20on-codeberg-4793CC.svg?logo=codeberg&logoColor=white)](https://codeberg.org/calckey/calckey/)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
- 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
|
- 💸 Liberapay: <https://liberapay.com/ThatOneCalculator>
|
||||||
- Donate publicly to get your name on the Patron list!
|
- Donate publicly to get your name on the Patron list!
|
||||||
- 🚢 Flagship instance: <https://i.calckey.cloud>
|
- 🚢 Flagship instance: <https://calckey.social>
|
||||||
- 📣 Official account: <https://i.calckey.cloud/@calckey>
|
- 📣 Official account: <https://i.calckey.cloud/@calckey>
|
||||||
- 💁 Matrix support room: <https://matrix.to/#/#calckey:matrix.fedibird.com>
|
- 💁 Matrix support room: <https://matrix.to/#/#calckey:matrix.fedibird.com>
|
||||||
- 📜 Instance list: <https://calckey.fediverse.observer/list>
|
- 📜 Instance list: <https://calckey.fediverse.observer/list>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
_lang_: "Deutsch"
|
_lang_: "Deutsch"
|
||||||
headlineMisskey: "Ein durch Notizen verbundenes Netzwerk"
|
headlineMisskey: "Ein durch Posts verbundenes Netzwerk"
|
||||||
introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
|
introMisskey: "Willkommen! Calckey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Posts“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Posts anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
|
||||||
monthAndDay: "{day}.{month}."
|
monthAndDay: "{day}.{month}."
|
||||||
search: "Suchen"
|
search: "Suchen"
|
||||||
notifications: "Benachrichtigungen"
|
notifications: "Benachrichtigungen"
|
||||||
|
|
|
@ -816,6 +816,7 @@ lastCommunication: "Last communication"
|
||||||
resolved: "Resolved"
|
resolved: "Resolved"
|
||||||
unresolved: "Unresolved"
|
unresolved: "Unresolved"
|
||||||
breakFollow: "Remove follower"
|
breakFollow: "Remove follower"
|
||||||
|
breakFollowConfirm: "Are you sure want to remove follower?"
|
||||||
itsOn: "Enabled"
|
itsOn: "Enabled"
|
||||||
itsOff: "Disabled"
|
itsOff: "Disabled"
|
||||||
emailRequiredForSignup: "Require email address for sign-up"
|
emailRequiredForSignup: "Require email address for sign-up"
|
||||||
|
|
|
@ -816,6 +816,7 @@ lastCommunication: "直近の通信"
|
||||||
resolved: "解決済み"
|
resolved: "解決済み"
|
||||||
unresolved: "未解決"
|
unresolved: "未解決"
|
||||||
breakFollow: "フォロワーを解除"
|
breakFollow: "フォロワーを解除"
|
||||||
|
breakFollowConfirm: "フォロワー解除しますか?"
|
||||||
itsOn: "オンになっています"
|
itsOn: "オンになっています"
|
||||||
itsOff: "オフになっています"
|
itsOff: "オフになっています"
|
||||||
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "calckey",
|
"name": "calckey",
|
||||||
"version": "13.0.9-rc",
|
"version": "13.1.2",
|
||||||
"codename": "aqua",
|
"codename": "aqua",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://codeberg.org/calckey/calckey.git"
|
"url": "https://codeberg.org/calckey/calckey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@7.26.0",
|
"packageManager": "pnpm@7.26.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",
|
"rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp",
|
||||||
|
|
|
@ -2,9 +2,9 @@ export function nyaize(text: string): string {
|
||||||
return (
|
return (
|
||||||
text
|
text
|
||||||
// ja-JP
|
// ja-JP
|
||||||
.replace(/な/g, "にゃ")
|
.replaceAll("な", "にゃ")
|
||||||
.replace(/ナ/g, "ニャ")
|
.replaceAll("ナ", "ニャ")
|
||||||
.replace(/ナ/g, "ニャ")
|
.replaceAll("ナ", "ニャ")
|
||||||
// en-US
|
// en-US
|
||||||
.replace(/(?<=n)a/gi, (x) => (x === "A" ? "YA" : "ya"))
|
.replace(/(?<=n)a/gi, (x) => (x === "A" ? "YA" : "ya"))
|
||||||
.replace(/(?<=morn)ing/gi, (x) => (x === "ING" ? "YAN" : "yan"))
|
.replace(/(?<=morn)ing/gi, (x) => (x === "ING" ? "YAN" : "yan"))
|
||||||
|
|
|
@ -35,6 +35,12 @@ export function convertLegacyReactions(reactions: Record<string, number>) {
|
||||||
} else {
|
} else {
|
||||||
_reactions[legacies[reaction]] = reactions[reaction];
|
_reactions[legacies[reaction]] = reactions[reaction];
|
||||||
}
|
}
|
||||||
|
} else if (reaction === "♥️") {
|
||||||
|
if (_reactions["❤️"]) {
|
||||||
|
_reactions["❤️"] += reactions[reaction];
|
||||||
|
} else {
|
||||||
|
_reactions["❤️"] = reactions[reaction];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_reactions[reaction]) {
|
if (_reactions[reaction]) {
|
||||||
_reactions[reaction] += reactions[reaction];
|
_reactions[reaction] += reactions[reaction];
|
||||||
|
@ -61,14 +67,16 @@ export async function toDbReaction(
|
||||||
|
|
||||||
reacterHost = toPunyNullable(reacterHost);
|
reacterHost = toPunyNullable(reacterHost);
|
||||||
|
|
||||||
// 文字列タイプのリアクションを絵文字に変換
|
// Convert string-type reactions to unicode
|
||||||
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
|
||||||
|
// Convert old heart to new
|
||||||
|
if (reaction === "♥️") return "❤️";
|
||||||
|
|
||||||
// Unicode絵文字
|
// Allow unicode reactions
|
||||||
const match = emojiRegex.exec(reaction);
|
const match = emojiRegex.exec(reaction);
|
||||||
if (match) {
|
if (match) {
|
||||||
const unicode = match[0];
|
const unicode = match[0];
|
||||||
return unicode.match("\u200d") ? unicode : unicode.replace(/\ufe0f/g, "");
|
return unicode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
|
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ps.tag) {
|
if (ps.tag) {
|
||||||
if (!safeForSql(ps.tag)) throw new Error("Injection");
|
if (!safeForSql(normalizeForSearch(ps.tag))) throw "Injection";
|
||||||
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
|
query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
|
||||||
} else {
|
} else {
|
||||||
query.andWhere(
|
query.andWhere(
|
||||||
|
@ -102,7 +102,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
qb.orWhere(
|
qb.orWhere(
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (!safeForSql(tag)) throw new Error("Injection");
|
if (!safeForSql(normalizeForSearch(ps.tag)))
|
||||||
|
throw "Injection";
|
||||||
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
|
qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -11,11 +11,7 @@ import { InternalStorage } from "@/services/drive/internal-storage.js";
|
||||||
import { createTemp } from "@/misc/create-temp.js";
|
import { createTemp } from "@/misc/create-temp.js";
|
||||||
import { downloadUrl } from "@/misc/download-url.js";
|
import { downloadUrl } from "@/misc/download-url.js";
|
||||||
import { detectType } from "@/misc/get-file-info.js";
|
import { detectType } from "@/misc/get-file-info.js";
|
||||||
import {
|
import { convertToWebp } from "@/services/drive/image-processor.js";
|
||||||
convertToWebp,
|
|
||||||
convertToJpeg,
|
|
||||||
convertToPng,
|
|
||||||
} from "@/services/drive/image-processor.js";
|
|
||||||
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
|
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
|
||||||
import { StatusError } from "@/misc/fetch.js";
|
import { StatusError } from "@/misc/fetch.js";
|
||||||
import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
|
import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
|
||||||
|
@ -77,7 +73,7 @@ export default async function (ctx: Koa.Context) {
|
||||||
"image/avif",
|
"image/avif",
|
||||||
].includes(mime)
|
].includes(mime)
|
||||||
) {
|
) {
|
||||||
return await convertToWebp(path, 498, 280);
|
return await convertToWebp(path, 996, 560);
|
||||||
} else if (mime.startsWith("video/")) {
|
} else if (mime.startsWith("video/")) {
|
||||||
return await GenerateVideoThumbnail(path);
|
return await GenerateVideoThumbnail(path);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +81,7 @@ export default async function (ctx: Koa.Context) {
|
||||||
|
|
||||||
if (isWebpublic) {
|
if (isWebpublic) {
|
||||||
if (["image/svg+xml"].includes(mime)) {
|
if (["image/svg+xml"].includes(mime)) {
|
||||||
return await convertToPng(path, 2048, 2048);
|
return await convertToWebp(path, 2048, 2048, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,9 @@ export async function proxyMedia(ctx: Koa.Context) {
|
||||||
let image: IImage;
|
let image: IImage;
|
||||||
|
|
||||||
if ("static" in ctx.query && isConvertibleImage) {
|
if ("static" in ctx.query && isConvertibleImage) {
|
||||||
image = await convertToWebp(path, 498, 280);
|
image = await convertToWebp(path, 996, 560);
|
||||||
} else if ("preview" in ctx.query && isConvertibleImage) {
|
} else if ("preview" in ctx.query && isConvertibleImage) {
|
||||||
image = await convertToWebp(path, 200, 200);
|
image = await convertToWebp(path, 400, 400);
|
||||||
} else if ("badge" in ctx.query) {
|
} else if ("badge" in ctx.query) {
|
||||||
if (!isConvertibleImage) {
|
if (!isConvertibleImage) {
|
||||||
// 画像でないなら404でお茶を濁す
|
// 画像でないなら404でお茶を濁す
|
||||||
|
|
|
@ -5,6 +5,9 @@ block vars
|
||||||
- const title = privateMode ? instanceName : (user.name ? `${user.name} (@${user.username})` : `@${user.username}`);
|
- const title = privateMode ? instanceName : (user.name ? `${user.name} (@${user.username})` : `@${user.username}`);
|
||||||
- const url = `${config.url}/notes/${note.id}`;
|
- const url = `${config.url}/notes/${note.id}`;
|
||||||
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||||
|
- const isImage = note.files.length !== 0 && note.files[0].type.startsWith('image');
|
||||||
|
- const isVideo = note.files.length !== 0 && note.files[0].type.startsWith('video');
|
||||||
|
- const imageUrl = isImage ? note.files[0].url : isVideo ? note.files[0].thumbnailUrl : avatarUrl;
|
||||||
|
|
||||||
block title
|
block title
|
||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
@ -19,7 +22,15 @@ block og
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:description' content= summary)
|
meta(property='og:description' content= summary)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:url' content= url)
|
||||||
meta(property='og:image' content= avatarUrl)
|
meta(property='og:image' content= imageUrl)
|
||||||
|
if isImage
|
||||||
|
meta(property='og:image:width' content=note.files[0].properties.width)
|
||||||
|
meta(property='og:image:height' content=note.files[0].properties.height)
|
||||||
|
meta(property='og:image:type' content=note.files[0].type)
|
||||||
|
meta(property='twitter:card' content="summary_large_image")
|
||||||
|
if isVideo
|
||||||
|
meta(property='og:video:type' content=note.files[0].type)
|
||||||
|
meta(property='og:video' content=note.files[0].url)
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
unless privateMode
|
unless privateMode
|
||||||
|
|
|
@ -30,11 +30,7 @@ import { IdentifiableError } from "@/misc/identifiable-error.js";
|
||||||
import { getS3 } from "./s3.js";
|
import { getS3 } from "./s3.js";
|
||||||
import { InternalStorage } from "./internal-storage.js";
|
import { InternalStorage } from "./internal-storage.js";
|
||||||
import type { IImage } from "./image-processor.js";
|
import type { IImage } from "./image-processor.js";
|
||||||
import {
|
import { convertSharpToWebp } from "./image-processor.js";
|
||||||
convertSharpToJpeg,
|
|
||||||
convertSharpToWebp,
|
|
||||||
convertSharpToPng,
|
|
||||||
} from "./image-processor.js";
|
|
||||||
import { driveLogger } from "./logger.js";
|
import { driveLogger } from "./logger.js";
|
||||||
import { GenerateVideoThumbnail } from "./generate-video-thumbnail.js";
|
import { GenerateVideoThumbnail } from "./generate-video-thumbnail.js";
|
||||||
import { deleteFile } from "./delete-file.js";
|
import { deleteFile } from "./delete-file.js";
|
||||||
|
@ -75,8 +71,8 @@ async function save(
|
||||||
if (type === "image/vnd.mozilla.apng") ext = ".apng";
|
if (type === "image/vnd.mozilla.apng") ext = ".apng";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、
|
// Some cloud providers (notably upcloud) will infer the content-type based
|
||||||
// 許可されているファイル形式でしか拡張子をつけない
|
// on extension, so we remove extensions from non-browser-safe types.
|
||||||
if (!FILE_TYPE_BROWSERSAFE.includes(type)) {
|
if (!FILE_TYPE_BROWSERSAFE.includes(type)) {
|
||||||
ext = "";
|
ext = "";
|
||||||
}
|
}
|
||||||
|
@ -282,13 +278,13 @@ export async function generateAlts(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (["image/jpeg"].includes(type)) {
|
if (["image/jpeg"].includes(type)) {
|
||||||
webpublic = await convertSharpToJpeg(img, 2048, 2048);
|
webpublic = await convertSharpToWebp(img, 2048, 2048);
|
||||||
} else if (["image/webp"].includes(type)) {
|
} else if (["image/webp"].includes(type)) {
|
||||||
webpublic = await convertSharpToPng(img, 2048, 2048);
|
webpublic = await convertSharpToWebp(img, 2048, 2048);
|
||||||
} else if (["image/png"].includes(type)) {
|
} else if (["image/png"].includes(type)) {
|
||||||
webpublic = await convertSharpToPng(img, 2048, 2048);
|
webpublic = await convertSharpToWebp(img, 2048, 2048, 100);
|
||||||
} else if (["image/svg+xml"].includes(type)) {
|
} else if (["image/svg+xml"].includes(type)) {
|
||||||
webpublic = await convertSharpToPng(img, 2048, 2048);
|
webpublic = await convertSharpToWebp(img, 2048, 2048);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("web image not created (not an required image)");
|
logger.debug("web image not created (not an required image)");
|
||||||
}
|
}
|
||||||
|
@ -315,7 +311,7 @@ export async function generateAlts(
|
||||||
"image/avif",
|
"image/avif",
|
||||||
].includes(type)
|
].includes(type)
|
||||||
) {
|
) {
|
||||||
thumbnail = await convertSharpToWebp(img, 498, 280);
|
thumbnail = await convertSharpToWebp(img, 996, 560);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("thumbnail not created (not an required file)");
|
logger.debug("thumbnail not created (not an required file)");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import { createTempDir } from "@/misc/create-temp.js";
|
import { createTempDir } from "@/misc/create-temp.js";
|
||||||
import type { IImage } from "./image-processor.js";
|
import type { IImage } from "./image-processor.js";
|
||||||
import { convertToJpeg } from "./image-processor.js";
|
import { convertToWebp } from "./image-processor.js";
|
||||||
import FFmpeg from "fluent-ffmpeg";
|
import FFmpeg from "fluent-ffmpeg";
|
||||||
|
|
||||||
export async function GenerateVideoThumbnail(source: string): Promise<IImage> {
|
export async function GenerateVideoThumbnail(source: string): Promise<IImage> {
|
||||||
|
@ -22,8 +22,7 @@ export async function GenerateVideoThumbnail(source: string): Promise<IImage> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる)
|
return await convertToWebp(`${dir}/out.png`, 996, 560);
|
||||||
return await convertToJpeg(`${dir}/out.png`, 498, 280);
|
|
||||||
} finally {
|
} finally {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,42 +6,6 @@ export type IImage = {
|
||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert to JPEG
|
|
||||||
* with resize, remove metadata, resolve orientation, stop animation
|
|
||||||
*/
|
|
||||||
export async function convertToJpeg(
|
|
||||||
path: string,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
): Promise<IImage> {
|
|
||||||
return convertSharpToJpeg(await sharp(path), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function convertSharpToJpeg(
|
|
||||||
sharp: sharp.Sharp,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
): Promise<IImage> {
|
|
||||||
const data = await sharp
|
|
||||||
.resize(width, height, {
|
|
||||||
fit: "inside",
|
|
||||||
withoutEnlargement: true,
|
|
||||||
})
|
|
||||||
.rotate()
|
|
||||||
.jpeg({
|
|
||||||
quality: 85,
|
|
||||||
progressive: true,
|
|
||||||
})
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
ext: "jpg",
|
|
||||||
type: "image/jpeg",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert to WebP
|
* Convert to WebP
|
||||||
* with resize, remove metadata, resolve orientation, stop animation
|
* with resize, remove metadata, resolve orientation, stop animation
|
||||||
|
@ -78,36 +42,3 @@ export async function convertSharpToWebp(
|
||||||
type: "image/webp",
|
type: "image/webp",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert to PNG
|
|
||||||
* with resize, remove metadata, resolve orientation, stop animation
|
|
||||||
*/
|
|
||||||
export async function convertToPng(
|
|
||||||
path: string,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
): Promise<IImage> {
|
|
||||||
return convertSharpToPng(await sharp(path), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function convertSharpToPng(
|
|
||||||
sharp: sharp.Sharp,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
): Promise<IImage> {
|
|
||||||
const data = await sharp
|
|
||||||
.resize(width, height, {
|
|
||||||
fit: "inside",
|
|
||||||
withoutEnlargement: true,
|
|
||||||
})
|
|
||||||
.rotate()
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
ext: "png",
|
|
||||||
type: "image/png",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -209,12 +209,12 @@ export default async function (
|
||||||
await Blockings.delete(blocking.id);
|
await Blockings.delete(blocking.id);
|
||||||
} else {
|
} else {
|
||||||
// それ以外は単純に例外
|
// それ以外は単純に例外
|
||||||
if (blocking != null)
|
if (blocking)
|
||||||
throw new IdentifiableError(
|
throw new IdentifiableError(
|
||||||
"710e8fb0-b8c3-4922-be49-d5d93d8e6a6e",
|
"710e8fb0-b8c3-4922-be49-d5d93d8e6a6e",
|
||||||
"blocking",
|
"blocking",
|
||||||
);
|
);
|
||||||
if (blocked != null)
|
if (blocked)
|
||||||
throw new IdentifiableError(
|
throw new IdentifiableError(
|
||||||
"3338392a-f764-498d-8855-db939dcf8c48",
|
"3338392a-f764-498d-8855-db939dcf8c48",
|
||||||
"blocked",
|
"blocked",
|
||||||
|
|
|
@ -38,8 +38,8 @@ export default async function (
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (blocking != null) throw new Error("blocking");
|
if (blocking) throw new Error("blocking");
|
||||||
if (blocked != null) throw new Error("blocked");
|
if (blocked) throw new Error("blocked");
|
||||||
|
|
||||||
const followRequest = await FollowRequests.insert({
|
const followRequest = await FollowRequests.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vanilla-tilt": "1.8.0",
|
"vanilla-tilt": "1.8.0",
|
||||||
"vite": "^4.0.4",
|
"vite": "^4.1.0-beta.1",
|
||||||
"vue": "3.2.45",
|
"vue": "3.2.45",
|
||||||
"vue-isyourpasswordsafe": "^2.0.0",
|
"vue-isyourpasswordsafe": "^2.0.0",
|
||||||
"vue-plyr": "^7.0.0",
|
"vue-plyr": "^7.0.0",
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<XModalWindow
|
||||||
|
ref="dialog"
|
||||||
|
:width="600"
|
||||||
|
@close="dialog?.close()"
|
||||||
|
@closed="$emit('closed')"
|
||||||
|
>
|
||||||
|
<template #header>{{ i18n.ts._mfm.cheatSheet }}</template>
|
||||||
|
|
||||||
|
<div class="_monolithic_">
|
||||||
|
<div class="_section">
|
||||||
|
<XCheatSheet/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</XModalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import XModalWindow from '@/components/MkModalWindow.vue';
|
||||||
|
import XCheatSheet from '@/pages/mfm-cheat-sheet.vue';
|
||||||
|
import { i18n } from '@/i18n';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'done'): void;
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
|
function close(res) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -316,7 +316,7 @@ function done(query?: any): boolean | void {
|
||||||
if (query == null) query = q.value;
|
if (query == null) query = q.value;
|
||||||
if (query == null || typeof query !== 'string') return;
|
if (query == null || typeof query !== 'string') return;
|
||||||
|
|
||||||
const q2 = query.replace(/:/g, '');
|
const q2 = query.replaceAll(':', '');
|
||||||
const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2);
|
const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2);
|
||||||
if (exactMatchCustom) {
|
if (exactMatchCustom) {
|
||||||
chosen(exactMatchCustom);
|
chosen(exactMatchCustom);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="xubzgfgb" :class="{ cover }" :title="title">
|
<div class="xubzgfgb" :class="{ cover }" :title="title">
|
||||||
<canvas v-if="!loaded" ref="canvas" :width="size" :height="size" :title="title"/>
|
<canvas v-if="!loaded" ref="canvas" :width="size" :height="size" :title="title"/>
|
||||||
<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/>
|
<img v-if="src" :src="src" :title="title" :type="type" :alt="alt" @load="onLoad"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,11 +13,13 @@ const props = withDefaults(defineProps<{
|
||||||
src?: string | null;
|
src?: string | null;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
|
type?: string | null;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
size?: number;
|
size?: number;
|
||||||
cover?: boolean;
|
cover?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
src: null,
|
src: null,
|
||||||
|
type: null,
|
||||||
alt: '',
|
alt: '',
|
||||||
title: null,
|
title: null,
|
||||||
size: 64,
|
size: 64,
|
||||||
|
|
|
@ -31,7 +31,7 @@ const computedStyle = getComputedStyle(document.documentElement);
|
||||||
const themeColor = instance.themeColor ?? computedStyle.getPropertyValue('--bg');
|
const themeColor = instance.themeColor ?? computedStyle.getPropertyValue('--bg');
|
||||||
|
|
||||||
const bg = {
|
const bg = {
|
||||||
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}33)`,
|
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}55)`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInstanceIcon(instance): string {
|
function getInstanceIcon(instance): string {
|
||||||
|
@ -41,11 +41,15 @@ function getInstanceIcon(instance): string {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.hpaizdrt {
|
.hpaizdrt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
padding: .2em .4em;
|
padding: .2em .4em;
|
||||||
|
padding: .2em .4em;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
text-shadow: 0 2px 2px var(--shadow);
|
text-shadow: 0 2px 2px var(--shadow);
|
||||||
|
@ -54,6 +58,10 @@ function getInstanceIcon(instance): string {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
.header > .body & {
|
||||||
|
width: max-content;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -61,17 +69,24 @@ function getInstanceIcon(instance): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
> .name {
|
> .name {
|
||||||
|
display: none;
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
font-size: 0.85em;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
text-shadow: -1px -1px 0 var(--bg), 1px -1px 0 var(--bg), -1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
|
text-shadow: -1px -1px 0 var(--bg), 1px -1px 0 var(--bg), -1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
|
||||||
.article > .main &, .header > .body & {
|
.article > .main &, .header > .body & {
|
||||||
display: unset;
|
display: unset;
|
||||||
}
|
}
|
||||||
|
.article > .main &, .header > .body & {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
:href="image.url"
|
:href="image.url"
|
||||||
:title="image.name"
|
:title="image.name"
|
||||||
>
|
>
|
||||||
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/>
|
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :type="image.type" :title="image.comment" :cover="false"/>
|
||||||
<div v-if="image.type === 'image/gif'" class="gif">GIF</div>
|
<div v-if="image.type === 'image/gif'" class="gif">GIF</div>
|
||||||
</a>
|
</a>
|
||||||
<button v-tooltip="i18n.ts.hide" class="_button hide" @click="hide = true"><i class="ph-eye-slash-bold ph-lg"></i></button>
|
<button v-tooltip="i18n.ts.hide" class="_button hide" @click="hide = true"><i class="ph-eye-slash-bold ph-lg"></i></button>
|
||||||
|
|
|
@ -143,6 +143,8 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
|
@ -10,32 +10,35 @@
|
||||||
:class="{ renote: isRenote }"
|
:class="{ renote: isRenote }"
|
||||||
>
|
>
|
||||||
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
|
<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
|
||||||
<div v-if="pinned" class="info"><i class="ph-push-pin-bold ph-lg"></i> {{ i18n.ts.pinnedNote }}</div>
|
<div class="note-context">
|
||||||
<div v-if="appearNote._prId_" class="info"><i class="ph-megaphone-simple-bold ph-lg"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ph-x-bold ph-lg"></i></button></div>
|
<div class="line"></div>
|
||||||
<div v-if="appearNote._featuredId_" class="info"><i class="ph-lightning-bold ph-lg"></i> {{ i18n.ts.featured }}</div>
|
<div v-if="appearNote._prId_" class="info"><i class="ph-megaphone-simple-bold ph-lg"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ph-x-bold ph-lg"></i></button></div>
|
||||||
<div v-if="isRenote" class="renote">
|
<div v-if="appearNote._featuredId_" class="info"><i class="ph-lightning-bold ph-lg"></i> {{ i18n.ts.featured }}</div>
|
||||||
<!-- <MkAvatar class="avatar" :user="note.user"/> -->
|
<div v-if="pinned" class="info"><i class="ph-push-pin-bold ph-lg"></i>{{ i18n.ts.pinnedNote }}</div>
|
||||||
<i class="ph-repeat-bold ph-lg"></i>
|
<div v-if="isRenote" class="renote">
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
<i class="ph-repeat-bold ph-lg"></i>
|
||||||
<template #user>
|
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||||
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
|
<template #user>
|
||||||
<MkUserName :user="note.user"/>
|
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
|
||||||
</MkA>
|
<MkUserName :user="note.user"/>
|
||||||
</template>
|
</MkA>
|
||||||
</I18n>
|
</template>
|
||||||
<div class="info">
|
</I18n>
|
||||||
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
|
<div class="info">
|
||||||
<i v-if="isMyRenote" class="ph-dots-three-outline-bold ph-lg dropdownIcon"></i>
|
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
|
||||||
<MkTime :time="note.createdAt"/>
|
<i v-if="isMyRenote" class="ph-dots-three-outline-bold ph-lg dropdownIcon"></i>
|
||||||
</button>
|
<MkTime :time="note.createdAt"/>
|
||||||
<MkVisibility :note="note"/>
|
</button>
|
||||||
|
<MkVisibility :note="note"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<article class="article" @contextmenu.stop="onContextmenu" @click.self="router.push(notePage(appearNote))">
|
<article class="article" @contextmenu.stop="onContextmenu" @click.self="router.push(notePage(appearNote))">
|
||||||
<MkAvatar class="avatar" :user="appearNote.user"/>
|
|
||||||
<div class="main" @click.self="router.push(notePage(appearNote))">
|
<div class="main" @click.self="router.push(notePage(appearNote))">
|
||||||
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
|
<div class="header-container">
|
||||||
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
|
<MkAvatar class="avatar" :user="appearNote.user"/>
|
||||||
|
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
|
||||||
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p v-if="appearNote.cw != null" class="cw">
|
<p v-if="appearNote.cw != null" class="cw">
|
||||||
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
||||||
|
@ -43,7 +46,6 @@
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
|
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
|
||||||
<div class="text" @click.self="router.push(notePage(appearNote))">
|
<div class="text" @click.self="router.push(notePage(appearNote))">
|
||||||
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-up-left-bold ph-lg"></i></MkA>
|
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
||||||
<!-- <a v-if="appearNote.renote != null" class="rp">RN:</a> -->
|
<!-- <a v-if="appearNote.renote != null" class="rp">RN:</a> -->
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
|
@ -344,146 +346,138 @@ function readPromo() {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px 32px 8px 32px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-size: 90%;
|
|
||||||
white-space: pre;
|
|
||||||
color: #f6c177;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .hide {
|
|
||||||
margin-left: auto;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .info + .article {
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .reply-to {
|
> .reply-to {
|
||||||
opacity: 0.7;
|
& + .note-context {
|
||||||
padding-bottom: 0;
|
.line::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
margin-bottom: -10px;
|
||||||
|
width: 2px;
|
||||||
|
background-color: var(--divider);
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .renote {
|
.note-context {
|
||||||
|
padding: 0 32px 0 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
&:first-child {
|
||||||
padding: 16px 32px 8px 32px;
|
margin-top: 20px;
|
||||||
line-height: 28px;
|
}
|
||||||
white-space: pre;
|
> :not(.line) {
|
||||||
color: var(--renote);
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
> .avatar {
|
position: relative;
|
||||||
flex-shrink: 0;
|
margin-bottom: -10px;
|
||||||
display: inline-block;
|
line-height: 28px;
|
||||||
width: 28px;
|
}
|
||||||
height: 28px;
|
> .line {
|
||||||
margin: 0 8px 0 0;
|
width: var(--avatarSize);
|
||||||
border-radius: 6px;
|
display: flex;
|
||||||
|
margin-right: 14px;
|
||||||
|
margin-top: 0;
|
||||||
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
> div > i {
|
||||||
margin-right: 4px;
|
margin-left: -.5px;
|
||||||
}
|
}
|
||||||
|
> .info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 90%;
|
||||||
|
white-space: pre;
|
||||||
|
color: #f6c177;
|
||||||
|
|
||||||
> span {
|
> i {
|
||||||
overflow: hidden;
|
margin-right: 4px;
|
||||||
flex-shrink: 1;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
> .name {
|
> .hide {
|
||||||
font-weight: bold;
|
margin-left: auto;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .info {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
> .time {
|
> .renote {
|
||||||
flex-shrink: 0;
|
display: flex;
|
||||||
color: inherit;
|
align-items: center;
|
||||||
|
white-space: pre;
|
||||||
|
color: var(--renote);
|
||||||
|
|
||||||
> .dropdownIcon {
|
|
||||||
margin-right: 4px;
|
> i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .info {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.9em;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> .time {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: inherit;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
> .dropdownIcon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
> .renote + .article {
|
& + .article {
|
||||||
padding-top: 8px;
|
padding-top: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .article {
|
> .article {
|
||||||
padding: 28px 32px 18px;
|
padding: 28px 32px 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
grid-template-columns: 58px;
|
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .avatar {
|
.header-container {
|
||||||
flex-shrink: 0;
|
display: flex;
|
||||||
display: block;
|
> .avatar {
|
||||||
margin: 0 14px 8px 0;
|
flex-shrink: 0;
|
||||||
grid-row: 1 / span 2;
|
display: block;
|
||||||
width: 48px;
|
margin: 0 14px 0 0;
|
||||||
height: 48px;
|
width: var(--avatarSize);
|
||||||
position: relative;
|
height: var(--avatarSize);
|
||||||
top: 0;
|
position: relative;
|
||||||
left: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
> .header {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .main {
|
> .main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: contents;
|
|
||||||
|
|
||||||
> header.header {
|
|
||||||
display: contents;
|
|
||||||
|
|
||||||
> .name, .info {
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> :not(.ticker) {
|
|
||||||
grid-column: 1 / span 3;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .name, .info {
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .ticker {
|
|
||||||
grid-row: 2;
|
|
||||||
align-self: flex-start;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .ticker {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
margin-top: .2em;
|
margin-top: .7em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-inline: -100px;
|
|
||||||
padding-inline: 100px;
|
|
||||||
|
|
||||||
> .cw {
|
> .cw {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
@ -520,7 +514,11 @@ function readPromo() {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height: 9em;
|
max-height: 9em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
> .text {
|
||||||
|
max-height: 9em;
|
||||||
|
mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||||
|
-webkit-mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||||
|
}
|
||||||
> .fade {
|
> .fade {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -528,7 +526,6 @@ function readPromo() {
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
background: linear-gradient(0deg, var(--panel), var(--X15));
|
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -569,6 +566,10 @@ function readPromo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .files {
|
||||||
|
margin-top: .4em;
|
||||||
|
margin-bottom: .4em;
|
||||||
|
}
|
||||||
> .url-preview {
|
> .url-preview {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
@ -595,15 +596,19 @@ function readPromo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
> .footer {
|
> .footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
> .button {
|
> .button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
flex-grow: 1;
|
||||||
&:not(:last-child) {
|
max-width: 3.5em;
|
||||||
margin-right: 16px;
|
width: max-content;
|
||||||
|
min-width: max-content;
|
||||||
|
&:first-of-type {
|
||||||
|
margin-left: -.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--fgHighlighted);
|
color: var(--fgHighlighted);
|
||||||
}
|
}
|
||||||
|
@ -628,67 +633,35 @@ function readPromo() {
|
||||||
|
|
||||||
&.max-width_500px {
|
&.max-width_500px {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
> .article {
|
|
||||||
> .avatar {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.max-width_450px {
|
&.max-width_450px {
|
||||||
> .renote {
|
--avatarSize: 46px;
|
||||||
padding: 8px 16px 0 16px;
|
padding-top: 6px;
|
||||||
|
> .note-context {
|
||||||
|
padding-inline: 16px;
|
||||||
|
margin-top: 0;
|
||||||
|
> :not(.line) {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
> .line {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .info {
|
|
||||||
padding: 8px 16px 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .article {
|
> .article {
|
||||||
padding: 14px 16px 9px;
|
padding: 16px 16px 9px;
|
||||||
|
|
||||||
> .avatar {
|
> .main > .header-container > .avatar {
|
||||||
margin: 0 10px 8px 0;
|
margin-right: 10px;
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
// top: calc(14px + var(--stickyTop, 0px));
|
// top: calc(14px + var(--stickyTop, 0px));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.max-width_350px {
|
|
||||||
> .article {
|
|
||||||
> .main {
|
|
||||||
> .footer {
|
|
||||||
> .button {
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.max-width_300px {
|
&.max-width_300px {
|
||||||
> .article {
|
--avatarSize: 40px;
|
||||||
> .avatar {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .main {
|
|
||||||
> .footer {
|
|
||||||
> .button {
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,9 +54,7 @@
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent" class="content">
|
<div v-show="appearNote.cw == null || showContent" class="content">
|
||||||
<div class="text" @click.self="router.push(notePage(appearNote))">
|
<div class="text" @click.self="router.push(notePage(appearNote))">
|
||||||
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ph-arrow-bend-up-left-bold ph-lg"></i></MkA>
|
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
|
||||||
<!-- <a v-if="appearNote.renote != null" class="rp">RN:</a> -->
|
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else class="translated">
|
<div v-else class="translated">
|
||||||
|
@ -338,14 +336,12 @@ if (appearNote.replyId) {
|
||||||
&:hover > .article > .main > .footer > .button {
|
&:hover > .article > .main > .footer > .button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .reply-to {
|
> .reply-to {
|
||||||
opacity: 0.7;
|
margin-bottom: -16px;
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .reply-to-more {
|
> .reply-to-more {
|
||||||
opacity: 0.7;
|
// opacity: 0.7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
|
@ -416,21 +412,26 @@ if (appearNote.replyId) {
|
||||||
> .avatar {
|
> .avatar {
|
||||||
display: block;
|
display: block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 52px;
|
width: var(--avatarSize);
|
||||||
height: 52px;
|
height: var(--avatarSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
|
width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-left: 16px;
|
padding-left: 14px;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
|
|
||||||
> .top {
|
> .top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
> .name {
|
> .name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .is-bot {
|
> .is-bot {
|
||||||
|
@ -568,18 +569,18 @@ if (appearNote.replyId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.max-width_450px {
|
&.max-width_450px {
|
||||||
|
|
||||||
|
> .reply-to-more:first-child {
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
> .renote {
|
> .renote {
|
||||||
padding: 8px 16px 0 16px;
|
padding: 8px 16px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .article {
|
> .article {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
> .header > .body {
|
||||||
> .header {
|
padding-left: 10px;
|
||||||
> .avatar {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -602,12 +603,6 @@ if (appearNote.replyId) {
|
||||||
font-size: 0.825em;
|
font-size: 0.825em;
|
||||||
|
|
||||||
> .article {
|
> .article {
|
||||||
> .header {
|
|
||||||
> .avatar {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .main {
|
> .main {
|
||||||
> .footer {
|
> .footer {
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="kkwtjztg">
|
<header class="kkwtjztg">
|
||||||
<MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
<div class="user-info">
|
||||||
<MkUserName :user="note.user" class="mkusername">
|
<div>
|
||||||
<span v-if="note.user.isBot" class="is-bot">bot</span>
|
<MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
||||||
</MkUserName>
|
<MkUserName :user="note.user" class="mkusername">
|
||||||
</MkA>
|
<span v-if="note.user.isBot" class="is-bot">bot</span>
|
||||||
<div class="username"><MkAcct :user="note.user"/></div>
|
</MkUserName>
|
||||||
<div class="info">
|
</MkA>
|
||||||
<MkA class="created-at" :to="notePage(note)">
|
<div class="username"><MkAcct :user="note.user"/></div>
|
||||||
<MkTime :time="note.createdAt"/>
|
</div>
|
||||||
</MkA>
|
<div>
|
||||||
<MkVisibility :note="note"/>
|
<div class="info">
|
||||||
|
<MkA class="created-at" :to="notePage(note)">
|
||||||
|
<MkTime :time="note.createdAt"/>
|
||||||
|
</MkA>
|
||||||
|
<MkVisibility :note="note"/>
|
||||||
|
</div>
|
||||||
|
<MkInstanceTicker v-if="showTicker" class="ticker" :instance="note.user.instance"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,66 +25,124 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import type * as misskey from 'calckey-js';
|
import type * as misskey from 'calckey-js';
|
||||||
|
import { defaultStore, noteViewInterruptors } from '@/store';
|
||||||
import MkVisibility from '@/components/MkVisibility.vue';
|
import MkVisibility from '@/components/MkVisibility.vue';
|
||||||
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
import { notePage } from '@/filters/note';
|
import { notePage } from '@/filters/note';
|
||||||
import { userPage } from '@/filters/user';
|
import { userPage } from '@/filters/user';
|
||||||
|
import { deepClone } from '@/scripts/clone';
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
let note = $ref(deepClone(props.note));
|
||||||
|
|
||||||
|
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && note.user.instance);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.kkwtjztg {
|
.kkwtjztg {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
padding: .1em .7em;
|
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
text-shadow: 0 2px 2px var(--shadow);
|
text-shadow: 0 2px 2px var(--shadow);
|
||||||
|
|
||||||
> .name {
|
> .avatar {
|
||||||
flex-shrink: 1;
|
width: 3.7em;
|
||||||
display: block;
|
height: 3.7em;
|
||||||
margin: 0 .5em 0 0;
|
margin-right: 1em;
|
||||||
padding: 0;
|
}
|
||||||
overflow: hidden;
|
> .user-info {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: flex;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-weight: bold;
|
> div {
|
||||||
text-decoration: none;
|
&:first-child {
|
||||||
text-overflow: ellipsis;
|
flex-grow: 1;
|
||||||
|
width: 0;
|
||||||
>.mkusername >.is-bot {
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
text-overflow: ellipsis;
|
||||||
align-self: center;
|
}
|
||||||
|
&:last-child {
|
||||||
|
max-width: 50%;
|
||||||
|
gap: .2em .5em;
|
||||||
|
}
|
||||||
|
.article > .main & {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
&:last-child {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
> * {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
// flex: 1 1 0px;
|
||||||
|
display: inline;
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .5em 0 0;
|
||||||
padding: 1px 6px;
|
padding: 0;
|
||||||
font-size: 80%;
|
overflow: hidden;
|
||||||
border: solid 0.5px var(--divider);
|
font-weight: bold;
|
||||||
border-radius: 3px;
|
text-decoration: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.mkusername >.is-bot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: center;
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
padding: 1px 6px;
|
||||||
|
font-size: 80%;
|
||||||
|
border: solid 0.5px var(--divider);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.username {
|
||||||
text-decoration: underline;
|
display: inline;
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
align-self: flex-start;
|
||||||
|
font-size: .9em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
> .username {
|
.info {
|
||||||
flex-shrink: 9999999;
|
display: inline-flex;
|
||||||
margin: 0 .5em 0 0;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
margin-left: .5em;
|
||||||
text-overflow: ellipsis;
|
font-size: 0.9em;
|
||||||
grid-row: 2;
|
.created-at {
|
||||||
align-self: flex-start;
|
max-width: 100%;
|
||||||
}
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .info {
|
.ticker {
|
||||||
flex-shrink: 0;
|
display: inline-flex;
|
||||||
margin-left: auto;
|
margin-left: .5em;
|
||||||
font-size: 0.9em;
|
vertical-align: middle;
|
||||||
|
> .name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-size="{ min: [350, 500] }" class="yohlumlk">
|
<div v-size="{ min: [350, 500] }" class="yohlumlk">
|
||||||
<!-- <MkAvatar class="avatar" :user="note.user"/> -->
|
<MkAvatar class="avatar" :user="note.user"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<XNoteHeader class="header" :note="note" :mini="true"/>
|
<XNoteHeader class="header" :note="note" :mini="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-size="{ max: [450] }" class="wrpstxzv" :class="{ children: depth > 1 }">
|
<div v-size="{ max: [450] }" class="wrpstxzv" :class="{ children: depth > 1 }">
|
||||||
<div class="main" @click="router.push(notePage(note.reply))">
|
<div class="main" @click="router.push(notePage(note))">
|
||||||
<MkAvatar class="avatar" :user="note.user"/>
|
<div class="avatar-container">
|
||||||
|
<MkAvatar class="avatar" :user="note.user"/>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<XNoteHeader class="header" :note="note" :mini="true"/>
|
<XNoteHeader class="header" :note="note" :mini="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -56,11 +59,7 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.wrpstxzv {
|
.wrpstxzv {
|
||||||
padding: 16px 32px;
|
padding: 16px 32px;
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&.max-width_450px {
|
|
||||||
padding: 14px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.children {
|
&.children {
|
||||||
padding: 10px 0 0 16px;
|
padding: 10px 0 0 16px;
|
||||||
|
@ -75,20 +74,21 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item
|
||||||
> .main {
|
> .main {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .avatar {
|
> .avatar-container {
|
||||||
flex-shrink: 0;
|
margin-right: 8px;
|
||||||
display: block;
|
> .avatar {
|
||||||
margin: 0 8px 0 0;
|
flex-shrink: 0;
|
||||||
width: 38px;
|
display: block;
|
||||||
height: 38px;
|
width: 38px;
|
||||||
border-radius: 8px;
|
height: 38px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
@ -129,5 +129,54 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item
|
||||||
> .more {
|
> .more {
|
||||||
padding: 10px 0 0 16px;
|
padding: 10px 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.reply-to, &.reply-to-more {
|
||||||
|
padding-bottom: 0;
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
.avatar-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 14px;
|
||||||
|
width: var(--avatarSize);
|
||||||
|
> .avatar {
|
||||||
|
width: var(--avatarSize);
|
||||||
|
height: var(--avatarSize);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
> .line {
|
||||||
|
width: var(--avatarSize);
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 2px;
|
||||||
|
background-color: var(--divider);
|
||||||
|
margin-inline: auto;
|
||||||
|
.note > & {
|
||||||
|
margin-bottom: -16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .main > .body {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.max-width_450px {
|
||||||
|
padding: 14px 16px;
|
||||||
|
&.reply-to, &.reply-to-more {
|
||||||
|
padding-top: 14px !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
> .main > .avatar-container {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="ph-hash-bold ph-lg"></i></button>
|
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="ph-hash-bold ph-lg"></i></button>
|
||||||
<button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="ph-smiley-bold ph-lg"></i></button>
|
<button v-tooltip="i18n.ts.emoji" class="_button" @click="insertEmoji"><i class="ph-smiley-bold ph-lg"></i></button>
|
||||||
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="ph-plug-bold ph-lg"></i></button>
|
<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" @click="showActions"><i class="ph-plug-bold ph-lg"></i></button>
|
||||||
|
<button v-tooltip="i18n.ts._mfm.cheatSheet" class="_button right" @click="openCheatSheet"><i class="ph-question-bold ph-lg"></i></button>
|
||||||
</footer>
|
</footer>
|
||||||
<datalist id="hashtags">
|
<datalist id="hashtags">
|
||||||
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
|
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
|
||||||
|
@ -90,6 +91,7 @@ import { instance } from '@/instance';
|
||||||
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
|
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
|
||||||
import { uploadFile } from '@/scripts/upload';
|
import { uploadFile } from '@/scripts/upload';
|
||||||
import { deepClone } from '@/scripts/clone';
|
import { deepClone } from '@/scripts/clone';
|
||||||
|
import XCheatSheet from '@/components/MkCheatSheetDialog.vue';
|
||||||
|
|
||||||
const modal = inject('modal');
|
const modal = inject('modal');
|
||||||
|
|
||||||
|
@ -624,6 +626,10 @@ async function insertEmoji(ev: MouseEvent) {
|
||||||
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl);
|
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openCheatSheet(ev: MouseEvent) {
|
||||||
|
os.popup(XCheatSheet, {}, {}, 'closed');
|
||||||
|
}
|
||||||
|
|
||||||
function showActions(ev) {
|
function showActions(ev) {
|
||||||
os.popupMenu(postFormActions.map(action => ({
|
os.popupMenu(postFormActions.map(action => ({
|
||||||
text: action.title,
|
text: action.title,
|
||||||
|
@ -711,6 +717,9 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.right {
|
||||||
|
float:right
|
||||||
|
}
|
||||||
.gafaadew {
|
.gafaadew {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ const isMe = computed(() => $i && $i.id === props.note.userId);
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tdflqwzn {
|
.tdflqwzn {
|
||||||
margin: 4px -2px 0 -2px;
|
margin: 4px -2px 0 -2px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
|
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
|
||||||
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ph-arrow-bend-up-left-bold ph-lg"></i></MkA>
|
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ph-arrow-bend-up-left-bold ph-lg"></i></MkA>
|
||||||
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
|
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
|
||||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">QB: ...</MkA>
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">{{ i18n.ts.quoteAttached }}: ...</MkA>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.files.length > 0">
|
<div v-if="note.files.length > 0">
|
||||||
<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
|
<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
|
||||||
|
@ -65,7 +65,11 @@ const collapsed = $ref(props.note.cw == null && isLong);
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height: 9em;
|
max-height: 9em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
> .body {
|
||||||
|
max-height: 9em;
|
||||||
|
mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||||
|
-webkit-mask: linear-gradient(black calc(100% - 64px), transparent);
|
||||||
|
}
|
||||||
> .fade {
|
> .fade {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -73,7 +77,6 @@ const collapsed = $ref(props.note.cw == null && isLong);
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
background: linear-gradient(0deg, var(--panel), var(--X15));
|
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default defineComponent({
|
||||||
> button {
|
> button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px 8px;
|
padding: 10px 8px;
|
||||||
|
margin: 0 8px;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
<div class="version">✨ {{ version }} 🚀</div>
|
<div class="version">✨ {{ version }} 🚀</div>
|
||||||
<div v-if="newRelease" class="releaseNotes">
|
<div v-if="newRelease" class="releaseNotes">
|
||||||
<Mfm :text="data.notes"/>
|
<Mfm :text="data.notes"/>
|
||||||
<!-- <div v-if="data.screenshots.length > 0" style="max-width: 500">
|
<div v-if="data.screenshots.length > 0" style="max-width: 500">
|
||||||
<img v-for="i in data.screenshots" :key="i" :src="i"/>
|
<img v-for="i in data.screenshots" :key="i" :src="i"/>
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkButton class="gotIt" primary full @click="modal.close()">{{ i18n.ts.gotIt }}</MkButton>
|
<MkButton class="gotIt" primary full @click="modal.close()">{{ i18n.ts.gotIt }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,13 +24,17 @@ import * as os from '@/os';
|
||||||
|
|
||||||
const modal = $ref<InstanceType<typeof MkModal>>();
|
const modal = $ref<InstanceType<typeof MkModal>>();
|
||||||
|
|
||||||
let newRelease = false;
|
let newRelease = $ref(false);
|
||||||
let data;
|
let data = $ref(Object);
|
||||||
|
|
||||||
os.api('release').then(res => {
|
os.api('release').then(res => {
|
||||||
data = res;
|
data = res;
|
||||||
console.log(data);
|
|
||||||
newRelease = (version === data?.version);
|
newRelease = (version === data?.version);
|
||||||
});
|
});
|
||||||
|
console.log(`Version: ${version}`)
|
||||||
|
console.log(`Data version: ${data.version}`)
|
||||||
|
console.log(newRelease)
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -210,14 +210,12 @@ onUnmounted(() => {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-shadow: 0 0 0 1px var(--divider);
|
border: 1px solid var(--divider);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-color: rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
> article > header > h1 {
|
> article > header > h1 {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1496,7 +1496,7 @@
|
||||||
{ "category": "symbols", "char": "🀄", "name": "mahjong", "keywords": ["game", "play", "chinese", "kanji"] },
|
{ "category": "symbols", "char": "🀄", "name": "mahjong", "keywords": ["game", "play", "chinese", "kanji"] },
|
||||||
{ "category": "symbols", "char": "♠️", "name": "spades", "keywords": ["poker", "cards", "suits", "magic"] },
|
{ "category": "symbols", "char": "♠️", "name": "spades", "keywords": ["poker", "cards", "suits", "magic"] },
|
||||||
{ "category": "symbols", "char": "♣️", "name": "clubs", "keywords": ["poker", "cards", "magic", "suits"] },
|
{ "category": "symbols", "char": "♣️", "name": "clubs", "keywords": ["poker", "cards", "magic", "suits"] },
|
||||||
{ "category": "symbols", "char": "♥️", "name": "hearts", "keywords": ["poker", "cards", "magic", "suits"] },
|
{ "category": "symbols", "char": "❤️", "name": "hearts", "keywords": ["poker", "cards", "magic", "suits"] },
|
||||||
{ "category": "symbols", "char": "♦️", "name": "diamonds", "keywords": ["poker", "cards", "magic", "suits"] },
|
{ "category": "symbols", "char": "♦️", "name": "diamonds", "keywords": ["poker", "cards", "magic", "suits"] },
|
||||||
{ "category": "symbols", "char": "🎴", "name": "flower_playing_cards", "keywords": ["game", "sunset", "red"] },
|
{ "category": "symbols", "char": "🎴", "name": "flower_playing_cards", "keywords": ["game", "sunset", "red"] },
|
||||||
{ "category": "symbols", "char": "💭", "name": "thought_balloon", "keywords": ["bubble", "cloud", "speech", "thinking", "dream"] },
|
{ "category": "symbols", "char": "💭", "name": "thought_balloon", "keywords": ["bubble", "cloud", "speech", "thinking", "dream"] },
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<MkSpacer :content-max="800">
|
<MkSpacer :content-max="800">
|
||||||
<div class="mwysmxbg">
|
<div class="mwysmxbg">
|
||||||
<div>{{ i18n.ts._mfm.intro }}</div>
|
<div>{{ i18n.ts._mfm.intro }}</div>
|
||||||
|
<br/>
|
||||||
<div class="section _block">
|
<div class="section _block">
|
||||||
<div class="title">{{ i18n.ts._mfm.mention }}</div>
|
<div class="title">{{ i18n.ts._mfm.mention }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -24,16 +25,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section _block">
|
|
||||||
<div class="title">{{ i18n.ts._mfm.url }}</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>{{ i18n.ts._mfm.urlDescription }}</p>
|
|
||||||
<div class="preview">
|
|
||||||
<Mfm :text="preview_url"/>
|
|
||||||
<MkTextarea v-model="preview_url"><template #label>MFM</template></MkTextarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="section _block">
|
<div class="section _block">
|
||||||
<div class="title">{{ i18n.ts._mfm.link }}</div>
|
<div class="title">{{ i18n.ts._mfm.link }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -320,7 +311,6 @@ import { instance } from '@/instance';
|
||||||
|
|
||||||
let preview_mention = $ref('@example');
|
let preview_mention = $ref('@example');
|
||||||
let preview_hashtag = $ref('#test');
|
let preview_hashtag = $ref('#test');
|
||||||
let preview_url = $ref('https://example.com');
|
|
||||||
let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`);
|
let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`);
|
||||||
let preview_emoji = $ref(instance.emojis.length ? `:${instance.emojis[0].name}:` : ':emojiname:');
|
let preview_emoji = $ref(instance.emojis.length ? `:${instance.emojis[0].name}:` : ':emojiname:');
|
||||||
let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`);
|
let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`);
|
||||||
|
|
|
@ -192,6 +192,8 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invalidateFollow() {
|
async function invalidateFollow() {
|
||||||
|
if (!(await getConfirmed(i18n.ts.breakFollowConfirm))) return;
|
||||||
|
|
||||||
os.apiWithDialog("following/invalidate", {
|
os.apiWithDialog("following/invalidate", {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { query } from '@/scripts/url';
|
import { query } from "@/scripts/url";
|
||||||
import { url } from '@/config';
|
import { url } from "@/config";
|
||||||
|
|
||||||
export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string {
|
export function getProxiedImageUrl(imageUrl: string, type?: "preview"): string {
|
||||||
return `${url}/proxy/image.webp?${query({
|
return `${url}/proxy/image.webp?${query({
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
fallback: '1',
|
fallback: "1",
|
||||||
...(type ? { [type]: '1' } : {}),
|
...(type ? { [type]: "1" } : {}),
|
||||||
})}`;
|
})}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
|
export function getProxiedImageUrlNullable(
|
||||||
|
imageUrl: string | null | undefined,
|
||||||
|
type?: "preview",
|
||||||
|
): string | null {
|
||||||
if (imageUrl == null) return null;
|
if (imageUrl == null) return null;
|
||||||
return getProxiedImageUrl(imageUrl, type);
|
return getProxiedImageUrl(imageUrl, type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
--radius: 12px;
|
--radius: 12px;
|
||||||
--marginFull: 16px;
|
--marginFull: 16px;
|
||||||
--marginHalf: 10px;
|
--marginHalf: 10px;
|
||||||
|
--avatarSize: 48px;
|
||||||
|
|
||||||
--margin: var(--marginFull);
|
--margin: var(--marginFull);
|
||||||
|
|
||||||
|
|
|
@ -447,7 +447,7 @@ importers:
|
||||||
typescript: 4.9.4
|
typescript: 4.9.4
|
||||||
uuid: 9.0.0
|
uuid: 9.0.0
|
||||||
vanilla-tilt: 1.8.0
|
vanilla-tilt: 1.8.0
|
||||||
vite: ^4.0.4
|
vite: ^4.1.0-beta.1
|
||||||
vue: 3.2.45
|
vue: 3.2.45
|
||||||
vue-isyourpasswordsafe: ^2.0.0
|
vue-isyourpasswordsafe: ^2.0.0
|
||||||
vue-plyr: ^7.0.0
|
vue-plyr: ^7.0.0
|
||||||
|
@ -519,7 +519,7 @@ importers:
|
||||||
typescript: 4.9.4
|
typescript: 4.9.4
|
||||||
uuid: 9.0.0
|
uuid: 9.0.0
|
||||||
vanilla-tilt: 1.8.0
|
vanilla-tilt: 1.8.0
|
||||||
vite: 4.0.4_sass@1.57.1
|
vite: 4.1.0-beta.2_sass@1.57.1
|
||||||
vue: 3.2.45
|
vue: 3.2.45
|
||||||
vue-isyourpasswordsafe: 2.0.0
|
vue-isyourpasswordsafe: 2.0.0
|
||||||
vue-plyr: 7.0.0
|
vue-plyr: 7.0.0
|
||||||
|
@ -2509,14 +2509,14 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@vitejs/plugin-vue/4.0.0_vite@4.0.4+vue@3.2.45:
|
/@vitejs/plugin-vue/4.0.0_f7f773kp2g3xbdt2psqzr7g57m:
|
||||||
resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==}
|
resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4.0.0
|
vite: ^4.0.0
|
||||||
vue: ^3.2.25
|
vue: ^3.2.25
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 4.0.4_sass@1.57.1
|
vite: 4.1.0-beta.2_sass@1.57.1
|
||||||
vue: 3.2.45
|
vue: 3.2.45
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -10804,6 +10804,14 @@ packages:
|
||||||
rangestr: 0.0.1
|
rangestr: 0.0.1
|
||||||
seedrandom: 2.4.2
|
seedrandom: 2.4.2
|
||||||
|
|
||||||
|
/rollup/3.12.1:
|
||||||
|
resolution: {integrity: sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==}
|
||||||
|
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/rollup/3.9.1:
|
/rollup/3.9.1:
|
||||||
resolution: {integrity: sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==}
|
resolution: {integrity: sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==}
|
||||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||||
|
@ -12464,8 +12472,8 @@ packages:
|
||||||
replace-ext: 1.0.1
|
replace-ext: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite/4.0.4_sass@1.57.1:
|
/vite/4.1.0-beta.2_sass@1.57.1:
|
||||||
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
|
resolution: {integrity: sha512-B1V2qkELCaxgg1YptKyCmpY3/6LBXWW5iD5H6L4BizvWNhp2hGL7wAwBblfjqCuudVv9kcnJiL5uY2zqvZFr5Q==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/node': '>= 14'
|
'@types/node': '>= 14'
|
||||||
|
@ -12491,7 +12499,7 @@ packages:
|
||||||
esbuild: 0.16.17
|
esbuild: 0.16.17
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
rollup: 3.9.1
|
rollup: 3.12.1
|
||||||
sass: 1.57.1
|
sass: 1.57.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "13.0.6-rc",
|
"version": "13.1.0",
|
||||||
"notes": "$[jelly Happy new year!] This release includes many changes, including:\n- New post layout\n- Automatic subdomain blocks\n- Notes are now called posts\n- Federation improvements\n- Many bug fixes and performance improvements",
|
"notes": "This release includes many changes, including:\n\n• New post and thread layout\n• Automatic subdomain blocks\n• Customizable default reactions\n• Federation improvements\n• Many bug fixes and performance improvements",
|
||||||
"screenshots": []
|
"screenshots": []
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue