Merge branch 'develop'
This commit is contained in:
		
						commit
						17fff8c665
					
				
					 23 changed files with 562 additions and 153 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue