mirror of
https://github.com/ae-utbm/sith.git
synced 2025-05-06 01:24:03 +00:00
commit
aaa8c4ba67
@ -1,4 +1,4 @@
|
|||||||
import type { Client, Options, RequestResult } from "@hey-api/client-fetch";
|
import type { Client, Options, RequestResult, TDataShape } from "@hey-api/client-fetch";
|
||||||
import { client } from "#openapi";
|
import { client } from "#openapi";
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
export interface PaginatedResponse<T> {
|
||||||
@ -14,6 +14,7 @@ export interface PaginatedRequest {
|
|||||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||||
page_size?: number;
|
page_size?: number;
|
||||||
};
|
};
|
||||||
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginatedEndpoint<T> = <ThrowOnError extends boolean = false>(
|
type PaginatedEndpoint<T> = <ThrowOnError extends boolean = false>(
|
||||||
@ -30,7 +31,7 @@ export const paginated = async <T>(
|
|||||||
options?: PaginatedRequest,
|
options?: PaginatedRequest,
|
||||||
): Promise<T[]> => {
|
): Promise<T[]> => {
|
||||||
const maxPerPage = 199;
|
const maxPerPage = 199;
|
||||||
const queryParams = options ?? {};
|
const queryParams = options ?? ({} as PaginatedRequest);
|
||||||
queryParams.query = queryParams.query ?? {};
|
queryParams.query = queryParams.query ?? {};
|
||||||
queryParams.query.page_size = maxPerPage;
|
queryParams.query.page_size = maxPerPage;
|
||||||
queryParams.query.page = 1;
|
queryParams.query.page = 1;
|
||||||
@ -53,7 +54,7 @@ export const paginated = async <T>(
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Request {
|
interface Request extends TDataShape {
|
||||||
client?: Client;
|
client?: Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
* Build the object containing the query parameters corresponding
|
* Build the object containing the query parameters corresponding
|
||||||
* to the current filters
|
* to the current filters
|
||||||
*/
|
*/
|
||||||
getQueryParams(): ProductSearchProductsDetailedData {
|
getQueryParams(): Omit<ProductSearchProductsDetailedData, "url"> {
|
||||||
const search = this.search.length > 0 ? this.search : null;
|
const search = this.search.length > 0 ? this.search : null;
|
||||||
// If active or archived products must be filtered, put the filter in the request
|
// If active or archived products must be filtered, put the filter in the request
|
||||||
// Else, don't include the filter
|
// Else, don't include the filter
|
||||||
|
28
sas/api.py
28
sas/api.py
@ -1,6 +1,3 @@
|
|||||||
from typing import Annotated
|
|
||||||
|
|
||||||
from annotated_types import MinLen
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -16,6 +13,8 @@ from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoo
|
|||||||
from core.models import Notification, User
|
from core.models import Notification, User
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
from sas.schemas import (
|
from sas.schemas import (
|
||||||
|
AlbumAutocompleteSchema,
|
||||||
|
AlbumFilterSchema,
|
||||||
AlbumSchema,
|
AlbumSchema,
|
||||||
IdentifiedUserSchema,
|
IdentifiedUserSchema,
|
||||||
ModerationRequestSchema,
|
ModerationRequestSchema,
|
||||||
@ -31,11 +30,30 @@ class AlbumController(ControllerBase):
|
|||||||
@route.get(
|
@route.get(
|
||||||
"/search",
|
"/search",
|
||||||
response=PaginatedResponseSchema[AlbumSchema],
|
response=PaginatedResponseSchema[AlbumSchema],
|
||||||
|
permissions=[IsAuthenticated],
|
||||||
|
url_name="search-album",
|
||||||
|
)
|
||||||
|
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||||
|
def fetch_album(self, filters: Query[AlbumFilterSchema]):
|
||||||
|
"""General-purpose album search."""
|
||||||
|
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||||
|
|
||||||
|
@route.get(
|
||||||
|
"/autocomplete-search",
|
||||||
|
response=PaginatedResponseSchema[AlbumAutocompleteSchema],
|
||||||
permissions=[CanAccessLookup],
|
permissions=[CanAccessLookup],
|
||||||
)
|
)
|
||||||
@paginate(PageNumberPaginationExtra, page_size=50)
|
@paginate(PageNumberPaginationExtra, page_size=50)
|
||||||
def search_album(self, search: Annotated[str, MinLen(1)]):
|
def autocomplete_album(self, filters: Query[AlbumFilterSchema]):
|
||||||
return Album.objects.filter(name__icontains=search)
|
"""Search route to use exclusively on autocomplete input fields.
|
||||||
|
|
||||||
|
This route is separated from `GET /sas/album/search` because
|
||||||
|
getting the path of an album may need an absurd amount of db queries.
|
||||||
|
|
||||||
|
If you don't need the path of the albums,
|
||||||
|
do NOT use this route.
|
||||||
|
"""
|
||||||
|
return filters.filter(Album.objects.viewable_by(self.context.request.user))
|
||||||
|
|
||||||
|
|
||||||
@api_controller("/sas/picture")
|
@api_controller("/sas/picture")
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from annotated_types import MinLen
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from ninja import FilterSchema, ModelSchema, Schema
|
from ninja import FilterSchema, ModelSchema, Schema
|
||||||
from pydantic import Field, NonNegativeInt
|
from pydantic import Field, NonNegativeInt
|
||||||
@ -9,7 +11,37 @@ from core.schemas import SimpleUserSchema, UserProfileSchema
|
|||||||
from sas.models import Album, Picture, PictureModerationRequest
|
from sas.models import Album, Picture, PictureModerationRequest
|
||||||
|
|
||||||
|
|
||||||
|
class AlbumFilterSchema(FilterSchema):
|
||||||
|
search: Annotated[str, MinLen(1)] | None = Field(None, q="name__icontains")
|
||||||
|
before_date: datetime | None = Field(None, q="event_date__lte")
|
||||||
|
after_date: datetime | None = Field(None, q="event_date__gte")
|
||||||
|
parent_id: int | None = Field(None, q="parent_id")
|
||||||
|
|
||||||
|
|
||||||
class AlbumSchema(ModelSchema):
|
class AlbumSchema(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = Album
|
||||||
|
fields = ["id", "name", "is_moderated"]
|
||||||
|
|
||||||
|
thumbnail: str | None
|
||||||
|
sas_url: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_thumbnail(obj: Album) -> str | None:
|
||||||
|
# Album thumbnails aren't stored in `Album.thumbnail` but in `Album.file`
|
||||||
|
# Don't ask me why.
|
||||||
|
if not obj.file:
|
||||||
|
return None
|
||||||
|
return obj.get_download_url()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_sas_url(obj: Album) -> str:
|
||||||
|
return obj.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
class AlbumAutocompleteSchema(ModelSchema):
|
||||||
|
"""Schema to use on album autocomplete input field."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Album
|
model = Album
|
||||||
fields = ["id", "name"]
|
fields = ["id", "name"]
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
import { paginated } from "#core:utils/api";
|
import { paginated } from "#core:utils/api";
|
||||||
import { History, initialUrlParams, updateQueryString } from "#core:utils/history";
|
import { History, initialUrlParams, updateQueryString } from "#core:utils/history";
|
||||||
import {
|
import {
|
||||||
|
type AlbumFetchAlbumData,
|
||||||
|
type AlbumSchema,
|
||||||
type PictureSchema,
|
type PictureSchema,
|
||||||
type PicturesFetchPicturesData,
|
type PicturesFetchPicturesData,
|
||||||
|
albumFetchAlbum,
|
||||||
picturesFetchPictures,
|
picturesFetchPictures,
|
||||||
} from "#openapi";
|
} from "#openapi";
|
||||||
|
|
||||||
interface AlbumConfig {
|
interface AlbumPicturesConfig {
|
||||||
albumId: number;
|
albumId: number;
|
||||||
maxPageSize: number;
|
maxPageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SubAlbumsConfig {
|
||||||
|
parentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
Alpine.data("pictures", (config: AlbumConfig) => ({
|
Alpine.data("pictures", (config: AlbumPicturesConfig) => ({
|
||||||
pictures: [] as PictureSchema[],
|
pictures: [] as PictureSchema[],
|
||||||
page: Number.parseInt(initialUrlParams.get("page")) || 1,
|
page: Number.parseInt(initialUrlParams.get("page")) || 1,
|
||||||
pushstate: History.Push /* Used to avoid pushing a state on a back action */,
|
pushstate: History.Push /* Used to avoid pushing a state on a back action */,
|
||||||
@ -23,6 +30,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.$watch("page", () => {
|
this.$watch("page", () => {
|
||||||
updateQueryString("page", this.page === 1 ? null : this.page, this.pushstate);
|
updateQueryString("page", this.page === 1 ? null : this.page, this.pushstate);
|
||||||
this.pushstate = History.Push;
|
this.pushstate = History.Push;
|
||||||
|
this.fetchPictures();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("popstate", () => {
|
||||||
@ -30,7 +38,6 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.page =
|
this.page =
|
||||||
Number.parseInt(new URLSearchParams(window.location.search).get("page")) || 1;
|
Number.parseInt(new URLSearchParams(window.location.search).get("page")) || 1;
|
||||||
});
|
});
|
||||||
this.config = config;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getPage(page: number) {
|
getPage(page: number) {
|
||||||
@ -43,11 +50,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
async fetchPictures() {
|
async fetchPictures() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.pictures = await paginated(picturesFetchPictures, {
|
this.pictures = await paginated(picturesFetchPictures, {
|
||||||
query: {
|
// biome-ignore lint/style/useNamingConvention: API is in snake_case
|
||||||
// biome-ignore lint/style/useNamingConvention: API is in snake_case
|
query: { album_id: config.albumId },
|
||||||
album_id: config.albumId,
|
} as PicturesFetchPicturesData);
|
||||||
} as PicturesFetchPicturesData["query"],
|
|
||||||
});
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -55,4 +60,22 @@ document.addEventListener("alpine:init", () => {
|
|||||||
return Math.ceil(this.pictures.length / config.maxPageSize);
|
return Math.ceil(this.pictures.length / config.maxPageSize);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Alpine.data("albums", (config: SubAlbumsConfig) => ({
|
||||||
|
albums: [] as AlbumSchema[],
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.fetchAlbums();
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchAlbums() {
|
||||||
|
this.loading = true;
|
||||||
|
this.albums = await paginated(albumFetchAlbum, {
|
||||||
|
// biome-ignore lint/style/useNamingConvention: API is snake_case
|
||||||
|
query: { parent_id: config.parentId },
|
||||||
|
} as AlbumFetchAlbumData);
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { AjaxSelect } from "#core:core/components/ajax-select-base";
|
|||||||
import { registerComponent } from "#core:utils/web-components";
|
import { registerComponent } from "#core:utils/web-components";
|
||||||
import type { TomOption } from "tom-select/dist/types/types";
|
import type { TomOption } from "tom-select/dist/types/types";
|
||||||
import type { escape_html } from "tom-select/dist/types/utils";
|
import type { escape_html } from "tom-select/dist/types/utils";
|
||||||
import { type AlbumSchema, albumSearchAlbum } from "#openapi";
|
import { type AlbumAutocompleteSchema, albumAutocompleteAlbum } from "#openapi";
|
||||||
|
|
||||||
@registerComponent("album-ajax-select")
|
@registerComponent("album-ajax-select")
|
||||||
export class AlbumAjaxSelect extends AjaxSelect {
|
export class AlbumAjaxSelect extends AjaxSelect {
|
||||||
@ -11,20 +11,20 @@ export class AlbumAjaxSelect extends AjaxSelect {
|
|||||||
protected searchField = ["path", "name"];
|
protected searchField = ["path", "name"];
|
||||||
|
|
||||||
protected async search(query: string): Promise<TomOption[]> {
|
protected async search(query: string): Promise<TomOption[]> {
|
||||||
const resp = await albumSearchAlbum({ query: { search: query } });
|
const resp = await albumAutocompleteAlbum({ query: { search: query } });
|
||||||
if (resp.data) {
|
if (resp.data) {
|
||||||
return resp.data.results;
|
return resp.data.results;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderOption(item: AlbumSchema, sanitize: typeof escape_html) {
|
protected renderOption(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) {
|
||||||
return `<div class="select-item">
|
return `<div class="select-item">
|
||||||
<span class="select-item-text">${sanitize(item.path)}</span>
|
<span class="select-item-text">${sanitize(item.path)}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderItem(item: AlbumSchema, sanitize: typeof escape_html) {
|
protected renderItem(item: AlbumAutocompleteSchema, sanitize: typeof escape_html) {
|
||||||
return `<span>${sanitize(item.path)}</span>`;
|
return `<span>${sanitize(item.path)}</span>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.pictures = await paginated(picturesFetchPictures, {
|
this.pictures = await paginated(picturesFetchPictures, {
|
||||||
query: {
|
// biome-ignore lint/style/useNamingConvention: from python api
|
||||||
// biome-ignore lint/style/useNamingConvention: from python api
|
query: { users_identified: [config.userId] },
|
||||||
users_identified: [config.userId],
|
} as PicturesFetchPicturesData);
|
||||||
} as PicturesFetchPicturesData["query"],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.albums = this.pictures.reduce(
|
this.albums = this.pictures.reduce(
|
||||||
(acc: Record<string, PictureSchema[]>, picture: PictureSchema) => {
|
(acc: Record<string, PictureSchema[]>, picture: PictureSchema) => {
|
||||||
|
@ -53,32 +53,43 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if children_albums|length > 0 %}
|
{% if show_albums %}
|
||||||
<h4>{% trans %}Albums{% endtrans %}</h4>
|
<div x-data="albums({ parentId: {{ album.id }} })" class="margin-bottom">
|
||||||
<div class="albums">
|
<h4>{% trans %}Albums{% endtrans %}</h4>
|
||||||
{% for a in children_albums %}
|
<div class="albums" :aria-busy="loading">
|
||||||
{{ display_album(a, is_sas_admin) }}
|
<template x-for="album in albums" :key="album.id">
|
||||||
{% endfor %}
|
<a :href="album.sas_url">
|
||||||
|
<div
|
||||||
|
x-data="{thumbUrl: album.thumbnail || '{{ static("core/img/sas.jpg") }}'}"
|
||||||
|
class="album"
|
||||||
|
:class="{not_moderated: !album.is_moderated}"
|
||||||
|
>
|
||||||
|
<img :src="thumbUrl" :alt="album.name" loading="lazy" />
|
||||||
|
<template x-if="album.is_moderated">
|
||||||
|
<div class="text" x-text="album.name"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!album.is_moderated">
|
||||||
|
<div class="overlay"> </div>
|
||||||
|
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
{% if edit_mode %}
|
||||||
|
<input type="checkbox" name="file_list" :value="album.id">
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div x-data="pictures({
|
<div x-data="pictures({ albumId: {{ album.id }}, maxPageSize: {{ settings.SITH_SAS_IMAGES_PER_PAGE }} })">
|
||||||
albumId: {{ album.id }},
|
|
||||||
maxPageSize: {{ settings.SITH_SAS_IMAGES_PER_PAGE }},
|
|
||||||
})">
|
|
||||||
|
|
||||||
{{ download_button(_("Download album")) }}
|
|
||||||
|
|
||||||
<h4>{% trans %}Pictures{% endtrans %}</h4>
|
<h4>{% trans %}Pictures{% endtrans %}</h4>
|
||||||
|
<br>
|
||||||
|
{{ download_button(_("Download album")) }}
|
||||||
<div class="photos" :aria-busy="loading">
|
<div class="photos" :aria-busy="loading">
|
||||||
<template x-for="picture in getPage(page)">
|
<template x-for="picture in getPage(page)">
|
||||||
<a :href="picture.sas_url">
|
<a :href="picture.sas_url">
|
||||||
<div
|
<div class="photo" :class="{not_moderated: !picture.is_moderated}">
|
||||||
class="photo"
|
|
||||||
:class="{not_moderated: !picture.is_moderated}"
|
|
||||||
>
|
|
||||||
<img :src="picture.thumb_url" :alt="picture.name" loading="lazy" />
|
<img :src="picture.thumb_url" :alt="picture.name" loading="lazy" />
|
||||||
<template x-if="!picture.is_moderated">
|
<template x-if="!picture.is_moderated">
|
||||||
<div class="overlay"> </div>
|
<div class="overlay"> </div>
|
||||||
@ -94,7 +105,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
{{ paginate_alpine("page", "nbPages()") }}
|
{{ paginate_alpine("page", "nbPages()") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if is_sas_admin %}
|
{% if is_sas_admin %}
|
||||||
|
@ -228,3 +228,16 @@ class TestPictureModeration(TestSas):
|
|||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert len(res.json()) == 1
|
assert len(res.json()) == 1
|
||||||
assert res.json()[0]["author"]["id"] == self.user_a.id
|
assert res.json()[0]["author"]["id"] == self.user_a.id
|
||||||
|
|
||||||
|
|
||||||
|
class TestAlbumSearch(TestSas):
|
||||||
|
def test_num_queries(self):
|
||||||
|
"""Check the number of queries is stable"""
|
||||||
|
self.client.force_login(subscriber_user.make())
|
||||||
|
cache.clear()
|
||||||
|
with self.assertNumQueries(7):
|
||||||
|
# - 2 for authentication
|
||||||
|
# - 3 to check permissions
|
||||||
|
# - 1 for pagination
|
||||||
|
# - 1 for the actual results
|
||||||
|
self.client.get(reverse("api:search-album"))
|
||||||
|
@ -186,10 +186,10 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
|
|||||||
kwargs["clipboard"] = SithFile.objects.filter(
|
kwargs["clipboard"] = SithFile.objects.filter(
|
||||||
id__in=self.request.session["clipboard"]
|
id__in=self.request.session["clipboard"]
|
||||||
)
|
)
|
||||||
kwargs["children_albums"] = list(
|
kwargs["show_albums"] = (
|
||||||
Album.objects.viewable_by(self.request.user)
|
Album.objects.viewable_by(self.request.user)
|
||||||
.filter(parent_id=self.object.id)
|
.filter(parent_id=self.object.id)
|
||||||
.order_by("-date")
|
.exists()
|
||||||
)
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from core.views.widgets.ajax_select import (
|
|||||||
AutoCompleteSelectMultiple,
|
AutoCompleteSelectMultiple,
|
||||||
)
|
)
|
||||||
from sas.models import Album
|
from sas.models import Album
|
||||||
from sas.schemas import AlbumSchema
|
from sas.schemas import AlbumAutocompleteSchema
|
||||||
|
|
||||||
_js = ["bundled/sas/components/ajax-select-index.ts"]
|
_js = ["bundled/sas/components/ajax-select-index.ts"]
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ _js = ["bundled/sas/components/ajax-select-index.ts"]
|
|||||||
class AutoCompleteSelectAlbum(AutoCompleteSelect):
|
class AutoCompleteSelectAlbum(AutoCompleteSelect):
|
||||||
component_name = "album-ajax-select"
|
component_name = "album-ajax-select"
|
||||||
model = Album
|
model = Album
|
||||||
adapter = TypeAdapter(list[AlbumSchema])
|
adapter = TypeAdapter(list[AlbumAutocompleteSchema])
|
||||||
|
|
||||||
js = _js
|
js = _js
|
||||||
|
|
||||||
@ -21,6 +21,6 @@ class AutoCompleteSelectAlbum(AutoCompleteSelect):
|
|||||||
class AutoCompleteSelectMultipleAlbum(AutoCompleteSelectMultiple):
|
class AutoCompleteSelectMultipleAlbum(AutoCompleteSelectMultiple):
|
||||||
component_name = "album-ajax-select"
|
component_name = "album-ajax-select"
|
||||||
model = Album
|
model = Album
|
||||||
adapter = TypeAdapter(list[AlbumSchema])
|
adapter = TypeAdapter(list[AlbumAutocompleteSchema])
|
||||||
|
|
||||||
js = _js
|
js = _js
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"outDir": "./staticfiles/generated/bundled/",
|
"outDir": "./staticfiles/generated/bundled/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"module": "es6",
|
"module": "esnext",
|
||||||
"target": "es6",
|
"target": "es2022",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
@ -12,7 +12,6 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": ["jquery", "alpinejs"],
|
"types": ["jquery", "alpinejs"],
|
||||||
"lib": ["es7"],
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"#openapi": ["./staticfiles/generated/openapi/client/index.ts"],
|
"#openapi": ["./staticfiles/generated/openapi/client/index.ts"],
|
||||||
"#core:*": ["./core/static/bundled/*"],
|
"#core:*": ["./core/static/bundled/*"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user