Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
294c9840de | |||
568ecd9477 | |||
169f3ed541 | |||
ff7ae427fd | |||
1597415340 | |||
47f3261b9f | |||
9e68eefbb7 | |||
630c531d99 | |||
c7da2a4b5f | |||
692078f490 | |||
0e29e864c8 | |||
1b7a601d27 | |||
a96076ee5b | |||
d580622d1b | |||
6edccad4dd | |||
8fa47dbcb1 | |||
157f4bbc21 | |||
3b0d0df068 | |||
69802a9f00 | |||
b940da45af | |||
bd6de0e204 | |||
958074e347 | |||
988ac80087 | |||
1c7c72181e | |||
6857153367 | |||
0a3a0f3beb | |||
e92e83746d | |||
3b34b3e9ea | |||
9506f53691 | |||
92dc6db134 | |||
1b88a7bc03 |
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,7 +1,31 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
12.0.0 indigo (unreleased)
|
||||
12.2.0 (2020/02/06)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* UIのアニメーションを無効にできるように
|
||||
* トークで絵文字ピッカーを表示できるように
|
||||
* 戻るボタンだけでなく、ホームボタンを押してホームに戻ったときもスクロール位置を復元するように
|
||||
* タブを見ていないときのタイムライン通知を削除
|
||||
|
||||
### 🐛Fixes
|
||||
* PWAとしてインストールできなかったのを修正
|
||||
* トークでドライブからファイルを添付出来ない問題を修正
|
||||
|
||||
12.1.0 (2020/02/06)
|
||||
--------------------
|
||||
### ✨Improvements
|
||||
* サーバー切断時に自動でリロードできるように
|
||||
|
||||
### 🐛Fixes
|
||||
* もっと読み込むを続けていくと表示が遅くなっていく問題を修正
|
||||
* Renote メニューが自分の投稿のRenoteでない限り表示されない問題を修正
|
||||
* MFM jump, spin, title が効かない問題を修正
|
||||
* AP: Likeで正しいActivity IDを提示するように修正
|
||||
* AP: Misskey以外からのトークの返信が受け取れないのを修正
|
||||
|
||||
12.0.0 indigo (2020/02/06)
|
||||
--------------------
|
||||
Misskey v12では、クライアントが設計し直され、全く新しいUIに生まれ変わりました。
|
||||
レスポンシブになり、ひとつのコードで様々なデバイスに対応できるようにしました。
|
||||
|
@ -28,7 +28,7 @@ gotIt: "わかった"
|
||||
cancel: "キャンセル"
|
||||
enterUsername: "ユーザー名を入力"
|
||||
renotedBy: "{user}がRenote"
|
||||
noNotes: "投稿はありません"
|
||||
noNotes: ノートはありません"
|
||||
noNotifications: "通知はありません"
|
||||
instance: "インスタンス"
|
||||
settings: "設定"
|
||||
@ -66,13 +66,13 @@ import: "インポート"
|
||||
export: "エクスポート"
|
||||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付した投稿も消えます。"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを添付したノートも消えます。"
|
||||
unfollowConfirm: "{name}のフォローを解除しますか?"
|
||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||
lists: "リスト"
|
||||
noLists: "リストはありません"
|
||||
notes: "投稿"
|
||||
notes: "ノート"
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
followsYou: "フォローされています"
|
||||
@ -95,7 +95,7 @@ enterEmoji: "絵文字を入力"
|
||||
renote: "Renote"
|
||||
unrenote: "Renote解除"
|
||||
quote: "引用"
|
||||
pinnedNote: "ピン留めされた投稿"
|
||||
pinnedNote: "ピン留めされたノート"
|
||||
you: "あなた"
|
||||
clickToShow: "クリックして表示"
|
||||
sensitive: "閲覧注意"
|
||||
@ -179,7 +179,7 @@ mutedUsers: "ミュートしたユーザー"
|
||||
blockedUsers: "ブロックしたユーザー"
|
||||
noUsers: "ユーザーはいません"
|
||||
editProfile: "プロフィールを編集"
|
||||
noteDeleteConfirm: "この投稿を削除しますか?"
|
||||
noteDeleteConfirm: "このノートを削除しますか?"
|
||||
pinLimitExceeded: "これ以上ピン留めできません"
|
||||
intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。"
|
||||
done: "完了"
|
||||
@ -304,8 +304,8 @@ name: "名前"
|
||||
antennaSource: "受信ソース"
|
||||
antennaKeywords: "受信キーワード"
|
||||
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
notifyAntenna: "新しい投稿を通知する"
|
||||
withFileAntenna: "ファイルが添付された投稿のみ"
|
||||
notifyAntenna: "新しいノートを通知する"
|
||||
withFileAntenna: "ファイルが添付されたノートのみ"
|
||||
serviceworker: "ServiceWorker"
|
||||
enableServiceworker: "ServiceWorkerを有効にする"
|
||||
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
|
||||
@ -348,6 +348,9 @@ resetPassword: "パスワードをリセット"
|
||||
newPasswordIs: "新しいパスワードは「{password}」です"
|
||||
post: "投稿"
|
||||
posted: "投稿しました"
|
||||
autoReloadWhenDisconnected: "サーバー切断時に自動リロード"
|
||||
autoNoteWatch: "ノートの自動ウォッチ"
|
||||
reduceUiAnimation: "UIのアニメーションを減らす"
|
||||
|
||||
_2fa:
|
||||
registerDevice: "デバイスを登録"
|
||||
@ -372,7 +375,7 @@ _permissions:
|
||||
"write:messaging": "トークを操作する"
|
||||
"read:mutes": "ミュートを見る"
|
||||
"write:mutes": "ミュートを操作する"
|
||||
"write:notes": "投稿を作成・削除する"
|
||||
"write:notes": "ノートを作成・削除する"
|
||||
"read:notifications": "通知を見る"
|
||||
"write:notifications": "通知を操作する"
|
||||
"read:reactions": "リアクションを見る"
|
||||
@ -390,10 +393,10 @@ _auth:
|
||||
permissionAsk: "このアプリは次の権限を要求しています"
|
||||
|
||||
_antennaSources:
|
||||
all: "全ての投稿"
|
||||
homeTimeline: "フォローしているユーザーの投稿"
|
||||
users: "指定した一人または複数のユーザーの投稿"
|
||||
userList: "指定したリストのユーザーの投稿"
|
||||
all: "全てのノート"
|
||||
homeTimeline: "フォローしているユーザーのノート"
|
||||
users: "指定した一人または複数のユーザーのノート"
|
||||
userList: "指定したリストのユーザーのノート"
|
||||
|
||||
_weekday:
|
||||
sunday: "日曜日"
|
||||
@ -411,6 +414,7 @@ _widgets:
|
||||
calendar: "カレンダー"
|
||||
trends: "トレンド"
|
||||
clock: "時計"
|
||||
rss: "RSSリーダー"
|
||||
|
||||
_cw:
|
||||
hide: "隠す"
|
||||
@ -453,8 +457,8 @@ _visibility:
|
||||
specifiedDescription: "指定したユーザーのみに公開"
|
||||
|
||||
_postForm:
|
||||
replyPlaceholder: "この投稿に返信..."
|
||||
quotePlaceholder: "この投稿を引用..."
|
||||
replyPlaceholder: "このノートに返信..."
|
||||
quotePlaceholder: "このノートを引用..."
|
||||
_placeholders:
|
||||
a: "いまどうしてる?"
|
||||
b: "何かありましたか?"
|
||||
@ -473,7 +477,7 @@ _profile:
|
||||
metadataContent: "内容"
|
||||
|
||||
_exportOrImport:
|
||||
allNotes: "全ての投稿"
|
||||
allNotes: "全てのノート"
|
||||
followingList: "フォロー"
|
||||
muteList: "ミュート"
|
||||
blockingList: "ブロック"
|
||||
@ -485,10 +489,10 @@ _charts:
|
||||
usersIncDec: "ユーザーの増減"
|
||||
usersTotal: "ユーザーの合計"
|
||||
activeUsers: "アクティブユーザー数"
|
||||
notesIncDec: "投稿の増減"
|
||||
localNotesIncDec: "ローカルの投稿の増減"
|
||||
remoteNotesIncDec: "リモートの投稿の増減"
|
||||
notesTotal: "投稿の合計"
|
||||
notesIncDec: "ノートの増減"
|
||||
localNotesIncDec: "ローカルのノートの増減"
|
||||
remoteNotesIncDec: "リモートのノートの増減"
|
||||
notesTotal: "ノートの合計"
|
||||
filesIncDec: "ファイルの増減"
|
||||
filesTotal: "ファイルの合計"
|
||||
storageUsageIncDec: "ストレージ使用量の増減"
|
||||
@ -498,8 +502,8 @@ _instanceCharts:
|
||||
requests: "リクエスト"
|
||||
users: "ユーザーの増減"
|
||||
usersTotal: "ユーザーの積算"
|
||||
notes: "投稿の増減"
|
||||
notesTotal: "投稿の積算"
|
||||
notes: "ノートの増減"
|
||||
notesTotal: "ノートの積算"
|
||||
ff: "フォロー/フォロワーの増減"
|
||||
ffTotal: "フォロー/フォロワーの積算"
|
||||
cacheSize: "キャッシュサイズの増減"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.0.0",
|
||||
"version": "12.2.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -2,10 +2,10 @@
|
||||
<div class="mk-app" v-hotkey.global="keymap">
|
||||
<header class="header">
|
||||
<div class="title" ref="title">
|
||||
<transition name="header" mode="out-in" appear>
|
||||
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
|
||||
<button class="_button back" v-if="canBack" @click="back()"><fa :icon="faChevronLeft"/></button>
|
||||
</transition>
|
||||
<transition name="header" mode="out-in" appear>
|
||||
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
|
||||
<div class="body" :key="pageKey">
|
||||
<div class="default">
|
||||
<portal-target name="avatar" slim/>
|
||||
@ -76,7 +76,7 @@
|
||||
<div class="contents">
|
||||
<main ref="main">
|
||||
<div class="content">
|
||||
<transition name="page" mode="out-in">
|
||||
<transition :name="$store.state.device.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
|
||||
<keep-alive :include="['index']">
|
||||
<router-view></router-view>
|
||||
</keep-alive>
|
||||
@ -100,9 +100,9 @@
|
||||
class="sortable"
|
||||
@sort="onWidgetSort"
|
||||
>
|
||||
<div v-for="widget in widgets" class="customize-container" :key="widget.id">
|
||||
<div v-for="widget in widgets" class="customize-container _panel" :key="widget.id">
|
||||
<header>
|
||||
<span class="handle"><fa :icon="faBars"/></span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)"><fa :icon="faTimes"/></button>
|
||||
<span class="handle"><fa :icon="faBars"/></span>{{ $t('_widgets.' + widget.name) }}<button class="remove _button" @click="removeWidget(widget)"><fa :icon="faTimes"/></button>
|
||||
</header>
|
||||
<div @click="widgetFunc(widget.id)">
|
||||
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true"/>
|
||||
@ -222,6 +222,10 @@ export default Vue.extend({
|
||||
|
||||
this.$root.stream.on('_disconnected_', () => {
|
||||
if (!this.disconnectedDialog) {
|
||||
if (this.$store.state.device.autoReload) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
this.disconnectedDialog = this.$root.dialog({
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
@ -254,6 +258,10 @@ export default Vue.extend({
|
||||
if (this.canBack) window.history.back();
|
||||
},
|
||||
|
||||
onTransition() {
|
||||
if (window._scroll) window._scroll();
|
||||
},
|
||||
|
||||
post() {
|
||||
this.$root.post();
|
||||
},
|
||||
@ -572,12 +580,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
||||
.header-enter-active, .header-leave-active {
|
||||
transition: opacity 0.5s, transform 0.5s !important;
|
||||
}
|
||||
@ -967,10 +969,10 @@ export default Vue.extend({
|
||||
> header {
|
||||
position: relative;
|
||||
line-height: 32px;
|
||||
background: #eee;
|
||||
|
||||
> .handle {
|
||||
padding: 0 8px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
> .remove {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction">
|
||||
<sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed">
|
||||
<template v-for="(item, i) in items">
|
||||
<slot :item="item" :i="i"></slot>
|
||||
<div class="separator" :key="item.id + '_date'" :data-index="i" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
|
||||
<div class="separator" :key="item.id + '_date'" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate()">
|
||||
<p class="date">
|
||||
<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span>
|
||||
<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span>
|
||||
@ -28,6 +28,11 @@ export default Vue.extend({
|
||||
direction: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2,26 +2,26 @@
|
||||
<x-popup :source="source" :no-center="noCenter" :fixed="fixed" :width="width" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
|
||||
<sequential-entrance class="rrevdjwt" :class="{ left: align === 'left' }" :delay="15" :direction="direction">
|
||||
<template v-for="(item, i) in items.filter(item => item !== undefined)">
|
||||
<div v-if="item === null" class="divider" :key="i" :data-index="i"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item" :key="i" :data-index="i">
|
||||
<div v-if="item === null" class="divider" :key="i"></div>
|
||||
<span v-else-if="item.type === 'label'" class="label item" :key="i">
|
||||
<span>{{ item.text }}</span>
|
||||
</span>
|
||||
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<router-link v-else-if="item.type === 'link'" :to="item.to" @click.native="close()" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<a v-else-if="item.type === 'a'" :href="item.href" :target="item.target" :download="item.download" @click="close()" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<span>{{ item.text }}</span>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</a>
|
||||
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
|
||||
<mk-avatar :user="item.user" class="avatar"/><mk-user-name :user="item.user"/>
|
||||
<i v-if="item.indicate"><fa :icon="faCircle"/></i>
|
||||
</button>
|
||||
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i" :data-index="i">
|
||||
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :key="i">
|
||||
<fa v-if="item.icon" :icon="item.icon" fixed-width/>
|
||||
<mk-avatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
|
||||
<span>{{ item.text }}</span>
|
||||
@ -88,12 +88,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
||||
.rrevdjwt {
|
||||
padding: 8px 0;
|
||||
|
||||
|
@ -36,5 +36,10 @@ export default Vue.extend({
|
||||
::v-deep pre {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
::v-deep .title {
|
||||
text-align: center;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="mk-modal">
|
||||
<transition name="bg-fade" appear>
|
||||
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
|
||||
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
||||
</transition>
|
||||
<transition name="modal" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
|
||||
<transition :name="$store.state.device.animation ? 'modal' : ''" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
|
||||
<div class="content" ref="content" v-if="show" @click.self="close()"><slot></slot></div>
|
||||
</transition>
|
||||
</div>
|
||||
|
@ -275,8 +275,14 @@ export default Vue.extend({
|
||||
methods: {
|
||||
capture(withHandler = false) {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.send('sn', { id: this.appearNote.id });
|
||||
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
|
||||
if (document.body.contains(this.$el)) {
|
||||
this.connection.send('sn', { id: this.appearNote.id });
|
||||
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
|
||||
} else {
|
||||
this.$once('hook:activated', () => {
|
||||
this.capture(withHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -458,7 +464,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
showRenoteMenu(ev) {
|
||||
if (!this.isMyNote) return;
|
||||
if (!this.$store.getters.isSignedIn || (this.$store.state.i.id !== this.note.userId)) return;
|
||||
this.$root.menu({
|
||||
items: [{
|
||||
text: this.$t('unrenote'),
|
||||
|
@ -5,7 +5,7 @@
|
||||
<mk-error v-if="error" @retry="init()"/>
|
||||
|
||||
<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note, i }">
|
||||
<x-note :note="note" :detail="detail" :key="note.id" :data-index="i"/>
|
||||
<x-note :note="note" :detail="detail" :key="note.id"/>
|
||||
</x-list>
|
||||
|
||||
<footer v-if="more">
|
||||
@ -36,19 +36,6 @@ export default Vue.extend({
|
||||
|
||||
mixins: [
|
||||
paging({
|
||||
onPrepend: (self, note) => {
|
||||
// タブが非表示なら通知
|
||||
if (document.hidden) {
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
new Notification(getUserName(note.user), {
|
||||
body: getNoteSummary(note),
|
||||
icon: note.user.avatarUrl,
|
||||
tag: 'newNote'
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
before: (self) => {
|
||||
self.$emit('before');
|
||||
},
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="mk-notifications">
|
||||
<div class="contents">
|
||||
<x-list class="notifications" :items="items" v-slot="{ item: notification, i }">
|
||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id" :data-index="i"/>
|
||||
<x-notification :notification="notification" :with-time="true" :full="true" class="notification" :key="notification.id"/>
|
||||
</x-list>
|
||||
|
||||
<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching">
|
||||
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="mk-popup">
|
||||
<transition name="bg-fade" appear>
|
||||
<transition :name="$store.state.device.animation ? 'bg-fade' : ''" appear>
|
||||
<div class="bg" ref="bg" @click="close()" v-if="show"></div>
|
||||
</transition>
|
||||
<transition name="popup" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
|
||||
<transition :name="$store.state.device.animation ? 'popup' : ''" appear @after-leave="() => { $emit('closed'); destroyDom(); }">
|
||||
<div class="content" :class="{ fixed }" ref="content" v-if="show" :style="{ width: width ? width + 'px' : 'auto' }"><slot></slot></div>
|
||||
</transition>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||
<transition name="form-fade" appear>
|
||||
<transition :name="$store.state.device.animation ? 'form-fade' : ''" appear>
|
||||
<div class="bg" ref="bg" v-if="show" @click="close()"></div>
|
||||
</transition>
|
||||
<div class="main" ref="main" @click.self="close()" @keydown="onKeydown">
|
||||
<transition name="form" appear
|
||||
<transition :name="$store.state.device.animation ? 'form' : ''" appear
|
||||
@after-leave="destroyDom"
|
||||
>
|
||||
<x-post-form ref="form"
|
||||
|
@ -13,7 +13,7 @@
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :data-index="i" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
|
||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
|
||||
</transition-group>
|
||||
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<transition-group
|
||||
<transition-group v-if="$store.state.device.animation"
|
||||
name="staggered-fade"
|
||||
tag="div"
|
||||
:css="false"
|
||||
@ -11,6 +11,9 @@
|
||||
>
|
||||
<slot></slot>
|
||||
</transition-group>
|
||||
<div v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -27,27 +30,37 @@ export default Vue.extend({
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'down'
|
||||
},
|
||||
reversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
i: 0,
|
||||
methods: {
|
||||
beforeEnter(el) {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
|
||||
let index = this.$options.i;
|
||||
const delay = this.delay * index;
|
||||
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
|
||||
this.$options.i++;
|
||||
},
|
||||
enter(el, done) {
|
||||
el.style.transition = [getComputedStyle(el).transition, 'transform 0.7s cubic-bezier(0.23, 1, 0.32, 1)', 'opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1)'].filter(x => x != '').join(',');
|
||||
setTimeout(() => {
|
||||
el.style.opacity = 1;
|
||||
el.style.transform = 'translateY(0px)';
|
||||
setTimeout(done, 700);
|
||||
}, this.delay * el.dataset.index)
|
||||
el.addEventListener('transitionend', () => {
|
||||
el.style.transition = '';
|
||||
this.$options.i--;
|
||||
done();
|
||||
}, { once: true });
|
||||
});
|
||||
},
|
||||
leave(el, done) {
|
||||
setTimeout(() => {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
|
||||
setTimeout(done, 700);
|
||||
}, this.delay * el.dataset.index)
|
||||
leave(el) {
|
||||
el.style.opacity = 0;
|
||||
el.style.transform = this.direction === 'down' ? 'translateY(64px)' : 'translateY(-64px)';
|
||||
},
|
||||
focus() {
|
||||
this.$slots.default[0].elm.focus();
|
||||
|
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<sequential-entrance class="users">
|
||||
<router-link v-for="(item, i) in items" class="user" :key="item.id" :data-index="i" :to="extract ? extract(item) : item | userPage">
|
||||
<router-link v-for="(item, i) in items" class="user" :key="item.id" :to="extract ? extract(item) : item | userPage">
|
||||
<mk-avatar :user="extract ? extract(item) : item" class="avatar" :disable-link="true"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="extract ? extract(item) : item" class="name"/>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<portal to="title">{{ $t('announcements') }}</portal>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="ruryvtyk" ref="list">
|
||||
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id" :data-index="i">
|
||||
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
|
||||
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
|
||||
<div class="_content">
|
||||
<mfm :text="announcement.text"/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="mk-follow-requests" ref="list">
|
||||
<div class="user _panel" v-for="(req, i) in items" :key="req.id" :data-index="i">
|
||||
<div class="user _panel" v-for="(req, i) in items" :key="req.id">
|
||||
<mk-avatar class="avatar" :user="req.follower"/>
|
||||
<div class="body">
|
||||
<div class="name">
|
||||
|
@ -173,12 +173,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
||||
._kjvfvyph_ {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<mk-pagination :pagination="pagination" class="emojis" ref="emojis">
|
||||
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selected = emoji" :class="{ selected: selected && (selected.id === emoji.id) }">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<span class="name">{{ emoji.name }}</span>
|
||||
@ -30,7 +30,7 @@
|
||||
<mk-pagination :pagination="remotePagination" class="emojis" ref="remoteEmojis">
|
||||
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" :data-index="i" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
|
||||
<div class="emoji" v-for="(emoji, i) in items" :key="emoji.id" @click="selectedRemote = emoji" :class="{ selected: selectedRemote && (selectedRemote.id === emoji.id) }">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<span class="name">{{ emoji.name }}</span>
|
||||
|
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="instances" ref="instances" :key="host + state">
|
||||
<div class="instance" v-for="(instance, i) in items" :key="instance.id" :data-index="i" @click="info(instance)">
|
||||
<div class="instance" v-for="(instance, i) in items" :key="instance.id" @click="info(instance)">
|
||||
<div class="host"><fa :icon="faCircle" class="indicator" :class="getStatus(instance)"/><b>{{ instance.host }}</b></div>
|
||||
<div class="status">
|
||||
<span class="sub" v-if="instance.followersCount > 0"><fa :icon="faCaretDown" class="icon"/>Sub</span>
|
||||
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="_content" style="max-height: 180px; overflow: auto;">
|
||||
<sequential-entrance :delay="15" v-if="jobs.length > 0">
|
||||
<div v-for="(job, i) in jobs" :key="job[0]" :data-index="i">
|
||||
<div v-for="(job, i) in jobs" :key="job[0]">
|
||||
<span>{{ job[0] }}</span>
|
||||
<span style="margin-left: 8px; opacity: 0.7;">({{ job[1] | number }} jobs)</span>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<div class="_title"><fa :icon="faUsers"/> {{ $t('users') }}</div>
|
||||
<div class="_content _list">
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="users" ref="users" :auto-margin="false">
|
||||
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" :data-index="i" @click="show(user)">
|
||||
<button class="user _button _listItem" v-for="(user, i) in items" :key="user.id" @click="show(user)">
|
||||
<mk-avatar :user="user" class="avatar"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="user" class="name"/>
|
||||
|
@ -5,7 +5,7 @@
|
||||
>
|
||||
<textarea
|
||||
v-model="text"
|
||||
ref="textarea"
|
||||
ref="text"
|
||||
@keypress="onKeypress"
|
||||
@paste="onPaste"
|
||||
:placeholder="$t('input-message-here')"
|
||||
@ -16,22 +16,20 @@
|
||||
<button class="send _button" @click="send" :disabled="!canSend || sending" :title="$t('send')">
|
||||
<template v-if="!sending"><fa :icon="faPaperPlane"/></template><template v-if="sending"><fa icon="spinner .spin"/></template>
|
||||
</button>
|
||||
<button class="attach-from-local _button" @click="chooseFile" :title="$t('attach-from-local')">
|
||||
<fa :icon="faUpload"/>
|
||||
</button>
|
||||
<button class="attach-from-drive _button" @click="chooseFileFromDrive" :title="$t('attach-from-drive')">
|
||||
<fa :icon="faCloud"/>
|
||||
</button>
|
||||
<button class="_button" @click="chooseFile"><fa :icon="faPhotoVideo"/></button>
|
||||
<button class="_button" @click="insertEmoji"><fa :icon="faLaughSquint"/></button>
|
||||
<input ref="file" type="file" @change="onChangeFile"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faPaperPlane, faUpload, faCloud } from '@fortawesome/free-solid-svg-icons';
|
||||
import i18n from '../i18n';
|
||||
import { faPaperPlane, faPhotoVideo, faLaughSquint } from '@fortawesome/free-solid-svg-icons';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as autosize from 'autosize';
|
||||
import i18n from '../i18n';
|
||||
import { formatTimeString } from '../../misc/format-time-string';
|
||||
import { selectFile } from '../scripts/select-file';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
@ -53,7 +51,7 @@ export default Vue.extend({
|
||||
text: null,
|
||||
file: null,
|
||||
sending: false,
|
||||
faPaperPlane, faUpload, faCloud
|
||||
faPaperPlane, faPhotoVideo, faLaughSquint
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -80,7 +78,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
autosize(this.$refs.textarea);
|
||||
autosize(this.$refs.text);
|
||||
|
||||
// 書きかけの投稿を復元
|
||||
const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftId];
|
||||
@ -160,14 +158,8 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
chooseFile() {
|
||||
(this.$refs.file as any).click();
|
||||
},
|
||||
|
||||
chooseFileFromDrive() {
|
||||
this.$chooseDriveFile({
|
||||
multiple: false
|
||||
}).then(file => {
|
||||
chooseFile(e) {
|
||||
selectFile(this, e.currentTarget || e.target, this.$t('selectFile'), false).then(file => {
|
||||
this.file = file;
|
||||
});
|
||||
},
|
||||
@ -227,6 +219,15 @@ export default Vue.extend({
|
||||
|
||||
localStorage.setItem('message_drafts', JSON.stringify(data));
|
||||
},
|
||||
|
||||
async insertEmoji(ev) {
|
||||
const vm = this.$root.new(await import('../components/emoji-picker.vue').then(m => m.default), {
|
||||
source: ev.currentTarget || ev.target
|
||||
}).$once('chosen', emoji => {
|
||||
insertTextAtCursor(this.$refs.text, emoji);
|
||||
vm.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -330,8 +331,7 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
|
||||
.attach-from-local,
|
||||
.attach-from-drive {
|
||||
._button {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
font-size: 1em;
|
||||
|
@ -18,8 +18,8 @@
|
||||
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
||||
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
||||
</button>
|
||||
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up">
|
||||
<x-message :message="message" :is-group="group != null" :key="message.id" :data-index="messages.length - i"/>
|
||||
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
|
||||
<x-message :message="message" :is-group="group != null" :key="message.id"/>
|
||||
</x-list>
|
||||
</div>
|
||||
<footer>
|
||||
|
@ -32,7 +32,7 @@
|
||||
</router-link>
|
||||
</sequential-entrance>
|
||||
<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
|
||||
<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
|
||||
<mk-loading v-if="fetching"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -282,17 +282,6 @@ export default Vue.extend({
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
> .fetching {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
|
||||
> [data-icon] {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
> .search {
|
||||
> .result {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<x-antenna v-if="draft" :antenna="draft" @created="onAntennaCreated" style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="antennas" ref="list">
|
||||
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :data-index="i" :antenna="antenna" @created="onAntennaDeleted"/>
|
||||
<x-antenna v-for="(antenna, i) in items" :key="antenna.id" :antenna="antenna" @created="onAntennaDeleted"/>
|
||||
</mk-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<mk-button @click="create" primary class="add"><fa :icon="faPlus"/> {{ $t('createList') }}</mk-button>
|
||||
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="lists" ref="list">
|
||||
<div class="list _panel" v-for="(list, i) in items" :key="list.id" :data-index="i">
|
||||
<div class="list _panel" v-for="(list, i) in items" :key="list.id">
|
||||
<router-link :to="`/lists/${ list.id }`">{{ list.name }}</router-link>
|
||||
</div>
|
||||
</mk-pagination>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="_title">{{ list.name }}</div>
|
||||
<div class="_content">
|
||||
<div class="users">
|
||||
<div class="user" v-for="(user, i) in users" :key="user.id" :data-index="i">
|
||||
<div class="user" v-for="(user, i) in users" :key="user.id">
|
||||
<mk-avatar :user="user" class="avatar"/>
|
||||
<div class="body">
|
||||
<mk-user-name :user="user" class="name"/>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="_title"><fa :icon="faCloud"/> {{ $t('drive') }}</div>
|
||||
<div class="_content">
|
||||
<mk-pagination :pagination="drivePagination" #default="{items}" class="drive" ref="drive">
|
||||
<div class="file" v-for="(file, i) in items" :key="file.id" :data-index="i" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
|
||||
<div class="file" v-for="(file, i) in items" :key="file.id" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }">
|
||||
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
|
||||
<div class="body">
|
||||
<p class="name">
|
||||
|
@ -10,8 +10,11 @@
|
||||
<mk-button primary :disabled="$store.state.settings.wallpaper == null" @click="delWallpaper()">{{ $t('removeWallpaper') }}</mk-button>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="autoReload">
|
||||
{{ $t('autoReloadWhenDisconnected') }}
|
||||
</mk-switch>
|
||||
<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch">
|
||||
{{ $t('auto-watch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
|
||||
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('auto-watch-desc') }}</template>
|
||||
</mk-switch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
@ -19,6 +22,11 @@
|
||||
<mk-button @click="readAllUnreadNotes">{{ $t('mark-as-read-all-unread-notes') }}</mk-button>
|
||||
<mk-button @click="readAllMessagingMessages">{{ $t('mark-as-read-all-talk-messages') }}</mk-button>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<mk-switch v-model="reduceAnimation">
|
||||
{{ $t('reduceUiAnimation') }}
|
||||
</mk-switch>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -52,6 +60,16 @@ export default Vue.extend({
|
||||
get() { return this.$store.state.settings.wallpaper; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'wallpaper', value }); }
|
||||
},
|
||||
|
||||
autoReload: {
|
||||
get() { return this.$store.state.device.autoReload; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); }
|
||||
},
|
||||
|
||||
reduceAnimation: {
|
||||
get() { return !this.$store.state.device.animation; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<mk-pagination :pagination="mutingPagination" class="muting">
|
||||
<template #empty><span>{{ $t('noUsers') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="user" v-for="(mute, i) in items" :key="mute.id" :data-index="i">
|
||||
<div class="user" v-for="(mute, i) in items" :key="mute.id">
|
||||
<router-link class="name" :to="mute.mutee | userPage">
|
||||
<mk-acct :user="mute.mutee"/>
|
||||
</router-link>
|
||||
@ -19,7 +19,7 @@
|
||||
<mk-pagination :pagination="blockingPagination" class="blocking">
|
||||
<template #empty><span>{{ $t('noUsers') }}</span></template>
|
||||
<template #default="{items}">
|
||||
<div class="user" v-for="(block, i) in items" :key="block.id" :data-index="i">
|
||||
<div class="user" v-for="(block, i) in items" :key="block.id">
|
||||
<router-link class="name" :to="block.blockee | userPage">
|
||||
<mk-acct :user="block.blockee"/>
|
||||
</router-link>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-pagination :pagination="pagination" #default="{items}" class="mk-following-or-followers" ref="list">
|
||||
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :data-index="i">
|
||||
<div class="user _panel" v-for="(user, i) in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id">
|
||||
<mk-avatar class="avatar" :user="user"/>
|
||||
<div class="body">
|
||||
<div class="name">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<portal to="avatar" v-if="user"><mk-avatar class="avatar" :user="user" :disable-preview="true"/></portal>
|
||||
|
||||
<div class="remote-caution _panel" v-if="user.host != null"><fa :icon="faExclamationTriangle" style="margin-right: 8px;"/>{{ $t('remoteUserCaution') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('showOnRemote') }}</a></div>
|
||||
<transition name="zoom" mode="out-in" appear>
|
||||
<transition :name="$store.state.device.animation ? 'zoom' : ''" mode="out-in" appear>
|
||||
<div class="profile _panel" :key="user.id">
|
||||
<div class="banner-container" :style="style">
|
||||
<div class="banner" ref="banner" :style="style"></div>
|
||||
@ -83,7 +83,7 @@
|
||||
<router-view :user="user"></router-view>
|
||||
<template v-if="$route.name == 'user'">
|
||||
<sequential-entrance class="pins">
|
||||
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :data-index="i" :detail="true" :pinned="true"/>
|
||||
<x-note v-for="(note, i) in user.pinnedNotes" class="note" :note="note" :key="note.id" :detail="true" :pinned="true"/>
|
||||
</sequential-entrance>
|
||||
<mk-container :body-togglable="true" class="content">
|
||||
<template #header><fa :icon="faImage"/>{{ $t('images') }}</template>
|
||||
@ -269,10 +269,11 @@ export default Vue.extend({
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
padding: 4px 6px;
|
||||
padding: 4px 8px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
font-size: 12px;
|
||||
font-size: 0.7em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
> .actions {
|
||||
|
@ -6,6 +6,8 @@ Vue.use(VueRouter);
|
||||
|
||||
const page = (path: string) => () => import(`./pages/${path}.vue`).then(m => m.default);
|
||||
|
||||
let indexScrollPos = 0;
|
||||
|
||||
export const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
@ -55,14 +57,19 @@ export const router = new VueRouter({
|
||||
// なんかHacky
|
||||
// 通常の使い方をすると scroll メソッドの behavior を設定できないため、自前で window.scroll するようにする
|
||||
// setTimeout しないと、アニメーション(トランジション)の関係でうまく動かない
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
setTimeout(() => {
|
||||
if (savedPosition) {
|
||||
window.scroll({ top: savedPosition.y, behavior: 'instant' });
|
||||
scrollBehavior(to) {
|
||||
window._scroll = () => { // さらにHacky
|
||||
if (to.name === 'index') {
|
||||
window.scroll({ top: indexScrollPos, behavior: 'instant' });
|
||||
} else {
|
||||
window.scroll({ top: 0, behavior: 'instant' });
|
||||
}
|
||||
}, 600);
|
||||
return;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
if (from.name === 'index') {
|
||||
indexScrollPos = window.scrollY;
|
||||
}
|
||||
});
|
||||
|
@ -22,12 +22,14 @@ const defaultDeviceSettings = {
|
||||
loadRawImages: false,
|
||||
alwaysShowNsfw: false,
|
||||
useOsDefaultEmojis: false,
|
||||
autoReload: false,
|
||||
accounts: [],
|
||||
recentEmojis: [],
|
||||
visibility: 'public',
|
||||
localOnly: false,
|
||||
themes: [],
|
||||
theme: 'light',
|
||||
animation: true,
|
||||
};
|
||||
|
||||
export default (os: MiOS) => new Vuex.Store({
|
||||
|
@ -339,3 +339,22 @@ a {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes jump {
|
||||
0% { transform: translateY(0); }
|
||||
25% { transform: translateY(-16px); }
|
||||
50% { transform: translateY(0); }
|
||||
75% { transform: translateY(-8px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
30% { opacity: 1; }
|
||||
90% { opacity: 0; }
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ self.addEventListener('install', ev => {
|
||||
caches.open(cacheName)
|
||||
.then(cache => {
|
||||
return cache.addAll([
|
||||
'/assets/error.jpg'
|
||||
'/'
|
||||
]);
|
||||
})
|
||||
.then(() => self.skipWaiting())
|
||||
|
@ -15,7 +15,7 @@ import { apLogger } from '../logger';
|
||||
import { DriveFile } from '../../../models/entities/drive-file';
|
||||
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
||||
import { Notes, Emojis, Polls } from '../../../models';
|
||||
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
import { IObject, getOneApId, getApId, validPost, ICreate, isCreate, IPost } from '../type';
|
||||
import { Emoji } from '../../../models/entities/emoji';
|
||||
@ -129,6 +129,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
}
|
||||
}
|
||||
|
||||
let isTalk = note._misskey_talk && visibility === 'specified';
|
||||
|
||||
const apHashtags = await extractHashtags(note.tag);
|
||||
|
||||
// 添付ファイル
|
||||
@ -153,7 +155,18 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}).catch(e => {
|
||||
}).catch(async e => {
|
||||
// トークだったらinReplyToのエラーは無視
|
||||
const uri = getApId(note.inReplyTo);
|
||||
if (uri.startsWith(config.url + '/')) {
|
||||
const id = uri.split('/').pop();
|
||||
const talk = await MessagingMessages.findOne(id);
|
||||
if (talk) {
|
||||
isTalk = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
})
|
||||
@ -250,7 +263,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
|
||||
if (actor.uri) updatePerson(actor.uri);
|
||||
}
|
||||
|
||||
if (note._misskey_talk && visibility === 'specified') {
|
||||
if (isTalk) {
|
||||
for (const recipient of visibleUsers) {
|
||||
await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
|
||||
return null;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import config from '../../../config';
|
||||
import { ILocalUser } from '../../../models/entities/user';
|
||||
import { NoteReaction } from '../../../models/entities/note-reaction';
|
||||
import { Note } from '../../../models/entities/note';
|
||||
|
||||
export default (user: ILocalUser, note: Note, reaction: string) => ({
|
||||
export const renderLike = (noteReaction: NoteReaction, note: Note) => ({
|
||||
type: 'Like',
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${note.id}`,
|
||||
_misskey_reaction: reaction
|
||||
id: `${config.url}/likes/${noteReaction.id}`,
|
||||
actor: `${config.url}/users/${noteReaction.userId}`,
|
||||
object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
|
||||
content: noteReaction.reaction,
|
||||
_misskey_reaction: noteReaction.reaction
|
||||
});
|
||||
|
@ -13,10 +13,11 @@ import Following from './activitypub/following';
|
||||
import Featured from './activitypub/featured';
|
||||
import { inbox as processInbox } from '../queue';
|
||||
import { isSelfHost } from '../misc/convert-host';
|
||||
import { Notes, Users, Emojis, UserKeypairs } from '../models';
|
||||
import { Notes, Users, Emojis, UserKeypairs, NoteReactions } from '../models';
|
||||
import { ILocalUser, User } from '../models/entities/user';
|
||||
import { In } from 'typeorm';
|
||||
import { ensure } from '../prelude/ensure';
|
||||
import { renderLike } from '../remote/activitypub/renderer/like';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
@ -202,4 +203,25 @@ router.get('/emojis/:emoji', async ctx => {
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
// like
|
||||
router.get('/likes/:like', async ctx => {
|
||||
const reaction = await NoteReactions.findOne(ctx.params.like);
|
||||
|
||||
if (reaction == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await Notes.findOne(reaction.noteId);
|
||||
|
||||
if (note == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = renderActivity(await renderLike(reaction, note));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import watch from '../watch';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import { renderLike } from '../../../remote/activitypub/renderer/like';
|
||||
import DeliverManager from '../../../remote/activitypub/deliver-manager';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import { IdentifiableError } from '../../../misc/identifiable-error';
|
||||
@ -38,7 +38,7 @@ export default async (user: User, note: Note, reaction?: string) => {
|
||||
}
|
||||
|
||||
// Create reaction
|
||||
await NoteReactions.save({
|
||||
const inserted = await NoteReactions.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
noteId: note.id,
|
||||
@ -94,7 +94,7 @@ export default async (user: User, note: Note, reaction?: string) => {
|
||||
|
||||
//#region 配信
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
const content = renderActivity(renderLike(user, note, reaction));
|
||||
const content = renderActivity(renderLike(inserted, note));
|
||||
const dm = new DeliverManager(user, content);
|
||||
if (note.userHost !== null) {
|
||||
const reactee = await Users.findOne(note.userId)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { publishNoteStream } from '../../stream';
|
||||
import renderLike from '../../../remote/activitypub/renderer/like';
|
||||
import { renderLike } from '../../../remote/activitypub/renderer/like';
|
||||
import renderUndo from '../../../remote/activitypub/renderer/undo';
|
||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||
import DeliverManager from '../../../remote/activitypub/deliver-manager';
|
||||
@ -40,7 +40,7 @@ export default async (user: User, note: Note) => {
|
||||
|
||||
//#region 配信
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
|
||||
const content = renderActivity(renderUndo(renderLike(exist, note), user));
|
||||
const dm = new DeliverManager(user, content);
|
||||
if (note.userHost !== null) {
|
||||
const reactee = await Users.findOne(note.userId)
|
||||
|
Reference in New Issue
Block a user