collective.fragmentsblock 1.0.0a2__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 (86) hide show
  1. collective_fragmentsblock-1.0.0a2/.copier-answers.yml +14 -0
  2. collective_fragmentsblock-1.0.0a2/.editorconfig +21 -0
  3. collective_fragmentsblock-1.0.0a2/.github/workflows/ci.yml +114 -0
  4. collective_fragmentsblock-1.0.0a2/.gitignore +97 -0
  5. collective_fragmentsblock-1.0.0a2/.pre-commit-config.yaml +14 -0
  6. collective_fragmentsblock-1.0.0a2/CHANGELOG.md +73 -0
  7. collective_fragmentsblock-1.0.0a2/PKG-INFO +194 -0
  8. collective_fragmentsblock-1.0.0a2/README.md +160 -0
  9. collective_fragmentsblock-1.0.0a2/bundle-src/.nvmrc +1 -0
  10. collective_fragmentsblock-1.0.0a2/bundle-src/build-plugins/scope-wrap.ts +106 -0
  11. collective_fragmentsblock-1.0.0a2/bundle-src/package.json +40 -0
  12. collective_fragmentsblock-1.0.0a2/bundle-src/pnpm-lock.yaml +1666 -0
  13. collective_fragmentsblock-1.0.0a2/bundle-src/pnpm-workspace.yaml +4 -0
  14. collective_fragmentsblock-1.0.0a2/bundle-src/src/FragmentBlockEdit.tsx +39 -0
  15. collective_fragmentsblock-1.0.0a2/bundle-src/src/FragmentBlockView.tsx +33 -0
  16. collective_fragmentsblock-1.0.0a2/bundle-src/src/FragmentIcon.tsx +22 -0
  17. collective_fragmentsblock-1.0.0a2/bundle-src/src/fragments.ts +192 -0
  18. collective_fragmentsblock-1.0.0a2/bundle-src/src/index.tsx +55 -0
  19. collective_fragmentsblock-1.0.0a2/bundle-src/src/schema.ts +102 -0
  20. collective_fragmentsblock-1.0.0a2/bundle-src/src/styles.css +21 -0
  21. collective_fragmentsblock-1.0.0a2/bundle-src/src/types.ts +17 -0
  22. collective_fragmentsblock-1.0.0a2/bundle-src/test/fragment-block.test.tsx +361 -0
  23. collective_fragmentsblock-1.0.0a2/bundle-src/test/stubs/plone-helpers.ts +20 -0
  24. collective_fragmentsblock-1.0.0a2/bundle-src/test/stubs/plone-registry.ts +21 -0
  25. collective_fragmentsblock-1.0.0a2/bundle-src/tsconfig.json +12 -0
  26. collective_fragmentsblock-1.0.0a2/bundle-src/vite.config.ts +88 -0
  27. collective_fragmentsblock-1.0.0a2/bundle-src/vitest.config.ts +21 -0
  28. collective_fragmentsblock-1.0.0a2/docs/adr/0001-fragments-are-registered-static-markup.md +115 -0
  29. collective_fragmentsblock-1.0.0a2/docs/adr/0002-the-editor-fetches-the-fragment-list-from-the-server.md +83 -0
  30. collective_fragmentsblock-1.0.0a2/pyproject.toml +127 -0
  31. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/__init__.py +1 -0
  32. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/__init__.py +1 -0
  33. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/configure.zcml +8 -0
  34. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/services/__init__.py +1 -0
  35. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/services/configure.zcml +8 -0
  36. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/services/fragments/__init__.py +1 -0
  37. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/services/fragments/configure.zcml +13 -0
  38. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/api/services/fragments/get.py +19 -0
  39. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/configure.zcml +61 -0
  40. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/fragments.py +197 -0
  41. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/i18n.py +6 -0
  42. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/interfaces.py +35 -0
  43. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/README.rst +37 -0
  44. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/__init__.py +0 -0
  45. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/collective.fragmentsblock.pot +16 -0
  46. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/en/LC_MESSAGES/collective.fragmentsblock.po +15 -0
  47. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/update.py +68 -0
  48. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/locales/update.sh +12 -0
  49. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/meta.zcml +17 -0
  50. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/permissions.zcml +11 -0
  51. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/default/browserlayer.xml +7 -0
  52. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/default/catalog.xml +5 -0
  53. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/default/metadata.xml +8 -0
  54. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/default/registry.xml +20 -0
  55. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/default/rolemap.xml +7 -0
  56. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/uninstall/browserlayer.xml +7 -0
  57. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/uninstall/metadata.xml +4 -0
  58. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/profiles/uninstall/registry.xml +11 -0
  59. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/setuphandlers.py +22 -0
  60. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/static/fragment-block.css +16 -0
  61. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/static/fragment-block.js +243 -0
  62. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/static/fragment-block.js.map +1 -0
  63. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/testing.py +71 -0
  64. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/__init__.py +0 -0
  65. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/conftest.py +16 -0
  66. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/fixture.zcml +8 -0
  67. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/fragments/contact-box.html +4 -0
  68. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/fragments/not an id.html +1 -0
  69. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/fragments/seal.html +4 -0
  70. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_fragments.py +181 -0
  71. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_service_fragments.py +47 -0
  72. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_setup.py +116 -0
  73. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_upgrade_step_1001.py +47 -0
  74. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_view_fragment_block_view.py +120 -0
  75. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/tests/test_zcml.py +86 -0
  76. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/1001/.gitkeep +0 -0
  77. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/1001/metadata.txt +1 -0
  78. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/1001.zcml +31 -0
  79. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/__init__.py +1 -0
  80. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/configure.zcml +8 -0
  81. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/upgrades/v1001.py +14 -0
  82. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/views/__init__.py +1 -0
  83. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/views/configure.zcml +14 -0
  84. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/views/fragment_block_view.py +23 -0
  85. collective_fragmentsblock-1.0.0a2/src/collective/fragmentsblock/zcml.py +39 -0
  86. collective_fragmentsblock-1.0.0a2/uv.lock +6706 -0
@@ -0,0 +1,14 @@
1
+ # Do not edit this file manually.
2
+ # This file is auto-generated by Copier.
3
+ # Changes here will be overwritten when you update the template.
4
+ _src_path: /home/dev/.copier-templates/plone-copier-templates/backend_addon
5
+ author_email: md@derico.de
6
+ author_name: Maik Derstappen
7
+ github_organization: collective
8
+ is_headless: false
9
+ package_description: 'Aurora fragment block: reusable content fragments referenced
10
+ live from central fragment documents'
11
+ package_name: collective.fragmentsblock
12
+ package_title: Collective Fragmentsblock
13
+ plone_version: '6.0'
14
+
@@ -0,0 +1,21 @@
1
+ [*]
2
+ charset = utf-8
3
+ end_of_line = lf
4
+ indent_style = space
5
+ insert_final_newline = true
6
+ trim_trailing_whitespace = true
7
+
8
+ [*.py]
9
+ indent_size = 4
10
+
11
+ [*.{cfg,ini,toml}]
12
+ indent_size = 4
13
+
14
+ [*.{xml,zcml,html,pt,zpt}]
15
+ indent_size = 2
16
+
17
+ [*.{css,js,json}]
18
+ indent_size = 2
19
+
20
+ [*.{yml,yaml}]
21
+ indent_size = 2
@@ -0,0 +1,114 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ env:
16
+ BLICCA_TOKEN: ${{ secrets.BLICCA_TOKEN }}
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.10", "3.11", "3.12"]
21
+
22
+ steps:
23
+ # plone.blicca.auroraeditor is PRIVATE and not on PyPI: it is the editor
24
+ # this block mounts in and the source of the rendering API the package
25
+ # imports, so without it nothing installs and nothing is tested. A
26
+ # missing token is a red job on purpose — a green run over a package
27
+ # that could not be installed says nothing.
28
+ - name: Require the host checkout token
29
+ if: env.BLICCA_TOKEN == ''
30
+ run: |
31
+ echo "::error::BLICCA_TOKEN is not set. This package depends on"
32
+ echo "::error::plone.blicca.auroraeditor, which is private and not"
33
+ echo "::error::on PyPI, so the tests cannot run."
34
+ exit 1
35
+
36
+ # pyproject resolves the sources below from `../<name>`, so this
37
+ # repository checks itself out into a subdirectory and its siblings land
38
+ # beside it, exactly as they sit under the site's `sources/`.
39
+ - uses: actions/checkout@v5
40
+ with:
41
+ path: collective.fragmentsblock
42
+
43
+ - uses: actions/checkout@v5
44
+ with:
45
+ repository: derico-de/plone.blicca.auroraeditor
46
+ token: ${{ env.BLICCA_TOKEN }}
47
+ path: plone.blicca.auroraeditor
48
+
49
+ - name: Set up uv and Python ${{ matrix.python-version }}
50
+ uses: astral-sh/setup-uv@v10.0.1
51
+ with:
52
+ enable-cache: true
53
+ python-version: ${{ matrix.python-version }}
54
+
55
+ - name: Run tests
56
+ working-directory: collective.fragmentsblock
57
+ run: uv run --frozen --extra test pytest -v
58
+
59
+ lint:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v5
63
+
64
+ - name: Set up uv
65
+ uses: astral-sh/setup-uv@v10.0.1
66
+ with:
67
+ enable-cache: true
68
+
69
+ - name: Run ruff
70
+ run: uvx ruff@0.16.6 check .
71
+
72
+ bundle:
73
+ # The editor half is committed build output — there is no Node on the
74
+ # deploy host (block add-on contract §3.1) — so nothing rebuilds it at
75
+ # install time and it rots the moment someone edits bundle-src/src/ and
76
+ # forgets. The `git diff --exit-code` below is what makes committing an
77
+ # artifact safe rather than merely convenient.
78
+ runs-on: ubuntu-latest
79
+ steps:
80
+ - uses: actions/checkout@v5
81
+
82
+ # pnpm is pinned here rather than by a `packageManager` field in
83
+ # bundle-src/package.json: pnpm 12 answers that field by writing a
84
+ # `packageManagerDependencies` block into the lockfile — its own binary
85
+ # for every platform — and the lockfile is not the place for that.
86
+ - uses: pnpm/action-setup@v4
87
+ with:
88
+ version: 12.3.4
89
+
90
+ - name: Set up Node
91
+ uses: actions/setup-node@v4
92
+ with:
93
+ node-version-file: bundle-src/.nvmrc
94
+ cache: pnpm
95
+ cache-dependency-path: bundle-src/pnpm-lock.yaml
96
+
97
+ # `working-directory`, not `--dir`: the repository root has no
98
+ # package.json, and pnpm resolves the workspace from where it stands.
99
+ - name: Install
100
+ run: pnpm install --frozen-lockfile
101
+ working-directory: bundle-src
102
+
103
+ - name: Build
104
+ run: pnpm build
105
+ working-directory: bundle-src
106
+
107
+ # After the build: the harness reads the built artifact.
108
+ - name: Test
109
+ run: pnpm test
110
+ working-directory: bundle-src
111
+
112
+ # If this fails, someone edited bundle-src/src/ and did not rebuild.
113
+ - name: The committed bundle is the one this source builds
114
+ run: git diff --exit-code -- src/collective/fragmentsblock/static/
@@ -0,0 +1,97 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # PyInstaller
27
+ *.manifest
28
+ *.spec
29
+
30
+ # Installer logs
31
+ pip-log.txt
32
+ pip-delete-this-directory.txt
33
+
34
+ # Unit test / coverage reports
35
+ htmlcov/
36
+ .tox/
37
+ .nox/
38
+ .coverage
39
+ .coverage.*
40
+ .cache
41
+ nosetests.xml
42
+ coverage.xml
43
+ *.cover
44
+ *.py,cover
45
+ .hypothesis/
46
+ .pytest_cache/
47
+
48
+ # Type checkers / linters
49
+ .mypy_cache/
50
+ .pyre/
51
+ .pytype/
52
+ .ruff_cache/
53
+
54
+ # Translations (compiled catalogs are generated; .pot/.po are tracked)
55
+ *.mo
56
+
57
+ # Environments
58
+ .env
59
+ .venv
60
+ env/
61
+ venv/
62
+ ENV/
63
+ env.bak/
64
+ venv.bak/
65
+
66
+ # IDEs / editors
67
+ .idea/
68
+ .vscode/
69
+ *.swp
70
+ *.swo
71
+ *~
72
+
73
+ # Node / frontend tooling (themes, mockup patterns, svelte apps)
74
+ node_modules/
75
+ npm-debug.log*
76
+ yarn-debug.log*
77
+ yarn-error.log*
78
+ pnpm-debug.log*
79
+ .pnpm-store/
80
+ .parcel-cache/
81
+ .vite/
82
+
83
+ # Build outputs from frontend tooling (in addition to top-level dist/)
84
+ src/collective/fragmentsblock/**/dist/
85
+ src/collective/fragmentsblock/**/.cache/
86
+
87
+ # Plone / Zope runtime data
88
+ *.fs
89
+ *.fs.index
90
+ *.fs.lock
91
+ *.fs.tmp
92
+ blobs/
93
+
94
+ # Project specific
95
+ *.log
96
+ .DS_Store
97
+ Thumbs.db
@@ -0,0 +1,14 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.5.0
4
+ hooks:
5
+ - id: check-yaml
6
+ - id: end-of-file-fixer
7
+ - id: trailing-whitespace
8
+ - id: check-merge-conflict
9
+ - repo: https://github.com/astral-sh/ruff-pre-commit
10
+ rev: v0.3.0
11
+ hooks:
12
+ - id: ruff
13
+ args: [--fix]
14
+ - id: ruff-format
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0a2 (2026-10-08)
4
+
5
+
6
+ - Nothing changed yet.
7
+
8
+
9
+ ## 1.0.0a1 (2026-10-04)
10
+
11
+ - The editor needs nothing from a provider add-on any more. The fragment
12
+ block's `install()` fetches the new `@fragments` service and registers
13
+ every provider's fragments before the first render; the host awaits it
14
+ (`plone.blicca.auroraeditor` 1.0.0a3, block-api 1.2, declared on the
15
+ record by upgrade step 1001). A theme ships its `fragments/*.html` and
16
+ the `fragments:folder` line, no editor bundle, no record, no rebuild.
17
+ `IFragmentsProvider` gains `records()`; a fragment's title is its
18
+ first-line `<!-- title: … -->` comment, else derived from the file name
19
+ (ADR 0002).
20
+
21
+ - Add the `fragments:folder` ZCML directive. A provider add-on registers
22
+ its fragments folder with one line in `configure.zcml` and no Python
23
+ code. The folder is resolved relative to the package, checked at
24
+ startup, and registered under the package name unless `name` is given.
25
+
26
+ - Register the `INonInstallable` utility that hides the uninstall profile.
27
+ `HiddenProfiles` was defined in `setuphandlers.py` but never registered in
28
+ `configure.zcml`, so it hid nothing: harmless while this package has no
29
+ upgrade profile, and a profile offered as an installable add-on of its own
30
+ the moment one is scaffolded.
31
+
32
+ - A fragment block now **names itself with the fragment's title** while blocks
33
+ are dragged. The editor collapses the canvas to one row per block and reads
34
+ each row's identifying line off the rendered block — but a fragment is a
35
+ piece of a design and is often pure decoration with no text at all, so every
36
+ fragment row read "Fragment" against an empty line and two of them in one
37
+ page were indistinguishable exactly while being reordered. The view stamps
38
+ `data-block-summary` with the record's title (block add-on contract §1.6),
39
+ which is the same string the picker offers, so the row reads "Fragment —
40
+ Balkenlage (Trenner)". Editing affordance only: the attribute is inert on
41
+ the classic page, whose server-rendered markup does not carry it.
42
+
43
+ - The fragment block's settings form now carries **block width** and
44
+ **background** — the two placement controls, offered because a fragment
45
+ is a piece of a design rather than a whole one: a contact box wants the
46
+ narrow column, a divider the full bleed, and the same file may want both
47
+ on two different pages. Both are Aurora style fields, so the editor and
48
+ the classic renderer stamp the class and custom properties on the block
49
+ wrapper generically — no change to either view component or to
50
+ `@@aurora-block-fragment`. Width defaults to `default`, the width
51
+ fragment blocks already rendered at, so existing content is unchanged;
52
+ the background slots are read from the host's registered palette and the
53
+ field is omitted on a host that registers none.
54
+
55
+ - Initial release: the Aurora **fragment block** (`@type: fragment`) —
56
+ drop registered **design fragments** (static HTML files, typically cut
57
+ verbatim from a design mockup) into any Aurora-edited page. Aurora-first:
58
+ provider add-ons register `{id, title, html}` records into the shared
59
+ `@plone/registry` singleton from their install function
60
+ (`type: collective.fragmentsblock.fragment`); the block enumerates them
61
+ for its picker and renders the markup client-side, as-is, with optional
62
+ `${var}` substitution from the block's persisted `variables` mapping
63
+ (HTML-escaped, missing variables empty). Blicca classic pages render the
64
+ same file server-side via `@@aurora-block-fragment` and named
65
+ `IFragmentsProvider` utilities (`FragmentsFolder` reads
66
+ `<id>.html` files from a provider package directory) with identical
67
+ substitution semantics. Fail-soft placeholders everywhere; ships both
68
+ halves of the Blicca block add-on contract, with the committed editor
69
+ bundle built from `bundle-src/`
70
+ (`@plone-collective/aurora-fragment-block`). Variables render on both
71
+ surfaces but have no sidebar UI yet — see
72
+ `docs/adr/0001-fragments-are-registered-static-markup.md` for the design
73
+ and its deferred parts.
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.5
2
+ Name: collective.fragmentsblock
3
+ Version: 1.0.0a2
4
+ Summary: Aurora fragment block: static design fragments registered by add-ons, rendered as-is
5
+ Project-URL: Homepage, https://github.com/collective/collective.fragmentsblock
6
+ Project-URL: Documentation, https://github.com/collective/collective.fragmentsblock
7
+ Project-URL: Repository, https://github.com/collective/collective.fragmentsblock.git
8
+ Project-URL: Issues, https://github.com/collective/collective.fragmentsblock/issues
9
+ Author-email: Maik Derstappen <md@derico.de>
10
+ License: GPL-2.0-or-later
11
+ Keywords: CMS,Plone
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: Plone
15
+ Classifier: Framework :: Plone :: 6.0
16
+ Classifier: Framework :: Plone :: Addon
17
+ Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: plone-blicca-auroraeditor>=1.0.0a3
25
+ Requires-Dist: plone-restapi
26
+ Requires-Dist: plone>=6.0
27
+ Provides-Extra: test
28
+ Requires-Dist: plone-app-testing; extra == 'test'
29
+ Requires-Dist: plone-testing; extra == 'test'
30
+ Requires-Dist: pytest; extra == 'test'
31
+ Requires-Dist: pytest-cov; extra == 'test'
32
+ Requires-Dist: pytest-plone>=0.5.0; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # collective.fragmentsblock
36
+
37
+ A **Fragment** block for the Aurora editor in [Plone](https://plone.org) Blicca.
38
+
39
+ A fragment is a static piece of HTML, usually cut straight from the design
40
+ mockup: a contact box, a seal, a call-to-action banner, a partner-logo
41
+ strip. Your theme ships it as a file in a folder it registers with one line
42
+ of ZCML. Editors then pick it from a list and drop it into a page. The
43
+ markup is rendered exactly as it is in the file, both in the editor and on
44
+ published pages.
45
+
46
+ Editors never edit the markup. When the file changes, every page using the
47
+ fragment updates on the next deployment.
48
+
49
+ ## Installation
50
+
51
+ Add `collective.fragmentsblock` to your project dependencies and install it
52
+ in the Plone add-ons control panel.
53
+
54
+ Requirements: Plone 6, Python 3.10 or newer, and `plone.blicca.auroraeditor`
55
+ 1.0.0a3 or newer (block-api 1.2: the editor waits for this block to fetch its
56
+ fragments before it renders).
57
+
58
+ This package only provides the block. Fragments come from your own add-on,
59
+ typically the theme package. See the next section.
60
+
61
+ ## Register a fragment
62
+
63
+ Fragments live in a folder of your add-on, one HTML file per fragment. The
64
+ server reads the files when it renders classic pages, and the editor fetches
65
+ the same files from the server when it opens. Nothing is built, bundled or
66
+ copied.
67
+
68
+ ```
69
+ src/my/theme/
70
+ ├── fragments/
71
+ │ ├── contact-box.html
72
+ │ └── seal.html
73
+ └── configure.zcml
74
+ ```
75
+
76
+ The file name is the fragment id. Ids must match `^[A-Za-z0-9][A-Za-z0-9_-]*$`
77
+ and are unique across the whole site, so prefix them if more than one
78
+ add-on may provide fragments. A file whose name is not a valid id is skipped
79
+ with a warning.
80
+
81
+ Register the folder in your `configure.zcml`:
82
+
83
+ ```xml
84
+ <!-- my/theme/configure.zcml -->
85
+ <configure
86
+ xmlns="http://namespaces.zope.org/zope"
87
+ xmlns:fragments="http://namespaces.plone.org/fragmentsblock">
88
+
89
+ <include package="collective.fragmentsblock" file="meta.zcml" />
90
+
91
+ <fragments:folder directory="fragments" />
92
+
93
+ </configure>
94
+ ```
95
+
96
+ `directory` is relative to your package. The folder is registered under
97
+ your package name. Pass `name="..."` if you need another one. A folder that
98
+ does not exist is an error at startup, not an empty picker.
99
+
100
+ That is all. Restart Plone and the fragments appear in the picker.
101
+
102
+ ### Titles
103
+
104
+ Editors pick a fragment by its title. Put it in a comment on the first line
105
+ of the file:
106
+
107
+ ```html
108
+ <!-- title: Contact box -->
109
+ <aside class="contact-box">
110
+ ...
111
+ </aside>
112
+ ```
113
+
114
+ Without that comment the title is derived from the file name:
115
+ `contact-box.html` becomes "Contact box", `partner_logos.html` becomes
116
+ "Partner logos". The comment is part of the markup and is rendered with it;
117
+ browsers ignore it.
118
+
119
+ ## Add a fragment to a page
120
+
121
+ 1. Open the page in the Aurora editor.
122
+ 2. Type `/` and choose **Fragment** from the slash menu.
123
+ 3. In the block settings, pick the fragment from the **Fragment** list.
124
+
125
+ The fragment renders immediately. Two more settings control placement:
126
+
127
+ - **Block width**: `narrow`, `default`, `layout` or `full`. Defaults to
128
+ `default`.
129
+ - **Background**: a named colour slot from your theme's palette, such as
130
+ `Grey` or `Accent`. The field only appears if the theme registers a
131
+ palette.
132
+
133
+ Both paint the block wrapper around the fragment, not the fragment itself.
134
+
135
+ If a fragment's add-on gets uninstalled, the editor shows a note in place
136
+ of the block and the published page renders nothing there. Pages never
137
+ break.
138
+
139
+ ## Good to know
140
+
141
+ - **Fragment HTML is trusted.** It ships with your add-on and is not
142
+ sanitised. A `<script>` in it runs on the published page.
143
+ - **Variables.** Markup may contain `${name}` tokens. They are filled from
144
+ the block's `variables` mapping, HTML-escaped, with missing names
145
+ rendered empty. There is no editor UI for this yet. The mapping can be
146
+ set through the REST API or an upgrade step. For now, if a fragment
147
+ needs different values on different pages, ship one fragment per
148
+ variant.
149
+ - **Wrapper.** Both the editor and the server wrap the markup in a
150
+ `<div class="block-fragment">`. Mockup HTML written for a grid or flex
151
+ parent needs to account for that.
152
+ - **The editor fetches `@fragments`.** When the editor opens, the fragment
153
+ block requests `<site>/@fragments` with the session cookie and registers
154
+ what comes back before the first render. The list is every provider's
155
+ `records()`; where two providers use the same id, the one whose name sorts
156
+ first wins, on both surfaces. If the request fails, the block stays
157
+ available with an empty picker and existing fragment blocks say so instead
158
+ of rendering nothing.
159
+ - **Other sources.** A folder of files is the stock provider. For anything
160
+ else, register a named utility that implements `IFragmentsProvider`:
161
+ `get(fragment_id)` returns the HTML or `None`, and `records()` lists every
162
+ fragment as `{"id", "title", "html"}`.
163
+ - **Registering from JavaScript still works.** An Aurora add-on may register
164
+ fragments itself, with `registerFragment(config, {id, title, html})` from
165
+ the npm package `@plone-collective/aurora-fragment-block` or with a bare
166
+ `config.registerUtility` of type `collective.fragmentsblock.fragment`. The
167
+ server then needs a matching provider, or the fragment renders only in the
168
+ editor.
169
+
170
+ ## Development
171
+
172
+ The editor half lives in `bundle-src/` and is built into the Python
173
+ package's `static/` folder. The build output is committed.
174
+
175
+ ```shell
176
+ cd bundle-src
177
+ pnpm install
178
+ pnpm build
179
+ pnpm test
180
+ ```
181
+
182
+ Python tests:
183
+
184
+ ```shell
185
+ uv run --extra test pytest
186
+ ```
187
+
188
+ ## License
189
+
190
+ GPLv2 for the Python package, MIT for the npm package.
191
+
192
+ ## Author
193
+
194
+ Maik Derstappen, [derico.de](https://derico.de), <md@derico.de>
@@ -0,0 +1,160 @@
1
+ # collective.fragmentsblock
2
+
3
+ A **Fragment** block for the Aurora editor in [Plone](https://plone.org) Blicca.
4
+
5
+ A fragment is a static piece of HTML, usually cut straight from the design
6
+ mockup: a contact box, a seal, a call-to-action banner, a partner-logo
7
+ strip. Your theme ships it as a file in a folder it registers with one line
8
+ of ZCML. Editors then pick it from a list and drop it into a page. The
9
+ markup is rendered exactly as it is in the file, both in the editor and on
10
+ published pages.
11
+
12
+ Editors never edit the markup. When the file changes, every page using the
13
+ fragment updates on the next deployment.
14
+
15
+ ## Installation
16
+
17
+ Add `collective.fragmentsblock` to your project dependencies and install it
18
+ in the Plone add-ons control panel.
19
+
20
+ Requirements: Plone 6, Python 3.10 or newer, and `plone.blicca.auroraeditor`
21
+ 1.0.0a3 or newer (block-api 1.2: the editor waits for this block to fetch its
22
+ fragments before it renders).
23
+
24
+ This package only provides the block. Fragments come from your own add-on,
25
+ typically the theme package. See the next section.
26
+
27
+ ## Register a fragment
28
+
29
+ Fragments live in a folder of your add-on, one HTML file per fragment. The
30
+ server reads the files when it renders classic pages, and the editor fetches
31
+ the same files from the server when it opens. Nothing is built, bundled or
32
+ copied.
33
+
34
+ ```
35
+ src/my/theme/
36
+ ├── fragments/
37
+ │ ├── contact-box.html
38
+ │ └── seal.html
39
+ └── configure.zcml
40
+ ```
41
+
42
+ The file name is the fragment id. Ids must match `^[A-Za-z0-9][A-Za-z0-9_-]*$`
43
+ and are unique across the whole site, so prefix them if more than one
44
+ add-on may provide fragments. A file whose name is not a valid id is skipped
45
+ with a warning.
46
+
47
+ Register the folder in your `configure.zcml`:
48
+
49
+ ```xml
50
+ <!-- my/theme/configure.zcml -->
51
+ <configure
52
+ xmlns="http://namespaces.zope.org/zope"
53
+ xmlns:fragments="http://namespaces.plone.org/fragmentsblock">
54
+
55
+ <include package="collective.fragmentsblock" file="meta.zcml" />
56
+
57
+ <fragments:folder directory="fragments" />
58
+
59
+ </configure>
60
+ ```
61
+
62
+ `directory` is relative to your package. The folder is registered under
63
+ your package name. Pass `name="..."` if you need another one. A folder that
64
+ does not exist is an error at startup, not an empty picker.
65
+
66
+ That is all. Restart Plone and the fragments appear in the picker.
67
+
68
+ ### Titles
69
+
70
+ Editors pick a fragment by its title. Put it in a comment on the first line
71
+ of the file:
72
+
73
+ ```html
74
+ <!-- title: Contact box -->
75
+ <aside class="contact-box">
76
+ ...
77
+ </aside>
78
+ ```
79
+
80
+ Without that comment the title is derived from the file name:
81
+ `contact-box.html` becomes "Contact box", `partner_logos.html` becomes
82
+ "Partner logos". The comment is part of the markup and is rendered with it;
83
+ browsers ignore it.
84
+
85
+ ## Add a fragment to a page
86
+
87
+ 1. Open the page in the Aurora editor.
88
+ 2. Type `/` and choose **Fragment** from the slash menu.
89
+ 3. In the block settings, pick the fragment from the **Fragment** list.
90
+
91
+ The fragment renders immediately. Two more settings control placement:
92
+
93
+ - **Block width**: `narrow`, `default`, `layout` or `full`. Defaults to
94
+ `default`.
95
+ - **Background**: a named colour slot from your theme's palette, such as
96
+ `Grey` or `Accent`. The field only appears if the theme registers a
97
+ palette.
98
+
99
+ Both paint the block wrapper around the fragment, not the fragment itself.
100
+
101
+ If a fragment's add-on gets uninstalled, the editor shows a note in place
102
+ of the block and the published page renders nothing there. Pages never
103
+ break.
104
+
105
+ ## Good to know
106
+
107
+ - **Fragment HTML is trusted.** It ships with your add-on and is not
108
+ sanitised. A `<script>` in it runs on the published page.
109
+ - **Variables.** Markup may contain `${name}` tokens. They are filled from
110
+ the block's `variables` mapping, HTML-escaped, with missing names
111
+ rendered empty. There is no editor UI for this yet. The mapping can be
112
+ set through the REST API or an upgrade step. For now, if a fragment
113
+ needs different values on different pages, ship one fragment per
114
+ variant.
115
+ - **Wrapper.** Both the editor and the server wrap the markup in a
116
+ `<div class="block-fragment">`. Mockup HTML written for a grid or flex
117
+ parent needs to account for that.
118
+ - **The editor fetches `@fragments`.** When the editor opens, the fragment
119
+ block requests `<site>/@fragments` with the session cookie and registers
120
+ what comes back before the first render. The list is every provider's
121
+ `records()`; where two providers use the same id, the one whose name sorts
122
+ first wins, on both surfaces. If the request fails, the block stays
123
+ available with an empty picker and existing fragment blocks say so instead
124
+ of rendering nothing.
125
+ - **Other sources.** A folder of files is the stock provider. For anything
126
+ else, register a named utility that implements `IFragmentsProvider`:
127
+ `get(fragment_id)` returns the HTML or `None`, and `records()` lists every
128
+ fragment as `{"id", "title", "html"}`.
129
+ - **Registering from JavaScript still works.** An Aurora add-on may register
130
+ fragments itself, with `registerFragment(config, {id, title, html})` from
131
+ the npm package `@plone-collective/aurora-fragment-block` or with a bare
132
+ `config.registerUtility` of type `collective.fragmentsblock.fragment`. The
133
+ server then needs a matching provider, or the fragment renders only in the
134
+ editor.
135
+
136
+ ## Development
137
+
138
+ The editor half lives in `bundle-src/` and is built into the Python
139
+ package's `static/` folder. The build output is committed.
140
+
141
+ ```shell
142
+ cd bundle-src
143
+ pnpm install
144
+ pnpm build
145
+ pnpm test
146
+ ```
147
+
148
+ Python tests:
149
+
150
+ ```shell
151
+ uv run --extra test pytest
152
+ ```
153
+
154
+ ## License
155
+
156
+ GPLv2 for the Python package, MIT for the npm package.
157
+
158
+ ## Author
159
+
160
+ Maik Derstappen, [derico.de](https://derico.de), <md@derico.de>