Merge branch 'develop' into 'refactor/kernel-index-cc'

# Conflicts:
#   packages/backend/src/remote/activitypub/kernel/index.ts
This commit is contained in:
Kainoa Kanter 2023-09-02 16:59:32 +00:00
commit cb3f0b2ada
463 changed files with 14572 additions and 3580 deletions

3
.gitignore vendored
View file

@ -32,6 +32,9 @@ coverage
# docker dev config # docker dev config
/dev/docker-compose.yml /dev/docker-compose.yml
# ESLint
.eslintcache
# misskey # misskey
built built
db db

View file

@ -1,10 +1,18 @@
{ {
"recommendations": [ "recommendations": [
"editorconfig.editorconfig", "editorconfig.editorconfig",
"rome.rome", "vue.volar",
"Vue.volar", "vue.vscode-typescript-vue-plugin",
"Vue.vscode-typescript-vue-plugin",
"arcanis.vscode-zipfs", "arcanis.vscode-zipfs",
"Orta.vscode-twoslash-queries" "orta.vscode-twoslash-queries",
"antfu.iconify",
"vivaxy.vscode-conventional-commits",
"ms-azuretools.vscode-docker",
"gitlab.gitlab-workflow",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"yoavbls.pretty-ts-errors",
"biomejs.biome"
] ]
} }

View file

@ -97,7 +97,7 @@ If you have access to a server that supports one of the sources below, I recomme
- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended) - 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended)
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended) - 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended)
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended) - 🍱 At least [Redis](https://redis.io/) v7
- Web Proxy (one of the following) - Web Proxy (one of the following)
- 🍀 Nginx (recommended) - 🍀 Nginx (recommended)
- 🦦 Caddy - 🦦 Caddy

12
biome.json Normal file
View file

@ -0,0 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.0.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

View file

@ -61,6 +61,8 @@ services:
# sonic: # sonic:
# restart: unless-stopped # restart: unless-stopped
# image: docker.io/valeriansaliou/sonic:v1.4.0 # image: docker.io/valeriansaliou/sonic:v1.4.0
# logging:
# driver: none
# networks: # networks:
# - calcnet # - calcnet
# volumes: # volumes:

View file

@ -71,7 +71,7 @@ driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{n
\ ? Il sera retiré de toutes les publications qui le contiennent comme pièce-jointe." \ ? Il sera retiré de toutes les publications qui le contiennent comme pièce-jointe."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?" unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre
un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive." un peu de temps. Une fois terminée, le fichier résultant sera ajouté au Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps." importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes" lists: "Listes"
noLists: "Vous navez aucune liste" noLists: "Vous navez aucune liste"

View file

@ -74,7 +74,7 @@ exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Sete
importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat." importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
lists: "Daftar" lists: "Daftar"
noLists: "Kamu tidak memiliki daftar apapun" noLists: "Kamu tidak memiliki daftar apapun"
note: "Postingan" note: "Posting"
notes: "Postingan" notes: "Postingan"
following: "Ikuti" following: "Ikuti"
followers: "Pengikut" followers: "Pengikut"
@ -1837,7 +1837,7 @@ _notification:
followBack: "Ikuti Kembali" followBack: "Ikuti Kembali"
reply: "Balas" reply: "Balas"
renote: "Posting ulang" renote: "Posting ulang"
reacted: berekasi ke postinganmu reacted: mereaksi postinganmu
renoted: memposting ulang postinganmu renoted: memposting ulang postinganmu
voted: memilih di angketmu voted: memilih di angketmu
_deck: _deck:

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,6 @@
_lang_: "한국어" _lang_: "한국어"
headlineFirefish: "노트로 연결되는 네트워크" headlineFirefish: "영원히 무료로 제공되는 오픈 소스 탈중앙화 소셜 미디어 플랫폼 🚀"
introFirefish: "환영합니다! Firefish 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 introFirefish: "환영합니다! Firefish 는 영원히 무료로 제공되는 오픈 소스 분산형 소셜 미디어 플랫폼입니다! 🚀"
있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n
새로운 세계를 탐험해 보세요🚀"
monthAndDay: "{month}월 {day}일" monthAndDay: "{month}월 {day}일"
search: "검색" search: "검색"
notifications: "알림" notifications: "알림"
@ -14,8 +12,8 @@ ok: "OK"
gotIt: "알겠어요" gotIt: "알겠어요"
cancel: "취소" cancel: "취소"
enterUsername: "유저명 입력" enterUsername: "유저명 입력"
renotedBy: "{user}님이 Renote" renotedBy: "{user}님이 부스트"
noNotes: "노트가 없습니다" noNotes: "게시물이 없습니다"
noNotifications: "표시할 알림이 없습니다" noNotifications: "표시할 알림이 없습니다"
instance: "서버" instance: "서버"
settings: "설정" settings: "설정"
@ -45,7 +43,7 @@ copyContent: "내용 복사"
copyLink: "링크 복사" copyLink: "링크 복사"
delete: "삭제" delete: "삭제"
deleteAndEdit: "삭제 후 편집" deleteAndEdit: "삭제 후 편집"
deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니까? 이 노트에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다." deleteAndEditConfirm: "이 게시물을 삭제한 뒤 다시 편집하시겠습니까? 이 게시물에 대한 리액션, 부스트, 답글 또한 모두 삭제됩니다."
addToList: "리스트에 추가" addToList: "리스트에 추가"
sendMessage: "메시지 보내기" sendMessage: "메시지 보내기"
copyUsername: "유저명 복사" copyUsername: "유저명 복사"
@ -59,20 +57,20 @@ receiveFollowRequest: "새로운 팔로우 요청이 있습니다"
followRequestAccepted: "팔로우가 수락되었습니다" followRequestAccepted: "팔로우가 수락되었습니다"
mention: "멘션" mention: "멘션"
mentions: "받은 멘션" mentions: "받은 멘션"
directNotes: "다이렉트 노트" directNotes: "다이렉트 게시물"
importAndExport: "가져오기와 내보내기" importAndExport: "가져오기와 내보내기"
import: "가져오기" import: "가져오기"
export: "내보내기" export: "내보내기"
files: "파일" files: "파일"
download: "다운로드" download: "다운로드"
driveFileDeleteConfirm: "파일 \"{name}\" 을 삭제하시겠습니까? 이 파일이 첨부되었더 노트에서도 같이 삭제됩니다." driveFileDeleteConfirm: "파일 \"{name}\" 을 삭제하시겠습니까? 이 파일이 첨부되었더 게시물에서도 같이 삭제됩니다."
unfollowConfirm: "{name}님을 언팔로우하시겠습니까?" unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다." exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다." importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
lists: "리스트" lists: "리스트"
noLists: "리스트가 없습니다" noLists: "리스트가 없습니다"
note: "노트" note: "게시"
notes: "노트" notes: "게시물"
following: "팔로잉" following: "팔로잉"
followers: "팔로워" followers: "팔로워"
followsYou: "당신을 팔로우합니다" followsYou: "당신을 팔로우합니다"
@ -96,13 +94,13 @@ followRequests: "팔로우 요청"
unfollow: "팔로우 해제" unfollow: "팔로우 해제"
followRequestPending: "팔로우 허가 대기중" followRequestPending: "팔로우 허가 대기중"
enterEmoji: "이모지 입력" enterEmoji: "이모지 입력"
renote: "Renote" renote: "부스트"
unrenote: "Renote 취소" unrenote: "부스트 취소"
renoted: "Renote 하였습니다" renoted: "부스트 하였습니다"
cantRenote: "이 게시물은 Renote할 수 없습니다." cantRenote: "이 게시물은 부스트할 수 없습니다."
cantReRenote: "Renote를 Renote할 수 없습니다." cantReRenote: "부스트를 부스트할 수 없습니다."
quote: "인용" quote: "인용"
pinnedNote: "고정해놓은 노트" pinnedNote: "고정해놓은 게시물"
pinned: "프로필에 고정" pinned: "프로필에 고정"
you: "당신" you: "당신"
clickToShow: "클릭하여 보기" clickToShow: "클릭하여 보기"
@ -146,7 +144,7 @@ flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우
봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
flagAsCat: "나는 고양이다냥" flagAsCat: "나는 고양이다냥"
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요." flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기" flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다." flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락" autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
addAccount: "계정 추가" addAccount: "계정 추가"
@ -205,7 +203,7 @@ noUsers: "아무도 없습니다"
editProfile: "프로필 수정" editProfile: "프로필 수정"
noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?" noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?"
pinLimitExceeded: "더 이상 고정할 수 없습니다." pinLimitExceeded: "더 이상 고정할 수 없습니다."
intro: "Misskey의 설치가 완료되었습니다! 관리자 계정을 생성해주세요." intro: "Firefish의 설치가 완료되었습니다! 관리자 계정을 생성해주세요."
done: "완료" done: "완료"
processing: "처리중" processing: "처리중"
preview: "미리보기" preview: "미리보기"
@ -294,7 +292,7 @@ emptyDrive: "드라이브가 비어 있습니다"
emptyFolder: "폴더가 비어 있습니다" emptyFolder: "폴더가 비어 있습니다"
unableToDelete: "삭제할 수 없습니다" unableToDelete: "삭제할 수 없습니다"
inputNewFileName: "바꿀 파일명을 입력해 주세요" inputNewFileName: "바꿀 파일명을 입력해 주세요"
inputNewDescription: "새 캡션을 입력해 주세요" inputNewDescription: "새 설명을 입력해 주세요"
inputNewFolderName: "바꿀 폴더명을 입력해 주세요" inputNewFolderName: "바꿀 폴더명을 입력해 주세요"
circularReferenceFolder: "지정한 폴더가 이동할 폴더의 하위 폴더입니다." circularReferenceFolder: "지정한 폴더가 이동할 폴더의 하위 폴더입니다."
hasChildFilesOrFolders: "이 폴더는 비어있지 않기 때문에 삭제할 수 없습니다." hasChildFilesOrFolders: "이 폴더는 비어있지 않기 때문에 삭제할 수 없습니다."
@ -386,7 +384,7 @@ exploreFediverse: "연합우주를 탐색"
popularTags: "인기 태그" popularTags: "인기 태그"
userList: "리스트" userList: "리스트"
about: "정보" about: "정보"
aboutFirefish: "Misskey에 대하여" aboutFirefish: "Firefish에 대하여"
administrator: "관리자" administrator: "관리자"
token: "토큰" token: "토큰"
twoStepAuthentication: "2단계 인증" twoStepAuthentication: "2단계 인증"
@ -537,8 +535,8 @@ sort: "정렬"
ascendingOrder: "오름차순" ascendingOrder: "오름차순"
descendingOrder: "내림차순" descendingOrder: "내림차순"
scratchpad: "스크래치 패드" scratchpad: "스크래치 패드"
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Firefish 와 상호 작용하는 코드를 작성, scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Firefish 와 상호 작용하는 코드를
실행 및 결과를 확인할 수 있습니다." 작성, 실행 및 결과를 확인할 수 있습니다."
output: "출력" output: "출력"
script: "스크립트" script: "스크립트"
disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음" disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음"
@ -572,8 +570,8 @@ disablePlayer: "플레이어 닫기"
expandTweet: "트윗 확장하기" expandTweet: "트윗 확장하기"
themeEditor: "테마 에디터" themeEditor: "테마 에디터"
description: "설명" description: "설명"
describeFile: "캡션 추가" describeFile: "설명 추가"
enterFileDescription: "캡션 입력" enterFileDescription: "설명 입력"
author: "작성자" author: "작성자"
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?" leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
manage: "관리" manage: "관리"
@ -703,7 +701,7 @@ experimentalFeatures: "실험실"
developer: "개발자" developer: "개발자"
makeExplorable: "\"발견하기\"에 내 계정 보이기" makeExplorable: "\"발견하기\"에 내 계정 보이기"
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다." makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시" showGapBetweenNotesInTimeline: "타임라인의 게시물 사이를 띄워서 표시"
duplicate: "복제" duplicate: "복제"
left: "왼쪽" left: "왼쪽"
center: "가운데" center: "가운데"
@ -758,7 +756,7 @@ unlikeConfirm: "좋아요를 취소할까요?"
fullView: "전체 화면" fullView: "전체 화면"
quitFullView: "전체 화면 해제" quitFullView: "전체 화면 해제"
addDescription: "설명 추가" addDescription: "설명 추가"
userPagePinTip: "각 게시물의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 노트를 표시해 둘 수 있어요." userPagePinTip: "각 게시물의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 게시물을 표시해 둘 수 있어요."
notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어요" notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어요"
info: "정보" info: "정보"
userInfo: "유저 정보" userInfo: "유저 정보"
@ -789,7 +787,7 @@ gallery: "갤러리"
recentPosts: "최근 포스트" recentPosts: "최근 포스트"
popularPosts: "인기 포스트" popularPosts: "인기 포스트"
shareWithNote: "게시물로 공유" shareWithNote: "게시물로 공유"
ads: "광고" ads: "커뮤니티 배너"
expiration: "기한" expiration: "기한"
memo: "메모" memo: "메모"
priority: "우선순위" priority: "우선순위"
@ -811,7 +809,7 @@ hashtags: "해시태그"
troubleshooting: "문제 해결" troubleshooting: "문제 해결"
useBlurEffect: "UI에 흐림 효과 사용" useBlurEffect: "UI에 흐림 효과 사용"
learnMore: "자세히" learnMore: "자세히"
misskeyUpdated: "Misskey가 업데이트 되었습니다!" misskeyUpdated: "Firefish가 업데이트 되었습니다!"
whatIsNew: "패치 정보 보기" whatIsNew: "패치 정보 보기"
translate: "번역" translate: "번역"
translatedFrom: "{x}에서 번역" translatedFrom: "{x}에서 번역"
@ -840,7 +838,7 @@ unmuteThread: "글타래 뮤트 해제"
ffVisibility: "내 인맥의 공개 범위" ffVisibility: "내 인맥의 공개 범위"
ffVisibilityDescription: "나의 팔로우와 팔로워 정보에 대한 공개 범위를 설정할 수 있습니다." ffVisibilityDescription: "나의 팔로우와 팔로워 정보에 대한 공개 범위를 설정할 수 있습니다."
continueThread: "이 글타래 이어서 보기" continueThread: "이 글타래 이어서 보기"
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? " deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까?"
incorrectPassword: "비밀번호가 올바르지 않습니다." incorrectPassword: "비밀번호가 올바르지 않습니다."
voteConfirm: "\"{choice}\"에 투표하시겠습니까?" voteConfirm: "\"{choice}\"에 투표하시겠습니까?"
hide: "숨기기" hide: "숨기기"
@ -992,12 +990,12 @@ _registry:
domain: "도메인" domain: "도메인"
createKey: "키 생성" createKey: "키 생성"
_aboutFirefish: _aboutFirefish:
about: "Misskey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다." about: "Firefish는 ThatOneCalculator에 의해서 2022년부터 개발되어 온 Misskey의 포크 소프트웨어 입니다."
contributors: "주요 기여자" contributors: "주요 기여자"
allContributors: "모든 기여자" allContributors: "모든 기여자"
source: "소스 코드" source: "소스 코드"
translation: "Misskey를 번역하기" translation: "Firefish를 번역하기"
donate: "Misskey에 기부하기" donate: "Firefish에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰" morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자" patrons: "후원자"
patronsList: 기부 금액이 아닌 시간 순서로 정렬합니다. 위 링크를 통해 후원하여 당신의 이름을 새겨 보세요! patronsList: 기부 금액이 아닌 시간 순서로 정렬합니다. 위 링크를 통해 후원하여 당신의 이름을 새겨 보세요!
@ -1006,15 +1004,16 @@ _aboutFirefish:
pleaseDonateToFirefish: Firefish의 개발에 후원하는 것을 검토하여 주십시오. pleaseDonateToFirefish: Firefish의 개발에 후원하는 것을 검토하여 주십시오.
donateHost: '{host} 에게 기부하기' donateHost: '{host} 에게 기부하기'
donateTitle: Firefish가 마음에 드시나요? donateTitle: Firefish가 마음에 드시나요?
misskeyContributors: 오리지널 Misskey 기여자
_nsfw: _nsfw:
respect: "열람주의로 설정된 미디어 숨기기" respect: "열람주의로 설정된 미디어 숨기기"
ignore: "열람 주의 미디어 항상 표시" ignore: "열람 주의 미디어 항상 표시"
force: "미디어 항상 숨기기" force: "미디어 항상 숨기기"
_mfm: _mfm:
cheatSheet: "MFM 도움말" cheatSheet: "MFM 도움말"
intro: "MFM는 Misskey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 intro: "MFM는 Misskey나 Firefish, Akkoma 외의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서
수 있습니다." 사용할 수 있는 구문을 확인할 수 있습니다."
dummy: "Misskey로 연합우주의 세계가 펼쳐집니다" dummy: "Firefish로 연합우주의 세계가 펼쳐집니다"
mention: "멘션" mention: "멘션"
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다." mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
hashtag: "해시태그" hashtag: "해시태그"
@ -1134,7 +1133,7 @@ _wordMute:
_instanceMute: _instanceMute:
instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 게시물과 부스트를 뮤트합니다." instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 게시물과 부스트를 뮤트합니다."
instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요" instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요"
title: "지정한 서버의 노트를 숨깁니다." title: "지정한 서버의 게시물을 숨깁니다."
heading: "뮤트할 서버" heading: "뮤트할 서버"
_theme: _theme:
explore: "테마 찾아보기" explore: "테마 찾아보기"
@ -1747,7 +1746,7 @@ _notification:
youGotMention: "{name}님이 멘션함" youGotMention: "{name}님이 멘션함"
youGotReply: "{name}님이 답글함" youGotReply: "{name}님이 답글함"
youGotQuote: "{name}님이 인용함" youGotQuote: "{name}님이 인용함"
youRenoted: "{name}님이 Boost" youRenoted: "{name}님의 부스트"
youGotPoll: "{name}님이 투표함" youGotPoll: "{name}님이 투표함"
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요" youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요" youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
@ -1824,7 +1823,7 @@ replayTutorial: 튜토리얼 다시 보기
renoteMute: 부스트 뮤트 renoteMute: 부스트 뮤트
antennaInstancesDescription: 서버 호스트를 한 줄에 하나씩 입력하세요 antennaInstancesDescription: 서버 호스트를 한 줄에 하나씩 입력하세요
userSaysSomethingReason: '{name} 님이 {reason}에 대해 말했습니다' userSaysSomethingReason: '{name} 님이 {reason}에 대해 말했습니다'
userSaysSomethingReasonQuote: '{name} 님이 {reason} 을 포함하는 노트를 인용했습니다' userSaysSomethingReasonQuote: '{name} 님이 {reason} 을 포함하는 게시물을 인용했습니다'
pushNotification: 푸시 알림 pushNotification: 푸시 알림
channelFederationWarn: 현재 채널은 다른 서버로 연합되지 않습니다 channelFederationWarn: 현재 채널은 다른 서버로 연합되지 않습니다
enableServerMachineStats: 서버의 머신 정보를 공개 enableServerMachineStats: 서버의 머신 정보를 공개
@ -1836,7 +1835,7 @@ cannotUploadBecauseExceedsFileSizeLimit: 파일 크기 제한을 초과하여
pushNotificationNotSupported: 브라우저 및 서버가 푸시 알림을 지원하지 않습니다 pushNotificationNotSupported: 브라우저 및 서버가 푸시 알림을 지원하지 않습니다
enableRecommendedTimeline: 추천 타임라인을 활성화 enableRecommendedTimeline: 추천 타임라인을 활성화
pushNotificationAlreadySubscribed: 푸시 알림이 활성화되었습니다 pushNotificationAlreadySubscribed: 푸시 알림이 활성화되었습니다
caption: 자동 캡션 caption: 자동으로 설명 붙이기
findOtherInstance: 다른 서버 둘러보기 findOtherInstance: 다른 서버 둘러보기
enableIdenticonGeneration: 유저 별 Identicon의 생성을 활성화 enableIdenticonGeneration: 유저 별 Identicon의 생성을 활성화
secureModeInfo: 인증 정보가 없는 리모트 서버로부터의 요청에 응답하지 않습니다. secureModeInfo: 인증 정보가 없는 리모트 서버로부터의 요청에 응답하지 않습니다.
@ -1858,7 +1857,7 @@ customKaTeXMacroDescription: 'KaTeX 매크로를 지정하여 수식을 더욱
사용할 수 없습니다. 올바르지 않은 정의는 무시됩니다. 문자열을 치환하는 수준에서만 지원하며, 조건 분기와 같은 고도의 구문은 사용할 수 없습니다.' 사용할 수 없습니다. 올바르지 않은 정의는 무시됩니다. 문자열을 치환하는 수준에서만 지원하며, 조건 분기와 같은 고도의 구문은 사용할 수 없습니다.'
reactionPickerSkinTone: 선호하는 이모지 피부 톤 reactionPickerSkinTone: 선호하는 이모지 피부 톤
selectInstance: 서버 선택 selectInstance: 서버 선택
showAds: 광고 보이기 showAds: 커뮤니티 배너를 보이기
searchPlaceholder: Firefish에서 검색 searchPlaceholder: Firefish에서 검색
addInstance: 서버 추가 addInstance: 서버 추가
listsDesc: 리스트를 사용하여 특정 유저로 이루어진 타임라인을 구성할 수 있습니다. 리스트는 '타임라인' 페이지에서 접근할 수 있습니다. listsDesc: 리스트를 사용하여 특정 유저로 이루어진 타임라인을 구성할 수 있습니다. 리스트는 '타임라인' 페이지에서 접근할 수 있습니다.
@ -1867,7 +1866,7 @@ showEmojisInReactionNotifications: 리액션 알림에 이모지 보이기
hiddenTagsDescription: 트렌드와 '발견하기'에서 제외할 해시태그를 ('#'을 제외하고) 한 줄에 하나씩 입력하여 주십시오. 이 설정은 hiddenTagsDescription: 트렌드와 '발견하기'에서 제외할 해시태그를 ('#'을 제외하고) 한 줄에 하나씩 입력하여 주십시오. 이 설정은
트렌드와 '발견하기' 외에는 영향을 주지 않습니다. 트렌드와 '발견하기' 외에는 영향을 주지 않습니다.
antennasDesc: "안테나에서는 조건에 맞는 게시물이 표시됩니다.\n'타임라인' 페이지에서 접근할 수 있습니다." antennasDesc: "안테나에서는 조건에 맞는 게시물이 표시됩니다.\n'타임라인' 페이지에서 접근할 수 있습니다."
expandOnNoteClick: 노트를 클릭하여 자세히 표시 expandOnNoteClick: 게시물을 클릭하여 자세히 표시
expandOnNoteClickDesc: 비활성화한 경우에도 우클릭 메뉴 또는 타임스탬프를 클릭하여 열 수 있습니다. expandOnNoteClickDesc: 비활성화한 경우에도 우클릭 메뉴 또는 타임스탬프를 클릭하여 열 수 있습니다.
customMOTDDescription: 유저가 페이지를 로딩/새로고침할 때 마다 무작위로 표시할 메시지를 한 줄에 하나씩 입력합니다. customMOTDDescription: 유저가 페이지를 로딩/새로고침할 때 마다 무작위로 표시할 메시지를 한 줄에 하나씩 입력합니다.
moveFrom: 다른 계정에서 이 계정으로 이사하기 moveFrom: 다른 계정에서 이 계정으로 이사하기
@ -1887,7 +1886,7 @@ swipeOnDesktop: 데스크톱에서도 모바일과 같은 스와이프를 사용
migration: 계정 이사 migration: 계정 이사
moveTo: 이 계정에서 새로운 계정으로 이사 moveTo: 이 계정에서 새로운 계정으로 이사
deleted: 삭제됨 deleted: 삭제됨
editNote: 노트 편집 editNote: 게시물 편집
edited: '편짐됨: {date} {time}' edited: '편짐됨: {date} {time}'
customMOTD: 사용자 지정 MOTD (스플래시 화면 메시지) customMOTD: 사용자 지정 MOTD (스플래시 화면 메시지)
selectChannel: 채널 선택 selectChannel: 채널 선택
@ -1896,14 +1895,14 @@ splash: 스플래시 화면
preventAiLearningDescription: 업로드한 게시물이나 미디어를 AI 모델이 학습하지 말기를 요구합니다. preventAiLearningDescription: 업로드한 게시물이나 미디어를 AI 모델이 학습하지 말기를 요구합니다.
isBot: 이 계정은 봇입니다 isBot: 이 계정은 봇입니다
isAdmin: 관리자 isAdmin: 관리자
newer: 새로운 노트 newer: 새로운 게시물
older: 이전 노트 older: 이전 게시물
renoteUnmute: 부스트 뮤트 해제 renoteUnmute: 부스트 뮤트 해제
accountMoved: '이 유저는 다른 계정으로 이사했습니다:' accountMoved: '이 유저는 다른 계정으로 이사했습니다:'
silencedInstances: 사일런스한 서버 silencedInstances: 사일런스한 서버
accessibility: 접근성 accessibility: 접근성
userSaysSomethingReasonReply: '{name} 님이 {reason} 을 포함하는 노트에 답글했습니다' userSaysSomethingReasonReply: '{name} 님이 {reason} 을 포함하는 게시물에 답글했습니다'
userSaysSomethingReasonRenote: '{name} 님이 {reason} 을 포함하는 노트를 부스트했습니다' userSaysSomethingReasonRenote: '{name} 님이 {reason} 을 포함하는 게시물을 부스트했습니다'
breakFollowConfirm: 팔로워를 해제하시겠습니까? breakFollowConfirm: 팔로워를 해제하시겠습니까?
indexFrom: 이 게시물 ID부터 인덱싱하기 indexFrom: 이 게시물 ID부터 인덱싱하기
noThankYou: 괜찮습니다 noThankYou: 괜찮습니다
@ -1949,8 +1948,8 @@ silencedWarning: 관리자가 사일런스한 서버에 속한 유저이며, 스
isModerator: 모더레이터 isModerator: 모더레이터
isPatron: Firefish 후원자 isPatron: Firefish 후원자
_experiments: _experiments:
postImportsCaption: 유저가 과거에 작성한 게시물을 Firefish(Firefish), Misskey, Mastodon, Akkoma, postImportsCaption: 유저가 과거에 작성한 게시물을 Firefish, Misskey, Mastodon, Akkoma, Pleroma
Pleroma 등에서 가져올 수 있게 합니다. 작업 대기열의 처리 속도가 느릴 경우 서비스에 영향이 갈 수 있습니다. 등에서 가져올 수 있게 합니다. 작업 대기열의 처리 속도가 느릴 경우 서비스에 영향이 갈 수 있습니다.
enablePostImports: 게시물 가져오기를 활성화 enablePostImports: 게시물 가져오기를 활성화
title: 실험실 title: 실험실
_messaging: _messaging:
@ -1958,8 +1957,8 @@ _messaging:
dms: 개인 메시지 dms: 개인 메시지
_tutorial: _tutorial:
title: Firefly의 사용 방법 title: Firefly의 사용 방법
step5_5: '{icon} 소셜 타임라인은 홈 타임라인과 소셜 타임라인을 합친 것과 같습니다.' step5_5: '{icon} 소셜 타임라인은 홈 타임라인과 로컬 타임라인을 합친 것과 같습니다.'
step4_1: 노트를 올려 봅시다. step4_1: 글을 올려 봅시다.
step5_3: '{icon} 홈 타임라인은 내가 팔로우하고 있는 계정의 게시물을 볼 수 있는 타임라인입니다.' step5_3: '{icon} 홈 타임라인은 내가 팔로우하고 있는 계정의 게시물을 볼 수 있는 타임라인입니다.'
step6_2: 이 서버에 가입을 마친 당신은 단순히 Firefish 서버의 유저가 아닌, 수많은 서버가 서로 상호작용하는 연합우주에 참가하시게 step6_2: 이 서버에 가입을 마친 당신은 단순히 Firefish 서버의 유저가 아닌, 수많은 서버가 서로 상호작용하는 연합우주에 참가하시게
된 것입니다. 된 것입니다.
@ -2003,3 +2002,17 @@ _feeds:
_dialog: _dialog:
charactersExceeded: 글자 수 제한을 초과했습니다! 현재 {current}자 / 최대 {max}자 charactersExceeded: 글자 수 제한을 초과했습니다! 현재 {current}자 / 최대 {max}자
charactersBelow: 최소 글자 수 보다 작습니다! 현재 {current}자 / 최소 {max}자 charactersBelow: 최소 글자 수 보다 작습니다! 현재 {current}자 / 최소 {max}자
emojiPackCreator: 이모지 팩 만든이
objectStorageS3ForcePathStyleDesc: Endpoint URL을 '<bucket>.s3.amazonaws.com'가 아닌 's3.amazonaws.com/<bucket>/'와
같은 형식으로 사용할 경우에 활성화해 주세요.
confirm: 확인
importZip: ZIP으로 가져오기
exportZip: ZIP으로 내보내기
origin: 원본
objectStorageS3ForcePathStyle: 경로 기반 Endpoint URL을 사용하기
delete2fa: 2FA를 비활성화
delete2faConfirm: 이 계정에서 2FA를 영구히 삭제합니다. 계속하시겠습니까?
deletePasskeys: 보안 키 삭제
deletePasskeysConfirm: 이 계정에서 모든 보안 키를 영구히 삭제합니다. 계속하시겠습니까?
inputNotMatch: 입력이 일치하지 않습니다
addRe: 열람주의로 표시된 게시물의 답장에 're:' 붙이기

View file

@ -1,7 +1,7 @@
---
_lang_: "Português" _lang_: "Português"
headlineFirefish: "Uma rede ligada por notas" headlineFirefish: "Uma rede ligada por notas"
introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de código aberto.\nCria \"notas\" e partilha o que te ocorre com todos à tua volta. 📡\nCom \"reações\" podes também expressar logo o que sentes às notas de todos. 👍\nExploremos um novo mundo! 🚀" introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de
código aberto, gratuito para sempre! 🚀"
monthAndDay: "{day}/{month}" monthAndDay: "{day}/{month}"
search: "Buscar" search: "Buscar"
notifications: "Notificações" notifications: "Notificações"
@ -44,7 +44,8 @@ copyContent: "Copiar conteúdos"
copyLink: "Copiar hiperligação" copyLink: "Copiar hiperligação"
delete: "Eliminar" delete: "Eliminar"
deleteAndEdit: "Eliminar e editar" deleteAndEdit: "Eliminar e editar"
deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la? Irás perder todas as suas reações, renotas e respostas." deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la?
Irás perder todas as suas reações, renotas e respostas."
addToList: "Adicionar a lista" addToList: "Adicionar a lista"
sendMessage: "Enviar uma mensagem" sendMessage: "Enviar uma mensagem"
copyUsername: "Copiar nome de utilizador" copyUsername: "Copiar nome de utilizador"
@ -64,9 +65,11 @@ import: "Importar"
export: "Exportar" export: "Exportar"
files: "Ficheiros" files: "Ficheiros"
download: "Descarregar" download: "Descarregar"
driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serão também apagadas." driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"\
? As notas que tenham este ficheiro anexado serão também apagadas."
unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?" unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo. Será adicionado à tua Drive após a conclusão do processo." exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo.
Será adicionado à tua Drive após a conclusão do processo."
importRequested: "Pediste uma importação. Este processo pode demorar algum tempo." importRequested: "Pediste uma importação. Este processo pode demorar algum tempo."
lists: "Listas" lists: "Listas"
noLists: "Não tens nenhuma lista" noLists: "Não tens nenhuma lista"
@ -81,9 +84,12 @@ error: "Erro"
somethingHappened: "Ocorreu um erro" somethingHappened: "Ocorreu um erro"
retry: "Tentar novamente" retry: "Tentar novamente"
pageLoadError: "Ocorreu um erro ao carregar a página." pageLoadError: "Ocorreu um erro ao carregar a página."
pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo." pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache
serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente novamente." do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para atualizar seu cliente." serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente
novamente."
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para
atualizar seu cliente."
enterListName: "Insira um nome para a lista" enterListName: "Insira um nome para a lista"
privacy: "Privacidade" privacy: "Privacidade"
makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados" makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
@ -108,7 +114,8 @@ sensitive: "Conteúdo sensível"
add: "Adicionar" add: "Adicionar"
reaction: "Reações" reaction: "Reações"
reactionSetting: "Quais reações a mostrar no selecionador de reações" reactionSetting: "Quais reações a mostrar no selecionador de reações"
reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione + para adicionar." reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione
+ para adicionar."
rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas" rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas"
attachCancel: "Remover anexo" attachCancel: "Remover anexo"
markAsSensitive: "Marcar como sensível" markAsSensitive: "Marcar como sensível"
@ -137,13 +144,18 @@ emojiUrl: "URL do Emoji"
addEmoji: "Adicionar um Emoji" addEmoji: "Adicionar um Emoji"
settingGuide: "Guia de configuração" settingGuide: "Guia de configuração"
cacheRemoteFiles: "Memória transitória de arquivos remotos" cacheRemoteFiles: "Memória transitória de arquivos remotos"
cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos não serão armazenados em memória transitória e serão vinculados diretamente. Economiza o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego." cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos
não serão armazenados em memória transitória e serão vinculados diretamente. Economiza
o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego."
flagAsBot: "Marcar conta como robô" flagAsBot: "Marcar conta como robô"
flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador. Quando ativado, serve como um sinalizador para evitar o encadeamento de reações para outros programadores, e o manuseio do sistema do Firefish é adequado para bots." flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador.
Quando ativado, serve como um sinalizador para evitar o encadeamento de reações
para outros programadores, e o manuseio do sistema do Firefish é adequado para bots."
flagAsCat: "Marcar conta como gato" flagAsCat: "Marcar conta como gato"
flagAsCatDescription: "Ative essa opção para marcar essa conta como gato." flagAsCatDescription: "Ative essa opção para marcar essa conta como gato."
flagShowTimelineReplies: "Mostrar respostas na linha de tempo" flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas às outras notas do utilizador, além da nota do utilizador." flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas
às outras notas do utilizador, além da nota do utilizador."
autoAcceptFollowed: "Aprove automaticamente os seguidores dos seguintes utilizadores" autoAcceptFollowed: "Aprove automaticamente os seguidores dos seguintes utilizadores"
addAccount: "Adicionar Conta" addAccount: "Adicionar Conta"
loginFailed: "Não consegui logar" loginFailed: "Não consegui logar"
@ -156,7 +168,10 @@ searchWith: "Buscar: {q}"
youHaveNoLists: "Não tem nenhuma lista" youHaveNoLists: "Não tem nenhuma lista"
followConfirm: "Tem certeza que quer deixar de seguir {name}?" followConfirm: "Tem certeza que quer deixar de seguir {name}?"
proxyAccount: "Conta proxy" proxyAccount: "Conta proxy"
proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota para utilizadores sob determinadas condições. Por exemplo, quando um utilizador lista um utilizador remoto, a atividade não será entregue à instância, a menos que alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir." proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota
para utilizadores sob determinadas condições. Por exemplo, quando um utilizador
lista um utilizador remoto, a atividade não será entregue à instância, a menos que
alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir."
host: "hospedeiro" host: "hospedeiro"
selectUser: "Selecionar utilizador" selectUser: "Selecionar utilizador"
recipient: "Morada" recipient: "Morada"
@ -186,11 +201,15 @@ instanceInfo: "Informações da instância"
statistics: "Estatisticas" statistics: "Estatisticas"
clearQueue: "Limpar a fila" clearQueue: "Limpar a fila"
clearQueueConfirmTitle: "Quer limpar a fila?" clearQueueConfirmTitle: "Quer limpar a fila?"
clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente você não precisa fazer isso." clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente
você não precisa fazer isso."
clearCachedFiles: "Limpar memória transitória" clearCachedFiles: "Limpar memória transitória"
clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos armazenados em memória transitória?" clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos
armazenados em memória transitória?"
blockedInstances: "Instância bloqueada" blockedInstances: "Instância bloqueada"
blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear, separados por quebras de linha. Uma instância bloqueada não poderá interagir com esta instância." blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear,
separados por quebras de linha. Uma instância bloqueada não poderá interagir com
esta instância."
muteAndBlock: "Silenciar e bloquear" muteAndBlock: "Silenciar e bloquear"
mutedUsers: "Silenciar utilizador" mutedUsers: "Silenciar utilizador"
blockedUsers: "Utilizadores bloqueados" blockedUsers: "Utilizadores bloqueados"
@ -238,7 +257,9 @@ saved: "Salvo"
messaging: "Chat" messaging: "Chat"
upload: "Enviando" upload: "Enviando"
keepOriginalUploading: "Manter a imagem original" keepOriginalUploading: "Manter a imagem original"
keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem. Quando desligado, a imagem para publicação na web será gerada no navegador no momento do upload." keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem.
Quando desligado, a imagem para publicação na web será gerada no navegador no momento
do upload."
fromDrive: "\nDa unidade" fromDrive: "\nDa unidade"
fromUrl: "Da URL" fromUrl: "Da URL"
uploadFromUrl: "Carregamento de URL" uploadFromUrl: "Carregamento de URL"
@ -262,8 +283,8 @@ yearsOld: "{age} anos"
registeredDate: "Data de registro" registeredDate: "Data de registro"
location: "Lugar, colocar" location: "Lugar, colocar"
theme: "tema" theme: "tema"
themeForLightMode: "Temas usados no modo de luz" themeForLightMode: "Tema a usar no Modo Diurno"
themeForDarkMode: "Temas usados no modo escuro" themeForDarkMode: "Temas usados no Modo Noturno"
light: "Claro" light: "Claro"
dark: "Escuro" dark: "Escuro"
lightThemes: "Tema claro" lightThemes: "Tema claro"
@ -271,7 +292,7 @@ darkThemes: "Tema escuro"
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo" syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
drive: "Unidades" drive: "Unidades"
fileName: "Nome do Ficheiro" fileName: "Nome do Ficheiro"
selectFile: "Selecione os arquivos" selectFile: "Selecione o arquivo"
selectFiles: "Selecione os arquivos" selectFiles: "Selecione os arquivos"
selectFolder: "Selecionar uma pasta" selectFolder: "Selecionar uma pasta"
selectFolders: "Selecionar uma pasta" selectFolders: "Selecionar uma pasta"
@ -286,8 +307,9 @@ emptyFolder: "A pasta está vazia"
unableToDelete: "Não é possível eliminar" unableToDelete: "Não é possível eliminar"
inputNewFileName: "Por favor, digite um novo nome para a pasta!" inputNewFileName: "Por favor, digite um novo nome para a pasta!"
inputNewDescription: "Insira uma nova legenda" inputNewDescription: "Insira uma nova legenda"
inputNewFolderName: "Por favor, digite um novo nome para a pasta!" inputNewFolderName: "Por favor, digite um novo nome para a pasta"
circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja mover." circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja
mover."
hasChildFilesOrFolders: "Esta pasta não está vazia e não pode ser excluída." hasChildFilesOrFolders: "Esta pasta não está vazia e não pode ser excluída."
copyUrl: "Copiar URL" copyUrl: "Copiar URL"
rename: "Renomear" rename: "Renomear"
@ -321,7 +343,8 @@ connectService: "Conectar"
disconnectService: "Desconectar" disconnectService: "Desconectar"
enableLocalTimeline: "Ativar linha do tempo local" enableLocalTimeline: "Ativar linha do tempo local"
enableGlobalTimeline: "Ativar linha do tempo global" enableGlobalTimeline: "Ativar linha do tempo global"
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores e moderadores ainda poderão usá-las por conveniência." disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores
e moderadores ainda poderão usá-las por conveniência."
registration: "Registar" registration: "Registar"
enableRegistration: "Permitir que qualquer pessoa se registre" enableRegistration: "Permitir que qualquer pessoa se registre"
invite: "Convidar" invite: "Convidar"
@ -333,9 +356,11 @@ bannerUrl: "URL da imagem do banner"
backgroundImageUrl: "URL da imagem de fundo" backgroundImageUrl: "URL da imagem de fundo"
basicInfo: "Informações básicas" basicInfo: "Informações básicas"
pinnedUsers: "Utilizador fixado" pinnedUsers: "Utilizador fixado"
pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página \"Localizar\", etc., separados por quebras de linha." pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página
\"Localizar\", etc., separados por quebras de linha."
pinnedPages: "Página fixada" pinnedPages: "Página fixada"
pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página superior da instância, separada por quebras de linha." pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página
superior da instância, separada por quebras de linha."
pinnedClipId: "ID do clipe a ser fixado" pinnedClipId: "ID do clipe a ser fixado"
pinnedNotes: "Post fixado" pinnedNotes: "Post fixado"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -346,18 +371,21 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Habilitar reCAPTCHA" enableRecaptcha: "Habilitar reCAPTCHA"
recaptchaSiteKey: "Chave do sítio web" recaptchaSiteKey: "Chave do sítio web"
recaptchaSecretKey: "Chave secreta" recaptchaSecretKey: "Chave secreta"
avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados." avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja
desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados."
antennas: "Antenas" antennas: "Antenas"
manageAntennas: "Gestão de antena" manageAntennas: "Gestão de antena"
name: "Nome" name: "Nome"
antennaSource: "Origem de entrada" antennaSource: "Origem de entrada"
antennaKeywords: "Palavras-chave recebidas" antennaKeywords: "Palavras-chave recebidas"
antennaExcludeKeywords: "Palavras-chave negativas" antennaExcludeKeywords: "Palavras-chave negativas"
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR." antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação
AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
notifyAntenna: "Notificar novas notas" notifyAntenna: "Notificar novas notas"
withFileAntenna: "Apenas notas com arquivos anexados" withFileAntenna: "Apenas notas com arquivos anexados"
enableServiceworker: "Ative as notificações push para o seu navegador" enableServiceworker: "Ative as notificações push para o seu navegador"
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha" antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de
linha"
caseSensitive: "Maiúsculas e minúsculas" caseSensitive: "Maiúsculas e minúsculas"
withReplies: "Incluindo resposta" withReplies: "Incluindo resposta"
connectedTo: "Você está conectado à seguinte conta" connectedTo: "Você está conectado à seguinte conta"
@ -433,15 +461,19 @@ showFeaturedNotesInTimeline: "Mostrar notas recomendadas na linha do tempo"
objectStorage: "Armazenamento de objetos" objectStorage: "Armazenamento de objetos"
useObjectStorage: "Usar armazenamento de objetos" useObjectStorage: "Usar armazenamento de objetos"
objectStorageBaseUrl: "URL base" objectStorageBaseUrl: "URL base"
objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/ <bucket>' ." objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um
CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/
<bucket>' ."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Especifique o nome do bucket do serviço a ser usado." objectStorageBucketDesc: "Especifique o nome do bucket do serviço a ser usado."
objectStoragePrefix: "Prefixo" objectStoragePrefix: "Prefixo"
objectStoragePrefixDesc: "Ele é armazenado neste diretório de prefixo." objectStoragePrefixDesc: "Ele é armazenado neste diretório de prefixo."
objectStorageEndpoint: "Ponto final" objectStorageEndpoint: "Ponto final"
objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'." objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique
o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'."
objectStorageRegion: "Região" objectStorageRegion: "Região"
objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'." objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço
não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'."
objectStorageUseSSL: "Usar SSL" objectStorageUseSSL: "Usar SSL"
objectStorageUseSSLDesc: "Desative-o se não quiser usar https para conexões de API" objectStorageUseSSLDesc: "Desative-o se não quiser usar https para conexões de API"
objectStorageUseProxy: "Usar proxy" objectStorageUseProxy: "Usar proxy"
@ -449,7 +481,8 @@ objectStorageUseProxyDesc: "Se você não usa proxy para conexão de API, desati
objectStorageSetPublicRead: "Definir 'public-read' ao fazer o upload" objectStorageSetPublicRead: "Definir 'public-read' ao fazer o upload"
serverLogs: "Registro do servidor" serverLogs: "Registro do servidor"
deleteAll: "Apagar Tudo" deleteAll: "Apagar Tudo"
showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do tempo" showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do
tempo"
newNoteRecived: "Nova nota recebida" newNoteRecived: "Nova nota recebida"
sounds: "Sons" sounds: "Sons"
listen: "Ouvir" listen: "Ouvir"
@ -618,7 +651,8 @@ _pages:
_dailyRannum: _dailyRannum:
arg1: "Valor mínimo" arg1: "Valor mínimo"
arg2: "Valor máximo" arg2: "Valor máximo"
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)" dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia
para cada usuário)"
_dailyRandomPick: _dailyRandomPick:
arg1: "Listas" arg1: "Listas"
seedRandom: "Aleatório (com semente)" seedRandom: "Aleatório (com semente)"
@ -634,7 +668,8 @@ _pages:
_seedRandomPick: _seedRandomPick:
arg1: "Semente" arg1: "Semente"
arg2: "Listas" arg2: "Listas"
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)" DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia
para cada usuário)"
_DRPWPM: _DRPWPM:
arg1: "Lista de texto" arg1: "Lista de texto"
pick: "Escolhe a partir da lista" pick: "Escolhe a partir da lista"
@ -665,7 +700,8 @@ _pages:
_for: _for:
arg1: "Número de repetições" arg1: "Número de repetições"
arg2: "Ação" arg2: "Ação"
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!" typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado
é do tipo \"{actual}\"!"
thereIsEmptySlot: "O espaço {slot} está vazio!" thereIsEmptySlot: "O espaço {slot} está vazio!"
types: types:
string: "Texto" string: "Texto"
@ -730,3 +766,5 @@ _deck:
list: "Listas" list: "Listas"
mentions: "Menções" mentions: "Menções"
direct: "Notas diretas" direct: "Notas diretas"
editNote: Editar post
edited: Editado a {date} às {time}

View file

@ -2044,7 +2044,7 @@ cannotUploadBecauseExceedsFileSizeLimit: Этот файл не может бы
apps: Приложения apps: Приложения
silenceThisInstance: Заглушить сервер silenceThisInstance: Заглушить сервер
silencedInstances: Заглушенные серверы silencedInstances: Заглушенные серверы
editNote: Редактировать заметку editNote: Редактировать пост
edited: 'Редактировано в {date} {time}' edited: 'Редактировано в {date} {time}'
deleted: Удалённое deleted: Удалённое
removeReaction: Удалить вашу реакцию removeReaction: Удалить вашу реакцию

View file

@ -1,22 +1,22 @@
---
_lang_: "ภาษาไทย" _lang_: "ภาษาไทย"
headlineFirefish: "เชื่อมต่อเครือข่ายโดยโน้ต" headlineFirefish: "เชื่อมต่อเครือข่ายโดยโน้ต"
introFirefish: "ยินดีต้อนรับจ้าาา! Firefish เป็นบริการไมโครบล็อกโอเพ่นซอร์ส แบบการกระจายอำนาจ\nสร้าง \"โน้ต\" เพื่อแบ่งปันความคิดของคุณกับทุกคนรอบตัวคุณกันเถอะ 📡\nด้วยการ \"รีแอคชั่นผู้คน\" คุณยังสามารถแสดงความรู้สึกของคุณเกี่ยวกับบันทึกของทุกคนได้อย่างรวดเร็ว 👍\n\nแล้วมาท่องสำรวจโลกใบใหม่กันเถอะ! 🚀" introFirefish: "ยินดีต้อนรับค่ะ/ครับ! Firefish เป็นแพลตฟอร์มโซเชียลมีเดียแบบโอเพ่นซอร์สที่มีการกระจายอำนาจซึ่งให้บริการฟรีตลอดไป!
🚀"
monthAndDay: "{เดือน}/{วัน}" monthAndDay: "{เดือน}/{วัน}"
search: "ค้นหา" search: "ค้นหา"
notifications: "การเเจ้งเตือน" notifications: "การเเจ้งเตือน"
username: "ชื่อผู้ใช้" username: "ชื่อผู้ใช้"
password: "รหัสผ่าน" password: "รหัสผ่าน"
forgotPassword: "ลืมรหัสผ่าน?" forgotPassword: "ลืมรหัสผ่านอ่ะ"
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส" fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส"
ok: "ตกลง" ok: "ตกลง"
gotIt: "เข้าใจแล้ว !" gotIt: "เข้าใจแล้ว !"
cancel: "ยกเลิก" cancel: "ยกเลิก"
enterUsername: "ใส่ชื่อผู้ใช้" enterUsername: "ใส่ชื่อผู้ใช้"
renotedBy: "รีโน้ตโดย {ผู้ใช้}" renotedBy: "บูตเตอร์โดย {user}"
noNotes: "ไม่มีโน้ต" noNotes: "ไม่มีโพสต์"
noNotifications: "ไม่มีการแจ้งเตือน" noNotifications: "ไม่มีการแจ้งเตือน"
instance: "ตัวอย่าง" instance: "เซิฟเวอร์"
settings: "การตั้งค่า" settings: "การตั้งค่า"
basicSettings: "การตั้งค่าพื้นฐาน" basicSettings: "การตั้งค่าพื้นฐาน"
otherSettings: "การตั้งค่าอื่นๆ" otherSettings: "การตั้งค่าอื่นๆ"
@ -44,7 +44,8 @@ copyContent: "คัดลอกเนื้อหา"
copyLink: "คัดลอกลิงก์" copyLink: "คัดลอกลิงก์"
delete: "ลบ" delete: "ลบ"
deleteAndEdit: "ลบและแก้ไข" deleteAndEdit: "ลบและแก้ไข"
deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ" deleteAndEditConfirm: "คุณแน่ใจแล้วเหรอว่าต้องการลบโพสต์นี้และแก้ไข? คุณอาจจะสูญเสียการโต้ตอบ,
โพสต์, และการตอบกลับทั้งหมดได้นะ"
addToList: "เพิ่มในลิสต์" addToList: "เพิ่มในลิสต์"
sendMessage: "ส่งข้อความ" sendMessage: "ส่งข้อความ"
copyUsername: "คัดลอกชื่อผู้ใช้" copyUsername: "คัดลอกชื่อผู้ใช้"
@ -58,30 +59,31 @@ receiveFollowRequest: "คำขอผู้ติดตามที่ได้
followRequestAccepted: "ผู้ติดตามได้ตอบรับคำขอร้องของคุณแล้ว" followRequestAccepted: "ผู้ติดตามได้ตอบรับคำขอร้องของคุณแล้ว"
mention: "กล่าวถึง" mention: "กล่าวถึง"
mentions: "พูดถึง" mentions: "พูดถึง"
directNotes: "ไดเร็คโน้ต" directNotes: "ไดเร็คข้อความ"
importAndExport: "นำเข้า / ส่งออก" importAndExport: "นำเข้า / ส่งออก"
import: "การนำเข้า" import: "การนำเข้า"
export: "การนำออก" export: "การนำออก"
files: "ไฟล์" files: "ไฟล์"
download: "ดาวน์โหลด" download: "ดาวน์โหลด"
driveFileDeleteConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการลบไฟล์ \"{name}\" โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ" driveFileDeleteConfirm: "คุณแน่ใจแล้วหรอว่าต้องการลบไฟล์ \"{name}\"? โพสต์ย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ"
unfollowConfirm: "นายแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?" unfollowConfirm: "คุณแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?"
exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว" exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว"
importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ" importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ"
lists: "รายการ" lists: "รายการ"
noLists: "คุณไม่มีลิสต์ใดๆนะ" noLists: "คุณไม่มีลิสต์ใดๆนะ"
note: "ตัวโน้ต" note: "โพสต์"
notes: "หมายเหตุ" notes: "โพสต์"
following: "กำลังติดตาม" following: "กำลังติดตาม"
followers: "ผู้ติดตาม" followers: "ผู้ติดตาม"
followsYou: "ติดตามคุณ" followsYou: "ติดตามคุณ"
createList: "สร้างลิสต์" createList: "สร้างลิสต์"
manageLists: "จัดการลิสต์" manageLists: "จัดการลิสต์"
error: "ผิดพลาด!" error: "ผิดพลาด"
somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผิดพลาด" somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผิดพลาด"
retry: "ลองใหม่อีกครั้ง" retry: "ลองใหม่อีกครั้ง"
pageLoadError: "เกิดข้อผิดพลาดในการโหลดหน้านี้" pageLoadError: "เกิดข้อผิดพลาดในการโหลดหน้านี้"
pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์ ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่ " pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์
ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่นะ"
serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง ได้โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้งนะ" serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง ได้โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้งนะ"
youShouldUpgradeClient: "หากต้องการดูหน้านี้ได้โปรดกรุณา รีเซ็ตเพื่ออัปเดตไคลเอ็นต์ของคุณนะ" youShouldUpgradeClient: "หากต้องการดูหน้านี้ได้โปรดกรุณา รีเซ็ตเพื่ออัปเดตไคลเอ็นต์ของคุณนะ"
enterListName: "ใส่ชื่อสำหรับรายการลิสต์" enterListName: "ใส่ชื่อสำหรับรายการลิสต์"
@ -89,18 +91,18 @@ privacy: "ความเป็นส่วนตัว"
makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ" makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ"
defaultNoteVisibility: "การมองเห็นที่เป็นค่าเริ่มต้น" defaultNoteVisibility: "การมองเห็นที่เป็นค่าเริ่มต้น"
follow: "กำลังติดตาม" follow: "กำลังติดตาม"
followRequest: "ส่งคำขอติดตาม" followRequest: "คำขอติดตาม"
followRequests: "ติดตามการร้องขอ" followRequests: "ติดตามการร้องขอ"
unfollow: "เลิกติดตาม" unfollow: "เลิกติดตาม"
followRequestPending: "กำลังรอดำเนินการร้องขอติดตาม" followRequestPending: "กำลังรอดำเนินการร้องขอติดตาม"
enterEmoji: "ใส่อีโมจิ" enterEmoji: "ใส่อีโมจิ"
renote: "รีโน้ต" renote: "บูสต์"
unrenote: "เลิกรีโน้ต" unrenote: "เลิกบูสต์"
renoted: "รีโน้ตเอาไว้" renoted: "บูสต์แล้ว"
cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ" cantRenote: "โพสต์นี้ไม่สามารถบูสต์ใหม่ได้"
cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ" cantReRenote: "ไม่สามารถบูสต์ไว้ใหม่ได้"
quote: "อ้างคำพูด" quote: "อ้างคำพูด"
pinnedNote: "โน้ตที่ปักหมุดเอาไว้" pinnedNote: "โพสต์ที่ปักหมุดแล้ว"
pinned: "ปักหมุดไปยังโปรไฟล์" pinned: "ปักหมุดไปยังโปรไฟล์"
you: "ตัวเอง" you: "ตัวเอง"
clickToShow: "คลิกเพื่อแสดง" clickToShow: "คลิกเพื่อแสดง"
@ -109,7 +111,7 @@ add: "เพิ่ม"
reaction: "รีแอคชั่น" reaction: "รีแอคชั่น"
reactionSetting: "รีแอคชั่นไปยังแสดงผลในตัวเลือกการรีแอคชั่น" reactionSetting: "รีแอคชั่นไปยังแสดงผลในตัวเลือกการรีแอคชั่น"
reactionSettingDescription2: "กดลากเพื่อจัดลำดับใหม่ กดคลิกเพื่อลบ กด \"+\" เพื่อเพิ่ม" reactionSettingDescription2: "กดลากเพื่อจัดลำดับใหม่ กดคลิกเพื่อลบ กด \"+\" เพื่อเพิ่ม"
rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นตัวโน้ต" rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นโพสต์"
attachCancel: "ลบไฟล์ออกที่แนบมา" attachCancel: "ลบไฟล์ออกที่แนบมา"
markAsSensitive: "ทำเครื่องหมายว่าละเอียดอ่อน" markAsSensitive: "ทำเครื่องหมายว่าละเอียดอ่อน"
unmarkAsSensitive: "ยกเลิกทำเครื่องหมายเป็น NSFW" unmarkAsSensitive: "ยกเลิกทำเครื่องหมายเป็น NSFW"
@ -120,11 +122,11 @@ block: "บล็อค"
unblock: "เลิกปิดกั้น" unblock: "เลิกปิดกั้น"
suspend: "ถูกระงับ" suspend: "ถูกระงับ"
unsuspend: "ยกเลิกระงับ" unsuspend: "ยกเลิกระงับ"
blockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการบล็อกบัญชีนี้" blockConfirm: "คุณแน่ใจแล้วเหรอ ว่าต้องการบล็อกบัญชีนี้?"
unblockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการปลดบล็อคบัญชีนี้" unblockConfirm: "คุณแน่ใจแล้วเหรอ ว่าต้องการปลดบล็อคบัญชีนี้?"
suspendConfirm: "นายแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?" suspendConfirm: "คุณแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?"
unsuspendConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการยกเลิกการระงับบัญชีนี้" unsuspendConfirm: "คุณแน่ใจแล้วหรอว่าต้องการยกเลิกการระงับบัญชีนี้?"
selectList: "เลือกรายการ (Automatic Translation)" selectList: "เลือกรายการ"
selectAntenna: "เลือกเสาอากาศ" selectAntenna: "เลือกเสาอากาศ"
selectWidget: "เลือกวิดเจ็ต" selectWidget: "เลือกวิดเจ็ต"
editWidgets: "แก้ไขวิดเจ็ต" editWidgets: "แก้ไขวิดเจ็ต"
@ -137,13 +139,17 @@ emojiUrl: "อิโมจิ URL"
addEmoji: "แทรกอีโมจิ" addEmoji: "แทรกอีโมจิ"
settingGuide: "การตั้งค่าที่แนะนำ" settingGuide: "การตั้งค่าที่แนะนำ"
cacheRemoteFiles: "แคชไฟล์ระยะไกล" cacheRemoteFiles: "แคชไฟล์ระยะไกล"
cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ" cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากระยะไกลเซิฟเวอร์
แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน
เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ"
flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท" flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท"
flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ และยังสามารถปรับเปลี่ยนระบบภายในของ Firefish เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท" flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม
หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ
และยังสามารถปรับเปลี่ยนระบบภายในของ Firefish เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท"
flagAsCat: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว" flagAsCat: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว"
flagAsCatDescription: "การเปิดใช้งานตัวเลือกนี้เพื่อทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว" flagAsCatDescription: "คุณจะได้รับหูแมวและพูดเหมือนแมวนะ!"
flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์ไลน์" flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์ไลน์"
flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้" flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโพสต์ของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้"
autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม" autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม"
addAccount: "เพิ่มบัญชี" addAccount: "เพิ่มบัญชี"
loginFailed: "การเข้าสู่ระบบไม่สำเร็จ" loginFailed: "การเข้าสู่ระบบไม่สำเร็จ"
@ -155,15 +161,17 @@ removeWallpaper: "นำวอลเปเปอร์ออก"
searchWith: "ค้นหา: {q}" searchWith: "ค้นหา: {q}"
youHaveNoLists: "รายการนี้ว่างเปล่า" youHaveNoLists: "รายการนี้ว่างเปล่า"
followConfirm: "คุณแน่ใจแล้วหรอว่าต้องการที่จะติดตาม {name}?" followConfirm: "คุณแน่ใจแล้วหรอว่าต้องการที่จะติดตาม {name}?"
proxyAccount: "บัญชี พร็อกซี่" proxyAccount: "บัญชีพร็อกซี"
proxyAccountDescription: "บัญชีพร็อกซี่ คือ บัญชีที่จะทำหน้าที่เป็นผู้ติดตามระยะไกลสำหรับผู้ใช้งานที่อยู่ภายใต้ด้วยเงื่อนไขบางอย่าง ยกตัวอย่าง เช่น เมื่อมีผู้ใช้งานนั้นได้เพิ่มผู้ใช้งานจากระยะไกลลงในรายการ แต่กิจกรรมของผู้ใช้ในระยะไกลนั้นจะไม่ถูกส่งไปยังอินสแตนซ์หากไม่มีผู้ใช้งานในพื้นที่ติดตามผู้ใช้รายนั้น ดังนั้นบัญชีพร็อกซีนี้จะติดตามแทน" proxyAccountDescription: "บัญชีพร็อกซี่ คือ บัญชีที่จะทำหน้าที่เป็นผู้ติดตามระยะไกลสำหรับผู้ใช้งานที่อยู่ภายใต้ด้วยเงื่อนไขบางอย่าง
ยกตัวอย่าง เช่น เมื่อมีผู้ใช้งานนั้นได้เพิ่มผู้ใช้งานจากระยะไกลลงในรายการ แต่กิจกรรมของผู้ใช้ในระยะไกลนั้นจะไม่ถูกส่งไปยังเซิฟเวอร์
หากไม่มีผู้ใช้งานในพื้นที่ติดตามผู้ใช้รายนั้น ดังนั้นบัญชีพร็อกซีนี้จะติดตามแทน"
host: "โฮสต์" host: "โฮสต์"
selectUser: "เลือกผู้ใช้งาน" selectUser: "เลือกผู้ใช้งาน"
recipient: "ผู้รับ" recipient: "ผู้รับ"
annotation: "ความคิดเห็น" annotation: "ความคิดเห็น"
federation: "สหพันธ์" federation: "สหพันธ์"
instances: "ตัวอย่าง" instances: "เซิฟเวอร์"
registeredAt: "จดทะเบียนที่" registeredAt: "จดทะเบียนแล้วที่"
latestRequestSentAt: "ส่งคำขอล่าสุดไปแล้ว" latestRequestSentAt: "ส่งคำขอล่าสุดไปแล้ว"
latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว" latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว"
latestStatus: "สถานะล่าสุด" latestStatus: "สถานะล่าสุด"
@ -186,7 +194,8 @@ instanceInfo: "ข้อมูล อินสแตนซ์"
statistics: "สถิติการใช้งาน" statistics: "สถิติการใช้งาน"
clearQueue: "ล้างคิว" clearQueue: "ล้างคิว"
clearQueueConfirmTitle: "คุณแน่ใจแล้วหรอว่าต้องการที่จะล้างคิว?" clearQueueConfirmTitle: "คุณแน่ใจแล้วหรอว่าต้องการที่จะล้างคิว?"
clearQueueConfirmText: "บันทึกย่อที่ยังไม่ได้ส่งที่เหลืออยู่ในคิวนั้นมักจะ ไม่ถูกรวมเข้าด้วยกัน โดยปกติแล้วไม่จำเป็นต้องดำเนินการนี้" clearQueueConfirmText: "บันทึกย่อที่ยังไม่ได้ส่งที่เหลืออยู่ในคิวนั้นมักจะ ไม่ถูกรวมเข้าด้วยกัน
โดยปกติแล้วไม่จำเป็นต้องดำเนินการนี้"
clearCachedFiles: "ล้างแคช" clearCachedFiles: "ล้างแคช"
clearCachedFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ระยะไกลที่แคชไว้ทั้งหมด?" clearCachedFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ระยะไกลที่แคชไว้ทั้งหมด?"
blockedInstances: "อินสแตนซ์ที่ ถูกบล็อก" blockedInstances: "อินสแตนซ์ที่ ถูกบล็อก"
@ -232,14 +241,15 @@ announcements: "ประกาศ"
imageUrl: "url รูปภาพ" imageUrl: "url รูปภาพ"
remove: "ลบ" remove: "ลบ"
removed: "ถูกลบไปแล้ว" removed: "ถูกลบไปแล้ว"
removeAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"" removeAreYouSure: "คุณแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"?"
deleteAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"" deleteAreYouSure: "คุณแน่ใจหรอว่าต้องการที่จะลบออก \"{x}\"?"
resetAreYouSure: "รีเซ็ตเลยไหม" resetAreYouSure: "จริงหรอรีเซ็ตเลยไหม?"
saved: "บันทึกแล้ว" saved: "บันทึกแล้ว"
messaging: "แชท" messaging: "แชท"
upload: "อัพโหลด" upload: "อัพโหลด"
keepOriginalUploading: "เก็บภาพต้นฉบับ" keepOriginalUploading: "เก็บภาพต้นฉบับ"
keepOriginalUploadingDescription: "บันทึกรูปภาพที่อัพโหลดต้นฉบับตามที่เป็นอยู่ ถ้าหากปิดอยู่ ระบบจะสร้างเวอร์ชั่นที่จะแสดงบนเว็บเมื่ออัพโหลดนะ" keepOriginalUploadingDescription: "บันทึกรูปภาพที่อัพโหลดต้นฉบับตามที่เป็นอยู่ ถ้าหากปิดอยู่
ระบบจะสร้างเวอร์ชั่นที่จะแสดงบนเว็บเมื่ออัพโหลดนะ"
fromDrive: "จากไดรฟ์" fromDrive: "จากไดรฟ์"
fromUrl: "จาก URL" fromUrl: "จาก URL"
uploadFromUrl: "อัพโหลดจาก URL" uploadFromUrl: "อัพโหลดจาก URL"
@ -253,7 +263,7 @@ startMessaging: "เริ่มการสนทนา"
nUsersRead: "อ่านโดย {n}" nUsersRead: "อ่านโดย {n}"
agreeTo: "ฉันยอมรับที่จะ {0}" agreeTo: "ฉันยอมรับที่จะ {0}"
tos: "ข้อกำหนดและเงื่อนไข" tos: "ข้อกำหนดและเงื่อนไข"
start: "เริ่มต้น​ใช้งาน​" start: "เริ่มต้น"
home: "หน้าแรก" home: "หน้าแรก"
remoteUserCaution: "เนื่องจากผู้ใช้งานรายนี้นั้น มาจากอินสแตนซ์ระยะไกล ข้อมูลที่แสดงดังกล่าวนั้นอาจจะไม่สมบูรณ์ก็ได้นะ" remoteUserCaution: "เนื่องจากผู้ใช้งานรายนี้นั้น มาจากอินสแตนซ์ระยะไกล ข้อมูลที่แสดงดังกล่าวนั้นอาจจะไม่สมบูรณ์ก็ได้นะ"
activity: "กิจกรรม" activity: "กิจกรรม"
@ -334,9 +344,11 @@ bannerUrl: "URL รูปภาพแบนเนอร์"
backgroundImageUrl: "URL ภาพพื้นหลัง" backgroundImageUrl: "URL ภาพพื้นหลัง"
basicInfo: "ข้อมูลเบื้องต้น" basicInfo: "ข้อมูลเบื้องต้น"
pinnedUsers: "ผู้ใช้งานที่ได้รับการปักหมุด" pinnedUsers: "ผู้ใช้งานที่ได้รับการปักหมุด"
pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดยคั่นด้วยการขึ้นบรรทัดใหม่เพื่อปักหมุดในแท็บ \"สำรวจ\"" pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดยคั่นด้วยการขึ้นบรรทัดใหม่เพื่อปักหมุดในแท็บ
\"สำรวจ\""
pinnedPages: "หน้าที่ปักหมุด" pinnedPages: "หน้าที่ปักหมุด"
pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้ โดยคั่นด้วยตัวแบ่งบรรทัด" pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้
โดยคั่นด้วยตัวแบ่งบรรทัด"
pinnedClipId: "ID ของคลิปที่จะปักหมุด" pinnedClipId: "ID ของคลิปที่จะปักหมุด"
pinnedNotes: "โน้ตที่ปักหมุดเอาไว้" pinnedNotes: "โน้ตที่ปักหมุดเอาไว้"
hcaptcha: "hCaptcha" hcaptcha: "hCaptcha"
@ -347,14 +359,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "เปิดใช้ reCAPTCHA" enableRecaptcha: "เปิดใช้ reCAPTCHA"
recaptchaSiteKey: "คีย์ไซต์" recaptchaSiteKey: "คีย์ไซต์"
recaptchaSecretKey: "คีย์ลับ" recaptchaSecretKey: "คีย์ลับ"
avoidMultiCaptchaConfirm: "การใช้ระบบ Captcha หลายระบบอาจทำให้เกิดการรบกวนหรืออาจจะเกิดข้อผิดพลาดได้ หากต้องการที่จะปิดการใช้งานระบบ Captcha อื่น ๆ แนะนำให้ปิดตัวอื่นๆก่อน ถ้าหากคุณต้องการให้เปิดใช้งานต่อไป ให้ กด ยกเลิก" avoidMultiCaptchaConfirm: "การใช้ระบบ Captcha หลายระบบอาจทำให้เกิดการรบกวนหรืออาจจะเกิดข้อผิดพลาดได้
หากต้องการที่จะปิดการใช้งานระบบ Captcha อื่น ๆ แนะนำให้ปิดตัวอื่นๆก่อน ถ้าหากคุณต้องการให้เปิดใช้งานต่อไป
ให้ กด ยกเลิก"
antennas: "เสาอากาศ" antennas: "เสาอากาศ"
manageAntennas: "จัดการเสาอากาศ" manageAntennas: "จัดการเสาอากาศ"
name: "ชื่อ" name: "ชื่อ"
antennaSource: "แหล่งเสาอากาศ" antennaSource: "แหล่งเสาอากาศ"
antennaKeywords: "คีย์เวิร์ดที่ควรฟัง" antennaKeywords: "คีย์เวิร์ดที่ควรฟัง"
antennaExcludeKeywords: "คีย์เวิร์ดที่จะยกเว้น" antennaExcludeKeywords: "คีย์เวิร์ดที่จะยกเว้น"
antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ" antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข
OR นะ"
notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่" notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่"
withFileAntenna: "เฉพาะโน้ตที่มีไฟล์" withFileAntenna: "เฉพาะโน้ตที่มีไฟล์"
enableServiceworker: "เปิดใช้งาน การแจ้งเตือนแบบพุชสำหรับเบราว์เซอร์ของคุณ" enableServiceworker: "เปิดใช้งาน การแจ้งเตือนแบบพุชสำหรับเบราว์เซอร์ของคุณ"
@ -433,7 +448,8 @@ invitationCode: "รหัสคำเชิญ"
checking: "Checking" checking: "Checking"
available: "พร้อมใช้งาน" available: "พร้อมใช้งาน"
unavailable: "ไม่พร้อมใช้" unavailable: "ไม่พร้อมใช้"
usernameInvalidFormat: "คุณสามารถใช้อักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก ตัวเลข และขีดล่างได้นะ ( a-z , A-Z , 0-9 , รวมไปถึงอักษรพิเศษเช่น + * / , . - อื่นๆเป็นต้น )" usernameInvalidFormat: "คุณสามารถใช้อักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก ตัวเลข และขีดล่างได้นะ
( a-z , A-Z , 0-9 , รวมไปถึงอักษรพิเศษเช่น + * / , . - อื่นๆเป็นต้น )"
tooShort: "สั้นเกินไปนะ" tooShort: "สั้นเกินไปนะ"
tooLong: "ยาวเกินไปนะ" tooLong: "ยาวเกินไปนะ"
weakPassword: "รหัสผ่าน แย่มาก" weakPassword: "รหัสผ่าน แย่มาก"
@ -483,19 +499,26 @@ showFeaturedNotesInTimeline: "แสดงโน้ตเด่นในไท
objectStorage: "อ็อบเจ็กต์ ที่จัดเก็บ" objectStorage: "อ็อบเจ็กต์ ที่จัดเก็บ"
useObjectStorage: "ใช้ อ็อบเจ็กต์ ที่จัดเก็บ" useObjectStorage: "ใช้ อ็อบเจ็กต์ ที่จัดเก็บ"
objectStorageBaseUrl: "URL ฐาน" objectStorageBaseUrl: "URL ฐาน"
objectStorageBaseUrlDesc: "URL ที่ใช้เป็นข้อมูลอ้างอิง ระบุ URL ของ CDN หรือ Proxy ถ้าหากคุณใช้อย่างใดอย่างหนึ่ง\n สำหรับการใช้งาน S3 'https://<bucket>.s3.amazonaws.com' และสำหรับ GCS หรือบริการที่เทียบเท่าใช้ 'https://storage.googleapis.com/<bucket>', เป็นต้น" objectStorageBaseUrlDesc: "URL ที่ใช้เป็นข้อมูลอ้างอิง ระบุ URL ของ CDN หรือ Proxy
ถ้าหากคุณใช้อย่างใดอย่างหนึ่ง\n สำหรับการใช้งาน S3 'https://<bucket>.s3.amazonaws.com'
และสำหรับ GCS หรือบริการที่เทียบเท่าใช้ 'https://storage.googleapis.com/<bucket>',
เป็นต้น"
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "โปรดระบุชื่อที่เก็บข้อมูลที่ใช้กับผู้ให้บริการของคุณ" objectStorageBucketDesc: "โปรดระบุชื่อที่เก็บข้อมูลที่ใช้กับผู้ให้บริการของคุณ"
objectStoragePrefix: "คำนำหน้า" objectStoragePrefix: "คำนำหน้า"
objectStoragePrefixDesc: "ไฟล์ทั้งหมดจะถูกเก็บไว้ภายใต้ไดเร็กทอรีที่มีคำนำหน้านี้นะ" objectStoragePrefixDesc: "ไฟล์ทั้งหมดจะถูกเก็บไว้ภายใต้ไดเร็กทอรีที่มีคำนำหน้านี้นะ"
objectStorageEndpoint: "ปลายทาง" objectStorageEndpoint: "ปลายทาง"
objectStorageEndpointDesc: "เว้นว่างไว้หากคุณใช้ AWS S3 หรือระบุปลายทางเป็น '<host>' หรือ '<host>:<port>' ทั้งนี้ขึ้นอยู่กับผู้ให้บริการที่คุณใช้อยู่ด้วย" objectStorageEndpointDesc: "เว้นว่างไว้หากคุณใช้ AWS S3 หรือระบุปลายทางเป็น '<host>'
หรือ '<host>:<port>' ทั้งนี้ขึ้นอยู่กับผู้ให้บริการที่คุณใช้อยู่ด้วย"
objectStorageRegion: "ภูมิภาค" objectStorageRegion: "ภูมิภาค"
objectStorageRegionDesc: "ระบุภูมิภาค เช่น 'xx-east-1' ถ้าหากบริการของคุณไม่ได้แยกความแตกต่างระหว่างภูมิภาคก็ให้ เว้นว่างไว้หรือป้อน 'us-east-1'" objectStorageRegionDesc: "ระบุภูมิภาค เช่น 'xx-east-1' ถ้าหากบริการของคุณไม่ได้แยกความแตกต่างระหว่างภูมิภาคก็ให้
เว้นว่างไว้หรือป้อน 'us-east-1'"
objectStorageUseSSL: "ใช้ SSL" objectStorageUseSSL: "ใช้ SSL"
objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้ ถ้าหากคุณจะไม่ใช้ HTTPS สำหรับการเชื่อมต่อ API" objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้ ถ้าหากคุณจะไม่ใช้ HTTPS สำหรับการเชื่อมต่อ
API"
objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี" objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี"
objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ API" objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ
API"
objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด" objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด"
serverLogs: "บันทึกของเซิร์ฟเวอร์" serverLogs: "บันทึกของเซิร์ฟเวอร์"
deleteAll: "ลบทั้งหมด" deleteAll: "ลบทั้งหมด"
@ -523,7 +546,8 @@ sort: "เรียงลำดับ"
ascendingOrder: "เรียงจากน้อยไปมาก" ascendingOrder: "เรียงจากน้อยไปมาก"
descendingOrder: "เรียงจากมากไปน้อย" descendingOrder: "เรียงจากมากไปน้อย"
scratchpad: "กระดานทดลอง" scratchpad: "กระดานทดลอง"
scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ Firefish มันได้ด้วยนะ" scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript
แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ Firefish มันได้ด้วยนะ"
output: "เอาท์พุต" output: "เอาท์พุต"
script: "สคริปต์" script: "สคริปต์"
disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ" disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ"
@ -531,11 +555,14 @@ updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งา
deleteAllFiles: "ลบไฟล์ทั้งหมด" deleteAllFiles: "ลบไฟล์ทั้งหมด"
deleteAllFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ทั้งหมด?" deleteAllFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ทั้งหมด?"
removeAllFollowing: "เลิกติดตามผู้ใช้ที่ติดตามทั้งหมด" removeAllFollowing: "เลิกติดตามผู้ใช้ที่ติดตามทั้งหมด"
removeAllFollowingDescription: "การที่คุณดำเนินการนี้จะเลิกติดตามบัญชีทั้งหมดจาก {host} โปรดเรียกใช้คำสั่งสิ่งนี้หากต้องการยกเลิกอินสแตนซ์ เช่น ไม่มีอยู่แล้ว" removeAllFollowingDescription: "การที่คุณดำเนินการนี้จะเลิกติดตามบัญชีทั้งหมดจาก {host}
โปรดเรียกใช้คำสั่งสิ่งนี้หากต้องการยกเลิกอินสแตนซ์ เช่น ไม่มีอยู่แล้ว"
userSuspended: "ผู้ใช้รายนี้ถูกระงับการใช้งาน" userSuspended: "ผู้ใช้รายนี้ถูกระงับการใช้งาน"
userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น" userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น"
yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ" yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่" yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน
หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด
ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
menu: "เมนู" menu: "เมนู"
divider: "ตัวแบ่ง" divider: "ตัวแบ่ง"
addItem: "เพิ่มรายการ" addItem: "เพิ่มรายการ"
@ -589,13 +616,15 @@ smtpHost: "โฮสต์"
smtpPort: "พอร์ต" smtpPort: "พอร์ต"
smtpUser: "ชื่อผู้ใช้" smtpUser: "ชื่อผู้ใช้"
smtpPass: "รหัสผ่าน" smtpPass: "รหัสผ่าน"
emptyToDisableSmtpAuth: "ปล่อยชื่อผู้ใช้และรหัสผ่านว่างไว้เพื่อปิดใช้งานการยืนยัน SMTP" emptyToDisableSmtpAuth: "ปล่อยชื่อผู้ใช้และรหัสผ่านว่างไว้เพื่อปิดใช้งานการยืนยัน
SMTP"
smtpSecure: "ใช้โดยนัย SSL/TLS สำหรับการเชื่อมต่อ SMTP" smtpSecure: "ใช้โดยนัย SSL/TLS สำหรับการเชื่อมต่อ SMTP"
smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS" smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS"
testEmail: "ทดสอบการส่งอีเมล" testEmail: "ทดสอบการส่งอีเมล"
wordMute: "ปิดเสียงคำ" wordMute: "ปิดเสียงคำ"
regexpError: "ข้อผิดพลาดของนิพจน์ทั่วไป" regexpError: "ข้อผิดพลาดของนิพจน์ทั่วไป"
regexpErrorDescription: "เกิดข้อผิดพลาดในนิพจน์ทั่วไปในบรรทัดที่ {line} ของการปิดเสียงคำ {tab} ของคุณ:" regexpErrorDescription: "เกิดข้อผิดพลาดในนิพจน์ทั่วไปในบรรทัดที่ {line} ของการปิดเสียงคำ
{tab} ของคุณ:"
instanceMute: "ปิดเสียง อินสแตนซ์" instanceMute: "ปิดเสียง อินสแตนซ์"
userSaysSomething: "{name} พูดอะไรบางอย่าง" userSaysSomething: "{name} พูดอะไรบางอย่าง"
makeActive: "เปิดใช้งาน" makeActive: "เปิดใช้งาน"
@ -611,10 +640,12 @@ create: "สร้าง"
notificationSetting: "ตั้งค่าการแจ้งเตือน" notificationSetting: "ตั้งค่าการแจ้งเตือน"
notificationSettingDesc: "เลือกประเภทการแจ้งเตือนที่ต้องการจะแสดง" notificationSettingDesc: "เลือกประเภทการแจ้งเตือนที่ต้องการจะแสดง"
useGlobalSetting: "ใช้การตั้งค่าส่วนกลาง" useGlobalSetting: "ใช้การตั้งค่าส่วนกลาง"
useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่ สามารถทำการกำหนดค่าแต่ละรายการได้นะ" useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่
สามารถทำการกำหนดค่าแต่ละรายการได้นะ"
other: "อื่น ๆ" other: "อื่น ๆ"
regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง" regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง"
regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ" regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ
โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ"
setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง" setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง"
fileIdOrUrl: "ไฟล์ ID หรือ URL" fileIdOrUrl: "ไฟล์ ID หรือ URL"
behavior: "พฤติกรรม" behavior: "พฤติกรรม"
@ -622,7 +653,8 @@ sample: "ตัวอย่าง"
abuseReports: "รายงาน" abuseReports: "รายงาน"
reportAbuse: "รายงาน" reportAbuse: "รายงาน"
reportAbuseOf: "รายงาน {ชื่อ}" reportAbuseOf: "รายงาน {ชื่อ}"
fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL" fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ
ได้โปรดระบุ URL"
abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ" abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ"
reporter: "นักข่าว" reporter: "นักข่าว"
reporteeOrigin: "รายงานต้นทาง" reporteeOrigin: "รายงานต้นทาง"
@ -648,7 +680,8 @@ createNewClip: "สร้างคลิปใหม่"
unclip: "ลบคลิป" unclip: "ลบคลิป"
confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?" confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?"
public: "สาธารณะ" public: "สาธารณะ"
i18nInfo: "Firefish กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}" i18nInfo: "Firefish กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่
{link}"
manageAccessTokens: "การจัดการโทเค็นการเข้าถึง" manageAccessTokens: "การจัดการโทเค็นการเข้าถึง"
accountInfo: "ข้อมูลบัญชี" accountInfo: "ข้อมูลบัญชี"
notesCount: "จำนวนของโน้ต" notesCount: "จำนวนของโน้ต"
@ -667,8 +700,10 @@ no: "ไม่"
driveFilesCount: "จำนวนไฟล์ไดรฟ์" driveFilesCount: "จำนวนไฟล์ไดรฟ์"
driveUsage: "การใช้พื้นที่ไดรฟ์" driveUsage: "การใช้พื้นที่ไดรฟ์"
noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล" noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล"
noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ บันทึกย่อ หน้า ฯลฯ" noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ บันทึกย่อ หน้า
lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\" โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม" ฯลฯ"
lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\"\
\ โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม"
alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น" alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ" loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว" disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
@ -684,7 +719,8 @@ clips: "คลิป"
experimentalFeatures: "ฟังก์ชั่นทดสอบ" experimentalFeatures: "ฟังก์ชั่นทดสอบ"
developer: "สำหรับนักพัฒนา" developer: "สำหรับนักพัฒนา"
makeExplorable: "ทำให้บัญชีมองเห็นใน \"สำรวจ\"" makeExplorable: "ทำให้บัญชีมองเห็นใน \"สำรวจ\""
makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน \"สำรวจ\" นะ" makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน
\"สำรวจ\" นะ"
showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์" showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์"
duplicate: "ทำซ้ำ" duplicate: "ทำซ้ำ"
left: "ซ้าย" left: "ซ้าย"
@ -699,7 +735,9 @@ onlineUsersCount: "{n} ผู้ใช้คนนี้กำลังออน
nUsers: "{n} ผู้ใช้งาน" nUsers: "{n} ผู้ใช้งาน"
nNotes: "{n} โน้ต" nNotes: "{n} โน้ต"
sendErrorReports: "ส่งรายงานว่าข้อผิดพลาด" sendErrorReports: "ส่งรายงานว่าข้อผิดพลาด"
sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ Firefish เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ Firefish\nซึ่งจะรวมถึงข้อมูล เช่น เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน Firefish เป็นต้น" sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ
Firefish เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ Firefish\nซึ่งจะรวมถึงข้อมูล เช่น
เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน Firefish เป็นต้น"
myTheme: "ธีมของฉัน" myTheme: "ธีมของฉัน"
backgroundColor: "ภาพพื้นหลัง" backgroundColor: "ภาพพื้นหลัง"
accentColor: "รูปแบบสี" accentColor: "รูปแบบสี"
@ -745,7 +783,8 @@ userInfo: "ข้อมูลผู้ใช้"
unknown: "ไม่ทราบสถานะ" unknown: "ไม่ทราบสถานะ"
onlineStatus: "สถานะออนไลน์" onlineStatus: "สถานะออนไลน์"
hideOnlineStatus: "ซ่อนสถานะออนไลน์" hideOnlineStatus: "ซ่อนสถานะออนไลน์"
hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง เช่น การค้นหา อ่ะนะ" hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง
เช่น การค้นหา อ่ะนะ"
online: "ออนไลน์" online: "ออนไลน์"
active: "ใช้งานอยู่" active: "ใช้งานอยู่"
offline: "ออฟไลน์" offline: "ออฟไลน์"
@ -795,7 +834,8 @@ whatIsNew: "แสดงการเปลี่ยนแปลง"
translate: "แปลภาษา" translate: "แปลภาษา"
translatedFrom: "แปลมาจาก {x}" translatedFrom: "แปลมาจาก {x}"
accountDeletionInProgress: "กำลังดำเนินการลบบัญชีอยู่" accountDeletionInProgress: "กำลังดำเนินการลบบัญชีอยู่"
usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร (a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง" usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร
(a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง"
aiChanMode: "โหมด Ai " aiChanMode: "โหมด Ai "
keepCw: "เก็บคำเตือนเนื้อหา" keepCw: "เก็บคำเตือนเนื้อหา"
pubSub: "บัญชีผับ/ย่อย" pubSub: "บัญชีผับ/ย่อย"
@ -864,7 +904,8 @@ typeToConfirm: "โปรดป้อน {x} เพื่อยืนยัน"
deleteAccount: "ลบบัญชี" deleteAccount: "ลบบัญชี"
document: "เอกสาร" document: "เอกสาร"
numberOfPageCache: "จำนวนหน้าเพจที่แคช" numberOfPageCache: "จำนวนหน้าเพจที่แคช"
numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย" numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน
แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย"
logoutConfirm: "คุณแน่ใจว่าต้องการออกจากระบบ?" logoutConfirm: "คุณแน่ใจว่าต้องการออกจากระบบ?"
lastActiveDate: "ใช้งานล่าสุดที่" lastActiveDate: "ใช้งานล่าสุดที่"
statusbar: "ไอคอนบนแถบสถานะ" statusbar: "ไอคอนบนแถบสถานะ"
@ -881,22 +922,29 @@ sensitiveMediaDetection: "การตรวจจับของสื่อ NS
localOnly: "เฉพาะท้องถิ่น" localOnly: "เฉพาะท้องถิ่น"
remoteOnly: "รีโมทเท่านั้น" remoteOnly: "รีโมทเท่านั้น"
failedToUpload: "การอัปโหลดล้มเหลว" failedToUpload: "การอัปโหลดล้มเหลว"
cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW" cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น
NSFW"
cannotUploadBecauseNoFreeSpace: "การอัปโหลดนั้นล้มเหลวเนื่องจากไม่มีความจุของไดรฟ์" cannotUploadBecauseNoFreeSpace: "การอัปโหลดนั้นล้มเหลวเนื่องจากไม่มีความจุของไดรฟ์"
beta: "เบต้า" beta: "เบต้า"
enableAutoSensitive: "ทำเครื่องหมาย NSFW อัตโนมัติ" enableAutoSensitive: "ทำเครื่องหมาย NSFW อัตโนมัติ"
enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้ แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้" enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้
activeEmailValidationDescription: "เปิดใช้งานการตรวจสอบที่อยู่อีเมลให้มีความเข้มงวดยิ่งขึ้น ซึ่งอาจจะรวมไปถึงการตรวจสอบที่อยู่อีเมล์ที่ใช้แล้วทิ้งและโดยให้พิจารณาว่าสามารถสื่อสารด้วยได้หรือไม่ เมื่อไม่เลือกระบบจะตรวจสอบเฉพาะรูปแบบของอีเมลเท่านั้น" แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้"
activeEmailValidationDescription: "เปิดใช้งานการตรวจสอบที่อยู่อีเมลให้มีความเข้มงวดยิ่งขึ้น
ซึ่งอาจจะรวมไปถึงการตรวจสอบที่อยู่อีเมล์ที่ใช้แล้วทิ้งและโดยให้พิจารณาว่าสามารถสื่อสารด้วยได้หรือไม่
เมื่อไม่เลือกระบบจะตรวจสอบเฉพาะรูปแบบของอีเมลเท่านั้น"
navbar: "แถบนำทาง" navbar: "แถบนำทาง"
shuffle: "สลับ" shuffle: "สลับ"
account: "บัญชีผู้ใช้" account: "บัญชีผู้ใช้"
move: "ย้าย" move: "ย้าย"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง
การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
sensitivity: "การตรวจจับความไว" sensitivity: "การตรวจจับความไว"
sensitivityDescription: "การลดความไวนั้นจะนำไปสู่การตรวจจับที่ผิดพลาดน้อยลง (ผลบวกที่ผิดพลาด) แต่ในขณะที่การเพิ่มนั้นจะนำไปสู่การตรวจหาที่พลาดน้อยลง (ผลลบเท็จ)" sensitivityDescription: "การลดความไวนั้นจะนำไปสู่การตรวจจับที่ผิดพลาดน้อยลง (ผลบวกที่ผิดพลาด)
แต่ในขณะที่การเพิ่มนั้นจะนำไปสู่การตรวจหาที่พลาดน้อยลง (ผลลบเท็จ)"
setSensitiveFlagAutomatically: "ทำเครื่องหมายว่าเป็น NSFW" setSensitiveFlagAutomatically: "ทำเครื่องหมายว่าเป็น NSFW"
setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่ ถึงแม้ว่าจะปิดตัวเลือกนี้" setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่
ถึงแม้ว่าจะปิดตัวเลือกนี้"
analyzeVideos: "เปิดใช้งานวิเคราะห์ของวิดีโอ" analyzeVideos: "เปิดใช้งานวิเคราะห์ของวิดีโอ"
analyzeVideosDescription: "การวิเคราะห์วิดีโอนอกเหนือจากรูปภาพนั้น การทำสิ่งนี้จะทำให้เพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" analyzeVideosDescription: "การวิเคราะห์วิดีโอนอกเหนือจากรูปภาพนั้น การทำสิ่งนี้จะทำให้เพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
_emailUnavailable: _emailUnavailable:
@ -915,7 +963,8 @@ _signup:
emailSent: "เราได้ส่งอีเมลยืนยันไปยังที่อยู่อีเมลของคุณแล้วนะ ({email}) โปรดคลิกลิงก์ที่รวมไว้เพื่อสร้างบัญชีให้เสร็จสิ้น" emailSent: "เราได้ส่งอีเมลยืนยันไปยังที่อยู่อีเมลของคุณแล้วนะ ({email}) โปรดคลิกลิงก์ที่รวมไว้เพื่อสร้างบัญชีให้เสร็จสิ้น"
_accountDelete: _accountDelete:
accountDelete: "ลบบัญชีผู้ใช้" accountDelete: "ลบบัญชีผู้ใช้"
mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์ ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ" mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์
ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ"
sendEmail: "เมื่อการลบบัญชีนี้เสร็จสิ้น เราอาจจะส่งอีเมลไปยังที่อยู่อีเมลของคุณที่เคยลงทะเบียนไว้กับบัญชีนี้นะ" sendEmail: "เมื่อการลบบัญชีนี้เสร็จสิ้น เราอาจจะส่งอีเมลไปยังที่อยู่อีเมลของคุณที่เคยลงทะเบียนไว้กับบัญชีนี้นะ"
requestAccountDelete: "ร้องขอให้ลบบัญชี" requestAccountDelete: "ร้องขอให้ลบบัญชี"
started: "การลบได้เริ่มต้นขึ้น" started: "การลบได้เริ่มต้นขึ้น"
@ -950,11 +999,13 @@ _preferencesBackups:
inputName: "กรุณาป้อนชื่อสำหรับข้อมูลสำรองนี้" inputName: "กรุณาป้อนชื่อสำหรับข้อมูลสำรองนี้"
cannotSave: "การบันทึกล้มเหลว" cannotSave: "การบันทึกล้มเหลว"
nameAlreadyExists: "มีข้อมูลสำรองชื่อ \"{name}\" นี้อยู่แล้ว กรุณาป้อนชื่ออื่นนะ" nameAlreadyExists: "มีข้อมูลสำรองชื่อ \"{name}\" นี้อยู่แล้ว กรุณาป้อนชื่ออื่นนะ"
applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ" applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ
การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ"
saveConfirm: "บันทึกข้อมูลสำรองเป็น {name} มั้ย?" saveConfirm: "บันทึกข้อมูลสำรองเป็น {name} มั้ย?"
deleteConfirm: "ลบข้อมูลสำรอง {name} มั้ย?" deleteConfirm: "ลบข้อมูลสำรอง {name} มั้ย?"
renameConfirm: "เปลี่ยนชื่อข้อมูลสำรองนี้จาก \"{old}\" เป็น \"{new}\" หรือป่าว" renameConfirm: "เปลี่ยนชื่อข้อมูลสำรองนี้จาก \"{old}\" เป็น \"{new}\" หรือป่าว"
noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้ \"สร้างการสำรองข้อมูลใหม่\"ได้นะ" noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้
\"สร้างการสำรองข้อมูลใหม่\"ได้นะ"
createdAt: "สร้างเมื่อ: {date} {time}" createdAt: "สร้างเมื่อ: {date} {time}"
updatedAt: "อัปเดตเมื่อ: {date} {time}" updatedAt: "อัปเดตเมื่อ: {date} {time}"
cannotLoad: "การโหลดล้มเหลว" cannotLoad: "การโหลดล้มเหลว"
@ -972,7 +1023,8 @@ _aboutFirefish:
source: "ซอร์สโค้ด" source: "ซอร์สโค้ด"
translation: "รับแปลภาษา Firefish" translation: "รับแปลภาษา Firefish"
donate: "บริจาคให้กับ Firefish" donate: "บริจาคให้กับ Firefish"
morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ ขอขอบคุณ! 🥰" morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ
ขอขอบคุณ! 🥰"
patrons: "สมาชิกพันธมิตร" patrons: "สมาชิกพันธมิตร"
_nsfw: _nsfw:
respect: "ซ่อนสื่อ NSFW" respect: "ซ่อนสื่อ NSFW"
@ -980,7 +1032,8 @@ _nsfw:
force: "ซ่อนสื่อทั้งหมด" force: "ซ่อนสื่อทั้งหมด"
_mfm: _mfm:
cheatSheet: "โค้ด MFM Cheat Sheet" cheatSheet: "โค้ด MFM Cheat Sheet"
intro: "MFM เป็นภาษามาร์กอัปพิเศษเฉพาะของ Firefish ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์ MFM ที่มีอยู่ทั้งหมดได้ที่นี่นะ" intro: "MFM เป็นภาษามาร์กอัปพิเศษเฉพาะของ Firefish ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์
MFM ที่มีอยู่ทั้งหมดได้ที่นี่นะ"
dummy: "Firefish ขยายโลกของ Fediverse" dummy: "Firefish ขยายโลกของ Fediverse"
mention: "กล่าวถึง" mention: "กล่าวถึง"
mentionDescription: "คุณสามารถระบุผู้ใช้โดยใช้ At-Symbol และชื่อผู้ใช้ได้นะ" mentionDescription: "คุณสามารถระบุผู้ใช้โดยใช้ At-Symbol และชื่อผู้ใช้ได้นะ"
@ -1069,15 +1122,18 @@ _menuDisplay:
hide: "ซ่อน" hide: "ซ่อน"
_wordMute: _wordMute:
muteWords: "ปิดเสียงคำ" muteWords: "ปิดเสียงคำ"
muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ" muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข
OR นะ"
muteWordsDescription2: "ล้อมรอบคีย์เวิร์ดด้วยเครื่องหมายทับเพื่อใช้นิพจน์ทั่วไป" muteWordsDescription2: "ล้อมรอบคีย์เวิร์ดด้วยเครื่องหมายทับเพื่อใช้นิพจน์ทั่วไป"
softDescription: "ซ่อนโน้ตให้ตรงตามเงื่อนไขที่ตั้งไว้จากไทม์ไลน์" softDescription: "ซ่อนโน้ตให้ตรงตามเงื่อนไขที่ตั้งไว้จากไทม์ไลน์"
hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์ นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม" hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์
นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม"
soft: "ซอฟ" soft: "ซอฟ"
hard: "ยาก" hard: "ยาก"
mutedNotes: "ปิดเสียงโน้ต" mutedNotes: "ปิดเสียงโน้ต"
_instanceMute: _instanceMute:
instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง" instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ
รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่" instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่"
title: "ซ่อนโน้ตจากอินสแตนซ์ที่มีอยู่ในรายการ" title: "ซ่อนโน้ตจากอินสแตนซ์ที่มีอยู่ในรายการ"
heading: "รายชื่ออินสแตนซ์ที่ถูกปิดเสียง" heading: "รายชื่ออินสแตนซ์ที่ถูกปิดเสียง"
@ -1191,3 +1247,23 @@ _deck:
antenna: "เสาอากาศ" antenna: "เสาอากาศ"
list: "รายการ" list: "รายการ"
mentions: "พูดถึง" mentions: "พูดถึง"
noThankYou: ไม่ล่ะขอบคุณ
removeReaction: ลบรีเเอดชั่นของคุณ
renoteMute: ปิดเสียงบูส
renoteUnmute: เลิกปิดเสียงบูส
manageGroups: จัดการกลุ่ม
addInstance: เพิ่มเซิฟเวอร์
searchPlaceholder: ค้นหา Firefish
deleted: ลบแล้ว
editNote: แก้ไขโพสต์
edited: แก้ไขแล้วเมื่อ {date} {time}
jumpToPrevious: ข้ามไปที่ก่อนหน้านี้
listsDesc: ลิสต์รายการนั้นช่วยให้คุณได้สร้างไทม์ไลน์กับผู้ใช้ที่ระบุได้นะ ยังสามารถเข้าถึงได้จากหน้าไทม์ไลน์ได้อีกด้วย
enableEmojiReactions: เปิดใช้งานรีแอดชั่นอีโมจิ
selectChannel: เลือกช่อง
older: เก่ากว่านี้
newer: ใหม่กว่านี้
selectInstance: เลือกเซิฟเวอร์
showEmojisInReactionNotifications: แสดงอิโมจิในการแจ้งเตือนรีแอคชั่น
flagSpeakAsCat: พูดเหมือนแมว
cw: คำเตือนเนื้อหา

View file

@ -1,16 +1,16 @@
{ {
"name": "firefish", "name": "firefish",
"version": "1.0.5-dev6", "version": "1.0.5-dev9",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.joinfirefish.org/firefish/firefish.git" "url": "https://git.joinfirefish.org/firefish/firefish.git"
}, },
"packageManager": "pnpm@8.6.11", "packageManager": "pnpm@8.7.1",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp", "rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
"build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp", "build": "./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start", "start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test", "start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate", "init": "pnpm run migrate",
@ -38,18 +38,19 @@
"chokidar": "^3.3.1" "chokidar": "^3.3.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.7.2", "@bull-board/api": "5.8.0",
"@bull-board/ui": "5.7.2", "@bull-board/ui": "5.8.0",
"@napi-rs/cli": "^2.16.2", "@napi-rs/cli": "^2.16.2",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^4.10.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.0.0",
"@types/gulp": "4.0.13", "@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2", "@types/gulp-rename": "2.0.2",
"@types/node": "20.4.9", "@types/node": "20.5.8",
"chalk": "4.1.2", "add": "2.0.6",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
"execa": "5.1.1", "execa": "5.1.1",
@ -59,8 +60,8 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"rome": "^12.1.3", "pnpm": "8.7.1",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "5.1.6" "typescript": "5.2.2"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -1305,6 +1305,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
name = "migration" name = "migration"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"basen",
"futures", "futures",
"indicatif", "indicatif",
"native-utils", "native-utils",

View file

@ -10,11 +10,11 @@ path = "src/lib.rs"
[features] [features]
default = [] default = []
convert = ["dep:indicatif", "dep:futures"] convert = ["dep:native-utils", "dep:indicatif", "dep:futures"]
[dependencies] [dependencies]
serde_json = "1.0.96" serde_json = "1.0.96"
native-utils = { path = "../" } native-utils = { path = "../", optional = true }
indicatif = { version = "0.17.4", features = ["tokio"], optional = true } indicatif = { version = "0.17.4", features = ["tokio"], optional = true }
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
futures = { version = "0.3.28", optional = true } futures = { version = "0.3.28", optional = true }
@ -24,6 +24,7 @@ urlencoding = "2.1.2"
redis = { version = "0.23.0", features = ["tokio-rustls-comp"] } redis = { version = "0.23.0", features = ["tokio-rustls-comp"] }
sea-orm = "0.11.3" sea-orm = "0.11.3"
url = { version = "2.4.0", features = ["serde"] } url = { version = "2.4.0", features = ["serde"] }
basen = "0.1.0"
[dependencies.sea-orm-migration] [dependencies.sea-orm-migration]
version = "0.11.0" version = "0.11.0"

View file

@ -1,5 +1,7 @@
pub use sea_orm_migration::prelude::*; pub use sea_orm_migration::prelude::*;
use basen::BASE36;
mod m20230531_180824_drop_reversi; mod m20230531_180824_drop_reversi;
mod m20230627_185451_index_note_url; mod m20230627_185451_index_note_url;
mod m20230709_000510_move_antenna_to_cache; mod m20230709_000510_move_antenna_to_cache;
@ -18,3 +20,12 @@ impl MigratorTrait for Migrator {
] ]
} }
} }
pub fn get_timestamp(id: &str) -> i64 {
const TIME_2000: i64 = 946_684_800_000;
let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
match n {
None => -1,
Some(n) => n as i64 + TIME_2000,
}
}

View file

@ -1,9 +1,10 @@
use std::env; use std::env;
use native_utils::util::id;
use redis::Commands; use redis::Commands;
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
use crate::get_timestamp;
#[derive(DeriveMigrationName)] #[derive(DeriveMigrationName)]
pub struct Migration; pub struct Migration;
@ -31,7 +32,7 @@ impl MigrationTrait for Migration {
let all_elems: Vec<Vec<Vec<String>>> = redis_conn.xrange_all(key).unwrap(); // Get all post IDs in stream let all_elems: Vec<Vec<Vec<String>>> = redis_conn.xrange_all(key).unwrap(); // Get all post IDs in stream
let stream_ids = all_elems let stream_ids = all_elems
.iter() .iter()
.map(|v| format!("{}-*", id::get_timestamp(&v[1][1]))); // Get correct stream id with timestamp .map(|v| format!("{}-*", get_timestamp(&v[1][1]))); // Get correct stream id with timestamp
redis_conn.del::<_, ()>(key).unwrap(); redis_conn.del::<_, ()>(key).unwrap();
for (j, v) in stream_ids.enumerate() { for (j, v) in stream_ids.enumerate() {
redis_conn redis_conn

View file

@ -42,8 +42,8 @@
"test": "pnpm run cargo:test && pnpm run build:napi && ava", "test": "pnpm run cargo:test && pnpm run build:napi && ava",
"universal": "napi universal", "universal": "napi universal",
"version": "napi version", "version": "napi version",
"format": "cargo fmt --all", "format": "cargo fmt --all -- --check",
"lint": "cargo clippy --fix", "lint": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all -- --check",
"cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration", "cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration",
"cargo:unit": "cargo test unit_test && cargo test -F napi unit_test", "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
"cargo:integration": "cargo test -F noarray int_test -- --test-threads=1" "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"

File diff suppressed because one or more lines are too long

View file

@ -16,19 +16,19 @@
"build": "pnpm swc src -d built -D", "build": "pnpm swc src -d built -D",
"build:debug": "pnpm swc src -d built -s -D", "build:debug": "pnpm swc src -d built -s -D",
"watch": "pnpm swc src -d built -D -w", "watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check --apply *", "lint": "pnpm biome check --apply **/*.ts ; pnpm run format",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "pnpm run mocha", "test": "pnpm run mocha",
"format": "pnpm rome format * --write" "format": "pnpm biome format * --write"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
"@tensorflow/tfjs-node": "3.21.1" "@tensorflow/tfjs-node": "3.21.1"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "5.7.2", "@bull-board/api": "5.8.0",
"@bull-board/koa": "5.7.2", "@bull-board/koa": "5.8.0",
"@bull-board/ui": "5.7.2", "@bull-board/ui": "5.8.0",
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0", "@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3", "@koa/cors": "3.4.3",
@ -41,17 +41,16 @@
"@tensorflow/tfjs": "^4.2.0", "@tensorflow/tfjs": "^4.2.0",
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "6.0.0",
"argon2": "^0.30.3", "argon2": "^0.31.1",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1413.0", "aws-sdk": "2.1413.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"bull": "4.11.2", "bull": "4.11.3",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "TheEssem/cacheable-lookup",
"cbor": "8.1.0",
"chalk": "5.3.0", "chalk": "5.3.0",
"chalk-template": "0.4.0", "chalk-template": "0.4.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
@ -63,19 +62,19 @@
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "17.1.6", "file-type": "18.5.0",
"firefish-js": "workspace:*", "firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"got": "12.5.3", "got": "13.0.0",
"gunzip-maybe": "^1.4.2", "gunzip-maybe": "^1.4.2",
"hpagent": "0.1.2", "hpagent": "1.2.0",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
"is-svg": "4.3.2", "is-svg": "5.0.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "20.0.3", "jsdom": "22.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.2.0", "jsonld": "8.2.1",
"jsrsasign": "10.8.6", "jsrsasign": "10.8.6",
"koa": "2.14.2", "koa": "2.14.2",
"koa-body": "^6.0.1", "koa-body": "^6.0.1",
@ -89,10 +88,10 @@
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"megalodon": "workspace:*", "megalodon": "workspace:*",
"meilisearch": "0.33.0", "meilisearch": "0.34.1",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"msgpackr": "1.9.6", "msgpackr": "1.9.7",
"multer": "1.4.4-lts.1", "multer": "1.4.4-lts.1",
"native-utils": "link:native-utils", "native-utils": "link:native-utils",
"nested-property": "4.0.0", "nested-property": "4.0.0",
@ -100,22 +99,22 @@
"nodemailer": "6.9.4", "nodemailer": "6.9.4",
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "^0.10.0", "oauth": "^0.10.0",
"opencc-js": "^1.0.5",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.1.4", "otpauth": "^9.1.4",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.11.2", "pg": "8.11.3",
"private-ip": "2.3.4", "private-ip": "3.0.1",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"punycode": "2.3.0", "punycode": "2.3.0",
"pureimage": "0.3.15", "pureimage": "0.4.8",
"qrcode": "1.5.3", "qrcode": "1.5.3",
"qs": "6.11.2", "qs": "6.11.2",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.20.1", "re2": "1.20.3",
"redis-lock": "0.1.4", "redis-semaphore": "5.5.0",
"redis-semaphore": "5.4.0",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rename": "1.0.4", "rename": "1.0.4",
"rndstr": "1.0.0", "rndstr": "1.0.0",
@ -123,12 +122,12 @@
"sanitize-html": "2.11.0", "sanitize-html": "2.11.0",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"semver": "7.5.4", "semver": "7.5.4",
"sharp": "0.32.4", "sharp": "0.32.5",
"sonic-channel": "^1.3.1", "sonic-channel": "^1.3.1",
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "2.7.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.18.13", "systeminformation": "5.21.3",
"tar-stream": "^3.1.6", "tar-stream": "^3.1.6",
"tesseract.js": "^4.1.1", "tesseract.js": "^4.1.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
@ -137,7 +136,7 @@
"typeorm": "0.3.17", "typeorm": "0.3.17",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"web-push": "3.6.4", "web-push": "3.6.5",
"websocket": "1.0.34", "websocket": "1.0.34",
"xev": "3.0.2" "xev": "3.0.2"
}, },
@ -146,7 +145,6 @@
"@swc/core": "^1.3.75", "@swc/core": "^1.3.75",
"@types/adm-zip": "^0.5.0", "@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21", "@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",

View file

@ -30,40 +30,34 @@ const themeColor = chalk.hex("#31748f");
function greet() { function greet() {
if (!envOption.quiet) { if (!envOption.quiet) {
//#region Firefish logo //#region Firefish logo
const v = `v${meta.version}`;
console.log( console.log(
themeColor( themeColor(
" ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯ ", "██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄ ",
), ),
); );
console.log( console.log(
themeColor( themeColor(
"█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄", "██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄ ",
), ),
); );
console.log( console.log(
themeColor( themeColor(
"█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄ ", "█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄ ",
), ),
); );
console.log( console.log(
themeColor( themeColor(
"█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄", "██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄ ",
), ),
); );
console.log( console.log(
themeColor( themeColor(
"█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄", "██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █ ",
), ),
); );
console.log( console.log(
themeColor( themeColor(
"█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █", "╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀ ",
),
);
console.log(
themeColor(
"█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀",
), ),
); );
//#endregion //#endregion

View file

@ -70,6 +70,8 @@ if (hasConfig) {
"of", "of",
"they", "they",
"these", "these",
"is",
"are",
"これ", "これ",
"それ", "それ",
"あれ", "あれ",
@ -81,37 +83,30 @@ if (hasConfig) {
"あそこ", "あそこ",
"こちら", "こちら",
"どこ", "どこ",
"だれ",
"なに",
"なん",
"何",
"私", "私",
"貴方", "僕",
"貴方方", "俺",
"君",
"あなた",
"我々", "我々",
"私達", "私達",
"あの人",
"あのか",
"彼女", "彼女",
"彼", "彼",
"です", "です",
"ありま", "ます",
"おりま",
"います",
"は", "は",
"が", "が",
"の", "の",
"に", "に",
"を", "を",
"で", "で",
"", "",
"から", "から",
"まで", "まで",
"より", "より",
"も", "も",
"どの", "どの",
"と", "と",
"し",
"それで", "それで",
"しかし", "しかし",
]) ])

View file

@ -1,2 +1,2 @@
// rome-ignore lint/suspicious/noExplicitAny: i have no idea // biome-ignore lint/suspicious/noExplicitAny: i have no idea
type FIXME = any; type FIXME = any;

View file

@ -1,33 +1,40 @@
import { redisClient } from "../db/redis.js"; import { redisClient } from "../db/redis.js";
import { promisify } from "node:util"; import { Mutex } from "redis-semaphore";
import redisLock from "redis-lock";
/** /**
* Retry delay (ms) for lock acquisition * Retry delay (ms) for lock acquisition
*/ */
const retryDelay = 100; const retryDelay = 100;
const lock: (key: string, timeout?: number) => Promise<() => void> = redisClient
? promisify(redisLock(redisClient, retryDelay))
: async () => () => {};
/** /**
* Get AP Object lock * Get AP Object lock
* @param uri AP object ID * @param uri AP object ID
* @param timeout Lock timeout (ms), The timeout releases previous lock. * @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function * @returns Unlock function
*/ */
export function getApLock(uri: string, timeout = 30 * 1000) { export async function getApLock(uri: string, timeout = 30 * 1000) {
return lock(`ap-object:${uri}`, timeout); const lock = new Mutex(redisClient, `ap-object:${uri}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
} }
export function getFetchInstanceMetadataLock( export async function getFetchInstanceMetadataLock(
host: string, host: string,
timeout = 30 * 1000, timeout = 30 * 1000,
) { ) {
return lock(`instance:${host}`, timeout); const lock = new Mutex(redisClient, `instance:${host}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
} }
export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) { export async function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
return lock(`chart-insert:${lockKey}`, timeout); const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
} }

View file

@ -32,8 +32,6 @@ export default async function (
// Interrupt if you block the announcement destination // Interrupt if you block the announcement destination
if (await shouldBlockInstance(extractDbHost(uri))) return; if (await shouldBlockInstance(extractDbHost(uri))) return;
const unlock = await getApLock(uri);
try { try {
// Check if something with the same URI is already registered // Check if something with the same URI is already registered
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
@ -60,9 +58,10 @@ export default async function (
throw e; throw e;
} }
if (!(await Notes.isVisibleForMe(renote, actor.id))) if (renote != null && !(await Notes.isVisibleForMe(renote, actor.id))) {
return "skip: invalid actor for this activity"; console.log("skip: invalid actor for this activity");
return;
}
logger.info(`Creating the (Re)Note: ${uri}`); logger.info(`Creating the (Re)Note: ${uri}`);
const activityAudience = await parseAudience( const activityAudience = await parseAudience(
@ -79,6 +78,6 @@ export default async function (
uri, uri,
}); });
} finally { } finally {
unlock(); await getApLock(uri);
} }
} }

View file

@ -31,8 +31,6 @@ export default async function (
} }
} }
const unlock = await getApLock(uri);
try { try {
const exist = await fetchNote(note); const exist = await fetchNote(note);
if (exist) return "skip: note exists"; if (exist) return "skip: note exists";
@ -46,6 +44,6 @@ export default async function (
throw e; throw e;
} }
} finally { } finally {
unlock(); await getApLock(uri);
} }
} }

View file

@ -13,8 +13,6 @@ export default async function (
): Promise<string> { ): Promise<string> {
logger.info(`Deleting the Note: ${uri}`); logger.info(`Deleting the Note: ${uri}`);
const unlock = await getApLock(uri);
try { try {
const dbResolver = new DbResolver(); const dbResolver = new DbResolver();
const note = await dbResolver.getNoteFromApId(uri); const note = await dbResolver.getNoteFromApId(uri);
@ -39,6 +37,6 @@ export default async function (
await deleteNode(actor, note); await deleteNode(actor, note);
return "ok: note deleted"; return "ok: note deleted";
} finally { } finally {
unlock(); await getApLock(uri);
} }
} }

View file

@ -68,13 +68,13 @@ export class LdSignature {
...options, ...options,
"@context": "https://w3id.org/identity/v1", "@context": "https://w3id.org/identity/v1",
}; };
transformedOptions.type = undefined; delete transformedOptions["type"];
transformedOptions.id = undefined; delete transformedOptions["id"];
transformedOptions.signatureValue = undefined; delete transformedOptions["signatureValue"];
const canonizedOptions = await this.normalize(transformedOptions); const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions); const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data }; const transformedData = { ...data };
transformedData.signature = undefined; delete transformedData["signature"];
const cannonidedData = await this.normalize(transformedData); const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(cannonidedData); const documentHash = this.sha256(cannonidedData);

View file

@ -415,8 +415,6 @@ export async function resolveNote(
`host ${extractDbHost(uri)} is blocked`, `host ${extractDbHost(uri)} is blocked`,
); );
const unlock = await getApLock(uri);
try { try {
//#region Returns if already registered with this server //#region Returns if already registered with this server
const exist = await fetchNote(uri); const exist = await fetchNote(uri);
@ -439,7 +437,7 @@ export async function resolveNote(
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server. // Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
return await createNote(uri, resolver, true); return await createNote(uri, resolver, true);
} finally { } finally {
unlock(); await getApLock(uri);
} }
} }

View file

@ -1,5 +1,4 @@
import { promisify } from "node:util"; import { decode } from "msgpackr";
import * as cbor from "cbor";
import define from "../../../define.js"; import define from "../../../define.js";
import { import {
UserProfiles, UserProfiles,
@ -12,7 +11,6 @@ import { procedures, hash } from "../../../2fa.js";
import { publishMainStream } from "@/services/stream.js"; import { publishMainStream } from "@/services/stream.js";
import { comparePassword } from "@/misc/password.js"; import { comparePassword } from "@/misc/password.js";
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8")); const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
export const meta = { export const meta = {
@ -64,7 +62,7 @@ export default define(meta, paramDef, async (ps, user) => {
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8")); const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
const attestation = await cborDecodeFirst(ps.attestationObject); const attestation = decode(Buffer.from(ps.attestationObject, "utf-8"));
const rpIdHash = attestation.authData.slice(0, 32); const rpIdHash = attestation.authData.slice(0, 32);
if (!rpIdHashReal.equals(rpIdHash)) { if (!rpIdHashReal.equals(rpIdHash)) {
@ -81,7 +79,7 @@ export default define(meta, paramDef, async (ps, user) => {
const credentialIdLength = authData.readUInt16BE(53); const credentialIdLength = authData.readUInt16BE(53);
const credentialId = authData.slice(55, 55 + credentialIdLength); const credentialId = authData.slice(55, 55 + credentialIdLength);
const publicKeyData = authData.slice(55 + credentialIdLength); const publicKeyData = authData.slice(55 + credentialIdLength);
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData); const publicKey: Map<number, any> = decode(publicKeyData);
if (publicKey.get(3) !== -7) { if (publicKey.get(3) !== -7) {
throw new Error("alg mismatch"); throw new Error("alg mismatch");
} }

View file

@ -1,6 +1,7 @@
import { URLSearchParams } from "node:url"; import { URLSearchParams } from "node:url";
import fetch from "node-fetch"; import fetch from "node-fetch";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { Converter } from "opencc-js";
import { getAgentByUrl } from "@/misc/fetch.js"; import { getAgentByUrl } from "@/misc/fetch.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
@ -38,6 +39,13 @@ export const paramDef = {
required: ["noteId", "targetLang"], required: ["noteId", "targetLang"],
} as const; } as const;
function convertChinese(convert: boolean, src: string) {
if (!convert) return src;
const converter = Converter({ from: "cn", to: "twp" });
return converter(src);
}
export default define(meta, paramDef, async (ps, user) => { export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch((err) => { const note = await getNote(ps.noteId, user).catch((err) => {
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
@ -93,7 +101,7 @@ export default define(meta, paramDef, async (ps, user) => {
return { return {
sourceLang: json.detectedLanguage?.language, sourceLang: json.detectedLanguage?.language,
text: json.translatedText, text: convertChinese(ps.targetLang === "zh-TW", json.translatedText),
}; };
} }
@ -128,6 +136,6 @@ export default define(meta, paramDef, async (ps, user) => {
return { return {
sourceLang: json.translations[0].detected_source_language, sourceLang: json.translations[0].detected_source_language,
text: json.translations[0].text, text: convertChinese(ps.targetLang === "zh-TW", json.translations[0].text),
}; };
}); });

View file

@ -59,7 +59,6 @@ export default class Connection {
accessToken: string, accessToken: string,
prepareStream: string | undefined, prepareStream: string | undefined,
) { ) {
console.log("constructor", prepareStream);
this.wsConnection = wsConnection; this.wsConnection = wsConnection;
this.subscriber = subscriber; this.subscriber = subscriber;
if (user) this.user = user; if (user) this.user = user;
@ -88,7 +87,6 @@ export default class Connection {
this.subscriber.on(`user:${this.user.id}`, this.onUserEvent); this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
} }
console.log("prepare", prepareStream);
if (prepareStream) { if (prepareStream) {
this.onWsConnectionMessage({ this.onWsConnectionMessage({
type: "utf8", type: "utf8",
@ -185,8 +183,7 @@ export default class Connection {
const tl = await client.getHomeTimeline(); const tl = await client.getHomeTimeline();
for (const t of tl.data) forSubscribe.push(t.id); for (const t of tl.data) forSubscribe.push(t.id);
} catch (e: any) { } catch (e: any) {
console.log(e); console.error(e);
console.error(e.response.data);
} }
} else if (simpleObj.stream === "public:local") { } else if (simpleObj.stream === "public:local") {
this.currentSubscribe.push(["public:local"]); this.currentSubscribe.push(["public:local"]);
@ -247,7 +244,6 @@ export default class Connection {
for (const obj of objs) { for (const obj of objs) {
const { type, body } = obj; const { type, body } = obj;
// console.log(type, body);
switch (type) { switch (type) {
case "readNotification": case "readNotification":
this.onReadNotification(body); this.onReadNotification(body);

View file

@ -3,10 +3,11 @@
"name": "Firefish", "name": "Firefish",
"description": "An open source, decentralized social media platform that's free forever!", "description": "An open source, decentralized social media platform that's free forever!",
"start_url": "/", "start_url": "/",
"scope": "/",
"display": "standalone", "display": "standalone",
"background_color": "#1f1d2e", "background_color": "#1f1d2e",
"theme_color": "#31748f", "theme_color": "#31748f",
"orientation": "portrait-primary", "orientation": "natural",
"icons": [ "icons": [
{ {
"src": "/static-assets/icons/192.png", "src": "/static-assets/icons/192.png",
@ -21,7 +22,7 @@
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/static-assets/icons/maskable.png", "src": "/static-assets/icons/512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"

View file

@ -11,7 +11,6 @@ export const manifestHandler = async (ctx: Koa.Context) => {
const instance = await fetchMeta(true); const instance = await fetchMeta(true);
res.short_name = instance.name || "Firefish"; res.short_name = instance.name || "Firefish";
res.name = instance.name || "Firefish";
if (instance.themeColor) res.theme_color = instance.themeColor; if (instance.themeColor) res.theme_color = instance.themeColor;
for (const icon of res.icons) { for (const icon of res.icons) {
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`; icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;

View file

@ -7,16 +7,16 @@ doctype html
// //
- -
▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯
█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄ ██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄
█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄ ██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄
█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄ █████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄
█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄ ██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄
█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █ ██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █
█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀
Thank you for using Firefish! Thank you for using Firefish!
If you are reading this message... how about joining the development? If you're reading this message... how about helping out with development?
https://git.joinfirefish.org/firefish/firefish https://git.joinfirefish.org/firefish/firefish
html html

View file

@ -430,7 +430,6 @@ export default abstract class Chart<T extends Schema> {
? `${this.name}:${date}:${span}:${group}` ? `${this.name}:${date}:${span}:${group}`
: `${this.name}:${date}:${span}`; : `${this.name}:${date}:${span}`;
const unlock = await getChartInsertLock(lockKey);
try { try {
// ロック内でもう1回チェックする // ロック内でもう1回チェックする
const currentLog = (await repository.findOneBy({ const currentLog = (await repository.findOneBy({
@ -466,14 +465,14 @@ export default abstract class Chart<T extends Schema> {
return log; return log;
} finally { } finally {
unlock(); await getChartInsertLock(lockKey);
} }
} }
protected commit(diff: Commit<T>, group: string | null = null): void { protected commit(diff: Commit<T>, group: string | null = null): void {
for (const [k, v] of Object.entries(diff)) { for (const [k, v] of Object.entries(diff)) {
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0)) if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
// rome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined // biome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
delete diff[k]; delete diff[k];
} }
this.buffer.push({ this.buffer.push({

View file

@ -15,8 +15,6 @@ export async function fetchInstanceMetadata(
instance: Instance, instance: Instance,
force = false, force = false,
): Promise<void> { ): Promise<void> {
const unlock = await getFetchInstanceMetadataLock(instance.host);
if (!force) { if (!force) {
const _instance = await Instances.findOneBy({ host: instance.host }); const _instance = await Instances.findOneBy({ host: instance.host });
const now = Date.now(); const now = Date.now();
@ -24,7 +22,7 @@ export async function fetchInstanceMetadata(
_instance?.infoUpdatedAt && _instance?.infoUpdatedAt &&
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24 now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
) { ) {
unlock(); await getFetchInstanceMetadataLock(instance.host);
return; return;
} }
} }
@ -53,7 +51,7 @@ export async function fetchInstanceMetadata(
} as Record<string, any>; } as Record<string, any>;
if (info) { if (info) {
updates.softwareName = info.software?.name.toLowerCase(); updates.softwareName = info.software?.name?.toLowerCase() || null;
updates.softwareVersion = info.software?.version; updates.softwareVersion = info.software?.version;
updates.openRegistrations = info.openRegistrations; updates.openRegistrations = info.openRegistrations;
updates.maintainerName = info.metadata updates.maintainerName = info.metadata
@ -80,24 +78,24 @@ export async function fetchInstanceMetadata(
} catch (e) { } catch (e) {
logger.error(`Failed to update metadata of ${instance.host}: ${e}`); logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
} finally { } finally {
unlock(); await getFetchInstanceMetadataLock(instance.host);
} }
} }
type NodeInfo = { type NodeInfo = {
openRegistrations?: any; openRegistrations?: boolean;
software?: { software?: {
name?: any; name?: string;
version?: any; version?: string;
}; };
metadata?: { metadata?: {
name?: any; name?: string;
nodeName?: any; nodeName?: string;
nodeDescription?: any; nodeDescription?: string;
description?: any; description?: string;
maintainer?: { maintainer?: {
name?: any; name?: string;
email?: any; email?: string;
}; };
}; };
}; };

View file

@ -169,7 +169,7 @@ export default async (
data: Option, data: Option,
silent = false, silent = false,
) => ) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME // biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
const dontFederateInitially = data.visibility === "hidden"; const dontFederateInitially = data.visibility === "hidden";

View file

@ -1,6 +1,7 @@
{ {
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"], "extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
"plugins": ["file-progress", "prettier"], "plugins": ["file-progress", "prettier"],
"ignorePatterns": ["**/*.json5"],
"rules": { "rules": {
"file-progress/activate": 1 "file-progress/activate": 1
} }

View file

@ -5,9 +5,9 @@
"watch": "pnpm vite build --watch --mode development", "watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build", "build": "pnpm vite build",
"build:debug": "pnpm run build", "build:debug": "pnpm run build",
"lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue", "lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
"lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache", "lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata" "format": "pnpm biome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
}, },
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
@ -16,7 +16,7 @@
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "5.0.0", "@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-json": "6.0.0", "@rollup/plugin-json": "6.0.0",
"@rollup/pluginutils": "^5.0.3", "@rollup/pluginutils": "^5.0.4",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
@ -28,15 +28,15 @@
"@types/seedrandom": "3.0.5", "@types/seedrandom": "3.0.5",
"@types/throttle-debounce": "5.0.0", "@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/uuid": "9.0.2", "@types/uuid": "9.0.3",
"@vitejs/plugin-vue": "4.3.1", "@vitejs/plugin-vue": "4.3.4",
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.3.4",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "6.0.1", "autosize": "6.0.1",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"broadcast-channel": "5.2.0", "broadcast-channel": "5.3.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer", "browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"chart.js": "4.3.3", "chart.js": "4.4.0",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "^2.0.1", "chartjs-chart-matrix": "^2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
@ -63,36 +63,35 @@
"katex": "0.16.8", "katex": "0.16.8",
"matter-js": "0.19.0", "matter-js": "0.19.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8", "photoswipe": "5.3.8",
"prettier": "3.0.2", "prettier": "3.0.3",
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0", "prismjs": "1.29.0",
"punycode": "2.3.0", "punycode": "2.3.0",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.28.0", "rollup": "3.28.1",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.66.0", "sass": "1.66.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"swiper": "10.2.0", "swiper": "10.2.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.155.0", "three": "0.156.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.7", "tsc-alias": "1.8.7",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "5.1.6", "typescript": "5.2.2",
"unicode-emoji-json": "^0.4.0", "unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.1", "vanilla-tilt": "1.8.1",
"vite": "4.4.9", "vite": "4.4.9",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vue": "3.3.4", "vue": "3.3.4",
"vue-draggable-plus": "^0.2.5", "vue-draggable-plus": "^0.2.6",
"vue-isyourpasswordsafe": "^2.0.0", "vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0", "vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2" "vue-prism-editor": "2.0.0-alpha.2"

View file

@ -1,10 +1,10 @@
import { defineAsyncComponent, reactive } from "vue"; import { defineAsyncComponent, reactive } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import { i18n } from "./i18n"; import { i18n } from "./i18n";
import { del, get, set } from "@/scripts/idb-proxy"; import { del, get, set } from "@/scripts/idb-proxy";
import { apiUrl } from "@/config"; import { apiUrl } from "@/config";
import { waiting, api, popup, popupMenu, success, alert } from "@/os"; import { alert, api, popup, popupMenu, success, waiting } from "@/os";
import { unisonReload, reloadChannel } from "@/scripts/unison-reload"; import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
@ -28,7 +28,7 @@ export async function signout() {
const accounts = await getAccounts(); const accounts = await getAccounts();
//#region Remove service worker registration // #region Remove service worker registration
try { try {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
const registration = await navigator.serviceWorker.ready; const registration = await navigator.serviceWorker.ready;
@ -52,7 +52,7 @@ export async function signout() {
}); });
} }
} catch (err) {} } catch (err) {}
//#endregion // #endregion
document.cookie = "igi=; path=/"; document.cookie = "igi=; path=/";

View file

@ -8,9 +8,11 @@
<div :class="$style.time"> <div :class="$style.time">
<MkTime :time="announcement.createdAt" /> <MkTime :time="announcement.createdAt" />
<div v-if="announcement.updatedAt"> <div v-if="announcement.updatedAt">
{{ i18n.ts.updatedAt }}: <small>
<MkTime :time="announcement.createdAt" /> {{ i18n.ts.updatedAt }}:
</div> <MkTime :time="announcement.createdAt" />
</small>
</span>
</div> </div>
<Mfm :text="text" /> <Mfm :text="text" />
<img <img
@ -80,6 +82,6 @@ const gotIt = () => {
} }
.gotIt { .gotIt {
margin: 8px 0 0 0; margin: 1rem 0 1rem 2rem;
} }
</style> </style>

View file

@ -199,7 +199,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue"; import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import * as Acct from "firefish-js/built/acct"; import * as Acct from "firefish-js/built/acct";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
@ -281,7 +281,9 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null); const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null); const selectedValue = ref(props.select?.default ?? null);
let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null); const disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(
null,
);
const okButtonDisabled = computed<boolean>(() => { const okButtonDisabled = computed<boolean>(() => {
if (props.input) { if (props.input) {
if (props.input.minLength) { if (props.input.minLength) {

View file

@ -39,7 +39,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from "vue"; import { computed, defineAsyncComponent, ref } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import copyToClipboard from "@/scripts/copy-to-clipboard"; import copyToClipboard from "@/scripts/copy-to-clipboard";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue"; import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import bytes from "@/filters/bytes"; import bytes from "@/filters/bytes";
@ -160,7 +160,7 @@ function rename() {
if (canceled) return; if (canceled) return;
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: props.file.id, fileId: props.file.id,
name: name, name,
}); });
}); });
} }
@ -179,7 +179,7 @@ function describe() {
{ {
done: (result) => { done: (result) => {
if (!result || result.canceled) return; if (!result || result.canceled) return;
let comment = result.result; const comment = result.result;
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: props.file.id, fileId: props.file.id,
comment: comment.length === 0 ? null : comment, comment: comment.length === 0 ? null : comment,

View file

@ -38,7 +38,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from "vue"; import { computed, defineAsyncComponent, ref } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -131,7 +131,7 @@ function onDrop(ev: DragEvent) {
return; return;
} }
//#region // #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
@ -141,9 +141,9 @@ function onDrop(ev: DragEvent) {
folderId: props.folder.id, folderId: props.folder.id,
}); });
} }
//#endregion // #endregion
//#region // #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") { if (driveFolder != null && driveFolder !== "") {
const folder = JSON.parse(driveFolder); const folder = JSON.parse(driveFolder);
@ -175,7 +175,7 @@ function onDrop(ev: DragEvent) {
} }
}); });
} }
//#endregion // #endregion
} }
function onDragstart(ev: DragEvent) { function onDragstart(ev: DragEvent) {
@ -207,7 +207,7 @@ function rename() {
if (canceled) return; if (canceled) return;
os.api("drive/folders/update", { os.api("drive/folders/update", {
folderId: props.folder.id, folderId: props.folder.id,
name: name, name,
}); });
}); });
} }

View file

@ -15,7 +15,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -86,7 +86,7 @@ function onDrop(ev: DragEvent) {
return; return;
} }
//#region // #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
@ -96,9 +96,9 @@ function onDrop(ev: DragEvent) {
folderId: props.folder ? props.folder.id : null, folderId: props.folder ? props.folder.id : null,
}); });
} }
//#endregion // #endregion
//#region // #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") { if (driveFolder != null && driveFolder !== "") {
const folder = JSON.parse(driveFolder); const folder = JSON.parse(driveFolder);
@ -110,7 +110,7 @@ function onDrop(ev: DragEvent) {
parentId: props.folder ? props.folder.id : null, parentId: props.folder ? props.folder.id : null,
}); });
} }
//#endregion // #endregion
} }
</script> </script>

View file

@ -139,7 +139,7 @@ import {
ref, ref,
watch, watch,
} from "vue"; } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import MkButton from "./MkButton.vue"; import MkButton from "./MkButton.vue";
import XNavFolder from "@/components/MkDrive.navFolder.vue"; import XNavFolder from "@/components/MkDrive.navFolder.vue";
import XFolder from "@/components/MkDrive.folder.vue"; import XFolder from "@/components/MkDrive.folder.vue";
@ -294,7 +294,7 @@ function onDrop(ev: DragEvent): any {
return; return;
} }
//#region // #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
@ -305,9 +305,9 @@ function onDrop(ev: DragEvent): any {
folderId: folder.value ? folder.value.id : null, folderId: folder.value ? folder.value.id : null,
}); });
} }
//#endregion // #endregion
//#region // #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") { if (driveFolder != null && driveFolder !== "") {
const droppedFolder = JSON.parse(driveFolder); const droppedFolder = JSON.parse(driveFolder);
@ -339,7 +339,7 @@ function onDrop(ev: DragEvent): any {
} }
}); });
} }
//#endregion // #endregion
} }
function selectLocalFile() { function selectLocalFile() {
@ -354,7 +354,7 @@ function urlUpload() {
}).then(({ canceled, result: url }) => { }).then(({ canceled, result: url }) => {
if (canceled || !url) return; if (canceled || !url) return;
os.api("drive/files/upload-from-url", { os.api("drive/files/upload-from-url", {
url: url, url,
folderId: folder.value ? folder.value.id : undefined, folderId: folder.value ? folder.value.id : undefined,
}); });
@ -372,7 +372,7 @@ function createFolder() {
}).then(({ canceled, result: name }) => { }).then(({ canceled, result: name }) => {
if (canceled) return; if (canceled) return;
os.api("drive/folders/create", { os.api("drive/folders/create", {
name: name, name,
parentId: folder.value ? folder.value.id : undefined, parentId: folder.value ? folder.value.id : undefined,
}).then((createdFolder) => { }).then((createdFolder) => {
addFolder(createdFolder, true); addFolder(createdFolder, true);
@ -389,7 +389,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
if (canceled) return; if (canceled) return;
os.api("drive/folders/update", { os.api("drive/folders/update", {
folderId: folderToRename.id, folderId: folderToRename.id,
name: name, name,
}).then((updatedFolder) => { }).then((updatedFolder) => {
// FIXME: // FIXME:
move(updatedFolder); move(updatedFolder);

View file

@ -68,7 +68,7 @@ const is = computed(() => {
"application/x-tar", "application/x-tar",
"application/gzip", "application/gzip",
"application/x-7z-compressed", "application/x-7z-compressed",
].some((archiveType) => archiveType === props.file.type) ].includes(props.file.type)
) )
return "archive"; return "archive";
return "unknown"; return "unknown";

View file

@ -37,7 +37,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import XDrive from "@/components/MkDrive.vue"; import XDrive from "@/components/MkDrive.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import number from "@/filters/number"; import number from "@/filters/number";

View file

@ -15,7 +15,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import XDrive from "@/components/MkDrive.vue"; import XDrive from "@/components/MkDrive.vue";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";

View file

@ -48,7 +48,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted } from "vue"; import { onMounted, ref, watch } from "vue";
import { addSkinTone } from "@/scripts/emojilist"; import { addSkinTone } from "@/scripts/emojilist";
const props = defineProps<{ const props = defineProps<{

View file

@ -1,5 +1,5 @@
<template> <template>
<FocusTrap v-bind:active="isActive"> <FocusTrap :active="isActive">
<div <div
class="omfetrab" class="omfetrab"
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
@ -163,14 +163,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue"; import { computed, onMounted, ref, watch } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import { FocusTrap } from "focus-trap-vue";
import XSection from "@/components/MkEmojiPicker.section.vue"; import XSection from "@/components/MkEmojiPicker.section.vue";
import type { UnicodeEmojiDef } from "@/scripts/emojilist";
import { import {
emojilist, emojilist,
unicodeEmojiCategories,
UnicodeEmojiDef,
getNicelyLabeledCategory, getNicelyLabeledCategory,
unicodeEmojiCategories,
} from "@/scripts/emojilist"; } from "@/scripts/emojilist";
import { getStaticImageUrl } from "@/scripts/get-static-image-url"; import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import Ripple from "@/components/MkRipple.vue"; import Ripple from "@/components/MkRipple.vue";
@ -180,7 +181,6 @@ import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance"; import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { FocusTrap } from "focus-trap-vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View file

@ -8,7 +8,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import * as os from "@/os"; import * as os from "@/os";
const meta = ref<Misskey.entities.DetailedInstanceMetadata>(); const meta = ref<Misskey.entities.DetailedInstanceMetadata>();

View file

@ -1,14 +1,15 @@
<template> <template>
<button <button
v-if="!hideMenu" v-if="!hideMenu"
v-tooltip="i18n.ts.menu"
class="menu _button" class="menu _button"
@click.stop="menu" @click.stop="menu"
v-tooltip="i18n.ts.menu"
> >
<i class="ph-dots-three-outline ph-bold ph-lg"></i> <i class="ph-dots-three-outline ph-bold ph-lg"></i>
</button> </button>
<button <button
v-if="$i != null && $i.id != user.id" v-if="$i != null && $i.id != user.id"
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
class="kpoogebi _button follow-button" class="kpoogebi _button follow-button"
:class="{ :class="{
wait, wait,
@ -18,9 +19,8 @@
blocking: isBlocking, blocking: isBlocking,
}" }"
:disabled="wait" :disabled="wait"
@click.stop="onClick"
:aria-label="`${state} ${user.name || user.username}`" :aria-label="`${state} ${user.name || user.username}`"
v-tooltip="full ? null : `${state} ${user.name || user.username}`" @click.stop="onClick"
> >
<template v-if="!wait"> <template v-if="!wait">
<template v-if="isBlocking"> <template v-if="isBlocking">
@ -88,13 +88,13 @@ const props = withDefaults(
const isBlocking = computed(() => props.user.isBlocking); const isBlocking = computed(() => props.user.isBlocking);
let state = ref(i18n.ts.processing); const state = ref(i18n.ts.processing);
let isFollowing = ref(props.user.isFollowing); const isFollowing = ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = ref( const hasPendingFollowRequestFromYou = ref(
props.user.hasPendingFollowRequestFromYou, props.user.hasPendingFollowRequestFromYou,
); );
let wait = ref(false); const wait = ref(false);
const connection = stream.useChannel("main"); const connection = stream.useChannel("main");
if (props.user.isFollowing == null) { if (props.user.isFollowing == null) {

View file

@ -64,7 +64,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import {} from "vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
@ -77,11 +76,11 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let dialog: InstanceType<typeof XModalWindow> = ref(); const dialog: InstanceType<typeof XModalWindow> = ref();
let username = ref(""); const username = ref("");
let email = ref(""); const email = ref("");
let processing = ref(false); const processing = ref(false);
async function onSubmit() { async function onSubmit() {
processing.value = true; processing.value = true;

View file

@ -3,7 +3,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue"; import { defineAsyncComponent, defineComponent } from "vue";
export default defineComponent({ export default defineComponent({
components: { components: {

View file

@ -8,7 +8,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, nextTick, watch, shallowRef, ref } from "vue"; import { nextTick, onMounted, ref, shallowRef, watch } from "vue";
import { Chart } from "chart.js"; import { Chart } from "chart.js";
import * as os from "@/os"; import * as os from "@/os";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -26,8 +26,8 @@ const props = defineProps<{
const rootEl = shallowRef<HTMLDivElement>(null); const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = shallowRef<HTMLCanvasElement>(null); const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date(); const now = new Date();
let chartInstance: Chart = null; let chartInstance: Chart = null,
let fetching = ref(true); fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({ const { handler: externalTooltipHandler } = useChartTooltip({
position: "middle", position: "middle",
@ -233,7 +233,7 @@ async function renderChart() {
return ["Active: " + v.v]; return ["Active: " + v.v];
}, },
}, },
//mode: 'index', // mode: 'index',
animation: { animation: {
duration: 0, duration: 0,
}, },

View file

@ -28,7 +28,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import bytes from "@/filters/bytes"; import bytes from "@/filters/bytes";
import number from "@/filters/number"; import number from "@/filters/number";

View file

@ -49,7 +49,7 @@ const props = withDefaults(
); );
const canvas = ref<HTMLCanvasElement>(); const canvas = ref<HTMLCanvasElement>();
let loaded = ref(false); const loaded = ref(false);
function draw() { function draw() {
if (props.hash == null || canvas.value == null) return; if (props.hash == null || canvas.value == null) return;

View file

@ -11,8 +11,8 @@
v-if="closeable" v-if="closeable"
v-tooltip="i18n.ts.close" v-tooltip="i18n.ts.close"
class="_buttonIcon close" class="_buttonIcon close"
@click.stop="close"
:aria-label="i18n.t('close')" :aria-label="i18n.t('close')"
@click.stop="close"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>

View file

@ -26,7 +26,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import * as firefish from "firefish-js"; import type * as firefish from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue"; import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os"; import * as os from "@/os";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy"; import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
@ -35,7 +35,7 @@ const props = defineProps<{
instance: firefish.entities.Instance; instance: firefish.entities.Instance;
}>(); }>();
let chartValues = ref<number[] | null>(null); const chartValues = ref<number[] | null>(null);
os.apiGet("charts/instance", { os.apiGet("charts/instance", {
host: props.instance.host, host: props.instance.host,

View file

@ -58,11 +58,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import type { Instance } from "firefish-js/built/entities";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { Instance } from "firefish-js/built/entities";
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "ok", selected: Instance): void; (ev: "ok", selected: Instance): void;
@ -70,10 +70,10 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let hostname = ref(""); const hostname = ref("");
let instances: Instance[] = ref([]); const instances: Instance[] = ref([]);
let selected: Instance | null = ref(null); const selected: Instance | null = ref(null);
let dialogEl = ref<InstanceType<typeof XModalWindow>>(); const dialogEl = ref<InstanceType<typeof XModalWindow>>();
let searchOrderLatch = 0; let searchOrderLatch = 0;
const search = () => { const search = () => {

View file

@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
initChart(); initChart();
const chartLimit = 500; const chartLimit = 500;
let chartSpan = ref<"hour" | "day">("hour"); const chartSpan = ref<"hour" | "day">("hour");
let chartSrc = ref("active-users"); const chartSrc = ref("active-users");
let heatmapSrc = ref("active-users"); const heatmapSrc = ref("active-users");
let subDoughnutEl = shallowRef<HTMLCanvasElement>(); const subDoughnutEl = shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = shallowRef<HTMLCanvasElement>(); const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({ const { handler: externalTooltipHandler1 } = useChartTooltip({
position: "middle", position: "middle",

View file

@ -1,8 +1,8 @@
<template> <template>
<div <div
class="hpaizdrt"
v-tooltip="capitalize(instance.softwareName)"
ref="ticker" ref="ticker"
v-tooltip="capitalize(instance.softwareName)"
class="hpaizdrt"
:style="bg" :style="bg"
> >
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" /> <img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
@ -26,7 +26,7 @@ const props = defineProps<{
}; };
}>(); }>();
let ticker = ref<HTMLElement | null>(null); const ticker = ref<HTMLElement | null>(null);
// if no instance data is given, this is for the local instance // if no instance data is given, this is for the local instance
const instance = props.instance ?? { const instance = props.instance ?? {

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="media" v-size="{ max: [350] }"> <div v-size="{ max: [350] }" class="media">
<button v-if="hide" class="hidden" @click="hide = false"> <button v-if="hide" class="hidden" @click="hide = false">
<ImgWithBlurhash <ImgWithBlurhash
:hash="media.blurhash" :hash="media.blurhash"
@ -89,7 +89,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref, computed } from "vue"; import { computed, ref, watch } from "vue";
import VuePlyr from "vue-plyr"; import VuePlyr from "vue-plyr";
import "vue-plyr/dist/vue-plyr.css"; import "vue-plyr/dist/vue-plyr.css";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
@ -104,7 +104,7 @@ const props = defineProps<{
raw?: boolean; raw?: boolean;
}>(); }>();
let hide = ref(true); const hide = ref(true);
const plyr = ref(); const plyr = ref();

View file

@ -71,7 +71,7 @@ const props = withDefaults(
); );
const audioEl = ref<HTMLAudioElement | null>(); const audioEl = ref<HTMLAudioElement | null>();
let hide = ref(true); const hide = ref(true);
function volumechange() { function volumechange() {
if (audioEl.value) if (audioEl.value)

View file

@ -29,7 +29,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import PhotoSwipeLightbox from "photoswipe/lightbox"; import PhotoSwipeLightbox from "photoswipe/lightbox";
import PhotoSwipe from "photoswipe"; import PhotoSwipe from "photoswipe";
import "photoswipe/style.css"; import "photoswipe/style.css";
@ -125,11 +125,11 @@ onMounted(() => {
className: "pwsp__alt-text-container", className: "pwsp__alt-text-container",
appendTo: "wrapper", appendTo: "wrapper",
onInit: (el, pwsp) => { onInit: (el, pwsp) => {
let textBox = document.createElement("p"); const textBox = document.createElement("p");
textBox.className = "pwsp__alt-text"; textBox.className = "pwsp__alt-text";
el.appendChild(textBox); el.appendChild(textBox);
let preventProp = function (ev: Event): void { const preventProp = function (ev: Event): void {
ev.stopPropagation(); ev.stopPropagation();
}; };

View file

@ -14,7 +14,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, ref } from "vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
const props = defineProps<{ const props = defineProps<{
items: MenuItem[]; items: MenuItem[];

View file

@ -14,8 +14,8 @@
width: width && !asDrawer ? width + 'px' : '', width: width && !asDrawer ? width + 'px' : '',
maxHeight: maxHeight ? maxHeight + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '',
}" }"
@contextmenu.self="(e) => e.preventDefault()"
tabindex="-1" tabindex="-1"
@contextmenu.self="(e) => e.preventDefault()"
> >
<template v-for="item in items2"> <template v-for="item in items2">
<div v-if="item === null" class="divider"></div> <div v-if="item === null" class="divider"></div>
@ -47,7 +47,7 @@
v-if="item.avatar" v-if="item.avatar"
:user="item.avatar" :user="item.avatar"
class="avatar" class="avatar"
disableLink disable-link
/> />
<span :style="item.textStyle || ''">{{ <span :style="item.textStyle || ''">{{
item.text item.text
@ -100,7 +100,7 @@
<MkAvatar <MkAvatar
:user="item.user" :user="item.user"
class="avatar" class="avatar"
disableLink disable-link
/><MkUserName :user="item.user" /> /><MkUserName :user="item.user" />
<span <span
v-if="item.indicate" v-if="item.indicate"
@ -168,7 +168,7 @@
v-if="item.avatar" v-if="item.avatar"
:user="item.avatar" :user="item.avatar"
class="avatar" class="avatar"
disableLink disable-link
/> />
<span :style="item.textStyle || ''">{{ <span :style="item.textStyle || ''">{{
item.text item.text
@ -210,11 +210,16 @@ import {
ref, ref,
watch, watch,
} from "vue"; } from "vue";
import { FocusTrap } from "focus-trap-vue";
import FormSwitch from "@/components/form/switch.vue"; import FormSwitch from "@/components/form/switch.vue";
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu"; import type {
InnerMenuItem,
MenuAction,
MenuItem,
MenuPending,
} from "@/types/menu";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { FocusTrap } from "focus-trap-vue";
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue")); const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
const focusTrap = ref(); const focusTrap = ref();
@ -233,13 +238,13 @@ const emit = defineEmits<{
(ev: "close", actioned?: boolean): void; (ev: "close", actioned?: boolean): void;
}>(); }>();
let itemsEl = ref<HTMLDivElement>(); const itemsEl = ref<HTMLDivElement>();
let items2: InnerMenuItem[] = ref([]); const items2: InnerMenuItem[] = ref([]);
let child = ref<InstanceType<typeof XChild>>(); const child = ref<InstanceType<typeof XChild>>();
let childShowingItem = ref<MenuItem | null>(); const childShowingItem = ref<MenuItem | null>();
watch( watch(
() => props.items, () => props.items,
@ -267,8 +272,8 @@ watch(
}, },
); );
let childMenu = ref<MenuItem[] | null>(); const childMenu = ref<MenuItem[] | null>();
let childTarget = ref<HTMLElement | null>(); const childTarget = ref<HTMLElement | null>();
function closeChild() { function closeChild() {
childMenu.value = null; childMenu.value = null;

View file

@ -25,7 +25,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref } from "vue"; import { ref, watch } from "vue";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { useInterval } from "@/scripts/use-interval"; import { useInterval } from "@/scripts/use-interval";
@ -37,10 +37,10 @@ const props = defineProps<{
const viewBoxX = 50; const viewBoxX = 50;
const viewBoxY = 50; const viewBoxY = 50;
const gradientId = uuid(); const gradientId = uuid();
let polylinePoints = ref(""); const polylinePoints = ref("");
let polygonPoints = ref(""); const polygonPoints = ref("");
let headX = ref<number | null>(null); const headX = ref<number | null>(null);
let headY = ref<number | null>(null); const headY = ref<number | null>(null);
const accent = tinycolor( const accent = tinycolor(
getComputedStyle(document.documentElement).getPropertyValue("--accent"), getComputedStyle(document.documentElement).getPropertyValue("--accent"),
); );

View file

@ -25,6 +25,7 @@
<div <div
v-show="manualShowing != null ? manualShowing : showing" v-show="manualShowing != null ? manualShowing : showing"
v-hotkey.global="keymap" v-hotkey.global="keymap"
v-focus
:class="[ :class="[
$style.root, $style.root,
{ {
@ -44,7 +45,6 @@
'--transformOrigin': transformOrigin, '--transformOrigin': transformOrigin,
}" }"
tabindex="-1" tabindex="-1"
v-focus
> >
<div <div
class="_modalBg data-cy-bg" class="_modalBg data-cy-bg"
@ -78,20 +78,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
computed,
nextTick, nextTick,
onMounted, onMounted,
watch,
provide,
onUnmounted, onUnmounted,
provide,
ref, ref,
shallowRef, shallowRef,
computed, watch,
} from "vue"; } from "vue";
import { FocusTrap } from "focus-trap-vue";
import * as os from "@/os"; import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch"; import { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
import { FocusTrap } from "focus-trap-vue";
function getFixedContainer(el: Element | null): Element | null { function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === "BODY") return null; if (el == null || el.tagName === "BODY") return null;
@ -139,13 +139,13 @@ const emit = defineEmits<{
provide("modal", true); provide("modal", true);
let maxHeight = ref<number>(); const maxHeight = ref<number>();
let fixed = ref(false); const fixed = ref(false);
let transformOrigin = ref("center"); const transformOrigin = ref("center");
let showing = ref(true); const showing = ref(true);
let content = shallowRef<HTMLElement>(); const content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority); const zIndex = os.claimZIndex(props.zPriority);
let useSendAnime = ref(false); const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => { const type = computed<ModalTypes>(() => {
if (props.preferType === "auto") { if (props.preferType === "auto") {
if ( if (
@ -164,7 +164,7 @@ const type = computed<ModalTypes>(() => {
const isEnableBgTransparent = computed( const isEnableBgTransparent = computed(
() => props.transparentBg && type.value === "popup", () => props.transparentBg && type.value === "popup",
); );
let transitionName = computed(() => const transitionName = computed(() =>
defaultStore.state.animation defaultStore.state.animation
? useSendAnime.value ? useSendAnime.value
? "send" ? "send"
@ -175,7 +175,7 @@ let transitionName = computed(() =>
: "modal" : "modal"
: "", : "",
); );
let transitionDuration = computed(() => const transitionDuration = computed(() =>
transitionName.value === "send" transitionName.value === "send"
? 400 ? 400
: transitionName.value === "modal-popup" : transitionName.value === "modal-popup"
@ -235,8 +235,7 @@ const align = () => {
const width = content.value!.offsetWidth; const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight; const height = content.value!.offsetHeight;
let left; let left, top;
let top;
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset); const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset); const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
@ -321,8 +320,8 @@ const align = () => {
left = 0; left = 0;
} }
let transformOriginX = "center"; let transformOriginX = "center",
let transformOriginY = "center"; transformOriginY = "center";
if ( if (
top >= top >=

View file

@ -28,8 +28,8 @@
</span> </span>
<button <button
class="_button" class="_button"
@click="$refs.modal.close()"
:aria-label="i18n.t('close')" :aria-label="i18n.t('close')"
@click="$refs.modal.close()"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
@ -52,7 +52,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, provide, ref, computed } from "vue"; import type { ComputedRef } from "vue";
import { computed, provide, ref } from "vue";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import { popout as _popout } from "@/scripts/popout"; import { popout as _popout } from "@/scripts/popout";
import copyToClipboard from "@/scripts/copy-to-clipboard"; import copyToClipboard from "@/scripts/copy-to-clipboard";
@ -60,7 +61,8 @@ import { url } from "@/config";
import * as os from "@/os"; import * as os from "@/os";
import { mainRouter, routes } from "@/router"; import { mainRouter, routes } from "@/router";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata"; import type { PageMetadata } from "@/scripts/page-metadata";
import { provideMetadataReceiver } from "@/scripts/page-metadata";
import { Router } from "@/nirax"; import { Router } from "@/nirax";
const props = defineProps<{ const props = defineProps<{
@ -76,12 +78,12 @@ const router = new Router(routes, props.initialPath);
router.addListener("push", (ctx) => {}); router.addListener("push", (ctx) => {});
let pageMetadata = ref<null | ComputedRef<PageMetadata>>(); const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let rootEl = ref(); const rootEl = ref();
let modal = ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
let path = ref(props.initialPath); const path = ref(props.initialPath);
let width = ref(860); const width = ref(860);
let height = ref(660); const height = ref(660);
const history = []; const history = [];
provide("router", router); provide("router", router);

View file

@ -25,10 +25,10 @@
<div ref="headerEl" class="header"> <div ref="headerEl" class="header">
<button <button
v-if="props.withOkButton" v-if="props.withOkButton"
v-tooltip="i18n.ts.close"
:aria-label="i18n.t('close')" :aria-label="i18n.t('close')"
class="_button" class="_button"
@click="$emit('close')" @click="$emit('close')"
v-tooltip="i18n.ts.close"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
@ -92,9 +92,9 @@ const emit = defineEmits<{
(event: "ok"): void; (event: "ok"): void;
}>(); }>();
let modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = shallowRef<InstanceType<typeof MkModal>>();
let rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
let headerEl = shallowRef<HTMLElement>(); const headerEl = shallowRef<HTMLElement>();
const close = (ev) => { const close = (ev) => {
modal.value?.close(ev); modal.value?.close(ev);

View file

@ -1,15 +1,15 @@
<template> <template>
<div <div
:aria-label="accessibleLabel"
v-if="!muted.muted" v-if="!muted.muted"
v-show="!isDeleted" v-show="!isDeleted"
:id="appearNote.id"
ref="el" ref="el"
v-hotkey="keymap" v-hotkey="keymap"
v-size="{ max: [500, 350] }" v-size="{ max: [500, 350] }"
:aria-label="accessibleLabel"
class="tkcbzcuz note-container" class="tkcbzcuz note-container"
:tabindex="!isDeleted ? '-1' : null" :tabindex="!isDeleted ? '-1' : null"
:class="{ renote: isRenote }" :class="{ renote: isRenote }"
:id="appearNote.id"
> >
<MkNoteSub <MkNoteSub
v-if="appearNote.reply && !detailedView && !collapsedReply" v-if="appearNote.reply && !detailedView && !collapsedReply"
@ -19,10 +19,10 @@
<div <div
v-if="!detailedView" v-if="!detailedView"
class="note-context" class="note-context"
@click="noteClick"
:class="{ :class="{
collapsedReply: collapsedReply && appearNote.reply, collapsedReply: collapsedReply && appearNote.reply,
}" }"
@click="noteClick"
> >
<div class="line"></div> <div class="line"></div>
<div v-if="appearNote._prId_" class="info"> <div v-if="appearNote._prId_" class="info">
@ -87,11 +87,11 @@
</div> </div>
<article <article
class="article" class="article"
@contextmenu.stop="onContextmenu"
@click="noteClick"
:style="{ :style="{
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '', cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
}" }"
@contextmenu.stop="onContextmenu"
@click="noteClick"
> >
<div class="main"> <div class="main">
<div class="header-container"> <div class="header-container">
@ -103,8 +103,8 @@
class="text" class="text"
:note="appearNote" :note="appearNote"
:detailed="true" :detailed="true"
:detailedView="detailedView" :detailed-view="detailedView"
:parentId="appearNote.parentId" :parent-id="appearNote.parentId"
@push="(e) => router.push(notePage(e))" @push="(e) => router.push(notePage(e))"
@focusfooter="footerEl.focus()" @focusfooter="footerEl.focus()"
@expanded="(e) => setPostExpanded(e)" @expanded="(e) => setPostExpanded(e)"
@ -171,7 +171,7 @@
class="button" class="button"
:note="appearNote" :note="appearNote"
:count="appearNote.renoteCount" :count="appearNote.renoteCount"
:detailedView="detailedView" :detailed-view="detailedView"
/> />
<XStarButtonNoEmoji <XStarButtonNoEmoji
v-if="!enableEmojiReactions" v-if="!enableEmojiReactions"
@ -212,9 +212,9 @@
appearNote.myReaction != null appearNote.myReaction != null
" "
ref="reactButton" ref="reactButton"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
class="button _button reacted" class="button _button reacted"
@click.stop="undoReact(appearNote)" @click.stop="undoReact(appearNote)"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
> >
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
@ -259,8 +259,8 @@ import { computed, inject, onMounted, ref } from "vue";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import type { Ref } from "vue"; import type { Ref } from "vue";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import MkNoteSub from "@/components/MkNoteSub.vue";
import MkSubNoteContent from "./MkSubNoteContent.vue"; import MkSubNoteContent from "./MkSubNoteContent.vue";
import MkNoteSub from "@/components/MkNoteSub.vue";
import XNoteHeader from "@/components/MkNoteHeader.vue"; import XNoteHeader from "@/components/MkNoteHeader.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue"; import XRenoteButton from "@/components/MkRenoteButton.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue"; import XReactionsViewer from "@/components/MkReactionsViewer.vue";
@ -271,7 +271,7 @@ import MkVisibility from "@/components/MkVisibility.vue";
import copyToClipboard from "@/scripts/copy-to-clipboard"; import copyToClipboard from "@/scripts/copy-to-clipboard";
import { url } from "@/config"; import { url } from "@/config";
import { pleaseLogin } from "@/scripts/please-login"; import { pleaseLogin } from "@/scripts/please-login";
import { focusPrev, focusNext } from "@/scripts/focus"; import { focusNext, focusPrev } from "@/scripts/focus";
import { getWordSoftMute } from "@/scripts/check-word-mute"; import { getWordSoftMute } from "@/scripts/check-word-mute";
import { useRouter } from "@/router"; import { useRouter } from "@/router";
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
@ -297,7 +297,7 @@ const props = defineProps<{
const inChannel = inject("inChannel", null); const inChannel = inject("inChannel", null);
let note = ref(deepClone(props.note)); const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -333,7 +333,7 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>(); const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>(); const reactButton = ref<HTMLElement>();
let appearNote = computed(() => const appearNote = computed(() =>
isRenote ? (note.value.renote as misskey.entities.Note) : note.value, isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
); );
const isMyRenote = $i && $i.id === note.value.userId; const isMyRenote = $i && $i.id === note.value.userId;
@ -385,7 +385,7 @@ function react(viaKeyboard = false): void {
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.value.id, noteId: appearNote.value.id,
reaction: reaction, reaction,
}); });
}, },
() => { () => {
@ -516,7 +516,7 @@ function showRenoteMenu(viaKeyboard = false): void {
], ],
renoteTime.value, renoteTime.value,
{ {
viaKeyboard: viaKeyboard, viaKeyboard,
}, },
); );
} }
@ -560,7 +560,7 @@ function readPromo() {
isDeleted.value = true; isDeleted.value = true;
} }
let postIsExpanded = ref(false); const postIsExpanded = ref(false);
function setPostExpanded(val: boolean) { function setPostExpanded(val: boolean) {
postIsExpanded.value = val; postIsExpanded.value = val;

View file

@ -10,27 +10,27 @@
:class="{ renote: isRenote }" :class="{ renote: isRenote }"
> >
<MkNoteSub <MkNoteSub
v-if="conversation"
v-for="note in conversation" v-for="note in conversation"
v-if="conversation"
:key="note.id" :key="note.id"
class="reply-to" class="reply-to"
:note="note" :note="note"
:detailedView="true" :detailed-view="true"
/> />
<MkLoading v-else-if="note.reply" mini /> <MkLoading v-else-if="note.reply" mini />
<MkNoteSub <MkNoteSub
v-if="note.reply" v-if="note.reply"
:note="note.reply" :note="note.reply"
class="reply-to" class="reply-to"
:detailedView="true" :detailed-view="true"
/> />
<MkNote <MkNote
ref="noteEl" ref="noteEl"
@contextmenu.stop="onContextmenu"
tabindex="-1" tabindex="-1"
:note="note" :note="note"
detailedView detailed-view
@contextmenu.stop="onContextmenu"
></MkNote> ></MkNote>
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab"> <MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
@ -41,22 +41,22 @@
}}</span> }}</span>
{{ i18n.ts._notification._types.reply }} {{ i18n.ts._notification._types.reply }}
</option> </option>
<option value="renotes" v-if="note.renoteCount > 0"> <option v-if="note.renoteCount > 0" value="renotes">
<!-- <i class="ph-repeat ph-bold ph-lg"></i> --> <!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
<span class="count">{{ note.renoteCount }}</span> <span class="count">{{ note.renoteCount }}</span>
{{ i18n.ts._notification._types.renote }} {{ i18n.ts._notification._types.renote }}
</option> </option>
<option value="reactions" v-if="reactionsCount > 0"> <option v-if="reactionsCount > 0" value="reactions">
<!-- <i class="ph-smiley ph-bold ph-lg"></i> --> <!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
<span class="count">{{ reactionsCount }}</span> <span class="count">{{ reactionsCount }}</span>
{{ i18n.ts.reaction }} {{ i18n.ts.reaction }}
</option> </option>
<option value="quotes" v-if="directQuotes?.length > 0"> <option v-if="directQuotes?.length > 0" value="quotes">
<!-- <i class="ph-quotes ph-bold ph-lg"></i> --> <!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
<span class="count">{{ directQuotes.length }}</span> <span class="count">{{ directQuotes.length }}</span>
{{ i18n.ts._notification._types.quote }} {{ i18n.ts._notification._types.quote }}
</option> </option>
<option value="clips" v-if="clips?.length > 0"> <option v-if="clips?.length > 0" value="clips">
<!-- <i class="ph-paperclip ph-bold ph-lg"></i> --> <!-- <i class="ph-paperclip ph-bold ph-lg"></i> -->
<span class="count">{{ clips.length }}</span> <span class="count">{{ clips.length }}</span>
{{ i18n.ts.clips }} {{ i18n.ts.clips }}
@ -64,26 +64,26 @@
</MkTab> </MkTab>
<MkNoteSub <MkNoteSub
v-if="directReplies && tab === 'replies'"
v-for="note in directReplies" v-for="note in directReplies"
v-if="directReplies && tab === 'replies'"
:key="note.id" :key="note.id"
:note="note" :note="note"
class="reply" class="reply"
:conversation="replies" :conversation="replies"
:detailedView="true" :detailed-view="true"
:parentId="note.id" :parent-id="note.id"
/> />
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" /> <MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
<MkNoteSub <MkNoteSub
v-if="directQuotes && tab === 'quotes'"
v-for="note in directQuotes" v-for="note in directQuotes"
v-if="directQuotes && tab === 'quotes'"
:key="note.id" :key="note.id"
:note="note" :note="note"
class="reply" class="reply"
:conversation="replies" :conversation="replies"
:detailedView="true" :detailed-view="true"
:parentId="note.id" :parent-id="note.id"
/> />
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" /> <MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
@ -94,8 +94,8 @@
:pagination="pagination" :pagination="pagination"
> --> > -->
<MkUserCardMini <MkUserCardMini
v-if="tab === 'renotes' && renotes"
v-for="item in renotes" v-for="item in renotes"
v-if="tab === 'renotes' && renotes"
:key="item.user.id" :key="item.user.id"
:user="item.user" :user="item.user"
:with-chart="false" :with-chart="false"
@ -151,11 +151,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, ref } from "vue"; import { onMounted, onUnmounted, onUpdated, ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import type { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
import MkTab from "@/components/MkTab.vue"; import MkTab from "@/components/MkTab.vue";
import MkNote from "@/components/MkNote.vue"; import MkNote from "@/components/MkNote.vue";
import MkNoteSub from "@/components/MkNoteSub.vue"; import MkNoteSub from "@/components/MkNoteSub.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue"; import type XRenoteButton from "@/components/MkRenoteButton.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue"; import MkUserCardMini from "@/components/MkUserCardMini.vue";
import MkReactedUsers from "@/components/MkReactedUsers.vue"; import MkReactedUsers from "@/components/MkReactedUsers.vue";
import { pleaseLogin } from "@/scripts/please-login"; import { pleaseLogin } from "@/scripts/please-login";
@ -170,16 +171,15 @@ import { getNoteMenu } from "@/scripts/get-note-menu";
import { useNoteCapture } from "@/scripts/use-note-capture"; import { useNoteCapture } from "@/scripts/use-note-capture";
import { deepClone } from "@/scripts/clone"; import { deepClone } from "@/scripts/clone";
import { stream } from "@/stream"; import { stream } from "@/stream";
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
pinned?: boolean; pinned?: boolean;
}>(); }>();
let tab = ref("replies"); const tab = ref("replies");
let note = ref(deepClone(props.note)); const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -214,12 +214,12 @@ const muted = ref(
); );
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
let conversation = ref<null | misskey.entities.Note[]>([]); const conversation = ref<null | misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]); const replies = ref<misskey.entities.Note[]>([]);
let directReplies = ref<null | misskey.entities.Note[]>([]); const directReplies = ref<null | misskey.entities.Note[]>([]);
let directQuotes = ref<null | misskey.entities.Note[]>([]); const directQuotes = ref<null | misskey.entities.Note[]>([]);
let clips = ref(); const clips = ref();
let renotes = ref(); const renotes = ref();
let isScrolling; let isScrolling;
const reactionsCount = Object.values(props.note.reactions).reduce( const reactionsCount = Object.values(props.note.reactions).reduce(
@ -238,7 +238,7 @@ const keymap = {
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,
note: note, note,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
@ -260,7 +260,7 @@ function react(viaKeyboard = false): void {
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: note.value.id, noteId: note.value.id,
reaction: reaction, reaction,
}); });
}, },
() => { () => {

View file

@ -49,7 +49,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import MkVisibility from "@/components/MkVisibility.vue"; import MkVisibility from "@/components/MkVisibility.vue";
@ -63,7 +62,7 @@ const props = defineProps<{
pinned?: boolean; pinned?: boolean;
}>(); }>();
let note = ref(props.note); const note = ref(props.note);
const showTicker = const showTicker =
defaultStore.state.instanceTicker === "always" || defaultStore.state.instanceTicker === "always" ||

View file

@ -1,6 +1,6 @@
<template> <template>
<div v-size="{ min: [350, 500] }" class="fefdfafb"> <div v-size="{ min: [350, 500] }" class="fefdfafb">
<MkAvatar class="avatar" :user="$i" disableLink /> <MkAvatar class="avatar" :user="$i" disable-link />
<div class="main"> <div class="main">
<div class="header"> <div class="header">
<MkUserName :user="$i" /> <MkUserName :user="$i" />
@ -11,7 +11,7 @@
:text="preprocess(text).trim()" :text="preprocess(text).trim()"
:author="$i" :author="$i"
:i="$i" :i="$i"
advancedMfm advanced-mfm
/> />
</div> </div>
</div> </div>

View file

@ -11,7 +11,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import XNoteHeader from "@/components/MkNoteHeader.vue"; import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue"; import MkSubNoteContent from "@/components/MkSubNoteContent.vue";

View file

@ -1,10 +1,10 @@
<template> <template>
<article <article
v-if="!muted.muted || muted.what === 'reply'" v-if="!muted.muted || muted.what === 'reply'"
:id="detailedView ? appearNote.id : null"
ref="el" ref="el"
v-size="{ max: [450, 500] }" v-size="{ max: [450, 500] }"
class="wrpstxzv" class="wrpstxzv"
:id="detailedView ? appearNote.id : null"
tabindex="-1" tabindex="-1"
:class="{ :class="{
children: depth > 1, children: depth > 1,
@ -16,8 +16,8 @@
<div v-if="conversation && depth > 1" class="line"></div> <div v-if="conversation && depth > 1" class="line"></div>
<div <div
class="main" class="main"
@click="noteClick"
:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }" :style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
@click="noteClick"
> >
<div class="avatar-container"> <div class="avatar-container">
<MkAvatar class="avatar" :user="appearNote.user" /> <MkAvatar class="avatar" :user="appearNote.user" />
@ -32,9 +32,9 @@
<MkSubNoteContent <MkSubNoteContent
class="text" class="text"
:note="note" :note="note"
:parentId="parentId" :parent-id="parentId"
:conversation="conversation" :conversation="conversation"
:detailedView="detailedView" :detailed-view="detailedView"
@focusfooter="footerEl.focus()" @focusfooter="footerEl.focus()"
/> />
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
@ -117,9 +117,9 @@
appearNote.myReaction != null appearNote.myReaction != null
" "
ref="reactButton" ref="reactButton"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
class="button _button reacted" class="button _button reacted"
@click.stop="undoReact(appearNote)" @click.stop="undoReact(appearNote)"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
> >
<i class="ph-minus ph-bold ph-lg"></i> <i class="ph-minus ph-bold ph-lg"></i>
</button> </button>
@ -137,17 +137,17 @@
</div> </div>
<template v-if="conversation"> <template v-if="conversation">
<MkNoteSub <MkNoteSub
v-if="replyLevel < 11 && depth < 5"
v-for="reply in replies" v-for="reply in replies"
v-if="replyLevel < 11 && depth < 5"
:key="reply.id" :key="reply.id"
:note="reply" :note="reply"
class="reply" class="reply"
:class="{ single: replies.length == 1 }" :class="{ single: replies.length == 1 }"
:conversation="conversation" :conversation="conversation"
:depth="replies.length == 1 ? depth : depth + 1" :depth="replies.length == 1 ? depth : depth + 1"
:replyLevel="replyLevel + 1" :reply-level="replyLevel + 1"
:parentId="appearNote.id" :parent-id="appearNote.id"
:detailedView="detailedView" :detailed-view="detailedView"
/> />
<div v-else-if="replies.length > 0" class="more"> <div v-else-if="replies.length > 0" class="more">
<div class="line"></div> <div class="line"></div>
@ -177,9 +177,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, ref, computed } from "vue"; import { computed, inject, ref } from "vue";
import type { Ref } from "vue"; import type { Ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import XNoteHeader from "@/components/MkNoteHeader.vue"; import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue"; import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue"; import XReactionsViewer from "@/components/MkReactionsViewer.vue";
@ -223,7 +223,7 @@ const props = withDefaults(
}, },
); );
let note = ref(deepClone(props.note)); const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -247,7 +247,7 @@ const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>(); const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>(); const reactButton = ref<HTMLElement>();
let appearNote = computed(() => const appearNote = computed(() =>
isRenote ? (note.value.renote as misskey.entities.Note) : note.value, isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
); );
const isDeleted = ref(false); const isDeleted = ref(false);
@ -291,7 +291,7 @@ function react(viaKeyboard = false): void {
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.value.id, noteId: appearNote.value.id,
reaction: reaction, reaction,
}); });
}, },
() => { () => {

View file

@ -12,7 +12,7 @@
</template> </template>
<template #default="{ items: notes }"> <template #default="{ items: notes }">
<div class="giivymft" :class="{ noGap }" ref="tlEl"> <div ref="tlEl" class="giivymft" :class="{ noGap }">
<XList <XList
ref="notes" ref="notes"
v-slot="{ item: note }" v-slot="{ item: note }"

View file

@ -219,7 +219,7 @@
<MkFollowButton <MkFollowButton
:user="notification.user" :user="notification.user"
:full="true" :full="true"
:hideMenu="true" :hide-menu="true"
/></div /></div
></span> ></span>
<span <span
@ -273,8 +273,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from "vue"; import { onMounted, onUnmounted, ref, watch } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import XReactionIcon from "@/components/MkReactionIcon.vue"; import XReactionIcon from "@/components/MkReactionIcon.vue";
import MkFollowButton from "@/components/MkFollowButton.vue"; import MkFollowButton from "@/components/MkFollowButton.vue";
import XReactionTooltip from "@/components/MkReactionTooltip.vue"; import XReactionTooltip from "@/components/MkReactionTooltip.vue";
@ -310,8 +310,7 @@ const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReact
? instance.defaultReaction ? instance.defaultReaction
: "⭐"; : "⭐";
let readObserver: IntersectionObserver | undefined; let readObserver: IntersectionObserver | undefined, connection;
let connection;
onMounted(() => { onMounted(() => {
if (!props.notification.isRead) { if (!props.notification.isRead) {

View file

@ -41,7 +41,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import {} from "vue";
import { notificationTypes } from "firefish-js"; import { notificationTypes } from "firefish-js";
import MkSwitch from "./form/switch.vue"; import MkSwitch from "./form/switch.vue";
import MkInfo from "./MkInfo.vue"; import MkInfo from "./MkInfo.vue";
@ -65,12 +64,12 @@ const props = withDefaults(
}, },
); );
let includingTypes = computed(() => props.includingTypes || []); const includingTypes = computed(() => props.includingTypes || []);
const dialog = ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
let typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({}); const typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
let useGlobalSetting = ref( const useGlobalSetting = ref(
(includingTypes.value === null || includingTypes.value.length === 0) && (includingTypes.value === null || includingTypes.value.length === 0) &&
props.showGlobalToggle, props.showGlobalToggle,
); );

View file

@ -28,7 +28,7 @@ const emit = defineEmits<{
}>(); }>();
const zIndex = os.claimZIndex("high"); const zIndex = os.claimZIndex("high");
let showing = ref(true); const showing = ref(true);
onMounted(() => { onMounted(() => {
window.setTimeout(() => { window.setTimeout(() => {

View file

@ -26,7 +26,7 @@
" "
:key="notification.id" :key="notification.id"
:note="notification.note" :note="notification.note"
:collapsedReply=" :collapsed-reply="
notification.type === 'reply' || notification.type === 'reply' ||
(notification.type === 'mention' && (notification.type === 'mention' &&
notification.note.replyId != null) notification.note.replyId != null)
@ -46,9 +46,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted, onMounted, computed, ref } from "vue"; import { computed, onMounted, onUnmounted, ref } from "vue";
import { notificationTypes } from "firefish-js"; import type { notificationTypes } from "firefish-js";
import MkPagination, { Paging } from "@/components/MkPagination.vue"; import type { Paging } from "@/components/MkPagination.vue";
import MkPagination from "@/components/MkPagination.vue";
import XNotification from "@/components/MkNotification.vue"; import XNotification from "@/components/MkNotification.vue";
import XList from "@/components/MkDateSeparatedList.vue"; import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue"; import XNote from "@/components/MkNote.vue";

View file

@ -8,8 +8,8 @@
:buttons-left="buttonsLeft" :buttons-left="buttonsLeft"
:buttons-right="buttonsRight" :buttons-right="buttonsRight"
:contextmenu="contextmenu" :contextmenu="contextmenu"
@closed="$emit('closed')"
class="page-window" class="page-window"
@closed="$emit('closed')"
> >
<template #header> <template #header>
<template v-if="pageMetadata?.value"> <template v-if="pageMetadata?.value">
@ -30,7 +30,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, provide, ref, computed } from "vue"; import type { ComputedRef } from "vue";
import { computed, provide, ref } from "vue";
import RouterView from "@/components/global/RouterView.vue"; import RouterView from "@/components/global/RouterView.vue";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import { popout as _popout } from "@/scripts/popout"; import { popout as _popout } from "@/scripts/popout";
@ -39,7 +40,8 @@ import { url } from "@/config";
import { mainRouter, routes } from "@/router"; import { mainRouter, routes } from "@/router";
import { Router } from "@/nirax"; import { Router } from "@/nirax";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata"; import type { PageMetadata } from "@/scripts/page-metadata";
import { provideMetadataReceiver } from "@/scripts/page-metadata";
const props = defineProps<{ const props = defineProps<{
initialPath: string; initialPath: string;
@ -51,8 +53,8 @@ defineEmits<{
const router = new Router(routes, props.initialPath); const router = new Router(routes, props.initialPath);
let pageMetadata = ref<null | ComputedRef<PageMetadata>>(); const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = ref<InstanceType<typeof XWindow>>(); const windowEl = ref<InstanceType<typeof XWindow>>();
const history = ref<{ path: string; key: any }[]>([ const history = ref<{ path: string; key: any }[]>([
{ {
path: router.getCurrentPath(), path: router.getCurrentPath(),

View file

@ -63,29 +63,22 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { import type { ComputedRef } from "vue";
computed, import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
ComputedRef, import type * as misskey from "firefish-js";
isRef,
onActivated,
onDeactivated,
ref,
watch,
} from "vue";
import * as misskey from "firefish-js";
import * as os from "@/os"; import * as os from "@/os";
import { import {
onScrollTop,
isTopVisible,
getScrollPosition,
getScrollContainer, getScrollContainer,
getScrollPosition,
isTopVisible,
onScrollTop,
} from "@/scripts/scroll"; } from "@/scripts/scroll";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
export type Paging< export interface Paging<
E extends keyof misskey.Endpoints = keyof misskey.Endpoints, E extends keyof misskey.Endpoints = keyof misskey.Endpoints,
> = { > {
endpoint: E; endpoint: E;
limit: number; limit: number;
params?: params?:
@ -104,7 +97,7 @@ export type Paging<
reversed?: boolean; reversed?: boolean;
offsetMode?: boolean; offsetMode?: boolean;
}; }
const SECOND_FETCH_LIMIT = 30; const SECOND_FETCH_LIMIT = 30;
@ -123,7 +116,10 @@ const emit = defineEmits<{
(ev: "queue", count: number): void; (ev: "queue", count: number): void;
}>(); }>();
type Item = { id: string; [another: string]: unknown }; interface Item {
id: string;
[another: string]: unknown;
}
const rootEl = ref<HTMLElement>(); const rootEl = ref<HTMLElement>();
const items = ref<Item[]>([]); const items = ref<Item[]>([]);
@ -207,12 +203,12 @@ const refresh = async (): void => {
}) })
.then( .then(
(res) => { (res) => {
let ids = items.value.reduce( const ids = items.value.reduce(
(a, b) => { (a, b) => {
a[b.id] = true; a[b.id] = true;
return a; return a;
}, },
{} as { [id: string]: boolean }, {} as Record<string, boolean>,
); );
for (let i = 0; i < res.length; i++) { for (let i = 0; i < res.length; i++) {
@ -364,7 +360,7 @@ const prepend = (item: Item): void => {
// //
if (items.value.length >= props.displayLimit) { if (items.value.length >= props.displayLimit) {
// Vue 3.2 // Vue 3.2
//items.value = items.value.slice(-props.displayLimit); // items.value = items.value.slice(-props.displayLimit);
while (items.value.length >= props.displayLimit) { while (items.value.length >= props.displayLimit) {
items.value.shift(); items.value.shift();
} }
@ -394,7 +390,7 @@ const prepend = (item: Item): void => {
// //
if (items.value.length >= props.displayLimit) { if (items.value.length >= props.displayLimit) {
// Vue 3.2 // Vue 3.2
//this.items = items.value.slice(0, props.displayLimit); // this.items = items.value.slice(0, props.displayLimit);
while (items.value.length >= props.displayLimit) { while (items.value.length >= props.displayLimit) {
items.value.pop(); items.value.pop();
} }

View file

@ -53,7 +53,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import { sum } from "@/scripts/array"; import { sum } from "@/scripts/array";
import { pleaseLogin } from "@/scripts/please-login"; import { pleaseLogin } from "@/scripts/please-login";
import * as os from "@/os"; import * as os from "@/os";

View file

@ -16,8 +16,8 @@
</MkInput> </MkInput>
<button <button
class="_button" class="_button"
@click="remove(i)"
:aria-label="i18n.t('remove')" :aria-label="i18n.t('remove')"
@click="remove(i)"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>

View file

@ -5,9 +5,9 @@
:z-priority="'high'" :z-priority="'high'"
:src="src" :src="src"
:transparent-bg="true" :transparent-bg="true"
tabindex="-1"
@click="modal?.close()" @click="modal?.close()"
@closed="emit('closed')" @closed="emit('closed')"
tabindex="-1"
> >
<MkMenu <MkMenu
:items="items" :items="items"
@ -28,7 +28,7 @@ import { ref } from "vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
defineProps<{ defineProps<{
items: MenuItem[]; items: MenuItem[];
@ -43,7 +43,7 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let modal = ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -86,8 +86,8 @@
{{ i18n.ts.quoteAttached {{ i18n.ts.quoteAttached
}}<button }}<button
class="_button" class="_button"
@click="quoteId = null"
:aria-label="i18n.t('removeQuote')" :aria-label="i18n.t('removeQuote')"
@click="quoteId = null"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
@ -99,8 +99,8 @@
<MkAcct :user="u" /> <MkAcct :user="u" />
<button <button
class="_button" class="_button"
@click="removeVisibleUser(u)"
:aria-label="i18n.t('removeRecipient')" :aria-label="i18n.t('removeRecipient')"
@click="removeVisibleUser(u)"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
@ -234,16 +234,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
computed,
defineAsyncComponent,
inject, inject,
watch,
nextTick, nextTick,
onMounted, onMounted,
defineAsyncComponent,
ref, ref,
computed, watch,
} from "vue"; } from "vue";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import autosize from "autosize"; import autosize from "autosize";
import insertTextAtCursor from "insert-text-at-cursor"; import insertTextAtCursor from "insert-text-at-cursor";
import { length } from "stringz"; import { length } from "stringz";
@ -315,40 +315,42 @@ const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = ref<HTMLInputElement | null>(null); const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = ref<HTMLElement | null>(null); const visibilityButton = ref<HTMLElement | null>(null);
let posting = ref(false); const posting = ref(false);
let text = ref(props.initialText ?? ""); const text = ref(props.initialText ?? "");
let files = ref(props.initialFiles ?? []); const files = ref(props.initialFiles ?? []);
let poll = ref<{ const poll = ref<{
choices: string[]; choices: string[];
multiple: boolean; multiple: boolean;
expiresAt: string | null; expiresAt: string | null;
expiredAfter: string | null; expiredAfter: string | null;
} | null>(null); } | null>(null);
let useCw = ref(false); const useCw = ref(false);
let showPreview = ref(false); const showPreview = ref(false);
let cw = ref<string | null>(null); const cw = ref<string | null>(null);
let localOnly = ref<boolean>( const localOnly = ref<boolean>(
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
? defaultStore.state.localOnly ? defaultStore.state.localOnly
: defaultStore.state.defaultNoteLocalOnly, : defaultStore.state.defaultNoteLocalOnly,
); );
let visibility = ref( const visibility = ref(
props.initialVisibility ?? props.initialVisibility ??
((defaultStore.state.rememberNoteVisibility ((defaultStore.state.rememberNoteVisibility
? defaultStore.state.visibility ? defaultStore.state.visibility
: defaultStore.state : defaultStore.state
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]), .defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
); );
let visibleUsers = ref([]); const visibleUsers = ref([]);
if (props.initialVisibleUsers) { if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser); props.initialVisibleUsers.forEach(pushVisibleUser);
} }
let autocomplete = ref(null); const autocomplete = ref(null);
let draghover = ref(false); const draghover = ref(false);
let quoteId = ref(null); const quoteId = ref(null);
let hasNotSpecifiedMentions = ref(false); const hasNotSpecifiedMentions = ref(false);
let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]")); const recentHashtags = ref(
let imeText = ref(""); JSON.parse(localStorage.getItem("hashtags") || "[]"),
);
const imeText = ref("");
const typing = throttle(3000, () => { const typing = throttle(3000, () => {
if (props.channel) { if (props.channel) {
@ -415,8 +417,8 @@ const maxTextLength = computed((): number => {
const canPost = computed((): boolean => { const canPost = computed((): boolean => {
return ( return (
!posting.value && !posting.value &&
(1 <= textLength.value || (textLength.value >= 1 ||
1 <= files.value.length || files.value.length >= 1 ||
!!poll.value || !!poll.value ||
!!props.renote) && !!props.renote) &&
textLength.value <= maxTextLength.value && textLength.value <= maxTextLength.value &&
@ -816,14 +818,14 @@ function onDrop(ev): void {
return; return;
} }
//#region // #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
files.value.push(file); files.value.push(file);
ev.preventDefault(); ev.preventDefault();
} }
//#endregion // #endregion
} }
function saveDraft() { function saveDraft() {
@ -896,7 +898,7 @@ async function post() {
} }
} }
let token = undefined; let token;
if (postAccount.value) { if (postAccount.value) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
@ -976,7 +978,7 @@ function showActions(ev) {
); );
} }
let postAccount = ref<misskey.entities.UserDetailed | null>(null); const postAccount = ref<misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) { function openAccountMenu(ev: MouseEvent) {
openAccountMenu_( openAccountMenu_(

View file

@ -8,9 +8,9 @@
delay-on-touch-only="true" delay-on-touch-only="true"
> >
<div <div
class="file"
v-for="element in _files" v-for="element in _files"
:key="element.id" :key="element.id"
class="file"
@click="showFileMenu(element, $event)" @click="showFileMenu(element, $event)"
@contextmenu.prevent="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)"
> >
@ -30,7 +30,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, ref, computed } from "vue"; import { computed, defineAsyncComponent, ref } from "vue";
import { VueDraggable } from "vue-draggable-plus"; import { VueDraggable } from "vue-draggable-plus";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue"; import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -105,10 +105,11 @@ async function describe(file) {
{ {
done: (result) => { done: (result) => {
if (!result || result.canceled) return; if (!result || result.canceled) return;
let comment = result.result.length === 0 ? null : result.result; const comment =
result.result.length === 0 ? null : result.result;
os.api("drive/files/update", { os.api("drive/files/update", {
fileId: file.id, fileId: file.id,
comment: comment, comment,
}).then(() => { }).then(() => {
file.comment = comment; file.comment = comment;
}); });

View file

@ -21,8 +21,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from "vue"; import { shallowRef } from "vue";
import {} from "vue"; import type * as misskey from "firefish-js";
import * as misskey from "firefish-js";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkPostForm from "@/components/MkPostForm.vue"; import MkPostForm from "@/components/MkPostForm.vue";
@ -48,8 +47,8 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let modal = shallowRef<InstanceType<typeof MkModal>>(); const modal = shallowRef<InstanceType<typeof MkModal>>();
let form = shallowRef<InstanceType<typeof MkPostForm>>(); const form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() { function onPosted() {
modal.value.close({ modal.value.close({

View file

@ -76,12 +76,12 @@ defineProps<{
}>(); }>();
// ServiceWorker registration // ServiceWorker registration
let registration = ref<ServiceWorkerRegistration | undefined>(); const registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification // If this browser supports push notification
let supported = ref(false); const supported = ref(false);
// If this browser has already subscribed to push notification // If this browser has already subscribed to push notification
let pushSubscription = ref<PushSubscription | null>(null); const pushSubscription = ref<PushSubscription | null>(null);
let pushRegistrationInServer = ref< const pushRegistrationInServer = ref<
| { | {
state?: string; state?: string;
key?: string; key?: string;
@ -209,6 +209,6 @@ if (navigator.serviceWorker == null) {
} }
defineExpose({ defineExpose({
pushRegistrationInServer: pushRegistrationInServer, pushRegistrationInServer,
}); });
</script> </script>

View file

@ -36,8 +36,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, watch, ref } from "vue"; import { onMounted, ref, watch } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import MkReactionIcon from "@/components/MkReactionIcon.vue"; import MkReactionIcon from "@/components/MkReactionIcon.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue"; import MkUserCardMini from "@/components/MkUserCardMini.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -46,10 +46,10 @@ const props = defineProps<{
noteId: misskey.entities.Note["id"]; noteId: misskey.entities.Note["id"];
}>(); }>();
let note = ref<misskey.entities.Note>(); const note = ref<misskey.entities.Note>();
let tab = ref<string>(); const tab = ref<string>();
let reactions = ref<string[]>(); const reactions = ref<string[]>();
let users = ref(); const users = ref();
watch(tab, async () => { watch(tab, async () => {
const res = await os.api("notes/reactions", { const res = await os.api("notes/reactions", {

View file

@ -22,7 +22,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import XDetails from "@/components/MkReactionsViewer.details.vue"; import XDetails from "@/components/MkReactionsViewer.details.vue";
import XReactionIcon from "@/components/MkReactionIcon.vue"; import XReactionIcon from "@/components/MkReactionIcon.vue";
import * as os from "@/os"; import * as os from "@/os";

View file

@ -18,7 +18,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import * as misskey from "firefish-js"; import type * as misskey from "firefish-js";
import { $i } from "@/account"; import { $i } from "@/account";
import XReaction from "@/components/MkReactionsViewer.reaction.vue"; import XReaction from "@/components/MkReactionsViewer.reaction.vue";

Some files were not shown because too many files have changed in this diff Show more