mirror of
https://github.com/hotomoe/hotomoe
synced 2024-11-28 14:58:15 +09:00
Merge branch 'develop'
This commit is contained in:
commit
17fff8c665
@ -900,11 +900,13 @@ _theme:
|
||||
funcKind: "Type de fonction"
|
||||
argument: "Argument"
|
||||
alpha: "Transparence"
|
||||
darken: "Assombrir"
|
||||
darken: "Sombre"
|
||||
lighten: "Clair"
|
||||
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."
|
||||
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const} ?"
|
||||
keys:
|
||||
accent: "Accentuation"
|
||||
bg: "Arrière-plan"
|
||||
fg: "Texte"
|
||||
focus: "Mise au point"
|
||||
@ -940,6 +942,8 @@ _theme:
|
||||
driveFolderBg: "Arrière-plan du dossier de disque"
|
||||
badge: "Badge"
|
||||
messageBg: "Arrière plan de la discussion"
|
||||
accentDarken: "Plus sombre"
|
||||
accentLighten: "Plus clair"
|
||||
fgHighlighted: "Texte mis en évidence"
|
||||
_sfx:
|
||||
note: "Nouvelle note"
|
||||
@ -1155,12 +1159,12 @@ _instanceCharts:
|
||||
usersTotal: "Nombre d'utilisateur·rice·s au total cumulé"
|
||||
notes: "Variation du nombre des notes"
|
||||
notesTotal: "Nombre total cumulé des notes"
|
||||
ff: "Variation des abonné·e·s"
|
||||
ffTotal: "Nombre d'abonné·e·s au total cumulé"
|
||||
ff: "Variation des abonné·e·s et des abonnements"
|
||||
ffTotal: "Total cumulé du nombre d'abonné·e·s et du nombre d'abonnements"
|
||||
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"
|
||||
filesTotal: "Nombre de fichiers au total cumulé"
|
||||
filesTotal: "Total cumulé du nombre de fichiers"
|
||||
_timelines:
|
||||
home: "Principal"
|
||||
local: "Local"
|
||||
@ -1237,7 +1241,7 @@ _pages:
|
||||
deleted: "La page a été supprimée"
|
||||
pageSetting: "Paramètres de la Page"
|
||||
nameAlreadyExists: "L'URL de page spécifiée existe déjà"
|
||||
invalidNameTitle: "La URL de la page spécifiée n’est pas valide"
|
||||
invalidNameTitle: "L'URL de page spécifiée n’est pas valide"
|
||||
invalidNameText: "Assurez-vous qu’il n’est pas vide"
|
||||
editThisPage: "Éditer cette page"
|
||||
viewSource: "Afficher la source"
|
||||
@ -1259,14 +1263,14 @@ _pages:
|
||||
font: "Police de caractères"
|
||||
fontSerif: "Serif"
|
||||
fontSansSerif: "Sans Serif"
|
||||
eyeCatchingImageSet: "Définir une image attirante"
|
||||
eyeCatchingImageRemove: "Supprimer une image attirante"
|
||||
eyeCatchingImageSet: "Définir une image attractive"
|
||||
eyeCatchingImageRemove: "Supprimer l'image attractive"
|
||||
chooseBlock: "Ajouter un bloc"
|
||||
selectType: "Choisir un type"
|
||||
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"
|
||||
inputBlocks: "Entrée"
|
||||
inputBlocks: "Blocs d'entrée"
|
||||
specialBlocks: "Spécial"
|
||||
blocks:
|
||||
text: "Texte"
|
||||
|
@ -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."
|
||||
other: "Avanzate"
|
||||
fileIdOrUrl: "ID o URL del file"
|
||||
chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta"
|
||||
behavior: "Comportamento"
|
||||
abuseReports: "Segnala"
|
||||
reportAbuse: "Segnala"
|
||||
@ -652,8 +653,10 @@ youAreRunningUpToDateClient: "Stai usando la versione più recente del client."
|
||||
newVersionOfClientAvailable: "Una nuova versione del tuo client è disponibile."
|
||||
usageAmount: "In utilizzo"
|
||||
capacity: "Capacità"
|
||||
inUse: "In utilizzo"
|
||||
editCode: "Modifica codice"
|
||||
apply: "Applica"
|
||||
receiveAnnouncementFromInstance: "Ricevi i messaggi informativi dall'istanza"
|
||||
emailNotification: "Eventi per notifiche via mail"
|
||||
publish: "Pubblico"
|
||||
inChannelSearch: "Cerca in canale"
|
||||
@ -932,6 +935,7 @@ _widgets:
|
||||
photos: "Foto"
|
||||
digitalClock: "Orologio digitale"
|
||||
federation: "Federazione"
|
||||
postForm: "Finestra di pubblicazione"
|
||||
button: "Pulsante"
|
||||
onlineUsers: "Utenti online"
|
||||
jobQueue: "Coda di lavoro"
|
||||
@ -1005,6 +1009,9 @@ _instanceCharts:
|
||||
users: "Variazione del numero di utenti"
|
||||
usersTotal: "Totale cumulativo di utenti"
|
||||
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:
|
||||
home: "Home"
|
||||
local: "Locale"
|
||||
@ -1012,8 +1019,16 @@ _timelines:
|
||||
global: "Federata"
|
||||
_rooms:
|
||||
roomOf: "Camera di {user}"
|
||||
addFurniture: "Disponi mobilia"
|
||||
translate: "Sposta"
|
||||
rotate: "Ruota"
|
||||
exit: "Indietro"
|
||||
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?"
|
||||
chooseImage: "Seleziona immagine"
|
||||
roomType: "Tipo di stanza"
|
||||
_roomType:
|
||||
default: "Predefinito"
|
||||
washitsu: "Washitsu"
|
||||
@ -1050,6 +1065,7 @@ _rooms:
|
||||
cube: "Cubo"
|
||||
tv: "Televisore"
|
||||
pinguin: "Pinguino"
|
||||
rubik-cube: "Cubo di Rubik"
|
||||
bin: "Cestino"
|
||||
cup-noodle: "Noodle istantanei"
|
||||
_pages:
|
||||
@ -1060,6 +1076,8 @@ _pages:
|
||||
updated: "Pagina aggiornata con successo!"
|
||||
deleted: "Pagina eliminata"
|
||||
pageSetting: "Impostazioni pagina"
|
||||
nameAlreadyExists: "Esiste già una pagina con lo stesso URL."
|
||||
invalidNameTitle: "L'URL di pagina definito non è valido"
|
||||
editThisPage: "Modifica questa pagina"
|
||||
viewSource: "Visualizza sorgente"
|
||||
viewPage: "Visualizza pagina"
|
||||
@ -1072,11 +1090,21 @@ _pages:
|
||||
content: "Blocco di pagina"
|
||||
variables: "Variabili"
|
||||
title: "Titolo"
|
||||
url: "URL della pagina"
|
||||
summary: "Riassunto di pagina"
|
||||
hideTitleWhenPinned: "Nascondere il titolo pagina quando è fissata in cima al profilo."
|
||||
font: "Tipo di carattere"
|
||||
fontSerif: "Serif"
|
||||
fontSansSerif: "Sans serif"
|
||||
eyeCatchingImageSet: "Imposta un'immagine attrattiva"
|
||||
eyeCatchingImageRemove: "Elimina l'immagine attrattiva"
|
||||
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:
|
||||
text: "Testo"
|
||||
textarea: "Area di testo"
|
||||
@ -1086,16 +1114,20 @@ _pages:
|
||||
if: "Se"
|
||||
_if:
|
||||
variable: "Variabili"
|
||||
post: "Finestra di pubblicazione"
|
||||
_post:
|
||||
text: "Contenuto"
|
||||
textInput: "Immissione testo"
|
||||
_textInput:
|
||||
name: "Nome della variabile"
|
||||
text: "Titolo"
|
||||
default: "Valore predefinito"
|
||||
textareaInput: "Immissione testo a più righe"
|
||||
_textareaInput:
|
||||
name: "Nome della variabile"
|
||||
text: "Titolo"
|
||||
default: "Valore predefinito"
|
||||
numberInput: "Immissione numerica"
|
||||
_numberInput:
|
||||
name: "Nome della variabile"
|
||||
text: "Titolo"
|
||||
@ -1108,24 +1140,35 @@ _pages:
|
||||
id: "ID nota"
|
||||
idDescription: "Qui puoi anche incollare l'URL della nota che vuoi impostare."
|
||||
detailed: "Visualizzazione dettagliata"
|
||||
switch: "Interruttore"
|
||||
_switch:
|
||||
name: "Nome della variabile"
|
||||
text: "Titolo"
|
||||
default: "Valore predefinito"
|
||||
counter: "Contatore"
|
||||
_counter:
|
||||
name: "Nome della variabile"
|
||||
text: "Titolo"
|
||||
inc: "Valore da aggiungere"
|
||||
_button:
|
||||
text: "Titolo"
|
||||
colored: "Colorato"
|
||||
action: "Operazione da eseguire quando viene premuto il pulsante"
|
||||
_action:
|
||||
dialog: "Visualizzare una finestra di dialogo"
|
||||
_dialog:
|
||||
content: "Contenuto"
|
||||
resetRandom: "Ripristinare un numero aleatorio"
|
||||
pushEvent: "Inviare evento"
|
||||
_pushEvent:
|
||||
event: "Nome evento"
|
||||
message: "Messaggio da visualizzare quando abilitato"
|
||||
variable: "Variabile da inviare"
|
||||
no-variable: "Nessun contenuto"
|
||||
callAiScript: "Chiamare AiScript"
|
||||
_callAiScript:
|
||||
functionName: "Nome della funzione"
|
||||
radioButton: "Opzioni"
|
||||
_radioButton:
|
||||
name: "Nome della variabile"
|
||||
title: "Titolo"
|
||||
@ -1139,6 +1182,8 @@ _pages:
|
||||
list: "Liste"
|
||||
blocks:
|
||||
text: "Testo"
|
||||
multiLineText: "Testo (a più righe)"
|
||||
textList: "Lista di testo"
|
||||
_strLen:
|
||||
arg1: "Testo"
|
||||
_strPick:
|
||||
@ -1193,13 +1238,18 @@ _pages:
|
||||
arg2: "B"
|
||||
_if:
|
||||
arg1: "Se"
|
||||
arg2: "Se"
|
||||
random: "Aleatorietà"
|
||||
_randomPick:
|
||||
arg1: "Liste"
|
||||
_dailyRandomPick:
|
||||
arg1: "Liste"
|
||||
_seedRandom:
|
||||
arg2: "Probabilità"
|
||||
_seedRandomPick:
|
||||
arg2: "Liste"
|
||||
_DRPWPM:
|
||||
arg1: "Lista di testo"
|
||||
_pick:
|
||||
arg1: "Liste"
|
||||
_listLen:
|
||||
@ -1213,6 +1263,7 @@ _pages:
|
||||
types:
|
||||
string: "Testo"
|
||||
array: "Liste"
|
||||
stringArray: "Lista di testo"
|
||||
_notification:
|
||||
fileUploaded: "File caricato correttamente"
|
||||
youGotMention: "{name} ti ha menzionato"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.79.1",
|
||||
"version": "12.79.2",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -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';
|
||||
}
|
||||
},
|
||||
src() {
|
||||
loaded(): boolean {
|
||||
return !!window[this.variable];
|
||||
},
|
||||
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) {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
168
src/client/pages/gallery/edit.vue
Normal file
168
src/client/pages/gallery/edit.vue
Normal 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>
|
@ -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>
|
@ -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>
|
||||
|
@ -25,7 +25,6 @@ export default defineComponent({
|
||||
endpoint: 'notes/mentions',
|
||||
limit: 10,
|
||||
},
|
||||
faAt
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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') },
|
||||
|
@ -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[];
|
||||
|
||||
|
76
src/client/themes/d-astro.json5
Normal file
76
src/client/themes/d-astro.json5
Normal 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',
|
||||
},
|
||||
}
|
@ -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',
|
||||
|
82
src/client/themes/l-vivid.json5
Normal file
82
src/client/themes/l-vivid.json5
Normal 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',
|
||||
},
|
||||
}
|
@ -36,7 +36,6 @@ export default defineComponent({
|
||||
endpoint: 'notes/mentions',
|
||||
limit: 10,
|
||||
},
|
||||
faAt
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -110,7 +110,7 @@ y = Math.floor(pos / mapWidth)
|
||||
```
|
||||
|
||||
### フォームコントロールの種類
|
||||
#### スイッチ
|
||||
#### Interruttore
|
||||
type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。
|
||||
|
||||
##### プロパティ
|
||||
|
40
src/server/api/endpoints/gallery/posts/delete.ts
Normal file
40
src/server/api/endpoints/gallery/posts/delete.ts
Normal 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);
|
||||
});
|
81
src/server/api/endpoints/gallery/posts/update.ts
Normal file
81
src/server/api/endpoints/gallery/posts/update.ts
Normal 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);
|
||||
});
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user