Compare commits
23 Commits
12.106.1
...
12.107.0-s
Author | SHA1 | Date | |
---|---|---|---|
6e7e11e061 | |||
8d568d533b | |||
348a5f3d7c | |||
2de4978a31 | |||
642a51a558 | |||
1c6ab5447d | |||
e7d6bd19eb | |||
f6e40a9092 | |||
720d5db041 | |||
fd3ce321c5 | |||
7df8cd2b5d | |||
64f4231283 | |||
0589171ceb | |||
3cf9c30974 | |||
c1f0fa5bd6 | |||
b01a0325ba | |||
eef8f63dc6 | |||
de6e3d64b4 | |||
ed38233044 | |||
64874417e0 | |||
b3decdc4e5 | |||
9f9b8d1cae | |||
c456825d0e |
26
CHANGELOG.md
26
CHANGELOG.md
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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: "自动重载"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "12.106.1",
|
||||
"version": "12.107.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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(/^\/(.+)\/(.*)$/);
|
||||
|
@ -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[];
|
||||
|
@ -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);
|
||||
}
|
||||
|
20
packages/client/src/themes/d-cherry.json5
Normal file
20
packages/client/src/themes/d-cherry.json5
Normal 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)',
|
||||
},
|
||||
}
|
21
packages/client/src/themes/l-cherry.json5
Normal file
21
packages/client/src/themes/l-cherry.json5
Normal 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)',
|
||||
},
|
||||
}
|
@ -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%;
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
> * {
|
||||
font-size: 22px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user