Merge branch 'develop' of https://firefish.dev/firefish/firefish into feat/note-edit-history
This commit is contained in:
commit
e8f178458f
133 changed files with 720 additions and 895 deletions
12
Makefile
12
Makefile
|
@ -1,7 +1,5 @@
|
||||||
ifneq (dev,$(wildcard config.env))
|
include ./dev/config.env
|
||||||
include ./dev/config.env
|
export
|
||||||
export
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: pre-commit
|
.PHONY: pre-commit
|
||||||
|
@ -29,13 +27,13 @@ build:
|
||||||
pnpm run migrate
|
pnpm run migrate
|
||||||
|
|
||||||
|
|
||||||
.PHONY: db.init db.up db.down
|
.PHONY: db.up db.down db.init
|
||||||
db.init:
|
|
||||||
$(MAKE) -C ./dev/db-container init
|
|
||||||
db.up:
|
db.up:
|
||||||
$(MAKE) -C ./dev/db-container up
|
$(MAKE) -C ./dev/db-container up
|
||||||
db.down:
|
db.down:
|
||||||
$(MAKE) -C ./dev/db-container down
|
$(MAKE) -C ./dev/db-container down
|
||||||
|
db.init:
|
||||||
|
$(MAKE) -C ./dev/db-container init
|
||||||
|
|
||||||
.PHONY: psql redis-cli
|
.PHONY: psql redis-cli
|
||||||
psql:
|
psql:
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
.PHONY: init up down
|
.PHONY: up down init
|
||||||
init: down up
|
|
||||||
up:
|
up:
|
||||||
$(COMPOSE) up --detach
|
$(COMPOSE) up --detach
|
||||||
down:
|
down:
|
||||||
$(COMPOSE) down
|
$(COMPOSE) down
|
||||||
|
init:
|
||||||
|
$(COMPOSE) down --volumes
|
||||||
|
$(COMPOSE) up --detach
|
||||||
|
|
||||||
.PHONY: psql redis-cli
|
.PHONY: psql redis-cli
|
||||||
psql:
|
psql:
|
||||||
|
|
|
@ -5,6 +5,8 @@ services:
|
||||||
image: docker.io/redis:7-alpine
|
image: docker.io/redis:7-alpine
|
||||||
ports:
|
ports:
|
||||||
- "26379:6379"
|
- "26379:6379"
|
||||||
|
volumes:
|
||||||
|
- "redis-data:/data"
|
||||||
db:
|
db:
|
||||||
image: docker.io/groonga/pgroonga:3.1.8-alpine-12
|
image: docker.io/groonga/pgroonga:3.1.8-alpine-12
|
||||||
env_file:
|
env_file:
|
||||||
|
@ -13,3 +15,10 @@ services:
|
||||||
- "25432:5432"
|
- "25432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- "./install.sql:/docker-entrypoint-initdb.d/install.sql:ro"
|
- "./install.sql:/docker-entrypoint-initdb.d/install.sql:ro"
|
||||||
|
- "postgres-data:/var/lib/postgresql/data"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
|
name: redis-data
|
||||||
|
postgres-data:
|
||||||
|
name: postgres-data
|
||||||
|
|
|
@ -5,6 +5,15 @@ Critical security updates are indicated by the :warning: icon.
|
||||||
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
|
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
|
||||||
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
|
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
|
||||||
|
|
||||||
|
## [v20240401](https://firefish.dev/firefish/firefish/-/merge_requests/10724/commits)
|
||||||
|
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
|
## :warning: [v20240330](https://firefish.dev/firefish/firefish/-/merge_requests/10719/commits)
|
||||||
|
|
||||||
|
- Fix bugs (including a critical security issue)
|
||||||
|
- We are very thankful to Oneric (the reporter of the security issue) and Laura Hausmann (Iceshrimp maintainer) for kindly and securely sharing the information to fix the issue.
|
||||||
|
|
||||||
## [v20240326](https://firefish.dev/firefish/firefish/-/merge_requests/10713/commits)
|
## [v20240326](https://firefish.dev/firefish/firefish/-/merge_requests/10713/commits)
|
||||||
|
|
||||||
- Fix bugs
|
- Fix bugs
|
||||||
|
|
|
@ -33,6 +33,13 @@ docker pull registry.firefish.dev/firefish/firefish:latest
|
||||||
# or podman pull registry.firefish.dev/firefish/firefish:latest
|
# or podman pull registry.firefish.dev/firefish/firefish:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Enable database extension
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker-compose up db --detach && sleep 5 && docker-compose exec db sh -c 'psql --user="${POSTGRES_USER}" --dbname="${POSTGRES_DB}" --command="CREATE EXTENSION pgroonga;"'
|
||||||
|
# or podman-compose up db --detach && sleep 5 && podman-compose exec db sh -c 'psql --user="${POSTGRES_USER}" --dbname="${POSTGRES_DB}" --command="CREATE EXTENSION pgroonga;"'
|
||||||
|
```
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -35,7 +35,7 @@ There are official installation instructions for many operating systems on <http
|
||||||
|
|
||||||
##### Ubuntu
|
##### Ubuntu
|
||||||
|
|
||||||
1. Add apt repository
|
1. Install subdependencies and add apt repository
|
||||||
```sh
|
```sh
|
||||||
sudo apt install -y software-properties-common
|
sudo apt install -y software-properties-common
|
||||||
sudo add-apt-repository -y universe
|
sudo add-apt-repository -y universe
|
||||||
|
@ -226,7 +226,7 @@ A new setting item has been added to control the log levels, so please consider
|
||||||
### For systemd/pm2 users
|
### For systemd/pm2 users
|
||||||
|
|
||||||
- Required Rust version has been bumped from v1.68 to v1.70.
|
- Required Rust version has been bumped from v1.68 to v1.70.
|
||||||
- `libvips` is no longer required (unless your server os is *BSD), so you may uninstall it from your system. Make sure to execute the following commands after that:
|
- `libvips` is no longer required (unless your server OS is *BSD), so you may uninstall it from your system. Make sure to execute the following commands after that:
|
||||||
```sh
|
```sh
|
||||||
pnpm clean-npm
|
pnpm clean-npm
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|
|
@ -21,8 +21,8 @@ searchUser: Търсене на потребител
|
||||||
reply: Отговор
|
reply: Отговор
|
||||||
showMore: Покажи още
|
showMore: Покажи още
|
||||||
loadMore: Зареди още
|
loadMore: Зареди още
|
||||||
followRequestAccepted: Заявка за последване приета
|
followRequestAccepted: Заявката за последване е приета
|
||||||
importAndExport: Импорт/Експорт на Данни
|
importAndExport: Импорт/експорт на данни
|
||||||
import: Импортиране
|
import: Импортиране
|
||||||
download: Изтегляне
|
download: Изтегляне
|
||||||
export: Експортиране
|
export: Експортиране
|
||||||
|
@ -47,7 +47,7 @@ groups: Групи
|
||||||
incorrectPassword: Грешна парола.
|
incorrectPassword: Грешна парола.
|
||||||
leaveGroup: Напускане на групата
|
leaveGroup: Напускане на групата
|
||||||
numberOfColumn: Брой колони
|
numberOfColumn: Брой колони
|
||||||
passwordLessLogin: Вход без парола
|
passwordLessLogin: Влизане без парола
|
||||||
newPasswordRetype: Повтори новата парола
|
newPasswordRetype: Повтори новата парола
|
||||||
saveAs: Запазване като...
|
saveAs: Запазване като...
|
||||||
resetPassword: Нулиране на паролата
|
resetPassword: Нулиране на паролата
|
||||||
|
@ -56,13 +56,13 @@ inputNewFolderName: Въведи ново име на папка
|
||||||
upload: Качване
|
upload: Качване
|
||||||
retypedNotMatch: Въвежданията не съвпадат.
|
retypedNotMatch: Въвежданията не съвпадат.
|
||||||
_ago:
|
_ago:
|
||||||
weeksAgo: преди {n}сед
|
weeksAgo: пр. {n}сед
|
||||||
secondsAgo: преди {n}сек
|
secondsAgo: пр. {n}сек
|
||||||
hoursAgo: преди {n}ч
|
hoursAgo: пр. {n}ч
|
||||||
minutesAgo: преди {n}мин
|
minutesAgo: пр. {n}мин
|
||||||
daysAgo: преди {n}д
|
daysAgo: пр. {n}д
|
||||||
monthsAgo: преди {n}мес
|
monthsAgo: пр. {n}мес
|
||||||
yearsAgo: преди {n}г
|
yearsAgo: пр. {n}г
|
||||||
future: Бъдеще
|
future: Бъдеще
|
||||||
justNow: Току-що
|
justNow: Току-що
|
||||||
folderName: Име на папка
|
folderName: Име на папка
|
||||||
|
@ -101,6 +101,8 @@ _profile:
|
||||||
metadataLabel: Етикет
|
metadataLabel: Етикет
|
||||||
metadataEdit: Редактиране на допълнителната информация
|
metadataEdit: Редактиране на допълнителната информация
|
||||||
changeAvatar: Промяна на профилната снимка
|
changeAvatar: Промяна на профилната снимка
|
||||||
|
youCanIncludeHashtags: Можеш също да включиш хаштагове в биографията си.
|
||||||
|
changeBanner: Промяна на банера
|
||||||
addAccount: Добавяне на акаунт
|
addAccount: Добавяне на акаунт
|
||||||
followRequestPending: Заявка за последване в изчакване
|
followRequestPending: Заявка за последване в изчакване
|
||||||
signinHistory: История на вписванията
|
signinHistory: История на вписванията
|
||||||
|
@ -108,7 +110,7 @@ or: Или
|
||||||
noUsers: Няма потребители
|
noUsers: Няма потребители
|
||||||
notes: Публикации
|
notes: Публикации
|
||||||
newNoteRecived: Има нови публикации
|
newNoteRecived: Има нови публикации
|
||||||
note: Публикуване
|
note: Публикация
|
||||||
instanceFollowing: Последвани на сървъра
|
instanceFollowing: Последвани на сървъра
|
||||||
_filters:
|
_filters:
|
||||||
followersOnly: Само последователи
|
followersOnly: Само последователи
|
||||||
|
@ -170,7 +172,7 @@ renotesCount: Брой изпратени подсилвания
|
||||||
license: Лиценз
|
license: Лиценз
|
||||||
lastUsedDate: Последно използвано на
|
lastUsedDate: Последно използвано на
|
||||||
rename: Преименуване
|
rename: Преименуване
|
||||||
customEmojis: Персонализирани емоджита
|
customEmojis: Персон. емоджита
|
||||||
emoji: Емоджи
|
emoji: Емоджи
|
||||||
_aboutFirefish:
|
_aboutFirefish:
|
||||||
translation: Преведи Firefish
|
translation: Преведи Firefish
|
||||||
|
@ -237,6 +239,7 @@ _theme:
|
||||||
installedThemes: Инсталирани теми
|
installedThemes: Инсталирани теми
|
||||||
constant: Константа
|
constant: Константа
|
||||||
addConstant: Добавяне на константа
|
addConstant: Добавяне на константа
|
||||||
|
make: Направа на тема
|
||||||
_pages:
|
_pages:
|
||||||
script:
|
script:
|
||||||
blocks:
|
blocks:
|
||||||
|
@ -369,8 +372,8 @@ enterUsername: Въведи потребителско име
|
||||||
renotedBy: Подсилено от {user}
|
renotedBy: Подсилено от {user}
|
||||||
noNotifications: Няма известия
|
noNotifications: Няма известия
|
||||||
instance: Сървър
|
instance: Сървър
|
||||||
basicSettings: Основни Настройки
|
basicSettings: Основни настройки
|
||||||
otherSettings: Други Настройки
|
otherSettings: Други настройки
|
||||||
openInWindow: Отваряне в прозорец
|
openInWindow: Отваряне в прозорец
|
||||||
profile: Профил
|
profile: Профил
|
||||||
timeline: Инфопоток
|
timeline: Инфопоток
|
||||||
|
@ -408,7 +411,7 @@ following: Последвани
|
||||||
followsYou: Следва те
|
followsYou: Следва те
|
||||||
createList: Създаване на списък
|
createList: Създаване на списък
|
||||||
error: Грешка
|
error: Грешка
|
||||||
manageLists: Управление на списъци
|
manageLists: Управление на списъците
|
||||||
retry: Повторен опит
|
retry: Повторен опит
|
||||||
follow: Последване
|
follow: Последване
|
||||||
followRequest: Заявка за последване
|
followRequest: Заявка за последване
|
||||||
|
@ -422,7 +425,7 @@ enterEmoji: Въведи емоджи
|
||||||
sensitive: Деликатно
|
sensitive: Деликатно
|
||||||
add: Добавяне
|
add: Добавяне
|
||||||
pinned: Закачено в профила
|
pinned: Закачено в профила
|
||||||
quote: Цитиране
|
quote: Цитат
|
||||||
pinnedNote: Закачена публикация
|
pinnedNote: Закачена публикация
|
||||||
cantReRenote: Подсилване не може да бъде подсилено.
|
cantReRenote: Подсилване не може да бъде подсилено.
|
||||||
clickToShow: Щракни за показване
|
clickToShow: Щракни за показване
|
||||||
|
@ -555,7 +558,7 @@ _visibility:
|
||||||
followers: Последователи
|
followers: Последователи
|
||||||
specified: Директна
|
specified: Директна
|
||||||
localOnly: Само местни
|
localOnly: Само местни
|
||||||
public: Публична
|
public: Общодостъпна
|
||||||
publicDescription: Публикацията ще бъде видима във всички публични инфопотоци
|
publicDescription: Публикацията ще бъде видима във всички публични инфопотоци
|
||||||
home: Скрита
|
home: Скрита
|
||||||
localOnlyDescription: Не е видима за отдалечени потребители
|
localOnlyDescription: Не е видима за отдалечени потребители
|
||||||
|
@ -638,7 +641,7 @@ _preferencesBackups:
|
||||||
updatedAt: 'Обновено на: {date} {time}'
|
updatedAt: 'Обновено на: {date} {time}'
|
||||||
editWidgetsExit: Готово
|
editWidgetsExit: Готово
|
||||||
done: Готово
|
done: Готово
|
||||||
emailRequiredForSignup: Изискване на адрес на ел. поща за регистриране
|
emailRequiredForSignup: Изискване на адрес за ел. поща за регистриране
|
||||||
preview: Преглед
|
preview: Преглед
|
||||||
privacy: Поверителност
|
privacy: Поверителност
|
||||||
about: Относно
|
about: Относно
|
||||||
|
@ -665,14 +668,14 @@ imageUrl: URL адрес на изображение
|
||||||
announcements: Оповестявания
|
announcements: Оповестявания
|
||||||
removeAreYouSure: Сигурни ли сте, че искате да премахнете "{x}"?
|
removeAreYouSure: Сигурни ли сте, че искате да премахнете "{x}"?
|
||||||
fromUrl: От URL адрес
|
fromUrl: От URL адрес
|
||||||
manageGroups: Управление на групи
|
manageGroups: Управление на групите
|
||||||
nUsersRead: прочетено от {n}
|
nUsersRead: прочетено от {n}
|
||||||
home: Начало
|
home: Начало
|
||||||
registeredDate: Присъединяване
|
registeredDate: Присъединяване
|
||||||
avatar: Профилна снимка
|
avatar: Профилна снимка
|
||||||
watch: Наблюдаване
|
watch: Наблюдаване
|
||||||
antennas: Антени
|
antennas: Антени
|
||||||
manageAntennas: Управление на антени
|
manageAntennas: Управление на антените
|
||||||
popularTags: Популярни тагове
|
popularTags: Популярни тагове
|
||||||
cacheClear: Изчистване на кеша
|
cacheClear: Изчистване на кеша
|
||||||
groupName: Име на групата
|
groupName: Име на групата
|
||||||
|
@ -700,7 +703,7 @@ gallery: Галерия
|
||||||
priority: Приоритет
|
priority: Приоритет
|
||||||
unread: Непрочетени
|
unread: Непрочетени
|
||||||
filter: Филтриране
|
filter: Филтриране
|
||||||
manageAccounts: Управление на акаунти
|
manageAccounts: Управление на акаунтите
|
||||||
deleteAccount: Изтриване на акаунта
|
deleteAccount: Изтриване на акаунта
|
||||||
fast: Бърза
|
fast: Бърза
|
||||||
remoteOnly: Само отдалечени
|
remoteOnly: Само отдалечени
|
||||||
|
@ -785,6 +788,9 @@ _menuDisplay:
|
||||||
hide: Скриване
|
hide: Скриване
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: Всички публикации
|
allNotes: Всички публикации
|
||||||
|
followingList: Следвани потребители
|
||||||
|
blockingList: Блокирани потребители
|
||||||
|
muteList: Заглушени потребители
|
||||||
exploreFediverse: Разглеждане на Федивселената
|
exploreFediverse: Разглеждане на Федивселената
|
||||||
recentlyUpdatedUsers: Последно активни потребители
|
recentlyUpdatedUsers: Последно активни потребители
|
||||||
uiLanguage: Език на потребителския интерфейс
|
uiLanguage: Език на потребителския интерфейс
|
||||||
|
@ -793,7 +799,7 @@ tags: Тагове
|
||||||
youHaveNoGroups: Нямаш групи
|
youHaveNoGroups: Нямаш групи
|
||||||
accessibility: Достъпност
|
accessibility: Достъпност
|
||||||
email: Ел. поща
|
email: Ел. поща
|
||||||
emailAddress: Адрес на ел. поща
|
emailAddress: Адрес за ел. поща
|
||||||
addItem: Добавяне на елемент
|
addItem: Добавяне на елемент
|
||||||
visibility: Видимост
|
visibility: Видимост
|
||||||
description: Описание
|
description: Описание
|
||||||
|
@ -867,3 +873,63 @@ unblockConfirm: Сигурни ли сте, че искате да отблок
|
||||||
followConfirm: Сигурни ли сте, че искате да последвате {name}?
|
followConfirm: Сигурни ли сте, че искате да последвате {name}?
|
||||||
accountMoved: 'Потребителят се премести на нов акаунт:'
|
accountMoved: 'Потребителят се премести на нов акаунт:'
|
||||||
inputNewDescription: Въведете ново описание
|
inputNewDescription: Въведете ново описание
|
||||||
|
tos: Условия за ползване
|
||||||
|
agreeTo: Съгласен съм с {0}
|
||||||
|
withFileAntenna: Само публикации с файлове
|
||||||
|
updateRemoteUser: Обновяване на инфо. за отдалечения потребител
|
||||||
|
receiveAnnouncementFromInstance: Получаване на известия от този сървър
|
||||||
|
userPagePinTip: Можеш да показваш публикации тук, като избереш "Закачане в профила"
|
||||||
|
от менюто на отделните публикации.
|
||||||
|
_ffVisibility:
|
||||||
|
public: Общодостъпна
|
||||||
|
private: Частна
|
||||||
|
followers: Видима само за последователи
|
||||||
|
_charts:
|
||||||
|
activeUsers: Дейни потребители
|
||||||
|
edit: Редактиране
|
||||||
|
toReply: Отговаряне
|
||||||
|
toPost: Публикуване
|
||||||
|
toQuote: Цитиране
|
||||||
|
charts: Диаграми
|
||||||
|
disablePagesScript: Изключване на AiScript в Страниците
|
||||||
|
updatedAt: Обновено на
|
||||||
|
privateDescription: Видима само за теб
|
||||||
|
enableTimelineStreaming: Автоматично обновяване на инфопотоците
|
||||||
|
toEdit: Редактиране
|
||||||
|
showEmojisInReactionNotifications: Показване на емоджита в известията за реакции
|
||||||
|
rememberNoteVisibility: Запомняне на настройките за видимост на публикациите
|
||||||
|
drive: Диск
|
||||||
|
banner: Банер
|
||||||
|
public: Общодостъпна
|
||||||
|
makeExplorable: Акаунтът да е видим в "Разглеждане"
|
||||||
|
hideOnlineStatus: Скриване на онлайн състоянието
|
||||||
|
customCss: Персонализиран CSS
|
||||||
|
keepCw: Запазване на предупрежденията за съдържание
|
||||||
|
makeReactionsPublic: Историята на реакциите да е общодостъпна
|
||||||
|
noEmailServerWarning: Сървърът за ел. поща не е конфигуриран.
|
||||||
|
languageForTranslation: Език за превеждане на публикации
|
||||||
|
private: Частна
|
||||||
|
replies: Отговори
|
||||||
|
wordMute: Заглушаване на думи и езици
|
||||||
|
instanceMute: Заглушаване на сървъри
|
||||||
|
notificationSettingDesc: Избиране на какви известия да се показват.
|
||||||
|
preventAiLearning: Предотвратяване на ИИ scraping
|
||||||
|
indexable: Индексируем
|
||||||
|
showPreviewByDefault: Показване на преглед при публикуване по подразбиране
|
||||||
|
showNoAltTextWarning: Показване на предупреждение при опит за публикуване на файлове
|
||||||
|
без описание
|
||||||
|
makeFollowManuallyApprove: Заявките за последване да изискват одобряване
|
||||||
|
enableEmojiReactions: Включване на реакциите с емоджи
|
||||||
|
autoAcceptFollowed: Автоматично одобряване на заявките за последване от последвани
|
||||||
|
потребители
|
||||||
|
expandOnNoteClick: Отваряне на публикацията при кликване
|
||||||
|
enableInfiniteScroll: Автоматично зареждане на повече
|
||||||
|
noCrawle: Предотвратяване на индексирането от търсачки
|
||||||
|
misskeyUpdated: Firefish бе обновен!
|
||||||
|
emailNotConfiguredWarning: Адресът за ел. поща не е зададен.
|
||||||
|
notificationSetting: Настройки за известията
|
||||||
|
emailNotification: Известия по ел. поща
|
||||||
|
clientSettings: Настройки за устройството
|
||||||
|
behavior: Поведение
|
||||||
|
detectPostLanguage: Автоматично откриване на езика и показване на бутон за превеждане
|
||||||
|
за публикации на чужди езици
|
||||||
|
|
|
@ -856,8 +856,8 @@ more: Més!
|
||||||
featured: Destacat
|
featured: Destacat
|
||||||
usernameOrUserId: Nom o ID d'usuari
|
usernameOrUserId: Nom o ID d'usuari
|
||||||
noSuchUser: No s'ha trobat l'usuari
|
noSuchUser: No s'ha trobat l'usuari
|
||||||
lookup: Cerca
|
lookup: Ves a
|
||||||
attachFile: Afegeix un fitxer
|
attachFile: Afegeix fitxers
|
||||||
currentPassword: Contrasenya actual
|
currentPassword: Contrasenya actual
|
||||||
newPassword: Nova contrasenya
|
newPassword: Nova contrasenya
|
||||||
announcements: Avisos
|
announcements: Avisos
|
||||||
|
@ -2210,7 +2210,7 @@ squareCatAvatars: Mostrar avatars quadrats per a comptes de gats
|
||||||
replaceChatButtonWithAccountButton: Canviar el botó de xats amb el botó de canvi de
|
replaceChatButtonWithAccountButton: Canviar el botó de xats amb el botó de canvi de
|
||||||
compte
|
compte
|
||||||
replaceWidgetsButtonWithReloadButton: Canviar el botó de ginys amb el botó de recarregar
|
replaceWidgetsButtonWithReloadButton: Canviar el botó de ginys amb el botó de recarregar
|
||||||
searchEngine: Motor de cerca fet servir anla barra MFM
|
searchEngine: Motor de cerca fet servir a la barra MFM
|
||||||
postSearch: Posar els resultats en aquest servidor
|
postSearch: Posar els resultats en aquest servidor
|
||||||
showBigPostButton: Mostrar un botó gegant al formulari de publicació
|
showBigPostButton: Mostrar un botó gegant al formulari de publicació
|
||||||
_emojiModPerm:
|
_emojiModPerm:
|
||||||
|
@ -2238,7 +2238,7 @@ enableTimelineStreaming: Actualitza les línies de temps automàticament
|
||||||
enablePullToRefresh: Activa "Baixa per actualitzar"
|
enablePullToRefresh: Activa "Baixa per actualitzar"
|
||||||
pullDownToReload: Baixa per actualitzar
|
pullDownToReload: Baixa per actualitzar
|
||||||
pullToRefreshThreshold: Distancia de baixada per actualitzar
|
pullToRefreshThreshold: Distancia de baixada per actualitzar
|
||||||
searchWords: Paraules / ID o adreça a URL a buscar
|
searchWords: Paraules / ID o adreça a URL a cercar
|
||||||
noSentFollowRequests: No tens cap sol·licitud de seguiment enviada
|
noSentFollowRequests: No tens cap sol·licitud de seguiment enviada
|
||||||
sentFollowRequests: Enviar sol·licituds de seguiment
|
sentFollowRequests: Enviar sol·licituds de seguiment
|
||||||
replyMute: Silencia les respostes a les línies de temps
|
replyMute: Silencia les respostes a les línies de temps
|
||||||
|
@ -2251,7 +2251,7 @@ searchWordsDescription: "Per cercar publicacions, escriu el terme a buscar. Sepa
|
||||||
Si vols cercar per una seqüencia de paraules (per exemple una frase) has d'escriure-les
|
Si vols cercar per una seqüencia de paraules (per exemple una frase) has d'escriure-les
|
||||||
entre cometes dobles, per no fer una cerca amb condicionant AND: \"Avui he aprés\"\
|
entre cometes dobles, per no fer una cerca amb condicionant AND: \"Avui he aprés\"\
|
||||||
\n \nSi vols anar a una pàgina d'usuari o publicació en concret, escriu la adreça
|
\n \nSi vols anar a una pàgina d'usuari o publicació en concret, escriu la adreça
|
||||||
URL o la ID en aquest camp i fes clic al botó 'Trobar'. Fent clic a 'Cercar' trobarà
|
URL o la ID en aquest camp i fes clic al botó 'Ves a'. Fent clic a 'Cerca' trobarà
|
||||||
publicacions que, literalment , continguin la ID/adreça URL."
|
publicacions que, literalment , continguin la ID/adreça URL."
|
||||||
searchPostsWithFiles: Només publicacions amb fitxers
|
searchPostsWithFiles: Només publicacions amb fitxers
|
||||||
searchCwAndAlt: Inclou avisos de contingut i arxius amb descripcions
|
searchCwAndAlt: Inclou avisos de contingut i arxius amb descripcions
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "firefish",
|
"name": "firefish",
|
||||||
"version": "20240326",
|
"version": "20240401",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://firefish.dev/firefish/firefish.git"
|
"url": "https://firefish.dev/firefish/firefish.git"
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"watch": "pnpm run dev",
|
"watch": "pnpm run dev",
|
||||||
"dev": "pnpm node ./scripts/dev.mjs",
|
"dev": "pnpm node ./scripts/dev.mjs",
|
||||||
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
|
"dev:staging": "NODE_OPTIONS=--max_old_space_size=3072 NODE_ENV=development pnpm run build && pnpm run start",
|
||||||
"lint": "pnpm -r --parallel run lint",
|
"lint": "pnpm --filter !megalodon --filter !firefish-js -r --parallel run lint",
|
||||||
"debug": "pnpm run build:debug && pnpm run start",
|
"debug": "pnpm run build:debug && pnpm run start",
|
||||||
"build:debug": "pnpm run clean && pnpm node ./scripts/dev-build.mjs && pnpm run gulp",
|
"build:debug": "pnpm run clean && pnpm node ./scripts/dev-build.mjs && pnpm run gulp",
|
||||||
"mocha": "pnpm --filter backend run mocha",
|
"mocha": "pnpm --filter backend run mocha",
|
||||||
|
|
|
@ -4,7 +4,6 @@ Cargo.lock
|
||||||
.github
|
.github
|
||||||
npm
|
npm
|
||||||
.eslintrc
|
.eslintrc
|
||||||
.prettierignore
|
|
||||||
rustfmt.toml
|
rustfmt.toml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
*.node
|
*.node
|
||||||
|
|
|
@ -8,10 +8,15 @@ import chalk from "chalk";
|
||||||
import Logger from "@/services/logger.js";
|
import Logger from "@/services/logger.js";
|
||||||
import IPCIDR from "ip-cidr";
|
import IPCIDR from "ip-cidr";
|
||||||
import PrivateIp from "private-ip";
|
import PrivateIp from "private-ip";
|
||||||
|
import { isValidUrl } from "./is-valid-url.js";
|
||||||
|
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
export async function downloadUrl(url: string, path: string): Promise<void> {
|
export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
|
if (!isValidUrl(url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
const logger = new Logger("download");
|
const logger = new Logger("download");
|
||||||
|
|
||||||
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||||
|
@ -44,6 +49,12 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
limit: 0,
|
limit: 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.on("redirect", (res: Got.Response, opts: Got.NormalizedOptions) => {
|
||||||
|
if (!isValidUrl(opts.url)) {
|
||||||
|
logger.warn(`Invalid URL: ${opts.url}`);
|
||||||
|
req.destroy();
|
||||||
|
}
|
||||||
|
})
|
||||||
.on("response", (res: Got.Response) => {
|
.on("response", (res: Got.Response) => {
|
||||||
if (
|
if (
|
||||||
(process.env.NODE_ENV === "production" ||
|
(process.env.NODE_ENV === "production" ||
|
||||||
|
|
|
@ -2,9 +2,10 @@ import * as http from "node:http";
|
||||||
import * as https from "node:https";
|
import * as https from "node:https";
|
||||||
import type { URL } from "node:url";
|
import type { URL } from "node:url";
|
||||||
import CacheableLookup from "cacheable-lookup";
|
import CacheableLookup from "cacheable-lookup";
|
||||||
import fetch from "node-fetch";
|
import fetch, { RequestRedirect } from "node-fetch";
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
|
import { isValidUrl } from "./is-valid-url.js";
|
||||||
|
|
||||||
export async function getJson(
|
export async function getJson(
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -57,7 +58,12 @@ export async function getResponse(args: {
|
||||||
headers: Record<string, string>;
|
headers: Record<string, string>;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
redirect?: RequestRedirect;
|
||||||
}) {
|
}) {
|
||||||
|
if (!isValidUrl(args.url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
const timeout = args.timeout || 10 * 1000;
|
const timeout = args.timeout || 10 * 1000;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
@ -73,8 +79,16 @@ export async function getResponse(args: {
|
||||||
size: args.size || 10 * 1024 * 1024,
|
size: args.size || 10 * 1024 * 1024,
|
||||||
agent: getAgentByUrl,
|
agent: getAgentByUrl,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
|
redirect: args.redirect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (args.redirect === "manual" && [301, 302, 307, 308].includes(res.status)) {
|
||||||
|
if (!isValidUrl(res.url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new StatusError(
|
throw new StatusError(
|
||||||
`${res.status} ${res.statusText}`,
|
`${res.status} ${res.statusText}`,
|
||||||
|
|
20
packages/backend/src/misc/is-valid-url.ts
Normal file
20
packages/backend/src/misc/is-valid-url.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
export function isValidUrl(url: string | URL | undefined): boolean {
|
||||||
|
if (process.env.NODE_ENV !== "production") return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (url == null) return false;
|
||||||
|
|
||||||
|
const u = typeof url === "string" ? new URL(url) : url;
|
||||||
|
if (!u.protocol.match(/^https?:$/) || u.hostname === "unix") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.port !== "" && !["80", "443"].includes(u.port)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import { getUserKeypair } from "@/misc/keypair-store.js";
|
import { getUserKeypair } from "@/misc/keypair-store.js";
|
||||||
import type { User, ILocalUser } from "@/models/entities/user.js";
|
import type { User, ILocalUser } from "@/models/entities/user.js";
|
||||||
import { getResponse } from "@/misc/fetch.js";
|
import { StatusError, getResponse } from "@/misc/fetch.js";
|
||||||
import { createSignedPost, createSignedGet } from "./ap-request.js";
|
import { createSignedPost, createSignedGet } from "./ap-request.js";
|
||||||
import type { Response } from "node-fetch";
|
import type { Response } from "node-fetch";
|
||||||
import type { IObject } from "./type.js";
|
import type { IObject } from "./type.js";
|
||||||
|
import { isValidUrl } from "@/misc/is-valid-url.js";
|
||||||
|
import { apLogger } from "@/remote/activitypub/logger.js";
|
||||||
|
|
||||||
export default async (user: { id: User["id"] }, url: string, object: any) => {
|
export default async (user: { id: User["id"] }, url: string, object: any) => {
|
||||||
const body = JSON.stringify(object);
|
const body = JSON.stringify(object);
|
||||||
|
@ -33,10 +35,19 @@ export default async (user: { id: User["id"] }, url: string, object: any) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ActivityPub object
|
* Get ActivityPub object
|
||||||
* @param user http-signature user
|
|
||||||
* @param url URL to fetch
|
* @param url URL to fetch
|
||||||
|
* @param user http-signature user
|
||||||
|
* @param redirects whether or not to accept redirects
|
||||||
*/
|
*/
|
||||||
export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
export async function apGet(
|
||||||
|
url: string,
|
||||||
|
user?: ILocalUser,
|
||||||
|
redirects: boolean = true,
|
||||||
|
): Promise<{ finalUrl: string; content: IObject }> {
|
||||||
|
if (!isValidUrl(url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
let res: Response;
|
let res: Response;
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -56,7 +67,16 @@ export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
||||||
url,
|
url,
|
||||||
method: req.request.method,
|
method: req.request.method,
|
||||||
headers: req.request.headers,
|
headers: req.request.headers,
|
||||||
|
redirect: redirects ? "manual" : "error",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||||
|
const newUrl = res.headers.get("location");
|
||||||
|
if (newUrl == null)
|
||||||
|
throw new Error("apGet got redirect but no target location");
|
||||||
|
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||||
|
return apGet(newUrl, user, false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res = await getResponse({
|
res = await getResponse({
|
||||||
url,
|
url,
|
||||||
|
@ -66,12 +86,23 @@ export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
||||||
'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
"User-Agent": config.userAgent,
|
"User-Agent": config.userAgent,
|
||||||
},
|
},
|
||||||
|
redirect: redirects ? "manual" : "error",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||||
|
const newUrl = res.headers.get("location");
|
||||||
|
if (newUrl == null)
|
||||||
|
throw new Error("apGet got redirect but no target location");
|
||||||
|
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||||
|
return apGet(newUrl, undefined, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers.get("content-type");
|
const contentType = res.headers.get("content-type");
|
||||||
if (contentType == null || !validateContentType(contentType)) {
|
if (contentType == null || !validateContentType(contentType)) {
|
||||||
throw new Error("Invalid Content Type");
|
throw new Error(
|
||||||
|
`apGet response had unexpected content-type: ${contentType}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.body == null) throw new Error("body is null");
|
if (res.body == null) throw new Error("body is null");
|
||||||
|
@ -79,7 +110,10 @@ export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
if (text.length > 65536) throw new Error("too big result");
|
if (text.length > 65536) throw new Error("too big result");
|
||||||
|
|
||||||
return JSON.parse(text) as IObject;
|
return {
|
||||||
|
finalUrl: res.url,
|
||||||
|
content: JSON.parse(text) as IObject,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateContentType(contentType: string): boolean {
|
function validateContentType(contentType: string): boolean {
|
||||||
|
|
|
@ -6,7 +6,13 @@ import { extractDbHost, isSelfHost } from "@/misc/convert-host.js";
|
||||||
import { apGet } from "./request.js";
|
import { apGet } from "./request.js";
|
||||||
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
|
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
|
||||||
import { isCollectionOrOrderedCollection, getApId } from "./type.js";
|
import { isCollectionOrOrderedCollection, getApId } from "./type.js";
|
||||||
import { Notes, NoteReactions, Polls, Users } from "@/models/index.js";
|
import {
|
||||||
|
FollowRequests,
|
||||||
|
Notes,
|
||||||
|
NoteReactions,
|
||||||
|
Polls,
|
||||||
|
Users,
|
||||||
|
} from "@/models/index.js";
|
||||||
import { parseUri } from "./db-resolver.js";
|
import { parseUri } from "./db-resolver.js";
|
||||||
import renderNote from "@/remote/activitypub/renderer/note.js";
|
import renderNote from "@/remote/activitypub/renderer/note.js";
|
||||||
import { renderLike } from "@/remote/activitypub/renderer/like.js";
|
import { renderLike } from "@/remote/activitypub/renderer/like.js";
|
||||||
|
@ -17,7 +23,7 @@ import { renderActivity } from "@/remote/activitypub/renderer/index.js";
|
||||||
import renderFollow from "@/remote/activitypub/renderer/follow.js";
|
import renderFollow from "@/remote/activitypub/renderer/follow.js";
|
||||||
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
||||||
import { apLogger } from "@/remote/activitypub/logger.js";
|
import { apLogger } from "@/remote/activitypub/logger.js";
|
||||||
import { In, IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
|
|
||||||
export default class Resolver {
|
export default class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
|
@ -114,7 +120,7 @@ export default class Resolver {
|
||||||
apLogger.debug("Getting object from remote, authenticated as user:");
|
apLogger.debug("Getting object from remote, authenticated as user:");
|
||||||
apLogger.debug(JSON.stringify(this.user, null, 2));
|
apLogger.debug(JSON.stringify(this.user, null, 2));
|
||||||
|
|
||||||
const object = await apGet(value, this.user);
|
const { finalUrl, content: object } = await apGet(value, this.user);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
object == null ||
|
object == null ||
|
||||||
|
@ -127,61 +133,73 @@ export default class Resolver {
|
||||||
throw new Error("invalid response");
|
throw new Error("invalid response");
|
||||||
}
|
}
|
||||||
|
|
||||||
return object;
|
if (object.id == null) {
|
||||||
|
throw new Error("Object has no ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalUrl === object.id) return object;
|
||||||
|
|
||||||
|
if (new URL(finalUrl).host !== new URL(object.id).host) {
|
||||||
|
throw new Error("Object ID host doesn't match final url host");
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalRes = await apGet(object.id, this.user);
|
||||||
|
|
||||||
|
if (finalRes.finalUrl !== finalRes.content.id)
|
||||||
|
throw new Error(
|
||||||
|
"Object ID still doesn't match final URL after second fetch attempt",
|
||||||
|
);
|
||||||
|
|
||||||
|
return finalRes.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveLocal(url: string): Promise<IObject> {
|
private async resolveLocal(url: string): Promise<IObject> {
|
||||||
const parsed = parseUri(url);
|
const parsed = parseUri(url);
|
||||||
if (!parsed.local) throw new Error("resolveLocal: not local");
|
if (!parsed.local) throw new Error("resolveLocal: not local");
|
||||||
|
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
case "notes":
|
case "notes":
|
||||||
return Notes.findOneByOrFail({ id: parsed.id }).then((note) => {
|
const note = await Notes.findOneByOrFail({ id: parsed.id });
|
||||||
if (parsed.rest === "activity") {
|
if (parsed.rest === "activity") {
|
||||||
// this refers to the create activity and not the note itself
|
// this refers to the create activity and not the note itself
|
||||||
return renderActivity(renderCreate(renderNote(note), note));
|
return renderActivity(renderCreate(renderNote(note), note));
|
||||||
} else {
|
} else {
|
||||||
return renderNote(note);
|
return renderNote(note);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
case "users":
|
case "users":
|
||||||
return Users.findOneByOrFail({ id: parsed.id }).then((user) =>
|
const user = await Users.findOneByOrFail({ id: parsed.id });
|
||||||
renderPerson(user as ILocalUser),
|
return await renderPerson(user as ILocalUser);
|
||||||
);
|
|
||||||
case "questions":
|
case "questions":
|
||||||
// Polls are indexed by the note they are attached to.
|
// Polls are indexed by the note they are attached to.
|
||||||
return Promise.all([
|
const [pollNote, poll] = await Promise.all([
|
||||||
Notes.findOneByOrFail({ id: parsed.id }),
|
Notes.findOneByOrFail({ id: parsed.id }),
|
||||||
Polls.findOneByOrFail({ noteId: parsed.id }),
|
Polls.findOneByOrFail({ noteId: parsed.id }),
|
||||||
]).then(([note, poll]) =>
|
]);
|
||||||
renderQuestion({ id: note.userId }, note, poll),
|
return await renderQuestion({ id: pollNote.userId }, pollNote, poll);
|
||||||
);
|
|
||||||
case "likes":
|
case "likes":
|
||||||
return NoteReactions.findOneByOrFail({ id: parsed.id }).then(
|
const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id });
|
||||||
(reaction) => renderActivity(renderLike(reaction, { uri: null })),
|
return renderActivity(renderLike(reaction, { uri: null }));
|
||||||
);
|
|
||||||
case "follows":
|
case "follows":
|
||||||
// if rest is a <followee id>
|
// if rest is a <followee id>
|
||||||
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
||||||
return Promise.all(
|
const [follower, followee] = await Promise.all(
|
||||||
[parsed.id, parsed.rest].map((id) => Users.findOneByOrFail({ id })),
|
[parsed.id, parsed.rest].map((id) => Users.findOneByOrFail({ id })),
|
||||||
).then(([follower, followee]) =>
|
|
||||||
renderActivity(renderFollow(follower, followee, url)),
|
|
||||||
);
|
);
|
||||||
|
return renderActivity(renderFollow(follower, followee, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another situation is there is only requestId, then obtained object from database.
|
// Another situation is there is only requestId, then obtained object from database.
|
||||||
const followRequest = FollowRequests.findOneBy({
|
const followRequest = await FollowRequests.findOneBy({
|
||||||
id: parsed.id,
|
id: parsed.id,
|
||||||
});
|
});
|
||||||
if (followRequest == null) {
|
if (followRequest == null) {
|
||||||
throw new Error("resolveLocal: invalid follow URI");
|
throw new Error("resolveLocal: invalid follow URI");
|
||||||
}
|
}
|
||||||
const follower = Users.findOneBy({
|
const follower = await Users.findOneBy({
|
||||||
id: followRequest.followerId,
|
id: followRequest.followerId,
|
||||||
host: IsNull(),
|
host: IsNull(),
|
||||||
});
|
});
|
||||||
const followee = Users.findOneBy({
|
const followee = await Users.findOneBy({
|
||||||
id: followRequest.followeeId,
|
id: followRequest.followeeId,
|
||||||
host: Not(IsNull()),
|
host: Not(IsNull()),
|
||||||
});
|
});
|
||||||
|
@ -190,7 +208,7 @@ export default class Resolver {
|
||||||
}
|
}
|
||||||
return renderActivity(renderFollow(follower, followee, url));
|
return renderActivity(renderFollow(follower, followee, url));
|
||||||
default:
|
default:
|
||||||
throw new Error(`resolveLocal: type ${type} unhandled`);
|
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ export default async function (ctx: Koa.Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.set("X-Content-Type-Options", "nosniff");
|
||||||
|
|
||||||
const isThumbnail = file.thumbnailAccessKey === key;
|
const isThumbnail = file.thumbnailAccessKey === key;
|
||||||
const isWebpublic = file.webpublicAccessKey === key;
|
const isWebpublic = file.webpublicAccessKey === key;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
{
|
{
|
||||||
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
||||||
"plugins": ["file-progress", "prettier"],
|
"plugins": ["file-progress"],
|
||||||
"ignorePatterns": ["**/*.json5"],
|
"ignorePatterns": ["**/*.json5"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"file-progress/activate": 1
|
"file-progress/activate": 1,
|
||||||
|
"prettier/prettier": 0,
|
||||||
|
"one-var": ["error", "never"],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"caughtErrorsIgnorePattern": "^_",
|
||||||
|
"destructuredArrayIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"build:debug": "pnpm run build",
|
"build:debug": "pnpm run build",
|
||||||
"lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
|
"lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
|
||||||
"lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
|
"lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
|
||||||
"format": "pnpm biome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
|
"format": "pnpm biome format * --write"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint-sets/eslint-config-vue3": "^5.12.0",
|
"@eslint-sets/eslint-config-vue3": "^5.12.0",
|
||||||
|
@ -48,7 +48,6 @@
|
||||||
"cropperjs": "2.0.0-beta.4",
|
"cropperjs": "2.0.0-beta.4",
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"emojilib": "^3.0.11",
|
"emojilib": "^3.0.11",
|
||||||
"eslint-config-prettier": "9.1.0",
|
|
||||||
"eslint-plugin-file-progress": "^1.3.0",
|
"eslint-plugin-file-progress": "^1.3.0",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"fast-blurhash": "^1.1.2",
|
"fast-blurhash": "^1.1.2",
|
||||||
|
@ -65,7 +64,6 @@
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"photoswipe": "5.4.3",
|
"photoswipe": "5.4.3",
|
||||||
"prettier": "3.2.5",
|
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
"rollup": "4.13.0",
|
"rollup": "4.13.0",
|
||||||
|
|
|
@ -179,8 +179,7 @@ async function renderActiveUsersChart() {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title(context) {
|
title(context) {
|
||||||
const v =
|
const v = context[0].dataset.data[context[0].dataIndex];
|
||||||
context[0].dataset.data[context[0].dataIndex];
|
|
||||||
return v.d;
|
return v.d;
|
||||||
},
|
},
|
||||||
label(context) {
|
label(context) {
|
||||||
|
|
|
@ -166,21 +166,21 @@ const texts = computed(() => {
|
||||||
return angles;
|
return angles;
|
||||||
});
|
});
|
||||||
|
|
||||||
let enabled = true,
|
let enabled = true;
|
||||||
majorGraduationColor = ref<string>(),
|
const majorGraduationColor = ref<string>();
|
||||||
// let minorGraduationColor = $ref<string>();
|
// let minorGraduationColor = $ref<string>();
|
||||||
sHandColor = ref<string>(),
|
const sHandColor = ref<string>();
|
||||||
mHandColor = ref<string>(),
|
const mHandColor = ref<string>();
|
||||||
hHandColor = ref<string>(),
|
const hHandColor = ref<string>();
|
||||||
nowColor = ref<string>(),
|
const nowColor = ref<string>();
|
||||||
h = ref<number>(0),
|
const h = ref<number>(0);
|
||||||
m = ref<number>(0),
|
const m = ref<number>(0);
|
||||||
s = ref<number>(0),
|
const s = ref<number>(0);
|
||||||
hAngle = ref<number>(0),
|
const hAngle = ref<number>(0);
|
||||||
mAngle = ref<number>(0),
|
const mAngle = ref<number>(0);
|
||||||
sAngle = ref<number>(0),
|
const sAngle = ref<number>(0);
|
||||||
disableSAnimate = ref(false),
|
const disableSAnimate = ref(false);
|
||||||
sOneRound = false;
|
let sOneRound = false;
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
|
@ -128,20 +128,20 @@ const getColor = (i) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let chartInstance: Chart = null,
|
let chartInstance: Chart = null;
|
||||||
chartData: {
|
let chartData: {
|
||||||
series: {
|
series: {
|
||||||
name: string;
|
name: string;
|
||||||
type: "line" | "area";
|
type: "line" | "area";
|
||||||
color?: string;
|
color?: string;
|
||||||
dashed?: boolean;
|
dashed?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
data: {
|
data: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}[];
|
|
||||||
}[];
|
}[];
|
||||||
} = null;
|
}[];
|
||||||
|
} = null;
|
||||||
|
|
||||||
const chartEl = ref<HTMLCanvasElement>(null);
|
const chartEl = ref<HTMLCanvasElement>(null);
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
|
|
|
@ -33,8 +33,8 @@ const rootEl = ref<HTMLDivElement>();
|
||||||
const zIndex = ref<number>(os.claimZIndex("high"));
|
const zIndex = ref<number>(os.claimZIndex("high"));
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let left = props.ev.pageX + 1, // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
||||||
top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
||||||
|
|
||||||
const width = rootEl.value.offsetWidth;
|
const width = rootEl.value.offsetWidth;
|
||||||
const height = rootEl.value.offsetHeight;
|
const height = rootEl.value.offsetHeight;
|
||||||
|
|
|
@ -64,8 +64,8 @@ const imgUrl = `${url}/proxy/image.webp?${query({
|
||||||
})}`;
|
})}`;
|
||||||
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
||||||
const imgEl = ref<HTMLImageElement>();
|
const imgEl = ref<HTMLImageElement>();
|
||||||
let cropper: Cropper | null = null,
|
let cropper: Cropper | null = null;
|
||||||
loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
const ok = async () => {
|
const ok = async () => {
|
||||||
const promise = new Promise<entities.DriveFile>(async (res) => {
|
const promise = new Promise<entities.DriveFile>(async (res) => {
|
||||||
|
|
|
@ -83,9 +83,7 @@ function getMenu() {
|
||||||
text: props.file.isSensitive
|
text: props.file.isSensitive
|
||||||
? i18n.ts.unmarkAsSensitive
|
? i18n.ts.unmarkAsSensitive
|
||||||
: i18n.ts.markAsSensitive,
|
: i18n.ts.markAsSensitive,
|
||||||
icon: props.file.isSensitive
|
icon: props.file.isSensitive ? "ph-eye ph-lg" : "ph-eye-slash ph-lg",
|
||||||
? "ph-eye ph-lg"
|
|
||||||
: "ph-eye-slash ph-lg",
|
|
||||||
action: toggleSensitive,
|
action: toggleSensitive,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -129,9 +127,7 @@ function onClick(ev: MouseEvent) {
|
||||||
} else {
|
} else {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
getMenu(),
|
getMenu(),
|
||||||
(ev.currentTarget ?? ev.target ?? undefined) as
|
(ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined,
|
||||||
| HTMLElement
|
|
||||||
| undefined,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -451,9 +451,7 @@ function chooseFile(file: entities.DriveFile) {
|
||||||
const isAlreadySelected = selectedFiles.value.some((f) => f.id === file.id);
|
const isAlreadySelected = selectedFiles.value.some((f) => f.id === file.id);
|
||||||
if (props.multiple) {
|
if (props.multiple) {
|
||||||
if (isAlreadySelected) {
|
if (isAlreadySelected) {
|
||||||
selectedFiles.value = selectedFiles.value.filter(
|
selectedFiles.value = selectedFiles.value.filter((f) => f.id !== file.id);
|
||||||
(f) => f.id !== file.id,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
selectedFiles.value.push(file);
|
selectedFiles.value.push(file);
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,19 +113,19 @@
|
||||||
<header>{{ i18n.ts.emoji }}</header>
|
<header>{{ i18n.ts.emoji }}</header>
|
||||||
<XSection
|
<XSection
|
||||||
v-for="category in unicodeEmojiCategories"
|
v-for="category in unicodeEmojiCategories"
|
||||||
:key="category"
|
:key="category.slug"
|
||||||
:skin-tone-selector="category === 'people'"
|
:skin-tone-selector="category.slug === 'people_body'"
|
||||||
:skin-tones="unicodeEmojiSkinTones"
|
:skin-tones="unicodeEmojiSkinTones"
|
||||||
:skin-tone-labels="unicodeEmojiSkinToneLabels"
|
:skin-tone-labels="unicodeEmojiSkinToneLabels"
|
||||||
:emojis="
|
:emojis="
|
||||||
emojilist
|
emojilist
|
||||||
.filter((e) => e.category === category)
|
.filter(
|
||||||
|
(e) => e.category_slug === category.slug,
|
||||||
|
)
|
||||||
.map((e) => e.emoji)
|
.map((e) => e.emoji)
|
||||||
"
|
"
|
||||||
@chosen="chosen"
|
@chosen="chosen"
|
||||||
>{{
|
>{{ category.name }}</XSection
|
||||||
getNicelyLabeledCategory(category) || category
|
|
||||||
}}</XSection
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,11 +169,7 @@ import type { entities } from "firefish-js";
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
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 type { UnicodeEmojiDef } from "@/scripts/emojilist";
|
||||||
import {
|
import { emojilist, unicodeEmojiCategories } from "@/scripts/emojilist";
|
||||||
emojilist,
|
|
||||||
getNicelyLabeledCategory,
|
|
||||||
unicodeEmojiCategories,
|
|
||||||
} 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";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -284,9 +280,7 @@ watch(q, () => {
|
||||||
keywords.every(
|
keywords.every(
|
||||||
(keyword) =>
|
(keyword) =>
|
||||||
emoji.name.includes(keyword) ||
|
emoji.name.includes(keyword) ||
|
||||||
emoji.aliases.some((alias) =>
|
emoji.aliases.some((alias) => alias.includes(keyword)),
|
||||||
alias.includes(keyword),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
matches.add(emoji);
|
matches.add(emoji);
|
||||||
|
@ -356,9 +350,7 @@ watch(q, () => {
|
||||||
keywords.every(
|
keywords.every(
|
||||||
(keyword) =>
|
(keyword) =>
|
||||||
emoji.slug.includes(keyword) ||
|
emoji.slug.includes(keyword) ||
|
||||||
emoji.keywords?.some((alias) =>
|
emoji.keywords?.some((alias) => alias.includes(keyword)),
|
||||||
alias.includes(keyword),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
matches.add(emoji);
|
matches.add(emoji);
|
||||||
|
@ -375,9 +367,7 @@ watch(q, () => {
|
||||||
if (matches.size >= max) return matches;
|
if (matches.size >= max) return matches;
|
||||||
|
|
||||||
for (const emoji of emojis) {
|
for (const emoji of emojis) {
|
||||||
if (
|
if (emoji.keywords?.some((keyword) => keyword.startsWith(newQ))) {
|
||||||
emoji.keywords?.some((keyword) => keyword.startsWith(newQ))
|
|
||||||
) {
|
|
||||||
matches.add(emoji);
|
matches.add(emoji);
|
||||||
if (matches.size >= max) break;
|
if (matches.size >= max) break;
|
||||||
}
|
}
|
||||||
|
@ -428,8 +418,7 @@ function getKey(
|
||||||
|
|
||||||
function chosen(emoji: any, ev?: MouseEvent) {
|
function chosen(emoji: any, ev?: MouseEvent) {
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev && ((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
|
|
@ -58,9 +58,7 @@ export default defineComponent({
|
||||||
showBody:
|
showBody:
|
||||||
this.persistKey &&
|
this.persistKey &&
|
||||||
localStorage.getItem(localStoragePrefix + this.persistKey)
|
localStorage.getItem(localStoragePrefix + this.persistKey)
|
||||||
? localStorage.getItem(
|
? localStorage.getItem(localStoragePrefix + this.persistKey) === "t"
|
||||||
localStoragePrefix + this.persistKey,
|
|
||||||
) === "t"
|
|
||||||
: this.expanded,
|
: this.expanded,
|
||||||
animation: defaultStore.state.animation,
|
animation: defaultStore.state.animation,
|
||||||
};
|
};
|
||||||
|
|
|
@ -112,8 +112,7 @@ if (props.user.isFollowing == null) {
|
||||||
function onFollowChange(user: entities.UserDetailed) {
|
function onFollowChange(user: entities.UserDetailed) {
|
||||||
if (user.id === props.user.id) {
|
if (user.id === props.user.id) {
|
||||||
isFollowing.value = user.isFollowing;
|
isFollowing.value = user.isFollowing;
|
||||||
hasPendingFollowRequestFromYou.value =
|
hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
|
||||||
user.hasPendingFollowRequestFromYou;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,10 +171,7 @@ async function onClick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
os.popupMenu(
|
os.popupMenu(getUserMenu(props.user, router), ev.currentTarget ?? ev.target);
|
||||||
getUserMenu(props.user, router),
|
|
||||||
ev.currentTarget ?? ev.target,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -22,14 +22,14 @@
|
||||||
@load="onLoad"
|
@load="onLoad"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
class="alt-indicator"
|
|
||||||
:class="icon('ph-subtitles')"
|
|
||||||
v-if="alt && showAltIndicator"
|
v-if="alt && showAltIndicator"
|
||||||
v-tooltip.noLabel="
|
v-tooltip.noLabel="
|
||||||
`${i18n.ts.alt}: ${
|
`${i18n.ts.alt}: ${
|
||||||
alt.length > 200 ? alt.trim().slice(0, 200) + '...' : alt.trim()
|
alt.length > 200 ? alt.trim().slice(0, 200) + '...' : alt.trim()
|
||||||
}`
|
}`
|
||||||
"
|
"
|
||||||
|
class="alt-indicator"
|
||||||
|
:class="icon('ph-subtitles')"
|
||||||
></i>
|
></i>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,7 @@ const instance = props.instance ?? {
|
||||||
faviconUrl: Instance.faviconUrl || Instance.iconUrl || "/favicon.ico",
|
faviconUrl: Instance.faviconUrl || Instance.iconUrl || "/favicon.ico",
|
||||||
name: instanceName,
|
name: instanceName,
|
||||||
themeColor: (
|
themeColor: (
|
||||||
document.querySelector(
|
document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement
|
||||||
'meta[name="theme-color-orig"]',
|
|
||||||
) as HTMLMetaElement
|
|
||||||
)?.content,
|
)?.content,
|
||||||
softwareName: Instance.softwareName ?? "Firefish",
|
softwareName: Instance.softwareName ?? "Firefish",
|
||||||
softwareVersion: version,
|
softwareVersion: version,
|
||||||
|
|
|
@ -40,9 +40,7 @@ const el = ref();
|
||||||
|
|
||||||
useTooltip(el, (showing) => {
|
useTooltip(el, (showing) => {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkUrlPreviewPopup.vue")),
|
||||||
() => import("@/components/MkUrlPreviewPopup.vue"),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
showing,
|
showing,
|
||||||
url: props.url,
|
url: props.url,
|
||||||
|
|
|
@ -58,9 +58,7 @@ export default {
|
||||||
{
|
{
|
||||||
class: $style.text,
|
class: $style.text,
|
||||||
style: {
|
style: {
|
||||||
animationDirection: reverse
|
animationDirection: reverse ? "reverse" : undefined,
|
||||||
? "reverse"
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$slots.default(),
|
$slots.default(),
|
||||||
|
|
|
@ -113,7 +113,7 @@ const url =
|
||||||
props.raw || defaultStore.state.loadRawImages
|
props.raw || defaultStore.state.loadRawImages
|
||||||
? props.media.url
|
? props.media.url
|
||||||
: defaultStore.state.disableShowingAnimatedImages &&
|
: defaultStore.state.disableShowingAnimatedImages &&
|
||||||
props.media.type.startsWith("image")
|
props.media.type.startsWith("image")
|
||||||
? getStaticImageUrl(props.media.thumbnailUrl)
|
? getStaticImageUrl(props.media.thumbnailUrl)
|
||||||
: props.media.thumbnailUrl;
|
: props.media.thumbnailUrl;
|
||||||
|
|
||||||
|
@ -150,8 +150,7 @@ watch(
|
||||||
hide.value =
|
hide.value =
|
||||||
defaultStore.state.nsfw === "force"
|
defaultStore.state.nsfw === "force"
|
||||||
? true
|
? true
|
||||||
: props.media.isSensitive &&
|
: props.media.isSensitive && defaultStore.state.nsfw !== "ignore";
|
||||||
defaultStore.state.nsfw !== "ignore";
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
|
|
|
@ -75,8 +75,7 @@ const audioEl = ref<HTMLAudioElement | null>();
|
||||||
const hide = ref(true);
|
const hide = ref(true);
|
||||||
|
|
||||||
function volumechange() {
|
function volumechange() {
|
||||||
if (audioEl.value)
|
if (audioEl.value) ColdDeviceStorage.set("mediaVolume", audioEl.value.volume);
|
||||||
ColdDeviceStorage.set("mediaVolume", audioEl.value.volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -183,12 +183,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
caption() {
|
caption() {
|
||||||
const img = document.getElementById(
|
const img = document.getElementById("imgtocaption") as HTMLImageElement;
|
||||||
"imgtocaption",
|
const ta = document.getElementById("captioninput") as HTMLTextAreaElement;
|
||||||
) as HTMLImageElement;
|
|
||||||
const ta = document.getElementById(
|
|
||||||
"captioninput",
|
|
||||||
) as HTMLTextAreaElement;
|
|
||||||
os.api("drive/files/caption-image", {
|
os.api("drive/files/caption-image", {
|
||||||
url: img.src,
|
url: img.src,
|
||||||
}).then((text) => {
|
}).then((text) => {
|
||||||
|
|
|
@ -168,10 +168,10 @@ function load() {
|
||||||
|
|
||||||
onMounted(load);
|
onMounted(load);
|
||||||
|
|
||||||
let currentRow = 0,
|
let currentRow = 0;
|
||||||
rowHeight = 0,
|
let rowHeight = 0;
|
||||||
buffer = null,
|
let buffer = null;
|
||||||
isSeeking = false;
|
const isSeeking = false;
|
||||||
|
|
||||||
function captionPopup() {
|
function captionPopup() {
|
||||||
os.alert({
|
os.alert({
|
||||||
|
@ -311,11 +311,7 @@ function getRow(pattern: number, rowOffset: number) {
|
||||||
const ops: string[] = [];
|
const ops: string[] = [];
|
||||||
|
|
||||||
for (let channel = 0; channel < nbChannels.value; channel++) {
|
for (let channel = 0; channel < nbChannels.value; channel++) {
|
||||||
const part = player.value.getPatternRowChannel(
|
const part = player.value.getPatternRowChannel(pattern, rowOffset, channel);
|
||||||
pattern,
|
|
||||||
rowOffset,
|
|
||||||
channel,
|
|
||||||
);
|
|
||||||
|
|
||||||
notes.push(part.substring(0, 3));
|
notes.push(part.substring(0, 3));
|
||||||
insts.push(part.substring(4, 6));
|
insts.push(part.substring(4, 6));
|
||||||
|
|
|
@ -235,7 +235,8 @@ const align = () => {
|
||||||
const width = content.value!.offsetWidth;
|
const width = content.value!.offsetWidth;
|
||||||
const height = content.value!.offsetHeight;
|
const height = content.value!.offsetHeight;
|
||||||
|
|
||||||
let left, top;
|
let left;
|
||||||
|
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);
|
||||||
|
@ -286,8 +287,7 @@ const align = () => {
|
||||||
left = window.innerWidth - width + window.scrollX - 1;
|
left = window.innerWidth - width + window.scrollX - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const underSpace =
|
const underSpace = window.innerHeight - MARGIN - (top - window.pageYOffset);
|
||||||
window.innerHeight - MARGIN - (top - window.pageYOffset);
|
|
||||||
const upperSpace = srcRect.top - MARGIN;
|
const upperSpace = srcRect.top - MARGIN;
|
||||||
|
|
||||||
// 画面から縦にはみ出る場合
|
// 画面から縦にはみ出る場合
|
||||||
|
@ -300,12 +300,7 @@ const align = () => {
|
||||||
top = window.scrollY + (upperSpace + MARGIN - height);
|
top = window.scrollY + (upperSpace + MARGIN - height);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
top =
|
top = window.innerHeight - MARGIN - height + window.pageYOffset - 1;
|
||||||
window.innerHeight -
|
|
||||||
MARGIN -
|
|
||||||
height +
|
|
||||||
window.pageYOffset -
|
|
||||||
1;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maxHeight.value = underSpace;
|
maxHeight.value = underSpace;
|
||||||
|
@ -320,8 +315,8 @@ const align = () => {
|
||||||
left = 0;
|
left = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transformOriginX = "center",
|
let transformOriginX = "center";
|
||||||
transformOriginY = "center";
|
let transformOriginY = "center";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
top >=
|
top >=
|
||||||
|
|
|
@ -412,8 +412,7 @@ async function translate() {
|
||||||
lang != null &&
|
lang != null &&
|
||||||
translateLang !== lang &&
|
translateLang !== lang &&
|
||||||
(!translation.value ||
|
(!translation.value ||
|
||||||
translation.value.sourceLang.toLowerCase() ===
|
translation.value.sourceLang.toLowerCase() === translateLang.slice(0, 2))
|
||||||
translateLang.slice(0, 2))
|
|
||||||
)
|
)
|
||||||
translation.value = await translate_(appearNote.value.id, lang);
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
translating.value = false;
|
translating.value = false;
|
||||||
|
@ -515,10 +514,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
icon: `${icon("ph-arrows-out-simple")}`,
|
icon: `${icon("ph-arrows-out-simple")}`,
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(
|
router.push(notePage(appearNote.value), "forcePage");
|
||||||
notePage(appearNote.value),
|
|
||||||
"forcePage",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -542,10 +538,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: `${icon("ph-arrow-square-up-right")}`,
|
icon: `${icon("ph-arrow-square-up-right")}`,
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href:
|
href: appearNote.value.url ?? appearNote.value.uri ?? "",
|
||||||
appearNote.value.url ??
|
|
||||||
appearNote.value.uri ??
|
|
||||||
"",
|
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -70,8 +70,7 @@ const note = ref(props.note);
|
||||||
|
|
||||||
const showTicker =
|
const showTicker =
|
||||||
defaultStore.state.instanceTicker === "always" ||
|
defaultStore.state.instanceTicker === "always" ||
|
||||||
(defaultStore.state.instanceTicker === "remote" &&
|
(defaultStore.state.instanceTicker === "remote" && note.value.user.instance);
|
||||||
note.value.user.instance);
|
|
||||||
|
|
||||||
function openServerInfo() {
|
function openServerInfo() {
|
||||||
if (!props.canOpenServerInfo || !defaultStore.state.openServerInfo) return;
|
if (!props.canOpenServerInfo || !defaultStore.state.openServerInfo) return;
|
||||||
|
|
|
@ -280,8 +280,7 @@ const replies: entities.Note[] =
|
||||||
props.conversation
|
props.conversation
|
||||||
?.filter(
|
?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.replyId === props.note.id ||
|
item.replyId === props.note.id || item.renoteId === props.note.id,
|
||||||
item.renoteId === props.note.id,
|
|
||||||
)
|
)
|
||||||
.reverse() ?? [];
|
.reverse() ?? [];
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
@ -319,8 +318,7 @@ async function translate() {
|
||||||
lang != null &&
|
lang != null &&
|
||||||
translateLang !== lang &&
|
translateLang !== lang &&
|
||||||
(!translation.value ||
|
(!translation.value ||
|
||||||
translation.value.sourceLang.toLowerCase() ===
|
translation.value.sourceLang.toLowerCase() === translateLang.slice(0, 2))
|
||||||
translateLang.slice(0, 2))
|
|
||||||
)
|
)
|
||||||
translation.value = await translate_(appearNote.value.id, lang);
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
translating.value = false;
|
translating.value = false;
|
||||||
|
@ -421,10 +419,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
icon: `${icon("ph-arrows-out-simple")}`,
|
icon: `${icon("ph-arrows-out-simple")}`,
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(
|
router.push(notePage(appearNote.value), "forcePage");
|
||||||
notePage(appearNote.value),
|
|
||||||
"forcePage",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -310,7 +310,8 @@ const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReact
|
||||||
? instance.defaultReaction
|
? instance.defaultReaction
|
||||||
: "⭐";
|
: "⭐";
|
||||||
|
|
||||||
let readObserver: IntersectionObserver | undefined, connection;
|
let readObserver: IntersectionObserver | undefined;
|
||||||
|
let connection;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!props.notification.isRead) {
|
if (!props.notification.isRead) {
|
||||||
|
|
|
@ -84,9 +84,7 @@ function ok() {
|
||||||
} else {
|
} else {
|
||||||
emit("done", {
|
emit("done", {
|
||||||
includingTypes: (
|
includingTypes: (
|
||||||
Object.keys(
|
Object.keys(typesMap.value) as (typeof notificationTypes)[number][]
|
||||||
typesMap.value,
|
|
||||||
) as (typeof notificationTypes)[number][]
|
|
||||||
).filter((type) => typesMap.value[type]),
|
).filter((type) => typesMap.value[type]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,7 @@ const pagination: Paging = {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
includeTypes: props.includeTypes ?? undefined,
|
includeTypes: props.includeTypes ?? undefined,
|
||||||
excludeTypes: props.includeTypes
|
excludeTypes: props.includeTypes ? undefined : me.mutingNotificationTypes,
|
||||||
? undefined
|
|
||||||
: me.mutingNotificationTypes,
|
|
||||||
unreadOnly: props.unreadOnly,
|
unreadOnly: props.unreadOnly,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
@ -114,20 +112,12 @@ onMounted(() => {
|
||||||
connection.on("readNotifications", (notificationIds) => {
|
connection.on("readNotifications", (notificationIds) => {
|
||||||
if (pagingComponent.value) {
|
if (pagingComponent.value) {
|
||||||
for (let i = 0; i < pagingComponent.value.queue.length; i++) {
|
for (let i = 0; i < pagingComponent.value.queue.length; i++) {
|
||||||
if (
|
if (notificationIds.includes(pagingComponent.value.queue[i].id)) {
|
||||||
notificationIds.includes(pagingComponent.value.queue[i].id)
|
|
||||||
) {
|
|
||||||
pagingComponent.value.queue[i].isRead = true;
|
pagingComponent.value.queue[i].isRead = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (
|
for (let i = 0; i < (pagingComponent.value.items || []).length; i++) {
|
||||||
let i = 0;
|
if (notificationIds.includes(pagingComponent.value.items[i].id)) {
|
||||||
i < (pagingComponent.value.items || []).length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
notificationIds.includes(pagingComponent.value.items[i].id)
|
|
||||||
) {
|
|
||||||
pagingComponent.value.items[i].isRead = true;
|
pagingComponent.value.items[i].isRead = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,14 +164,10 @@ const init = async (): Promise<void> => {
|
||||||
res.length > (props.pagination.limit || 10)
|
res.length > (props.pagination.limit || 10)
|
||||||
) {
|
) {
|
||||||
res.pop();
|
res.pop();
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
||||||
? [...res].reverse()
|
|
||||||
: res;
|
|
||||||
more.value = true;
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
items.value = props.pagination.reversed
|
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
||||||
? [...res].reverse()
|
|
||||||
: res;
|
|
||||||
more.value = false;
|
more.value = false;
|
||||||
}
|
}
|
||||||
offset.value = res.length;
|
offset.value = res.length;
|
||||||
|
@ -381,8 +377,7 @@ const prepend = (item: Item): void => {
|
||||||
|
|
||||||
const isTop =
|
const isTop =
|
||||||
isBackTop.value ||
|
isBackTop.value ||
|
||||||
(document.body.contains(rootEl.value) &&
|
(document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
|
||||||
isTopVisible(rootEl.value));
|
|
||||||
|
|
||||||
if (isTop) {
|
if (isTop) {
|
||||||
// Prepend the item
|
// Prepend the item
|
||||||
|
|
|
@ -74,8 +74,7 @@ const closed = computed(() => remaining.value === 0);
|
||||||
const isLocal = computed(() => !props.note.uri);
|
const isLocal = computed(() => !props.note.uri);
|
||||||
const isVoted = computed(
|
const isVoted = computed(
|
||||||
() =>
|
() =>
|
||||||
!props.note.poll.multiple &&
|
!props.note.poll.multiple && props.note.poll.choices.some((c) => c.isVoted),
|
||||||
props.note.poll.choices.some((c) => c.isVoted),
|
|
||||||
);
|
);
|
||||||
const timer = computed(() =>
|
const timer = computed(() =>
|
||||||
i18n.t(
|
i18n.t(
|
||||||
|
@ -101,10 +100,8 @@ const showResult = ref(props.readOnly || isVoted.value);
|
||||||
if (props.note.poll.expiresAt) {
|
if (props.note.poll.expiresAt) {
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
remaining.value = Math.floor(
|
remaining.value = Math.floor(
|
||||||
Math.max(
|
Math.max(new Date(props.note.poll.expiresAt).getTime() - Date.now(), 0) /
|
||||||
new Date(props.note.poll.expiresAt).getTime() - Date.now(),
|
1000,
|
||||||
0,
|
|
||||||
) / 1000,
|
|
||||||
);
|
);
|
||||||
if (remaining.value === 0) {
|
if (remaining.value === 0) {
|
||||||
showResult.value = true;
|
showResult.value = true;
|
||||||
|
|
|
@ -331,8 +331,8 @@ import { vibrate } from "@/scripts/vibrate";
|
||||||
import { langmap } from "@/scripts/langmap";
|
import { langmap } from "@/scripts/langmap";
|
||||||
import {
|
import {
|
||||||
detectLanguage,
|
detectLanguage,
|
||||||
isSupportedLang,
|
|
||||||
isSameLanguage,
|
isSameLanguage,
|
||||||
|
isSupportedLang,
|
||||||
languageContains,
|
languageContains,
|
||||||
parentLanguage,
|
parentLanguage,
|
||||||
} from "@/scripts/language-utils";
|
} from "@/scripts/language-utils";
|
||||||
|
@ -527,9 +527,7 @@ if (
|
||||||
(props.reply.user.host != null && props.reply.user.host !== host))
|
(props.reply.user.host != null && props.reply.user.host !== host))
|
||||||
) {
|
) {
|
||||||
text.value = `@${props.reply.user.username}${
|
text.value = `@${props.reply.user.username}${
|
||||||
props.reply.user.host != null
|
props.reply.user.host != null ? "@" + toASCII(props.reply.user.host) : ""
|
||||||
? "@" + toASCII(props.reply.user.host)
|
|
||||||
: ""
|
|
||||||
} `;
|
} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,11 +587,9 @@ if (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.reply.userId !== me.id) {
|
if (props.reply.userId !== me.id) {
|
||||||
os.api("users/show", { userId: props.reply.userId }).then(
|
os.api("users/show", { userId: props.reply.userId }).then((user) => {
|
||||||
(user) => {
|
pushVisibleUser(user);
|
||||||
pushVisibleUser(user);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,9 +728,7 @@ function setVisibility() {
|
||||||
}
|
}
|
||||||
|
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkVisibilityPicker.vue")),
|
||||||
() => import("@/components/MkVisibilityPicker.vue"),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
currentVisibility: visibility.value,
|
currentVisibility: visibility.value,
|
||||||
currentLocalOnly: localOnly.value,
|
currentLocalOnly: localOnly.value,
|
||||||
|
@ -834,8 +828,7 @@ function setLanguage() {
|
||||||
|
|
||||||
for (const lang of langs) {
|
for (const lang of langs) {
|
||||||
if (lang === language.value) continue;
|
if (lang === language.value) continue;
|
||||||
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang))
|
if (defaultStore.state.recentlyUsedPostLanguages.includes(lang)) continue;
|
||||||
continue;
|
|
||||||
actions.push({
|
actions.push({
|
||||||
text: langmap[lang].nativeName,
|
text: langmap[lang].nativeName,
|
||||||
danger: false,
|
danger: false,
|
||||||
|
@ -1072,8 +1065,7 @@ async function post() {
|
||||||
let postData = {
|
let postData = {
|
||||||
editId: props.editId ? props.editId : undefined,
|
editId: props.editId ? props.editId : undefined,
|
||||||
text: processedText === "" ? undefined : processedText,
|
text: processedText === "" ? undefined : processedText,
|
||||||
fileIds:
|
fileIds: files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
|
||||||
files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
|
|
||||||
replyId: props.reply ? props.reply.id : undefined,
|
replyId: props.reply ? props.reply.id : undefined,
|
||||||
renoteId: props.renote
|
renoteId: props.renote
|
||||||
? props.renote.id
|
? props.renote.id
|
||||||
|
@ -1085,8 +1077,7 @@ async function post() {
|
||||||
cw: useCw.value ? cw.value || "" : undefined,
|
cw: useCw.value ? cw.value || "" : undefined,
|
||||||
lang: language.value ? language.value : undefined,
|
lang: language.value ? language.value : undefined,
|
||||||
localOnly: localOnly.value,
|
localOnly: localOnly.value,
|
||||||
visibility:
|
visibility: visibility.value === "private" ? "specified" : visibility.value,
|
||||||
visibility.value === "private" ? "specified" : visibility.value,
|
|
||||||
visibleUserIds:
|
visibleUserIds:
|
||||||
visibility.value === "private"
|
visibility.value === "private"
|
||||||
? []
|
? []
|
||||||
|
@ -1101,9 +1092,7 @@ async function post() {
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((x) => (x.startsWith("#") ? x : "#" + x))
|
.map((x) => (x.startsWith("#") ? x : "#" + x))
|
||||||
.join(" ");
|
.join(" ");
|
||||||
postData.text = postData.text
|
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
|
||||||
? `${postData.text} ${hashtags_}`
|
|
||||||
: hashtags_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// plugin
|
// plugin
|
||||||
|
@ -1117,9 +1106,7 @@ async function post() {
|
||||||
|
|
||||||
if (postAccount.value) {
|
if (postAccount.value) {
|
||||||
const storedAccounts = await getAccounts();
|
const storedAccounts = await getAccounts();
|
||||||
token = storedAccounts.find(
|
token = storedAccounts.find((x) => x.id === postAccount.value.id)?.token;
|
||||||
(x) => x.id === postAccount.value.id,
|
|
||||||
)?.token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
posting.value = true;
|
posting.value = true;
|
||||||
|
@ -1165,14 +1152,9 @@ async function post() {
|
||||||
"recentlyUsedPostLanguages",
|
"recentlyUsedPostLanguages",
|
||||||
[language.value]
|
[language.value]
|
||||||
.concat(
|
.concat(
|
||||||
defaultStore.state.recentlyUsedPostLanguages.filter(
|
defaultStore.state.recentlyUsedPostLanguages.filter((lang) => {
|
||||||
(lang) => {
|
return lang !== language.value && languages.includes(lang);
|
||||||
return (
|
}),
|
||||||
lang !== language.value &&
|
|
||||||
languages.includes(lang)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.slice(0, maxLength),
|
.slice(0, maxLength),
|
||||||
);
|
);
|
||||||
|
@ -1268,9 +1250,7 @@ onMounted(() => {
|
||||||
visibility.value = draft.data.visibility;
|
visibility.value = draft.data.visibility;
|
||||||
localOnly.value = draft.data.localOnly;
|
localOnly.value = draft.data.localOnly;
|
||||||
language.value = draft.data.lang;
|
language.value = draft.data.lang;
|
||||||
files.value = (draft.data.files || []).filter(
|
files.value = (draft.data.files || []).filter((draftFile) => draftFile);
|
||||||
(draftFile) => draftFile,
|
|
||||||
);
|
|
||||||
if (draft.data.poll) {
|
if (draft.data.poll) {
|
||||||
poll.value = draft.data.poll;
|
poll.value = draft.data.poll;
|
||||||
}
|
}
|
||||||
|
@ -1298,6 +1278,14 @@ onMounted(() => {
|
||||||
quoteId.value = init.renote ? init.renote.id : null;
|
quoteId.value = init.renote ? init.renote.id : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inherit language settings when quoting or replying
|
||||||
|
if (props.renote) {
|
||||||
|
language.value = props.renote.lang ?? null;
|
||||||
|
}
|
||||||
|
if (props.reply) {
|
||||||
|
language.value = props.reply.lang ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => watchForDraft());
|
nextTick(() => watchForDraft());
|
||||||
nextTick(() => autosize.update(textareaEl.value));
|
nextTick(() => autosize.update(textareaEl.value));
|
||||||
});
|
});
|
||||||
|
|
|
@ -107,8 +107,7 @@ async function describe(file) {
|
||||||
{
|
{
|
||||||
done: (result) => {
|
done: (result) => {
|
||||||
if (!result || result.canceled) return;
|
if (!result || result.canceled) return;
|
||||||
const comment =
|
const comment = result.result.length === 0 ? null : result.result;
|
||||||
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,
|
||||||
|
|
|
@ -45,7 +45,7 @@ const isRefreshing = ref(false);
|
||||||
const pullDistance = ref(0);
|
const pullDistance = ref(0);
|
||||||
|
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
let supportPointerDesktop = false;
|
const supportPointerDesktop = false;
|
||||||
let startScreenY: number | null = null;
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
const rootEl = shallowRef<HTMLDivElement>();
|
const rootEl = shallowRef<HTMLDivElement>();
|
||||||
|
@ -102,8 +102,7 @@ function moveBySystem(to: number): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextHeight =
|
const nextHeight =
|
||||||
initialHeight -
|
initialHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time;
|
||||||
(overHeight / RELEASE_TRANSITION_DURATION) * time;
|
|
||||||
if (pullDistance.value < nextHeight) return;
|
if (pullDistance.value < nextHeight) return;
|
||||||
pullDistance.value = nextHeight;
|
pullDistance.value = nextHeight;
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
|
@ -94,17 +94,14 @@ const pushRegistrationInServer = ref<
|
||||||
>();
|
>();
|
||||||
|
|
||||||
function subscribe() {
|
function subscribe() {
|
||||||
if (!registration.value || !supported.value || !instance.swPublickey)
|
if (!registration.value || !supported.value || !instance.swPublickey) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
||||||
return promiseDialog(
|
return promiseDialog(
|
||||||
registration.value.pushManager
|
registration.value.pushManager
|
||||||
.subscribe({
|
.subscribe({
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: urlBase64ToUint8Array(
|
applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
|
||||||
instance.swPublickey,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
async (subscription) => {
|
async (subscription) => {
|
||||||
|
@ -121,9 +118,7 @@ function subscribe() {
|
||||||
// When subscribe failed
|
// When subscribe failed
|
||||||
// 通知が許可されていなかったとき
|
// 通知が許可されていなかったとき
|
||||||
if (err?.name === "NotAllowedError") {
|
if (err?.name === "NotAllowedError") {
|
||||||
console.info(
|
console.info("User denied the notification permission request.");
|
||||||
"User denied the notification permission request.",
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,9 +166,7 @@ function encode(buffer: ArrayBuffer | null) {
|
||||||
*/
|
*/
|
||||||
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
const base64 = (base64String + padding)
|
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||||
.replace(/-/g, "+")
|
|
||||||
.replace(/_/g, "/");
|
|
||||||
|
|
||||||
const rawData = window.atob(base64);
|
const rawData = window.atob(base64);
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
|
@ -102,10 +102,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
| HTMLElement
|
|
||||||
| null
|
|
||||||
| undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
@ -129,10 +126,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
| HTMLElement
|
|
||||||
| null
|
|
||||||
| undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
@ -157,10 +151,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
| HTMLElement
|
|
||||||
| null
|
|
||||||
| undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
@ -182,10 +173,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
| HTMLElement
|
|
||||||
| null
|
|
||||||
| undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
@ -221,10 +209,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
| HTMLElement
|
|
||||||
| null
|
|
||||||
| undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
|
|
@ -198,9 +198,7 @@ function openDescription(kind: "words" | "users" | "range"): void {
|
||||||
};
|
};
|
||||||
|
|
||||||
popup(
|
popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkSimpleTextWindow.vue")),
|
||||||
() => import("@/components/MkSimpleTextWindow.vue"),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
title: i18n.ts.help,
|
title: i18n.ts.help,
|
||||||
description: descriptions[kind],
|
description: descriptions[kind],
|
||||||
|
|
|
@ -205,13 +205,11 @@ function queryKey() {
|
||||||
.get({
|
.get({
|
||||||
publicKey: {
|
publicKey: {
|
||||||
challenge: byteify(challengeData.value.challenge, "base64"),
|
challenge: byteify(challengeData.value.challenge, "base64"),
|
||||||
allowCredentials: challengeData.value.securityKeys.map(
|
allowCredentials: challengeData.value.securityKeys.map((key) => ({
|
||||||
(key) => ({
|
id: byteify(key.id, "hex"),
|
||||||
id: byteify(key.id, "hex"),
|
type: "public-key",
|
||||||
type: "public-key",
|
transports: ["usb", "nfc", "ble", "internal"],
|
||||||
transports: ["usb", "nfc", "ble", "internal"],
|
})),
|
||||||
}),
|
|
||||||
),
|
|
||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -226,9 +224,7 @@ function queryKey() {
|
||||||
username: username.value,
|
username: username.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
signature: hexify(credential.response.signature),
|
signature: hexify(credential.response.signature),
|
||||||
authenticatorData: hexify(
|
authenticatorData: hexify(credential.response.authenticatorData),
|
||||||
credential.response.authenticatorData,
|
|
||||||
),
|
|
||||||
clientDataJSON: hexify(credential.response.clientDataJSON),
|
clientDataJSON: hexify(credential.response.clientDataJSON),
|
||||||
credentialId: credential.id,
|
credentialId: credential.id,
|
||||||
challengeId: challengeData.value.challengeId,
|
challengeId: challengeData.value.challengeId,
|
||||||
|
@ -277,9 +273,7 @@ function onSubmit() {
|
||||||
"hcaptcha-response": hCaptchaResponse.value,
|
"hcaptcha-response": hCaptchaResponse.value,
|
||||||
"g-recaptcha-response": reCaptchaResponse.value,
|
"g-recaptcha-response": reCaptchaResponse.value,
|
||||||
token:
|
token:
|
||||||
user.value && user.value.twoFactorEnabled
|
user.value && user.value.twoFactorEnabled ? token.value : undefined,
|
||||||
? token.value
|
|
||||||
: undefined,
|
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
emit("login", res);
|
emit("login", res);
|
||||||
|
|
|
@ -79,8 +79,8 @@ const el = ref<HTMLElement>();
|
||||||
const width = ref(0);
|
const width = ref(0);
|
||||||
const height = ref(0);
|
const height = ref(0);
|
||||||
const colors = ["#eb6f92", "#9ccfd8", "#f6c177", "#f6c177", "#f6c177"];
|
const colors = ["#eb6f92", "#9ccfd8", "#f6c177", "#f6c177", "#f6c177"];
|
||||||
let stop = false,
|
let stop = false;
|
||||||
ro: ResizeObserver | undefined;
|
let ro: ResizeObserver | undefined;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!reducedMotion()) {
|
if (!reducedMotion()) {
|
||||||
|
@ -104,9 +104,7 @@ onMounted(() => {
|
||||||
};
|
};
|
||||||
particles.value.push(particle);
|
particles.value.push(particle);
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
particles.value = particles.value.filter(
|
particles.value = particles.value.filter((x) => x.id !== particle.id);
|
||||||
(x) => x.id !== particle.id,
|
|
||||||
);
|
|
||||||
}, particle.dur - 100);
|
}, particle.dur - 100);
|
||||||
|
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
|
|
|
@ -60,13 +60,10 @@ function star(ev?: MouseEvent): void {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
reaction:
|
reaction:
|
||||||
defaultStore.state.woozyMode === true
|
defaultStore.state.woozyMode === true ? "🥴" : instance.defaultReaction,
|
||||||
? "🥴"
|
|
||||||
: instance.defaultReaction,
|
|
||||||
});
|
});
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev && ((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
|
|
@ -66,8 +66,7 @@ function toggleStar(ev?: MouseEvent): void {
|
||||||
reaction: instance.defaultReaction,
|
reaction: instance.defaultReaction,
|
||||||
});
|
});
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev && ((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
||||||
((ev.currentTarget ?? ev.target) as HTMLElement | null | undefined);
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = rect.left + el.offsetWidth / 2;
|
const x = rect.left + el.offsetWidth / 2;
|
||||||
|
|
|
@ -32,14 +32,9 @@ export default defineComponent({
|
||||||
role: "tab",
|
role: "tab",
|
||||||
key: option.key,
|
key: option.key,
|
||||||
"aria-selected":
|
"aria-selected":
|
||||||
this.modelValue === option.props?.value
|
this.modelValue === option.props?.value ? "true" : "false",
|
||||||
? "true"
|
|
||||||
: "false",
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.$emit(
|
this.$emit("update:modelValue", option.props?.value);
|
||||||
"update:modelValue",
|
|
||||||
option.props?.value,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
option.children,
|
option.children,
|
||||||
|
|
|
@ -25,18 +25,12 @@ const SAFE_FOR_HTML_ID = "abcdefghijklmnopqrstuvwxyz";
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
const computedStyle = getComputedStyle(document.documentElement);
|
||||||
const idForCanvas = Array.from(Array(16))
|
const idForCanvas = Array.from(Array(16))
|
||||||
.map(
|
.map(
|
||||||
() =>
|
() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)],
|
||||||
SAFE_FOR_HTML_ID[
|
|
||||||
Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
const idForTags = Array.from(Array(16))
|
const idForTags = Array.from(Array(16))
|
||||||
.map(
|
.map(
|
||||||
() =>
|
() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)],
|
||||||
SAFE_FOR_HTML_ID[
|
|
||||||
Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
const available = ref(false);
|
const available = ref(false);
|
||||||
|
|
|
@ -174,12 +174,12 @@ provide("inWindow", true);
|
||||||
|
|
||||||
const rootEl = ref<HTMLElement | null>();
|
const rootEl = ref<HTMLElement | null>();
|
||||||
const showing = ref(true);
|
const showing = ref(true);
|
||||||
let beforeClickedAt = 0,
|
let beforeClickedAt = 0;
|
||||||
maximized = ref(false),
|
const maximized = ref(false);
|
||||||
unMaximizedTop = "",
|
let unMaximizedTop = "";
|
||||||
unMaximizedLeft = "",
|
let unMaximizedLeft = "";
|
||||||
unMaximizedWidth = "",
|
let unMaximizedWidth = "";
|
||||||
unMaximizedHeight = "";
|
let unMaximizedHeight = "";
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
showing.value = false;
|
showing.value = false;
|
||||||
|
@ -203,9 +203,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
// 最前面へ移動
|
// 最前面へ移動
|
||||||
function top() {
|
function top() {
|
||||||
if (rootEl.value) {
|
if (rootEl.value) {
|
||||||
rootEl.value.style.zIndex = os.claimZIndex(
|
rootEl.value.style.zIndex = os.claimZIndex(props.front ? "middle" : "low");
|
||||||
props.front ? "middle" : "low",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,8 +280,8 @@ function onHeaderMousedown(evt: MouseEvent) {
|
||||||
const windowHeight = main.offsetHeight;
|
const windowHeight = main.offsetHeight;
|
||||||
|
|
||||||
function move(x: number, y: number) {
|
function move(x: number, y: number) {
|
||||||
let moveLeft = x - moveBaseX,
|
let moveLeft = x - moveBaseX;
|
||||||
moveTop = y - moveBaseY;
|
let moveTop = y - moveBaseY;
|
||||||
|
|
||||||
// 下はみ出し
|
// 下はみ出し
|
||||||
if (moveTop + windowHeight > browserHeight)
|
if (moveTop + windowHeight > browserHeight)
|
||||||
|
@ -310,13 +308,9 @@ function onHeaderMousedown(evt: MouseEvent) {
|
||||||
// 動かした時
|
// 動かした時
|
||||||
dragListen((me) => {
|
dragListen((me) => {
|
||||||
const x =
|
const x =
|
||||||
me.touches && me.touches.length > 0
|
me.touches && me.touches.length > 0 ? me.touches[0].clientX : me.clientX;
|
||||||
? me.touches[0].clientX
|
|
||||||
: me.clientX;
|
|
||||||
const y =
|
const y =
|
||||||
me.touches && me.touches.length > 0
|
me.touches && me.touches.length > 0 ? me.touches[0].clientY : me.clientY;
|
||||||
? me.touches[0].clientY
|
|
||||||
: me.clientY;
|
|
||||||
|
|
||||||
move(x, y);
|
move(x, y);
|
||||||
});
|
});
|
||||||
|
|
|
@ -146,14 +146,12 @@ useInterval(
|
||||||
() => {
|
() => {
|
||||||
if (prefixEl.value) {
|
if (prefixEl.value) {
|
||||||
if (prefixEl.value.offsetWidth) {
|
if (prefixEl.value.offsetWidth) {
|
||||||
inputEl.value.style.paddingLeft =
|
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + "px";
|
||||||
prefixEl.value.offsetWidth + "px";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (suffixEl.value) {
|
if (suffixEl.value) {
|
||||||
if (suffixEl.value.offsetWidth) {
|
if (suffixEl.value.offsetWidth) {
|
||||||
inputEl.value.style.paddingRight =
|
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + "px";
|
||||||
suffixEl.value.offsetWidth + "px";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,8 +60,7 @@ export default defineComponent({
|
||||||
value: option.props?.value,
|
value: option.props?.value,
|
||||||
disabled: option.props?.disabled,
|
disabled: option.props?.disabled,
|
||||||
modelValue: this.value,
|
modelValue: this.value,
|
||||||
"onUpdate:modelValue": (value) =>
|
"onUpdate:modelValue": (value) => (this.value = value),
|
||||||
(this.value = value),
|
|
||||||
},
|
},
|
||||||
option.children,
|
option.children,
|
||||||
),
|
),
|
||||||
|
|
|
@ -120,14 +120,12 @@ useInterval(
|
||||||
() => {
|
() => {
|
||||||
if (prefixEl.value) {
|
if (prefixEl.value) {
|
||||||
if (prefixEl.value.offsetWidth) {
|
if (prefixEl.value.offsetWidth) {
|
||||||
inputEl.value.style.paddingLeft =
|
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + "px";
|
||||||
prefixEl.value.offsetWidth + "px";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (suffixEl.value) {
|
if (suffixEl.value) {
|
||||||
if (suffixEl.value.offsetWidth) {
|
if (suffixEl.value.offsetWidth) {
|
||||||
inputEl.value.style.paddingRight =
|
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + "px";
|
||||||
suffixEl.value.offsetWidth + "px";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -88,9 +88,7 @@ const choseAd = (): Ad | null => {
|
||||||
return widgetAds;
|
return widgetAds;
|
||||||
} else if (ads.length === 0) {
|
} else if (ads.length === 0) {
|
||||||
if (lowPriorityAds.length !== 0) {
|
if (lowPriorityAds.length !== 0) {
|
||||||
return lowPriorityAds[
|
return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)];
|
||||||
Math.floor(Math.random() * lowPriorityAds.length)
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,10 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let ro: ResizeObserver,
|
let ro: ResizeObserver;
|
||||||
root = ref<HTMLElement>(),
|
const root = ref<HTMLElement>();
|
||||||
content = ref<HTMLElement>(),
|
const content = ref<HTMLElement>();
|
||||||
margin = ref(0);
|
const margin = ref(0);
|
||||||
const shouldSpacerMin = inject("shouldSpacerMin", false);
|
const shouldSpacerMin = inject("shouldSpacerMin", false);
|
||||||
|
|
||||||
const adjust = (rect: { width: number; height: number }) => {
|
const adjust = (rect: { width: number; height: number }) => {
|
||||||
|
|
|
@ -51,9 +51,7 @@ const el = ref();
|
||||||
|
|
||||||
useTooltip(el, (showing) => {
|
useTooltip(el, (showing) => {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkUrlPreviewPopup.vue")),
|
||||||
() => import("@/components/MkUrlPreviewPopup.vue"),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
showing,
|
showing,
|
||||||
url: props.url,
|
url: props.url,
|
||||||
|
|
|
@ -36,11 +36,9 @@ export default defineComponent({
|
||||||
const note: Ref<Record<string, any> | null> = ref(null);
|
const note: Ref<Record<string, any> | null> = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
os.api("notes/show", { noteId: props.block.note }).then(
|
os.api("notes/show", { noteId: props.block.note }).then((result) => {
|
||||||
(result) => {
|
note.value = result;
|
||||||
note.value = result;
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -63,10 +63,7 @@ export default defineComponent({
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", blob);
|
formData.append("file", blob);
|
||||||
if (this.defaultStore.state.uploadFolder) {
|
if (this.defaultStore.state.uploadFolder) {
|
||||||
formData.append(
|
formData.append("folderId", this.defaultStore.state.uploadFolder);
|
||||||
"folderId",
|
|
||||||
this.defaultStore.state.uploadFolder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(apiUrl + "/drive/files/create", {
|
fetch(apiUrl + "/drive/files/create", {
|
||||||
|
@ -87,9 +84,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async post() {
|
async post() {
|
||||||
this.posting = true;
|
this.posting = true;
|
||||||
const file = this.block.attachCanvasImage
|
const file = this.block.attachCanvasImage ? await this.upload() : null;
|
||||||
? await this.upload()
|
|
||||||
: null;
|
|
||||||
os.apiWithDialog("notes/create", {
|
os.apiWithDialog("notes/create", {
|
||||||
text: this.text === "" ? null : this.text,
|
text: this.text === "" ? null : this.text,
|
||||||
fileIds: file ? [file.id] : undefined,
|
fileIds: file ? [file.id] : undefined,
|
||||||
|
|
|
@ -93,8 +93,8 @@ export class Router extends EventEmitter<{
|
||||||
}
|
}
|
||||||
|
|
||||||
public resolve(path: string): Resolved | null {
|
public resolve(path: string): Resolved | null {
|
||||||
let queryString: string | null = null,
|
let queryString: string | null = null;
|
||||||
hash: string | null = null;
|
let hash: string | null = null;
|
||||||
if (path[0] === "/") path = path.substring(1);
|
if (path[0] === "/") path = path.substring(1);
|
||||||
if (path.includes("#")) {
|
if (path.includes("#")) {
|
||||||
hash = path.substring(path.indexOf("#") + 1);
|
hash = path.substring(path.indexOf("#") + 1);
|
||||||
|
|
|
@ -776,8 +776,8 @@ type AwaitType<T> = T extends Promise<infer U>
|
||||||
: T extends (...args: any[]) => Promise<infer V>
|
: T extends (...args: any[]) => Promise<infer V>
|
||||||
? V
|
? V
|
||||||
: T;
|
: T;
|
||||||
let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null,
|
let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null;
|
||||||
activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null;
|
let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null;
|
||||||
export async function openEmojiPicker(
|
export async function openEmojiPicker(
|
||||||
src?: HTMLElement,
|
src?: HTMLElement,
|
||||||
opts,
|
opts,
|
||||||
|
|
|
@ -94,10 +94,7 @@ export default defineComponent({
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
search() {
|
search() {
|
||||||
if (
|
if ((this.q === "" || this.q == null) && this.selectedTags.size === 0) {
|
||||||
(this.q === "" || this.q == null) &&
|
|
||||||
this.selectedTags.size === 0
|
|
||||||
) {
|
|
||||||
this.searchEmojis = null;
|
this.searchEmojis = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -105,17 +102,13 @@ export default defineComponent({
|
||||||
if (this.selectedTags.size === 0) {
|
if (this.selectedTags.size === 0) {
|
||||||
this.searchEmojis = this.customEmojis.filter(
|
this.searchEmojis = this.customEmojis.filter(
|
||||||
(emoji) =>
|
(emoji) =>
|
||||||
emoji.name.includes(this.q) ||
|
emoji.name.includes(this.q) || emoji.aliases.includes(this.q),
|
||||||
emoji.aliases.includes(this.q),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.searchEmojis = this.customEmojis.filter(
|
this.searchEmojis = this.customEmojis.filter(
|
||||||
(emoji) =>
|
(emoji) =>
|
||||||
(emoji.name.includes(this.q) ||
|
(emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) &&
|
||||||
emoji.aliases.includes(this.q)) &&
|
[...this.selectedTags].every((t) => emoji.aliases.includes(t)),
|
||||||
[...this.selectedTags].every((t) =>
|
|
||||||
emoji.aliases.includes(t),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -276,9 +276,7 @@ function easterEgg() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (iconClicks % 6 === 0) {
|
if (iconClicks % 6 === 0) {
|
||||||
iconSrc.value =
|
iconSrc.value =
|
||||||
instance.iconUrl ||
|
instance.iconUrl || instance.faviconUrl || "/favicon.ico";
|
||||||
instance.faviconUrl ||
|
|
||||||
"/favicon.ico";
|
|
||||||
} else {
|
} else {
|
||||||
iconSrc.value = "/static-assets/woozy.png";
|
iconSrc.value = "/static-assets/woozy.png";
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,9 +254,7 @@ const headerTabs = computed(() => [
|
||||||
|
|
||||||
definePageMetadata(
|
definePageMetadata(
|
||||||
computed(() => ({
|
computed(() => ({
|
||||||
title: file.value
|
title: file.value ? i18n.ts.file + ": " + file.value.name : i18n.ts.file,
|
||||||
? i18n.ts.file + ": " + file.value.name
|
|
||||||
: i18n.ts.file,
|
|
||||||
icon: `${icon("ph-file")}`,
|
icon: `${icon("ph-file")}`,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
|
@ -169,12 +169,10 @@ onMounted(() => {
|
||||||
if (tabEl && tabHighlightEl.value) {
|
if (tabEl && tabHighlightEl.value) {
|
||||||
// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
|
// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
|
||||||
// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
|
// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
|
||||||
const parentRect =
|
const parentRect = tabEl.parentElement.getBoundingClientRect();
|
||||||
tabEl.parentElement.getBoundingClientRect();
|
|
||||||
const rect = tabEl.getBoundingClientRect();
|
const rect = tabEl.getBoundingClientRect();
|
||||||
tabHighlightEl.value.style.width = rect.width + "px";
|
tabHighlightEl.value.style.width = rect.width + "px";
|
||||||
tabHighlightEl.value.style.left =
|
tabHighlightEl.value.style.left = rect.left - parentRect.left + "px";
|
||||||
rect.left - parentRect.left + "px";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -97,9 +97,7 @@ function remove(announcement) {
|
||||||
text: i18n.t("removeAreYouSure", { x: announcement.title }),
|
text: i18n.t("removeAreYouSure", { x: announcement.title }),
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
announcements.value = announcements.value.filter(
|
announcements.value = announcements.value.filter((x) => x !== announcement);
|
||||||
(x) => x !== announcement,
|
|
||||||
);
|
|
||||||
os.api("admin/announcements/delete", announcement);
|
os.api("admin/announcements/delete", announcement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,7 @@ import icon from "@/scripts/icon";
|
||||||
const databasePromiseFactory = () =>
|
const databasePromiseFactory = () =>
|
||||||
os
|
os
|
||||||
.api("admin/get-table-stats")
|
.api("admin/get-table-stats")
|
||||||
.then((res) =>
|
.then((res) => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
|
||||||
Object.entries(res).sort((a, b) => b[1].size - a[1].size),
|
|
||||||
);
|
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
|
|
@ -184,9 +184,7 @@ const remotePagination = {
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
query:
|
query:
|
||||||
queryRemote.value && queryRemote.value !== ""
|
queryRemote.value && queryRemote.value !== "" ? queryRemote.value : null,
|
||||||
? queryRemote.value
|
|
||||||
: null,
|
|
||||||
host: host.value && host.value !== "" ? host.value : null,
|
host: host.value && host.value !== "" ? host.value : null,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
@ -203,9 +201,7 @@ const selectAll = () => {
|
||||||
|
|
||||||
const toggleSelect = (emoji) => {
|
const toggleSelect = (emoji) => {
|
||||||
if (selectedEmojis.value.includes(emoji.id)) {
|
if (selectedEmojis.value.includes(emoji.id)) {
|
||||||
selectedEmojis.value = selectedEmojis.value.filter(
|
selectedEmojis.value = selectedEmojis.value.filter((x) => x !== emoji.id);
|
||||||
(x) => x !== emoji.id,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
selectedEmojis.value.push(emoji.id);
|
selectedEmojis.value.push(emoji.id);
|
||||||
}
|
}
|
||||||
|
@ -305,9 +301,7 @@ const menu = (ev: MouseEvent) => {
|
||||||
icon: `${icon("ph-upload-simple")}`,
|
icon: `${icon("ph-upload-simple")}`,
|
||||||
text: i18n.ts.importZip,
|
text: i18n.ts.importZip,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const file = await selectFile(
|
const file = await selectFile(ev.currentTarget ?? ev.target);
|
||||||
ev.currentTarget ?? ev.target,
|
|
||||||
);
|
|
||||||
os.api("admin/emoji/import-zip", {
|
os.api("admin/emoji/import-zip", {
|
||||||
fileId: file.id,
|
fileId: file.id,
|
||||||
})
|
})
|
||||||
|
@ -329,10 +323,7 @@ const menu = (ev: MouseEvent) => {
|
||||||
icon: `${icon("ph-info")}`,
|
icon: `${icon("ph-info")}`,
|
||||||
text: i18n.ts.emojiPackCreator,
|
text: i18n.ts.emojiPackCreator,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(
|
window.open("https://firefish.dev/firefish/emoji-gen", "_blank");
|
||||||
"https://firefish.dev/firefish/emoji-gen",
|
|
||||||
"_blank",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -103,9 +103,7 @@ const pagination = {
|
||||||
userId: userId.value && userId.value !== "" ? userId.value : null,
|
userId: userId.value && userId.value !== "" ? userId.value : null,
|
||||||
origin: origin.value,
|
origin: origin.value,
|
||||||
hostname:
|
hostname:
|
||||||
searchHost.value && searchHost.value !== ""
|
searchHost.value && searchHost.value !== "" ? searchHost.value : null,
|
||||||
? searchHost.value
|
|
||||||
: null,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,7 @@ async function init() {
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog("admin/update-meta", {
|
os.apiWithDialog("admin/update-meta", {
|
||||||
hiddenTags:
|
hiddenTags: hiddenTags.value.split("\n").map((h: string) => h.trim()) || [],
|
||||||
hiddenTags.value.split("\n").map((h: string) => h.trim()) || [],
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
});
|
});
|
||||||
|
|
|
@ -221,31 +221,25 @@ const menuDef = computed(() => [
|
||||||
icon: `${icon("ph-gear-six")}`,
|
icon: `${icon("ph-gear-six")}`,
|
||||||
text: i18n.ts.general,
|
text: i18n.ts.general,
|
||||||
to: "/admin/settings",
|
to: "/admin/settings",
|
||||||
active:
|
active: currentPage.value?.route.name === "settings",
|
||||||
currentPage.value?.route.name === "settings",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-envelope-simple-open")}`,
|
icon: `${icon("ph-envelope-simple-open")}`,
|
||||||
text: i18n.ts.emailServer,
|
text: i18n.ts.emailServer,
|
||||||
to: "/admin/email-settings",
|
to: "/admin/email-settings",
|
||||||
active:
|
active: currentPage.value?.route.name === "email-settings",
|
||||||
currentPage.value?.route.name ===
|
|
||||||
"email-settings",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-cloud")}`,
|
icon: `${icon("ph-cloud")}`,
|
||||||
text: i18n.ts.objectStorage,
|
text: i18n.ts.objectStorage,
|
||||||
to: "/admin/object-storage",
|
to: "/admin/object-storage",
|
||||||
active:
|
active: currentPage.value?.route.name === "object-storage",
|
||||||
currentPage.value?.route.name ===
|
|
||||||
"object-storage",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-lock")}`,
|
icon: `${icon("ph-lock")}`,
|
||||||
text: i18n.ts.security,
|
text: i18n.ts.security,
|
||||||
to: "/admin/security",
|
to: "/admin/security",
|
||||||
active:
|
active: currentPage.value?.route.name === "security",
|
||||||
currentPage.value?.route.name === "security",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-arrows-merge")}`,
|
icon: `${icon("ph-arrows-merge")}`,
|
||||||
|
@ -257,38 +251,31 @@ const menuDef = computed(() => [
|
||||||
icon: `${icon("ph-prohibit")}`,
|
icon: `${icon("ph-prohibit")}`,
|
||||||
text: i18n.ts.instanceBlocking,
|
text: i18n.ts.instanceBlocking,
|
||||||
to: "/admin/instance-block",
|
to: "/admin/instance-block",
|
||||||
active:
|
active: currentPage.value?.route.name === "instance-block",
|
||||||
currentPage.value?.route.name ===
|
|
||||||
"instance-block",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-hash")}`,
|
icon: `${icon("ph-hash")}`,
|
||||||
text: i18n.ts.hiddenTags,
|
text: i18n.ts.hiddenTags,
|
||||||
to: "/admin/hashtags",
|
to: "/admin/hashtags",
|
||||||
active:
|
active: currentPage.value?.route.name === "hashtags",
|
||||||
currentPage.value?.route.name === "hashtags",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-ghost")}`,
|
icon: `${icon("ph-ghost")}`,
|
||||||
text: i18n.ts.proxyAccount,
|
text: i18n.ts.proxyAccount,
|
||||||
to: "/admin/proxy-account",
|
to: "/admin/proxy-account",
|
||||||
active:
|
active: currentPage.value?.route.name === "proxy-account",
|
||||||
currentPage.value?.route.name ===
|
|
||||||
"proxy-account",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-database")}`,
|
icon: `${icon("ph-database")}`,
|
||||||
text: i18n.ts.database,
|
text: i18n.ts.database,
|
||||||
to: "/admin/database",
|
to: "/admin/database",
|
||||||
active:
|
active: currentPage.value?.route.name === "database",
|
||||||
currentPage.value?.route.name === "database",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: `${icon("ph-flask")}`,
|
icon: `${icon("ph-flask")}`,
|
||||||
text: i18n.ts._experiments.title,
|
text: i18n.ts._experiments.title,
|
||||||
to: "/admin/experiments",
|
to: "/admin/experiments",
|
||||||
active:
|
active: currentPage.value?.route.name === "experiments",
|
||||||
currentPage.value?.route.name === "experiments",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,8 +70,7 @@ async function init() {
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog("admin/update-meta", {
|
os.apiWithDialog("admin/update-meta", {
|
||||||
blockedHosts: blockedHosts.value.split("\n").map((h) => h.trim()) || [],
|
blockedHosts: blockedHosts.value.split("\n").map((h) => h.trim()) || [],
|
||||||
silencedHosts:
|
silencedHosts: silencedHosts.value.split("\n").map((h) => h.trim()) || [],
|
||||||
silencedHosts.value.split("\n").map((h) => h.trim()) || [],
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
});
|
});
|
||||||
|
|
|
@ -530,7 +530,8 @@ function isValidHttpUrl(src: string) {
|
||||||
function parseMoreUrls(src: string): { name: string; url: string }[] {
|
function parseMoreUrls(src: string): { name: string; url: string }[] {
|
||||||
const toReturn: { name: string; url: string }[] = [];
|
const toReturn: { name: string; url: string }[] = [];
|
||||||
const pattern = /"(.+)"\s*:\s*(http.+)/;
|
const pattern = /"(.+)"\s*:\s*(http.+)/;
|
||||||
src.trim()
|
src
|
||||||
|
.trim()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.forEach((line) => {
|
.forEach((line) => {
|
||||||
const match = pattern.exec(line);
|
const match = pattern.exec(line);
|
||||||
|
@ -590,9 +591,7 @@ async function init() {
|
||||||
defaultReaction.value = ["⭐", "👍", "❤️"].includes(meta.defaultReaction)
|
defaultReaction.value = ["⭐", "👍", "❤️"].includes(meta.defaultReaction)
|
||||||
? meta.defaultReaction
|
? meta.defaultReaction
|
||||||
: "custom";
|
: "custom";
|
||||||
defaultReactionCustom.value = ["⭐", "👍", "❤️"].includes(
|
defaultReactionCustom.value = ["⭐", "👍", "❤️"].includes(meta.defaultReaction)
|
||||||
meta.defaultReaction,
|
|
||||||
)
|
|
||||||
? ""
|
? ""
|
||||||
: meta.defaultReaction;
|
: meta.defaultReaction;
|
||||||
enableServerMachineStats.value = meta.enableServerMachineStats;
|
enableServerMachineStats.value = meta.enableServerMachineStats;
|
||||||
|
|
|
@ -72,11 +72,9 @@ onMounted(() => {
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
|
|
||||||
if (session.value.app.isAuthorized) {
|
if (session.value.app.isAuthorized) {
|
||||||
os.api("auth/accept", { token: session.value.token }).then(
|
os.api("auth/accept", { token: session.value.token }).then(() => {
|
||||||
() => {
|
accepted();
|
||||||
accepted();
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
state.value = "waiting";
|
state.value = "waiting";
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, ref } from "vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import { notePage } from "@/filters/note";
|
import { notePage } from "@/filters/note";
|
||||||
import { onMounted, ref, defineAsyncComponent } from "vue";
|
|
||||||
const XNotFound = defineAsyncComponent(() => import("./not-found.vue"));
|
const XNotFound = defineAsyncComponent(() => import("./not-found.vue"));
|
||||||
|
|
||||||
const err = ref(false);
|
const err = ref(false);
|
||||||
|
|
|
@ -75,28 +75,25 @@ const headerActions = computed(() =>
|
||||||
icon: `${icon("ph-pencil")}`,
|
icon: `${icon("ph-pencil")}`,
|
||||||
text: i18n.ts.toEdit,
|
text: i18n.ts.toEdit,
|
||||||
handler: async (): Promise<void> => {
|
handler: async (): Promise<void> => {
|
||||||
const { canceled, result } = await os.form(
|
const { canceled, result } = await os.form(clip.value.name, {
|
||||||
clip.value.name,
|
name: {
|
||||||
{
|
type: "string",
|
||||||
name: {
|
label: i18n.ts.name,
|
||||||
type: "string",
|
default: clip.value.name,
|
||||||
label: i18n.ts.name,
|
|
||||||
default: clip.value.name,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: "string",
|
|
||||||
required: false,
|
|
||||||
multiline: true,
|
|
||||||
label: i18n.ts.description,
|
|
||||||
default: clip.value.description,
|
|
||||||
},
|
|
||||||
isPublic: {
|
|
||||||
type: "boolean",
|
|
||||||
label: i18n.ts.public,
|
|
||||||
default: clip.value.isPublic,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
description: {
|
||||||
|
type: "string",
|
||||||
|
required: false,
|
||||||
|
multiline: true,
|
||||||
|
label: i18n.ts.description,
|
||||||
|
default: clip.value.description,
|
||||||
|
},
|
||||||
|
isPublic: {
|
||||||
|
type: "boolean",
|
||||||
|
label: i18n.ts.public,
|
||||||
|
default: clip.value.isPublic,
|
||||||
|
},
|
||||||
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog("clips/update", {
|
os.apiWithDialog("clips/update", {
|
||||||
|
|
|
@ -37,14 +37,12 @@ function menu(ev) {
|
||||||
text: i18n.ts.license,
|
text: i18n.ts.license,
|
||||||
icon: `${icon("ph-info")}`,
|
icon: `${icon("ph-info")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.apiGet("emoji", { name: props.emoji.name }).then(
|
os.apiGet("emoji", { name: props.emoji.name }).then((res) => {
|
||||||
(res) => {
|
os.alert({
|
||||||
os.alert({
|
type: "info",
|
||||||
type: "info",
|
text: `${res.license || i18n.ts.notSet}`,
|
||||||
text: `${res.license || i18n.ts.notSet}`,
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -182,8 +182,7 @@ function onMessage(message): void {
|
||||||
!(
|
!(
|
||||||
(m.recipientId === message.recipientId &&
|
(m.recipientId === message.recipientId &&
|
||||||
m.userId === message.userId) ||
|
m.userId === message.userId) ||
|
||||||
(m.recipientId === message.userId &&
|
(m.recipientId === message.userId && m.userId === message.recipientId)
|
||||||
m.userId === message.recipientId)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -267,8 +266,7 @@ onMounted(() => {
|
||||||
const _messages = userMessages.concat(groupMessages);
|
const _messages = userMessages.concat(groupMessages);
|
||||||
_messages.sort(
|
_messages.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.createdAt).getTime() -
|
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||||
new Date(a.createdAt).getTime(),
|
|
||||||
);
|
);
|
||||||
messages.value = _messages;
|
messages.value = _messages;
|
||||||
},
|
},
|
||||||
|
|
|
@ -90,8 +90,7 @@ const draftKey = computed(() =>
|
||||||
props.user ? "user:" + props.user.id : "group:" + props.group?.id,
|
props.user ? "user:" + props.user.id : "group:" + props.group?.id,
|
||||||
);
|
);
|
||||||
const canSend = computed(
|
const canSend = computed(
|
||||||
() =>
|
() => (text.value != null && text.value.trim() !== "") || file.value != null,
|
||||||
(text.value != null && text.value.trim() !== "") || file.value != null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
watch([text, file], saveDraft);
|
watch([text, file], saveDraft);
|
||||||
|
|
|
@ -280,9 +280,7 @@ function onRead(x) {
|
||||||
if (!Array.isArray(x)) x = [x];
|
if (!Array.isArray(x)) x = [x];
|
||||||
for (const id of x) {
|
for (const id of x) {
|
||||||
if (pagingComponent.value.items.some((y) => y.id === id)) {
|
if (pagingComponent.value.items.some((y) => y.id === id)) {
|
||||||
const exist = pagingComponent.value.items
|
const exist = pagingComponent.value.items.map((y) => y.id).indexOf(id);
|
||||||
.map((y) => y.id)
|
|
||||||
.indexOf(id);
|
|
||||||
pagingComponent.value.items[exist] = {
|
pagingComponent.value.items[exist] = {
|
||||||
...pagingComponent.value.items[exist],
|
...pagingComponent.value.items[exist],
|
||||||
isRead: true,
|
isRead: true,
|
||||||
|
@ -292,15 +290,10 @@ function onRead(x) {
|
||||||
} else if (group.value) {
|
} else if (group.value) {
|
||||||
for (const id of x.ids) {
|
for (const id of x.ids) {
|
||||||
if (pagingComponent.value.items.some((y) => y.id === id)) {
|
if (pagingComponent.value.items.some((y) => y.id === id)) {
|
||||||
const exist = pagingComponent.value.items
|
const exist = pagingComponent.value.items.map((y) => y.id).indexOf(id);
|
||||||
.map((y) => y.id)
|
|
||||||
.indexOf(id);
|
|
||||||
pagingComponent.value.items[exist] = {
|
pagingComponent.value.items[exist] = {
|
||||||
...pagingComponent.value.items[exist],
|
...pagingComponent.value.items[exist],
|
||||||
reads: [
|
reads: [...pagingComponent.value.items[exist].reads, x.userId],
|
||||||
...pagingComponent.value.items[exist].reads,
|
|
||||||
x.userId,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,8 +72,8 @@ const headerTabs = computed(() => []);
|
||||||
|
|
||||||
const list = ref<typeof MkPagination | null>(null);
|
const list = ref<typeof MkPagination | null>(null);
|
||||||
|
|
||||||
let isCached = false,
|
let isCached = false;
|
||||||
refreshTimer: number | null = null;
|
let refreshTimer: number | null = null;
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
|
|
|
@ -175,20 +175,15 @@ definePageMetadata(
|
||||||
appearNote.value
|
appearNote.value
|
||||||
? {
|
? {
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user:
|
user: appearNote.value.user.name || appearNote.value.user.username,
|
||||||
appearNote.value.user.name ||
|
|
||||||
appearNote.value.user.username,
|
|
||||||
}),
|
}),
|
||||||
subtitle: new Date(
|
subtitle: new Date(appearNote.value.createdAt).toLocaleString(),
|
||||||
appearNote.value.createdAt,
|
|
||||||
).toLocaleString(),
|
|
||||||
avatar: appearNote.value.user,
|
avatar: appearNote.value.user,
|
||||||
path: `/notes/${appearNote.value.id}`,
|
path: `/notes/${appearNote.value.id}`,
|
||||||
share: {
|
share: {
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user:
|
user:
|
||||||
appearNote.value.user.name ||
|
appearNote.value.user.name || appearNote.value.user.username,
|
||||||
appearNote.value.user.username,
|
|
||||||
}),
|
}),
|
||||||
text: appearNote.value.text,
|
text: appearNote.value.text,
|
||||||
},
|
},
|
||||||
|
|
|
@ -164,9 +164,7 @@ export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
XContainer,
|
XContainer,
|
||||||
MkTextarea,
|
MkTextarea,
|
||||||
XV: defineAsyncComponent(
|
XV: defineAsyncComponent(() => import("./page-editor.script-block.vue")),
|
||||||
() => import("./page-editor.script-block.vue"),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
inject: ["getScriptBlockList"],
|
inject: ["getScriptBlockList"],
|
||||||
|
@ -228,12 +226,10 @@ export default defineComponent({
|
||||||
watch: {
|
watch: {
|
||||||
slots: {
|
slots: {
|
||||||
handler() {
|
handler() {
|
||||||
this.modelValue.value.slots = this.slots
|
this.modelValue.value.slots = this.slots.split("\n").map((x) => ({
|
||||||
.split("\n")
|
name: x,
|
||||||
.map((x) => ({
|
type: null,
|
||||||
name: x,
|
}));
|
||||||
type: null,
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
@ -243,9 +239,7 @@ export default defineComponent({
|
||||||
if (this.modelValue.value == null) this.modelValue.value = null;
|
if (this.modelValue.value == null) this.modelValue.value = null;
|
||||||
|
|
||||||
if (this.modelValue.value && this.modelValue.value.slots)
|
if (this.modelValue.value && this.modelValue.value.slots)
|
||||||
this.slots = this.modelValue.value.slots
|
this.slots = this.modelValue.value.slots.map((x) => x.name).join("\n");
|
||||||
.map((x) => x.name)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.modelValue.type,
|
() => this.modelValue.type,
|
||||||
|
@ -261,10 +255,7 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.modelValue.type && this.modelValue.type.startsWith("fn:")) {
|
||||||
this.modelValue.type &&
|
|
||||||
this.modelValue.type.startsWith("fn:")
|
|
||||||
) {
|
|
||||||
const fnName = this.modelValue.type.split(":")[1];
|
const fnName = this.modelValue.type.split(":")[1];
|
||||||
const fn = this.hpml.getVarByName(fnName);
|
const fn = this.hpml.getVarByName(fnName);
|
||||||
|
|
||||||
|
@ -280,27 +271,17 @@ export default defineComponent({
|
||||||
if (isLiteralValue(this.modelValue)) return;
|
if (isLiteralValue(this.modelValue)) return;
|
||||||
|
|
||||||
const empties = [];
|
const empties = [];
|
||||||
for (
|
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
|
||||||
let i = 0;
|
|
||||||
i < funcDefs[this.modelValue.type].in.length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
empties.push({ id, type: null });
|
empties.push({ id, type: null });
|
||||||
}
|
}
|
||||||
this.modelValue.args = empties;
|
this.modelValue.args = empties;
|
||||||
|
|
||||||
for (
|
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
|
||||||
let i = 0;
|
|
||||||
i < funcDefs[this.modelValue.type].in.length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
const inType = funcDefs[this.modelValue.type].in[i];
|
const inType = funcDefs[this.modelValue.type].in[i];
|
||||||
if (typeof inType !== "number") {
|
if (typeof inType !== "number") {
|
||||||
if (inType === "number")
|
if (inType === "number") this.modelValue.args[i].type = "number";
|
||||||
this.modelValue.args[i].type = "number";
|
if (inType === "string") this.modelValue.args[i].type = "text";
|
||||||
if (inType === "string")
|
|
||||||
this.modelValue.args[i].type = "text";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -47,7 +47,6 @@
|
||||||
v-tooltip="i18n.ts._pages.viewSource"
|
v-tooltip="i18n.ts._pages.viewSource"
|
||||||
:to="`/@${username}/pages/${pageName}/view-source`"
|
:to="`/@${username}/pages/${pageName}/view-source`"
|
||||||
class="menu _button"
|
class="menu _button"
|
||||||
style="transform: translateY(2px)"
|
|
||||||
><i :class="icon('ph-code')"
|
><i :class="icon('ph-code')"
|
||||||
/></MkA>
|
/></MkA>
|
||||||
<template
|
<template
|
||||||
|
@ -59,7 +58,6 @@
|
||||||
v-tooltip="i18n.ts._pages.editPage"
|
v-tooltip="i18n.ts._pages.editPage"
|
||||||
class="menu _button"
|
class="menu _button"
|
||||||
:to="`/pages/edit/${page.id}`"
|
:to="`/pages/edit/${page.id}`"
|
||||||
style="transform: translateY(2px)"
|
|
||||||
><i :class="icon('ph-pencil')"
|
><i :class="icon('ph-pencil')"
|
||||||
/></MkA>
|
/></MkA>
|
||||||
<button
|
<button
|
||||||
|
@ -215,7 +213,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { shareAvailable } from "@/scripts/share-available";
|
import { shareAvailable } from "@/scripts/share-available";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import icon from "@/scripts/icon";
|
import icon from "@/scripts/icon";
|
||||||
import { isSignedIn } from "@/me";
|
import { me, isSignedIn } from "@/me";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
pageName: string;
|
pageName: string;
|
||||||
|
@ -381,6 +379,7 @@ definePageMetadata(
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0 0 8px var(--shadow);
|
text-shadow: 0 0 8px var(--shadow);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .koudoku {
|
> .koudoku {
|
||||||
|
|
|
@ -64,9 +64,7 @@ function fetchKeys() {
|
||||||
os.api("i/registry/keys-with-type", {
|
os.api("i/registry/keys-with-type", {
|
||||||
scope: scope.value,
|
scope: scope.value,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
keys.value = Object.entries(res).sort((a, b) =>
|
keys.value = Object.entries(res).sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
a[0].localeCompare(b[0]),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,9 +56,7 @@ async function save() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.token == null) {
|
if (props.token == null) {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkForgotPassword.vue")),
|
||||||
() => import("@/components/MkForgotPassword.vue"),
|
|
||||||
),
|
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
"closed",
|
"closed",
|
||||||
|
|
|
@ -89,10 +89,7 @@ async function run() {
|
||||||
out: (value) => {
|
out: (value) => {
|
||||||
logs.value.push({
|
logs.value.push({
|
||||||
id: Math.random(),
|
id: Math.random(),
|
||||||
text:
|
text: value.type === "str" ? value.value : utils.valToString(value),
|
||||||
value.type === "str"
|
|
||||||
? value.value
|
|
||||||
: utils.valToString(value),
|
|
||||||
print: true,
|
print: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -78,8 +78,7 @@ const notesPagination = {
|
||||||
host: props.host == null ? undefined : getHost(props.host),
|
host: props.host == null ? undefined : getHost(props.host),
|
||||||
sinceDate:
|
sinceDate:
|
||||||
props.since == null ? undefined : getUnixTime(props.since, false),
|
props.since == null ? undefined : getUnixTime(props.since, false),
|
||||||
untilDate:
|
untilDate: props.until == null ? undefined : getUnixTime(props.until, true),
|
||||||
props.until == null ? undefined : getUnixTime(props.until, true),
|
|
||||||
withFiles: props.withFiles === "1",
|
withFiles: props.withFiles === "1",
|
||||||
searchCwAndAlt: props.searchCwAndAlt === "1",
|
searchCwAndAlt: props.searchCwAndAlt === "1",
|
||||||
channelId: props.channel,
|
channelId: props.channel,
|
||||||
|
|
|
@ -107,8 +107,7 @@ async function install() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, author, description, permissions, config } =
|
const { name, version, author, description, permissions, config } = metadata;
|
||||||
metadata;
|
|
||||||
if (name == null || version == null || author == null) {
|
if (name == null || version == null || author == null) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -123,29 +122,22 @@ async function install() {
|
||||||
: await new Promise((res, rej) => {
|
: await new Promise((res, rej) => {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(
|
||||||
() =>
|
() => import("@/components/MkTokenGenerateWindow.vue"),
|
||||||
import(
|
|
||||||
"@/components/MkTokenGenerateWindow.vue"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
title: i18n.ts.tokenRequested,
|
title: i18n.ts.tokenRequested,
|
||||||
information:
|
information: i18n.ts.pluginTokenRequestedDescription,
|
||||||
i18n.ts.pluginTokenRequestedDescription,
|
|
||||||
initialName: name,
|
initialName: name,
|
||||||
initialPermissions: permissions,
|
initialPermissions: permissions,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
done: async (result) => {
|
done: async (result) => {
|
||||||
const { name, permissions } = result;
|
const { name, permissions } = result;
|
||||||
const { token } = await os.api(
|
const { token } = await os.api("miauth/gen-token", {
|
||||||
"miauth/gen-token",
|
session: null,
|
||||||
{
|
name,
|
||||||
session: null,
|
permission: permissions,
|
||||||
name,
|
});
|
||||||
permission: permissions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
res(token);
|
res(token);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -196,10 +196,7 @@ function validate(profile: unknown): void {
|
||||||
|
|
||||||
// Check if createdAt and updatedAt is Date
|
// Check if createdAt and updatedAt is Date
|
||||||
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
// https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
|
||||||
if (
|
if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt).getTime()))
|
||||||
!profile.createdAt ||
|
|
||||||
Number.isNaN(new Date(profile.createdAt).getTime())
|
|
||||||
)
|
|
||||||
throw new Error("createdAt is falsy or not Date");
|
throw new Error("createdAt is falsy or not Date");
|
||||||
if (profile.updatedAt) {
|
if (profile.updatedAt) {
|
||||||
if (Number.isNaN(new Date(profile.updatedAt).getTime())) {
|
if (Number.isNaN(new Date(profile.updatedAt).getTime())) {
|
||||||
|
@ -228,10 +225,7 @@ function getSettings(): Profile["settings"] {
|
||||||
hot,
|
hot,
|
||||||
cold,
|
cold,
|
||||||
fontSize: localStorage.getItem("fontSize"),
|
fontSize: localStorage.getItem("fontSize"),
|
||||||
useSystemFont: localStorage.getItem("useSystemFont") as
|
useSystemFont: localStorage.getItem("useSystemFont") as "t" | "f" | null,
|
||||||
| "t"
|
|
||||||
| "f"
|
|
||||||
| null,
|
|
||||||
wallpaper: localStorage.getItem("wallpaper"),
|
wallpaper: localStorage.getItem("wallpaper"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -468,12 +462,9 @@ function menu(ev: MouseEvent, profileId: string) {
|
||||||
text: i18n.ts.download,
|
text: i18n.ts.download,
|
||||||
icon: `${icon("ph-download-simple")}`,
|
icon: `${icon("ph-download-simple")}`,
|
||||||
href: URL.createObjectURL(
|
href: URL.createObjectURL(
|
||||||
new Blob(
|
new Blob([JSON.stringify(profiles.value[profileId], null, 2)], {
|
||||||
[JSON.stringify(profiles.value[profileId], null, 2)],
|
type: "application/json",
|
||||||
{
|
}),
|
||||||
type: "application/json",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
download: `${profiles.value[profileId].name}.json`,
|
download: `${profiles.value[profileId].name}.json`,
|
||||||
},
|
},
|
||||||
|
@ -502,20 +493,17 @@ function menu(ev: MouseEvent, profileId: string) {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// streamingのuser storage updateイベントを監視して更新
|
// streamingのuser storage updateイベントを監視して更新
|
||||||
connection?.on(
|
connection?.on("registryUpdated", ({ scope: recievedScope, key, value }) => {
|
||||||
"registryUpdated",
|
if (
|
||||||
({ scope: recievedScope, key, value }) => {
|
!recievedScope ||
|
||||||
if (
|
recievedScope.length !== scope.length ||
|
||||||
!recievedScope ||
|
recievedScope[0] !== scope[0]
|
||||||
recievedScope.length !== scope.length ||
|
)
|
||||||
recievedScope[0] !== scope[0]
|
return;
|
||||||
)
|
if (!profiles.value) return;
|
||||||
return;
|
|
||||||
if (!profiles.value) return;
|
|
||||||
|
|
||||||
profiles.value[key] = value;
|
profiles.value[key] = value;
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
|
@ -213,9 +213,7 @@ while (fields.length < 4) {
|
||||||
|
|
||||||
function saveFields() {
|
function saveFields() {
|
||||||
os.apiWithDialog("i/update", {
|
os.apiWithDialog("i/update", {
|
||||||
fields: fields.filter(
|
fields: fields.filter((field) => field.name !== "" && field.value !== ""),
|
||||||
(field) => field.name !== "" && field.value !== "",
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,9 +179,7 @@ function remove(reaction, ev: MouseEvent) {
|
||||||
{
|
{
|
||||||
text: i18n.ts.remove,
|
text: i18n.ts.remove,
|
||||||
action: () => {
|
action: () => {
|
||||||
reactions.value = reactions.value.filter(
|
reactions.value = reactions.value.filter((x) => x !== reaction);
|
||||||
(x) => x !== reaction,
|
|
||||||
);
|
|
||||||
save();
|
save();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -192,9 +190,7 @@ function remove(reaction, ev: MouseEvent) {
|
||||||
|
|
||||||
function preview(ev: MouseEvent) {
|
function preview(ev: MouseEvent) {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(() => import("@/components/MkEmojiPickerDialog.vue")),
|
||||||
() => import("@/components/MkEmojiPickerDialog.vue"),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
asReactionPicker: true,
|
asReactionPicker: true,
|
||||||
src: ev.currentTarget ?? ev.target,
|
src: ev.currentTarget ?? ev.target,
|
||||||
|
|
|
@ -71,13 +71,11 @@ const pagination = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function change() {
|
async function change() {
|
||||||
const { canceled: canceled1, result: currentPassword } = await os.inputText(
|
const { canceled: canceled1, result: currentPassword } = await os.inputText({
|
||||||
{
|
title: i18n.ts.currentPassword,
|
||||||
title: i18n.ts.currentPassword,
|
type: "password",
|
||||||
type: "password",
|
autocomplete: "current-password",
|
||||||
autocomplete: "current-password",
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
if (canceled1) return;
|
if (canceled1) return;
|
||||||
|
|
||||||
const { canceled: canceled2, result: newPassword } = await os.inputText({
|
const { canceled: canceled2, result: newPassword } = await os.inputText({
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue