This commit is contained in:
Sergey Yarkov 2025-11-24 16:33:27 +03:00
parent e03cd19a78
commit 5dcb790ff1
17 changed files with 356 additions and 1001 deletions

View File

@ -1,658 +0,0 @@
export interface Article {
/** @primaryKey */
id: number;
/** @required */
translations: ArticleTranslation[] | null;
}
export interface ArticleTag {
/** @primaryKey */
id: number;
article_id?: Article | string | null;
tag_id?: Tag | string | null;
}
export interface ArticleTranslation {
/** @primaryKey */
id: number;
article_id?: Article | string | null;
/** @required */
languages_code: Language | string;
/** @required */
title: string;
/** @required */
description: string;
/** @required */
slug: string;
/** @required */
content: string;
/** @required */
status: 'published' | 'draft' | 'archived';
/** @required */
author: DirectusUser | string;
/** @required */
pub_date: string;
/** @required */
views: number;
cover_image?: DirectusFile | string | null;
/** @required */
tags: ArticleTranslationsTag[] | string[];
}
export interface ArticleTranslationsTag {
/** @primaryKey */
id: number;
article_translations_id?: ArticleTranslation | string | null;
tag_id?: Tag | string | null;
}
export interface Global {
/** @primaryKey */
id: number;
/** @required */
title: string;
ym_counter?: number | null;
github?: string | null;
telegram?: string | null;
email?: string | null;
/** @required */
recent_articles_limit: number;
translations?: GlobalTranslation[] | null;
}
export interface GlobalTranslation {
/** @primaryKey */
id: number;
global_id?: Global | string | null;
languages_code?: Language | string | null;
description?: string | null;
navigation?: Array<{ name: string; link: string }> | null;
}
export interface HomePage {
/** @primaryKey */
id: number;
translations?: HomePageTranslation[] | null;
}
export interface HomePageTranslation {
/** @primaryKey */
id: number;
home_page_id?: HomePage | string | null;
languages_code?: Language | string | null;
/** @required */
headline: string;
/** @required */
subheadline: string;
/** @required */
description: string;
}
export interface Language {
/** @primaryKey */
code: string;
/** @required */
name: string;
/** @required */
direction: 'ltr' | 'rtl';
}
export interface Project {
/** @primaryKey */
id: number;
/** @required */
translations: ProjectTranslation[] | null;
}
export interface ProjectTranslation {
/** @primaryKey */
id: number;
project_id?: Project | string | null;
languages_code?: Language | string | null;
/** @required */
title: string;
/** @required */
year: string;
/** @required */
description: string;
/** @required */
source_url: string;
}
export interface Tag {
/** @primaryKey */
id: number;
/** @required */
title: string;
}
export interface DirectusAccess {
/** @primaryKey */
id: string;
role?: DirectusRole | string | null;
user?: DirectusUser | string | null;
policy?: DirectusPolicy | string;
sort?: number | null;
}
export interface DirectusActivity {
/** @primaryKey */
id: number;
action?: string;
user?: DirectusUser | string | null;
timestamp?: string;
ip?: string | null;
user_agent?: string | null;
collection?: string;
item?: string;
origin?: string | null;
revisions?: DirectusRevision[] | string[];
}
export interface DirectusCollection {
/** @primaryKey */
collection: string;
icon?: string | null;
note?: string | null;
display_template?: string | null;
hidden?: boolean;
singleton?: boolean;
translations?: Array<{ language: string; translation: string; singular: string; plural: string }> | null;
archive_field?: string | null;
archive_app_filter?: boolean;
archive_value?: string | null;
unarchive_value?: string | null;
sort_field?: string | null;
accountability?: 'all' | 'activity' | null | null;
color?: string | null;
item_duplication_fields?: 'json' | null;
sort?: number | null;
group?: DirectusCollection | string | null;
collapse?: string;
preview_url?: string | null;
versioning?: boolean;
}
export interface DirectusComment {
/** @primaryKey */
id: string;
collection?: DirectusCollection | string;
item?: string;
comment?: string;
date_created?: string | null;
date_updated?: string | null;
user_created?: DirectusUser | string | null;
user_updated?: DirectusUser | string | null;
}
export interface DirectusField {
/** @primaryKey */
id: number;
collection?: DirectusCollection | string;
field?: string;
special?: string[] | null;
interface?: string | null;
options?: 'json' | null;
display?: string | null;
display_options?: 'json' | null;
readonly?: boolean;
hidden?: boolean;
sort?: number | null;
width?: string | null;
translations?: 'json' | null;
note?: string | null;
conditions?: 'json' | null;
required?: boolean | null;
group?: DirectusField | string | null;
validation?: 'json' | null;
validation_message?: string | null;
searchable?: boolean;
}
export interface DirectusFile {
/** @primaryKey */
id: string;
storage?: string;
filename_disk?: string | null;
filename_download?: string;
title?: string | null;
type?: string | null;
folder?: DirectusFolder | string | null;
uploaded_by?: DirectusUser | string | null;
created_on?: string;
modified_by?: DirectusUser | string | null;
modified_on?: string;
charset?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
duration?: number | null;
embed?: string | null;
description?: string | null;
location?: string | null;
tags?: string[] | null;
metadata?: 'json' | null;
focal_point_x?: number | null;
focal_point_y?: number | null;
tus_id?: string | null;
tus_data?: 'json' | null;
uploaded_on?: string | null;
}
export interface DirectusFolder {
/** @primaryKey */
id: string;
name?: string;
parent?: DirectusFolder | string | null;
}
export interface DirectusMigration {
/** @primaryKey */
version: string;
name?: string;
timestamp?: string | null;
}
export interface DirectusPermission {
/** @primaryKey */
id: number;
collection?: string;
action?: string;
permissions?: 'json' | null;
validation?: 'json' | null;
presets?: 'json' | null;
fields?: string[] | null;
policy?: DirectusPolicy | string;
}
export interface DirectusPolicy {
/** @primaryKey */
id: string;
/** @required */
name: string;
icon?: string;
description?: string | null;
ip_access?: string[] | null;
enforce_tfa?: boolean;
admin_access?: boolean;
app_access?: boolean;
permissions?: DirectusPermission[] | string[];
users?: DirectusAccess[] | string[];
roles?: DirectusAccess[] | string[];
}
export interface DirectusPreset {
/** @primaryKey */
id: number;
bookmark?: string | null;
user?: DirectusUser | string | null;
role?: DirectusRole | string | null;
collection?: string | null;
search?: string | null;
layout?: string | null;
layout_query?: 'json' | null;
layout_options?: 'json' | null;
refresh_interval?: number | null;
filter?: 'json' | null;
icon?: string | null;
color?: string | null;
}
export interface DirectusRelation {
/** @primaryKey */
id: number;
many_collection?: string;
many_field?: string;
one_collection?: string | null;
one_field?: string | null;
one_collection_field?: string | null;
one_allowed_collections?: string[] | null;
junction_field?: string | null;
sort_field?: string | null;
one_deselect_action?: string;
}
export interface DirectusRevision {
/** @primaryKey */
id: number;
activity?: DirectusActivity | string;
collection?: string;
item?: string;
data?: 'json' | null;
delta?: 'json' | null;
parent?: DirectusRevision | string | null;
version?: DirectusVersion | string | null;
}
export interface DirectusRole {
/** @primaryKey */
id: string;
/** @required */
name: string;
icon?: string;
description?: string | null;
parent?: DirectusRole | string | null;
children?: DirectusRole[] | string[];
policies?: DirectusAccess[] | string[];
users?: DirectusUser[] | string[];
}
export interface DirectusSession {
/** @primaryKey */
token: string;
user?: DirectusUser | string | null;
expires?: string;
ip?: string | null;
user_agent?: string | null;
share?: DirectusShare | string | null;
origin?: string | null;
next_token?: string | null;
}
export interface DirectusSettings {
/** @primaryKey */
id: number;
project_name?: string;
project_url?: string | null;
project_color?: string;
project_logo?: DirectusFile | string | null;
public_foreground?: DirectusFile | string | null;
public_background?: DirectusFile | string | null;
public_note?: string | null;
auth_login_attempts?: number | null;
auth_password_policy?: null | `/^.{8,}$/` | `/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/` | null;
storage_asset_transform?: 'all' | 'none' | 'presets' | null;
storage_asset_presets?: Array<{ key: string; fit: 'contain' | 'cover' | 'inside' | 'outside'; width: number; height: number; quality: number; withoutEnlargement: boolean; format: 'auto' | 'jpeg' | 'png' | 'webp' | 'tiff' | 'avif'; transforms: 'json' }> | null;
custom_css?: string | null;
storage_default_folder?: DirectusFolder | string | null;
basemaps?: Array<{ name: string; type: 'raster' | 'tile' | 'style'; url: string; tileSize: number; attribution: string }> | null;
mapbox_key?: string | null;
module_bar?: 'json' | null;
project_descriptor?: string | null;
default_language?: string;
custom_aspect_ratios?: Array<{ text: string; value: number }> | null;
public_favicon?: DirectusFile | string | null;
default_appearance?: 'auto' | 'light' | 'dark';
default_theme_light?: string | null;
theme_light_overrides?: 'json' | null;
default_theme_dark?: string | null;
theme_dark_overrides?: 'json' | null;
report_error_url?: string | null;
report_bug_url?: string | null;
report_feature_url?: string | null;
public_registration?: boolean;
public_registration_verify_email?: boolean;
public_registration_role?: DirectusRole | string | null;
public_registration_email_filter?: 'json' | null;
visual_editor_urls?: Array<{ url: string }> | null;
project_id?: string | null;
mcp_enabled?: boolean;
mcp_allow_deletes?: boolean;
mcp_prompts_collection?: string | null;
mcp_system_prompt_enabled?: boolean;
mcp_system_prompt?: string | null;
project_owner?: string | null;
project_usage?: string | null;
org_name?: string | null;
product_updates?: boolean | null;
project_status?: string | null;
}
export interface DirectusUser {
/** @primaryKey */
id: string;
first_name?: string | null;
last_name?: string | null;
email?: string | null;
password?: string | null;
location?: string | null;
title?: string | null;
description?: string | null;
tags?: string[] | null;
avatar?: DirectusFile | string | null;
language?: string | null;
tfa_secret?: string | null;
status?: 'draft' | 'invited' | 'unverified' | 'active' | 'suspended' | 'archived';
role?: DirectusRole | string | null;
token?: string | null;
last_access?: string | null;
last_page?: string | null;
provider?: string;
external_identifier?: string | null;
auth_data?: 'json' | null;
email_notifications?: boolean | null;
appearance?: null | 'auto' | 'light' | 'dark' | null;
theme_dark?: string | null;
theme_light?: string | null;
theme_light_overrides?: 'json' | null;
theme_dark_overrides?: 'json' | null;
text_direction?: 'auto' | 'ltr' | 'rtl';
policies?: DirectusAccess[] | string[];
}
export interface DirectusWebhook {
/** @primaryKey */
id: number;
name?: string;
method?: null;
url?: string;
status?: 'active' | 'inactive';
data?: boolean;
actions?: 'create' | 'update' | 'delete';
collections?: string[];
headers?: Array<{ header: string; value: string }> | null;
was_active_before_deprecation?: boolean;
migrated_flow?: DirectusFlow | string | null;
}
export interface DirectusDashboard {
/** @primaryKey */
id: string;
name?: string;
icon?: string;
note?: string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
color?: string | null;
panels?: DirectusPanel[] | string[];
}
export interface DirectusPanel {
/** @primaryKey */
id: string;
dashboard?: DirectusDashboard | string;
name?: string | null;
icon?: string | null;
color?: string | null;
show_header?: boolean;
note?: string | null;
type?: string;
position_x?: number;
position_y?: number;
width?: number;
height?: number;
options?: 'json' | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
}
export interface DirectusNotification {
/** @primaryKey */
id: number;
timestamp?: string | null;
status?: string | null;
recipient?: DirectusUser | string;
sender?: DirectusUser | string | null;
subject?: string;
message?: string | null;
collection?: string | null;
item?: string | null;
}
export interface DirectusShare {
/** @primaryKey */
id: string;
name?: string | null;
collection?: DirectusCollection | string;
item?: string;
role?: DirectusRole | string | null;
password?: string | null;
user_created?: DirectusUser | string | null;
date_created?: string | null;
date_start?: string | null;
date_end?: string | null;
times_used?: number | null;
max_uses?: number | null;
}
export interface DirectusFlow {
/** @primaryKey */
id: string;
name?: string;
icon?: string | null;
color?: string | null;
description?: string | null;
status?: string;
trigger?: string | null;
accountability?: string | null;
options?: 'json' | null;
operation?: DirectusOperation | string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
operations?: DirectusOperation[] | string[];
}
export interface DirectusOperation {
/** @primaryKey */
id: string;
name?: string | null;
key?: string;
type?: string;
position_x?: number;
position_y?: number;
options?: 'json' | null;
resolve?: DirectusOperation | string | null;
reject?: DirectusOperation | string | null;
flow?: DirectusFlow | string;
date_created?: string | null;
user_created?: DirectusUser | string | null;
}
export interface DirectusTranslation {
/** @primaryKey */
id: string;
/** @required */
language: string;
/** @required */
key: string;
/** @required */
value: string;
}
export interface DirectusVersion {
/** @primaryKey */
id: string;
key?: string;
name?: string | null;
collection?: DirectusCollection | string;
item?: string;
hash?: string | null;
date_created?: string | null;
date_updated?: string | null;
user_created?: DirectusUser | string | null;
user_updated?: DirectusUser | string | null;
delta?: 'json' | null;
}
export interface DirectusExtension {
enabled?: boolean;
/** @primaryKey */
id: string;
folder?: string;
source?: string;
bundle?: string | null;
}
export interface Schema {
article: Article[];
article_tag: ArticleTag[];
article_translations: ArticleTranslation[];
article_translations_tag: ArticleTranslationsTag[];
global: Global;
global_translations: GlobalTranslation[];
home_page: HomePage;
home_page_translations: HomePageTranslation[];
languages: Language[];
project: Project[];
project_translations: ProjectTranslation[];
tag: Tag[];
directus_access: DirectusAccess[];
directus_activity: DirectusActivity[];
directus_collections: DirectusCollection[];
directus_comments: DirectusComment[];
directus_fields: DirectusField[];
directus_files: DirectusFile[];
directus_folders: DirectusFolder[];
directus_migrations: DirectusMigration[];
directus_permissions: DirectusPermission[];
directus_policies: DirectusPolicy[];
directus_presets: DirectusPreset[];
directus_relations: DirectusRelation[];
directus_revisions: DirectusRevision[];
directus_roles: DirectusRole[];
directus_sessions: DirectusSession[];
directus_settings: DirectusSettings;
directus_users: DirectusUser[];
directus_webhooks: DirectusWebhook[];
directus_dashboards: DirectusDashboard[];
directus_panels: DirectusPanel[];
directus_notifications: DirectusNotification[];
directus_shares: DirectusShare[];
directus_flows: DirectusFlow[];
directus_operations: DirectusOperation[];
directus_translations: DirectusTranslation[];
directus_versions: DirectusVersion[];
directus_extensions: DirectusExtension[];
}
export enum CollectionNames {
article = 'article',
article_tag = 'article_tag',
article_translations = 'article_translations',
article_translations_tag = 'article_translations_tag',
global = 'global',
global_translations = 'global_translations',
home_page = 'home_page',
home_page_translations = 'home_page_translations',
languages = 'languages',
project = 'project',
project_translations = 'project_translations',
tag = 'tag',
directus_access = 'directus_access',
directus_activity = 'directus_activity',
directus_collections = 'directus_collections',
directus_comments = 'directus_comments',
directus_fields = 'directus_fields',
directus_files = 'directus_files',
directus_folders = 'directus_folders',
directus_migrations = 'directus_migrations',
directus_permissions = 'directus_permissions',
directus_policies = 'directus_policies',
directus_presets = 'directus_presets',
directus_relations = 'directus_relations',
directus_revisions = 'directus_revisions',
directus_roles = 'directus_roles',
directus_sessions = 'directus_sessions',
directus_settings = 'directus_settings',
directus_users = 'directus_users',
directus_webhooks = 'directus_webhooks',
directus_dashboards = 'directus_dashboards',
directus_panels = 'directus_panels',
directus_notifications = 'directus_notifications',
directus_shares = 'directus_shares',
directus_flows = 'directus_flows',
directus_operations = 'directus_operations',
directus_translations = 'directus_translations',
directus_versions = 'directus_versions',
directus_extensions = 'directus_extensions'
}

View File

@ -1,146 +0,0 @@
import { createDirectus, graphql, staticToken } from "@directus/sdk";
import { DIRECTUS_TOKEN, DIRECTUS_URL } from "astro:env/server";
import type { Article, ArticleTranslation, Schema, Tag } from "./directus-schema";
const directus = createDirectus<Schema>(DIRECTUS_URL)
.with(staticToken(DIRECTUS_TOKEN))
.with(graphql());
export async function fetchArticleTranslationsBySlug(
slug: string
): Promise<ArticleTranslation | null> {
const data = await directus.query<{ article_translations: ArticleTranslation[] }>(`
query Article_translations {
article_translations(
filter: {
slug: {
_eq: "${slug}"
}
}
) {
id
title
tags {
tag_id {
title
}
}
description
slug
content
author {
first_name
last_name
}
pub_date
views
languages_code {
code
name
}
cover_image {
title
filename_disk
}
article_id {
translations {
slug
languages_code {
code
name
}
pub_date
}
}
}
}
`);
if (data.article_translations.length === 0) return null;
return { ...data.article_translations[0] };
}
export async function fetchArticles(): Promise<Article[]> {
const data = await directus.query<{ article: Article[] }>(`
query Article {
article {
translations(
filter: {
status: { _eq: "published" }
}
) {
id
title
tags {
tag_id {
title
}
}
description
slug
pub_date
languages_code {
code
name
}
}
}
}
`);
return data.article;
}
export async function fetchRecentArticles(): Promise<Article[]> {
const data = await directus.query<{ article: Article[] }>(`
query Article {
article {
translations(
filter: {
status: { _eq: "published" }
}
) {
title
cover_image {
id
title
}
slug
pub_date
views
languages_code { code }
}
}
}
`);
return data.article;
}
export async function incArticleTranslationViews(
article: Pick<ArticleTranslation, "id" | "views">
): Promise<number> {
const data = await directus.query<{ update_article_translations_item: { views: number } }>(`
mutation Update_article_translations_item {
update_article_translations_item(id: ${article.id}, data: { views: ${article.views + 1} }) {
views
}
}
`);
return data.update_article_translations_item.views;
}
export async function fetchTags(): Promise<Tag[]> {
const data = await directus.query<{ tag: Tag[] }>(`
query Tag {
tag {
id
title
}
}
`);
return data.tag;
}
export default directus;

190
package-lock.json generated
View File

@ -14,7 +14,6 @@
"@astrojs/rss": "^4.0.13",
"@astrojs/sitemap": "^3.6.0",
"@astrojs/solid-js": "^5.1.3",
"@directus/sdk": "^20.1.1",
"@iconify-json/mdi": "^1.2.3",
"astro": "^5.15.8",
"astro-icon": "^1.1.5",
@ -44,7 +43,6 @@
"@types/html-escaper": "^3.0.4",
"@types/mime-types": "^2.1.4",
"@types/remark-prism": "^1.3.7",
"directus-sdk-typegen": "^0.2.1",
"dotenv": "^17.2.3",
"sass": "^1.68.0",
"typescript": "^5.9.3"
@ -381,6 +379,17 @@
}
}
},
"node_modules/@astrojs/solid-js/node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@astrojs/solid-js/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@ -403,7 +412,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -416,7 +424,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@ -584,7 +591,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -867,18 +873,6 @@
"node": ">=18"
}
},
"node_modules/@directus/sdk": {
"version": "20.1.1",
"resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-20.1.1.tgz",
"integrity": "sha512-cCWvxDRKOygVHBkKA7l1I4O4niomK8SKR/+Ul1L9NFF1K2zAf3jRqhjc7JavCECOiiAWME/s2wYatNFqFFh0gQ==",
"license": "MIT",
"engines": {
"node": ">=22"
},
"funding": {
"url": "https://github.com/directus/directus?sponsor=1"
}
},
"node_modules/@emmetio/abbreviation": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
@ -1840,6 +1834,18 @@
}
}
},
"node_modules/@graphql-codegen/cli/node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@graphql-codegen/cli/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -2388,7 +2394,6 @@
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
@ -3487,7 +3492,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
@ -5544,7 +5548,6 @@
"resolved": "https://registry.npmjs.org/astro/-/astro-5.15.8.tgz",
"integrity": "sha512-QiiRnNPdxCcAGO2UlO07o+QeGgRfEC5Dlm0x35WPB/ixFK1T2bsNB6KaXri70cVkYY1GYgoRtrWv3HCR26o9aw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@astrojs/compiler": "^2.13.0",
"@astrojs/internal-helpers": "0.7.4",
@ -5636,6 +5639,17 @@
"@iconify/utils": "^2.1.30"
}
},
"node_modules/astro/node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/astro/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@ -5679,7 +5693,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -5846,7 +5859,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@ -6190,7 +6202,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@ -7208,32 +7219,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/directus-sdk-typegen": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/directus-sdk-typegen/-/directus-sdk-typegen-0.2.1.tgz",
"integrity": "sha512-a0tcgCa4p2kAO8AJ+SBC97legirxdC3An6VztsvoZwtttFilI8YmCgCM4TfgDtCR/sh+QrpPPgh/Tae7mDxYfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^8.3.0"
},
"bin": {
"directus-sdk-typegen": "dist/index.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/directus-sdk-typegen/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@ -8127,7 +8112,6 @@
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
@ -8408,7 +8392,6 @@
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
@ -9306,7 +9289,6 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@ -12115,7 +12097,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz",
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==",
"devOptional": true,
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -12225,7 +12206,6 @@
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
}
@ -12528,7 +12508,6 @@
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.1.0",
"seroval": "~1.3.0",
@ -12951,7 +12930,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -13103,7 +13081,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -13181,7 +13158,8 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT",
"optional": true
"optional": true,
"peer": true
},
"node_modules/unicode-properties": {
"version": "1.4.1",
@ -13950,7 +13928,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8.3.0"
},
@ -14238,7 +14215,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@ -14495,6 +14471,16 @@
"vite-plugin-solid": "^2.11.8"
},
"dependencies": {
"@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"optional": true,
"peer": true,
"requires": {
"undici-types": "~7.16.0"
}
},
"fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@ -14504,14 +14490,12 @@
"picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"peer": true
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
},
"vite": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"peer": true,
"requires": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@ -14584,7 +14568,6 @@
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"peer": true,
"requires": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -14771,11 +14754,6 @@
"fontkit": "^2.0.2"
}
},
"@directus/sdk": {
"version": "20.1.1",
"resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-20.1.1.tgz",
"integrity": "sha512-cCWvxDRKOygVHBkKA7l1I4O4niomK8SKR/+Ul1L9NFF1K2zAf3jRqhjc7JavCECOiiAWME/s2wYatNFqFFh0gQ=="
},
"@emmetio/abbreviation": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
@ -15259,6 +15237,17 @@
"dev": true,
"requires": {}
},
"@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"optional": true,
"peer": true,
"requires": {
"undici-types": "~7.16.0"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -15633,7 +15622,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"peer": true,
"requires": {}
}
}
@ -16237,7 +16225,6 @@
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"peer": true,
"requires": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
@ -17474,7 +17461,6 @@
"version": "5.15.8",
"resolved": "https://registry.npmjs.org/astro/-/astro-5.15.8.tgz",
"integrity": "sha512-QiiRnNPdxCcAGO2UlO07o+QeGgRfEC5Dlm0x35WPB/ixFK1T2bsNB6KaXri70cVkYY1GYgoRtrWv3HCR26o9aw==",
"peer": true,
"requires": {
"@astrojs/compiler": "^2.13.0",
"@astrojs/internal-helpers": "0.7.4",
@ -17541,6 +17527,16 @@
"zod-to-ts": "^1.2.0"
},
"dependencies": {
"@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"optional": true,
"peer": true,
"requires": {
"undici-types": "~7.16.0"
}
},
"chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@ -17563,8 +17559,7 @@
"picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"peer": true
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
},
"readdirp": {
"version": "4.1.2",
@ -17625,7 +17620,6 @@
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"peer": true,
"requires": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@ -17813,7 +17807,6 @@
"version": "4.28.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
"peer": true,
"requires": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@ -18489,23 +18482,6 @@
"resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
"integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="
},
"directus-sdk-typegen": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/directus-sdk-typegen/-/directus-sdk-typegen-0.2.1.tgz",
"integrity": "sha512-a0tcgCa4p2kAO8AJ+SBC97legirxdC3An6VztsvoZwtttFilI8YmCgCM4TfgDtCR/sh+QrpPPgh/Tae7mDxYfw==",
"dev": true,
"requires": {
"commander": "^8.3.0"
},
"dependencies": {
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true
}
}
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@ -19103,8 +19079,7 @@
"graphql": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
"peer": true
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="
},
"graphql-config": {
"version": "5.1.5",
@ -19271,7 +19246,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"peer": true,
"requires": {}
}
}
@ -19846,8 +19820,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"devOptional": true,
"peer": true
"devOptional": true
},
"js-tokens": {
"version": "4.0.0",
@ -21696,7 +21669,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz",
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==",
"devOptional": true,
"peer": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -21776,8 +21748,7 @@
"seroval": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
"peer": true
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="
},
"seroval-plugins": {
"version": "1.3.3",
@ -21978,7 +21949,6 @@
"version": "1.9.10",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
"peer": true,
"requires": {
"csstype": "^3.1.0",
"seroval": "~1.3.0",
@ -22264,8 +22234,7 @@
"picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"peer": true
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
}
}
},
@ -22358,8 +22327,7 @@
"typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"peer": true
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="
},
"typescript-auto-import-cache": {
"version": "0.3.6",
@ -22397,9 +22365,11 @@
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
},
"undici-types": {
"version": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"optional": true
"optional": true,
"peer": true
},
"unicode-properties": {
"version": "1.4.1",
@ -22918,7 +22888,6 @@
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"peer": true,
"requires": {}
},
"xml-name-validator": {
@ -23099,8 +23068,7 @@
"zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"peer": true
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
},
"zod-to-json-schema": {
"version": "3.24.6",

View File

@ -5,7 +5,7 @@
"description": "Personal website created with Astro.",
"author": {
"name": "Sergey Yarkov",
"email": "serzh.yarkov@gmail.com",
"email": "email@yarkov.tech",
"url": "https://yarkov.tech"
},
"license": "MIT",
@ -16,7 +16,6 @@
"start": "node --env-file=.env ./dist/server/entry.mjs",
"preview": "astro preview",
"astro": "astro",
"directus:gen-types": "node --env-file=.env ./scripts/gen-types.js",
"codegen": "graphql-codegen --require dotenv/config --config codegen.ts --watch"
},
"dependencies": {
@ -25,7 +24,6 @@
"@astrojs/rss": "^4.0.13",
"@astrojs/sitemap": "^3.6.0",
"@astrojs/solid-js": "^5.1.3",
"@directus/sdk": "^20.1.1",
"@iconify-json/mdi": "^1.2.3",
"astro": "^5.15.8",
"astro-icon": "^1.1.5",
@ -55,7 +53,6 @@
"@types/html-escaper": "^3.0.4",
"@types/mime-types": "^2.1.4",
"@types/remark-prism": "^1.3.7",
"directus-sdk-typegen": "^0.2.1",
"dotenv": "^17.2.3",
"sass": "^1.68.0",
"typescript": "^5.9.3"

View File

@ -287,7 +287,7 @@ type article_translations {
tags(filter: article_translations_tag_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [article_translations_tag]
tags_func: count_functions
title: String!
views: Int
views: Int!
}
type article_translations_aggregated {
@ -482,7 +482,7 @@ input create_article_translations_input {
status: String
tags: [create_article_translations_tag_input]
title: String!
views: Int
views: Int!
}
input create_article_translations_tag_input {

View File

@ -1,15 +0,0 @@
import { generateDirectusTypes } from "directus-sdk-typegen";
async function generateTypes() {
try {
await generateDirectusTypes({
outputPath: "./directus-schema.ts",
directusUrl: process.env.DIRECTUS_URL,
directusToken: process.env.DIRECTUS_TOKEN,
});
} catch (error) {
console.error("Failed to generate types:", error);
}
}
generateTypes();

View File

@ -2,20 +2,19 @@ import type { Component } from "solid-js";
import { createRelativeArticleUrl, createDateFormatter } from "@root/utils";
import { DEFAULT_LANGUAGE, URL_BLOG_PREFIX } from "@root/constants";
import "./ArticleItem.scss";
import type { ArticleTranslation } from "@/directus-schema";
import { Article_Translations } from "@/src/graphql/graphql";
export type ArticleItemProps = {
data: ArticleTranslation;
title: Article_Translations["title"];
pub_date: Article_Translations["pub_date"];
slug: Article_Translations["slug"];
languages_code: Article_Translations["languages_code"];
pageLang: string;
};
const ArticleItem: Component<ArticleItemProps> = (props) => {
const {
data: { title, pub_date: pubDate, slug, languages_code },
pageLang,
} = props;
const articleLang =
typeof languages_code === "object" ? languages_code.code.split("-")[0] : DEFAULT_LANGUAGE;
const { title, pub_date: pubDate, slug, languages_code, pageLang } = props;
const articleLang = languages_code?.code.split("-")[0] || DEFAULT_LANGUAGE;
const href = createRelativeArticleUrl({ pubDate, slug, articleLang }, URL_BLOG_PREFIX);

View File

@ -6,19 +6,28 @@ import ArticleComments from "@components/ArticleComments.astro";
import TranslatedLink from "@components/TranslatedLink.astro";
import AvailableOnLangs from "@components/AvailableOnLangs.astro";
import { useTranslation, getLanguageFromURL } from "@i18n/utils";
import type { ArticleTranslation } from "@/directus-schema";
import { DIRECTUS_URL } from "astro:env/server";
import { Article_TranslationsQuery } from "../graphql/graphql";
export type Props = {
article: ArticleTranslation;
article: Article_TranslationsQuery["article_translations"][0];
lang: string;
};
const { article } = Astro.props as Props;
const pageLang = getLanguageFromURL(Astro.url.pathname);
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const { cover_image, title, author, description, tags, pub_date, slug, views, article_id } =
article;
const {
cover_image,
title,
author,
description,
tags,
pub_date,
slug,
views,
article_id: articleTranslations,
} = article;
const t = useTranslation(Astro);
---
@ -29,16 +38,13 @@ const t = useTranslation(Astro);
tags && (
<div class="tags">
<ul itemprop="keywords">
{tags.map(
(tag) =>
typeof tag === "object" && (
<li>
<TranslatedLink rel="tag" to={`/articles/?tags=${tag.tag_id.title}`}>
{tag.tag_id.title}
</TranslatedLink>
</li>
)
)}
{tags.map((tag) => (
<li>
<TranslatedLink rel="tag" to={`/articles/?tags=${tag?.tag_id?.title}`}>
{tag?.tag_id?.title}
</TranslatedLink>
</li>
))}
</ul>
</div>
)
@ -55,7 +61,7 @@ const t = useTranslation(Astro);
<time itemprop="datePublished" datetime={pub_date}>
{createDateFormatter(pageLang).format(new Date(pub_date))}&nbsp;
</time>
<span itemprop="author">{`${author.first_name} ${author.last_name}`}</span>
<span itemprop="author">{`${author?.first_name} ${author?.last_name}`}</span>
</div>
{
cover_image && (
@ -67,7 +73,7 @@ const t = useTranslation(Astro);
{description}
</p>
</div>
<AvailableOnLangs articles={article_id.translations} />
<AvailableOnLangs articles={articleTranslations} />
</header>
<slot />
<ArticleComments url={canonicalUrl.href} id={slug} />

View File

@ -5,7 +5,6 @@ import SearchInput from "./SearchInput";
import TagsList from "./TagsList";
import ArticleItem from "@components/ArticleItem";
import "./ArticlesList.scss";
import type { ArticleTranslation } from "@/directus-schema";
import { ArticleQuery } from "@/src/graphql/graphql";
type UiStringsType = { "articles.empty": string; "input.search": string };
@ -24,7 +23,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
const isEmpty = () => Object.keys(articles()).length === 0;
const sortYears = (articles: ArticlesBlockType) => {
const sortByYears = (articles: ArticlesBlockType) => {
return Object.keys(articles)
.map(Number)
.sort((a, b) => b - a);
@ -35,22 +34,26 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
}: {
params: { search: string; tags?: string[] };
}): ArticlesBlockType => {
const filter = (cb: (articles: ArticleTranslation[]) => ArticleTranslation[]) => {
const filter = (
cb: (
articles: ArticleQuery["article"][0]["translations"]
) => ArticleQuery["article"][0]["translations"]
) => {
return Object.fromEntries(
Object.keys(props.articles)
.map((year) => [year, cb(props.articles[year])])
.filter((articles) => articles[1].length !== 0)
.filter((articles) => articles[1]?.length !== 0)
);
};
return filter((articles) => {
let filtered = articles.filter((a) =>
a.title.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase())
let filtered = articles?.filter(
(a) => a && a.title.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase())
);
if (tags && tags.length > 0) {
filtered = filtered.filter((a) =>
filtered = filtered?.filter((a) =>
tags.some((t) => {
return a.tags.map((t) => t.tag_id.title).includes(t);
return a?.tags?.map((t) => t?.tag_id?.title).includes(t);
})
);
}
@ -70,12 +73,22 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
<TagsList tags={props.tags} />
<div itemscope itemtype="http://schema.org/Blog" class="articles-list">
{!isEmpty() ? (
<For each={sortYears(articles())}>
<For each={sortByYears(articles())}>
{(year) => (
<div class="articles-list__wrapper">
<h2>{year}</h2>
<For each={articles()[year]}>
{(data) => <ArticleItem data={data} pageLang={props.pageLang} />}
{(a) =>
a && (
<ArticleItem
slug={a.slug}
title={a.title}
languages_code={a.languages_code}
pub_date={a.pub_date}
pageLang={props.pageLang}
/>
)
}
</For>
</div>
)}

View File

@ -1,10 +1,10 @@
---
import { getLanguageFromURL, useTranslation } from "@root/i18n/utils";
import { createRelativeArticleUrl } from "@root/utils";
import type { ArticleTranslation } from "@/directus-schema";
import { Article_TranslationsQuery } from "../graphql/graphql";
export interface Props {
articles: ArticleTranslation[];
articles: Article_TranslationsQuery["article_translations"][0]["article_id"];
}
const pageLang = getLanguageFromURL(Astro.url.pathname);
@ -13,16 +13,19 @@ const { articles } = Astro.props;
---
{
articles.length !== 1 && (
articles && articles.translations && articles.translations?.length !== 1 && (
<div>
{t("article.availableOn")}:{" "}
{articles
.filter((a) => a.languages_code.code.split("-")[0] !== pageLang)
.map((a) => (
<a
href={`${createRelativeArticleUrl({ slug: a.slug, articleLang: a.languages_code.code.split('-')[0], pubDate: a.pub_date })}`}
>{`${a.languages_code.name}`}</a>
))}
{articles.translations
.filter((a) => a && a.languages_code?.code.split("-")[0] !== pageLang)
.map(
(a) =>
a && (
<a
href={`${createRelativeArticleUrl({ slug: a.slug, articleLang: `${a.languages_code?.code.split("-")[0]}`, pubDate: a.pub_date })}`}
>{`${a.languages_code?.name}`}</a>
)
)}
</div>
)
}

View File

@ -5,7 +5,7 @@ import ArticleItem from "@components/ArticleItem";
import EmptyList from "@components/EmptyList.astro";
import UIString from "@components/UIString.astro";
import TranslatedLink from "@components/TranslatedLink.astro";
import { fetchRecentArticles } from "@/directus";
import { getRecentArticleList } from "../queries";
export interface Props {
limit?: number;
@ -13,7 +13,7 @@ export interface Props {
const { limit = 1 } = Astro.props;
const pageLang = getLanguageFromURL(Astro.url.pathname);
const data = await fetchRecentArticles();
const data = await getRecentArticleList();
const articles = data.flatMap((a) => utils.removeArticleDuplicates(a.translations, pageLang));
const isEmpty = articles.length === 0;
---
@ -32,7 +32,18 @@ const isEmpty = articles.length === 0;
!isEmpty ? (
articles
.slice(0, limit)
.map((article) => article && <ArticleItem data={article} pageLang={pageLang} />)
.map(
(a) =>
a && (
<ArticleItem
slug={a.slug}
title={a.title}
languages_code={a.languages_code}
pub_date={a.pub_date}
pageLang={pageLang}
/>
)
)
) : (
<EmptyList />
)

View File

@ -16,10 +16,14 @@ import * as types from './graphql';
*/
type Documents = {
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleDocument,
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.RecentArticleDocument,
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.Article_TranslationsDocument,
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": typeof types.TagDocument,
};
const documents: Documents = {
"\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleDocument,
"\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.RecentArticleDocument,
"\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.Article_TranslationsDocument,
"\n\t\tquery Tag {\n\t\t\ttag {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t}\n\t\t}\n\t": types.TagDocument,
};
@ -27,6 +31,14 @@ const documents: Documents = {
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').ArticleDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n\t\tquery RecentArticle {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tcover_image {\n\t\t\t\t\t\tid\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tviews\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').RecentArticleDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').Article_TranslationsDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

View File

@ -1411,7 +1411,7 @@ export type Article_Translations = {
tags?: Maybe<Array<Maybe<Article_Translations_Tag>>>;
tags_func?: Maybe<Count_Functions>;
title: Scalars['String']['output'];
views?: Maybe<Scalars['Int']['output']>;
views: Scalars['Int']['output'];
};
@ -1686,7 +1686,7 @@ export type Create_Article_Translations_Input = {
status?: InputMaybe<Scalars['String']['input']>;
tags?: InputMaybe<Array<InputMaybe<Create_Article_Translations_Tag_Input>>>;
title: Scalars['String']['input'];
views?: InputMaybe<Scalars['Int']['input']>;
views: Scalars['Int']['input'];
};
export type Create_Article_Translations_Tag_Input = {
@ -4302,6 +4302,18 @@ export type ArticleQueryVariables = Exact<{ [key: string]: never; }>;
export type ArticleQuery = { __typename?: 'Query', article: Array<{ __typename?: 'article', translations?: Array<{ __typename?: 'article_translations', id: string, title: string, description: string, slug: string, pub_date: any, tags?: Array<{ __typename?: 'article_translations_tag', tag_id?: { __typename?: 'tag', title: string } | null } | null> | null, languages_code?: { __typename?: 'languages', code: string, name?: string | null } | null } | null> | null }> };
export type RecentArticleQueryVariables = Exact<{ [key: string]: never; }>;
export type RecentArticleQuery = { __typename?: 'Query', article: Array<{ __typename?: 'article', translations?: Array<{ __typename?: 'article_translations', title: string, slug: string, pub_date: any, views: number, cover_image?: { __typename?: 'directus_files', id: string, title?: string | null } | null, languages_code?: { __typename?: 'languages', code: string } | null } | null> | null }> };
export type Article_TranslationsQueryVariables = Exact<{
slug: Scalars['String']['input'];
}>;
export type Article_TranslationsQuery = { __typename?: 'Query', article_translations: Array<{ __typename?: 'article_translations', id: string, title: string, description: string, slug: string, content: string, pub_date: any, views: number, tags?: Array<{ __typename?: 'article_translations_tag', tag_id?: { __typename?: 'tag', title: string } | null } | null> | null, author?: { __typename?: 'directus_users', first_name?: string | null, last_name?: string | null } | null, languages_code?: { __typename?: 'languages', code: string, name?: string | null } | null, cover_image?: { __typename?: 'directus_files', title?: string | null, type?: string | null, filename_disk?: string | null } | null, article_id?: { __typename?: 'article', translations?: Array<{ __typename?: 'article_translations', slug: string, pub_date: any, languages_code?: { __typename?: 'languages', code: string, name?: string | null } | null } | null> | null } | null }> };
export type TagQueryVariables = Exact<{ [key: string]: never; }>;
@ -4348,6 +4360,66 @@ export const ArticleDocument = new TypedDocumentString(`
}
}
`) as unknown as TypedDocumentString<ArticleQuery, ArticleQueryVariables>;
export const RecentArticleDocument = new TypedDocumentString(`
query RecentArticle {
article {
translations(filter: {status: {_eq: "published"}}) {
title
cover_image {
id
title
}
slug
pub_date
views
languages_code {
code
}
}
}
}
`) as unknown as TypedDocumentString<RecentArticleQuery, RecentArticleQueryVariables>;
export const Article_TranslationsDocument = new TypedDocumentString(`
query Article_translations($slug: String!) {
article_translations(filter: {slug: {_eq: $slug}}) {
id
title
tags {
tag_id {
title
}
}
description
slug
content
author {
first_name
last_name
}
pub_date
views
languages_code {
code
name
}
cover_image {
title
type
filename_disk
}
article_id {
translations {
slug
languages_code {
code
name
}
pub_date
}
}
}
}
`) as unknown as TypedDocumentString<Article_TranslationsQuery, Article_TranslationsQueryVariables>;
export const TagDocument = new TypedDocumentString(`
query Tag {
tag {

View File

@ -7,13 +7,12 @@ import ArticleContent from "@components/ArticleContent.astro";
import ArticlePost from "@components/ArticlePost.astro";
import OpenGraph from "@components/OpenGraph.astro";
// import TableOfContents from "@root/components/TableOfContents.astro";
import type { ArticleTranslation } from "@/directus-schema";
import { markdownToHTML } from "@utils/markdown";
import { Article_TranslationsQuery } from "../graphql/graphql";
export interface Props {
lang?: string | undefined;
article: ArticleTranslation;
headings: MarkdownHeading[];
lang?: string;
article: Article_TranslationsQuery["article_translations"][0];
}
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
@ -28,17 +27,22 @@ const htmlContent = await markdownToHTML(content);
title={title}
siteName={SITE_METADATA.name}
description={description}
type='article'
type="article"
url={canonicalUrl.href}
image={cover_image
? { path: cover_image.filename_disk, width: "700", height: "400", type: cover_image.type }
? {
path: `${cover_image.filename_disk}`,
width: "700",
height: "400",
type: `${cover_image.type}`,
}
: undefined}
/>
<script is:inline defer src='/assets/js/load-code-highlight.js'></script>
<script is:inline defer src="/assets/js/load-code-highlight.js"></script>
</BaseHead>
<BaseLayout>
<div class='container'>
<div class="container">
<ArticlePost lang={lang} article={article}>
<ArticleContent set:html={htmlContent} />
</ArticlePost>

View File

@ -1,17 +1,17 @@
---
import ArticleLayout from "@root/layouts/article.astro";
import { fetchArticleTranslationsBySlug, incArticleTranslationViews } from "@/directus";
import { getArticleTranslationsBySlug } from "@/src/queries";
const { url } = Astro.params as { blog: string; url: string };
const slug = url.split("/").at(-1);
if (!slug) return Astro.redirect("/404");
const article = await fetchArticleTranslationsBySlug(slug);
const article = await getArticleTranslationsBySlug(slug);
if (!article) return Astro.redirect("/404");
if (import.meta.env.PROD) await incArticleTranslationViews(article);
// if (import.meta.env.PROD) await incArticleTranslationViews(article);
---
<ArticleLayout lang={article.languages_code.code} article={article} />
<ArticleLayout lang={article.languages_code?.code} article={article} />

View File

@ -31,3 +31,91 @@ export async function getArticleList() {
return result.data.article;
}
export async function getRecentArticleList() {
const RecentArticleQuery = graphql(`
query RecentArticle {
article {
translations(filter: { status: { _eq: "published" } }) {
title
cover_image {
id
title
}
slug
pub_date
views
languages_code {
code
}
}
}
}
`);
const result = await execute(RecentArticleQuery);
if (!result.data) return [];
return result.data.article;
}
export async function getArticleTranslationsBySlug(slug: string) {
const ArticleTranslationQuery = graphql(`
query Article_translations($slug: String!) {
article_translations(filter: { slug: { _eq: $slug } }) {
id
title
tags {
tag_id {
title
}
}
description
slug
content
author {
first_name
last_name
}
pub_date
views
languages_code {
code
name
}
cover_image {
title
type
filename_disk
}
article_id {
translations {
slug
languages_code {
code
name
}
pub_date
}
}
}
}
`);
const result = await execute(ArticleTranslationQuery, { slug });
if (!result.data || result.data.article_translations.length === 0) return null;
return { ...result.data.article_translations[0] };
}
// export async function incArticleTranslationViews() {
// const IncArticleTranslationViewsQuery = graphql(`
// mutation Update_article_translations_item($id: ID!) {
// update_article_translations_item(id: $id, data: { views: ${article.views + 1} }) {
// views
// }
// }
// `)
// }

View File

@ -1,20 +1,21 @@
import type { MarkdownHeading } from "astro";
import { DEFAULT_LANGUAGE } from "@root/constants";
import type { ArticleTranslation } from "@/directus-schema";
import { ArticleQuery } from "../graphql/graphql";
import { ArticleQuery, RecentArticleQuery } from "../graphql/graphql";
/**
* If there is a translation of an article in the current language of the page,
* the remaining articles with other translations should be removed.
*/
export function removeArticleDuplicates(
articles: ArticleQuery["article"][0]["translations"],
articles:
| ArticleQuery["article"][0]["translations"]
| RecentArticleQuery["article"][0]["translations"],
pageLang: LanguageKeys
) {
if (!articles) return [];
if (articles && articles.length >= 2) {
const pageLangArticles = articles.filter(
(a) => a?.languages_code?.code.split("-")[0] === pageLang
(a) => a && a.languages_code?.code.split("-")[0] === pageLang
);
if (pageLangArticles.length !== 0) return pageLangArticles;
return articles;