Merge branch 'develop'

This commit is contained in:
syuilo 2021-04-26 13:00:10 +09:00
commit 17fff8c665
23 changed files with 562 additions and 153 deletions

View file

@ -900,11 +900,13 @@ _theme:
funcKind: "Type de fonction" funcKind: "Type de fonction"
argument: "Argument" argument: "Argument"
alpha: "Transparence" alpha: "Transparence"
darken: "Assombrir" darken: "Sombre"
lighten: "Clair"
inputConstantName: "Insérez un nom de constante" inputConstantName: "Insérez un nom de constante"
importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant son code ici." importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant son code ici."
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const} ?" deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const} ?"
keys: keys:
accent: "Accentuation"
bg: "Arrière-plan" bg: "Arrière-plan"
fg: "Texte" fg: "Texte"
focus: "Mise au point" focus: "Mise au point"
@ -940,6 +942,8 @@ _theme:
driveFolderBg: "Arrière-plan du dossier de disque" driveFolderBg: "Arrière-plan du dossier de disque"
badge: "Badge" badge: "Badge"
messageBg: "Arrière plan de la discussion" messageBg: "Arrière plan de la discussion"
accentDarken: "Plus sombre"
accentLighten: "Plus clair"
fgHighlighted: "Texte mis en évidence" fgHighlighted: "Texte mis en évidence"
_sfx: _sfx:
note: "Nouvelle note" note: "Nouvelle note"
@ -1155,12 +1159,12 @@ _instanceCharts:
usersTotal: "Nombre d'utilisateur·rice·s au total cumulé" usersTotal: "Nombre d'utilisateur·rice·s au total cumulé"
notes: "Variation du nombre des notes" notes: "Variation du nombre des notes"
notesTotal: "Nombre total cumulé des notes" notesTotal: "Nombre total cumulé des notes"
ff: "Variation des abonné·e·s" ff: "Variation des abonné·e·s et des abonnements"
ffTotal: "Nombre d'abonné·e·s au total cumulé" ffTotal: "Total cumulé du nombre d'abonné·e·s et du nombre d'abonnements"
cacheSize: "Variation de la taille du cache" cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "La taille du cache au total cumulé" cacheSizeTotal: "Total cumulé de la taille du cache"
files: "Variation du nombre de fichiers" files: "Variation du nombre de fichiers"
filesTotal: "Nombre de fichiers au total cumulé" filesTotal: "Total cumulé du nombre de fichiers"
_timelines: _timelines:
home: "Principal" home: "Principal"
local: "Local" local: "Local"
@ -1237,7 +1241,7 @@ _pages:
deleted: "La page a été supprimée" deleted: "La page a été supprimée"
pageSetting: "Paramètres de la Page" pageSetting: "Paramètres de la Page"
nameAlreadyExists: "L'URL de page spécifiée existe déjà" nameAlreadyExists: "L'URL de page spécifiée existe déjà"
invalidNameTitle: "La URL de la page spécifiée nest pas valide" invalidNameTitle: "L'URL de page spécifiée nest pas valide"
invalidNameText: "Assurez-vous quil nest pas vide" invalidNameText: "Assurez-vous quil nest pas vide"
editThisPage: "Éditer cette page" editThisPage: "Éditer cette page"
viewSource: "Afficher la source" viewSource: "Afficher la source"
@ -1259,14 +1263,14 @@ _pages:
font: "Police de caractères" font: "Police de caractères"
fontSerif: "Serif" fontSerif: "Serif"
fontSansSerif: "Sans Serif" fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Définir une image attirante" eyeCatchingImageSet: "Définir une image attractive"
eyeCatchingImageRemove: "Supprimer une image attirante" eyeCatchingImageRemove: "Supprimer l'image attractive"
chooseBlock: "Ajouter un bloc" chooseBlock: "Ajouter un bloc"
selectType: "Choisir un type" selectType: "Choisir un type"
enterVariableName: "Veuillez entrer un nom pour votre variable" enterVariableName: "Veuillez entrer un nom pour votre variable"
variableNameIsAlreadyUsed: "Cette variable est déjà utilisée" variableNameIsAlreadyUsed: "Ce nom de variable est déjà utilisé"
contentBlocks: "Contenu" contentBlocks: "Contenu"
inputBlocks: "Entrée" inputBlocks: "Blocs d'entrée"
specialBlocks: "Spécial" specialBlocks: "Spécial"
blocks: blocks:
text: "Texte" text: "Texte"

View file

@ -571,6 +571,7 @@ useGlobalSetting: "Usa impostazioni generali"
useGlobalSettingDesc: "Se abilitato, le impostazioni notifiche dell'account verranno utilizzate. Se disabilitato, si possono definire diverse singole impostazioni." useGlobalSettingDesc: "Se abilitato, le impostazioni notifiche dell'account verranno utilizzate. Se disabilitato, si possono definire diverse singole impostazioni."
other: "Avanzate" other: "Avanzate"
fileIdOrUrl: "ID o URL del file" fileIdOrUrl: "ID o URL del file"
chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta"
behavior: "Comportamento" behavior: "Comportamento"
abuseReports: "Segnala" abuseReports: "Segnala"
reportAbuse: "Segnala" reportAbuse: "Segnala"
@ -652,8 +653,10 @@ youAreRunningUpToDateClient: "Stai usando la versione più recente del client."
newVersionOfClientAvailable: "Una nuova versione del tuo client è disponibile." newVersionOfClientAvailable: "Una nuova versione del tuo client è disponibile."
usageAmount: "In utilizzo" usageAmount: "In utilizzo"
capacity: "Capacità" capacity: "Capacità"
inUse: "In utilizzo"
editCode: "Modifica codice" editCode: "Modifica codice"
apply: "Applica" apply: "Applica"
receiveAnnouncementFromInstance: "Ricevi i messaggi informativi dall'istanza"
emailNotification: "Eventi per notifiche via mail" emailNotification: "Eventi per notifiche via mail"
publish: "Pubblico" publish: "Pubblico"
inChannelSearch: "Cerca in canale" inChannelSearch: "Cerca in canale"
@ -932,6 +935,7 @@ _widgets:
photos: "Foto" photos: "Foto"
digitalClock: "Orologio digitale" digitalClock: "Orologio digitale"
federation: "Federazione" federation: "Federazione"
postForm: "Finestra di pubblicazione"
button: "Pulsante" button: "Pulsante"
onlineUsers: "Utenti online" onlineUsers: "Utenti online"
jobQueue: "Coda di lavoro" jobQueue: "Coda di lavoro"
@ -1005,6 +1009,9 @@ _instanceCharts:
users: "Variazione del numero di utenti" users: "Variazione del numero di utenti"
usersTotal: "Totale cumulativo di utenti" usersTotal: "Totale cumulativo di utenti"
notes: "Variazione del numero di note" notes: "Variazione del numero di note"
notesTotal: "Totale cumulato di note"
files: "Variazione del numero di file"
filesTotal: "Totale cumulato del numero di file"
_timelines: _timelines:
home: "Home" home: "Home"
local: "Locale" local: "Locale"
@ -1012,8 +1019,16 @@ _timelines:
global: "Federata" global: "Federata"
_rooms: _rooms:
roomOf: "Camera di {user}" roomOf: "Camera di {user}"
addFurniture: "Disponi mobilia"
translate: "Sposta"
rotate: "Ruota"
exit: "Indietro"
remove: "Togli" remove: "Togli"
clear: "Rimuovi tutto"
clearConfirm: "Sei sicur@ di voler rimuovere tutti i mobili dalla tua camera?"
leaveConfirm: "Hai fatto modifiche ancora non salvate. Vuoi davvero uscire?" leaveConfirm: "Hai fatto modifiche ancora non salvate. Vuoi davvero uscire?"
chooseImage: "Seleziona immagine"
roomType: "Tipo di stanza"
_roomType: _roomType:
default: "Predefinito" default: "Predefinito"
washitsu: "Washitsu" washitsu: "Washitsu"
@ -1050,6 +1065,7 @@ _rooms:
cube: "Cubo" cube: "Cubo"
tv: "Televisore" tv: "Televisore"
pinguin: "Pinguino" pinguin: "Pinguino"
rubik-cube: "Cubo di Rubik"
bin: "Cestino" bin: "Cestino"
cup-noodle: "Noodle istantanei" cup-noodle: "Noodle istantanei"
_pages: _pages:
@ -1060,6 +1076,8 @@ _pages:
updated: "Pagina aggiornata con successo!" updated: "Pagina aggiornata con successo!"
deleted: "Pagina eliminata" deleted: "Pagina eliminata"
pageSetting: "Impostazioni pagina" pageSetting: "Impostazioni pagina"
nameAlreadyExists: "Esiste già una pagina con lo stesso URL."
invalidNameTitle: "L'URL di pagina definito non è valido"
editThisPage: "Modifica questa pagina" editThisPage: "Modifica questa pagina"
viewSource: "Visualizza sorgente" viewSource: "Visualizza sorgente"
viewPage: "Visualizza pagina" viewPage: "Visualizza pagina"
@ -1072,11 +1090,21 @@ _pages:
content: "Blocco di pagina" content: "Blocco di pagina"
variables: "Variabili" variables: "Variabili"
title: "Titolo" title: "Titolo"
url: "URL della pagina"
summary: "Riassunto di pagina"
hideTitleWhenPinned: "Nascondere il titolo pagina quando è fissata in cima al profilo." hideTitleWhenPinned: "Nascondere il titolo pagina quando è fissata in cima al profilo."
font: "Tipo di carattere" font: "Tipo di carattere"
fontSerif: "Serif" fontSerif: "Serif"
fontSansSerif: "Sans serif" fontSansSerif: "Sans serif"
eyeCatchingImageSet: "Imposta un'immagine attrattiva"
eyeCatchingImageRemove: "Elimina l'immagine attrattiva"
chooseBlock: "Aggiungi blocco" chooseBlock: "Aggiungi blocco"
selectType: "Seleziona tipo"
enterVariableName: "Digita un nome di variabile"
variableNameIsAlreadyUsed: "Esiste già una variabile con lo stesso nome"
contentBlocks: "Contenuto"
inputBlocks: "Blocchi di input"
specialBlocks: "Speciale"
blocks: blocks:
text: "Testo" text: "Testo"
textarea: "Area di testo" textarea: "Area di testo"
@ -1086,16 +1114,20 @@ _pages:
if: "Se" if: "Se"
_if: _if:
variable: "Variabili" variable: "Variabili"
post: "Finestra di pubblicazione"
_post: _post:
text: "Contenuto" text: "Contenuto"
textInput: "Immissione testo"
_textInput: _textInput:
name: "Nome della variabile" name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito" default: "Valore predefinito"
textareaInput: "Immissione testo a più righe"
_textareaInput: _textareaInput:
name: "Nome della variabile" name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito" default: "Valore predefinito"
numberInput: "Immissione numerica"
_numberInput: _numberInput:
name: "Nome della variabile" name: "Nome della variabile"
text: "Titolo" text: "Titolo"
@ -1108,24 +1140,35 @@ _pages:
id: "ID nota" id: "ID nota"
idDescription: "Qui puoi anche incollare l'URL della nota che vuoi impostare." idDescription: "Qui puoi anche incollare l'URL della nota che vuoi impostare."
detailed: "Visualizzazione dettagliata" detailed: "Visualizzazione dettagliata"
switch: "Interruttore"
_switch: _switch:
name: "Nome della variabile" name: "Nome della variabile"
text: "Titolo" text: "Titolo"
default: "Valore predefinito" default: "Valore predefinito"
counter: "Contatore"
_counter: _counter:
name: "Nome della variabile" name: "Nome della variabile"
text: "Titolo" text: "Titolo"
inc: "Valore da aggiungere"
_button: _button:
text: "Titolo" text: "Titolo"
colored: "Colorato"
action: "Operazione da eseguire quando viene premuto il pulsante"
_action: _action:
dialog: "Visualizzare una finestra di dialogo"
_dialog: _dialog:
content: "Contenuto" content: "Contenuto"
resetRandom: "Ripristinare un numero aleatorio"
pushEvent: "Inviare evento" pushEvent: "Inviare evento"
_pushEvent: _pushEvent:
event: "Nome evento" event: "Nome evento"
message: "Messaggio da visualizzare quando abilitato" message: "Messaggio da visualizzare quando abilitato"
variable: "Variabile da inviare" variable: "Variabile da inviare"
no-variable: "Nessun contenuto" no-variable: "Nessun contenuto"
callAiScript: "Chiamare AiScript"
_callAiScript:
functionName: "Nome della funzione"
radioButton: "Opzioni"
_radioButton: _radioButton:
name: "Nome della variabile" name: "Nome della variabile"
title: "Titolo" title: "Titolo"
@ -1139,6 +1182,8 @@ _pages:
list: "Liste" list: "Liste"
blocks: blocks:
text: "Testo" text: "Testo"
multiLineText: "Testo (a più righe)"
textList: "Lista di testo"
_strLen: _strLen:
arg1: "Testo" arg1: "Testo"
_strPick: _strPick:
@ -1193,13 +1238,18 @@ _pages:
arg2: "B" arg2: "B"
_if: _if:
arg1: "Se" arg1: "Se"
arg2: "Se"
random: "Aleatorietà" random: "Aleatorietà"
_randomPick: _randomPick:
arg1: "Liste" arg1: "Liste"
_dailyRandomPick: _dailyRandomPick:
arg1: "Liste" arg1: "Liste"
_seedRandom:
arg2: "Probabilità"
_seedRandomPick: _seedRandomPick:
arg2: "Liste" arg2: "Liste"
_DRPWPM:
arg1: "Lista di testo"
_pick: _pick:
arg1: "Liste" arg1: "Liste"
_listLen: _listLen:
@ -1213,6 +1263,7 @@ _pages:
types: types:
string: "Testo" string: "Testo"
array: "Liste" array: "Liste"
stringArray: "Lista di testo"
_notification: _notification:
fileUploaded: "File caricato correttamente" fileUploaded: "File caricato correttamente"
youGotMention: "{name} ti ha menzionato" youGotMention: "{name} ti ha menzionato"

View file

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.79.1", "version": "12.79.2",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -6,7 +6,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
type Captcha = { type Captcha = {
render(container: string | Node, options: { render(container: string | Node, options: {
@ -32,7 +32,7 @@ declare global {
export default defineComponent({ export default defineComponent({
props: { props: {
provider: { provider: {
type: String, type: String as PropType<CaptchaProvider>,
required: true, required: true,
}, },
sitekey: { sitekey: {
@ -51,19 +51,25 @@ export default defineComponent({
}, },
computed: { computed: {
loaded() { variable(): string {
return !!window[this.provider as CaptchaProvider]; switch (this.provider) {
case 'hcaptcha': return 'hcaptcha';
case 'recaptcha': return 'grecaptcha';
}
}, },
src() { loaded(): boolean {
return !!window[this.variable];
},
src(): string {
const endpoint = ({ const endpoint = ({
hcaptcha: 'https://hcaptcha.com/1', hcaptcha: 'https://hcaptcha.com/1',
recaptcha: 'https://www.recaptcha.net/recaptcha', 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() { captcha(): Captcha {
return window[this.provider as CaptchaProvider] || {} as unknown as Captcha; return window[this.variable] || {} as unknown as Captcha;
}, },
}, },
@ -94,7 +100,7 @@ export default defineComponent({
methods: { methods: {
reset() { reset() {
this.captcha?.reset(); if (this.captcha?.reset) this.captcha.reset();
}, },
requestRender() { requestRender() {
if (this.captcha.render && this.$refs.captcha instanceof Element) { if (this.captcha.render && this.$refs.captcha instanceof Element) {

View file

@ -81,7 +81,7 @@ export default defineComponent({
getMenu() { getMenu() {
return [{ return [{
text: this.$ts.rename, text: this.$ts.rename,
icon: faICursor, icon: 'fas fa-i-cursor',
action: this.rename action: this.rename
}, { }, {
text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,

View file

@ -247,7 +247,7 @@ export default defineComponent({
} }
}, null, { }, null, {
text: this.$ts.rename, text: this.$ts.rename,
icon: faICursor, icon: 'fas fa-i-cursor',
action: this.rename action: this.rename
}, null, { }, null, {
text: this.$ts.delete, text: this.$ts.delete,

View file

@ -614,7 +614,7 @@ export default defineComponent({
type: 'label' type: 'label'
}, this.folder ? { }, this.folder ? {
text: this.$ts.renameFolder, text: this.$ts.renameFolder,
icon: faICursor, icon: 'fas fa-i-cursor',
action: () => { this.renameFolder(this.folder); } action: () => { this.renameFolder(this.folder); }
} : undefined, this.folder ? { } : undefined, this.folder ? {
text: this.$ts.deleteFolder, text: this.$ts.deleteFolder,

View file

@ -93,7 +93,7 @@ export default defineComponent({
if (this.menu) return; if (this.menu) return;
this.menu = os.modalMenu([{ this.menu = os.modalMenu([{
text: this.$ts.renameFile, text: this.$ts.renameFile,
icon: faICursor, icon: 'fas fa-i-cursor',
action: () => { this.rename(file) } action: () => { this.rename(file) }
}, { }, {
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,

View file

@ -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>

View file

@ -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>

View file

@ -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> <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>
<div class="other"> <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="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> <button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
</div> </div>
@ -84,6 +85,11 @@ export default defineComponent({
title: this.post.title, title: this.post.title,
text: this.post.description, text: this.post.description,
}, },
actions: [{
icon: 'fas fa-pencil-alt',
text: this.$ts.edit,
handler: this.edit
}]
} : null), } : null),
otherPostsPagination: { otherPostsPagination: {
endpoint: 'users/gallery/posts', endpoint: 'users/gallery/posts',
@ -154,6 +160,10 @@ export default defineComponent({
this.post.likedCount--; this.post.likedCount--;
}); });
}, },
edit() {
this.$router.push(`/gallery/${this.post.id}/edit`);
}
} }
}); });
</script> </script>

View file

@ -25,7 +25,6 @@ export default defineComponent({
endpoint: 'notes/mentions', endpoint: 'notes/mentions',
limit: 10, limit: 10,
}, },
faAt
}; };
}, },

View file

@ -161,15 +161,15 @@
</dl> </dl>
</div> </div>
<div class="status"> <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> <b>{{ number(user.notesCount) }}</b>
<span>{{ $ts.notes }}</span> <span>{{ $ts.notes }}</span>
</MkA> </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> <b>{{ number(user.followingCount) }}</b>
<span>{{ $ts.following }}</span> <span>{{ $ts.following }}</span>
</MkA> </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> <b>{{ number(user.followersCount) }}</b>
<span>{{ $ts.followers }}</span> <span>{{ $ts.followers }}</span>
</MkA> </MkA>

View file

@ -38,7 +38,8 @@ export const router = createRouter({
{ path: '/pages/new', component: page('page-editor/page-editor') }, { 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: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/gallery', component: page('gallery/index') }, { 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: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) },
{ path: '/channels', component: page('channels') }, { path: '/channels', component: page('channels') },
{ path: '/channels/new', component: page('channel-editor') }, { path: '/channels/new', component: page('channel-editor') },

View file

@ -18,9 +18,11 @@ export const builtinThemes = [
require('@client/themes/l-light.json5'), require('@client/themes/l-light.json5'),
require('@client/themes/l-apricot.json5'), require('@client/themes/l-apricot.json5'),
require('@client/themes/l-rainy.json5'), require('@client/themes/l-rainy.json5'),
require('@client/themes/l-vivid.json5'),
require('@client/themes/d-dark.json5'), require('@client/themes/d-dark.json5'),
require('@client/themes/d-persimmon.json5'), require('@client/themes/d-persimmon.json5'),
require('@client/themes/d-astro.json5'),
require('@client/themes/d-black.json5'), require('@client/themes/d-black.json5'),
] as Theme[]; ] as Theme[];

View file

@ -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',
},
}

View file

@ -10,7 +10,7 @@
props: { props: {
bg: '#f9f9f9', bg: '#f9f9f9',
fg: '#676767', fg: '#676767',
divider: 'rgb(223, 223, 223)', divider: '#e8e8e8',
header: ':alpha<0.7<@panel', header: ':alpha<0.7<@panel',
navBg: '#fff', navBg: '#fff',
panel: '#fff', panel: '#fff',

View file

@ -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',
},
}

View file

@ -36,7 +36,6 @@ export default defineComponent({
endpoint: 'notes/mentions', endpoint: 'notes/mentions',
limit: 10, limit: 10,
}, },
faAt
} }
}, },

View file

@ -110,7 +110,7 @@ y = Math.floor(pos / mapWidth)
``` ```
### フォームコントロールの種類 ### フォームコントロールの種類
#### スイッチ #### Interruttore
type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。 type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。
##### プロパティ ##### プロパティ

View file

@ -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);
});

View file

@ -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);
});

View file

@ -252,7 +252,7 @@ router.get('/users/:user', async ctx => {
}); });
// Note // Note
router.get('/notes/:note', async ctx => { router.get('/notes/:note', async (ctx, next) => {
const note = await Notes.findOne(ctx.params.note); const note = await Notes.findOne(ctx.params.note);
if (note) { if (note) {
@ -277,11 +277,11 @@ router.get('/notes/:note', async ctx => {
return; return;
} }
ctx.status = 404; await next();
}); });
// Page // Page
router.get('/@:user/pages/:page', async ctx => { router.get('/@:user/pages/:page', async (ctx, next) => {
const { username, host } = parseAcct(ctx.params.user); const { username, host } = parseAcct(ctx.params.user);
const user = await Users.findOne({ const user = await Users.findOne({
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
@ -314,12 +314,12 @@ router.get('/@:user/pages/:page', async ctx => {
return; return;
} }
ctx.status = 404; await next();
}); });
// Clip // Clip
// TODO: 非publicなclipのハンドリング // TODO: 非publicなclipのハンドリング
router.get('/clips/:clip', async ctx => { router.get('/clips/:clip', async (ctx, next) => {
const clip = await Clips.findOne({ const clip = await Clips.findOne({
id: ctx.params.clip, id: ctx.params.clip,
}); });
@ -339,11 +339,11 @@ router.get('/clips/:clip', async ctx => {
return; return;
} }
ctx.status = 404; await next();
}); });
// Gallery post // Gallery post
router.get('/gallery/:post', async ctx => { router.get('/gallery/:post', async (ctx, next) => {
const post = await GalleryPosts.findOne(ctx.params.post); const post = await GalleryPosts.findOne(ctx.params.post);
if (post) { if (post) {
@ -362,11 +362,11 @@ router.get('/gallery/:post', async ctx => {
return; return;
} }
ctx.status = 404; await next();
}); });
// Channel // Channel
router.get('/channels/:channel', async ctx => { router.get('/channels/:channel', async (ctx, next) => {
const channel = await Channels.findOne({ const channel = await Channels.findOne({
id: ctx.params.channel, id: ctx.params.channel,
}); });
@ -384,7 +384,7 @@ router.get('/channels/:channel', async ctx => {
return; return;
} }
ctx.status = 404; await next();
}); });
//#endregion //#endregion