From 9cab2cd9401a3a265a0fdf5513db73e5f3d85cec Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 24 Apr 2021 18:38:38 +0900 Subject: [PATCH 01/53] Tweak animation --- src/client/components/launch-pad.vue | 4 ++-- src/client/directives/click-anime.ts | 4 ++++ src/client/style.scss | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/client/components/launch-pad.vue b/src/client/components/launch-pad.vue index e3d24c70f2..e66bbd73e4 100644 --- a/src/client/components/launch-pad.vue +++ b/src/client/components/launch-pad.vue @@ -3,12 +3,12 @@
- +
{{ $ts.help }}
- +
{{ $t('aboutX', { x: instanceName }) }}
- +
{{ $ts.aboutMisskey }}
From 23821d6fadf7eefe2a8291277ffa8273d3a41dab Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Apr 2021 12:31:11 +0900 Subject: [PATCH 14/53] Improve client --- src/client/components/launch-pad.vue | 4 +- src/client/pages/about.vue | 96 +++++++++++++++++----------- src/client/pages/instance-info.vue | 3 +- src/client/ui/default.sidebar.vue | 25 +++++--- 4 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/client/components/launch-pad.vue b/src/client/components/launch-pad.vue index 936ca85730..58b74bdaee 100644 --- a/src/client/components/launch-pad.vue +++ b/src/client/components/launch-pad.vue @@ -25,7 +25,7 @@
{{ $t('aboutX', { x: instanceName }) }}
- +
{{ $ts.aboutMisskey }}
@@ -101,6 +101,7 @@ export default defineComponent({ flex-direction: column; align-items: center; justify-content: center; + vertical-align: bottom; width: 128px; height: 128px; border-radius: var(--radius); @@ -117,6 +118,7 @@ export default defineComponent({ > .icon { font-size: 26px; + height: 32px; } > .text { diff --git a/src/client/pages/about.vue b/src/client/pages/about.vue index 4f70998eee..bdd4c78827 100644 --- a/src/client/pages/about.vue +++ b/src/client/pages/about.vue @@ -1,39 +1,57 @@ @@ -45,9 +63,12 @@ import FormLink from '@client/components/form/link.vue'; import FormBase from '@client/components/form/base.vue'; import FormGroup from '@client/components/form/group.vue'; import FormKeyValueView from '@client/components/form/key-value-view.vue'; +import FormTextarea from '@client/components/form/textarea.vue'; +import FormSuspense from '@client/components/form/suspense.vue'; import * as os from '@client/os'; import number from '@client/filters/number'; import * as symbols from '@client/symbols'; +import { host } from '@client/config'; export default defineComponent({ components: { @@ -55,6 +76,8 @@ export default defineComponent({ FormGroup, FormLink, FormKeyValueView, + FormTextarea, + FormSuspense, }, data() { @@ -63,24 +86,17 @@ export default defineComponent({ title: this.$ts.instanceInfo, icon: 'fas fa-info-circle' }, + host, version, instanceName, stats: null, + initStats: () => os.api('stats', { + }).then((stats) => { + this.stats = stats; + }) } }, - computed: { - meta() { - return this.$instance; - }, - }, - - created() { - os.api('stats').then(stats => { - this.stats = stats; - }); - }, - methods: { number } @@ -88,18 +104,20 @@ export default defineComponent({ diff --git a/src/client/pages/instance-info.vue b/src/client/pages/instance-info.vue index 662b82ddb1..c66ad50f6d 100644 --- a/src/client/pages/instance-info.vue +++ b/src/client/pages/instance-info.vue @@ -147,7 +147,6 @@ import * as os from '@client/os'; import number from '@client/filters/number'; import bytes from '@client/filters/bytes'; import * as symbols from '@client/symbols'; -import { url } from '@client/config'; const chartLimit = 90; const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); @@ -449,7 +448,7 @@ export default defineComponent({ .fnfelxur { padding: 16px; - > img { + > .icon { display: block; margin: auto; height: 64px; diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index a55a1770ff..725fd844d9 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -31,8 +31,10 @@ {{ $ts.settings }}
-
- +
+ + +
@@ -260,14 +262,21 @@ export default defineComponent({ } } - > .misskey { + > .about { fill: currentColor; - } - - > .foo { - text-align: center; padding: 8px 0 16px 0; - opacity: 0.5; + text-align: center; + + > .link { + display: block; + width: 32px; + margin: 0 auto; + + img { + display: block; + width: 100%; + } + } } > .item { From f37c25d00ea237c82525a89bdb7a7dd80ea2a26e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Apr 2021 12:33:55 +0900 Subject: [PATCH 15/53] Update api-permissions.ts --- src/misc/api-permissions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/misc/api-permissions.ts b/src/misc/api-permissions.ts index de9fdea52c..eb20c3d289 100644 --- a/src/misc/api-permissions.ts +++ b/src/misc/api-permissions.ts @@ -27,4 +27,8 @@ export const kinds = [ 'write:user-groups', 'read:channels', 'write:channels', + 'read:gallery', + 'write:gallery', + 'read:gallery-likes', + 'write:gallery-likes', ]; From 3f6a55aabdc87b13ea6ea28beb1ff49bf1d5552f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 25 Apr 2021 15:14:26 +0900 Subject: [PATCH 16/53] Tweak UI --- src/client/pages/gallery/post.vue | 14 +-- src/client/pages/page.vue | 199 +++++++++++++++++++++++------- 2 files changed, 163 insertions(+), 50 deletions(-) diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue index 86fae99888..9bd102cee2 100644 --- a/src/client/pages/gallery/post.vue +++ b/src/client/pages/gallery/post.vue @@ -19,7 +19,7 @@ {{ post.likedCount }}
- +
@@ -125,6 +125,12 @@ export default defineComponent({ }); }, + shareWithNote() { + os.post({ + initialText: `${this.post.title} ${url}/gallery/${this.post.id}` + }); + }, + like() { os.apiWithDialog('gallery/posts/like', { postId: this.postId, @@ -148,12 +154,6 @@ export default defineComponent({ this.post.likedCount--; }); }, - - createNote() { - os.post({ - initialText: `${this.post.title} ${url}/gallery/${this.post.id}` - }); - } } }); diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue index e43add7b0b..6ee3ee8d26 100644 --- a/src/client/pages/page.vue +++ b/src/client/pages/page.vue @@ -1,35 +1,60 @@ @@ -39,11 +64,20 @@ import XPage from '@client/components/page/page.vue'; import MkButton from '@client/components/ui/button.vue'; import * as os from '@client/os'; import * as symbols from '@client/symbols'; +import { url } from '@client/config'; +import MkFollowButton from '@client/components/follow-button.vue'; +import MkContainer from '@client/components/ui/container.vue'; +import MkPagination from '@client/components/ui/pagination.vue'; +import MkPagePreview from '@client/components/page-preview.vue'; export default defineComponent({ components: { XPage, MkButton, + MkFollowButton, + MkContainer, + MkPagination, + MkPagePreview, }, props: { @@ -69,6 +103,14 @@ export default defineComponent({ }, } : null), page: null, + error: null, + otherPostsPagination: { + endpoint: 'users/pages', + limit: 6, + params: () => ({ + userId: this.page.user.id + }) + }, }; }, @@ -90,11 +132,28 @@ export default defineComponent({ methods: { fetch() { + this.page = null; os.api('pages/show', { name: this.pageName, username: this.username, }).then(page => { this.page = page; + }).catch(e => { + this.error = e; + }); + }, + + share() { + navigator.share({ + title: this.page.title || this.page.name, + text: this.page.summary, + url: `${url}/@${this.page.user.username}/pages/${this.page.name}` + }); + }, + + shareWithNote() { + os.post({ + initialText: `${this.page.title || this.page.name} ${url}/@${this.page.user.username}/pages/${this.page.name}` }); }, @@ -132,6 +191,15 @@ export default defineComponent({ diff --git a/src/client/pages/gallery/new.vue b/src/client/pages/gallery/new.vue deleted file mode 100644 index 3f9756df8e..0000000000 --- a/src/client/pages/gallery/new.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - - - diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue index 9bd102cee2..703506a78d 100644 --- a/src/client/pages/gallery/post.vue +++ b/src/client/pages/gallery/post.vue @@ -19,6 +19,7 @@ {{ post.likedCount }}
+
@@ -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`); + } } }); diff --git a/src/client/router.ts b/src/client/router.ts index 5371bf17d9..8dcc1d1eb4 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -38,7 +38,8 @@ export const router = createRouter({ { path: '/pages/new', component: page('page-editor/page-editor') }, { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, { path: '/gallery', component: page('gallery/index') }, - { path: '/gallery/new', component: page('gallery/new') }, + { path: '/gallery/new', component: page('gallery/edit') }, + { path: '/gallery/:postId/edit', component: page('gallery/edit'), props: route => ({ postId: route.params.postId }) }, { path: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) }, { path: '/channels', component: page('channels') }, { path: '/channels/new', component: page('channel-editor') }, diff --git a/src/server/api/endpoints/gallery/posts/delete.ts b/src/server/api/endpoints/gallery/posts/delete.ts new file mode 100644 index 0000000000..8b54828b20 --- /dev/null +++ b/src/server/api/endpoints/gallery/posts/delete.ts @@ -0,0 +1,40 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ApiError } from '../../../error'; +import { GalleryPosts } from '../../../../../models'; +import { ID } from '@/misc/cafy-id'; + +export const meta = { + tags: ['gallery'], + + requireCredential: true as const, + + kind: 'write:gallery', + + params: { + postId: { + validator: $.type(ID), + }, + }, + + errors: { + noSuchPost: { + message: 'No such post.', + code: 'NO_SUCH_POST', + id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5' + }, + } +}; + +export default define(meta, async (ps, user) => { + const post = await GalleryPosts.findOne({ + id: ps.postId, + userId: user.id, + }); + + if (post == null) { + throw new ApiError(meta.errors.noSuchPost); + } + + await GalleryPosts.delete(post.id); +}); diff --git a/src/server/api/endpoints/gallery/posts/update.ts b/src/server/api/endpoints/gallery/posts/update.ts new file mode 100644 index 0000000000..c8bb8d48c9 --- /dev/null +++ b/src/server/api/endpoints/gallery/posts/update.ts @@ -0,0 +1,81 @@ +import $ from 'cafy'; +import * as ms from 'ms'; +import define from '../../../define'; +import { ID } from '../../../../../misc/cafy-id'; +import { DriveFiles, GalleryPosts } from '../../../../../models'; +import { GalleryPost } from '../../../../../models/entities/gallery-post'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['gallery'], + + requireCredential: true as const, + + kind: 'write:gallery', + + limit: { + duration: ms('1hour'), + max: 300 + }, + + params: { + postId: { + validator: $.type(ID), + }, + + title: { + validator: $.str.min(1), + }, + + description: { + validator: $.optional.nullable.str, + }, + + fileIds: { + validator: $.arr($.type(ID)).unique().range(1, 32), + }, + + isSensitive: { + validator: $.optional.bool, + default: false, + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'GalleryPost', + }, + + errors: { + + } +}; + +export default define(meta, async (ps, user) => { + const files = (await Promise.all(ps.fileIds.map(fileId => + DriveFiles.findOne({ + id: fileId, + userId: user.id + }) + ))).filter(file => file != null); + + if (files.length === 0) { + throw new Error(); + } + + await GalleryPosts.update({ + id: ps.postId, + userId: user.id, + }, { + updatedAt: new Date(), + title: ps.title, + description: ps.description, + isSensitive: ps.isSensitive, + fileIds: files.map(file => file.id) + }); + + const post = await GalleryPosts.findOneOrFail(ps.postId); + + return await GalleryPosts.pack(post, user); +}); From d348e211d440aa42626b8593666315ea72ae62f5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 26 Apr 2021 11:12:34 +0900 Subject: [PATCH 23/53] New Crowdin updates (#7482) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations reversi-bot.md (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (French) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Italian) --- locales/fr-FR.yml | 24 ++++++++++------- locales/it-IT.yml | 51 +++++++++++++++++++++++++++++++++++ src/docs/it-IT/reversi-bot.md | 2 +- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index dcac32e3ef..5500c3a377 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -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" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index b96f3c99a5..cc1178426a 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -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" diff --git a/src/docs/it-IT/reversi-bot.md b/src/docs/it-IT/reversi-bot.md index 7ab2a7212e..00d4a18a8e 100644 --- a/src/docs/it-IT/reversi-bot.md +++ b/src/docs/it-IT/reversi-bot.md @@ -110,7 +110,7 @@ y = Math.floor(pos / mapWidth) ``` ### フォームコントロールの種類 -#### スイッチ +#### Interruttore type: `switch` スイッチを表示します。何かの機能をオン/オフさせたい場合に有用です。 ##### プロパティ From 4b205aee913fdfcb9a58f48c13149301f69ce568 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 26 Apr 2021 12:34:41 +0900 Subject: [PATCH 24/53] Fix #7480 --- src/server/web/index.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/server/web/index.ts b/src/server/web/index.ts index c3b184088b..cc343063af 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -252,7 +252,7 @@ router.get('/users/:user', async ctx => { }); // Note -router.get('/notes/:note', async ctx => { +router.get('/notes/:note', async (ctx, next) => { const note = await Notes.findOne(ctx.params.note); if (note) { @@ -277,11 +277,11 @@ router.get('/notes/:note', async ctx => { return; } - ctx.status = 404; + await next(); }); // Page -router.get('/@:user/pages/:page', async ctx => { +router.get('/@:user/pages/:page', async (ctx, next) => { const { username, host } = parseAcct(ctx.params.user); const user = await Users.findOne({ usernameLower: username.toLowerCase(), @@ -314,12 +314,12 @@ router.get('/@:user/pages/:page', async ctx => { return; } - ctx.status = 404; + await next(); }); // Clip // TODO: 非publicなclipのハンドリング -router.get('/clips/:clip', async ctx => { +router.get('/clips/:clip', async (ctx, next) => { const clip = await Clips.findOne({ id: ctx.params.clip, }); @@ -339,11 +339,11 @@ router.get('/clips/:clip', async ctx => { return; } - ctx.status = 404; + await next(); }); // Gallery post -router.get('/gallery/:post', async ctx => { +router.get('/gallery/:post', async (ctx, next) => { const post = await GalleryPosts.findOne(ctx.params.post); if (post) { @@ -362,11 +362,11 @@ router.get('/gallery/:post', async ctx => { return; } - ctx.status = 404; + await next(); }); // Channel -router.get('/channels/:channel', async ctx => { +router.get('/channels/:channel', async (ctx, next) => { const channel = await Channels.findOne({ id: ctx.params.channel, }); @@ -384,7 +384,7 @@ router.get('/channels/:channel', async ctx => { return; } - ctx.status = 404; + await next(); }); //#endregion From 77ccf3b929e46f6df1222f70fff936cab449fe21 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 26 Apr 2021 12:47:54 +0900 Subject: [PATCH 25/53] Fix #7483 --- src/client/components/captcha.vue | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/client/components/captcha.vue b/src/client/components/captcha.vue index 26215df09d..5da8ede3b9 100644 --- a/src/client/components/captcha.vue +++ b/src/client/components/captcha.vue @@ -6,7 +6,7 @@ diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index 2c883e0c32..f8249ffcd6 100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue @@ -11,6 +11,7 @@ {{ $ts.password }} + {{ signing ? $ts.loggingIn : $ts.login }} @@ -49,8 +50,8 @@ + + diff --git a/src/client/router.ts b/src/client/router.ts index 8dcc1d1eb4..4c3aa765e6 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -23,6 +23,7 @@ export const router = createRouter({ { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, { path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, + { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, { path: '/announcements', component: page('announcements') }, { path: '/about', component: page('about') }, { path: '/about-misskey', component: page('about-misskey') }, diff --git a/src/client/style.scss b/src/client/style.scss index aa00303a15..523ab13034 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -337,7 +337,7 @@ hr { } ._monolithic_ { - ._section { + ._section:not(:empty) { box-sizing: border-box; padding: var(--root-margin, 32px); diff --git a/src/db/postgre.ts b/src/db/postgre.ts index c8b0121719..e2a779a52d 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -70,6 +70,7 @@ import { Channel } from '../models/entities/channel'; import { ChannelFollowing } from '../models/entities/channel-following'; import { ChannelNotePining } from '../models/entities/channel-note-pining'; import { RegistryItem } from '../models/entities/registry-item'; +import { PasswordResetRequest } from '@/models/entities/password-reset-request'; const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); @@ -169,6 +170,7 @@ export const entities = [ ChannelFollowing, ChannelNotePining, RegistryItem, + PasswordResetRequest, ...charts as any ]; diff --git a/src/models/entities/password-reset-request.ts b/src/models/entities/password-reset-request.ts new file mode 100644 index 0000000000..6d41d38a93 --- /dev/null +++ b/src/models/entities/password-reset-request.ts @@ -0,0 +1,30 @@ +import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; +import { id } from '../id'; +import { User } from './user'; + +@Entity() +export class PasswordResetRequest { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, + }) + public token: string; + + @Index() + @Column({ + ...id(), + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; +} diff --git a/src/models/index.ts b/src/models/index.ts index 9d08e49858..6ce453ef33 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -60,6 +60,7 @@ import { MutedNote } from './entities/muted-note'; import { ChannelFollowing } from './entities/channel-following'; import { ChannelNotePining } from './entities/channel-note-pining'; import { RegistryItem } from './entities/registry-item'; +import { PasswordResetRequest } from './entities/password-reset-request'; export const Announcements = getRepository(Announcement); export const AnnouncementReads = getRepository(AnnouncementRead); @@ -122,3 +123,4 @@ export const Channels = getCustomRepository(ChannelRepository); export const ChannelFollowings = getRepository(ChannelFollowing); export const ChannelNotePinings = getRepository(ChannelNotePining); export const RegistryItems = getRepository(RegistryItem); +export const PasswordResetRequests = getRepository(PasswordResetRequest); diff --git a/src/server/api/endpoints/request-reset-password.ts b/src/server/api/endpoints/request-reset-password.ts new file mode 100644 index 0000000000..c880df7527 --- /dev/null +++ b/src/server/api/endpoints/request-reset-password.ts @@ -0,0 +1,73 @@ +import $ from 'cafy'; +import { publishMainStream } from '../../../services/stream'; +import define from '../define'; +import rndstr from 'rndstr'; +import config from '@/config'; +import * as ms from 'ms'; +import { Users, UserProfiles, PasswordResetRequests } from '../../../models'; +import { sendEmail } from '../../../services/send-email'; +import { ApiError } from '../error'; +import { genId } from '@/misc/gen-id'; +import { IsNull } from 'typeorm'; + +export const meta = { + requireCredential: false as const, + + limit: { + duration: ms('1hour'), + max: 3 + }, + + params: { + username: { + validator: $.str + }, + + email: { + validator: $.str + }, + }, + + errors: { + + } +}; + +export default define(meta, async (ps) => { + const user = await Users.findOne({ + usernameLower: ps.username.toLowerCase(), + host: IsNull() + }); + + // 合致するユーザーが登録されていなかったら無視 + if (user == null) { + return; + } + + const profile = await UserProfiles.findOneOrFail(user.id); + + // 合致するメアドが登録されていなかったら無視 + if (profile.email !== ps.email) { + return; + } + + // メアドが認証されていなかったら無視 + if (!profile.emailVerified) { + return; + } + + const token = rndstr('a-z0-9', 64); + + await PasswordResetRequests.insert({ + id: genId(), + createdAt: new Date(), + userId: profile.userId, + token + }); + + const link = `${config.url}/reset-password/${token}`; + + sendEmail(ps.email, 'Password reset requested', + `To reset password, please click this link:
${link}`, + `To reset password, please click this link: ${link}`); +}); diff --git a/src/server/api/endpoints/reset-password.ts b/src/server/api/endpoints/reset-password.ts new file mode 100644 index 0000000000..5f79bdbd00 --- /dev/null +++ b/src/server/api/endpoints/reset-password.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import { publishMainStream } from '../../../services/stream'; +import define from '../define'; +import { Users, UserProfiles, PasswordResetRequests } from '../../../models'; +import { ApiError } from '../error'; + +export const meta = { + requireCredential: false as const, + + params: { + token: { + validator: $.str + }, + + password: { + validator: $.str + } + }, + + errors: { + + } +}; + +export default define(meta, async (ps, user) => { + const req = await PasswordResetRequests.findOneOrFail({ + token: ps.token, + }); + + // 発行してから30分以上経過していたら無効 + if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { + throw new Error(); // TODO + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.password, salt); + + await UserProfiles.update(req.userId, { + password: hash + }); + + PasswordResetRequests.delete(req.id); +}); diff --git a/src/server/well-known.ts b/src/server/well-known.ts index b1b6b2a771..57b6aba9a0 100644 --- a/src/server/well-known.ts +++ b/src/server/well-known.ts @@ -61,6 +61,11 @@ router.get('/.well-known/nodeinfo', async ctx => { ctx.body = { links }; }); +/* TODO +router.get('/.well-known/change-password', async ctx => { +}); +*/ + router.get(webFingerPath, async ctx => { const fromId = (id: User['id']): Record => ({ id, From e9170e630c35a7669e99f7e6fb73243f171ac7b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 4 May 2021 17:02:14 +0900 Subject: [PATCH 36/53] =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=83=94=E3=83=83=E3=82=AB=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=8C=E3=83=AA=E3=82=A2=E3=83=AB=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=A0=E3=81=A7=E5=8F=8D=E6=98=A0=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/components/emoji-picker.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue index 9bec319af2..06653324d7 100644 --- a/src/client/components/emoji-picker.vue +++ b/src/client/components/emoji-picker.vue @@ -35,6 +35,7 @@ class="_button" @click="chosen(emoji, $event)" tabindex="0" + :key="emoji" > @@ -104,7 +105,7 @@ export default defineComponent({ return { emojilist: markRaw(emojilist), getStaticImageUrl, - pinned: this.$store.state.reactions, + pinned: this.$store.reactiveState.reactions, width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3, height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2, big: this.asReactionPicker ? isDeviceTouch : false, From 71ebb068f7eb51f473f35d8e6ae7cfbad1e74b62 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 4 May 2021 17:09:57 +0900 Subject: [PATCH 37/53] =?UTF-8?q?=E3=83=A1=E3=83=BC=E3=83=AB=E3=82=A2?= =?UTF-8?q?=E3=83=89=E3=83=AC=E3=82=B9=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E4=BF=83=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 1 + src/client/pages/settings/index.vue | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2b973ae55f..5a3f40a6dc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -748,6 +748,7 @@ gallery: "ギャラリー" recentPosts: "最近の投稿" popularPosts: "人気の投稿" shareWithNote: "ノートで共有" +emailNotConfiguredWarning: "メールアドレスの設定がされていません。" _forgotPassword: enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。" diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue index 049e912898..3fd10fc44f 100644 --- a/src/client/pages/settings/index.vue +++ b/src/client/pages/settings/index.vue @@ -10,6 +10,7 @@ {{ $ts.accounts }} + {{ $ts.emailNotConfiguredWarning }} {{ $ts.configure }} {{ $ts.profile }} @@ -58,10 +59,13 @@ import FormLink from '@client/components/form/link.vue'; import FormGroup from '@client/components/form/group.vue'; import FormBase from '@client/components/form/base.vue'; import FormButton from '@client/components/form/button.vue'; +import FormInfo from '@client/components/form/info.vue'; import { scroll } from '@client/scripts/scroll'; import { signout } from '@client/account'; import { unisonReload } from '@client/scripts/unison-reload'; import * as symbols from '@client/symbols'; +import { instance } from '@client/instance'; +import { $i } from '@client/account'; export default defineComponent({ components: { @@ -69,6 +73,7 @@ export default defineComponent({ FormLink, FormGroup, FormButton, + FormInfo, }, props: { @@ -173,6 +178,8 @@ export default defineComponent({ } }); + const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); + return { [symbols.PAGE_INFO]: INFO, page, @@ -182,6 +189,7 @@ export default defineComponent({ onInfo, pageProps, component, + emailNotConfigured, logout: () => { signout(); }, From 18e1efc7ecd3f5a6d774c16f17526d12ae46b2f5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 4 May 2021 21:15:57 +0900 Subject: [PATCH 38/53] Ad (#7495) * wip * Update ad.vue * Update default.widgets.vue * wip * Create 1620019354680-ad.ts * wip * Update ads.vue * wip * Update ad.vue --- locales/ja-JP.yml | 7 + migration/1620019354680-ad.ts | 18 +++ package.json | 1 - src/client/components/date-separated-list.vue | 22 ++- src/client/components/global/ad.vue | 142 ++++++++++++++++++ src/client/components/index.ts | 4 +- src/client/components/notes.vue | 2 +- src/client/pages/gallery/post.vue | 1 + src/client/pages/instance/ads.vue | 125 +++++++++++++++ src/client/pages/instance/index.vue | 2 + src/client/pages/page.vue | 1 + src/client/scripts/paging.ts | 8 +- src/client/style.scss | 2 + src/client/ui/chat/date-separated-list.vue | 6 +- src/client/ui/default.widgets.vue | 1 + src/db/postgre.ts | 2 + src/models/entities/ad.ts | 53 +++++++ src/models/index.ts | 2 + src/models/repositories/note.ts | 11 +- src/server/api/endpoints/admin/ad/create.ts | 45 ++++++ src/server/api/endpoints/admin/ad/delete.ts | 34 +++++ src/server/api/endpoints/admin/ad/list.ts | 36 +++++ src/server/api/endpoints/admin/ad/update.ts | 59 ++++++++ src/server/api/endpoints/meta.ts | 39 ++++- 24 files changed, 596 insertions(+), 27 deletions(-) create mode 100644 migration/1620019354680-ad.ts create mode 100644 src/client/components/global/ad.vue create mode 100644 src/client/pages/instance/ads.vue create mode 100644 src/models/entities/ad.ts create mode 100644 src/server/api/endpoints/admin/ad/create.ts create mode 100644 src/server/api/endpoints/admin/ad/delete.ts create mode 100644 src/server/api/endpoints/admin/ad/list.ts create mode 100644 src/server/api/endpoints/admin/ad/update.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5a3f40a6dc..0f786a6b14 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -748,6 +748,13 @@ gallery: "ギャラリー" recentPosts: "最近の投稿" popularPosts: "人気の投稿" shareWithNote: "ノートで共有" +ads: "広告" +expiration: "期限" +memo: "メモ" +priority: "優先度" +high: "高" +middle: "中" +low: "低" emailNotConfiguredWarning: "メールアドレスの設定がされていません。" _forgotPassword: diff --git a/migration/1620019354680-ad.ts b/migration/1620019354680-ad.ts new file mode 100644 index 0000000000..27fb99f181 --- /dev/null +++ b/migration/1620019354680-ad.ts @@ -0,0 +1,18 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class ad1620019354680 implements MigrationInterface { + name = 'ad1620019354680' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "ad" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "place" character varying(32) NOT NULL, "priority" character varying(32) NOT NULL, "url" character varying(1024) NOT NULL, "imageUrl" character varying(1024) NOT NULL, "memo" character varying(8192) NOT NULL, CONSTRAINT "PK_0193d5ef09746e88e9ea92c634d" PRIMARY KEY ("id")); COMMENT ON COLUMN "ad"."createdAt" IS 'The created date of the Ad.'; COMMENT ON COLUMN "ad"."expiresAt" IS 'The expired date of the Ad.'`); + await queryRunner.query(`CREATE INDEX "IDX_1129c2ef687fc272df040bafaa" ON "ad" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_2da24ce20ad209f1d9dc032457" ON "ad" ("expiresAt") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_2da24ce20ad209f1d9dc032457"`); + await queryRunner.query(`DROP INDEX "IDX_1129c2ef687fc272df040bafaa"`); + await queryRunner.query(`DROP TABLE "ad"`); + } + +} diff --git a/package.json b/package.json index 25ebacaa7c..9a14373667 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "resolutions": { "chokidar": "^3.3.1", "constantinople": "^4.0.1", - "gulp/gulp-cli/yargs/yargs-parser": "5.0.0-security.0", "jsonld/rdf-canonize/node-forge": "0.10.0", "lodash": "^4.17.20" }, diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue index 2a861adb09..d458a0eeb8 100644 --- a/src/client/components/date-separated-list.vue +++ b/src/client/components/date-separated-list.vue @@ -1,5 +1,6 @@ + + diff --git a/src/client/components/index.ts b/src/client/components/index.ts index 0630ed3d8c..8b914c5eec 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -12,8 +12,10 @@ import url from './global/url.vue'; import i18n from './global/i18n'; import loading from './global/loading.vue'; import error from './global/error.vue'; +import ad from './global/ad.vue'; export default function(app: App) { + app.component('I18n', i18n); app.component('Mfm', mfm); app.component('MkA', a); app.component('MkAcct', acct); @@ -25,5 +27,5 @@ export default function(app: App) { app.component('MkUrl', url); app.component('MkLoading', loading); app.component('MkError', error); - app.component('I18n', i18n); + app.component('MkAd', ad); } diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue index 675748d540..e90102921a 100644 --- a/src/client/components/notes.vue +++ b/src/client/components/notes.vue @@ -17,7 +17,7 @@ - + diff --git a/src/client/pages/gallery/post.vue b/src/client/pages/gallery/post.vue index 703506a78d..50f81376ec 100644 --- a/src/client/pages/gallery/post.vue +++ b/src/client/pages/gallery/post.vue @@ -33,6 +33,7 @@ + diff --git a/src/client/pages/instance/ads.vue b/src/client/pages/instance/ads.vue new file mode 100644 index 0000000000..4297e56c37 --- /dev/null +++ b/src/client/pages/instance/ads.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue index 5972a02de0..974c4345bb 100644 --- a/src/client/pages/instance/index.vue +++ b/src/client/pages/instance/index.vue @@ -23,6 +23,7 @@ {{ $ts.jobQueue }} {{ $ts.files }} {{ $ts.announcements }} + {{ $ts.ads }} {{ $ts.abuseReports }} @@ -102,6 +103,7 @@ export default defineComponent({ case 'queue': return defineAsyncComponent(() => import('./queue.vue')); case 'files': return defineAsyncComponent(() => import('./files.vue')); case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); + case 'ads': return defineAsyncComponent(() => import('./ads.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue')); diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue index 6ee3ee8d26..4e237c2186 100644 --- a/src/client/pages/page.vue +++ b/src/client/pages/page.vue @@ -45,6 +45,7 @@
{{ $ts.createdAt }}:
{{ $ts.updatedAt }}:
+ diff --git a/src/client/scripts/paging.ts b/src/client/scripts/paging.ts index 2e49f1a64c..bcb0d7f2b0 100644 --- a/src/client/scripts/paging.ts +++ b/src/client/scripts/paging.ts @@ -91,8 +91,10 @@ export default (opts) => ({ ...params, limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, }).then(items => { - for (const item of items) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; markRaw(item); + if (i === 3) item._shouldInsertAd_ = true; } if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { items.pop(); @@ -128,8 +130,10 @@ export default (opts) => ({ untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, }), }).then(items => { - for (const item of items) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; markRaw(item); + if (i === 10) item._shouldInsertAd_ = true; } if (items.length > SECOND_FETCH_LIMIT) { items.pop(); diff --git a/src/client/style.scss b/src/client/style.scss index 523ab13034..39bf6ef2d5 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -11,6 +11,8 @@ @media (max-width: 500px) { --margin: var(--marginHalf); } + + //--ad: rgb(255 169 0 / 10%); } ::selection { diff --git a/src/client/ui/chat/date-separated-list.vue b/src/client/ui/chat/date-separated-list.vue index b073a38eb1..bc7fc91d38 100644 --- a/src/client/ui/chat/date-separated-list.vue +++ b/src/client/ui/chat/date-separated-list.vue @@ -42,11 +42,7 @@ export default defineComponent({ if ( i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() && - !item._prId_ && - !this.items[i + 1]._prId_ && - !item._featuredId_ && - !this.items[i + 1]._featuredId_ + new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() ) { const separator = h('div', { class: 'separator', diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue index cabd83937e..0dd073409b 100644 --- a/src/client/ui/default.widgets.vue +++ b/src/client/ui/default.widgets.vue @@ -1,6 +1,7 @@