summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-04-26 13:00:10 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-04-26 13:00:10 +0900
commit17fff8c66525d62721bd1fa29d11360908904ec3 (patch)
tree4f22894599833ba4ef7f5b15da0bc1571cc66eb8 /src
parentMerge branch 'develop' (diff)
parent12.79.2 (diff)
downloadmisskey-17fff8c66525d62721bd1fa29d11360908904ec3.tar.gz
misskey-17fff8c66525d62721bd1fa29d11360908904ec3.tar.bz2
misskey-17fff8c66525d62721bd1fa29d11360908904ec3.zip
Merge branch 'develop'
Diffstat (limited to 'src')
-rw-r--r--src/client/components/captcha.vue26
-rw-r--r--src/client/components/drive.file.vue2
-rw-r--r--src/client/components/drive.folder.vue2
-rw-r--r--src/client/components/drive.vue2
-rw-r--r--src/client/components/post-form-attaches.vue2
-rw-r--r--src/client/pages/gallery/edit.vue168
-rw-r--r--src/client/pages/gallery/new.vue110
-rw-r--r--src/client/pages/gallery/post.vue10
-rw-r--r--src/client/pages/mentions.vue1
-rw-r--r--src/client/pages/user/index.vue6
-rw-r--r--src/client/router.ts3
-rw-r--r--src/client/scripts/theme.ts2
-rw-r--r--src/client/themes/d-astro.json576
-rw-r--r--src/client/themes/l-light.json52
-rw-r--r--src/client/themes/l-vivid.json582
-rw-r--r--src/client/ui/deck/mentions-column.vue1
-rw-r--r--src/docs/it-IT/reversi-bot.md2
-rw-r--r--src/server/api/endpoints/gallery/posts/delete.ts40
-rw-r--r--src/server/api/endpoints/gallery/posts/update.ts81
-rw-r--r--src/server/web/index.ts20
20 files changed, 496 insertions, 142 deletions
diff --git a/src/client/components/captcha.vue b/src/client/components/captcha.vue
index 26215df09d..5da8ede3b9 100644
--- a/src/client/components/captcha.vue
+++ b/src/client/components/captcha.vue
@@ -6,7 +6,7 @@
</template>
<script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, PropType } from 'vue';
type Captcha = {
render(container: string | Node, options: {
@@ -32,7 +32,7 @@ declare global {
export default defineComponent({
props: {
provider: {
- type: String,
+ type: String as PropType<CaptchaProvider>,
required: true,
},
sitekey: {
@@ -51,19 +51,25 @@ export default defineComponent({
},
computed: {
- loaded() {
- return !!window[this.provider as CaptchaProvider];
+ variable(): string {
+ switch (this.provider) {
+ case 'hcaptcha': return 'hcaptcha';
+ case 'recaptcha': return 'grecaptcha';
+ }
+ },
+ loaded(): boolean {
+ return !!window[this.variable];
},
- src() {
+ src(): string {
const endpoint = ({
hcaptcha: 'https://hcaptcha.com/1',
recaptcha: 'https://www.recaptcha.net/recaptcha',
- } as Record<PropertyKey, unknown>)[this.provider];
+ } as Record<CaptchaProvider, string>)[this.provider];
- return `${typeof endpoint == 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`;
+ return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`;
},
- captcha() {
- return window[this.provider as CaptchaProvider] || {} as unknown as Captcha;
+ captcha(): Captcha {
+ return window[this.variable] || {} as unknown as Captcha;
},
},
@@ -94,7 +100,7 @@ export default defineComponent({
methods: {
reset() {
- this.captcha?.reset();
+ if (this.captcha?.reset) this.captcha.reset();
},
requestRender() {
if (this.captcha.render && this.$refs.captcha instanceof Element) {
diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue
index 293465e428..37b1afc1b3 100644
--- a/src/client/components/drive.file.vue
+++ b/src/client/components/drive.file.vue
@@ -81,7 +81,7 @@ export default defineComponent({
getMenu() {
return [{
text: this.$ts.rename,
- icon: faICursor,
+ icon: 'fas fa-i-cursor',
action: this.rename
}, {
text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
diff --git a/src/client/components/drive.folder.vue b/src/client/components/drive.folder.vue
index 6ddd392527..4c09e7775a 100644
--- a/src/client/components/drive.folder.vue
+++ b/src/client/components/drive.folder.vue
@@ -247,7 +247,7 @@ export default defineComponent({
}
}, null, {
text: this.$ts.rename,
- icon: faICursor,
+ icon: 'fas fa-i-cursor',
action: this.rename
}, null, {
text: this.$ts.delete,
diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue
index bd5bd8008b..06f9cf7806 100644
--- a/src/client/components/drive.vue
+++ b/src/client/components/drive.vue
@@ -614,7 +614,7 @@ export default defineComponent({
type: 'label'
}, this.folder ? {
text: this.$ts.renameFolder,
- icon: faICursor,
+ icon: 'fas fa-i-cursor',
action: () => { this.renameFolder(this.folder); }
} : undefined, this.folder ? {
text: this.$ts.deleteFolder,
diff --git a/src/client/components/post-form-attaches.vue b/src/client/components/post-form-attaches.vue
index eba7e7af36..f832ea87b5 100644
--- a/src/client/components/post-form-attaches.vue
+++ b/src/client/components/post-form-attaches.vue
@@ -93,7 +93,7 @@ export default defineComponent({
if (this.menu) return;
this.menu = os.modalMenu([{
text: this.$ts.renameFile,
- icon: faICursor,
+ icon: 'fas fa-i-cursor',
action: () => { this.rename(file) }
}, {
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
diff --git a/src/client/pages/gallery/edit.vue b/src/client/pages/gallery/edit.vue
new file mode 100644
index 0000000000..cd6a0defdd
--- /dev/null
+++ b/src/client/pages/gallery/edit.vue
@@ -0,0 +1,168 @@
+<template>
+<FormBase>
+ <FormSuspense :p="init">
+ <FormInput v-model:value="title">
+ <span>{{ $ts.title }}</span>
+ </FormInput>
+
+ <FormTextarea v-model:value="description" :max="500">
+ <span>{{ $ts.description }}</span>
+ </FormTextarea>
+
+ <FormGroup>
+ <div v-for="file in files" :key="file.id" class="_formItem _formPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
+ <div class="name">{{ file.name }}</div>
+ <button class="remove _button" @click="remove(file)" v-tooltip="$ts.remove"><i class="fas fa-times"></i></button>
+ </div>
+ <FormButton @click="selectFile" primary><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton>
+ </FormGroup>
+
+ <FormSwitch v-model:value="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch>
+
+ <FormButton v-if="postId" @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+ <FormButton v-else @click="save" primary><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton>
+
+ <FormButton v-if="postId" @click="del" danger><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
+ </FormSuspense>
+</FormBase>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import FormButton from '@client/components/form/button.vue';
+import FormInput from '@client/components/form/input.vue';
+import FormTextarea from '@client/components/form/textarea.vue';
+import FormSwitch from '@client/components/form/switch.vue';
+import FormTuple from '@client/components/form/tuple.vue';
+import FormBase from '@client/components/form/base.vue';
+import FormGroup from '@client/components/form/group.vue';
+import FormSuspense from '@client/components/form/suspense.vue';
+import { selectFile } from '@client/scripts/select-file';
+import * as os from '@client/os';
+import * as symbols from '@client/symbols';
+
+export default defineComponent({
+ components: {
+ FormButton,
+ FormInput,
+ FormTextarea,
+ FormSwitch,
+ FormBase,
+ FormGroup,
+ FormSuspense,
+ },
+
+ props: {
+ postId: {
+ type: String,
+ required: false,
+ default: null,
+ }
+ },
+
+ data() {
+ return {
+ [symbols.PAGE_INFO]: computed(() => this.postId ? {
+ title: this.$ts.edit,
+ icon: 'fas fa-pencil-alt'
+ } : {
+ title: this.$ts.postToGallery,
+ icon: 'fas fa-pencil-alt'
+ }),
+ init: null,
+ files: [],
+ description: null,
+ title: null,
+ isSensitive: false,
+ }
+ },
+
+ watch: {
+ postId: {
+ handler() {
+ this.init = () => this.postId ? os.api('gallery/posts/show', {
+ postId: this.postId
+ }).then(post => {
+ this.files = post.files;
+ this.title = post.title;
+ this.description = post.description;
+ this.isSensitive = post.isSensitive;
+ }) : Promise.resolve(null);
+ },
+ immediate: true,
+ }
+ },
+
+ methods: {
+ selectFile(e) {
+ selectFile(e.currentTarget || e.target, null, true).then(files => {
+ this.files = this.files.concat(files);
+ });
+ },
+
+ remove(file) {
+ this.files = this.files.filter(f => f.id !== file.id);
+ },
+
+ async save() {
+ if (this.postId) {
+ await os.apiWithDialog('gallery/posts/update', {
+ postId: this.postId,
+ title: this.title,
+ description: this.description,
+ fileIds: this.files.map(file => file.id),
+ isSensitive: this.isSensitive,
+ });
+ this.$router.push(`/gallery/${this.postId}`);
+ } else {
+ const post = await os.apiWithDialog('gallery/posts/create', {
+ title: this.title,
+ description: this.description,
+ fileIds: this.files.map(file => file.id),
+ isSensitive: this.isSensitive,
+ });
+ this.$router.push(`/gallery/${post.id}`);
+ }
+ },
+
+ async del() {
+ const { canceled } = await os.dialog({
+ type: 'warning',
+ text: this.$ts.deleteConfirm,
+ showCancelButton: true
+ });
+ if (canceled) return;
+ await os.apiWithDialog('gallery/posts/delete', {
+ postId: this.postId,
+ });
+ this.$router.push(`/gallery`);
+ }
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.wqugxsfx {
+ height: 200px;
+ background-size: contain;
+ background-position: center;
+ background-repeat: no-repeat;
+ position: relative;
+
+ > .name {
+ position: absolute;
+ top: 8px;
+ left: 9px;
+ padding: 8px;
+ background: var(--panel);
+ }
+
+ > .remove {
+ position: absolute;
+ top: 8px;
+ right: 9px;
+ padding: 8px;
+ background: var(--panel);
+ }
+}
+</style>
diff --git a/src/client/pages/gallery/new.vue b/src/client/pages/gallery/new.vue
deleted file mode 100644
index 3f9756df8e..0000000000
--- a/src/client/pages/gallery/new.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<template>
-<FormBase>
- <FormInput v-model:value="title">
- <span>{{ $ts.title }}</span>
- </FormInput>
-
- <FormTextarea v-model:value="description" :max="500">
- <span>{{ $ts.description }}</span>
- </FormTextarea>
-
- <FormGroup>
- <div v-for="file in files" :key="file.id" class="_formItem _formPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
- <div class="name">{{ file.name }}</div>
- <button class="remove _button" @click="remove(file)" v-tooltip="$ts.remove"><i class="fas fa-times"></i></button>
- </div>
- <FormButton @click="selectFile" primary><i class="fas fa-plus"></i> {{ $ts.attachFile }}</FormButton>
- </FormGroup>
-
- <FormSwitch v-model:value="isSensitive">{{ $ts.markAsSensitive }}</FormSwitch>
-
- <FormButton @click="publish" primary><i class="fas fa-save"></i> {{ $ts.publish }}</FormButton>
-</FormBase>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from '@client/components/form/button.vue';
-import FormInput from '@client/components/form/input.vue';
-import FormTextarea from '@client/components/form/textarea.vue';
-import FormSwitch from '@client/components/form/switch.vue';
-import FormTuple from '@client/components/form/tuple.vue';
-import FormBase from '@client/components/form/base.vue';
-import FormGroup from '@client/components/form/group.vue';
-import { selectFile } from '@client/scripts/select-file';
-import * as os from '@client/os';
-import * as symbols from '@client/symbols';
-
-export default defineComponent({
- components: {
- FormButton,
- FormInput,
- FormTextarea,
- FormSwitch,
- FormBase,
- FormGroup,
- },
-
- data() {
- return {
- [symbols.PAGE_INFO]: {
- title: this.$ts.postToGallery,
- icon: 'fas fa-pencil-alt'
- },
- files: [],
- description: null,
- title: null,
- isSensitive: false,
- }
- },
-
- methods: {
- selectFile(e) {
- selectFile(e.currentTarget || e.target, null, true).then(files => {
- this.files = this.files.concat(files);
- });
- },
-
- remove(file) {
- this.files = this.files.filter(f => f.id !== file.id);
- },
-
- async publish() {
- const post = await os.apiWithDialog('gallery/posts/create', {
- title: this.title,
- description: this.description,
- fileIds: this.files.map(file => file.id),
- isSensitive: this.isSensitive,
- });
-
- this.$router.push(`/gallery/${post.id}`);
- }
- }
-});
-</script>
-
-<style lang="scss" scoped>
-.wqugxsfx {
- height: 200px;
- background-size: contain;
- background-position: center;
- background-repeat: no-repeat;
- position: relative;
-
- > .name {
- position: absolute;
- top: 8px;
- left: 9px;
- padding: 8px;
- background: var(--panel);
- }
-
- > .remove {
- position: absolute;
- top: 8px;
- right: 9px;
- padding: 8px;
- background: var(--panel);
- }
-}
-</style>
diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue
index 9bd102cee2..703506a78d 100644
--- a/src/client/pages/gallery/post.vue
+++ b/src/client/pages/gallery/post.vue
@@ -19,6 +19,7 @@
<MkButton class="button" @click="like()" v-else v-tooltip="$ts._gallery.like"><i class="far fa-heart"></i><span class="count" v-if="post.likedCount > 0">{{ post.likedCount }}</span></MkButton>
</div>
<div class="other">
+ <button v-if="$i && $i.id === post.user.id" class="_button" @click="edit" v-tooltip="$ts.edit" v-click-anime><i class="fas fa-pencil-alt fa-fw"></i></button>
<button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button>
<button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
</div>
@@ -84,6 +85,11 @@ export default defineComponent({
title: this.post.title,
text: this.post.description,
},
+ actions: [{
+ icon: 'fas fa-pencil-alt',
+ text: this.$ts.edit,
+ handler: this.edit
+ }]
} : null),
otherPostsPagination: {
endpoint: 'users/gallery/posts',
@@ -154,6 +160,10 @@ export default defineComponent({
this.post.likedCount--;
});
},
+
+ edit() {
+ this.$router.push(`/gallery/${this.post.id}/edit`);
+ }
}
});
</script>
diff --git a/src/client/pages/mentions.vue b/src/client/pages/mentions.vue
index a12993ebb8..798d3e342d 100644
--- a/src/client/pages/mentions.vue
+++ b/src/client/pages/mentions.vue
@@ -25,7 +25,6 @@ export default defineComponent({
endpoint: 'notes/mentions',
limit: 10,
},
- faAt
};
},
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
index a1fe7ec09f..717ca5d8fe 100644
--- a/src/client/pages/user/index.vue
+++ b/src/client/pages/user/index.vue
@@ -161,15 +161,15 @@
</dl>
</div>
<div class="status">
- <MkA :to="userPage(user)" :class="{ active: page === 'index' }">
+ <MkA :to="userPage(user)" :class="{ active: page === 'index' }" v-click-anime>
<b>{{ number(user.notesCount) }}</b>
<span>{{ $ts.notes }}</span>
</MkA>
- <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
+ <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }" v-click-anime>
<b>{{ number(user.followingCount) }}</b>
<span>{{ $ts.following }}</span>
</MkA>
- <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
+ <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }" v-click-anime>
<b>{{ number(user.followersCount) }}</b>
<span>{{ $ts.followers }}</span>
</MkA>
diff --git a/src/client/router.ts b/src/client/router.ts
index 5371bf17d9..8dcc1d1eb4 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -38,7 +38,8 @@ export const router = createRouter({
{ path: '/pages/new', component: page('page-editor/page-editor') },
{ path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/gallery', component: page('gallery/index') },
- { path: '/gallery/new', component: page('gallery/new') },
+ { path: '/gallery/new', component: page('gallery/edit') },
+ { path: '/gallery/:postId/edit', component: page('gallery/edit'), props: route => ({ postId: route.params.postId }) },
{ path: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) },
{ path: '/channels', component: page('channels') },
{ path: '/channels/new', component: page('channel-editor') },
diff --git a/src/client/scripts/theme.ts b/src/client/scripts/theme.ts
index b0bf620a7d..09441c8a76 100644
--- a/src/client/scripts/theme.ts
+++ b/src/client/scripts/theme.ts
@@ -18,9 +18,11 @@ export const builtinThemes = [
require('@client/themes/l-light.json5'),
require('@client/themes/l-apricot.json5'),
require('@client/themes/l-rainy.json5'),
+ require('@client/themes/l-vivid.json5'),
require('@client/themes/d-dark.json5'),
require('@client/themes/d-persimmon.json5'),
+ require('@client/themes/d-astro.json5'),
require('@client/themes/d-black.json5'),
] as Theme[];
diff --git a/src/client/themes/d-astro.json5 b/src/client/themes/d-astro.json5
new file mode 100644
index 0000000000..a88e949c3c
--- /dev/null
+++ b/src/client/themes/d-astro.json5
@@ -0,0 +1,76 @@
+{
+ id: '080a01c5-377d-4fbb-88cc-6bb5d04977ea',
+ base: 'dark',
+ name: 'Mi Astro',
+ author: 'syuilo',
+ props: {
+ bg: '#232125',
+ fg: '#efdab9',
+ cwBg: '#687390',
+ cwFg: '#393f4f',
+ link: '#78b0a0',
+ warn: '#ecb637',
+ badge: '#31b1ce',
+ error: '#ec4137',
+ focus: ':alpha<0.3<@accent',
+ navBg: '@panel',
+ navFg: '@fg',
+ panel: '#2a272b',
+ accent: '#81c08b',
+ header: ':alpha<0.7<@bg',
+ infoBg: '#253142',
+ infoFg: '#fff',
+ renote: '#659CC8',
+ shadow: 'rgba(0, 0, 0, 0.3)',
+ divider: 'rgba(255, 255, 255, 0.1)',
+ hashtag: '#ff9156',
+ mention: '#ffd152',
+ modalBg: 'rgba(0, 0, 0, 0.5)',
+ success: '#86b300',
+ buttonBg: 'rgba(255, 255, 255, 0.05)',
+ acrylicBg: ':alpha<0.5<@bg',
+ cwHoverBg: '#707b97',
+ indicator: '@accent',
+ mentionMe: '#fb5d38',
+ messageBg: ':lighten<5<@bg',
+ navActive: '@accent',
+ infoWarnBg: '#42321c',
+ infoWarnFg: '#ffbd3e',
+ navHoverFg: ':lighten<17<@fg',
+ dateLabelFg: '@fg',
+ inputBorder: '#959da2',
+ panelBorder: 'rgba(0, 0, 0, 0)',
+ panelShadow: '" 0 8px 24px rgba(0, 0, 0, 0.12)',
+ accentDarken: ':darken<10<@accent',
+ acrylicPanel: ':alpha<0.5<@panel',
+ navIndicator: '@accent',
+ accentLighten: ':lighten<10<@accent',
+ buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
+ driveFolderBg: ':alpha<0.3<@accent',
+ fgHighlighted: ':lighten<3<@fg',
+ panelHeaderBg: ':lighten<3<@panel',
+ panelHeaderFg: '@fg',
+ htmlThemeColor: '@bg',
+ panelHighlight: ':lighten<3<@panel',
+ listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
+ scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
+ wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
+ panelHeaderDivider: 'rgba(0, 0, 0, 0)',
+ scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
+ X2: ':darken<2<@panel',
+ X3: 'rgba(255, 255, 255, 0.05)',
+ X4: 'rgba(255, 255, 255, 0.1)',
+ X5: 'rgba(255, 255, 255, 0.05)',
+ X6: 'rgba(255, 255, 255, 0.15)',
+ X7: 'rgba(255, 255, 255, 0.05)',
+ X8: ':lighten<5<@accent',
+ X9: ':darken<5<@accent',
+ X10: ':alpha<0.4<@accent',
+ X11: 'rgba(0, 0, 0, 0.3)',
+ X12: 'rgba(255, 255, 255, 0.1)',
+ X13: 'rgba(255, 255, 255, 0.15)',
+ X14: ':alpha<0.5<@navBg',
+ X15: ':alpha<0<@panel',
+ X16: ':alpha<0.7<@panel',
+ },
+}
diff --git a/src/client/themes/l-light.json5 b/src/client/themes/l-light.json5
index fdc1700b95..27a973c88a 100644
--- a/src/client/themes/l-light.json5
+++ b/src/client/themes/l-light.json5
@@ -10,7 +10,7 @@
props: {
bg: '#f9f9f9',
fg: '#676767',
- divider: 'rgb(223, 223, 223)',
+ divider: '#e8e8e8',
header: ':alpha<0.7<@panel',
navBg: '#fff',
panel: '#fff',
diff --git a/src/client/themes/l-vivid.json5 b/src/client/themes/l-vivid.json5
new file mode 100644
index 0000000000..a21e053b0a
--- /dev/null
+++ b/src/client/themes/l-vivid.json5
@@ -0,0 +1,82 @@
+{
+ id: '6128c2a9-5c54-43fe-a47d-17942356470b',
+
+ name: 'Mi Vivid',
+ author: 'syuilo',
+
+ base: 'light',
+
+ props: {
+ bg: '#fafafa',
+ fg: '#444',
+ cwBg: '#b1b9c1',
+ cwFg: '#fff',
+ link: '#ff9400',
+ warn: '#ecb637',
+ badge: '#31b1ce',
+ error: '#ec4137',
+ focus: ':alpha<0.3<@accent',
+ navBg: '@panel',
+ navFg: '@fg',
+ panel: '#fff',
+ accent: '#008cff',
+ header: ':alpha<0.7<@panel',
+ infoBg: '#e5f5ff',
+ infoFg: '#72818a',
+ renote: '@accent',
+ shadow: 'rgba(0, 0, 0, 0.1)',
+ divider: 'rgba(0, 0, 0, 0.08)',
+ hashtag: '#92d400',
+ mention: '@accent',
+ modalBg: 'rgba(0, 0, 0, 0.3)',
+ success: '#86b300',
+ buttonBg: 'rgba(0, 0, 0, 0.05)',
+ acrylicBg: ':alpha<0.5<@bg',
+ cwHoverBg: '#bbc4ce',
+ indicator: '@accent',
+ mentionMe: '@mention',
+ messageBg: '@panel',
+ navActive: '@accent',
+ infoWarnBg: '#fff0db',
+ infoWarnFg: '#573c08',
+ navHoverFg: ':darken<17<@fg',
+ dateLabelFg: '@fg',
+ inputBorder: '#dae0e4',
+ panelBorder: 'rgba(0, 0, 0, 0)',
+ panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
+ accentDarken: ':darken<10<@accent',
+ acrylicPanel: ':alpha<0.5<@panel',
+ navIndicator: '@accent',
+ accentLighten: ':lighten<10<@accent',
+ buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
+ driveFolderBg: ':alpha<0.3<@accent',
+ fgHighlighted: ':darken<3<@fg',
+ fgTransparent: ':alpha<0.5<@fg',
+ panelHeaderBg: ':lighten<3<@panel',
+ panelHeaderFg: '@fg',
+ htmlThemeColor: '@bg',
+ panelHighlight: ':darken<3<@panel',
+ listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
+ scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
+ wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
+ fgTransparentWeak: ':alpha<0.75<@fg',
+ panelHeaderDivider: '@divider',
+ scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
+ X2: ':darken<2<@panel',
+ X3: 'rgba(0, 0, 0, 0.05)',
+ X4: 'rgba(0, 0, 0, 0.1)',
+ X5: 'rgba(0, 0, 0, 0.05)',
+ X6: 'rgba(0, 0, 0, 0.25)',
+ X7: 'rgba(0, 0, 0, 0.05)',
+ X8: ':lighten<5<@accent',
+ X9: ':darken<5<@accent',
+ X10: ':alpha<0.4<@accent',
+ X11: 'rgba(0, 0, 0, 0.1)',
+ X12: 'rgba(0, 0, 0, 0.1)',
+ X13: 'rgba(0, 0, 0, 0.15)',
+ X14: ':alpha<0.5<@navBg',
+ X15: ':alpha<0<@panel',
+ X16: ':alpha<0.7<@panel',
+ X17: ':alpha<0.8<@bg',
+ },
+}
diff --git a/src/client/ui/deck/mentions-column.vue b/src/client/ui/deck/mentions-column.vue
index 053ef918f0..c625bb3ea1 100644
--- a/src/client/ui/deck/mentions-column.vue
+++ b/src/client/ui/deck/mentions-column.vue
@@ -36,7 +36,6 @@ export default defineComponent({
endpoint: 'notes/mentions',
limit: 10,
},
- faAt
}
},
diff --git a/src/docs/it-IT/reversi-bot.md b/src/docs/it-IT/reversi-bot.md
index 7ab2a7212e..00d4a18a8e 100644
--- a/src/docs/it-IT/reversi-bot.md
+++ b/src/docs/it-IT/reversi-bot.md
@@ -110,7 +110,7 @@ y = Math.floor(pos / mapWidth)
```
### フォームコントロールの種類
-#### スイッチ
+#### Interruttore
type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。
##### プロパティ
diff --git a/src/server/api/endpoints/gallery/posts/delete.ts b/src/server/api/endpoints/gallery/posts/delete.ts
new file mode 100644
index 0000000000..8b54828b20
--- /dev/null
+++ b/src/server/api/endpoints/gallery/posts/delete.ts
@@ -0,0 +1,40 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { ApiError } from '../../../error';
+import { GalleryPosts } from '../../../../../models';
+import { ID } from '@/misc/cafy-id';
+
+export const meta = {
+ tags: ['gallery'],
+
+ requireCredential: true as const,
+
+ kind: 'write:gallery',
+
+ params: {
+ postId: {
+ validator: $.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchPost: {
+ message: 'No such post.',
+ code: 'NO_SUCH_POST',
+ id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5'
+ },
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const post = await GalleryPosts.findOne({
+ id: ps.postId,
+ userId: user.id,
+ });
+
+ if (post == null) {
+ throw new ApiError(meta.errors.noSuchPost);
+ }
+
+ await GalleryPosts.delete(post.id);
+});
diff --git a/src/server/api/endpoints/gallery/posts/update.ts b/src/server/api/endpoints/gallery/posts/update.ts
new file mode 100644
index 0000000000..c8bb8d48c9
--- /dev/null
+++ b/src/server/api/endpoints/gallery/posts/update.ts
@@ -0,0 +1,81 @@
+import $ from 'cafy';
+import * as ms from 'ms';
+import define from '../../../define';
+import { ID } from '../../../../../misc/cafy-id';
+import { DriveFiles, GalleryPosts } from '../../../../../models';
+import { GalleryPost } from '../../../../../models/entities/gallery-post';
+import { ApiError } from '../../../error';
+
+export const meta = {
+ tags: ['gallery'],
+
+ requireCredential: true as const,
+
+ kind: 'write:gallery',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 300
+ },
+
+ params: {
+ postId: {
+ validator: $.type(ID),
+ },
+
+ title: {
+ validator: $.str.min(1),
+ },
+
+ description: {
+ validator: $.optional.nullable.str,
+ },
+
+ fileIds: {
+ validator: $.arr($.type(ID)).unique().range(1, 32),
+ },
+
+ isSensitive: {
+ validator: $.optional.bool,
+ default: false,
+ },
+ },
+
+ res: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'GalleryPost',
+ },
+
+ errors: {
+
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const files = (await Promise.all(ps.fileIds.map(fileId =>
+ DriveFiles.findOne({
+ id: fileId,
+ userId: user.id
+ })
+ ))).filter(file => file != null);
+
+ if (files.length === 0) {
+ throw new Error();
+ }
+
+ await GalleryPosts.update({
+ id: ps.postId,
+ userId: user.id,
+ }, {
+ updatedAt: new Date(),
+ title: ps.title,
+ description: ps.description,
+ isSensitive: ps.isSensitive,
+ fileIds: files.map(file => file.id)
+ });
+
+ const post = await GalleryPosts.findOneOrFail(ps.postId);
+
+ return await GalleryPosts.pack(post, user);
+});
diff --git a/src/server/web/index.ts b/src/server/web/index.ts
index c3b184088b..cc343063af 100644
--- a/src/server/web/index.ts
+++ b/src/server/web/index.ts
@@ -252,7 +252,7 @@ router.get('/users/:user', async ctx => {
});
// Note
-router.get('/notes/:note', async ctx => {
+router.get('/notes/:note', async (ctx, next) => {
const note = await Notes.findOne(ctx.params.note);
if (note) {
@@ -277,11 +277,11 @@ router.get('/notes/:note', async ctx => {
return;
}
- ctx.status = 404;
+ await next();
});
// Page
-router.get('/@:user/pages/:page', async ctx => {
+router.get('/@:user/pages/:page', async (ctx, next) => {
const { username, host } = parseAcct(ctx.params.user);
const user = await Users.findOne({
usernameLower: username.toLowerCase(),
@@ -314,12 +314,12 @@ router.get('/@:user/pages/:page', async ctx => {
return;
}
- ctx.status = 404;
+ await next();
});
// Clip
// TODO: 非publicなclipのハンドリング
-router.get('/clips/:clip', async ctx => {
+router.get('/clips/:clip', async (ctx, next) => {
const clip = await Clips.findOne({
id: ctx.params.clip,
});
@@ -339,11 +339,11 @@ router.get('/clips/:clip', async ctx => {
return;
}
- ctx.status = 404;
+ await next();
});
// Gallery post
-router.get('/gallery/:post', async ctx => {
+router.get('/gallery/:post', async (ctx, next) => {
const post = await GalleryPosts.findOne(ctx.params.post);
if (post) {
@@ -362,11 +362,11 @@ router.get('/gallery/:post', async ctx => {
return;
}
- ctx.status = 404;
+ await next();
});
// Channel
-router.get('/channels/:channel', async ctx => {
+router.get('/channels/:channel', async (ctx, next) => {
const channel = await Channels.findOne({
id: ctx.params.channel,
});
@@ -384,7 +384,7 @@ router.get('/channels/:channel', async ctx => {
return;
}
- ctx.status = 404;
+ await next();
});
//#endregion