focomy 0.1.104__tar.gz → 0.1.106__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. {focomy-0.1.104 → focomy-0.1.106}/PKG-INFO +1 -1
  2. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/form.yaml +1 -7
  3. {focomy-0.1.104 → focomy-0.1.106}/core/main.py +1 -1
  4. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/entity_form.html +349 -3
  5. {focomy-0.1.104 → focomy-0.1.106}/pyproject.toml +1 -1
  6. {focomy-0.1.104 → focomy-0.1.106}/.gitignore +0 -0
  7. {focomy-0.1.104 → focomy-0.1.106}/LICENSE +0 -0
  8. {focomy-0.1.104 → focomy-0.1.106}/README.md +0 -0
  9. {focomy-0.1.104 → focomy-0.1.106}/core/__init__.py +0 -0
  10. {focomy-0.1.104 → focomy-0.1.106}/core/admin/__init__.py +0 -0
  11. {focomy-0.1.104 → focomy-0.1.106}/core/admin/routes.py +0 -0
  12. {focomy-0.1.104 → focomy-0.1.106}/core/admin/url.py +0 -0
  13. {focomy-0.1.104 → focomy-0.1.106}/core/api/__init__.py +0 -0
  14. {focomy-0.1.104 → focomy-0.1.106}/core/api/auth.py +0 -0
  15. {focomy-0.1.104 → focomy-0.1.106}/core/api/comments.py +0 -0
  16. {focomy-0.1.104 → focomy-0.1.106}/core/api/entities.py +0 -0
  17. {focomy-0.1.104 → focomy-0.1.106}/core/api/forms.py +0 -0
  18. {focomy-0.1.104 → focomy-0.1.106}/core/api/media.py +0 -0
  19. {focomy-0.1.104 → focomy-0.1.106}/core/api/relations.py +0 -0
  20. {focomy-0.1.104 → focomy-0.1.106}/core/api/revisions.py +0 -0
  21. {focomy-0.1.104 → focomy-0.1.106}/core/api/schema.py +0 -0
  22. {focomy-0.1.104 → focomy-0.1.106}/core/api/search.py +0 -0
  23. {focomy-0.1.104 → focomy-0.1.106}/core/api/seo.py +0 -0
  24. {focomy-0.1.104 → focomy-0.1.106}/core/cli.py +0 -0
  25. {focomy-0.1.104 → focomy-0.1.106}/core/config.py +0 -0
  26. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/__init__.py +0 -0
  27. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/audit_log.yaml +0 -0
  28. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/category.yaml +0 -0
  29. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/channel.yaml +0 -0
  30. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/comment.yaml +0 -0
  31. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/edit_lock.yaml +0 -0
  32. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/form_submission.yaml +0 -0
  33. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/menu_item.yaml +0 -0
  34. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/news.yaml +0 -0
  35. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/page.yaml +0 -0
  36. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/plugin.yaml +0 -0
  37. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/plugin_developer.yaml +0 -0
  38. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/plugin_review.yaml +0 -0
  39. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/post.yaml +0 -0
  40. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/redirect.yaml +0 -0
  41. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/scheduled_action.yaml +0 -0
  42. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/series.yaml +0 -0
  43. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/site_setting.yaml +0 -0
  44. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/tag.yaml +0 -0
  45. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/user.yaml +0 -0
  46. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/widget.yaml +0 -0
  47. {focomy-0.1.104 → focomy-0.1.106}/core/content_types/workflow_history.yaml +0 -0
  48. {focomy-0.1.104 → focomy-0.1.106}/core/database.py +0 -0
  49. {focomy-0.1.104 → focomy-0.1.106}/core/engine/__init__.py +0 -0
  50. {focomy-0.1.104 → focomy-0.1.106}/core/engine/routes.py +0 -0
  51. {focomy-0.1.104 → focomy-0.1.106}/core/migrations/env.py +0 -0
  52. {focomy-0.1.104 → focomy-0.1.106}/core/migrations/script.py.mako +0 -0
  53. {focomy-0.1.104 → focomy-0.1.106}/core/migrations/versions/2038bdf6693b_add_import_jobs_table.py +0 -0
  54. {focomy-0.1.104 → focomy-0.1.106}/core/migrations/versions/3a1b2c3d4e5f_add_file_hash_to_media.py +0 -0
  55. {focomy-0.1.104 → focomy-0.1.106}/core/models/__init__.py +0 -0
  56. {focomy-0.1.104 → focomy-0.1.106}/core/models/auth.py +0 -0
  57. {focomy-0.1.104 → focomy-0.1.106}/core/models/entity.py +0 -0
  58. {focomy-0.1.104 → focomy-0.1.106}/core/models/import_job.py +0 -0
  59. {focomy-0.1.104 → focomy-0.1.106}/core/models/media.py +0 -0
  60. {focomy-0.1.104 → focomy-0.1.106}/core/models/relation.py +0 -0
  61. {focomy-0.1.104 → focomy-0.1.106}/core/models/revision.py +0 -0
  62. {focomy-0.1.104 → focomy-0.1.106}/core/plugins/__init__.py +0 -0
  63. {focomy-0.1.104 → focomy-0.1.106}/core/plugins/base.py +0 -0
  64. {focomy-0.1.104 → focomy-0.1.106}/core/plugins/hooks.py +0 -0
  65. {focomy-0.1.104 → focomy-0.1.106}/core/plugins/loader.py +0 -0
  66. {focomy-0.1.104 → focomy-0.1.106}/core/plugins/manager.py +0 -0
  67. {focomy-0.1.104 → focomy-0.1.106}/core/rate_limit.py +0 -0
  68. {focomy-0.1.104 → focomy-0.1.106}/core/relations.yaml +0 -0
  69. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/.env.template +0 -0
  70. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/.gitignore.template +0 -0
  71. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/__init__.py +0 -0
  72. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/config.yaml.template +0 -0
  73. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/archive.html +0 -0
  74. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/base.html +0 -0
  75. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/category.html +0 -0
  76. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/home.html +0 -0
  77. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/post.html +0 -0
  78. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/templates/search.html +0 -0
  79. {focomy-0.1.104 → focomy-0.1.106}/core/scaffold/themes/default/theme.yaml +0 -0
  80. {focomy-0.1.104 → focomy-0.1.106}/core/schemas/__init__.py +0 -0
  81. {focomy-0.1.104 → focomy-0.1.106}/core/schemas/import_schema.py +0 -0
  82. {focomy-0.1.104 → focomy-0.1.106}/core/seo/__init__.py +0 -0
  83. {focomy-0.1.104 → focomy-0.1.106}/core/services/__init__.py +0 -0
  84. {focomy-0.1.104 → focomy-0.1.106}/core/services/api_auth.py +0 -0
  85. {focomy-0.1.104 → focomy-0.1.106}/core/services/assets.py +0 -0
  86. {focomy-0.1.104 → focomy-0.1.106}/core/services/audit.py +0 -0
  87. {focomy-0.1.104 → focomy-0.1.106}/core/services/auth.py +0 -0
  88. {focomy-0.1.104 → focomy-0.1.106}/core/services/block_converter.py +0 -0
  89. {focomy-0.1.104 → focomy-0.1.106}/core/services/bulk.py +0 -0
  90. {focomy-0.1.104 → focomy-0.1.106}/core/services/cache.py +0 -0
  91. {focomy-0.1.104 → focomy-0.1.106}/core/services/cleanup.py +0 -0
  92. {focomy-0.1.104 → focomy-0.1.106}/core/services/comment.py +0 -0
  93. {focomy-0.1.104 → focomy-0.1.106}/core/services/config_priority.py +0 -0
  94. {focomy-0.1.104 → focomy-0.1.106}/core/services/deployment.py +0 -0
  95. {focomy-0.1.104 → focomy-0.1.106}/core/services/edit_lock.py +0 -0
  96. {focomy-0.1.104 → focomy-0.1.106}/core/services/entity.py +0 -0
  97. {focomy-0.1.104 → focomy-0.1.106}/core/services/export.py +0 -0
  98. {focomy-0.1.104 → focomy-0.1.106}/core/services/field.py +0 -0
  99. {focomy-0.1.104 → focomy-0.1.106}/core/services/formula.py +0 -0
  100. {focomy-0.1.104 → focomy-0.1.106}/core/services/i18n.py +0 -0
  101. {focomy-0.1.104 → focomy-0.1.106}/core/services/index.py +0 -0
  102. {focomy-0.1.104 → focomy-0.1.106}/core/services/invite.py +0 -0
  103. {focomy-0.1.104 → focomy-0.1.106}/core/services/link_validator.py +0 -0
  104. {focomy-0.1.104 → focomy-0.1.106}/core/services/logging.py +0 -0
  105. {focomy-0.1.104 → focomy-0.1.106}/core/services/mail.py +0 -0
  106. {focomy-0.1.104 → focomy-0.1.106}/core/services/marketplace.py +0 -0
  107. {focomy-0.1.104 → focomy-0.1.106}/core/services/marketplace_verify.py +0 -0
  108. {focomy-0.1.104 → focomy-0.1.106}/core/services/media.py +0 -0
  109. {focomy-0.1.104 → focomy-0.1.106}/core/services/media_cleanup.py +0 -0
  110. {focomy-0.1.104 → focomy-0.1.106}/core/services/menu.py +0 -0
  111. {focomy-0.1.104 → focomy-0.1.106}/core/services/migration_helpers.py +0 -0
  112. {focomy-0.1.104 → focomy-0.1.106}/core/services/oauth.py +0 -0
  113. {focomy-0.1.104 → focomy-0.1.106}/core/services/pagination.py +0 -0
  114. {focomy-0.1.104 → focomy-0.1.106}/core/services/plugin_resolver.py +0 -0
  115. {focomy-0.1.104 → focomy-0.1.106}/core/services/plugin_sandbox.py +0 -0
  116. {focomy-0.1.104 → focomy-0.1.106}/core/services/preview.py +0 -0
  117. {focomy-0.1.104 → focomy-0.1.106}/core/services/query_optimizer.py +0 -0
  118. {focomy-0.1.104 → focomy-0.1.106}/core/services/rbac.py +0 -0
  119. {focomy-0.1.104 → focomy-0.1.106}/core/services/redirect.py +0 -0
  120. {focomy-0.1.104 → focomy-0.1.106}/core/services/relation.py +0 -0
  121. {focomy-0.1.104 → focomy-0.1.106}/core/services/revision.py +0 -0
  122. {focomy-0.1.104 → focomy-0.1.106}/core/services/routing.py +0 -0
  123. {focomy-0.1.104 → focomy-0.1.106}/core/services/sanitizer.py +0 -0
  124. {focomy-0.1.104 → focomy-0.1.106}/core/services/schedule.py +0 -0
  125. {focomy-0.1.104 → focomy-0.1.106}/core/services/search.py +0 -0
  126. {focomy-0.1.104 → focomy-0.1.106}/core/services/sentry.py +0 -0
  127. {focomy-0.1.104 → focomy-0.1.106}/core/services/seo.py +0 -0
  128. {focomy-0.1.104 → focomy-0.1.106}/core/services/settings.py +0 -0
  129. {focomy-0.1.104 → focomy-0.1.106}/core/services/spam_filter.py +0 -0
  130. {focomy-0.1.104 → focomy-0.1.106}/core/services/storage.py +0 -0
  131. {focomy-0.1.104 → focomy-0.1.106}/core/services/theme.py +0 -0
  132. {focomy-0.1.104 → focomy-0.1.106}/core/services/theme_inheritance.py +0 -0
  133. {focomy-0.1.104 → focomy-0.1.106}/core/services/thumbnail.py +0 -0
  134. {focomy-0.1.104 → focomy-0.1.106}/core/services/update.py +0 -0
  135. {focomy-0.1.104 → focomy-0.1.106}/core/services/widget.py +0 -0
  136. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/__init__.py +0 -0
  137. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/acf.py +0 -0
  138. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/analyzer.py +0 -0
  139. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/constants.py +0 -0
  140. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/content_sanitizer.py +0 -0
  141. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/diff_detector.py +0 -0
  142. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/dry_run.py +0 -0
  143. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/error_collector.py +0 -0
  144. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/id_resolver.py +0 -0
  145. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/import_service.py +0 -0
  146. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/importer.py +0 -0
  147. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/link_fixer.py +0 -0
  148. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/media.py +0 -0
  149. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/preview.py +0 -0
  150. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/redirects.py +0 -0
  151. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/rest_client.py +0 -0
  152. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/rollback.py +0 -0
  153. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/verification.py +0 -0
  154. {focomy-0.1.104 → focomy-0.1.106}/core/services/wordpress_import/wxr_parser.py +0 -0
  155. {focomy-0.1.104 → focomy-0.1.106}/core/services/workflow.py +0 -0
  156. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/backup.html +0 -0
  157. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/base.html +0 -0
  158. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/comments.html +0 -0
  159. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/components/editor.html +0 -0
  160. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/customize.html +0 -0
  161. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/dashboard.html +0 -0
  162. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/entity_list.html +0 -0
  163. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/forgot_password.html +0 -0
  164. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/import.html +0 -0
  165. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/link_validator.html +0 -0
  166. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/login.html +0 -0
  167. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/media.html +0 -0
  168. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/menus.html +0 -0
  169. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/plugins.html +0 -0
  170. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/redirects.html +0 -0
  171. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/reset_password.html +0 -0
  172. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/settings.html +0 -0
  173. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/sitemap.html +0 -0
  174. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/system.html +0 -0
  175. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/themes.html +0 -0
  176. {focomy-0.1.104 → focomy-0.1.106}/core/templates/admin/widgets.html +0 -0
  177. {focomy-0.1.104 → focomy-0.1.106}/core/themes/__init__.py +0 -0
  178. {focomy-0.1.104 → focomy-0.1.106}/core/themes/customizer.py +0 -0
  179. {focomy-0.1.104 → focomy-0.1.106}/core/themes/manager.py +0 -0
  180. {focomy-0.1.104 → focomy-0.1.106}/core/themes/marketplace.py +0 -0
  181. {focomy-0.1.104 → focomy-0.1.106}/core/utils.py +0 -0
  182. {focomy-0.1.104 → focomy-0.1.106}/static/favicon.svg +0 -0
  183. {focomy-0.1.104 → focomy-0.1.106}/themes/blog/templates/base.html +0 -0
  184. {focomy-0.1.104 → focomy-0.1.106}/themes/blog/templates/home.html +0 -0
  185. {focomy-0.1.104 → focomy-0.1.106}/themes/blog/templates/post.html +0 -0
  186. {focomy-0.1.104 → focomy-0.1.106}/themes/blog/theme.yaml +0 -0
  187. {focomy-0.1.104 → focomy-0.1.106}/themes/corporate/templates/base.html +0 -0
  188. {focomy-0.1.104 → focomy-0.1.106}/themes/corporate/templates/home.html +0 -0
  189. {focomy-0.1.104 → focomy-0.1.106}/themes/corporate/templates/page.html +0 -0
  190. {focomy-0.1.104 → focomy-0.1.106}/themes/corporate/theme.yaml +0 -0
  191. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/404.html +0 -0
  192. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/500.html +0 -0
  193. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/archive.html +0 -0
  194. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/base.html +0 -0
  195. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/category.html +0 -0
  196. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/channel.html +0 -0
  197. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/form.html +0 -0
  198. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/form_success.html +0 -0
  199. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/home.html +0 -0
  200. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/page.html +0 -0
  201. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/post.html +0 -0
  202. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/search.html +0 -0
  203. {focomy-0.1.104 → focomy-0.1.106}/themes/default/templates/series.html +0 -0
  204. {focomy-0.1.104 → focomy-0.1.106}/themes/default/theme.yaml +0 -0
  205. {focomy-0.1.104 → focomy-0.1.106}/themes/minimal/templates/base.html +0 -0
  206. {focomy-0.1.104 → focomy-0.1.106}/themes/minimal/templates/home.html +0 -0
  207. {focomy-0.1.104 → focomy-0.1.106}/themes/minimal/templates/page.html +0 -0
  208. {focomy-0.1.104 → focomy-0.1.106}/themes/minimal/templates/post.html +0 -0
  209. {focomy-0.1.104 → focomy-0.1.106}/themes/minimal/theme.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: focomy
3
- Version: 0.1.104
3
+ Version: 0.1.106
4
4
  Summary: The Most Beautiful CMS - A metadata-driven, zero-duplicate-code content management system
5
5
  Project-URL: Homepage, https://github.com/focomy/focomy
6
6
  Project-URL: Documentation, https://focomy.dev/docs
@@ -28,16 +28,10 @@ fields:
28
28
  - name: fields_config
29
29
  type: json
30
30
  label: フィールド設定
31
- hint: |
32
- フォームフィールドをJSON配列で定義します。
33
- 例: [{"name": "email", "type": "email", "label": "メールアドレス", "required": true}]
34
31
 
35
32
  - name: steps
36
33
  type: json
37
- label: ステップ設定
38
- hint: |
39
- ウィザード形式の場合、ステップを定義します。
40
- 例: [{"title": "基本情報", "fields": ["name", "email"]}, {"title": "詳細", "fields": ["message"]}]
34
+ label: ステップ設定(ウィザード形式)
41
35
 
42
36
  - name: submit_label
43
37
  type: string
@@ -81,7 +81,7 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
81
81
  # CSP for public pages (more restrictive)
82
82
  PUBLIC_CSP = (
83
83
  "default-src 'self'; "
84
- "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; "
84
+ "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://unpkg.com; "
85
85
  "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
86
86
  "img-src 'self' data: https: blob:; "
87
87
  "font-src 'self' https://fonts.gstatic.com; "
@@ -50,7 +50,7 @@
50
50
  name="{{ field.name }}"
51
51
  class="form-input"
52
52
  value="{{ entity.get(field.name, '') if entity else '' }}"
53
- pattern="[a-z0-9-]+"
53
+ pattern="[-a-z0-9]+"
54
54
  {% if field.required %}required{% endif %}
55
55
  >
56
56
  <p class="form-hint">URL-friendly identifier (lowercase letters, numbers, hyphens)</p>
@@ -231,7 +231,7 @@
231
231
 
232
232
  <div class="form-group">
233
233
  <label class="form-label" for="field-name">フィールド名 <span style="color: var(--error);">*</span></label>
234
- <input type="text" id="field-name" class="form-input" pattern="[a-z0-9_]+" required>
234
+ <input type="text" id="field-name" class="form-input" pattern="[a-z0-9_]+">
235
235
  <p class="form-hint">英小文字、数字、アンダースコアのみ</p>
236
236
  </div>
237
237
 
@@ -292,6 +292,56 @@
292
292
  </div>
293
293
  </div>
294
294
  </div>
295
+ {% elif field.name == 'steps' %}
296
+ {# === Visual Step Builder for steps === #}
297
+ <input type="hidden" id="{{ field.name }}" name="{{ field.name }}" value='{{ entity.get(field.name, "[]") | tojson if entity and entity.get(field.name) else "[]" }}'>
298
+
299
+ <div class="step-builder" id="step-builder">
300
+ <p class="form-hint" style="margin-bottom: 0.75rem;">
301
+ 複数ステップに分けるとウィザード形式のフォームになります。空の場合は1ページ形式です。
302
+ </p>
303
+ <div class="step-builder-toolbar">
304
+ <button type="button" class="btn btn-primary btn-sm" onclick="StepBuilder.showAddModal()">
305
+ + ステップ追加
306
+ </button>
307
+ </div>
308
+ <ul class="step-builder-list" id="step-builder-list">
309
+ {# Steps rendered by JavaScript #}
310
+ </ul>
311
+ <div class="step-builder-empty" id="step-builder-empty" style="display: none;">
312
+ <p class="text-muted">ステップがありません。1ページ形式で表示されます。</p>
313
+ </div>
314
+ </div>
315
+
316
+ {# Step Edit Modal #}
317
+ <div id="step-modal" class="modal" style="display: none;">
318
+ <div class="modal-overlay" onclick="StepBuilder.hideModal()"></div>
319
+ <div class="modal-content">
320
+ <div class="modal-header">
321
+ <h3 id="step-modal-title">ステップ追加</h3>
322
+ <button type="button" class="modal-close" onclick="StepBuilder.hideModal()">&times;</button>
323
+ </div>
324
+ <div class="modal-body">
325
+ <input type="hidden" id="step-edit-index" value="-1">
326
+
327
+ <div class="form-group">
328
+ <label class="form-label" for="step-title">ステップタイトル <span style="color: var(--error);">*</span></label>
329
+ <input type="text" id="step-title" class="form-input" placeholder="例: 基本情報">
330
+ </div>
331
+
332
+ <div class="form-group">
333
+ <label class="form-label">このステップに含めるフィールド</label>
334
+ <div id="step-fields-selector" class="step-fields-selector">
335
+ <p class="text-muted" id="step-no-fields">先にフィールドを追加してください</p>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ <div class="modal-footer">
340
+ <button type="button" class="btn btn-secondary" onclick="StepBuilder.hideModal()">キャンセル</button>
341
+ <button type="button" class="btn btn-primary" onclick="StepBuilder.saveStep()">保存</button>
342
+ </div>
343
+ </div>
344
+ </div>
295
345
  {% else %}
296
346
  <textarea
297
347
  id="{{ field.name }}"
@@ -940,6 +990,94 @@
940
990
  padding: 1rem 1.5rem;
941
991
  border-top: 1px solid var(--border);
942
992
  }
993
+
994
+ /* === Step Builder Styles === */
995
+ .step-builder {
996
+ border: 1px solid var(--border);
997
+ border-radius: 0.5rem;
998
+ background: var(--bg);
999
+ }
1000
+ .step-builder-toolbar {
1001
+ padding: 0.75rem;
1002
+ border-bottom: 1px solid var(--border);
1003
+ background: var(--card);
1004
+ border-radius: 0.5rem 0.5rem 0 0;
1005
+ }
1006
+ .step-builder-list {
1007
+ list-style: none;
1008
+ padding: 0.75rem;
1009
+ margin: 0;
1010
+ min-height: 60px;
1011
+ }
1012
+ .step-builder-item {
1013
+ display: flex;
1014
+ align-items: center;
1015
+ gap: 0.75rem;
1016
+ padding: 0.75rem 1rem;
1017
+ background: var(--card);
1018
+ border: 1px solid var(--border);
1019
+ border-radius: 0.375rem;
1020
+ margin-bottom: 0.5rem;
1021
+ }
1022
+ .step-builder-item:last-child {
1023
+ margin-bottom: 0;
1024
+ }
1025
+ .step-builder-item.sortable-ghost {
1026
+ opacity: 0.4;
1027
+ }
1028
+ .step-builder-item.sortable-chosen {
1029
+ border-color: var(--primary);
1030
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1031
+ }
1032
+ .step-builder-drag {
1033
+ cursor: grab;
1034
+ color: var(--text-muted);
1035
+ font-size: 1rem;
1036
+ }
1037
+ .step-builder-drag:active {
1038
+ cursor: grabbing;
1039
+ }
1040
+ .step-builder-info {
1041
+ flex: 1;
1042
+ min-width: 0;
1043
+ }
1044
+ .step-builder-title {
1045
+ font-weight: 500;
1046
+ }
1047
+ .step-builder-fields {
1048
+ font-size: 0.75rem;
1049
+ color: var(--text-muted);
1050
+ margin-top: 0.25rem;
1051
+ }
1052
+ .step-builder-actions {
1053
+ display: flex;
1054
+ gap: 0.5rem;
1055
+ }
1056
+ .step-builder-empty {
1057
+ padding: 1.5rem;
1058
+ text-align: center;
1059
+ }
1060
+ .step-fields-selector {
1061
+ max-height: 200px;
1062
+ overflow-y: auto;
1063
+ border: 1px solid var(--border);
1064
+ border-radius: 0.375rem;
1065
+ padding: 0.5rem;
1066
+ }
1067
+ .step-field-option {
1068
+ display: flex;
1069
+ align-items: center;
1070
+ gap: 0.5rem;
1071
+ padding: 0.375rem 0.5rem;
1072
+ border-radius: 0.25rem;
1073
+ cursor: pointer;
1074
+ }
1075
+ .step-field-option:hover {
1076
+ background: var(--bg);
1077
+ }
1078
+ .step-field-option input {
1079
+ width: auto;
1080
+ }
943
1081
  </style>
944
1082
 
945
1083
  {% set has_blocks = namespace(value=false) %}
@@ -1456,7 +1594,215 @@ const FormBuilder = (function() {
1456
1594
  hideModal,
1457
1595
  toggleOptionsEditor,
1458
1596
  addOption,
1459
- saveField
1597
+ saveField,
1598
+ getFields: function() { return fields; }
1599
+ };
1600
+ })();
1601
+
1602
+ // === Step Builder ===
1603
+ const StepBuilder = (function() {
1604
+ let steps = [];
1605
+
1606
+ function init() {
1607
+ const input = document.getElementById('steps');
1608
+ if (!input) return;
1609
+
1610
+ // Parse existing data
1611
+ try {
1612
+ const data = JSON.parse(input.value || '[]');
1613
+ steps = Array.isArray(data) ? data : [];
1614
+ } catch (e) {
1615
+ console.error('Failed to parse steps:', e);
1616
+ steps = [];
1617
+ }
1618
+
1619
+ render();
1620
+ initSortable();
1621
+ }
1622
+
1623
+ function initSortable() {
1624
+ const list = document.getElementById('step-builder-list');
1625
+ if (!list || typeof Sortable === 'undefined') return;
1626
+
1627
+ new Sortable(list, {
1628
+ animation: 150,
1629
+ handle: '.step-builder-drag',
1630
+ ghostClass: 'sortable-ghost',
1631
+ chosenClass: 'sortable-chosen',
1632
+ onEnd: function() {
1633
+ const items = list.querySelectorAll('.step-builder-item');
1634
+ const newSteps = [];
1635
+ items.forEach(item => {
1636
+ const index = parseInt(item.dataset.index);
1637
+ if (steps[index]) {
1638
+ newSteps.push(steps[index]);
1639
+ }
1640
+ });
1641
+ steps = newSteps;
1642
+ save();
1643
+ render();
1644
+ }
1645
+ });
1646
+ }
1647
+
1648
+ function render() {
1649
+ const list = document.getElementById('step-builder-list');
1650
+ const empty = document.getElementById('step-builder-empty');
1651
+ if (!list) return;
1652
+
1653
+ if (steps.length === 0) {
1654
+ list.innerHTML = '';
1655
+ if (empty) empty.style.display = 'block';
1656
+ return;
1657
+ }
1658
+
1659
+ if (empty) empty.style.display = 'none';
1660
+
1661
+ list.innerHTML = steps.map((step, index) => {
1662
+ const fieldLabels = getFieldLabels(step.fields || []);
1663
+ return `
1664
+ <li class="step-builder-item" data-index="${index}">
1665
+ <span class="step-builder-drag">&#9776;</span>
1666
+ <div class="step-builder-info">
1667
+ <div class="step-builder-title">ステップ${index + 1}: ${escapeHtml(step.title)}</div>
1668
+ <div class="step-builder-fields">フィールド: ${fieldLabels || '(なし)'}</div>
1669
+ </div>
1670
+ <div class="step-builder-actions">
1671
+ <button type="button" class="btn btn-secondary btn-sm" onclick="StepBuilder.editStep(${index})">編集</button>
1672
+ <button type="button" class="btn btn-danger btn-sm" onclick="StepBuilder.deleteStep(${index})">削除</button>
1673
+ </div>
1674
+ </li>
1675
+ `;
1676
+ }).join('');
1677
+
1678
+ initSortable();
1679
+ }
1680
+
1681
+ function getFieldLabels(fieldNames) {
1682
+ if (!fieldNames || fieldNames.length === 0) return '';
1683
+ const fields = typeof FormBuilder !== 'undefined' ? FormBuilder.getFields() : [];
1684
+ return fieldNames.map(name => {
1685
+ const field = fields.find(f => f.name === name);
1686
+ return field ? (field.label || field.name) : name;
1687
+ }).join(', ');
1688
+ }
1689
+
1690
+ function escapeHtml(str) {
1691
+ if (!str) return '';
1692
+ return str.replace(/&/g, '&amp;')
1693
+ .replace(/</g, '&lt;')
1694
+ .replace(/>/g, '&gt;')
1695
+ .replace(/"/g, '&quot;');
1696
+ }
1697
+
1698
+ function save() {
1699
+ const input = document.getElementById('steps');
1700
+ if (input) {
1701
+ input.value = JSON.stringify(steps);
1702
+ }
1703
+ }
1704
+
1705
+ function showAddModal() {
1706
+ document.getElementById('step-modal-title').textContent = 'ステップ追加';
1707
+ document.getElementById('step-edit-index').value = '-1';
1708
+ document.getElementById('step-title').value = '';
1709
+ renderFieldSelector([]);
1710
+ document.getElementById('step-modal').style.display = 'flex';
1711
+ }
1712
+
1713
+ function editStep(index) {
1714
+ const step = steps[index];
1715
+ if (!step) return;
1716
+
1717
+ document.getElementById('step-modal-title').textContent = 'ステップ編集';
1718
+ document.getElementById('step-edit-index').value = index;
1719
+ document.getElementById('step-title').value = step.title || '';
1720
+ renderFieldSelector(step.fields || []);
1721
+ document.getElementById('step-modal').style.display = 'flex';
1722
+ }
1723
+
1724
+ function renderFieldSelector(selectedFields) {
1725
+ const selector = document.getElementById('step-fields-selector');
1726
+ const noFields = document.getElementById('step-no-fields');
1727
+ const fields = typeof FormBuilder !== 'undefined' ? FormBuilder.getFields() : [];
1728
+
1729
+ if (fields.length === 0) {
1730
+ selector.innerHTML = '<p class="text-muted">先にフィールドを追加してください</p>';
1731
+ return;
1732
+ }
1733
+
1734
+ selector.innerHTML = fields.map(field => {
1735
+ const checked = selectedFields.includes(field.name) ? 'checked' : '';
1736
+ return `
1737
+ <label class="step-field-option">
1738
+ <input type="checkbox" value="${escapeHtml(field.name)}" ${checked}>
1739
+ <span>${escapeHtml(field.label || field.name)}</span>
1740
+ </label>
1741
+ `;
1742
+ }).join('');
1743
+ }
1744
+
1745
+ function hideModal() {
1746
+ document.getElementById('step-modal').style.display = 'none';
1747
+ }
1748
+
1749
+ function saveStep() {
1750
+ const title = document.getElementById('step-title').value.trim();
1751
+ if (!title) {
1752
+ alert('ステップタイトルは必須です');
1753
+ return;
1754
+ }
1755
+
1756
+ const editIndex = parseInt(document.getElementById('step-edit-index').value);
1757
+
1758
+ // Collect selected fields
1759
+ const selectedFields = [];
1760
+ document.querySelectorAll('#step-fields-selector input[type="checkbox"]:checked').forEach(cb => {
1761
+ selectedFields.push(cb.value);
1762
+ });
1763
+
1764
+ const step = {
1765
+ title: title,
1766
+ fields: selectedFields
1767
+ };
1768
+
1769
+ if (editIndex >= 0) {
1770
+ steps[editIndex] = step;
1771
+ } else {
1772
+ steps.push(step);
1773
+ }
1774
+
1775
+ save();
1776
+ render();
1777
+ hideModal();
1778
+ }
1779
+
1780
+ function deleteStep(index) {
1781
+ if (!confirm('このステップを削除しますか?')) return;
1782
+ steps.splice(index, 1);
1783
+ save();
1784
+ render();
1785
+ }
1786
+
1787
+ // Initialize on DOM ready
1788
+ document.addEventListener('DOMContentLoaded', init);
1789
+
1790
+ // Close modal on escape
1791
+ document.addEventListener('keydown', function(e) {
1792
+ if (e.key === 'Escape') {
1793
+ const modal = document.getElementById('step-modal');
1794
+ if (modal && modal.style.display === 'flex') {
1795
+ hideModal();
1796
+ }
1797
+ }
1798
+ });
1799
+
1800
+ return {
1801
+ showAddModal,
1802
+ editStep,
1803
+ deleteStep,
1804
+ hideModal,
1805
+ saveStep
1460
1806
  };
1461
1807
  })();
1462
1808
  </script>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "focomy"
7
- version = "0.1.104"
7
+ version = "0.1.106"
8
8
  description = "The Most Beautiful CMS - A metadata-driven, zero-duplicate-code content management system"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes