-
+
{{ entryData.overview }}
-
Journal
-
-
- État :
-
- Pas encore dans le journal.
-
+
+
+ Dans le Journal
+
+
-
+
+ Pas encore dans le Journal
+
+ Ajouter
+ →
+
+
-
-
@@ -116,13 +199,22 @@
diff --git a/src/components/loading/LoadingBox.vue b/src/components/loading/LoadingBox.vue
index 8a0665a..51a5f3e 100644
--- a/src/components/loading/LoadingBox.vue
+++ b/src/components/loading/LoadingBox.vue
@@ -1,4 +1,45 @@
-
+
+
+
+
+
diff --git a/src/db/schemas/constants.ts b/src/db/schemas/constants.ts
index 40fe09c..f3adf28 100644
--- a/src/db/schemas/constants.ts
+++ b/src/db/schemas/constants.ts
@@ -11,8 +11,6 @@ export const DIARY_ENTRY_STATES = {
TO_FIND: "to_find",
/** Un média déjà vu et à revoir. */
TO_REWATCH: "to_rewatch",
- /** Un média souhaité mais dont l'état est encore flou. */
- TO_SORT: "to_sort",
/** Un média acquis et à regarder. */
TO_WATCH: "to_watch",
/** Un média à l'état inconnu (état par défaut). */
diff --git a/src/libs/apis/clients.ts b/src/libs/apis/clients.ts
index 69bf478..6daa11b 100644
--- a/src/libs/apis/clients.ts
+++ b/src/libs/apis/clients.ts
@@ -1,4 +1,4 @@
-import type { Input } from "@effect/platform/UrlParams";
+import type { Coercible, Input } from "@effect/platform/UrlParams";
import { FetchHttpClient, HttpClient, HttpClientRequest, Url, UrlParams } from "@effect/platform";
import { Effect, pipe } from "effect";
@@ -19,6 +19,14 @@ export const createUrlWithParams = (stringUrl: string) => (params: Input) =>
return Url.setUrlParams(url, urlParams);
});
+export const createUrlWithParameterizedPathAndParams = (stringUrl: string, parameter: Coercible) => (params: Input) =>
+ Effect.gen(function*() {
+ const url: URL = yield* Url.fromString(`${stringUrl}/${String(parameter)}`);
+ const urlParams: UrlParams.UrlParams = UrlParams.fromInput(params);
+
+ return Url.setUrlParams(url, urlParams);
+ });
+
export const createGetHttpRequest = (url: URL): HttpClientRequest.HttpClientRequest =>
pipe(
HttpClientRequest.get(url),
diff --git a/src/libs/apis/routes.ts b/src/libs/apis/routes.ts
index aee5801..7c1fbb5 100644
--- a/src/libs/apis/routes.ts
+++ b/src/libs/apis/routes.ts
@@ -1 +1,2 @@
export const TMDB_ROUTE_SEARCH_MOVIE = "https://api.themoviedb.org/3/search/movie";
+export const TMDB_ROUTE_MOVIE_DETAILS = "https://api.themoviedb.org/3/movie";
diff --git a/src/libs/apis/tmdb/schemas.ts b/src/libs/apis/tmdb/schemas.ts
index e3653ff..020a18a 100644
--- a/src/libs/apis/tmdb/schemas.ts
+++ b/src/libs/apis/tmdb/schemas.ts
@@ -1,5 +1,9 @@
import { Schema } from "effect";
+import { TmdbCompany } from "./schemas/companies";
+import { TmdbCredit } from "./schemas/credits";
+import { TmdbLanguage } from "./schemas/languages";
+
// Requête
export class TmdbMovieSearchQueryParams extends Schema.Class
("TmdbMovieSearchArgs")({
@@ -24,6 +28,14 @@ export class TmdbMovieSearchQueryParams extends Schema.Class("TmdbMovieDetailsQueryParams")({
+ append_to_response: Schema.Literal("credits"),
+ language: Schema.NonEmptyString,
+ movie_id: Schema.NonNegativeInt,
+ })
+{}
+
// Réponse
export class TmdbMovieSearchResponseResult
@@ -51,3 +63,39 @@ export class TmdbMovieSearchResponse extends Schema.Class("TmdbMovieDetailsWithCreditsResponse")({
+ adult: Schema.Boolean,
+ backdrop_path: Schema.Union(Schema.String, Schema.Null),
+ belongs_to_collection: Schema.Unknown,
+ budget: Schema.NonNegativeInt,
+ credits: Schema.Struct({
+ cast: Schema.Array(TmdbCredit),
+ crew: Schema.Array(TmdbCredit),
+ }),
+ genres: Schema.Array(Schema.Struct({
+ id: Schema.NonNegativeInt,
+ name: Schema.NonEmptyString,
+ })),
+ homepage: Schema.String,
+ id: Schema.NonNegativeInt,
+ imdb_id: Schema.Union(Schema.String, Schema.Null),
+ original_language: Schema.String,
+ original_title: Schema.String,
+ overview: Schema.String,
+ popularity: Schema.Number,
+ poster_path: Schema.Union(Schema.String, Schema.Null),
+ production_companies: Schema.Array(TmdbCompany),
+ release_date: Schema.String,
+ revenue: Schema.NonNegativeInt,
+ runtime: Schema.NonNegativeInt,
+ spoken_languages: Schema.Array(TmdbLanguage),
+ status: Schema.NonEmptyString,
+ tagline: Schema.String,
+ title: Schema.NonEmptyString,
+ video: Schema.Boolean,
+ vote_average: Schema.Number,
+ vote_count: Schema.NonNegativeInt,
+ })
+{}
diff --git a/src/libs/apis/tmdb/schemas/companies.ts b/src/libs/apis/tmdb/schemas/companies.ts
new file mode 100644
index 0000000..3f8c907
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/companies.ts
@@ -0,0 +1,22 @@
+import { Brand, Schema } from "effect";
+
+import { CountryIsoCode } from "./countries";
+
+/** L'ID d'une société sous forme de nombre entier positif. */
+export type CompanyId = Brand.Brand<"CompanyId"> & number;
+export const CompanyId = Brand.refined(
+ n => Number.isInteger(n) && n >= 0,
+ n => Brand.error(`${String(n)} doit être un nombre entier positif.`),
+);
+
+/** Entité représentant une société (p. ex. de production). */
+export class TmdbCompany extends Schema.Class("TmdbCompany")({
+ /** ID de la société. */
+ id: Schema.NonNegativeInt.pipe(Schema.fromBrand(CompanyId)),
+ /** Chemin relatif du logo de la société. */
+ logo_path: Schema.Union(Schema.String, Schema.Null),
+ /** Nom de la société. */
+ name: Schema.String,
+ /** Pays d'origine de la société sous forme de code ISO 3166-1. */
+ origin_country: Schema.Union(Schema.String.pipe(Schema.fromBrand(CountryIsoCode)), Schema.String),
+}) {}
diff --git a/src/libs/apis/tmdb/schemas/countries.ts b/src/libs/apis/tmdb/schemas/countries.ts
new file mode 100644
index 0000000..90d7d1f
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/countries.ts
@@ -0,0 +1,14 @@
+import { Brand, Schema } from "effect";
+
+// TODO: Vérifier le code ISO dans une liste.
+/** Le code ISO 3166-1 (à deux lettres) d'un pays. */
+export type CountryIsoCode = Brand.Brand & string;
+export const CountryIsoCode = Brand.refined(
+ s => s.length == 2,
+ s => Brand.error(`${s} ne correspond pas au code ISO 3166-1 connu d'un pays.`),
+);
+
+export class TmdbCountry extends Schema.Class("TmdbCountry")({
+ iso_3166_1: Schema.NonEmptyString.pipe(Schema.fromBrand(CountryIsoCode)),
+ name: Schema.String,
+}) {}
diff --git a/src/libs/apis/tmdb/schemas/credits.ts b/src/libs/apis/tmdb/schemas/credits.ts
new file mode 100644
index 0000000..1cb0dc1
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/credits.ts
@@ -0,0 +1,66 @@
+import type { Values } from "@/libs/utils/types";
+
+import { Brand, Match, Schema } from "effect";
+
+export const PERSON_GENDER_VALUES = {
+ FEMALE: "female",
+ MALE: "male",
+ NON_BINARY: "non-binary",
+ OTHER: "other",
+} as const;
+export type PersonGender = Values;
+
+const GenderStringFromInteger = Schema.transform(
+ Schema.Literal(0, 1, 2, 3),
+ Schema.Enums(PERSON_GENDER_VALUES),
+ {
+ decode: (number: 0 | 1 | 2 | 3) =>
+ Match.value(number).pipe(
+ Match.when(0, () => PERSON_GENDER_VALUES.OTHER),
+ Match.when(1, () => PERSON_GENDER_VALUES.FEMALE),
+ Match.when(2, () => PERSON_GENDER_VALUES.MALE),
+ Match.when(3, () => PERSON_GENDER_VALUES.NON_BINARY),
+ Match.exhaustive,
+ ),
+ encode: (gender: PersonGender) =>
+ Match.value(gender).pipe(
+ Match.when(PERSON_GENDER_VALUES.OTHER, () => 0 as const),
+ Match.when(PERSON_GENDER_VALUES.FEMALE, () => 1 as const),
+ Match.when(PERSON_GENDER_VALUES.MALE, () => 2 as const),
+ Match.when(PERSON_GENDER_VALUES.NON_BINARY, () => 3 as const),
+ Match.exhaustive,
+ ),
+ strict: true,
+ },
+);
+
+/** L'ID d'une personne sous forme de nombre entier positif. */
+export type PersonId = Brand.Brand<"PersonId"> & number;
+export const PersonId = Brand.refined(
+ n => Number.isInteger(n) && n >= 0,
+ n => Brand.error(`${String(n)} doit être un nombre entier positif.`),
+);
+
+/** Le nom d'une personne. */
+export type PersonName = Brand.Brand<"PersonName"> & string;
+export const PersonName = Brand.refined(
+ s => s.length > 2,
+ s => Brand.error(`${String(s)} doit contenir au moins 2 lettres.`),
+);
+
+export class TmdbCredit extends Schema.Class("TmdbCredit")({
+ adult: Schema.Boolean,
+ cast_id: Schema.NonNegativeInt.pipe(Schema.optional),
+ character: Schema.NonEmptyString.pipe(Schema.optional),
+ credit_id: Schema.String,
+ department: Schema.String.pipe(Schema.optional),
+ gender: GenderStringFromInteger,
+ id: Schema.NonNegativeInt.pipe(Schema.fromBrand(PersonId)),
+ job: Schema.NonEmptyString.pipe(Schema.optional),
+ known_for_department: Schema.String,
+ name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
+ order: Schema.NonNegativeInt.pipe(Schema.optional),
+ original_name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
+ popularity: Schema.Number,
+ profile_path: Schema.Union(Schema.String, Schema.Null),
+}) {}
diff --git a/src/libs/apis/tmdb/schemas/languages.ts b/src/libs/apis/tmdb/schemas/languages.ts
new file mode 100644
index 0000000..a13645b
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/languages.ts
@@ -0,0 +1,13 @@
+import { Brand, Schema } from "effect";
+
+export type LanguageIsoCode = Brand.Brand & string;
+export const LanguageIsoCode = Brand.refined(
+ s => /[a-z]{2}/.test(s),
+ s => Brand.error(`${s} ne correspond pas au code ISO 639-1 connu d'une langue.`),
+);
+
+export class TmdbLanguage extends Schema.Class("TmdbLanguage")({
+ english_name: Schema.NonEmptyString,
+ iso_639_1: Schema.NonEmptyString.pipe(Schema.fromBrand(LanguageIsoCode)),
+ name: Schema.NonEmptyString,
+}) {}
diff --git a/src/machines/EditEntryDialog.ts b/src/machines/EditEntryDialog.ts
new file mode 100644
index 0000000..d0f6ffc
--- /dev/null
+++ b/src/machines/EditEntryDialog.ts
@@ -0,0 +1,53 @@
+import type { MergedTmdbLocalData } from "@/libs/search/schemas";
+
+import { not, setup } from "xstate";
+
+export const editEntryDialogMachine = setup({
+ guards: {
+ /** Est-ce que l'ouvre présentée possède une entrée dans le Journal ? */
+ hasDiaryEntry: ({ context }) => context.entryData?.entryId !== undefined,
+ },
+ types: {
+ context: {} as { entryData: MergedTmdbLocalData | undefined },
+ input: {} as { entryData: MergedTmdbLocalData },
+ },
+})
+ .createMachine({
+ context: ({ input }) => ({
+ entryData: input.entryData,
+ }),
+ id: "editEntryDialog",
+ initial: "blankOrEntry",
+ states: {
+ blankOrEntry: {
+ always: [
+ {
+ guard: not("hasDiaryEntry"),
+ target: "viewing.blank",
+ },
+ {
+ guard: "hasDiaryEntry",
+ target: "viewing.entry",
+ },
+ ],
+ description: "État initial « fantôme » ne servant qu'à déterminer si le média est dans le Journal ou non.",
+ },
+ editing: {
+ description: "État de finalisation.",
+ type: "final",
+ },
+ viewing: {
+ description: "État de lecture seule des données de l'entrée.",
+ initial: "blank",
+ on: {
+ editWanted: {
+ target: "editing",
+ },
+ },
+ states: {
+ blank: {},
+ entry: {},
+ },
+ },
+ },
+ });
diff --git a/src/pages/AddEntryPage.vue b/src/pages/AddEntryPage.vue
new file mode 100644
index 0000000..a8a6302
--- /dev/null
+++ b/src/pages/AddEntryPage.vue
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/pages/SearchPage.vue b/src/pages/SearchPage.vue
index a3b45de..e09f148 100644
--- a/src/pages/SearchPage.vue
+++ b/src/pages/SearchPage.vue
@@ -23,6 +23,7 @@
import { Effect, pipe, Schema } from "effect";
import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
+ import { useEntryStore } from "@/stores/entry.ts";
// États
@@ -54,6 +55,9 @@
const editedEntry: Ref = ref();
+ // Magasins
+ const entryStore = useEntryStore();
+
// Fonctions
let updateUrlQuery = updateUrlQueryFromFormData(router, form.value);
@@ -148,33 +152,32 @@
effect => getOrUndefined(effect),
);
- // TODO: Uniformiser la casse des propriétés.
- searchData.value.set(
- result.id,
- yield* Schema.decodeUnknown(MergedTmdbLocalData)(
- {
- artWorkId: artWork?.id,
- artWorkMediumTypeId: artWork?.mediumTypeId,
- entryAppreciation: entry?.appreciation,
- entryCommentary: entry?.commentary,
- entryDateCreated: entry?.dateCreated,
- entryDateModified: entry?.dateModified,
- entryDateObtained: entry?.dateObtained,
- entryId: entry?.id,
- entryStateId: entry?.stateId,
- genreIds: result.genre_ids,
- originalLanguage: result.original_language,
- originalResultIndex: index,
- originalTitle: result.original_title,
- overview: result.overview,
- popularity: result.popularity,
- posterUrl: result.poster_path,
- releaseDate: result.release_date,
- title: result.title,
- tmdbId: result.id,
- } satisfies MergedTmdbLocalData,
- ),
+ const newEntry = yield* Schema.decodeUnknown(MergedTmdbLocalData)(
+ {
+ artWorkId: artWork?.id,
+ artWorkMediumTypeId: artWork?.mediumTypeId,
+ entryAppreciation: entry?.appreciation,
+ entryCommentary: entry?.commentary,
+ entryDateCreated: entry?.dateCreated,
+ entryDateModified: entry?.dateModified,
+ entryDateObtained: entry?.dateObtained,
+ entryId: entry?.id,
+ entryStateId: entry?.stateId,
+ genreIds: result.genre_ids,
+ originalLanguage: result.original_language,
+ originalResultIndex: index,
+ originalTitle: result.original_title,
+ overview: result.overview,
+ popularity: result.popularity,
+ posterUrl: result.poster_path,
+ releaseDate: result.release_date,
+ title: result.title,
+ tmdbId: result.id,
+ } satisfies MergedTmdbLocalData,
);
+
+ searchData.value.set(result.id, newEntry);
+ entryStore.setEntry(newEntry);
}).pipe(Effect.runPromise)
);
}));
diff --git a/src/router/index.ts b/src/router/index.ts
index 1372f30..7c36b72 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -29,6 +29,18 @@ const router: Router = createRouter({
name: "Search",
path: "/search",
},
+ {
+ component: () => import("@/pages/AddEntryPage.vue"),
+ meta: { title: "Ajouter une entrée au Journal" },
+ name: "AddEntry",
+ path: "/entry/add",
+ },
+ {
+ component: () => import("@/pages/HomePage.vue"),
+ meta: { title: "Voir une entrée du Journal" },
+ name: "ViewEntry",
+ path: "/entry/:entryId",
+ },
],
});
diff --git a/src/router/typed-routes.d.ts b/src/router/typed-routes.d.ts
index 480896c..37e82ef 100644
--- a/src/router/typed-routes.d.ts
+++ b/src/router/typed-routes.d.ts
@@ -3,6 +3,13 @@ import type { RouteRecordInfo } from "vue-router";
import "vue-router";
export interface RouteNamedMap {
+ AddEntry: RouteRecordInfo<
+ "AddEntry",
+ "/entry/add",
+ Record,
+ Record,
+ { title: string }
+ >;
Home: RouteRecordInfo<
"Home",
"/",
@@ -24,6 +31,13 @@ export interface RouteNamedMap {
Record,
{ title: string }
>;
+ ViewEntry: RouteRecordInfo<
+ "ViewEntry",
+ "/entry/:entryId",
+ { entryId: number },
+ { entryId: number },
+ { title: string }
+ >;
}
// Last, you will need to augment the Vue Router types with this map of routes
diff --git a/src/services/read-api.ts b/src/services/read-api.ts
index 71f80ba..0221c8e 100644
--- a/src/services/read-api.ts
+++ b/src/services/read-api.ts
@@ -14,8 +14,6 @@ export class ReadApi extends Effect.Service()("ReadApi", {
yield* Effect.logDebug("--- READ-API ---");
- // TODO: Implémenter ReadApiError pour des requêtes avec zéro retour.
-
return {
getAllEntries: () => query(_ => _.select().from(DiaryEntries)),
getArtworkByTmdbId: (tmdbId: number) =>
diff --git a/src/services/tmdb-api.ts b/src/services/tmdb-api.ts
index c5b9408..a9eeb95 100644
--- a/src/services/tmdb-api.ts
+++ b/src/services/tmdb-api.ts
@@ -1,6 +1,16 @@
-import { createGetHttpRequest, createUrlWithParams, DebugHttpClient as Dhc } from "@/libs/apis/clients";
-import { TMDB_ROUTE_SEARCH_MOVIE } from "@/libs/apis/routes";
-import { type TmdbMovieSearchQueryParams, TmdbMovieSearchResponse } from "@/libs/apis/tmdb/schemas";
+import {
+ createGetHttpRequest,
+ createUrlWithParameterizedPathAndParams,
+ createUrlWithParams,
+ DebugHttpClient as Dhc,
+} from "@/libs/apis/clients";
+import { TMDB_ROUTE_MOVIE_DETAILS, TMDB_ROUTE_SEARCH_MOVIE } from "@/libs/apis/routes";
+import {
+ TmdbMovieDetailsQueryParams,
+ TmdbMovieDetailsWithCreditsResponse,
+ type TmdbMovieSearchQueryParams,
+ TmdbMovieSearchResponse,
+} from "@/libs/apis/tmdb/schemas";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform";
import { Data, Effect, pipe } from "effect";
@@ -14,6 +24,20 @@ export class TmdbApi extends Effect.Service()("TmdbApi", {
const client = yield* Dhc;
return {
+ getMovieDetails: (args: TmdbMovieDetailsQueryParams) =>
+ pipe(
+ createUrlWithParameterizedPathAndParams(TMDB_ROUTE_MOVIE_DETAILS, args.movie_id)({
+ append_to_response: args.append_to_response,
+ language: args.language,
+ }),
+ Effect.andThen((url: URL) => createGetHttpRequest(url)),
+ Effect.andThen((request: HttpClientRequest.HttpClientRequest) => client.execute(request)),
+ HttpClient.withTracerPropagation(false),
+ Effect.andThen((response: HttpClientResponse.HttpClientResponse) =>
+ HttpClientResponse.schemaBodyJson(TmdbMovieDetailsWithCreditsResponse)(response)
+ ),
+ Effect.scoped,
+ ),
searchMovie: (args: TmdbMovieSearchQueryParams) =>
pipe(
createUrlWithParams(TMDB_ROUTE_SEARCH_MOVIE)({ ...args }),
diff --git a/src/stores/entry.ts b/src/stores/entry.ts
new file mode 100644
index 0000000..d806d7d
--- /dev/null
+++ b/src/stores/entry.ts
@@ -0,0 +1,20 @@
+import type { TmdbMovieDetailsWithCreditsResponse } from "@/libs/apis/tmdb/schemas";
+import type { MergedTmdbLocalData } from "@/libs/search/schemas";
+
+import { defineStore } from "pinia";
+import { type Ref, ref } from "vue";
+
+export const useEntryStore = defineStore("entry", () => {
+ const entry: Ref = ref();
+ const tmdbData: Ref = ref();
+
+ const setEntry = (newEntry: MergedTmdbLocalData | undefined): void => {
+ entry.value = newEntry;
+ };
+
+ const setTmdbData = (newData: TmdbMovieDetailsWithCreditsResponse | undefined): void => {
+ tmdbData.value = newData;
+ };
+
+ return { entry, setEntry, setTmdbData, tmdbData };
+});
diff --git a/src/styles/base/base.css b/src/styles/base/base.css
index 71d26b2..adc72e5 100755
--- a/src/styles/base/base.css
+++ b/src/styles/base/base.css
@@ -67,7 +67,7 @@ body {
navigation: auto;
}
-@media (prefers-reduced-motion) {
+/* @media (prefers-reduced-motion) {
*, *::before, *::after {
scroll-behavior: auto !important;
transition: none !important;
@@ -77,4 +77,4 @@ body {
@view-transition {
navigation: none !important;
}
-}
+} */
diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css
index 70c7789..8a4ba58 100755
--- a/src/styles/base/variables.css
+++ b/src/styles/base/variables.css
@@ -11,6 +11,15 @@
--color-tertiary: dimgrey;
--color-quartary: #be2727;
+ /* *
+ * Depuis saumon :
+ * Complémentaire : #72ecfa
+ * Brun foncé : #502924
+ * Brun clair : #7a3f38
+ * Rouge brun : #a5544b
+ * Rouge marron : #cf6a5f
+ */
+
/* Hauteurs de ligne */
--line-height-comfortable: 1.4;
--line-height-compact: 1.1;
@@ -34,7 +43,11 @@
--s5: calc(var(--s4) * var(--ratio));
/* Arrière-plans à motifs */
- --bg25-secondary: repeating-conic-gradient(var(--color-tertiary) 0% 25%, transparent 0% 100%) 1px
+ --bg25-tertiary: repeating-conic-gradient(var(--color-tertiary) 0% 25%, transparent 0% 100%) 1px
+ 0.5px / 2px 2px;
+ --bg75-tertiary: repeating-conic-gradient(var(--color-tertiary) 0% 75%, transparent 0% 100%) 1px
+ 0.5px / 2px 2px;
+ --bg25-secondary: repeating-conic-gradient(var(--color-secondary) 0% 25%, transparent 0% 100%) 1px
0.5px / 2px 2px;
--bg75-secondary: repeating-conic-gradient(var(--color-secondary) 0% 75%, transparent 0% 100%) 1px
0.5px / 2px 2px;
diff --git a/src/styles/components/imposter-box.css b/src/styles/components/imposter-box.css
index 6b222c3..0954530 100644
--- a/src/styles/components/imposter-box.css
+++ b/src/styles/components/imposter-box.css
@@ -1,6 +1,6 @@
/* Élément conteneur de la fenêtre modale. */
.dialog {
- --dialog-overlay-background: var(--bg25-secondary);
+ --dialog-overlay-background: var(--bg25-tertiary);
--dialog-background-color: var(--root-background-color);
--dialog-border-color: var(--root-text-color);
--dialog-shadow-color: var(--root-text-color);
@@ -37,14 +37,16 @@
border-block-end: 1px solid var(--dialog-border-color);
h2 {
+ font-family: "BRKLY", monospace;
padding-inline: var(--s-1);
- font-size: var(--s1);
+ text-transform: uppercase;
+ font-size: var(--s0);
+ font-weight: var(--brkly-font-weight-semibold);
}
button {
padding: var(--s-1);
- font-family: Banquise, monospace;
- font-weight: 500;
+ font-weight: var(--brkly-font-weight-semibold);
}
}
diff --git a/src/styles/themes/default/animations.css b/src/styles/themes/default/animations.css
index be0e837..8dd9d7b 100644
--- a/src/styles/themes/default/animations.css
+++ b/src/styles/themes/default/animations.css
@@ -13,3 +13,25 @@
opacity: 1;
}
}
+
+@keyframes loading {
+ 0% {
+ content: "";
+ }
+
+ 25% {
+ content: ".";
+ }
+
+ 50% {
+ content: "..";
+ }
+
+ 75% {
+ content: "...";
+ }
+
+ 100% {
+ content: "";
+ }
+}
diff --git a/src/styles/themes/default/headings.css b/src/styles/themes/default/headings.css
index 891f9e5..9690ab1 100755
--- a/src/styles/themes/default/headings.css
+++ b/src/styles/themes/default/headings.css
@@ -6,16 +6,19 @@ h1 {
font-family: var(--headings-font-family);
font-size: var(--s3);
letter-spacing: var(--letter-spacing-medium);
+ line-height: var(--line-height-compact);
}
h2 {
font-family: var(--headings-font-family);
font-size: var(--s2);
letter-spacing: var(--letter-spacing-small);
+ line-height: var(--line-height-compact);
}
h3 {
font-family: var(--headings-font-family);
font-size: var(--s1);
letter-spacing: var(--letter-spacing-small);
+ line-height: var(--line-height-compact);
}
diff --git a/vite.config.mts b/vite.config.mts
index 88adb28..46f543e 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -1,4 +1,5 @@
import vue from "@vitejs/plugin-vue";
+import fs from "node:fs";
import { defineConfig } from "vite";
export default defineConfig({
@@ -7,7 +8,13 @@ export default defineConfig({
emptyOutDir: true,
outDir: "dist",
reportCompressedSize: true,
- rollupOptions: { output: { compact: true, format: "esm", validate: true } },
+ rollupOptions: {
+ output: {
+ compact: true,
+ format: "esm",
+ validate: true,
+ },
+ },
sourcemap: false,
},
cacheDir: ".cache/vite",
@@ -16,26 +23,27 @@ export default defineConfig({
optimizeDeps: { exclude: ["sqlocal"] },
plugins: [
vue(),
- {
- configureServer: server => {
- server.middlewares.use((_req, res, next) => {
- res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
- res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
- res.setHeader("Access-Control-Allow-Origin", "http://localhost:4321");
- res.setHeader("Access-Control-Allow-Credentials", "true");
- res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
- res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
- res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
- next();
- });
- },
- name: "configure-response-headers",
- },
],
resolve: {
alias: {
"@": Bun.fileURLToPath(new URL("./src", import.meta.url)),
},
},
+ server: {
+ allowedHosts: ["journal.site"],
+ cors: { origin: ["https://journal.site"] },
+ hmr: {
+ clientPort: 5173,
+ protocol: "wss",
+ },
+ host: "0.0.0.0",
+ https: {
+ cert: fs.readFileSync(".tls/cert.pem"),
+ key: fs.readFileSync(".tls/key.pem"),
+ },
+ port: 5173,
+ proxy: {},
+ strictPort: true,
+ },
worker: { format: "es" },
});