Compare commits

..

23 Commits

Author SHA1 Message Date
6e7e11e061 Merge branch 'develop' 2022-02-12 17:36:42 +09:00
8d568d533b 12.107.0 2022-02-12 17:36:34 +09:00
348a5f3d7c New Crowdin updates (#8310)
* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)
2022-02-12 17:35:29 +09:00
2de4978a31 fix(server): stats APIで内部エラーが発生する問題を修正
Fix #8308
2022-02-12 17:33:29 +09:00
642a51a558 fix(client): nextTickの中でonUnmounted呼び出しても効かない可能性がある 2022-02-12 17:29:15 +09:00
1c6ab5447d fix(client): 一部環境でサイドバーの投稿ボタンが表示されない問題を修正 2022-02-12 17:28:33 +09:00
e7d6bd19eb 🎨 2022-02-12 17:00:09 +09:00
f6e40a9092 Update CHANGELOG.md 2022-02-12 16:53:25 +09:00
720d5db041 🎨 2022-02-12 16:52:01 +09:00
fd3ce321c5 fix(client): フッターでセーフエリアを考慮するように 2022-02-12 14:48:01 +09:00
7df8cd2b5d fix(client): tweak ui
#8311
2022-02-12 14:43:21 +09:00
64f4231283 ソフトミュートですべてがマッチしてしまうのを修正 (#8307)
* ソフトミュートですべてがマッチしてしまうのを修正

* Clean up

* Update packages/client/src/scripts/check-word-mute.ts

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* fix

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2022-02-11 23:26:51 +09:00
0589171ceb Merge branch 'develop' 2022-02-11 22:17:17 +09:00
3cf9c30974 12.106.3 2022-02-11 22:17:06 +09:00
c1f0fa5bd6 enhance(client): tweak padding 2022-02-11 22:16:20 +09:00
b01a0325ba fix(client): ノート詳細が開けないのを直したり
Fix #8305
2022-02-11 22:14:14 +09:00
eef8f63dc6 Merge branch 'develop' 2022-02-11 21:40:15 +09:00
de6e3d64b4 12.106.2 2022-02-11 21:40:04 +09:00
ed38233044 New Crowdin updates (#8304)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)
2022-02-11 21:38:17 +09:00
64874417e0 Update CHANGELOG.md 2022-02-11 21:37:19 +09:00
b3decdc4e5 fix(client): ノートの参照を断ち切るように
Fix #8201
Close #8237
2022-02-11 21:35:28 +09:00
9f9b8d1cae Update 1644010796173-convert-hard-mutes.js 2022-02-11 21:17:07 +09:00
c456825d0e Update 1644010796173-convert-hard-mutes.js 2022-02-11 21:06:38 +09:00
21 changed files with 146 additions and 56 deletions

View File

@ -10,6 +10,32 @@
You should also include the user name that made the change.
-->
## 12.107.0 (2022/02/12)
### Improvements
- クライアント: テーマを追加 @syuilo
### Bugfixes
- API: stats APIで内部エラーが発生する問題を修正 @syuilo
- クライアント: ソフトミュートですべてがマッチしてしまう場合があるのを修正 @tamaina
- クライアント: デバイスのスクリーンのセーフエリアを考慮するように @syuilo
- クライアント: 一部環境でサイドバーの投稿ボタンが表示されない問題を修正 @syuilo
## 12.106.3 (2022/02/11)
### Improvements
- クライアント: スマートフォンでの余白を調整 @syuilo
### Bugfixes
- クライアント: ノートの詳細が表示されない問題を修正 @syuilo
## 12.106.2 (2022/02/11)
### Bugfixes
- クライアント: 削除したノートがタイムラインから自動で消えない問題を修正 @syuilo
- クライアント: リアクション数が正しくないことがある問題を修正 @syuilo
- 一部環境でマイグレーションが動作しない問題を修正 @syuilo
## 12.106.1 (2022/02/11)
### Bugfixes

View File

@ -831,6 +831,8 @@ smartphone: "Smartphone"
tablet: "Tablet"
auto: "Automatisch"
themeColor: "Instanzfarbe"
size: "Größe"
numberOfColumn: "Spaltenanzahl"
_emailUnavailable:
used: "Diese Email-Adresse wird bereits verwendet"
format: "Das Format dieser Email-Adresse ist ungültig"

View File

@ -829,6 +829,8 @@ smartphone: "Smartphone"
tablet: "Tablet"
auto: "Auto"
themeColor: "Theme Color"
size: "Size"
numberOfColumn: "Number of columns"
_emailUnavailable:
used: "This email address is already being used"
format: "The format of this email address is invalid"

View File

@ -830,6 +830,8 @@ smartphone: "Smartfón"
tablet: "Tablet"
auto: "Automaticky"
themeColor: "Farba témy"
size: "Veľkosť"
numberOfColumn: "Počet stĺpcov"
_emailUnavailable:
used: "Táto emailová adresa sa už používa"
format: "Formát emailovej adresy je nesprávny"

View File

@ -831,6 +831,8 @@ smartphone: "智能手机"
tablet: "平板"
auto: "自动"
themeColor: "主题颜色"
size: "大小"
numberOfColumn: "列数"
_emailUnavailable:
used: "已经被使用过"
format: "无效的格式"
@ -958,7 +960,7 @@ _mfm:
rotateDescription: "旋转指定的角度。"
_instanceTicker:
none: "不显示"
remote: "显示远程用户"
remote: "显示远程用户"
always: "始终显示"
_serverDisconnectedBehavior:
reload: "自动重载"

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "12.106.1",
"version": "12.107.0",
"codename": "indigo",
"repository": {
"type": "git",

View File

@ -5,10 +5,11 @@ module.exports = class convertHardMutes1644010796173 {
name = 'convertHardMutes1644010796173'
async up(queryRunner) {
let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile"`);
let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile" WHERE "userHost" IS NULL`);
for(let i = 0; i < entries.length; i++) {
let words = entries[i].mutedWords
.map(line => {
if (typeof line === 'string') return [];
const regexp = line.join(" ").match(/^\/(.+)\/(.*)$/);
if (regexp) {
// convert regexp's

View File

@ -1,6 +1,6 @@
import define from '../define';
import { NoteReactions, Notes, Users } from '@/models/index';
import { federationChart, driveChart } from '@/services/chart/index';
import { Instances, NoteReactions, Notes, Users } from '@/models/index';
import { } from '@/services/chart/index';
export const meta = {
requireCredential: false,
@ -63,7 +63,7 @@ export default define(meta, async () => {
Users.count({ where: { host: null }, cache: 3600000 }),
NoteReactions.count({ cache: 3600000 }), // 1 hour
//NoteReactions.count({ where: { userHost: null }, cache: 3600000 }),
federationChart.getChart('hour', 1, null).then(chart => chart.instance.total[0]),
Instances.count({ cache: 3600000 }),
]);
return {

View File

@ -7,6 +7,7 @@
</template>
<script lang="ts">
import { deviceKind } from '@/scripts/device-kind';
import { defineComponent, inject, onMounted, onUnmounted, ref } from 'vue';
export default defineComponent({
@ -35,7 +36,7 @@ export default defineComponent({
const margin = ref(0);
const shouldSpacerMin = inject('shouldSpacerMin', false);
const adjust = (rect: { width: number; height: number; }) => {
if (shouldSpacerMin) {
if (shouldSpacerMin || deviceKind === 'smartphone') {
margin.value = props.marginMin;
return;
}

View File

@ -154,11 +154,13 @@ const props = defineProps<{
const inChannel = inject('inChannel', null);
const note = $ref(JSON.parse(JSON.stringify(props.note)));
const isRenote = (
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
);
const el = ref<HTMLElement>();
@ -166,8 +168,8 @@ const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note);
const isMyRenote = $i && ($i.id === props.note.userId);
let appearNote = $ref(isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
@ -188,8 +190,9 @@ const keymap = {
};
useNoteCapture({
appearNote: $$(appearNote),
rootEl: el,
note: $$(appearNote),
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
@ -237,12 +240,12 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus);
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
}
}
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
viaKeyboard
}).then(focus);
}
@ -255,7 +258,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: props.note.id
noteId: note.id
});
isDeleted.value = true;
}

View File

@ -138,11 +138,13 @@ const props = defineProps<{
const inChannel = inject('inChannel', null);
const note = $ref(JSON.parse(JSON.stringify(props.note)));
const isRenote = (
props.note.renote != null &&
props.note.text == null &&
props.note.fileIds.length === 0 &&
props.note.poll == null
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
);
const el = ref<HTMLElement>();
@ -150,8 +152,8 @@ const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note);
const isMyRenote = $i && ($i.id === props.note.userId);
let appearNote = $ref(isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
(appearNote.text.split('\n').length > 9) ||
@ -176,8 +178,9 @@ const keymap = {
};
useNoteCapture({
appearNote: $$(appearNote),
rootEl: el,
note: $$(appearNote),
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
@ -225,12 +228,12 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus);
os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton }), ev).then(focus);
}
}
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
viaKeyboard
}).then(focus);
}
@ -243,7 +246,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: props.note.id
noteId: note.id
});
isDeleted.value = true;
}

View File

@ -180,12 +180,12 @@ const setPosition = () => {
el.value.style.top = top + 'px';
};
let loopHandler;
onMounted(() => {
nextTick(() => {
setPosition();
let loopHandler;
const loop = () => {
loopHandler = window.requestAnimationFrame(() => {
setPosition();
@ -194,12 +194,12 @@ onMounted(() => {
};
loop();
onUnmounted(() => {
window.cancelAnimationFrame(loopHandler);
});
});
});
onUnmounted(() => {
window.cancelAnimationFrame(loopHandler);
});
</script>
<style lang="scss" scoped>

View File

@ -95,8 +95,7 @@ window.addEventListener('resize', () => {
if (['smartphone', 'tablet'].includes(deviceKind)) {
const viewport = document.getElementsByName('viewport').item(0);
viewport.setAttribute('content',
`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
document.head.appendChild(viewport);
`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
}
//#region Set lang attr

View File

@ -7,7 +7,11 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
const matched = mutedWords.some(filter => {
if (Array.isArray(filter)) {
return filter.every(keyword => note.text!.includes(keyword));
// Clean up
const filteredFilter = filter.filter(keyword => keyword !== '');
if (filteredFilter.length === 0) return false;
return filteredFilter.every(keyword => note.text!.includes(keyword));
} else {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);

View File

@ -20,6 +20,7 @@ export const builtinThemes = [
require('@/themes/l-apricot.json5'),
require('@/themes/l-rainy.json5'),
require('@/themes/l-vivid.json5'),
require('@/themes/l-cherry.json5'),
require('@/themes/l-sushi.json5'),
require('@/themes/d-dark.json5'),
@ -27,6 +28,7 @@ export const builtinThemes = [
require('@/themes/d-astro.json5'),
require('@/themes/d-future.json5'),
require('@/themes/d-botanical.json5'),
require('@/themes/d-cherry.json5'),
require('@/themes/d-pumpkin.json5'),
require('@/themes/d-black.json5'),
] as Theme[];

View File

@ -5,34 +5,35 @@ import { $i } from '@/account';
export function useNoteCapture(props: {
rootEl: Ref<HTMLElement>;
appearNote: Ref<misskey.entities.Note>;
note: Ref<misskey.entities.Note>;
isDeletedRef: Ref<boolean>;
}) {
const appearNote = props.appearNote;
const note = props.note;
const connection = $i ? stream : null;
function onStreamNoteUpdated(data): void {
const { type, id, body } = data;
if (id !== appearNote.value.id) return;
if (id !== note.value.id) return;
switch (type) {
case 'reacted': {
const reaction = body.reaction;
if (body.emoji) {
const emojis = appearNote.value.emojis || [];
const emojis = note.value.emojis || [];
if (!emojis.includes(body.emoji)) {
appearNote.value.emojis = [...emojis, body.emoji];
note.value.emojis = [...emojis, body.emoji];
}
}
// TODO: reactionsプロパティがない場合ってあったっけ なければ || {} は消せる
const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
const currentCount = (note.value.reactions || {})[reaction] || 0;
appearNote.value.reactions[reaction] = currentCount + 1;
note.value.reactions[reaction] = currentCount + 1;
if ($i && (body.userId === $i.id)) {
appearNote.value.myReaction = reaction;
note.value.myReaction = reaction;
}
break;
}
@ -41,12 +42,12 @@ export function useNoteCapture(props: {
const reaction = body.reaction;
// TODO: reactionsプロパティがない場合ってあったっけ なければ || {} は消せる
const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
const currentCount = (note.value.reactions || {})[reaction] || 0;
appearNote.value.reactions[reaction] = Math.max(0, currentCount - 1);
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
if ($i && (body.userId === $i.id)) {
appearNote.value.myReaction = null;
note.value.myReaction = null;
}
break;
}
@ -54,7 +55,7 @@ export function useNoteCapture(props: {
case 'pollVoted': {
const choice = body.choice;
const choices = [...appearNote.value.poll.choices];
const choices = [...note.value.poll.choices];
choices[choice] = {
...choices[choice],
votes: choices[choice].votes + 1,
@ -63,12 +64,12 @@ export function useNoteCapture(props: {
} : {})
};
appearNote.value.poll.choices = choices;
note.value.poll.choices = choices;
break;
}
case 'deleted': {
appearNote.value.deletedAt = new Date();
props.isDeletedRef.value = true;
break;
}
}
@ -77,7 +78,7 @@ export function useNoteCapture(props: {
function capture(withHandler = false): void {
if (connection) {
// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id });
connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: note.value.id });
if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
}
}
@ -85,7 +86,7 @@ export function useNoteCapture(props: {
function decapture(withHandler = false): void {
if (connection) {
connection.send('un', {
id: appearNote.value.id,
id: note.value.id,
});
if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated);
}

View File

@ -0,0 +1,20 @@
{
id: '679b3b87-a4e9-4789-8696-b56c15cc33b0',
name: 'Mi Cherry Dark',
author: 'syuilo',
base: 'dark',
props: {
accent: 'rgb(255, 89, 117)',
bg: 'rgb(28, 28, 37)',
fg: 'rgb(236, 239, 244)',
panel: 'rgb(35, 35, 47)',
renote: '@accent',
link: '@accent',
mention: '@accent',
hashtag: '@accent',
divider: 'rgb(63, 63, 80)',
},
}

View File

@ -0,0 +1,21 @@
{
id: 'ac168876-f737-4074-a3fc-a370c732ef48',
name: 'Mi Cherry Light',
author: 'syuilo',
base: 'light',
props: {
accent: 'rgb(219, 96, 114)',
bg: 'rgb(254, 248, 249)',
fg: 'rgb(152, 13, 26)',
panel: 'rgb(255, 255, 255)',
renote: '@accent',
link: 'rgb(156, 187, 5)',
mention: '@accent',
hashtag: '@accent',
divider: 'rgba(134, 51, 51, 0.1)',
inputBorderHover: 'rgb(238, 221, 222)',
},
}

View File

@ -305,7 +305,7 @@ export default defineComponent({
&.post:before {
width: calc(100% - 28px);
height: min-content;
height: auto;
aspect-ratio: 1/1;
border-radius: 100%;
}

View File

@ -276,7 +276,7 @@ export default defineComponent({
}
> * {
font-size: 22px;
font-size: 20px;
}
&:disabled {

View File

@ -340,13 +340,14 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
z-index: 1000;
bottom: 0;
left: 0;
padding: 16px;
padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px) 16px;
display: flex;
width: 100%;
box-sizing: border-box;
-webkit-backdrop-filter: var(--blur, blur(32px));
backdrop-filter: var(--blur, blur(32px));
background-color: var(--header);
border-top: solid 0.5px var(--divider);
> .button {
position: relative;
@ -392,7 +393,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
}
> * {
font-size: 22px;
font-size: 20px;
}
&:disabled {