fix(front): make (`lint:tsc`) completely happy
This commit is contained in:
		
							parent
							
								
									d8a7c033eb
								
							
						
					
					
						commit
						1972f6c8a0
					
				|  | @ -18,7 +18,7 @@ | ||||||
|     "test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node", |     "test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node", | ||||||
|     "lint": "yarn lint:es && yarn lint:tsc", |     "lint": "yarn lint:es && yarn lint:tsc", | ||||||
|     "lint:es": "eslint --max-warnings 0 --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html,.cjs . cypress public/embed.html src test ui-docs", |     "lint:es": "eslint --max-warnings 0 --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html,.cjs . cypress public/embed.html src test ui-docs", | ||||||
|     "lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental -p cypress src test ui-docs", |     "lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental --project tsconfig.json", | ||||||
|     "generate-types-from-local-schema": "yarn run openapi-typescript ../api/funkwhale_api/common/schema.yml -o src/generated/types.ts", |     "generate-types-from-local-schema": "yarn run openapi-typescript ../api/funkwhale_api/common/schema.yml -o src/generated/types.ts", | ||||||
|     "generate-types-from-remote-schema": "yarn run openapi-typescript https://docs.funkwhale.audio/develop/swagger/schema.yml -o src/generated/types.ts", |     "generate-types-from-remote-schema": "yarn run openapi-typescript https://docs.funkwhale.audio/develop/swagger/schema.yml -o src/generated/types.ts", | ||||||
|     "fmt:es": "yarn lint:es --fix", |     "fmt:es": "yarn lint:es --fix", | ||||||
|  |  | ||||||
|  | @ -108,7 +108,7 @@ watch( | ||||||
|       /> |       /> | ||||||
|     </slot> |     </slot> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="albums && count > props.limit" |       v-if="page && albums && count > props.limit" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((count || 0) / props.limit)" |       :pages="Math.ceil((count || 0) / props.limit)" | ||||||
|       style="grid-column: 1 / -1;" |       style="grid-column: 1 / -1;" | ||||||
|  |  | ||||||
|  | @ -108,7 +108,7 @@ watch( | ||||||
|       :artist="artist" |       :artist="artist" | ||||||
|     /> |     /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="artists && count > limit" |       v-if="page && artists && count > limit" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       style="grid-column: 1 / -1;" |       style="grid-column: 1 / -1;" | ||||||
|       :pages="Math.ceil((count || 0) / limit)" |       :pages="Math.ceil((count || 0) / limit)" | ||||||
|  |  | ||||||
|  | @ -90,7 +90,7 @@ watch([() => props.filters, page], | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && count > limit && limit > 16" |       v-if="page && result && count > limit && limit > 16" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((count || 0) / limit)" |       :pages="Math.ceil((count || 0) / limit)" | ||||||
|       style="grid-column: 1 / -1;" |       style="grid-column: 1 / -1;" | ||||||
|  | @ -101,7 +101,7 @@ watch([() => props.filters, page], | ||||||
|       :object="channel" |       :object="channel" | ||||||
|     /> |     /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && count > limit" |       v-if="page && result && count > limit" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((count || 0) / limit)" |       :pages="Math.ceil((count || 0) / limit)" | ||||||
|       style="grid-column: 1 / -1;" |       style="grid-column: 1 / -1;" | ||||||
|  |  | ||||||
|  | @ -216,7 +216,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => { | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="count > props.limit" |       v-if="page && count > props.limit" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((count || 0) / props.limit)" |       :pages="Math.ceil((count || 0) / props.limit)" | ||||||
|       style="grid-column: 1 / -1;" |       style="grid-column: 1 / -1;" | ||||||
|  |  | ||||||
|  | @ -215,7 +215,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value] | ||||||
|         </Layout> |         </Layout> | ||||||
|       </Layout> |       </Layout> | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="results && count > paginateBy" |         v-if="page && results && count > paginateBy" | ||||||
|         v-model:page="page" |         v-model:page="page" | ||||||
|         :pages="Math.ceil((count || 0) / paginateBy)" |         :pages="Math.ceil((count || 0) / paginateBy)" | ||||||
|         style="grid-column: 1 / -1;" |         style="grid-column: 1 / -1;" | ||||||
|  |  | ||||||
|  | @ -229,7 +229,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|     </Layout> |     </Layout> | ||||||
|     <Loader v-if="isLoading" /> |     <Loader v-if="isLoading" /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((result.count || 0)/paginateBy)" |       :pages="Math.ceil((result.count || 0)/paginateBy)" | ||||||
|     /> |     /> | ||||||
|  | @ -271,7 +271,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|     </Layout> |     </Layout> | ||||||
|     <Spacer grow /> |     <Spacer grow /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil((result.count || 0)/paginateBy)" |       :pages="Math.ceil((result.count || 0)/paginateBy)" | ||||||
|     /> |     /> | ||||||
|  |  | ||||||
|  | @ -237,7 +237,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|     </Layout> |     </Layout> | ||||||
|     <Loader v-if="isLoading" /> |     <Loader v-if="isLoading" /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil(result.count / paginateBy)" |       :pages="Math.ceil(result.count / paginateBy)" | ||||||
|     /> |     /> | ||||||
|  | @ -279,7 +279,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|     </Layout> |     </Layout> | ||||||
|     <Spacer grow /> |     <Spacer grow /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil(result.count / paginateBy)" |       :pages="Math.ceil(result.count / paginateBy)" | ||||||
|     /> |     /> | ||||||
|  |  | ||||||
|  | @ -309,7 +309,7 @@ const { to: upload } = useModal('upload') | ||||||
|     </Layout> |     </Layout> | ||||||
|     <Spacer grow /> |     <Spacer grow /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       :page="page" |       :page="page" | ||||||
|       :pages="Math.ceil((result?.results.length || 0)/paginateBy)" |       :pages="Math.ceil((result?.results.length || 0)/paginateBy)" | ||||||
|     /> |     /> | ||||||
|  |  | ||||||
|  | @ -277,7 +277,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value] | ||||||
|       flex |       flex | ||||||
|     > |     > | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="result && result.count > paginateBy" |         v-if="page && result && result.count > paginateBy" | ||||||
|         v-model:page="page" |         v-model:page="page" | ||||||
|         :pages="Math.ceil(result.count / paginateBy)" |         :pages="Math.ceil(result.count / paginateBy)" | ||||||
|       /> |       /> | ||||||
|  | @ -288,7 +288,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value] | ||||||
|         :custom-radio="radio" |         :custom-radio="radio" | ||||||
|       /> |       /> | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="result && result.count > paginateBy" |         v-if="page && result && result.count > paginateBy" | ||||||
|         v-model:page="page" |         v-model:page="page" | ||||||
|         :pages="Math.ceil(result.count / paginateBy)" |         :pages="Math.ceil(result.count / paginateBy)" | ||||||
|       /> |       /> | ||||||
|  |  | ||||||
|  | @ -250,12 +250,12 @@ const labels = computed(() => ({ | ||||||
|   </action-table> |   </action-table> | ||||||
|   <div> |   <div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil(result.count / paginateBy)" |       :pages="Math.ceil(result.count / paginateBy)" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <span v-if="result && result.results.length > 0"> |     <span v-if="page && result && result.results.length > 0"> | ||||||
|       {{ t('components.manage.ChannelsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |       {{ t('components.manage.ChannelsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -253,12 +253,12 @@ const labels = computed(() => ({ | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && page !== undefined && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.AlbumsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }} |     {{ t('components.manage.library.AlbumsTable.pagination.results', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -249,12 +249,12 @@ const getUrl = (artist: { channel?: number; id: number }) => { | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.ArtistsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.ArtistsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -266,12 +266,12 @@ const getCurrentState = (target?: StateTarget): ReviewState => { | ||||||
|     @refresh="fetchData()" |     @refresh="fetchData()" | ||||||
|   /> |   /> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.EditsCardList.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.EditsCardList.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -273,12 +273,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => { | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.LibrariesTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.LibrariesTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -215,12 +215,12 @@ const showUploadDetailModal = ref(false) | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.TagsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.TagsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -254,12 +254,12 @@ const labels = computed(() => ({ | ||||||
|   </action-table> |   </action-table> | ||||||
|   <div> |   <div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil(result.count / paginateBy)" |       :pages="Math.ceil(result.count / paginateBy)" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <span v-if="result && result.results.length > 0"> |     <span v-if="page && result && result.results.length > 0"> | ||||||
|       {{ t('components.manage.library.TracksTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |       {{ t('components.manage.library.TracksTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -365,12 +365,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => { | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -231,13 +231,13 @@ const labels = computed(() => ({ | ||||||
|   </div> |   </div> | ||||||
|   <div> |   <div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :paginate-by="paginateBy" |       :paginate-by="paginateBy" | ||||||
|       :pages="result.count" |       :pages="result.count" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <span v-if="result && result.results.length > 0"> |     <span v-if="page && result && result.results.length > 0"> | ||||||
|       {{ t('components.manage.moderation.AccountsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |       {{ t('components.manage.moderation.AccountsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -262,15 +262,15 @@ const labels = computed(() => ({ | ||||||
|   </div> |   </div> | ||||||
|   <div> |   <div> | ||||||
|     <pagination |     <pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       v-model:pages="result.count" |       :pages="result.count" | ||||||
|       :compact="true" |       :compact="true" | ||||||
|       :paginate-by="paginateBy" |       :paginate-by="paginateBy" | ||||||
|       :total="result.count" |       :total="result.count" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <span v-if="result && result.results.length > 0"> |     <span v-if="page && result && result.results.length > 0"> | ||||||
|       {{ t('components.manage.moderation.DomainsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |       {{ t('components.manage.moderation.DomainsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -230,13 +230,13 @@ const labels = computed(() => ({ | ||||||
|   </div> |   </div> | ||||||
|   <div> |   <div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       v-model:pages="result.count" |       v-model:pages="result.count" | ||||||
|       :paginate-by="paginateBy" |       :paginate-by="paginateBy" | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <span v-if="result && result.results.length > 0"> |     <span v-if="page && result && result.results.length > 0"> | ||||||
|       {{ t('components.manage.users.InvitationsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }, result.results.length) }} |       {{ t('components.manage.users.InvitationsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }, result.results.length) }} | ||||||
|     </span> |     </span> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Notification, LibraryFollow } from '~/types' | import type { Notification, LibraryFollow } from '~/types' | ||||||
| import type { components } from '~/types/generated' | import type { components } from '~/generated/types' | ||||||
|  | import type { RouteLocationRaw } from 'vue-router' | ||||||
| 
 | 
 | ||||||
| import { computed, ref, watchEffect, watch } from 'vue' | import { computed, ref, watchEffect, watch } from 'vue' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
|  | @ -72,7 +73,10 @@ const notificationData = computed(() => { | ||||||
|       if (activity.related_object?.approved === null) { |       if (activity.related_object?.approved === null) { | ||||||
|         return { |         return { | ||||||
|           detailUrl, |           detailUrl, | ||||||
|           message: t('components.notifications.NotificationRow.message.userPendingFollow', { username: username.value, user: activity.object.target?.full_username }), |           message: t('components.notifications.NotificationRow.message.userPendingFollow', { username: username.value, | ||||||
|  |             // TODO: This is just wrong. Start with fixing the types upstream. | ||||||
|  |             // @ts-expect-error `activity.object needs to have a type. Where is it declared? | ||||||
|  |             user: activity.object.target?.full_username }), | ||||||
|           acceptFollow: { |           acceptFollow: { | ||||||
|             buttonClass: 'success', |             buttonClass: 'success', | ||||||
|             icon: 'check', |             icon: 'check', | ||||||
|  | @ -134,24 +138,37 @@ const handleAction = (handler?: () => void) => { | ||||||
| 
 | 
 | ||||||
| const approveLibraryFollow = async (follow: LibraryFollow) => { | const approveLibraryFollow = async (follow: LibraryFollow) => { | ||||||
|   await axios.post(`federation/follows/library/${follow.uuid}/accept/`) |   await axios.post(`federation/follows/library/${follow.uuid}/accept/`) | ||||||
|  |   // TODO: This is not how Axios works. You have to send a request with | ||||||
|  |   // the correct type as a parameter. | ||||||
|  |   // @ts-expect-error Post this with the axios payload: { ...follow, approved: true} | ||||||
|   follow.approved = true |   follow.approved = true | ||||||
|   item.value.is_read = true |   item.value.is_read = true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const rejectLibraryFollow = async (follow: LibraryFollow) => { | const rejectLibraryFollow = async (follow: LibraryFollow) => { | ||||||
|   await axios.post(`federation/follows/library/${follow.uuid}/reject/`) |   await axios.post(`federation/follows/library/${follow.uuid}/reject/`) | ||||||
|  |   // TODO: This is not how Axios works. You have to send a request with | ||||||
|  |   // the correct type as a parameter. | ||||||
|  |   // @ts-expect-error Post this with the axios payload: { ...follow, approved: false} | ||||||
|   follow.approved = false |   follow.approved = false | ||||||
|   item.value.is_read = true |   item.value.is_read = true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const approveUserFollow = async (follow: UserFollow) => { | const approveUserFollow = async (follow: components["schemas"]["Follow"]) => { | ||||||
|   await axios.post(`federation/follows/user/${follow.uuid}/accept/`) |   await axios.post(`federation/follows/user/${follow.uuid}/accept/`) | ||||||
|  |   // TODO: This is not how Axios works. You have to send a request with | ||||||
|  |   // the correct type as a parameter. | ||||||
|  |   // @ts-expect-error Post this with the axios payload: { ...follow, approved: true} | ||||||
|   follow.approved = true |   follow.approved = true | ||||||
|   item.value.is_read = true |   item.value.is_read = true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const rejectUserFollow = async (follow: UserFollow) => { | const rejectUserFollow = async (follow: components["schemas"]["Follow"]) => { | ||||||
|   await axios.post(`federation/follows/user/${follow.uuid}/reject/`) |   await axios.post(`federation/follows/user/${follow.uuid}/reject/`) | ||||||
|  | 
 | ||||||
|  |   // TODO: This is not how Axios works. You have to send a request with | ||||||
|  |   // the correct type as a parameter. | ||||||
|  |   // @ts-expect-error Post this with the axios payload: { ...follow, approved: false} | ||||||
|   follow.approved = false |   follow.approved = false | ||||||
|   item.value.is_read = true |   item.value.is_read = true | ||||||
| } | } | ||||||
|  | @ -166,11 +183,13 @@ const rejectUserFollow = async (follow: UserFollow) => { | ||||||
|       /> |       /> | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|  |       <!-- TODO: Make sure `notificationData.detailUrl` has a type that satisfies `RouteLocationRaw` --> | ||||||
|  |       <!-- @vue-ignore --> | ||||||
|       <router-link |       <router-link | ||||||
|         v-if="notificationData.detailUrl" |         v-if="notificationData.detailUrl" | ||||||
|         v-slot="{ navigate }" |         v-slot="{ navigate }" | ||||||
|         custom |         custom | ||||||
|         :to="notificationData.detailUrl" |         :to="notificationData.detailUrl as RouteLocationRaw" | ||||||
|       > |       > | ||||||
|         <sanitized-html |         <sanitized-html | ||||||
|           tag="span" |           tag="span" | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import defaultCover from '~/assets/audio/default-cover.png' | ||||||
| import { momentFormat } from '~/utils/filters' | import { momentFormat } from '~/utils/filters' | ||||||
| import { ref, computed } from 'vue' | import { ref, computed } from 'vue' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
| import { useRouter } from 'vue-router' |  | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| 
 | 
 | ||||||
| import moment from 'moment' | import moment from 'moment' | ||||||
|  | @ -21,7 +20,6 @@ interface Props { | ||||||
| 
 | 
 | ||||||
| const props = defineProps<Props>() | const props = defineProps<Props>() | ||||||
| const store = useStore() | const store = useStore() | ||||||
| const router = useRouter() |  | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| 
 | 
 | ||||||
| const images = computed(() => { | const images = computed(() => { | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ interface ModifiedPlaylistTrack extends PlaylistTrack { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const tracks = computed({ | const tracks = computed({ | ||||||
|   get: () => playlistTracks.value.map((playlistTrack, index) => ({ ...playlistTrack, _id: `${index}-${playlistTrack.track.id}` } as ModifiedPlaylistTrack)), |   get: () => playlistTracks.value.map((playlistTrack, index) => ({ ...playlistTrack, _id: `${ index }-${ playlistTrack.track }` } as ModifiedPlaylistTrack)), | ||||||
|   set: (playlist) => { |   set: (playlist) => { | ||||||
|     playlistTracks.value = playlist.map((modifiedPlaylistTrack, index) => { |     playlistTracks.value = playlist.map((modifiedPlaylistTrack, index) => { | ||||||
|       const res = { ...modifiedPlaylistTrack, index } as ModifiedPlaylistTrack |       const res = { ...modifiedPlaylistTrack, index } as ModifiedPlaylistTrack | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ watch( | ||||||
|     /> |     /> | ||||||
|   </Section> |   </Section> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="objects && count > (props.filters.limit as number)" |     v-if="page && objects && count > (props.filters.limit as number)" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil((count || 0) / (props.filters.limit as number))" |     :pages="Math.ceil((count || 0) / (props.filters.limit as number))" | ||||||
|   /> |   /> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { ComponentProps } from 'vue-component-type-helpers' | import type { ComponentProps } from 'vue-component-type-helpers' | ||||||
| import { useAttrs } from 'vue' |  | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Spacer from '~/components/ui/Spacer.vue' | import Spacer from '~/components/ui/Spacer.vue' | ||||||
|  | @ -60,6 +59,9 @@ const props = defineProps<{ | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|         <slot name="topleft" /> |         <slot name="topleft" /> | ||||||
|  |         <!-- The inferred type of props occasionally overloads the typescript compiler. --> | ||||||
|  |         <!-- TODO: Remove @vue-ignore once tsc is re-implemented in Go (and 10x faster) --> | ||||||
|  |         <!-- @vue-ignore --> | ||||||
|         <Heading |         <Heading | ||||||
|           v-bind="props" |           v-bind="props" | ||||||
|           style=" |           style=" | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ import { useElementSize } from '@vueuse/core' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { ref, computed, watch } from 'vue' | import { ref, computed, watch } from 'vue' | ||||||
| import { isMobileView } from '~/composables/screen' | import { isMobileView } from '~/composables/screen' | ||||||
| import { preventNonNumeric } from '~/utils/event-validators' |  | ||||||
| 
 | 
 | ||||||
| import Button from '~/components/ui/Button.vue' | import Button from '~/components/ui/Button.vue' | ||||||
| import Input from '~/components/ui/Input.vue' | import Input from '~/components/ui/Input.vue' | ||||||
|  |  | ||||||
|  | @ -109,13 +109,23 @@ onMounted(() => { | ||||||
|           :key="key" |           :key="key" | ||||||
|           :class="$style.description" |           :class="$style.description" | ||||||
|           :style="`margin-right: -20%; --current-step: 0; color: magenta;`" |           :style="`margin-right: -20%; --current-step: 0; color: magenta;`" | ||||||
|         ><Markdown :md="options[key]" /></span> |         > | ||||||
|  |           <!-- For some reason, the linter complains that (Record<T, string>)[T] is not string... --> | ||||||
|  |           <!-- TODO: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437 --> | ||||||
|  |           <!-- @vue-ignore --> | ||||||
|  |           <Markdown :md="options[model]" /> | ||||||
|  |         </span> | ||||||
|       </span> |       </span> | ||||||
|       <span |       <span | ||||||
|         v-if="model !== undefined" |         v-if="model !== undefined" | ||||||
|         style="position: absolute;" |         style="position: absolute;" | ||||||
|         :class="$style.description" |         :class="$style.description" | ||||||
|       ><Markdown :md="options[model]" /></span> |       > | ||||||
|  |         <!-- For some reason, the linter complains that (Record<T, string>)[T] is not string... --> | ||||||
|  |         <!-- TODO: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437 --> | ||||||
|  |         <!-- @vue-ignore --> | ||||||
|  |         <Markdown :md="options[model]" /> | ||||||
|  |       </span> | ||||||
|     </span> |     </span> | ||||||
|   </Layout> |   </Layout> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,16 +1,9 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { type RouterLinkProps } from 'vue-router' | import { type TabProps, TABS_INJECTION_KEY } from '~/injection-keys' | ||||||
| import { TABS_INJECTION_KEY } from '~/injection-keys' |  | ||||||
| import { whenever } from '@vueuse/core' | import { whenever } from '@vueuse/core' | ||||||
| import { inject, ref } from 'vue' | import { inject, ref } from 'vue' | ||||||
| 
 | 
 | ||||||
| export type Props = { | const props = defineProps<TabProps>() | ||||||
|   title: string, |  | ||||||
|   to?: RouterLinkProps['to'] |  | ||||||
|   icon?: string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const props = defineProps<Props>() |  | ||||||
| 
 | 
 | ||||||
| const { currentTitle, tabs } = inject(TABS_INJECTION_KEY, { | const { currentTitle, tabs } = inject(TABS_INJECTION_KEY, { | ||||||
|   currentTitle: ref(props.title), |   currentTitle: ref(props.title), | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { TABS_INJECTION_KEY } from '~/injection-keys' | import { type TabProps, TABS_INJECTION_KEY } from '~/injection-keys' | ||||||
| import { computed, provide, reactive, ref, watch } from 'vue' | import { computed, provide, reactive, ref, watch } from 'vue' | ||||||
| 
 | 
 | ||||||
| import { type Props as TabProps } from '~/components/ui/Tab.vue' |  | ||||||
| 
 |  | ||||||
| import Button from '~/components/ui/Button.vue' | import Button from '~/components/ui/Button.vue' | ||||||
| import Link from '~/components/ui/Link.vue' | import Link from '~/components/ui/Link.vue' | ||||||
| import { useRoute } from 'vue-router' | import { useRoute } from 'vue-router' | ||||||
|  |  | ||||||
|  | @ -39,16 +39,12 @@ const styles = { | ||||||
|   }[a!]) |   }[a!]) | ||||||
| } as const | } as const | ||||||
| 
 | 
 | ||||||
| const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>) => | const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>):string => | ||||||
|   ( |   ( | ||||||
|     typeof styles[key] === 'function' |     typeof styles[key] === 'string' | ||||||
|       ? (styles[key]( |       ? styles[key] | ||||||
|       // @ts-expect-error We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
 |       // @ts-expect-error We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
 | ||||||
|           (key in props && props[key]) |       : (styles[key]((key in props && props[key]) ? props[((props[key]), (key))] : value)) | ||||||
|             ? props[((props[key]), (key))] |  | ||||||
|             : value |  | ||||||
|         )) |  | ||||||
|       : styles[key] |  | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
| const merge = (rules: string[]) => (attributes: HTMLAttributes = {}) => | const merge = (rules: string[]) => (attributes: HTMLAttributes = {}) => | ||||||
|  |  | ||||||
|  | @ -111,6 +111,9 @@ export const useQueue = createGlobalState(() => { | ||||||
|       const { uploads } = await axios.get(`tracks/${track.id}/`) |       const { uploads } = await axios.get(`tracks/${track.id}/`) | ||||||
|         .then(response => response.data as Track, () => ({ uploads: [] as Upload[] })) |         .then(response => response.data as Track, () => ({ uploads: [] as Upload[] })) | ||||||
| 
 | 
 | ||||||
|  |       // TODO: Either make `track` a writable ref or implement the client/cache model
 | ||||||
|  |       // See Issue: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437
 | ||||||
|  |       // @ts-expect-error `track` is read-only
 | ||||||
|       track.uploads = uploads |       track.uploads = uploads | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/ | ||||||
| import type { components } from '~/generated/types' | import type { components } from '~/generated/types' | ||||||
| import type { ContentFilter } from '~/store/moderation' | import type { ContentFilter } from '~/store/moderation' | ||||||
| 
 | 
 | ||||||
| import { useCurrentElement } from '@vueuse/core' |  | ||||||
| import { computed, markRaw, ref } from 'vue' | import { computed, markRaw, ref } from 'vue' | ||||||
| import { i18n } from '~/init/locale' | import { i18n } from '~/init/locale' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
|  | @ -154,7 +153,7 @@ export default (props: PlayOptionsProps) => { | ||||||
|     return tracks.filter(track => track.uploads?.length).map(markRaw) |     return tracks.filter(track => track.uploads?.length).map(markRaw) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const el = useCurrentElement() |   // const el = useCurrentElement()
 | ||||||
| 
 | 
 | ||||||
|   const enqueue = async () => { |   const enqueue = async () => { | ||||||
|     const tracks = await getPlayableTracks() |     const tracks = await getPlayableTracks() | ||||||
|  |  | ||||||
|  | @ -4,10 +4,12 @@ import { ref } from 'vue' | ||||||
| 
 | 
 | ||||||
| export default () => { | export default () => { | ||||||
|   const pageQuery = useRouteQuery<string>('page', '1') |   const pageQuery = useRouteQuery<string>('page', '1') | ||||||
|   const page = ref() |   const page = ref<number>() | ||||||
|   syncRef(pageQuery, page, { |   syncRef(pageQuery, page, { | ||||||
|     transform: { |     transform: { | ||||||
|       ltr: (left) => +left, |       ltr: (left) => +left, | ||||||
|  |       // TODO: Why toString?
 | ||||||
|  |       // @ts-expect-error string vs. number
 | ||||||
|       rtl: (right) => right.toString() |       rtl: (right) => right.toString() | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  | @ -42,13 +42,13 @@ const styles = { | ||||||
|   ...widths, ...sizes |   ...widths, ...sizes | ||||||
| } as const satisfies Record<Key, string | ((w: string) => string)> | } as const satisfies Record<Key, string | ((w: string) => string)> | ||||||
| 
 | 
 | ||||||
|  | // The `lint:tsc` script more errors here than the language server is happy.
 | ||||||
|  | // TODO: Fix this Issue: https://dev.funkwhale.audio/funkwhale/funkwhale/-/issues/2437
 | ||||||
| const getStyle = (props: Partial<WidthProps>) => (key: Key):string => | const getStyle = (props: Partial<WidthProps>) => (key: Key):string => | ||||||
|   key in props |   key in props | ||||||
|     ? typeof styles[key] === 'function' |     ? typeof styles[key] === 'function' | ||||||
|       ? styles[key]( |  | ||||||
|       // @ts-expect-error Typescript is hard. Make the typescript compiler understand `key in props`
 |       // @ts-expect-error Typescript is hard. Make the typescript compiler understand `key in props`
 | ||||||
|         props[key] |       ? styles[key](props[key]) | ||||||
|       ) |  | ||||||
|       : styles[key] as string |       : styles[key] as string | ||||||
|     : '' |     : '' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -70,6 +70,8 @@ export const install: InitModule = ({ store }) => { | ||||||
|       const { current } = store.state.radios |       const { current } = store.state.radios | ||||||
| 
 | 
 | ||||||
|       if (current.clientOnly) { |       if (current.clientOnly) { | ||||||
|  |         // TODO: Type this event
 | ||||||
|  |         // @ts-expect-error untyped event
 | ||||||
|         await CLIENT_RADIOS[current.type].handleListen(current, event) |         await CLIENT_RADIOS[current.type].handleListen(current, event) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,11 @@ | ||||||
| import { type InjectionKey, type Ref } from 'vue' | import { type InjectionKey, type Ref } from 'vue' | ||||||
| import { type Props as TabProps } from '~/components/ui/Tab.vue' | import { type RouterLinkProps } from 'vue-router' | ||||||
|  | 
 | ||||||
|  | export type TabProps = { | ||||||
|  |   title: string, | ||||||
|  |   to?: RouterLinkProps['to'] | ||||||
|  |   icon?: string | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{ | export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{ | ||||||
|   tabs: TabProps[] |   tabs: TabProps[] | ||||||
|  |  | ||||||
|  | @ -16,10 +16,6 @@ export interface State { | ||||||
|   settings: Settings |   settings: Settings | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type TotalCount = { |  | ||||||
|   total: number |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // export interface NodeInfo {
 | // export interface NodeInfo {
 | ||||||
| //   version: string;
 | //   version: string;
 | ||||||
| //   software: {
 | //   software: {
 | ||||||
|  |  | ||||||
|  | @ -10,7 +10,8 @@ import Alert from '~/components/ui/Alert.vue' | ||||||
| import Button from '~/components/ui/Button.vue' | import Button from '~/components/ui/Button.vue' | ||||||
| import Modal from '~/components/ui/Modal.vue' | import Modal from '~/components/ui/Modal.vue' | ||||||
| import Input from '~/components/ui/Input.vue' | import Input from '~/components/ui/Input.vue' | ||||||
| import FileUploadWidget from '~/components/library/FileUploadWidget.vue' | 
 | ||||||
|  | // TODO: Delete this file once all upload functionality is moved to the new UI. | ||||||
| 
 | 
 | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| 
 | 
 | ||||||
|  | @ -38,13 +39,15 @@ const combinedFileSize = computed(() => bytesToHumanSize( | ||||||
| )) | )) | ||||||
| 
 | 
 | ||||||
| // Actions | // Actions | ||||||
| const processFiles = (fileList: FileList) => { |  | ||||||
|   if (!uploads.currentUploadGroup) return |  | ||||||
| 
 | 
 | ||||||
|   for (const file of fileList) { | // TODO: Is this needed? | ||||||
|     uploads.currentUploadGroup.queueUpload(file) | // const processFiles = (fileList: FileList) => { | ||||||
|   } | //   if (!uploads.currentUploadGroup) return | ||||||
| } | 
 | ||||||
|  | //   for (const file of fileList) { | ||||||
|  | //     uploads.currentUploadGroup.queueUpload(file) | ||||||
|  | //   } | ||||||
|  | // } | ||||||
| 
 | 
 | ||||||
| const router = useRouter() | const router = useRouter() | ||||||
| const cancel = () => { | const cancel = () => { | ||||||
|  | @ -53,14 +56,14 @@ const cancel = () => { | ||||||
|   uploads.currentUploadGroup = undefined |   uploads.currentUploadGroup = undefined | ||||||
| 
 | 
 | ||||||
|   if (uploads.queue.length > 0) { |   if (uploads.queue.length > 0) { | ||||||
|     return router.push('/upload/running') |     router.push('/upload/running') | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const continueInBackground = () => { | const continueInBackground = () => { | ||||||
|   libraryOpen.value = false |   libraryOpen.value = false | ||||||
|   uploads.currentUploadGroup = undefined |   uploads.currentUploadGroup = undefined | ||||||
|   return router.push('/upload/running') |   router.push('/upload/running') | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO (whole file): Delete this file, please. | // TODO (whole file): Delete this file, please. | ||||||
|  | @ -106,12 +109,16 @@ const isOpen = computed({ | ||||||
|       </Alert> |       </Alert> | ||||||
|     </template> |     </template> | ||||||
| 
 | 
 | ||||||
|  |     <!-- TODO: Use a file input. We haven't implemented this yet. | ||||||
|  |     We could say v-model can be of type `string | number | File | File[]` | ||||||
|  |     and then implement this functionality. --> | ||||||
|  |     <!-- v-model="processFiles" --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Input |     <Input | ||||||
|       type="file" |       type="file" | ||||||
|       :accept="['.flac', '.ogg', '.opus', '.mp3', '.aac', '.aif', '.aiff', '.m4a'].join(', ')" |       :accept="['.flac', '.ogg', '.opus', '.mp3', '.aac', '.aif', '.aiff', '.m4a'].join(', ')" | ||||||
|       multiple |       multiple | ||||||
|       auto-reset |       auto-reset | ||||||
|       @files="processFiles" |  | ||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <!-- Upload path --> |     <!-- Upload path --> | ||||||
|  |  | ||||||
|  | @ -14,7 +14,6 @@ import Popover from '~/components/ui/Popover.vue' | ||||||
| import PopoverItem from '~/components/ui/popover/PopoverItem.vue' | import PopoverItem from '~/components/ui/popover/PopoverItem.vue' | ||||||
| import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue' | import PopoverSubmenu from '~/components/ui/popover/PopoverSubmenu.vue' | ||||||
| import Spacer from '~/components/ui/Spacer.vue' | import Spacer from '~/components/ui/Spacer.vue' | ||||||
| import Pill from '~/components/ui/Pill.vue' |  | ||||||
| 
 | 
 | ||||||
| const route = useRoute() | const route = useRoute() | ||||||
| const store = useStore() | const store = useStore() | ||||||
|  |  | ||||||
|  | @ -23,7 +23,9 @@ | ||||||
| //   return Metadata.parseBlob(file).then(metadata => metadata.common)
 | //   return Metadata.parseBlob(file).then(metadata => metadata.common)
 | ||||||
| // }
 | // }
 | ||||||
| 
 | 
 | ||||||
|  | // @ts-expect-error This is not installed...?
 | ||||||
| import * as jsmediaTags from 'jsmediatags/dist/jsmediatags.min.js' | import * as jsmediaTags from 'jsmediatags/dist/jsmediatags.min.js' | ||||||
|  | // @ts-expect-error This is not installed...?
 | ||||||
| import type { ShortcutTags } from 'jsmediatags' | import type { ShortcutTags } from 'jsmediatags' | ||||||
| 
 | 
 | ||||||
| const REQUIRED_TAGS = ['title', 'artist', 'album'] | const REQUIRED_TAGS = ['title', 'artist', 'album'] | ||||||
|  | @ -47,6 +49,7 @@ export const getCoverUrl = async (tags: Tags): Promise<string | undefined> => { | ||||||
| export const getTags = async (file: File) => { | export const getTags = async (file: File) => { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|     jsmediaTags.read(file, { |     jsmediaTags.read(file, { | ||||||
|  |       // @ts-expect-error Please type `tags`
 | ||||||
|       onSuccess: ({ tags }) => { |       onSuccess: ({ tags }) => { | ||||||
|         if (tags.picture?.data) { |         if (tags.picture?.data) { | ||||||
|           tags.picture.data = new Uint8Array(tags.picture.data) |           tags.picture.data = new Uint8Array(tags.picture.data) | ||||||
|  | @ -59,6 +62,7 @@ export const getTags = async (file: File) => { | ||||||
| 
 | 
 | ||||||
|         resolve(tags) |         resolve(tags) | ||||||
|       }, |       }, | ||||||
|  |       // @ts-expect-error Please type `error`
 | ||||||
|       onError: (error) => reject(error) |       onError: (error) => reject(error) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|  | @ -4,8 +4,8 @@ import { computed, ref, type Ref } from 'vue' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
| import axios from 'axios' | import axios from 'axios' | ||||||
| 
 | 
 | ||||||
| type Item = { type: 'custom' | 'preset', label: string } | export type Item = { type: 'custom' | 'preset', label: string } | ||||||
| type Model = { currents: Item[], others?: Item[] } | export type Model = { currents: Item[], others?: Item[] } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Load and cache all tags. |  * Load and cache all tags. | ||||||
|  |  | ||||||
|  | @ -190,9 +190,13 @@ export const useUploadsStore = defineStore('uploads', () => { | ||||||
|       const upload = group.queue.find(entry => entry.guid === event.upload.uuid) |       const upload = group.queue.find(entry => entry.guid === event.upload.uuid) | ||||||
|       if (!upload) continue |       if (!upload) continue | ||||||
| 
 | 
 | ||||||
|       if (event.new_status !== 'failed') { |       if (event.new_status !== 'errored') { | ||||||
|  |         // TODO: Find out what other field to use here
 | ||||||
|  |         // @ts-expect-error wrong field
 | ||||||
|         upload.importedAt = event.upload.import_date |         upload.importedAt = event.upload.import_date | ||||||
|       } else { |       } else { | ||||||
|  |         // TODO: Add second parameter `error`
 | ||||||
|  |         // @ts-expect-error missing parameter
 | ||||||
|         upload.fail('import-failed') |         upload.fail('import-failed') | ||||||
|       } |       } | ||||||
|       break |       break | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ import useMarkdown from '~/composables/useMarkdown' | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Loader from '~/components/ui/Loader.vue' | import Loader from '~/components/ui/Loader.vue' | ||||||
| import Spacer from '~/components/ui/Spacer.vue' |  | ||||||
| import Header from '~/components/ui/Header.vue' | import Header from '~/components/ui/Header.vue' | ||||||
| import Toggle from '~/components/ui/Toggle.vue' | import Toggle from '~/components/ui/Toggle.vue' | ||||||
| import Button from '~/components/ui/Button.vue' | import Button from '~/components/ui/Button.vue' | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { computed } from 'vue' |  | ||||||
| 
 | 
 | ||||||
| import EditsCardList from '~/components/manage/library/EditsCardList.vue' | import EditsCardList from '~/components/manage/library/EditsCardList.vue' | ||||||
| 
 | 
 | ||||||
|  | @ -15,9 +14,11 @@ withDefaults(defineProps<Props>(), { | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| const labels = computed(() => ({ | 
 | ||||||
|   title: t('views.admin.library.EditsList.title') | // TODO: Do we want to use this title? | ||||||
| })) | // const labels = computed(() => ({ | ||||||
|  | //   title: t('views.admin.library.EditsList.title') | ||||||
|  | // })) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ import { useI18n } from 'vue-i18n' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
| 
 | 
 | ||||||
| import axios from 'axios' | import axios from 'axios' | ||||||
| import $ from 'jquery' |  | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Loader from '~/components/ui/Loader.vue' | import Loader from '~/components/ui/Loader.vue' | ||||||
|  | @ -108,6 +107,7 @@ fetchData() | ||||||
| const el = useCurrentElement() | const el = useCurrentElement() | ||||||
| watch(object, async () => { | watch(object, async () => { | ||||||
|   await nextTick() |   await nextTick() | ||||||
|  |   // @ts-expect-error JQuery owhere to be found... | ||||||
|   $(el.value).find('select.dropdown').dropdown() |   $(el.value).find('select.dropdown').dropdown() | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,12 +47,14 @@ const logger = useLogger() | ||||||
| 
 | 
 | ||||||
| const search = ref() | const search = ref() | ||||||
| 
 | 
 | ||||||
| const page = usePage() |  | ||||||
| const result = ref<BackendResponse<Report>>() | const result = ref<BackendResponse<Report>>() | ||||||
| 
 | 
 | ||||||
| const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props) | const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props) | ||||||
| const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props) | const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props) | ||||||
| 
 | 
 | ||||||
|  | const page = usePage() | ||||||
|  | const pages = computed(() => result.value?.count ? Math.ceil(result.value.count / paginateBy.value) : 0) | ||||||
|  | 
 | ||||||
| const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ | const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ | ||||||
|   ['creation_date', 'creation_date'], |   ['creation_date', 'creation_date'], | ||||||
|   ['applied_date', 'applied_date'] |   ['applied_date', 'applied_date'] | ||||||
|  | @ -214,8 +216,9 @@ const labels = computed(() => ({ | ||||||
|     </div> |     </div> | ||||||
|     <div class="ui center aligned basic segment"> |     <div class="ui center aligned basic segment"> | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="result && result.count > paginateBy" |         v-if="page && result && result.count > paginateBy" | ||||||
|         v-model:current="page" |         v-model:page="page" | ||||||
|  |         :pages="pages" | ||||||
|         :paginate-by="paginateBy" |         :paginate-by="paginateBy" | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -110,10 +110,10 @@ const labels = computed(() => ({ | ||||||
|           <Input |           <Input | ||||||
|             id="requests-search" |             id="requests-search" | ||||||
|             ref="search" |             ref="search" | ||||||
|  |             v-model="query" | ||||||
|             name="search" |             name="search" | ||||||
|             search |             search | ||||||
|             :label="t('views.admin.moderation.RequestsList.label.search')" |             :label="t('views.admin.moderation.RequestsList.label.search')" | ||||||
|             :value="query" |  | ||||||
|             :placeholder="labels.searchPlaceholder" |             :placeholder="labels.searchPlaceholder" | ||||||
|           /> |           /> | ||||||
|         </form> |         </form> | ||||||
|  | @ -194,8 +194,9 @@ const labels = computed(() => ({ | ||||||
|       @handled="fetchData" |       @handled="fetchData" | ||||||
|     /> |     /> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result.count > paginateBy" |       v-if="page && result.count > paginateBy" | ||||||
|       v-model:current="page" |       v-model:page="page" | ||||||
|  |       v-model:pages="result.count" | ||||||
|       :paginate-by="paginateBy" |       :paginate-by="paginateBy" | ||||||
|     /> |     /> | ||||||
|   </template> |   </template> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { computed, ref } from 'vue' | import { computed, ref } from 'vue' | ||||||
|  | import { useRoute } from 'vue-router' | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Nav from '~/components/ui/Nav.vue' | import Nav from '~/components/ui/Nav.vue' | ||||||
|  | @ -33,6 +34,6 @@ const tabs = ref([ | ||||||
|   > |   > | ||||||
|     <Nav v-model="tabs" /> |     <Nav v-model="tabs" /> | ||||||
| 
 | 
 | ||||||
|     <router-view :key="$route.fullPath" /> |     <router-view :key="useRoute().fullPath" /> | ||||||
|   </Layout> |   </Layout> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { ImportStatus, PrivacyLevel, Upload, BackendResponse } from '~/types' | import type { ImportStatus, PrivacyLevel, Upload } from '~/types' | ||||||
| import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch' | import type { SmartSearchProps } from '~/composables/navigation/useSmartSearch' | ||||||
| import type { OrderingProps } from '~/composables/navigation/useOrdering' | import type { OrderingProps } from '~/composables/navigation/useOrdering' | ||||||
| import type { RouteRecordName } from 'vue-router' | import type { RouteRecordName } from 'vue-router' | ||||||
|  | @ -595,12 +595,12 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => { | ||||||
|     </template> |     </template> | ||||||
|   </action-table> |   </action-table> | ||||||
|   <Pagination |   <Pagination | ||||||
|     v-if="result && result.count > paginateBy" |     v-if="page && result && result.count > paginateBy" | ||||||
|     v-model:page="page" |     v-model:page="page" | ||||||
|     :pages="Math.ceil(result.count / paginateBy)" |     :pages="Math.ceil(result.count / paginateBy)" | ||||||
|   /> |   /> | ||||||
| 
 | 
 | ||||||
|   <span v-if="result && result.results.length > 0"> |   <span v-if="page && result && result.results.length > 0"> | ||||||
|     {{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} |     {{ t('components.manage.library.UploadsTable.pagination.results', { start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count }) }} | ||||||
|   </span> |   </span> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,13 +1,12 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { BackendError } from '~/types' | import type { BackendError } from '~/types' | ||||||
| 
 | 
 | ||||||
| import { computed, ref, onMounted, nextTick } from 'vue' | import { computed, ref } from 'vue' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { useRouter } from 'vue-router' | import { useRouter } from 'vue-router' | ||||||
| 
 | 
 | ||||||
| import Input from '~/components/ui/Input.vue' | import Input from '~/components/ui/Input.vue' | ||||||
| import Button from '~/components/ui/Button.vue' | import Button from '~/components/ui/Button.vue' | ||||||
| import Spacer from '~/components/ui/Spacer.vue' |  | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
| import Link from '~/components/ui/Link.vue' | import Link from '~/components/ui/Link.vue' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,7 +12,6 @@ import UserFollowButton from '~/components/federation/UserFollowButton.vue' | ||||||
| import axios from 'axios' | import axios from 'axios' | ||||||
| 
 | 
 | ||||||
| import useErrorHandler from '~/composables/useErrorHandler' | import useErrorHandler from '~/composables/useErrorHandler' | ||||||
| import useReport from '~/composables/moderation/useReport' |  | ||||||
| import RenderedDescription from '~/components/common/RenderedDescription.vue' | import RenderedDescription from '~/components/common/RenderedDescription.vue' | ||||||
| 
 | 
 | ||||||
| import Layout from '~/components/ui/Layout.vue' | import Layout from '~/components/ui/Layout.vue' | ||||||
|  | @ -35,7 +34,6 @@ const props = withDefaults(defineProps<Props>(), { | ||||||
|   domain: null |   domain: null | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const { report, getReportableObjects } = useReport() |  | ||||||
| const store = useStore() | const store = useStore() | ||||||
| 
 | 
 | ||||||
| const object = ref<components['schemas']['FullActor'] | null>(null) | const object = ref<components['schemas']['FullActor'] | null>(null) | ||||||
|  | @ -43,7 +41,9 @@ const object = ref<components['schemas']['FullActor'] | null>(null) | ||||||
| const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username))) | const actorColor = computed(() => intToRGB(hashCode(object.value?.full_username))) | ||||||
| const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` })) | const defaultAvatarStyle = computed(() => ({ backgroundColor: `#${actorColor.value}` })) | ||||||
| 
 | 
 | ||||||
| const displayName = computed(() => object.value?.name ?? object.value?.preferred_username) | // TODO: Check if still needed | ||||||
|  | //const displayName = computed(() => object.value?.name ?? object.value?.preferred_username) | ||||||
|  | 
 | ||||||
| const fullUsername = computed(() => props.domain | const fullUsername = computed(() => props.domain | ||||||
|   ? `${props.username}@${props.domain}` |   ? `${props.username}@${props.domain}` | ||||||
|   : `${props.username}@${store.getters['instance/domain']}` |   : `${props.username}@${store.getters['instance/domain']}` | ||||||
|  | @ -79,7 +79,6 @@ const fetchData = async () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| watch(props, fetchData, { immediate: true }) | watch(props, fetchData, { immediate: true }) | ||||||
| const recentActivity = ref(0) |  | ||||||
| 
 | 
 | ||||||
| const { copy, copied, isSupported } = useClipboard() | const { copy, copied, isSupported } = useClipboard() | ||||||
| 
 | 
 | ||||||
|  | @ -106,13 +105,19 @@ const tabs = ref([{ | ||||||
|     main |     main | ||||||
|   > |   > | ||||||
|     <!-- TODO: Translate Edit Link --> |     <!-- TODO: Translate Edit Link --> | ||||||
|  |     <!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Header |     <Header | ||||||
|       :h1="props.username" |       :h1="props.username" | ||||||
|       :action="{ |       :action="{ | ||||||
|         text:'Edit profile', |         text:'Edit profile', | ||||||
|  |         // @ts-ignore | ||||||
|         to:'/settings', |         to:'/settings', | ||||||
|  |         // @ts-ignore | ||||||
|         primary: true, |         primary: true, | ||||||
|  |         // @ts-ignore | ||||||
|         solid: true, |         solid: true, | ||||||
|  |         // @ts-ignore | ||||||
|         icon: 'bi-pencil-fill' |         icon: 'bi-pencil-fill' | ||||||
|       }" |       }" | ||||||
|       style="margin-top: 58px;" |       style="margin-top: 58px;" | ||||||
|  | @ -169,6 +174,8 @@ const tabs = ref([{ | ||||||
|         flex |         flex | ||||||
|         no-gap |         no-gap | ||||||
|       > |       > | ||||||
|  |         <!-- TODO: Fix error with `$event` not being the right type --> | ||||||
|  |         <!-- @vue-ignore --> | ||||||
|         <RenderedDescription |         <RenderedDescription | ||||||
|           :content="{ html: object?.summary?.html || '' }" |           :content="{ html: object?.summary?.html || '' }" | ||||||
|           :field-name="'summary'" |           :field-name="'summary'" | ||||||
|  |  | ||||||
|  | @ -142,6 +142,9 @@ const remove = async () => { | ||||||
| 
 | 
 | ||||||
| const updateSubscriptionCount = (delta: number) => { | const updateSubscriptionCount = (delta: number) => { | ||||||
|   if (object.value) { |   if (object.value) { | ||||||
|  |     // TODO: Store a modified copy in the cache or on the db instead of mutating the object in-memory. | ||||||
|  |     // #2438 | ||||||
|  |     // @ts-expect-error Property 'subscriptions_count' is readonly on type 'Channel' | ||||||
|     object.value.subscriptions_count -= delta |     object.value.subscriptions_count -= delta | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -139,12 +139,17 @@ const showCreateModal = ref(false) | ||||||
|     stack |     stack | ||||||
|     main |     main | ||||||
|   > |   > | ||||||
|  |     <!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Header |     <Header | ||||||
|       :h1="t('views.channels.SubscriptionsList.title')" |       :h1="t('views.channels.SubscriptionsList.title')" | ||||||
|       :action="{ |       :action="{ | ||||||
|         text: t('views.channels.SubscriptionsList.link.addNew'), |         text: t('views.channels.SubscriptionsList.link.addNew'), | ||||||
|  |         // @ts-ignore | ||||||
|         onClick: () => { showSubscribeModal = true }, |         onClick: () => { showSubscribeModal = true }, | ||||||
|  |         // @ts-ignore | ||||||
|         primary: true, |         primary: true, | ||||||
|  |         // @ts-ignore | ||||||
|         icon: 'bi-plus' |         icon: 'bi-plus' | ||||||
|       }" |       }" | ||||||
|       large-section-heading |       large-section-heading | ||||||
|  | @ -194,13 +199,17 @@ const showCreateModal = ref(false) | ||||||
|       :show-modification-date="true" |       :show-modification-date="true" | ||||||
|       :filters="{q: subscribedQuery, subscribed: 'true'}" |       :filters="{q: subscribedQuery, subscribed: 'true'}" | ||||||
|     /> |     /> | ||||||
|     <!-- TODO: Translations --> |     <!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Header |     <Header | ||||||
|       :h1="t('views.auth.ProfileOverview.header.channels')" |       :h1="t('views.auth.ProfileOverview.header.channels')" | ||||||
|       :action="{ |       :action="{ | ||||||
|         text: t('views.channels.SubscriptionsList.link.addNew'), |         text: t('views.channels.SubscriptionsList.link.addNew'), | ||||||
|  |         // @ts-ignore | ||||||
|         onClick: () => { showCreateModal = true }, |         onClick: () => { showCreateModal = true }, | ||||||
|  |         // @ts-ignore | ||||||
|         icon: 'bi-plus', |         icon: 'bi-plus', | ||||||
|  |         // @ts-ignore | ||||||
|         primary: true |         primary: true | ||||||
|       }" |       }" | ||||||
|       large-section-heading |       large-section-heading | ||||||
|  |  | ||||||
|  | @ -64,12 +64,17 @@ const showSubscribeModal = ref(false) | ||||||
|     stack |     stack | ||||||
|     main |     main | ||||||
|   > |   > | ||||||
|  |     <!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Header |     <Header | ||||||
|       :h1="labels.title" |       :h1="labels.title" | ||||||
|       :action="{ |       :action="{ | ||||||
|         text: t('views.channels.SubscriptionsList.link.addNew'), |         text: t('views.channels.SubscriptionsList.link.addNew'), | ||||||
|  |         // @ts-ignore | ||||||
|         onClick: () => { showSubscribeModal = true }, |         onClick: () => { showSubscribeModal = true }, | ||||||
|  |         // @ts-ignore | ||||||
|         primary: true, |         primary: true, | ||||||
|  |         // @ts-ignore | ||||||
|         icon: 'bi-plus' |         icon: 'bi-plus' | ||||||
|       }" |       }" | ||||||
|       page-heading |       page-heading | ||||||
|  |  | ||||||
|  | @ -6,8 +6,6 @@ import { useI18n } from 'vue-i18n' | ||||||
| import { useStore } from '~/store' | import { useStore } from '~/store' | ||||||
| import { computed } from 'vue' | import { computed } from 'vue' | ||||||
| 
 | 
 | ||||||
| import moment from 'moment' |  | ||||||
| 
 |  | ||||||
| import TagsList from '~/components/tags/List.vue' | import TagsList from '~/components/tags/List.vue' | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|  | @ -25,20 +23,20 @@ const imageUrl = computed(() => props.channel.artist?.cover | ||||||
|   ? store.getters['instance/absoluteUrl'](props.channel.artist?.cover.urls.medium_square_crop) |   ? store.getters['instance/absoluteUrl'](props.channel.artist?.cover.urls.medium_square_crop) | ||||||
|   : fallbackImageUrl |   : fallbackImageUrl | ||||||
| ) | ) | ||||||
| const urlId = computed(() => props.channel.actor?.is_local | 
 | ||||||
|   ? props.channel.actor.preferred_username | // TODO: Find out if still useful: | ||||||
|   : props.channel.actor | // const urlId = computed(() => props.channel.actor?.is_local | ||||||
|     ? props.channel.actor.full_username | //   ? props.channel.actor.preferred_username | ||||||
|     : props.channel.uuid | //   : props.channel.actor | ||||||
| ) | //     ? props.channel.actor.full_username | ||||||
|  | //     : props.channel.uuid | ||||||
|  | // ) | ||||||
| 
 | 
 | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| const updatedTitle = computed(() => { | const updatedTitle = computed(() => { | ||||||
|   const date = momentFormat(new Date(props.channel.artist?.modification_date ?? '1970-01-01')) |   const date = momentFormat(new Date(props.channel.artist?.modification_date ?? '1970-01-01')) | ||||||
|   return t('components.audio.ChannelCard.title', { date }) |   return t('components.audio.ChannelCard.title', { date }) | ||||||
| }) | }) | ||||||
| 
 |  | ||||||
| const updatedAgo = computed(() => moment(props.channel.artist?.modification_date).fromNow()) |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  |  | ||||||
|  | @ -55,18 +55,30 @@ const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fie | ||||||
|           <human-date :date="library.creation_date" /> |           <human-date :date="library.creation_date" /> | ||||||
|         </span> |         </span> | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|  |       <!-- TODO: Add `description` field to `Library` --> | ||||||
|  |       <!-- @vue-ignore --> | ||||||
|       <div class="description"> |       <div class="description"> | ||||||
|         {{ library.description }} |         {{ | ||||||
|  |           // @ts-expect-error Property 'description' does not exist on type 'Library' | ||||||
|  |           library.description | ||||||
|  |         }} | ||||||
|         <div class="ui hidden divider" /> |         <div class="ui hidden divider" /> | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|       <div class="content"> |       <div class="content"> | ||||||
|  |         <!-- TODO: Add `size` field to `Library` (or find out how else to load size) --> | ||||||
|  |         <!-- @vue-ignore --> | ||||||
|         <span |         <span | ||||||
|           v-if="library.size" |           v-if="library.size" | ||||||
|           class="right floated" |           class="right floated" | ||||||
|           :data-tooltip="sizeLabel" |           :data-tooltip="sizeLabel" | ||||||
|         > |         > | ||||||
|           <i class="database icon" /> |           <i class="database icon" /> | ||||||
|           {{ humanSize(library.size) }} |           {{ | ||||||
|  |             // @ts-expect-error Property 'size' does not exist on type 'Library' | ||||||
|  |             humanSize(library.size) | ||||||
|  |           }} | ||||||
|         </span> |         </span> | ||||||
|         <i class="music icon" /> |         <i class="music icon" /> | ||||||
|         {{ t('views.content.libraries.Card.meta.tracks', library.uploads_count) }} |         {{ t('views.content.libraries.Card.meta.tracks', library.uploads_count) }} | ||||||
|  |  | ||||||
|  | @ -56,8 +56,6 @@ const props = withDefaults(defineProps<Props>(), { | ||||||
|   orderingConfigName: undefined |   orderingConfigName: undefined | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const search = ref() |  | ||||||
| 
 |  | ||||||
| const page = usePage() | const page = usePage() | ||||||
| const result = ref<BackendResponse<Upload>>() | const result = ref<BackendResponse<Upload>>() | ||||||
| 
 | 
 | ||||||
|  | @ -347,7 +345,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => { | ||||||
|   </action-table> |   </action-table> | ||||||
|   <div> |   <div> | ||||||
|     <Pagination |     <Pagination | ||||||
|       v-if="result && result.count > paginateBy" |       v-if="page && result && result.count > paginateBy" | ||||||
|       v-model:page="page" |       v-model:page="page" | ||||||
|       :pages="Math.ceil(result.count / paginateBy)" |       :pages="Math.ceil(result.count / paginateBy)" | ||||||
|     /> |     /> | ||||||
|  |  | ||||||
|  | @ -37,6 +37,9 @@ const labels = computed(() => ({ | ||||||
| })) | })) | ||||||
| 
 | 
 | ||||||
| const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me') | const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me') | ||||||
|  | 
 | ||||||
|  | // TODO: Add 'description' to the Library type | ||||||
|  | // @ts-expect-error Property 'description' does not exist on type 'Library' | ||||||
| const currentDescription = ref(props.library?.description ?? '') | const currentDescription = ref(props.library?.description ?? '') | ||||||
| const currentName = ref(props.library?.name ?? '') | const currentName = ref(props.library?.name ?? '') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -48,7 +48,9 @@ const isLoadingFollow = ref(false) | ||||||
| const showScan = ref(false) | const showScan = ref(false) | ||||||
| const latestScan = ref(props.initialLibrary.latest_scan) | const latestScan = ref(props.initialLibrary.latest_scan) | ||||||
| 
 | 
 | ||||||
| const scanProgress = computed(() => Math.min(latestScan.value.processed_files * 100 / latestScan.value.total_files, 100)) | const scanProgress = computed(() => latestScan.value && latestScan.value.processed_files && latestScan.value.total_files | ||||||
|  |   ? Math.min(latestScan.value.processed_files * 100 / latestScan.value.total_files, 100) | ||||||
|  |   : 0) | ||||||
| const scanStatus = computed(() => latestScan.value?.status ?? 'unknown') | const scanStatus = computed(() => latestScan.value?.status ?? 'unknown') | ||||||
| const canLaunchScan = computed(() => scanStatus.value !== 'pending' && scanStatus.value !== 'scanning') | const canLaunchScan = computed(() => scanStatus.value !== 'pending' && scanStatus.value !== 'scanning') | ||||||
| const radioPlayable = computed(() => ( | const radioPlayable = computed(() => ( | ||||||
|  | @ -191,8 +193,13 @@ const isOpen = ref(false) | ||||||
|     </template> |     </template> | ||||||
| 
 | 
 | ||||||
|     <div class="content"> |     <div class="content"> | ||||||
|  |       <!-- TODO: Add `description` field to `Library` --> | ||||||
|  |       <!-- @vue-ignore --> | ||||||
|       <div class="description"> |       <div class="description"> | ||||||
|         {{ library.description }} |         {{ | ||||||
|  |           // @ts-ignore | ||||||
|  |           library.description | ||||||
|  |         }} | ||||||
|       </div> |       </div> | ||||||
|       <Spacer :size="8" /> |       <Spacer :size="8" /> | ||||||
|       <div |       <div | ||||||
|  | @ -215,7 +222,7 @@ const isOpen = ref(false) | ||||||
|           <i class="bi bi-check-circle" /> |           <i class="bi bi-check-circle" /> | ||||||
|           {{ t('views.content.remote.Card.label.scanSuccess') }} |           {{ t('views.content.remote.Card.label.scanSuccess') }} | ||||||
|         </template> |         </template> | ||||||
|         <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files > 0"> |         <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files && latestScan.errored_files > 0"> | ||||||
|           <i class="bi bi-exclamation-circle" /> |           <i class="bi bi-exclamation-circle" /> | ||||||
|           {{ t('views.content.remote.Card.label.scanPartialSuccess') }} |           {{ t('views.content.remote.Card.label.scanPartialSuccess') }} | ||||||
|         </template> |         </template> | ||||||
|  |  | ||||||
|  | @ -37,8 +37,9 @@ fetchData() | ||||||
| 
 | 
 | ||||||
| const getLibraryFromFollow = (follow: LibraryFollow) => { | const getLibraryFromFollow = (follow: LibraryFollow) => { | ||||||
|   const { target } = follow |   const { target } = follow | ||||||
|   target.follow = follow |   // TODO: Actually load the target from the database or the cache. Use `client` with cache. | ||||||
|   return target as Library |   // @ts-expect-error target is a string, not a library! | ||||||
|  |   return ({ ...target, follow: follow } as Library) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const scanResult = ref() | const scanResult = ref() | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ defineProps<Props>() | ||||||
| <template> | <template> | ||||||
|   <section> |   <section> | ||||||
|     <album-widget |     <album-widget | ||||||
|  |       v-if="object.uuid" | ||||||
|       :key="String(object.uploads_count)" |       :key="String(object.uploads_count)" | ||||||
|       :header="false" |       :header="false" | ||||||
|       :search="true" |       :search="true" | ||||||
|  |  | ||||||
|  | @ -4,10 +4,8 @@ import type { Library } from '~/types' | ||||||
| import ArtistWidget from '~/components/artist/Widget.vue' | import ArtistWidget from '~/components/artist/Widget.vue' | ||||||
| 
 | 
 | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import { useStore } from '~/store' |  | ||||||
| 
 | 
 | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| const store = useStore() |  | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   object: Library |   object: Library | ||||||
|  | @ -20,6 +18,7 @@ defineProps<Props>() | ||||||
| <template> | <template> | ||||||
|   <section> |   <section> | ||||||
|     <artist-widget |     <artist-widget | ||||||
|  |       v-if="object.uuid" | ||||||
|       :key="object.uploads_count" |       :key="object.uploads_count" | ||||||
|       ref="artists" |       ref="artists" | ||||||
|       :header="false" |       :header="false" | ||||||
|  |  | ||||||
|  | @ -2,6 +2,9 @@ | ||||||
| import type { Library } from '~/types' | import type { Library } from '~/types' | ||||||
| 
 | 
 | ||||||
| import TrackTable from '~/components/audio/track/Table.vue' | import TrackTable from '~/components/audio/track/Table.vue' | ||||||
|  | import { useI18n } from 'vue-i18n' | ||||||
|  | 
 | ||||||
|  | const { t } = useI18n() | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   object: Library |   object: Library | ||||||
|  |  | ||||||
|  | @ -51,6 +51,10 @@ fetchData() | ||||||
| const updateApproved = async (follow: LibraryFollow, approved: boolean) => { | const updateApproved = async (follow: LibraryFollow, approved: boolean) => { | ||||||
|   try { |   try { | ||||||
|     await axios.post(`federation/follows/library/${follow.uuid}/${approved ? 'accept' : 'reject'}/`) |     await axios.post(`federation/follows/library/${follow.uuid}/${approved ? 'accept' : 'reject'}/`) | ||||||
|  | 
 | ||||||
|  |     // TODO: This is not how Axios works. You have to send a request with | ||||||
|  |     // the correct type as a parameter. | ||||||
|  |     // @ts-expect-error Post this with the axios payload: { ...follow, approved } | ||||||
|     follow.approved = approved |     follow.approved = approved | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     useErrorHandler(error as Error) |     useErrorHandler(error as Error) | ||||||
|  |  | ||||||
|  | @ -194,9 +194,9 @@ const tabs = ref([{ | ||||||
|         <i class="bi bi-music-note-list" /> |         <i class="bi bi-music-note-list" /> | ||||||
|         {{ t('views.library.LibraryBase.meta.tracks', object.uploads_count) }} |         {{ t('views.library.LibraryBase.meta.tracks', object.uploads_count) }} | ||||||
|       </span> |       </span> | ||||||
|       <span v-if="object && 'size' in object && object.size"> |       <span v-if="object && 'size' in object && object.size && typeof object.size === 'number'"> | ||||||
|         <i class="bi bi-database-fill" /> |         <i class="bi bi-database-fill" /> | ||||||
|         {{ humanSize(object.size) }} |         {{ humanSize(object.size as number) }} | ||||||
|       </span> |       </span> | ||||||
|     </Layout> |     </Layout> | ||||||
| 
 | 
 | ||||||
|  | @ -213,15 +213,18 @@ const tabs = ref([{ | ||||||
|         v-if="!isOwner" |         v-if="!isOwner" | ||||||
|       > |       > | ||||||
|         <library-follow-button |         <library-follow-button | ||||||
|           v-if="store.state.auth.authenticated" |           v-if="store.state.auth.authenticated && object" | ||||||
|           :library="object" |           :library="object" | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|     </Layout> |     </Layout> | ||||||
| 
 | 
 | ||||||
|  |     <!-- TODO: Add `description` field to library --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <rendered-description |     <rendered-description | ||||||
|       :content="object?.description ? {html: object?.description} : null" |       v-if="object && 'description' in object" | ||||||
|       :update-url="`channels/${object?.uuid}/`" |       :content="{ html: object.description }" | ||||||
|  |       :update-url="`channels/${object.uuid}/`" | ||||||
|       :can-update="false" |       :can-update="false" | ||||||
|     /> |     /> | ||||||
|     <Layout form> |     <Layout form> | ||||||
|  |  | ||||||
|  | @ -117,16 +117,20 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|     stack |     stack | ||||||
|     main |     main | ||||||
|   > |   > | ||||||
|  |     <!-- TODO: `yarn lint:tsc` doesn't understand the `Prop` type for `Header` while the language server does. It may be a question of typescript version... Investigate and fix! --> | ||||||
|  |     <!-- @vue-ignore --> | ||||||
|     <Header |     <Header | ||||||
|       v-if="store.state.auth.authenticated" |       v-if="store.state.auth.authenticated" | ||||||
|       :h1="t('views.playlists.List.header.browse')" |       :h1="t('views.playlists.List.header.browse')" | ||||||
|       page-heading |       page-heading | ||||||
|       :action="{ |       :action="{ | ||||||
|         onClick: () => { store.commit('playlists/showModal', true) }, |         text: t('views.playlists.List.button.create'), | ||||||
|         text: t('views.playlists.List.button.manage'), |         // @ts-ignore | ||||||
|  |         icon: 'bi-plus', | ||||||
|  |         // @ts-ignore | ||||||
|         primary: true, |         primary: true, | ||||||
|         icon: 'bi-music-note-list', |         // @ts-ignore | ||||||
|         ariaPressed: store.state.playlists.showModal || undefined |         onClick: () => { store.commit('playlists/showModal', true) } | ||||||
|       }" |       }" | ||||||
|     /> |     /> | ||||||
|     <Header |     <Header | ||||||
|  | @ -243,7 +247,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|         </Button> |         </Button> | ||||||
|       </Alert> |       </Alert> | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="result && result.count > paginateBy" |         v-if="page && result && result.count > paginateBy" | ||||||
|         v-model:page="page" |         v-model:page="page" | ||||||
|         style="grid-column: 1 / -1;" |         style="grid-column: 1 / -1;" | ||||||
|         :pages="Math.ceil(result.count/paginateBy)" |         :pages="Math.ceil(result.count/paginateBy)" | ||||||
|  | @ -255,10 +259,10 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value] | ||||||
|       /> |       /> | ||||||
|       <Spacer grow /> |       <Spacer grow /> | ||||||
|       <Pagination |       <Pagination | ||||||
|         v-if="result && result.count > paginateBy" |         v-if="page && result && result.count > paginateBy" | ||||||
|         v-model:page="page" |         v-model:page="page" | ||||||
|         style="grid-column: 1 / -1;" |  | ||||||
|         :pages="Math.ceil(result.count/paginateBy)" |         :pages="Math.ceil(result.count/paginateBy)" | ||||||
|  |         style="grid-column: 1 / -1;" | ||||||
|       /> |       /> | ||||||
|     </Section> |     </Section> | ||||||
|   </Layout> |   </Layout> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 upsiflu
						upsiflu