Merge branch 'develop' into swn

This commit is contained in:
tamaina 2021-10-25 03:13:27 +09:00
commit fb375d92d4
12 changed files with 96 additions and 87 deletions

View file

@ -10,14 +10,19 @@
--> -->
## 12.x.x (unreleased) ## 12.94.0 (2021/10/25)
### Improvements ### Improvements
- クライアント: 画像ビューアを強化
- クライアント: メンションにユーザーのアバターを表示するように - クライアント: メンションにユーザーのアバターを表示するように
- クライアント: デザインの調整 - クライアント: デザインの調整
- クライアント: twemojiをセルフホスティングするように - クライアント: twemojiをセルフホスティングするように
### Bugfixes ### Bugfixes
- クライアント: CWで画像が隠されたとき、画像の高さがおかしいことになる問題を修正
### NOTE
- このバージョンから、iOS 15未満のサポートがされなくなります。対象のバージョンをお使いの方は、iOSのバージョンアップを行ってください。
## 12.93.2 (2021/10/23) ## 12.93.2 (2021/10/23)

View file

@ -795,7 +795,9 @@ itsOff: "已关闭"
emailRequiredForSignup: "注册账户需要电子邮件地址" emailRequiredForSignup: "注册账户需要电子邮件地址"
unread: "未读" unread: "未读"
filter: "筛选" filter: "筛选"
controlPanel: "控制面板"
manageAccounts: "管理账户" manageAccounts: "管理账户"
classic: "经典"
_signup: _signup:
almostThere: "即将完成" almostThere: "即将完成"
emailAddressInfo: "请输入您所使用的电子邮件地址" emailAddressInfo: "请输入您所使用的电子邮件地址"

View file

@ -1,7 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "version": "12.94.0",
"version": "12.93.2",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -183,6 +182,7 @@
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.7.1", "pg": "8.7.1",
"photoswipe": "git://github.com/dimsemenov/photoswipe#v5-beta",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.3.11", "postcss": "8.3.11",
"postcss-loader": "6.2.0", "postcss-loader": "6.2.0",

View file

@ -12,7 +12,6 @@
<a <a
:href="image.url" :href="image.url"
:title="image.name" :title="image.name"
@click.prevent="onClick"
> >
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/> <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/>
<div class="gif" v-if="image.type === 'image/gif'">GIF</div> <div class="gif" v-if="image.type === 'image/gif'">GIF</div>
@ -73,17 +72,6 @@ export default defineComponent({
immediate: true, immediate: true,
}); });
}, },
methods: {
onClick() {
if (this.$store.state.imageNewTab) {
window.open(this.image.url, '_blank');
} else {
os.popup(ImageViewer, {
image: this.image
}, {}, 'closed');
}
}
}
}); });
</script> </script>

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="mk-media-list"> <div class="hoawjimk">
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :media="media" :key="media.id"/> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :media="media" :key="media.id"/>
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" ref="gridOuter"> <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
<div :data-count="mediaList.filter(media => previewable(media)).length" :style="gridInnerStyle"> <div :data-count="mediaList.filter(media => previewable(media)).length" ref="gallery">
<template v-for="media in mediaList"> <template v-for="media in mediaList">
<XVideo :video="media" :key="media.id" v-if="media.type.startsWith('video')"/> <XVideo :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
<XImage :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/> <XImage class="image" :data-id="media.id" :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
</template> </template>
</div> </div>
</div> </div>
@ -13,11 +13,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, PropType, ref } from 'vue';
import * as misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js';
import PhotoSwipe from 'photoswipe/dist/photoswipe.esm.js';
import 'photoswipe/dist/photoswipe.css';
import XBanner from './media-banner.vue'; import XBanner from './media-banner.vue';
import XImage from './media-image.vue'; import XImage from './media-image.vue';
import XVideo from './media-video.vue'; import XVideo from './media-video.vue';
import * as os from '@client/os'; import * as os from '@client/os';
import { defaultStore } from '@client/store';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -27,63 +32,63 @@ export default defineComponent({
}, },
props: { props: {
mediaList: { mediaList: {
required: true type: Array as PropType<misskey.entities.DriveFile[]>,
required: true,
}, },
raw: { raw: {
default: false default: false
}, },
}, },
data() { setup(props) {
return { const gallery = ref(null);
gridInnerStyle: {},
sizeWaiting: false
}
},
mounted() {
this.size();
window.addEventListener('resize', this.size);
},
beforeUnmount() {
window.removeEventListener('resize', this.size);
},
activated() {
this.size();
},
methods: {
previewable(file) {
return file.type.startsWith('video') || file.type.startsWith('image');
},
size() {
// for Safari bug
if (this.sizeWaiting) return;
this.sizeWaiting = true; onMounted(() => {
const lightbox = new PhotoSwipeLightbox({
window.requestAnimationFrame(() => { dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => ({
this.sizeWaiting = false; src: media.url,
w: media.properties.width,
if (this.$refs.gridOuter) { h: media.properties.height,
let height = 287; alt: media.name,
const parent = this.$parent.$el; })),
gallery: gallery.value,
if (this.$refs.gridOuter.clientHeight) { children: '.image',
height = this.$refs.gridOuter.clientHeight; thumbSelector: '.image',
} else if (parent) { pswpModule: PhotoSwipe
height = parent.getBoundingClientRect().width * 9 / 16;
}
this.gridInnerStyle = { height: `${height}px` };
} else {
this.gridInnerStyle = {};
}
}); });
}
lightbox.on('itemData', (e) => {
const { itemData } = e;
// element is children
const { element } = itemData;
const id = element.dataset.id;
const file = props.mediaList.find(media => media.id === id);
itemData.src = file.url;
itemData.w = Number(file.properties.width);
itemData.h = Number(file.properties.height);
itemData.msrc = file.thumbnailUrl;
itemData.thumbCropped = true;
});
lightbox.init();
});
const previewable = (file: misskey.entities.DriveFile): boolean => {
return file.type.startsWith('video') || file.type.startsWith('image');
};
return {
previewable,
gallery,
};
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mk-media-list { .hoawjimk {
> .gird-container { > .gird-container {
position: relative; position: relative;
width: 100%; width: 100%;

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="_section"> <MkSpacer :content-max="800">
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content"> <MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content">
<section class="_card announcement _gap" v-for="(announcement, i) in items" :key="announcement.id"> <section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
<div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div> <div class="_title"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content"> <div class="_content">
<Mfm :text="announcement.text"/> <Mfm :text="announcement.text"/>
@ -12,7 +12,7 @@
</div> </div>
</section> </section>
</MkPagination> </MkPagination>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -58,6 +58,10 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.ruryvtyk { .ruryvtyk {
> .announcement { > .announcement {
&:not(:last-child) {
margin-bottom: var(--margin);
}
> ._content { > ._content {
> img { > img {
display: block; display: block;

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="_section"> <MkSpacer :content-max="800">
<XNotes class="_content" ref="notes" :pagination="pagination" @before="before" @after="after"/> <XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="_section"> <MkSpacer :content-max="800">
<XNotes class="_content" :pagination="pagination" @before="before()" @after="after()"/> <XNotes :pagination="pagination" @before="before()" @after="after()"/>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -1,7 +1,7 @@
<template> <template>
<div> <MkSpacer :content-max="800">
<XNotes :pagination="pagination" @before="before()" @after="after()"/> <XNotes :pagination="pagination" @before="before()" @after="after()"/>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -348,6 +348,7 @@ hr {
contain: layout; // ふき出しがボックスから飛び出て表示されるようなデザインをする場合もあるので paint contain することができない contain: layout; // ふき出しがボックスから飛び出て表示されるようなデザインをする場合もあるので paint contain することができない
} }
// TODO: 廃止
._monolithic_ { ._monolithic_ {
._section:not(:empty) { ._section:not(:empty) {
box-sizing: border-box; box-sizing: border-box;

View file

@ -34,8 +34,8 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
ひとつのストリーム上で、同時に複数のチャンネルに接続することができます。 ひとつのストリーム上で、同時に複数のチャンネルに接続することができます。
### チャンネルに接続する ### 连接到频道
チャンネルに接続するには、次のようなデータをJSONでストリームに送信します: 要连接到频道请将JSON数据发送到流
```json ```json
{ {
@ -51,16 +51,16 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
``` ```
其中: 其中:
* `channel`には接続したいチャンネル名を設定します。チャンネルの種類については後述します * `channel`には接続したいチャンネル名を設定します。频道类型将在后面说明
* `id`にはそのチャンネルとやり取りするための任意のIDを設定します。ストリームでは様々なメッセージが流れるので、そのメッセージがどのチャンネルからのものなのか識別する必要があるからです。このIDは、UUIDや、乱数のようなもので構いません。 * `id`にはそのチャンネルとやり取りするための任意のIDを設定します。ストリームでは様々なメッセージが流れるので、そのメッセージがどのチャンネルからのものなのか識別する必要があるからです。このIDは、UUIDや、乱数のようなもので構いません。
* `params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。 * `params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。
<div class="info"> IDはチャンネルごとではなく「チャンネルの接続ごと」です。なぜなら、同じチャンネルに異なるパラメータで複数接続するケースもあるからです。</div> <div class="info"> IDはチャンネルごとではなく「チャンネルの接続ごと」です。なぜなら、同じチャンネルに異なるパラメータで複数接続するケースもあるからです。</div>
### チャンネルからのメッセージを受け取る ### 从频道接收消息
例えばタイムラインのチャンネルなら、新しい投稿があった時にメッセージを発します。そのメッセージを受け取ることで、タイムラインに新しい投稿がされたことをリアルタイムで知ることができます。 例えばタイムラインのチャンネルなら、新しい投稿があった時にメッセージを発します。そのメッセージを受け取ることで、タイムラインに新しい投稿がされたことをリアルタイムで知ることができます。
チャンネルがメッセージを発すると、次のようなデータがJSONでストリームに流れてきます: 当频道发送消息时以下数据将以JSON格式传输到流中
```json ```json
{ {
type: 'channel', type: 'channel',
@ -79,10 +79,10 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
* `type`にはメッセージの種類が設定されます。チャンネルによって、どのような種類のメッセージが流れてくるかは異なります。 * `type`にはメッセージの種類が設定されます。チャンネルによって、どのような種類のメッセージが流れてくるかは異なります。
* `body`にはメッセージの内容が設定されます。チャンネルによって、どのような内容のメッセージが流れてくるかは異なります。 * `body`にはメッセージの内容が設定されます。チャンネルによって、どのような内容のメッセージが流れてくるかは異なります。
### チャンネルに向けてメッセージを送信する ### 向频道发送消息
チャンネルによっては、メッセージを受け取るだけでなく、こちらから何かメッセージを送信し、何らかの操作を行える場合があります。 チャンネルによっては、メッセージを受け取るだけでなく、こちらから何かメッセージを送信し、何らかの操作を行える場合があります。
チャンネルにメッセージを送信するには、次のようなデータをJSONでストリームに送信します: 要将消息发送到频道请将JSON格式数据发送到流
```json ```json
{ {
type: 'channel', type: 'channel',
@ -101,7 +101,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
* `type`にはメッセージの種類を設定します。チャンネルによって、どのような種類のメッセージを受け付けるかは異なります。 * `type`にはメッセージの種類を設定します。チャンネルによって、どのような種類のメッセージを受け付けるかは異なります。
* `body`にはメッセージの内容を設定します。チャンネルによって、どのような内容のメッセージを受け付けるかは異なります。 * `body`にはメッセージの内容を設定します。チャンネルによって、どのような内容のメッセージを受け付けるかは異なります。
### チャンネルから切断する ### 断开频道连接
チャンネルから切断するには、次のようなデータをJSONでストリームに送信します: チャンネルから切断するには、次のようなデータをJSONでストリームに送信します:
```json ```json
@ -116,7 +116,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
其中: 其中:
* `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。 * `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。
## ストリームを経由してAPIリクエストする ## 通过流发送API请求
ストリームを経由してAPIリクエストすると、HTTPリクエストを発生させずにAPIを利用できます。そのため、コードを簡潔にできたり、パフォーマンスの向上を見込めるかもしれません。 ストリームを経由してAPIリクエストすると、HTTPリクエストを発生させずにAPIを利用できます。そのため、コードを簡潔にできたり、パフォーマンスの向上を見込めるかもしれません。
@ -207,7 +207,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `body`内の`type`に、イベントの種類が設定されます。 * `body`内の`type`に、イベントの種類が設定されます。
* `body`内の`body`に、イベントの詳細が設定されます。 * `body`内の`body`に、イベントの詳細が設定されます。
#### イベントの種類 #### 事件类型
##### `reacted` ##### `reacted`
その投稿にリアクションがされた時に発生します。 その投稿にリアクションがされた時に発生します。
@ -233,7 +233,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
##### `deleted` ##### `deleted`
その投稿が削除された時に発生します。 その投稿が削除された時に発生します。
* `deletedAt`に、削除日時が設定されます * `deletedAt`表示删除的日期和时间
例: 例:
```json ```json

View file

@ -8271,6 +8271,10 @@ pgpass@1.x:
dependencies: dependencies:
split "^1.0.0" split "^1.0.0"
"photoswipe@git://github.com/dimsemenov/photoswipe#v5-beta":
version "5.1.7"
resolved "git://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7"
picocolors@^1.0.0: picocolors@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"