feat: author support and UI support for multi-category
This commit is contained in:
1
drizzle/0003_foamy_whiplash.sql
Normal file
1
drizzle/0003_foamy_whiplash.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `media` ADD `author` text;
|
||||||
766
drizzle/meta/0003_snapshot.json
Normal file
766
drizzle/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "602674b9-0b1e-4d1b-aed3-125bac4d1dda",
|
||||||
|
"prevId": "26aa5345-b6d8-4426-a144-0199140a896a",
|
||||||
|
"tables": {
|
||||||
|
"chat_conversations": {
|
||||||
|
"name": "chat_conversations",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"name": "model",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"copilot_session_id": {
|
||||||
|
"name": "copilot_session_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"chat_messages": {
|
||||||
|
"name": "chat_messages",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"conversation_id": {
|
||||||
|
"name": "conversation_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tool_call_id": {
|
||||||
|
"name": "tool_call_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tool_calls": {
|
||||||
|
"name": "tool_calls",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"import_definitions": {
|
||||||
|
"name": "import_definitions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"wxr_file_path": {
|
||||||
|
"name": "wxr_file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"uploads_folder_path": {
|
||||||
|
"name": "uploads_folder_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_analysis_result": {
|
||||||
|
"name": "last_analysis_result",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"name": "media",
|
||||||
|
"columns": {
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"name": "filename",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"original_name": {
|
||||||
|
"name": "original_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"name": "mime_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"name": "size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"name": "width",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"name": "height",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"alt": {
|
||||||
|
"name": "alt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"caption": {
|
||||||
|
"name": "caption",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "author",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"name": "file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sidecar_path": {
|
||||||
|
"name": "sidecar_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"checksum": {
|
||||||
|
"name": "checksum",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"post_links": {
|
||||||
|
"name": "post_links",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"source_post_id": {
|
||||||
|
"name": "source_post_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"target_post_id": {
|
||||||
|
"name": "target_post_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"link_text": {
|
||||||
|
"name": "link_text",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"post_media": {
|
||||||
|
"name": "post_media",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"post_id": {
|
||||||
|
"name": "post_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"media_id": {
|
||||||
|
"name": "media_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sort_order": {
|
||||||
|
"name": "sort_order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"post_media_post_media_idx": {
|
||||||
|
"name": "post_media_post_media_idx",
|
||||||
|
"columns": [
|
||||||
|
"post_id",
|
||||||
|
"media_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"posts": {
|
||||||
|
"name": "posts",
|
||||||
|
"columns": {
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"excerpt": {
|
||||||
|
"name": "excerpt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'draft'"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "author",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_at": {
|
||||||
|
"name": "published_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"name": "file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"checksum": {
|
||||||
|
"name": "checksum",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"name": "categories",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_title": {
|
||||||
|
"name": "published_title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_content": {
|
||||||
|
"name": "published_content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_tags": {
|
||||||
|
"name": "published_tags",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_categories": {
|
||||||
|
"name": "published_categories",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_excerpt": {
|
||||||
|
"name": "published_excerpt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"posts_project_slug_idx": {
|
||||||
|
"name": "posts_project_slug_idx",
|
||||||
|
"columns": [
|
||||||
|
"project_id",
|
||||||
|
"slug"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"name": "projects",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"data_path": {
|
||||||
|
"name": "data_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"name": "is_active",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"projects_slug_unique": {
|
||||||
|
"name": "projects_slug_unique",
|
||||||
|
"columns": [
|
||||||
|
"slug"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"name": "settings",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"name": "tags",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"project_id": {
|
||||||
|
"name": "project_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"tags_project_name_idx": {
|
||||||
|
"name": "tags_project_name_idx",
|
||||||
|
"columns": [
|
||||||
|
"project_id",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
"when": 1771141922712,
|
"when": 1771141922712,
|
||||||
"tag": "0002_rainy_luckman",
|
"tag": "0002_rainy_luckman",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1771168311850,
|
||||||
|
"tag": "0003_foamy_whiplash",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -57,6 +57,7 @@ export const media = sqliteTable('media', {
|
|||||||
title: text('title'),
|
title: text('title'),
|
||||||
alt: text('alt'),
|
alt: text('alt'),
|
||||||
caption: text('caption'),
|
caption: text('caption'),
|
||||||
|
author: text('author'),
|
||||||
filePath: text('file_path').notNull(),
|
filePath: text('file_path').notNull(),
|
||||||
sidecarPath: text('sidecar_path').notNull(),
|
sidecarPath: text('sidecar_path').notNull(),
|
||||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import type { WxrPost, WxrMedia } from './WxrParser';
|
|||||||
export interface ImportExecutionOptions {
|
export interface ImportExecutionOptions {
|
||||||
/** Path to the WordPress uploads folder for media files */
|
/** Path to the WordPress uploads folder for media files */
|
||||||
uploadsFolder?: string;
|
uploadsFolder?: string;
|
||||||
|
/** Default author to use when WXR post/media has no author */
|
||||||
|
defaultAuthor?: string;
|
||||||
/** Progress callback */
|
/** Progress callback */
|
||||||
onProgress?: (phase: string, current: number, total: number, detail?: string) => void;
|
onProgress?: (phase: string, current: number, total: number, detail?: string) => void;
|
||||||
}
|
}
|
||||||
@@ -461,7 +463,7 @@ export class ImportExecutionEngine extends EventEmitter {
|
|||||||
excerpt: wxrPost.excerpt || undefined,
|
excerpt: wxrPost.excerpt || undefined,
|
||||||
content: transformedContent,
|
content: transformedContent,
|
||||||
status,
|
status,
|
||||||
author: wxrPost.creator || undefined,
|
author: wxrPost.creator || options.defaultAuthor || undefined,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
publishedAt,
|
publishedAt,
|
||||||
@@ -634,6 +636,7 @@ export class ImportExecutionEngine extends EventEmitter {
|
|||||||
title: wxrMedia.title || undefined,
|
title: wxrMedia.title || undefined,
|
||||||
alt: wxrMedia.description || undefined,
|
alt: wxrMedia.description || undefined,
|
||||||
mimeType: wxrMedia.mimeType,
|
mimeType: wxrMedia.mimeType,
|
||||||
|
author: options.defaultAuthor,
|
||||||
tags: [],
|
tags: [],
|
||||||
linkedPostIds,
|
linkedPostIds,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface MediaData {
|
|||||||
title?: string;
|
title?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
|
author?: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
@@ -46,6 +47,7 @@ export interface MediaMetadata {
|
|||||||
title?: string;
|
title?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
|
author?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
@@ -317,6 +319,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: mediaData.title,
|
title: mediaData.title,
|
||||||
alt: mediaData.alt,
|
alt: mediaData.alt,
|
||||||
caption: mediaData.caption,
|
caption: mediaData.caption,
|
||||||
|
author: mediaData.author,
|
||||||
createdAt: mediaData.createdAt.toISOString(),
|
createdAt: mediaData.createdAt.toISOString(),
|
||||||
updatedAt: mediaData.updatedAt.toISOString(),
|
updatedAt: mediaData.updatedAt.toISOString(),
|
||||||
tags: mediaData.tags,
|
tags: mediaData.tags,
|
||||||
@@ -337,6 +340,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
if (metadata.title) lines.push(`title: "${metadata.title}"`);
|
if (metadata.title) lines.push(`title: "${metadata.title}"`);
|
||||||
if (metadata.alt) lines.push(`alt: "${metadata.alt}"`);
|
if (metadata.alt) lines.push(`alt: "${metadata.alt}"`);
|
||||||
if (metadata.caption) lines.push(`caption: "${metadata.caption}"`);
|
if (metadata.caption) lines.push(`caption: "${metadata.caption}"`);
|
||||||
|
if (metadata.author) lines.push(`author: "${metadata.author}"`);
|
||||||
|
|
||||||
lines.push(`createdAt: ${metadata.createdAt}`);
|
lines.push(`createdAt: ${metadata.createdAt}`);
|
||||||
lines.push(`updatedAt: ${metadata.updatedAt}`);
|
lines.push(`updatedAt: ${metadata.updatedAt}`);
|
||||||
@@ -410,6 +414,9 @@ export class MediaEngine extends EventEmitter {
|
|||||||
case 'caption':
|
case 'caption':
|
||||||
metadata.caption = value;
|
metadata.caption = value;
|
||||||
break;
|
break;
|
||||||
|
case 'author':
|
||||||
|
metadata.author = value;
|
||||||
|
break;
|
||||||
case 'createdAt':
|
case 'createdAt':
|
||||||
metadata.createdAt = value;
|
metadata.createdAt = value;
|
||||||
break;
|
break;
|
||||||
@@ -514,6 +521,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: metadata?.title,
|
title: metadata?.title,
|
||||||
alt: metadata?.alt,
|
alt: metadata?.alt,
|
||||||
caption: metadata?.caption,
|
caption: metadata?.caption,
|
||||||
|
author: metadata?.author,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
tags: metadata?.tags || [],
|
tags: metadata?.tags || [],
|
||||||
@@ -541,6 +549,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: mediaData.title,
|
title: mediaData.title,
|
||||||
alt: mediaData.alt,
|
alt: mediaData.alt,
|
||||||
caption: mediaData.caption,
|
caption: mediaData.caption,
|
||||||
|
author: mediaData.author,
|
||||||
filePath: destPath,
|
filePath: destPath,
|
||||||
sidecarPath,
|
sidecarPath,
|
||||||
createdAt: mediaData.createdAt,
|
createdAt: mediaData.createdAt,
|
||||||
@@ -591,6 +600,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: updated.title,
|
title: updated.title,
|
||||||
alt: updated.alt,
|
alt: updated.alt,
|
||||||
caption: updated.caption,
|
caption: updated.caption,
|
||||||
|
author: updated.author,
|
||||||
updatedAt: updated.updatedAt,
|
updatedAt: updated.updatedAt,
|
||||||
tags: JSON.stringify(updated.tags),
|
tags: JSON.stringify(updated.tags),
|
||||||
})
|
})
|
||||||
@@ -740,6 +750,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: dbMedia.title || undefined,
|
title: dbMedia.title || undefined,
|
||||||
alt: dbMedia.alt || undefined,
|
alt: dbMedia.alt || undefined,
|
||||||
caption: dbMedia.caption || undefined,
|
caption: dbMedia.caption || undefined,
|
||||||
|
author: dbMedia.author || undefined,
|
||||||
createdAt: dbMedia.createdAt,
|
createdAt: dbMedia.createdAt,
|
||||||
updatedAt: dbMedia.updatedAt,
|
updatedAt: dbMedia.updatedAt,
|
||||||
tags: JSON.parse(dbMedia.tags || '[]'),
|
tags: JSON.parse(dbMedia.tags || '[]'),
|
||||||
@@ -766,6 +777,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: dbMedia.title || undefined,
|
title: dbMedia.title || undefined,
|
||||||
alt: dbMedia.alt || undefined,
|
alt: dbMedia.alt || undefined,
|
||||||
caption: dbMedia.caption || undefined,
|
caption: dbMedia.caption || undefined,
|
||||||
|
author: dbMedia.author || undefined,
|
||||||
createdAt: dbMedia.createdAt,
|
createdAt: dbMedia.createdAt,
|
||||||
updatedAt: dbMedia.updatedAt,
|
updatedAt: dbMedia.updatedAt,
|
||||||
tags: JSON.parse(dbMedia.tags || '[]'),
|
tags: JSON.parse(dbMedia.tags || '[]'),
|
||||||
@@ -827,6 +839,7 @@ export class MediaEngine extends EventEmitter {
|
|||||||
title: dbMedia.title || undefined,
|
title: dbMedia.title || undefined,
|
||||||
alt: dbMedia.alt || undefined,
|
alt: dbMedia.alt || undefined,
|
||||||
caption: dbMedia.caption || undefined,
|
caption: dbMedia.caption || undefined,
|
||||||
|
author: dbMedia.author || undefined,
|
||||||
createdAt: dbMedia.createdAt,
|
createdAt: dbMedia.createdAt,
|
||||||
updatedAt: dbMedia.updatedAt,
|
updatedAt: dbMedia.updatedAt,
|
||||||
tags: JSON.parse(dbMedia.tags || '[]'),
|
tags: JSON.parse(dbMedia.tags || '[]'),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface ProjectMetadata {
|
|||||||
description?: string;
|
description?: string;
|
||||||
dataPath?: string; // Custom path for project data
|
dataPath?: string; // Custom path for project data
|
||||||
mainLanguage?: string; // Main language for AI-generated content (ISO code, e.g., 'en', 'de', 'es')
|
mainLanguage?: string; // Main language for AI-generated content (ISO code, e.g., 'en', 'de', 'es')
|
||||||
|
defaultAuthor?: string; // Default author for new posts and media
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -138,6 +138,16 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
safeHandle('posts:create', async (_, data: Partial<PostData>) => {
|
safeHandle('posts:create', async (_, data: Partial<PostData>) => {
|
||||||
const engine = getPostEngine();
|
const engine = getPostEngine();
|
||||||
|
|
||||||
|
// If no author provided, use default author from project settings
|
||||||
|
if (!data.author) {
|
||||||
|
const metaEngine = getMetaEngine();
|
||||||
|
const metadata = await metaEngine.getProjectMetadata();
|
||||||
|
if (metadata?.defaultAuthor) {
|
||||||
|
data.author = metadata.defaultAuthor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return engine.createPost(data);
|
return engine.createPost(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -279,6 +289,17 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
safeHandle('media:import', async (_, sourcePath: string, metadata?: Partial<MediaData>) => {
|
safeHandle('media:import', async (_, sourcePath: string, metadata?: Partial<MediaData>) => {
|
||||||
const engine = getMediaEngine();
|
const engine = getMediaEngine();
|
||||||
|
|
||||||
|
// If no author provided, use default author from project settings
|
||||||
|
if (!metadata?.author) {
|
||||||
|
const metaEngine = getMetaEngine();
|
||||||
|
const projectMetadata = await metaEngine.getProjectMetadata();
|
||||||
|
if (projectMetadata?.defaultAuthor) {
|
||||||
|
metadata = metadata || {};
|
||||||
|
metadata.author = projectMetadata.defaultAuthor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return engine.importMedia(sourcePath, metadata);
|
return engine.importMedia(sourcePath, metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,9 +330,14 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
const imported: MediaData[] = [];
|
const imported: MediaData[] = [];
|
||||||
|
|
||||||
|
// Get default author from project settings
|
||||||
|
const metaEngine = getMetaEngine();
|
||||||
|
const projectMetadata = await metaEngine.getProjectMetadata();
|
||||||
|
const defaultAuthor = projectMetadata?.defaultAuthor;
|
||||||
|
|
||||||
for (const filePath of result.filePaths) {
|
for (const filePath of result.filePaths) {
|
||||||
try {
|
try {
|
||||||
const media = await engine.importMedia(filePath);
|
const media = await engine.importMedia(filePath, defaultAuthor ? { author: defaultAuthor } : undefined);
|
||||||
imported.push(media);
|
imported.push(media);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to import ${filePath}:`, error);
|
console.error(`Failed to import ${filePath}:`, error);
|
||||||
@@ -881,8 +907,14 @@ export function registerIpcHandlers(): void {
|
|||||||
executionEngine.setProjectContext(activeProject.id, activeProject.dataPath);
|
executionEngine.setProjectContext(activeProject.id, activeProject.dataPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get default author from project settings
|
||||||
|
const metaEngine = getMetaEngine();
|
||||||
|
const projectMetadata = await metaEngine.getProjectMetadata();
|
||||||
|
const defaultAuthor = projectMetadata?.defaultAuthor;
|
||||||
|
|
||||||
const result = await executionEngine.executeImport(report, {
|
const result = await executionEngine.executeImport(report, {
|
||||||
uploadsFolder,
|
uploadsFolder,
|
||||||
|
defaultAuthor,
|
||||||
onProgress: (phase, current, total, detail) => {
|
onProgress: (phase, current, total, detail) => {
|
||||||
// Update processed items count based on phase progress
|
// Update processed items count based on phase progress
|
||||||
processedItems++;
|
processedItems++;
|
||||||
|
|||||||
@@ -884,7 +884,7 @@ const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
tags,
|
tags,
|
||||||
categories: category ? [category] : ['article'],
|
categories: selectedCategories.length > 0 ? selectedCategories : ['article'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
@@ -903,7 +903,7 @@ const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
}, [postId, title, content, tags, category, isDirty, isSaving, updatePost, markClean, showErrorModal]);
|
}, [postId, title, content, tags, selectedCategories, isDirty, isSaving, updatePost, markClean, showErrorModal]);
|
||||||
|
|
||||||
const handlePublish = async () => {
|
const handlePublish = async () => {
|
||||||
await handleSave();
|
await handleSave();
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export const SettingsView: React.FC = () => {
|
|||||||
const [projectDataPath, setProjectDataPath] = useState('');
|
const [projectDataPath, setProjectDataPath] = useState('');
|
||||||
const [defaultProjectPath, setDefaultProjectPath] = useState('');
|
const [defaultProjectPath, setDefaultProjectPath] = useState('');
|
||||||
const [projectMainLanguage, setProjectMainLanguage] = useState('en');
|
const [projectMainLanguage, setProjectMainLanguage] = useState('en');
|
||||||
|
const [projectDefaultAuthor, setProjectDefaultAuthor] = useState('');
|
||||||
|
|
||||||
// Post categories management
|
// Post categories management
|
||||||
const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES);
|
const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES);
|
||||||
@@ -136,11 +137,16 @@ export const SettingsView: React.FC = () => {
|
|||||||
setDefaultProjectPath(path);
|
setDefaultProjectPath(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load project metadata (includes mainLanguage)
|
// Load project metadata (includes mainLanguage and defaultAuthor)
|
||||||
window.electronAPI?.meta.getProjectMetadata().then(metadata => {
|
window.electronAPI?.meta.getProjectMetadata().then(metadata => {
|
||||||
if (metadata?.mainLanguage) {
|
if (metadata?.mainLanguage) {
|
||||||
setProjectMainLanguage(metadata.mainLanguage);
|
setProjectMainLanguage(metadata.mainLanguage);
|
||||||
}
|
}
|
||||||
|
if (metadata?.defaultAuthor) {
|
||||||
|
setProjectDefaultAuthor(metadata.defaultAuthor);
|
||||||
|
} else {
|
||||||
|
setProjectDefaultAuthor('');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [activeProject]);
|
}, [activeProject]);
|
||||||
@@ -233,12 +239,13 @@ export const SettingsView: React.FC = () => {
|
|||||||
setActiveProject(updated as any);
|
setActiveProject(updated as any);
|
||||||
useAppStore.getState().updateProject(activeProject.id, updated as any);
|
useAppStore.getState().updateProject(activeProject.id, updated as any);
|
||||||
|
|
||||||
// Also update project.json to keep dataPath and mainLanguage in sync
|
// Also update project.json to keep dataPath, mainLanguage, and defaultAuthor in sync
|
||||||
await window.electronAPI?.meta.updateProjectMetadata({
|
await window.electronAPI?.meta.updateProjectMetadata({
|
||||||
name: projectName.trim() || activeProject.name,
|
name: projectName.trim() || activeProject.name,
|
||||||
description: projectDescription.trim(),
|
description: projectDescription.trim(),
|
||||||
dataPath: projectDataPath.trim() || undefined,
|
dataPath: projectDataPath.trim() || undefined,
|
||||||
mainLanguage: projectMainLanguage,
|
mainLanguage: projectMainLanguage,
|
||||||
|
defaultAuthor: projectDefaultAuthor.trim() || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
showToast.success('Project settings saved');
|
showToast.success('Project settings saved');
|
||||||
@@ -260,7 +267,7 @@ export const SettingsView: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Keywords for each section for search filtering
|
// Keywords for each section for search filtering
|
||||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'path', 'folder', 'location', 'data', 'language'];
|
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'path', 'folder', 'location', 'data', 'language', 'author', 'default'];
|
||||||
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
|
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
|
||||||
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
|
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
|
||||||
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode'];
|
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode'];
|
||||||
@@ -359,6 +366,20 @@ export const SettingsView: React.FC = () => {
|
|||||||
</select>
|
</select>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
|
<SettingRow
|
||||||
|
id="project-author"
|
||||||
|
label="Default Author"
|
||||||
|
description="The default author name for new posts and media. Can be overridden per item."
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="project-author"
|
||||||
|
type="text"
|
||||||
|
placeholder="Author Name"
|
||||||
|
value={projectDefaultAuthor}
|
||||||
|
onChange={(e) => setProjectDefaultAuthor(e.target.value)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
|
||||||
<div className="setting-actions">
|
<div className="setting-actions">
|
||||||
<button className="primary" onClick={handleSaveProject}>
|
<button className="primary" onClick={handleSaveProject}>
|
||||||
Save Project Settings
|
Save Project Settings
|
||||||
|
|||||||
@@ -582,6 +582,38 @@ describe('MediaEngine', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Author Field', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFiles.set('/source/author-test.jpg', Buffer.from('image-data'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store author when provided during import', async () => {
|
||||||
|
const media = await mediaEngine.importMedia('/source/author-test.jpg', {
|
||||||
|
author: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(media.author).toBe('John Doe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle media without author', async () => {
|
||||||
|
const media = await mediaEngine.importMedia('/source/author-test.jpg');
|
||||||
|
|
||||||
|
expect(media.author).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include author in database insert', async () => {
|
||||||
|
await mediaEngine.importMedia('/source/author-test.jpg', {
|
||||||
|
author: 'Jane Smith',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that the database insert was called with author
|
||||||
|
expect(mockLocalDb.insert).toHaveBeenCalled();
|
||||||
|
// The mock captures the inserted data in mockMedia
|
||||||
|
const insertedMedia = Array.from(mockMedia.values()).pop();
|
||||||
|
expect(insertedMedia?.author).toBe('Jane Smith');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Date-based folder structure', () => {
|
describe('Date-based folder structure', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFiles.set('/source/dated-image.jpg', Buffer.from('image-data'));
|
mockFiles.set('/source/dated-image.jpg', Buffer.from('image-data'));
|
||||||
|
|||||||
@@ -498,6 +498,58 @@ describe('MetaEngine', () => {
|
|||||||
consoleErrorSpy.mockRestore();
|
consoleErrorSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set and get defaultAuthor in project metadata', async () => {
|
||||||
|
await metaEngine.setProjectMetadata({
|
||||||
|
name: 'My Blog',
|
||||||
|
description: 'A blog',
|
||||||
|
defaultAuthor: 'John Doe',
|
||||||
|
});
|
||||||
|
|
||||||
|
const metadata = await metaEngine.getProjectMetadata();
|
||||||
|
expect(metadata?.defaultAuthor).toBe('John Doe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update defaultAuthor only', async () => {
|
||||||
|
await metaEngine.setProjectMetadata({
|
||||||
|
name: 'My Blog',
|
||||||
|
description: 'A blog',
|
||||||
|
});
|
||||||
|
|
||||||
|
await metaEngine.updateProjectMetadata({ defaultAuthor: 'Jane Smith' });
|
||||||
|
|
||||||
|
const metadata = await metaEngine.getProjectMetadata();
|
||||||
|
expect(metadata?.name).toBe('My Blog');
|
||||||
|
expect(metadata?.defaultAuthor).toBe('Jane Smith');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should persist defaultAuthor to filesystem', async () => {
|
||||||
|
await metaEngine.setProjectMetadata({
|
||||||
|
name: 'Test Project',
|
||||||
|
defaultAuthor: 'Author Name',
|
||||||
|
});
|
||||||
|
|
||||||
|
const metaDir = metaEngine.getMetaDir();
|
||||||
|
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||||
|
|
||||||
|
const content = mockFiles.get(projectPath);
|
||||||
|
const parsed = JSON.parse(content!);
|
||||||
|
expect(parsed.defaultAuthor).toBe('Author Name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load defaultAuthor from filesystem', async () => {
|
||||||
|
const metaDir = metaEngine.getMetaDir();
|
||||||
|
const projectPath = normalizePath(`${metaDir}/project.json`);
|
||||||
|
mockFiles.set(projectPath, JSON.stringify({
|
||||||
|
name: 'Loaded Project',
|
||||||
|
defaultAuthor: 'Loaded Author',
|
||||||
|
}));
|
||||||
|
|
||||||
|
await metaEngine.loadProjectMetadata();
|
||||||
|
|
||||||
|
const metadata = await metaEngine.getProjectMetadata();
|
||||||
|
expect(metadata?.defaultAuthor).toBe('Loaded Author');
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle ENOENT error when loading categories (no file)', async () => {
|
it('should handle ENOENT error when loading categories (no file)', async () => {
|
||||||
// No file exists, should not throw
|
// No file exists, should not throw
|
||||||
await metaEngine.loadCategories();
|
await metaEngine.loadCategories();
|
||||||
|
|||||||
Reference in New Issue
Block a user