diff --git a/CALCKEY.md b/CALCKEY.md index 3b6afb177b..dfcb191e41 100644 --- a/CALCKEY.md +++ b/CALCKEY.md @@ -87,7 +87,10 @@ - AVIF support - Page drafts - Patron list +- Animations respect reduced motion - Obliteration of Ai-chan +- Undo renote button inside original note +- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1) - [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996) - [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056) - [OAuth bearer token authentication](https://github.com/misskey-dev/misskey/pull/9021) diff --git a/README.md b/README.md index b4af8e16a4..8e4b03be94 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - Improved UI/UX (especially on mobile) - Improved notifications - Improved instance security + - Improved accessibility - Recommended Instances timeline - OCR image captioning - New and improved Groups @@ -48,13 +49,27 @@ This guide will work for both **starting from scratch** and **migrating from Mis ## ๐Ÿ“ฆ Dependencies -- At least ๐Ÿข [NodeJS](https://nodejs.org/en/) v18.12.1 (v19.1.0 recommended) - +- ๐Ÿข At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v19.1.0 recommended) + - Install with [nvm](https://github.com/nvm-sh/nvm) - ๐Ÿ˜ At least [PostgreSQL](https://www.postgresql.org/) v12 - - ๐Ÿฑ At least [Redis](https://redis.io/) v6 (v7 recommended) -- ๐Ÿ›ฐ๏ธ (Optional, for non-Docker) [pm2](https://pm2.io/) +### ๐Ÿ˜— Optional dependencies + +- ๐Ÿ“— [FFmpeg](https://ffmpeg.org/) for video transcoding +- ๐Ÿ” [ElasticSearch](https://www.elastic.co/elasticsearch/) for full-text search + - OpenSearch/Sonic are not supported as of right now +- ๐Ÿฅก Management (choose one of the following) + - ๐Ÿ›ฐ๏ธ [pm2](https://pm2.io/) + - ๐Ÿณ [Docker](https://docker.com) + - ๐Ÿ“ Service manager (systemd, openrc, etc) + +### ๐Ÿ—๏ธ Build dependencies + +- ๐Ÿฆฌ C/C++ compiler & build tools + - `build-essential` on Debian/Ubuntu Linux + - `base-devel` on Arch Linux +- ๐Ÿ [Python 3](https://www.python.org/) ## ๐Ÿ‘€ Get folder ready @@ -71,10 +86,19 @@ cd calckey/ corepack enable ``` +## ๐Ÿ˜ Create database + +Assuming you set up PostgreSQL correctly, all you have to run is: + +```sh +psql postgres -c "create database calckey with encoding = 'UTF8';" +``` + ## ๐Ÿ’… Customize -- To add custom CSS for all users, edit `./custom/instance.css`. -- To add static assets (such as images for the splash screen), place them in the `./custom/` directory. They'll then be avaliable on `https://yourinstance.tld/static-assets/filename.ext`. +- To add custom CSS for all users, edit `./custom/assets/instance.css`. +- To add static assets (such as images for the splash screen), place them in the `./custom/assets/` directory. They'll then be avaliable on `https://yourinstance.tld/static-assets/filename.ext`. +- To add custom locales, place them in the `./custom/locales/` directory. If you name your custom locale the same as an existing locale, it will overwrite it. If you give it a unique name, it will be added to the list. Also make sure that the first part of the filename matches the locale you're basing it on. (Example: `en-FOO.yml`) - To update custom assets without rebuilding, just run `yarn run gulp`. ## ๐Ÿง‘โ€๐Ÿ”ฌ Configuring a new instance @@ -103,7 +127,7 @@ cp -r ../misskey/files . # if you don't use object storage ## ๐Ÿš€ Build and launch! -### ๐Ÿข NodeJS +### ๐Ÿข NodeJS + pm2 #### `git pull` and run these steps to update Calckey in the future! @@ -133,6 +157,7 @@ docker-compose up -d - When editing the config file, please don't fill out the settings at the bottom. They're designed *only* for managed hosting, not self hosting. Those settings are much better off being set in Calckey's control panel. - Port 3000 (used in the default config) might be already used on your server for something else. To find an open port for Calckey, run `for p in $(seq 3000 4000); do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1` +- I'd recommend you use a S3 Bucket/CDN for Object Storage, especially if you use Docker. - I'd ***strongly*** recommend against using CloudFlare, but if you do, make sure to turn code minification off. - For push notifications, run `npx web-push generate-vapid-keys`, the put the public and private keys into Control Panel > General > ServiceWorker. - For translations, make a [DeepL](https://deepl.com) account and generate an API key, then put it into Control Panel > General > DeepL Translation. diff --git a/custom/instance.css b/custom/assets/instance.css similarity index 100% rename from custom/instance.css rename to custom/assets/instance.css diff --git a/custom/locales/.gitkeep b/custom/locales/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gulpfile.js b/gulpfile.js index 86f860e568..89a6acb839 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,7 +16,7 @@ gulp.task('copy:backend:views', () => ); gulp.task('copy:backend:custom', () => - gulp.src('./custom/*').pipe(gulp.dest('./packages/backend/assets/')) + gulp.src('./custom/assets/*').pipe(gulp.dest('./packages/backend/assets/')) ); gulp.task('copy:client:fonts', () => diff --git a/locales/en-US.yml b/locales/en-US.yml index 6ad3bf614e..0f3fff72e3 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -32,12 +32,12 @@ uploading: "Uploading..." save: "Save" users: "Users" addUser: "Add a user" -favorite: "Add to favorites" -favorites: "Favorites" -unfavorite: "Remove from favorites" -favorited: "Added to favorites." -alreadyFavorited: "Already added to favorites." -cantFavorite: "Couldn't add to favorites." +favorite: "Add to bookmarks" +favorites: "Bookmarks" +unfavorite: "Remove from bookmarks" +favorited: "Added to bookmarks." +alreadyFavorited: "Already added to bookmarks." +cantFavorite: "Couldn't add to bookmarks." pin: "Pin to profile" unpin: "Unpin from profile" copyContent: "Copy contents" @@ -679,7 +679,7 @@ disableShowingAnimatedImages: "Don't play animated images" verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification." notSet: "Not set" emailVerified: "Email has been verified" -noteFavoritesCount: "Number of favorite notes" +noteFavoritesCount: "Number of bookmarked notes" pageLikesCount: "Number of liked Pages" pageLikedCount: "Number of received Page likes" contact: "Contact" @@ -1227,7 +1227,7 @@ _tutorial: step5_3: "The Home {icon} timeline is where you can see posts from your followers." step5_4: "The Local {icon} timeline is where you can see posts from everyone else on this instance." step5_5: "The Recommended {icon} timeline is where you can see posts from instances the admins recommend." - step5_6: "The Social {icon} timeline is where you can see posts from friends of your followers." + step5_6: "The Social {icon} timeline is your home + local." step5_7: "The Global {icon} timeline is where you can see posts from every other connected instance." step6_1: "So, what is this place?" step6_2: "Well, you didn't just join Calckey. You joined a portal to the Fediverse, an interconnected network of thousands of servers, called \"instances\"." @@ -1250,8 +1250,8 @@ _permissions: "write:blocks": "Edit your list of blocked users" "read:drive": "Access your Drive files and folders" "write:drive": "Edit or delete your Drive files and folders" - "read:favorites": "View your list of favorites" - "write:favorites": "Edit your list of favorites" + "read:favorites": "View your list of bookmarks" + "write:favorites": "Edit your list of bookmarks" "read:following": "View information on who you follow" "write:following": "Follow or unfollow other accounts" "read:messaging": "View your chats" diff --git a/locales/index.js b/locales/index.js index 92cd9b467c..7399bb5a18 100644 --- a/locales/index.js +++ b/locales/index.js @@ -4,6 +4,8 @@ const fs = require('fs'); const yaml = require('js-yaml'); +let languages = [] +let languages_custom = [] const merge = (...args) => args.reduce((a, c) => ({ ...a, @@ -13,33 +15,20 @@ const merge = (...args) => args.reduce((a, c) => ({ .reduce((a, [k, v]) => (a[k] = merge(v, c[k]), a), {}) }), {}); -const languages = [ - 'ar-SA', - 'cs-CZ', - 'da-DK', - 'de-DE', - 'en-US', - 'es-ES', - 'fr-FR', - 'id-ID', - 'it-IT', - 'ja-JP', - 'ja-KS', - 'kab-KAB', - 'kn-IN', - 'ko-KR', - 'nl-NL', - 'no-NO', - 'pl-PL', - 'pt-PT', - 'ru-RU', - 'sk-SK', - 'ug-CN', - 'uk-UA', - 'vi-VN', - 'zh-CN', - 'zh-TW', -]; + +fs.readdirSync(__dirname).forEach((file) => { + if (file.includes('.yml')){ + file = file.slice(0, file.indexOf('.')) + languages.push(file); + } +}) + +fs.readdirSync(__dirname + '/../custom/locales').forEach((file) => { + if (file.includes('.yml')){ + file = file.slice(0, file.indexOf('.')) + languages_custom.push(file); + } +}) const primaries = { 'en': 'US', @@ -51,6 +40,8 @@ const primaries = { const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {}); +const locales_custom = languages_custom.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/../custom/locales/${c}.yml`, 'utf-8'))) || {}, a), {}); +Object.assign(locales, locales_custom) module.exports = Object.entries(locales) .reduce((a, [k ,v]) => (a[k] = (() => { diff --git a/package.json b/package.json index 18d9782826..7e0d154012 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "12.119.0-calc.17", + "version": "12.119.0-calc.18", "codename": "aqua", "repository": { "type": "git", diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 42b47ab150..efa3e860ba 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -1,14 +1,14 @@ import { In, Repository } from 'typeorm'; -import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; import { Notification } from '@/models/entities/notification.js'; import { awaitAll } from '@/prelude/await-all.js'; -import { Packed } from '@/misc/schema.js'; -import { Note } from '@/models/entities/note.js'; -import { NoteReaction } from '@/models/entities/note-reaction.js'; -import { User } from '@/models/entities/user.js'; +import type { Packed } from '@/misc/schema.js'; +import type { Note } from '@/models/entities/note.js'; +import type { NoteReaction } from '@/models/entities/note-reaction.js'; +import type { User } from '@/models/entities/user.js'; import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { notificationTypes } from '@/types.js'; import { db } from '@/db/postgre.js'; +import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js'; export const NotificationRepository = db.getRepository(Notification).extend({ async pack( @@ -17,7 +17,7 @@ export const NotificationRepository = db.getRepository(Notification).extend({ _hintForEachNotes_?: { myReactions: Map; }; - } + }, ): Promise> { const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null; @@ -86,7 +86,7 @@ export const NotificationRepository = db.getRepository(Notification).extend({ async packMany( notifications: Notification[], - meId: User['id'] + meId: User['id'], ) { if (notifications.length === 0) return []; @@ -106,10 +106,15 @@ export const NotificationRepository = db.getRepository(Notification).extend({ await prefetchEmojis(aggregateNoteEmojis(notes)); - return await Promise.all(notifications.map(x => this.pack(x, { - _hintForEachNotes_: { - myReactions: myReactionsMap, - }, - }))); + const results = await Promise.all(notifications + .map(x => + this.pack(x, { + _hintForEachNotes_: { + myReactions: myReactionsMap, + }, + }).catch(e => null), + ), + ); + return results.filter(x => x != null); }, }); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 9e8a81bb39..022be0ad8e 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -26,7 +26,7 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise console.log(e)); + await updateQuestion(object, resolver).catch(e => console.log(e)); return `ok: Question updated`; } else { return `skip: Unknown type: ${getApType(object)}`; diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 6097e3b6ed..5ef04588e9 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -271,7 +271,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise logger.error(err)); + await updateFeatured(user!.id, resolver).catch(err => logger.error(err)); return user!; } @@ -384,7 +384,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), }); - await updateFeatured(exist.id).catch(err => logger.error(err)); + await updateFeatured(exist.id, resolver).catch(err => logger.error(err)); } /** @@ -462,14 +462,14 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined) return { fields, services }; } -export async function updateFeatured(userId: User['id']) { +export async function updateFeatured(userId: User['id'], resolver?: Resolver) { const user = await Users.findOneByOrFail({ id: userId }); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; logger.info(`Updating the featured: ${user.uri}`); - const resolver = new Resolver(); + if (resolver == null) resolver = new Resolver(); // Resolve to (Ordered)Collection Object const collection = await resolver.resolveCollection(user.featured); diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index f0321fdf2f..94a50d4f71 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -40,7 +40,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver * @param uri URI of AP Question object * @returns true if updated */ -export async function updateQuestion(value: any) { +export async function updateQuestion(value: any, resolver?: Resolver) { const uri = typeof value === 'string' ? value : value.id; // URIใŒใ“ใฎใ‚ตใƒผใƒใƒผใ‚’ๆŒ‡ใ—ใฆใ„ใ‚‹ใชใ‚‰ใ‚นใ‚ญใƒƒใƒ— @@ -55,7 +55,7 @@ export async function updateQuestion(value: any) { //#endregion // resolve new Question object - const resolver = new Resolver(); + if (resolver == null) resolver = new Resolver(); const question = await resolver.resolve(value) as IQuestion; apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 5c9d44292e..94227e4db5 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -19,9 +19,11 @@ import renderFollow from '@/remote/activitypub/renderer/follow.js'; export default class Resolver { private history: Set; private user?: ILocalUser; + private recursionLimit?: number; - constructor() { + constructor(recursionLimit = 100) { this.history = new Set(); + this.recursionLimit = recursionLimit; } public getHistory(): string[] { @@ -59,7 +61,9 @@ export default class Resolver { if (this.history.has(value)) { throw new Error('cannot resolve already resolved one'); } - + if (this.recursionLimit && this.history.size > this.recursionLimit) { + throw new Error('hit recursion limit'); + } this.history.add(value); const host = extractDbHost(value); diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index c81506384c..446df15541 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -232,8 +232,43 @@ const getFeed = async (acct: string) => { return user && await packFeed(user); }; +// As the /@user[.json|.rss|.atom]/sub endpoint is complicated, we will use a regex to switch between them. +const reUser = new RegExp(`^/@(?[^/]+?)(?:\.(?json|rss|atom))?(?:/(?[^/]+))?$`); +router.get(reUser, async (ctx, next) => { + const groups = reUser.exec(ctx.originalUrl)?.groups; + if (!groups) { + await next(); + return; + } + + ctx.params = groups; + + console.log(ctx, ctx.params) + if (groups.feed) { + if (groups.sub) { + await next(); + return; + } + + switch (groups.feed) { + case 'json': + await jsonFeed(ctx, next); + break; + case 'rss': + await rssFeed(ctx, next); + break; + case 'atom': + await atomFeed(ctx, next); + break; + } + return; + } + + await userPage(ctx, next); +}); + // Atom -router.get('/@:user.atom', async ctx => { +const atomFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -242,10 +277,10 @@ router.get('/@:user.atom', async ctx => { } else { ctx.status = 404; } -}); +}; // RSS -router.get('/@:user.rss', async ctx => { +const rssFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -254,10 +289,10 @@ router.get('/@:user.rss', async ctx => { } else { ctx.status = 404; } -}); +}; // JSON -router.get('/@:user.json', async ctx => { +const jsonFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -266,43 +301,47 @@ router.get('/@:user.json', async ctx => { } else { ctx.status = 404; } -}); +}; //#region SSR (for crawlers) // User -router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); +const userPage: Router.Middleware = async (ctx, next) => { + const userParam = ctx.params.user; + const subParam = ctx.params.sub; + const { username, host } = Acct.parse(userParam); + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, }); - if (user != null) { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - const meta = await fetchMeta(); - const me = profile.fields - ? profile.fields - .filter(filed => filed.value != null && filed.value.match(/^https?:/)) - .map(field => field.value) - : []; - - await ctx.render('user', { - user, profile, me, - avatarUrl: await Users.getAvatarUrl(user), - sub: ctx.params.sub, - instanceName: meta.name || 'Calckey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - privateMode: meta.privateMode, - }); - ctx.set('Cache-Control', 'public, max-age=15'); - } else { - // ใƒชใƒขใƒผใƒˆใƒฆใƒผใ‚ถใƒผใชใฎใง - // ใƒขใƒ‡ใƒฌใƒผใ‚ฟใŒAPI็ตŒ็”ฑใงๅ‚็…งๅฏ่ƒฝใซใ™ใ‚‹ใŸใ‚ใซ404ใซใฏใ—ใชใ„ + if (user === null) { await next(); + return; } -}); + + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + const meta = await fetchMeta(); + const me = profile.fields + ? profile.fields + .filter(filed => filed.value != null && filed.value.match(/^https?:/)) + .map(field => field.value) + : []; + + const userDetail = { + user, profile, me, + avatarUrl: await Users.getAvatarUrl(user), + sub: subParam, + instanceName: meta.name || 'Calckey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + privateMode: meta.privateMode, + }; + + await ctx.render('user', userDetail); + ctx.set('Cache-Control', 'public, max-age=15'); +}; router.get('/users/:user', async ctx => { const user = await Users.findOneBy({ diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index cfdaac600d..5072e0ad4e 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -42,7 +42,7 @@ html { width: 28px; height: 28px; transform: translateY(110px); - display: none !important; + display: none; color: var(--accent); } #splashSpinner > .spinner { @@ -101,6 +101,16 @@ html { } } +@media(prefers-reduced-motion) { + #splashSpinner { + display: block; + } + + #splashIcon { + animation: none; + } +} + #splashText { position: absolute; top: 0; diff --git a/packages/client/src/components/MkChannelPreview.vue b/packages/client/src/components/MkChannelPreview.vue index 26cff3b211..a970c9ae6d 100644 --- a/packages/client/src/components/MkChannelPreview.vue +++ b/packages/client/src/components/MkChannelPreview.vue @@ -81,9 +81,12 @@ const bannerStyle = computed(() => { top: 16px; left: 16px; padding: 12px 16px; - background: rgba(0, 0, 0, 0.7); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + background: rgba(0, 0, 0, 0.2); color: #fff; font-size: 1.2em; + border-radius: 999px; } > .status { @@ -93,7 +96,9 @@ const bannerStyle = computed(() => { right: 16px; padding: 8px 12px; font-size: 80%; - background: rgba(0, 0, 0, 0.7); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + background: rgba(0, 0, 0, 0.2); border-radius: 6px; color: #fff; } diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue index f1dce72841..02568c6ece 100644 --- a/packages/client/src/components/MkContainer.vue +++ b/packages/client/src/components/MkContainer.vue @@ -178,6 +178,7 @@ export default defineComponent({ > ::v-deep(i) { margin-right: 6px; + transform: translateY(0.1em); } &:empty { diff --git a/packages/client/src/components/MkInstanceTicker.vue b/packages/client/src/components/MkInstanceTicker.vue index ed9d30b8c9..5c53b5fb36 100644 --- a/packages/client/src/components/MkInstanceTicker.vue +++ b/packages/client/src/components/MkInstanceTicker.vue @@ -64,6 +64,7 @@ const bg = { font-size: 0.9em; vertical-align: top; font-weight: bold; + text-overflow: clip; } } diff --git a/packages/client/src/components/MkModalPageWindow.vue b/packages/client/src/components/MkModalPageWindow.vue index b503e9161a..60ce03a2c2 100644 --- a/packages/client/src/components/MkModalPageWindow.vue +++ b/packages/client/src/components/MkModalPageWindow.vue @@ -2,7 +2,7 @@
- + diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 258eec5014..613203091b 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -71,21 +71,21 @@
- - -
@@ -426,13 +426,18 @@ function readPromo() { > .article { display: flex; padding: 28px 32px 18px; + cursor: pointer; + + @media (pointer: coarse) { + cursor: default; + } > .avatar { flex-shrink: 0; display: block; margin: 0 14px 8px 0; - width: 58px; - height: 58px; + width: 52px; + height: 52px; position: sticky; /* For some reason this breaks avatar positions on notes, commenting it for now */ @@ -613,7 +618,7 @@ function readPromo() { margin: 0 10px 8px 0; width: 46px; height: 46px; - top: calc(14px + var(--stickyTop, 0px)); + // top: calc(14px + var(--stickyTop, 0px)); } } } diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 83d44f59f1..2deb184416 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -81,21 +81,21 @@
- - - @@ -117,7 +117,7 @@ diff --git a/packages/client/src/components/MkSparkle.vue b/packages/client/src/components/MkSparkle.vue index db38864a3c..2311adce83 100644 --- a/packages/client/src/components/MkSparkle.vue +++ b/packages/client/src/components/MkSparkle.vue @@ -65,6 +65,7 @@ diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 43d3651b81..a4be9e85fa 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -24,7 +24,7 @@
- +
@@ -67,6 +67,7 @@ import { url } from '@/config'; import { useRouter } from '@/router'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { shareAvailable } from '@/scripts/share-available'; const router = useRouter(); diff --git a/packages/client/src/pages/messaging/messaging-room.message.vue b/packages/client/src/pages/messaging/messaging-room.message.vue index 9e2af50466..7e66dfcc2d 100644 --- a/packages/client/src/pages/messaging/messaging-room.message.vue +++ b/packages/client/src/pages/messaging/messaging-room.message.vue @@ -90,7 +90,6 @@ function del(): void { min-height: 38px; border-radius: 16px; max-width: 100%; - margin-left: 4%; & + * { clear: both; @@ -215,8 +214,6 @@ function del(): void { > .balloon { $color: var(--X4); - margin-right: 4%; - margin-left: 0%; background: $color; &.noText { diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 0557cbd425..e89fd3e2a8 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -34,7 +34,7 @@
- +
@@ -81,6 +81,7 @@ import MkPagination from '@/components/MkPagination.vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { shareAvailable } from '@/scripts/share-available'; const props = defineProps<{ pageName: string; @@ -249,14 +250,14 @@ definePageMetadata(computed(() => page ? { } > .content { - padding: 16px 0 0 0; + padding: 16px 0; } > .actions { display: flex; align-items: center; margin-top: 16px; - padding: 16px 0 0 0; + padding: 16px 0; border-top: solid 0.5px var(--divider); > .like { @@ -290,8 +291,8 @@ definePageMetadata(computed(() => page ? { align-items: center; > .avatar { - width: 52px; - height: 52px; + width: 40px; + height: 40px; } > .name { diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index 0e53aad2ac..e17ade59c0 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -8,6 +8,7 @@ import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { url } from '@/config'; import { noteActions } from '@/store'; +import { shareAvailable } from '@/scripts/share-available'; export function getNoteMenu(props: { note: misskey.entities.Note; @@ -220,23 +221,23 @@ export function getNoteMenu(props: { window.open(appearNote.url || appearNote.uri, '_blank'); }, } : undefined, - { + shareAvailable() ? { icon: 'ph-share-network-bold ph-lg', text: i18n.ts.share, action: share, - }, + } : undefined, instance.translatorAvailable ? { icon: 'ph-translate-bold ph-lg', text: i18n.ts.translate, action: translate, } : undefined, null, - statePromise.then(state => state.isFavorited ? { - icon: 'ph-star-bold ph-lg', + statePromise.then(state => state?.isFavorited ? { + icon: 'ph-bookmark-simple-bold ph-lg', text: i18n.ts.unfavorite, action: () => toggleFavorite(false), } : { - icon: 'ph-star-bold ph-lg', + icon: 'ph-bookmark-simple-bold ph-lg', text: i18n.ts.favorite, action: () => toggleFavorite(true), }), diff --git a/packages/client/src/scripts/reduced-motion.ts b/packages/client/src/scripts/reduced-motion.ts new file mode 100644 index 0000000000..cccbb2cabb --- /dev/null +++ b/packages/client/src/scripts/reduced-motion.ts @@ -0,0 +1,3 @@ +export function reducedMotion(): boolean { + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; +} diff --git a/packages/client/src/scripts/share-available.ts b/packages/client/src/scripts/share-available.ts new file mode 100644 index 0000000000..8056d6dc2e --- /dev/null +++ b/packages/client/src/scripts/share-available.ts @@ -0,0 +1,6 @@ +export function shareAvailable(): boolean { + if (navigator.share) { + return true; + } + return false; +} diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index ffea050e50..48babbe2a9 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -568,6 +568,22 @@ hr { } } +@media(prefers-reduced-motion) { + @keyframes tada { + from { + transform: scale3d(1, 1, 1); + } + + 50% { + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + transform: scale3d(1, 1, 1); + } + } +} + ._anime_bounce { will-change: transform; animation: bounce ease 0.7s; diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue index 1ea59dd260..38ad0591be 100644 --- a/packages/client/src/ui/_common_/common.vue +++ b/packages/client/src/ui/_common_/common.vue @@ -11,7 +11,7 @@ -
+
DEV BUILD
@@ -99,8 +99,8 @@ if ($i) { top: 0; left: 0; z-index: 2147483647; - color: #ff0; - background: rgba(0, 0, 0, 0.5); + color: #f6c177; + background: #6e6a86; padding: 4px 5px; font-size: 14px; pointer-events: none; diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index 558e05ee8f..69909bad21 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -133,25 +133,25 @@ function getMenu() { text: i18n.ts.move + '...', icon: 'ph-arrows-out-cardinal-bold ph-lg', children: [{ - icon: 'ph--left-bold ph-lg', + icon: 'ph-caret-left-bold ph-lg', text: i18n.ts._deck.swapLeft, action: () => { swapLeftColumn(props.column.id); }, }, { - icon: 'ph--right-bold ph-lg', + icon: 'ph-caret-right-bold ph-lg', text: i18n.ts._deck.swapRight, action: () => { swapRightColumn(props.column.id); }, }, props.isStacked ? { - icon: 'ph--up-bold ph-lg', + icon: 'ph-caret-up-bold ph-lg', text: i18n.ts._deck.swapUp, action: () => { swapUpColumn(props.column.id); }, } : undefined, props.isStacked ? { - icon: 'ph--down-bold ph-lg', + icon: 'ph-caret-down-bold ph-lg', text: i18n.ts._deck.swapDown, action: () => { swapDownColumn(props.column.id); diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 8ba813ace4..9ffe4c23f2 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -377,6 +377,10 @@ const wallpaper = localStorage.getItem('wallpaper') != null; > .button-wrapper { + > i { + transform: translateY(0.05em); + } + &.on { background-color: var(--accentedBg); width: 100%; diff --git a/patrons.json b/patrons.json index 65306d72f8..194c0e98c3 100644 --- a/patrons.json +++ b/patrons.json @@ -4,6 +4,8 @@ "@shoq@newsroom.social", "@pikadude@erisly.social", "@sage@stop.voring.me", - "@sky@therian.club" + "@sky@therian.club", + "@panos@electricrequiem.com", + "@redhunt07@www.foxyhole.io" ] } diff --git a/push-docker.sh b/push-docker.sh new file mode 100755 index 0000000000..434c9cc630 --- /dev/null +++ b/push-docker.sh @@ -0,0 +1,10 @@ +sudo systemctl start docker.service +sudo docker rmi $(docker images -q) +sudo docker compose build +sudo docker tag thatonecalculator/calckey:latest thatonecalculator/calckey:$(git describe --tags --exact-match) +sudo docker images +echo "\nPress any key to continue\n" +read +sudo docker push thatonecalculator/calckey:$(git describe --tags --exact-match) +sudo docker push thatonecalculator/calckey:latest +sudo systemctl stop docker.service