├── .babelrc
├── .github
└── FUNDING.yml
├── .gitignore
├── .nvmrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── config
├── install
│ └── patternkit.settings.yml
├── optional
│ ├── tour.tour.patternkit_admin.yml
│ └── tour.tour.patternkit_block.yml
└── schema
│ ├── patternkit.data_types.schema.yml
│ └── patternkit.schema.yml
├── css
├── bootstrap.min.css
├── cygnet
│ ├── cygnet--overrides.css
│ ├── cygnet--prefixed-for-drupal--upstream.css
│ ├── cygnet--prefixed-for-drupal.css
│ └── cygnet.css
├── patternkit.css
├── prosemirror.css
├── quill.snow.css
└── sceditor
│ ├── default.min.css
│ └── famfamfam.png
├── drupalci.yml
├── drush.services.yml
├── fonts
└── materialicons.woff2
├── gulpfile.js
├── js
├── README.md
├── ajv.min.js
├── ajv.min.js.map
├── handlebars.js
├── handlebars.min.js
├── jsoneditor.js
├── nonmin
│ ├── jsoneditor.js
│ └── jsoneditor.js.map
├── patternkit.jsoneditor.ckeditor.es6.js
├── patternkit.jsoneditor.cygnet.es6.js
├── patternkit.jsoneditor.editor.array.es6.js
├── patternkit.jsoneditor.editor.object.es6.js
├── patternkit.jsoneditor.es6.js
├── patternkit.jsoneditor.js
├── patternkit.jsoneditor.prosemirror.es6.js
├── patternkit.jsoneditor.quill.es6.js
├── quill.js
├── quill.min.js
└── sceditor.min.js
├── modules
├── patternkit_example
│ ├── README.md
│ ├── composer.json
│ ├── lib
│ │ └── patternkit
│ │ │ ├── .eslintrc
│ │ │ ├── .gitignore
│ │ │ ├── .nvmrc
│ │ │ ├── .patternlabrc
│ │ │ ├── .sass-lint.yml
│ │ │ ├── Gulpfile.js
│ │ │ ├── README.md
│ │ │ ├── dist
│ │ │ ├── patternkit.css
│ │ │ └── patternkit.min.js
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ └── atoms
│ │ │ │ ├── example
│ │ │ │ ├── dist
│ │ │ │ │ ├── example.css
│ │ │ │ │ └── example.min.js
│ │ │ │ ├── index.js
│ │ │ │ └── src
│ │ │ │ │ ├── example.json
│ │ │ │ │ ├── example.md
│ │ │ │ │ ├── example.scss
│ │ │ │ │ ├── example.ts
│ │ │ │ │ ├── example.twig
│ │ │ │ │ └── example.yml
│ │ │ │ ├── example_filtered
│ │ │ │ ├── dist
│ │ │ │ │ ├── example_filtered.css
│ │ │ │ │ └── example_filtered.min.js
│ │ │ │ ├── index.js
│ │ │ │ └── src
│ │ │ │ │ ├── example_filtered.json
│ │ │ │ │ ├── example_filtered.md
│ │ │ │ │ ├── example_filtered.scss
│ │ │ │ │ ├── example_filtered.ts
│ │ │ │ │ ├── example_filtered.twig
│ │ │ │ │ └── example_filtered.yml
│ │ │ │ └── example_ref
│ │ │ │ ├── dist
│ │ │ │ ├── example_ref.css
│ │ │ │ └── example_ref.min.js
│ │ │ │ ├── index.js
│ │ │ │ └── src
│ │ │ │ ├── example_ref.json
│ │ │ │ ├── example_ref.md
│ │ │ │ ├── example_ref.scss
│ │ │ │ ├── example_ref.ts
│ │ │ │ ├── example_ref.twig
│ │ │ │ └── example_ref.yml
│ │ │ └── tslint.json
│ ├── patternkit_example.info.yml
│ ├── patternkit_example.libraries.yml
│ └── patternkit_example.module
└── patternkit_media_library
│ ├── .babelrc
│ ├── .gitignore
│ ├── .nvmrc
│ ├── README.md
│ ├── composer.json
│ ├── config
│ ├── install
│ │ └── patternkit_media_library.settings.yml
│ └── schema
│ │ └── patternkit_media_library.schema.yml
│ ├── css
│ └── media-library-modal.css
│ ├── gulpfile.js
│ ├── js
│ ├── patternkit.jsoneditor.media_library.es6.js
│ └── patternkit.jsoneditor.media_library.js
│ ├── npm-shrinkwrap.json
│ ├── package.json
│ ├── patternkit_media_library.info.yml
│ ├── patternkit_media_library.install
│ ├── patternkit_media_library.libraries.yml
│ ├── patternkit_media_library.links.task.yml
│ ├── patternkit_media_library.module
│ ├── patternkit_media_library.routing.yml
│ ├── patternkit_media_library.services.yml
│ └── src
│ ├── Controller
│ └── PatternkitMediaLibraryController.php
│ ├── Form
│ └── MediaLibrarySettingsForm.php
│ └── MediaLibraryJSONLibraryOpener.php
├── npm-shrinkwrap.json
├── package.json
├── patternkit.api.php
├── patternkit.info.yml
├── patternkit.install
├── patternkit.libraries.yml
├── patternkit.links.action.yml
├── patternkit.links.contextual.yml
├── patternkit.links.menu.yml
├── patternkit.links.task.yml
├── patternkit.module
├── patternkit.polyfil.php
├── patternkit.routing.yml
├── patternkit.services.yml
├── src
├── AJAX
│ └── PatternkitEditorUpdateCommand.php
├── Annotation
│ ├── PatternFieldProcessor.php
│ └── PatternLibrary.php
├── Asset
│ ├── Library.php
│ ├── LibraryInterface.php
│ ├── LibraryNamespaceResolver.php
│ ├── LibraryNamespaceResolverInterface.php
│ ├── PatternDiscovery.php
│ ├── PatternDiscoveryCollector.php
│ ├── PatternDiscoveryInterface.php
│ ├── PatternLibraryParser
│ │ ├── FilePatternLibraryParser.php
│ │ ├── JSONPatternLibraryParser.php
│ │ ├── RESTPatternLibraryParser.php
│ │ ├── TwigPatternLibraryParser.php
│ │ └── WebcomponentPatternLibraryParser.php
│ ├── PatternLibraryParserBase.php
│ └── PatternLibraryParserInterface.php
├── Commands
│ └── PatternkitCommands.php
├── Controller
│ └── PatternkitController.php
├── Element
│ ├── Pattern.php
│ └── PatternError.php
├── Entity
│ ├── Pattern.php
│ ├── PatternInterface.php
│ ├── PatternRouteProvider.php
│ └── PatternkitBlock.php
├── Exception
│ ├── SchemaException.php
│ ├── SchemaReferenceException.php
│ └── SchemaValidationException.php
├── Form
│ ├── PatternLibraryJSONForm.php
│ ├── PatternkitForm.php
│ ├── PatternkitSettingsForm.php
│ └── PatternkitTranslateBlockForm.php
├── FormElement
│ └── PatternkitJson.php
├── JSONSchemaEditorTrait.php
├── Loader
│ └── PatternLibraryLoader.php
├── PathProcessor
│ └── PathProcessorPatterns.php
├── PatternEditorConfig.php
├── PatternFieldProcessorPluginManager.php
├── PatternLibrary.php
├── PatternLibraryJSONParserTrait.php
├── PatternLibraryPluginDefault.php
├── PatternLibraryPluginDefinition.php
├── PatternLibraryPluginInterface.php
├── PatternLibraryPluginManager.php
├── PatternTranslationHandler.php
├── Plugin
│ ├── Block
│ │ └── PatternkitBlock.php
│ ├── Derivative
│ │ └── PatternkitBlock.php
│ ├── Field
│ │ └── FieldType
│ │ │ └── SerializedData.php
│ ├── Menu
│ │ └── LocalAction
│ │ │ └── PatternkitAddLocalAction.php
│ ├── PatternFieldProcessor
│ │ ├── PatternFieldProcessorBase.php
│ │ ├── PatternFieldProcessorInterface.php
│ │ ├── TokenProcessor.php
│ │ └── WysiwygFieldProcessor.php
│ └── PatternLibrary
│ │ ├── PatternLibraryFile.php
│ │ ├── PatternLibraryJSON.php
│ │ ├── PatternLibraryREST.php
│ │ └── PatternLibraryTwig.php
├── Routing
│ └── PatternkitTranslateBlockFormRouteSubscriber.php
├── Schema
│ ├── DataPreProcessor
│ │ └── ObjectCoercionDataPreProcessor.php
│ ├── PatternkitRefProvider.php
│ ├── SchemaFactory.php
│ ├── SchemaHelper.php
│ ├── SchemaIterator.php
│ ├── SchemaWalker.php
│ └── SchemaWalkerFactory.php
└── StreamWrapper
│ ├── LibraryStream.php
│ └── PatternkitStream.php
├── templates
└── patternkit-add-list.html.twig
└── tests
├── modules
└── patternkit_test
│ ├── lib
│ ├── patternkit_test.info.yml
│ ├── patternkit_test.libraries.yml
│ └── src
│ └── Plugin
│ └── PatternFieldProcessor
│ └── ExceptionThrowerProcessor.php
└── src
├── Functional
├── PatternkitSettingsTest.php
└── PatternkitTest.php
├── Kernel
├── Asset
│ ├── LibraryNamespaceResolverTest.php
│ ├── LibraryTest.php
│ ├── PatternDiscoveryTest.php
│ └── PatternLibraryParser
│ │ ├── JsonPatternLibraryParserTest.php
│ │ ├── PatternLibraryParserTestBase.php
│ │ └── TwigPatternLibraryParserTest.php
├── Loader
│ └── PatternLibraryLoaderTest.php
└── PatternFieldProcessorPluginManagerTest.php
├── Traits
├── JsonDecodeTrait.php
├── PatternkitBlockHelperTrait.php
├── SchemaFixtureTrait.php
└── SchemaHelperTestTrait.php
└── Unit
├── Asset
├── LibraryTest.php
└── TestLibrary.php
├── PatternElementTest.php
├── PatternFieldProcessorPluginManagerTest.php
├── Plugin
└── PatternFieldProcessor
│ ├── TokenProcessorTest.php
│ └── WysiwygFieldProcessorTest.php
└── Schema
├── PatternkitRefProviderTest.php
├── SchemaFactoryTest.php
├── SchemaHelperTest.php
├── SchemaIteratorTest.php
├── SchemaWalkerTest.php
└── TestPatternkitRefProvider.php
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "browsers": ["last 2 versions", "ie >= 11"]
8 | },
9 | "forceAllTransforms": true
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [cybtachyon]
4 | patreon: cybtachyon
5 | ko_fi: cybtachyon
6 | tidelift: packagist/drupal/patternkit
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | js/globals**.js**
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/erbium
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at cyb.tachyon@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal/patternkit",
3 | "description": "Patternkit is a Drupal module that lets you drag and drop your theme templates, patterns, and components into layouts, whether using Layout Builder, Panels, or the default Block Layout editor.",
4 | "type": "drupal-module",
5 | "authors": [
6 | {
7 | "name": "Derek Reese",
8 | "email": "cyb.tachyon@gmail.com"
9 | },
10 | {
11 | "name": "Red Hat Inc.",
12 | "email": "drupal@redhat.com"
13 | }
14 | ],
15 | "license": "MIT",
16 | "require": {
17 | "php": ">=7.4.0",
18 | "ext-json": "*",
19 | "swaggest/json-schema": "^0.12",
20 | "symfony/finder": "^3.4 || ^4.0",
21 | "symfony/yaml": "^3.4 || ^4.0",
22 | "webmozart/path-util": "^2.1.0"
23 | },
24 | "require-dev": {
25 | "drupal/core-recommended": "^9",
26 | "drupal/core-dev": "^9",
27 | "drupal/devel": "^4.1"
28 | },
29 | "extra": {
30 | "drush": {
31 | "services": {
32 | "drush.services.yml": "^9"
33 | }
34 | }
35 | },
36 | "repositories": [],
37 | "scripts": {
38 | "post-autoload-dump": [
39 | "wget -Oq -P js/ --no-check-certificate https://raw.github.com/jdorn/json-editor/master/dist/jsoneditor.min.js",
40 | "wget -Oq -P js/ --no-check-certificate https://raw.github.com/jdorn/json-editor/master/dist/jsoneditor.js"
41 | ],
42 | "js:build": [
43 | ". ~/.profile && nvm install && nvm use",
44 | "npm install && npx gulp"
45 | ]
46 | },
47 | "suggest": {
48 | "drupal/entity_browser": "Improves display for the patternkit_media_library module.",
49 | "drupal/media_entity_browser": "Improves display for the patternkit_media_library module."
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/config/install/patternkit.settings.yml:
--------------------------------------------------------------------------------
1 | patternkit_cache_enabled: true
2 | patternkit_json_editor_css: ""
3 | patternkit_json_editor_icons: ""
4 | patternkit_json_editor_js: ""
5 | patternkit_json_editor_theme: cygnet
6 | patternkit_libraries: []
7 | patternkit_json_editor_use_shadow_dom: true
8 | patternkit_json_editor_wysiwyg: ""
9 | patternkit_json_editor_ckeditor_toolbar: ""
10 | patternkit_json_editor_disable_properties_buttons: false
11 | patternkit_json_editor_disable_edit_json_button: true
12 |
--------------------------------------------------------------------------------
/config/optional/tour.tour.patternkit_admin.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | module:
5 | - patternkit
6 | id: patternkit_admin
7 | label: 'Patternkit admin settings page'
8 | module: patternkit
9 | routes:
10 | -
11 | route_name: patternkit.settings
12 | tips:
13 | patternkit-libraries:
14 | id: patternkit-libraries
15 | plugin: text
16 | label: 'Manage Patternkit library usage and visibility'
17 | body: 'Disabled libraries will not be usable for twig or blocks. Invisible libraries will not appear in lists.'
18 | weight: 1
19 | attributes:
20 | data-id: edit-patternkit-libraries
21 | patternkit-library-cache:
22 | id: patternkit-library-cache
23 | plugin: text
24 | label: 'Set library cache usage'
25 | body: 'Controlled primarily by the Drupal discovery cache, this cache can be cleared with drush cr --cache-clear=discovery.'
26 | weight: 2
27 | attributes:
28 | data-id: edit-patternkit-cache-enabled
29 | patternkit-disk-cache:
30 | id: patternkit-disk-cache
31 | plugin: text
32 | label: 'Set disk cache usage'
33 | body: 'Useful for REST libraries, the disk cache controls if a request is made for new patterns on each page load.'
34 | weight: 3
35 | attributes:
36 | data-id: edit-patternkit-render-cache
37 | patternkit-ttl:
38 | id: patternkit-ttl
39 | plugin: text
40 | label: 'Set Time-To-Live settings'
41 | body: 'Limit the amount of time the Patternkit Disk cache will save REST API requested patterns before requesting a new one from the REST server.'
42 | weight: 4
43 | attributes:
44 | data-id: edit-patternkit-default-module-ttl
45 |
--------------------------------------------------------------------------------
/config/optional/tour.tour.patternkit_block.yml:
--------------------------------------------------------------------------------
1 | langcode: en
2 | status: true
3 | dependencies:
4 | module:
5 | - patternkit
6 | id: patternkit_block
7 | label: 'Patternkit Layout Builder Add Block page'
8 | module: patternkit
9 | routes:
10 | -
11 | route_name: layout_builder.add_block
12 | tips:
13 | patternkit-context-node:
14 | id: patternkit-context-node
15 | plugin: text
16 | label: 'Add route context from the node'
17 | body: 'Set this to allow the block to use tokens from the node context.'
18 | weight: 1
19 | attributes:
20 | data-class: form-item-settings-context-mapping-node
21 | patternkit-context-user:
22 | id: patternkit-context-user
23 | plugin: text
24 | label: 'Add route context from the user'
25 | body: 'Set this to allow the block to use tokens from the user context.'
26 | weight: 2
27 | attributes:
28 | data-class: form-item-settings-context-mapping-user
29 | patternkit-reusable:
30 | id: patternkit-reusable
31 | plugin: text
32 | label: 'Make the block reusable'
33 | body: 'This allows you to make the block reusable, where it will show up in block listings and can be used on other layouts and block placements on the site.'
34 | weight: 3
35 | attributes:
36 | data-class: form-item-settings-reusable
37 | patternkit-presentation:
38 | id: patternkit-presentation
39 | plugin: text
40 | label: 'Presentation settings'
41 | body: 'Additional Patternkit plugins can add support for other presentation styles, such as web components, JSON, or JavaScript modules.'
42 | weight: 4
43 | attributes:
44 | data-class: form-item-settings-presentation-style
45 | patternkit-tokens:
46 | id: patternkit-tokens
47 | plugin: text
48 | label: 'Token replacements'
49 | body: 'Use these tokens provided by context to insert those values into the pattern.'
50 | weight: 5
51 | attributes:
52 | data-id: edit-settings-global-tokens
53 |
--------------------------------------------------------------------------------
/config/schema/patternkit.data_types.schema.yml:
--------------------------------------------------------------------------------
1 | patternkit.json:
2 | type: string
3 | label: 'Text with text format'
4 | # We declare the entire mapping of text and text format as translatable. This
5 | # causes the entire mapping to be saved to the language overrides of the
6 | # configuration. Storing only the (to be formatted) text could result in
7 | # security problems in case the text format of the source text is changed.
8 | translatable: true
9 | form_element_class: 'Drupal\patternkit\FormElement\PatternkitJson'
10 |
--------------------------------------------------------------------------------
/config/schema/patternkit.schema.yml:
--------------------------------------------------------------------------------
1 | patternkit.settings:
2 | type: config_object
3 | label: 'Patternkit Module Settings'
4 | mapping:
5 | patternkit_cache_enabled:
6 | type: boolean
7 | label: 'Boolean that sets if the Patternkit cache is used or not.'
8 | patternkit_json_editor_css:
9 | type: string
10 | label: 'Comma-separated string of stylesheet files to load with the JSON Editor.'
11 | patternkit_json_editor_icons:
12 | type: string
13 | label: 'String selecting the icon set to be used for the JSON Editor.'
14 | patternkit_json_editor_js:
15 | type: string
16 | label: 'Comma-separated string of Javascript files to load with the JSON Editor.'
17 | patternkit_json_editor_theme:
18 | type: string
19 | label: 'String selecting the theme to be used for the JSON Editor.'
20 | patternkit_libraries:
21 | type: mapping
22 | label: 'A list of settings for each Patternkit library.'
23 | mapping:
24 | enabled:
25 | type: boolean
26 | label: 'Library Enabled'
27 | visible:
28 | type: boolean
29 | label: 'Library visible in lists'
30 | patternkit_json_editor_use_shadow_dom:
31 | type: boolean
32 | label: 'Boolean that determines whether to use the Shadow DOM for the JSON Editor.'
33 | patternkit_json_editor_wysiwyg:
34 | type: string
35 | label: 'String selecting the WYSIWYG tool to use in the JSON Editor.'
36 | patternkit_json_editor_ckeditor_toolbar:
37 | type: string
38 | label: 'String selecting the CKEditor text format to use for CKEditor inside the JSON Editor.'
39 | patternkit_json_editor_disable_properties_buttons:
40 | type: boolean
41 | label: 'Boolean that determines whether to show the properties buttons.'
42 | patternkit_json_editor_disable_edit_json_button:
43 | type: boolean
44 | label: 'Boolean that determines whether to show the JSON Edit button.'
45 |
46 | block.settings.patternkit_block:*:
47 | type: block_settings
48 | mapping:
49 | pattern:
50 | type: integer
51 | label: 'Revision ID of the pattern in use on the block.'
52 | patternkit_block_id:
53 | type: integer
54 | label: 'ID of the Patternkit Block content entity.'
55 | patternkit_block_rid:
56 | type: integer
57 | label: 'Revision ID of the Patternkit Block content entity.'
58 | presentation_style:
59 | type: string
60 | label: 'Presentation style for the patternkit block when rendered.'
61 | version:
62 | type: string
63 | label: 'Version of the pattern or containing library.'
64 | form_element_class: 'Drupal\patternkit\FormElement\PatternkitJson'
65 |
--------------------------------------------------------------------------------
/css/cygnet/cygnet--overrides.css:
--------------------------------------------------------------------------------
1 | #drupal-off-canvas [data-theme=cygnet] label {
2 | color: inherit;
3 | font-size: inherit;
4 | font-weight: inherit;
5 | line-height: inherit;
6 | }
7 |
8 | #drupal-off-canvas [data-theme=cygnet] label {
9 | font-family: 'RedHatText', Helvetica, Arial, sans-serif;
10 | }
11 | #drupal-off-canvas [data-theme=cygnet] button {
12 | font-family: 'RedHatText', Helvetica, Arial, sans-serif;
13 | }
14 |
15 | /*
16 | * Upstream styles in node_modules/@json-editor/json-editor/src/style.css set
17 | * the height to 300px, which is too tall for all textareas. The browser
18 | * provides a tooltip to resize the element.
19 | */
20 | #drupal-off-canvas .je-textarea {
21 | height: auto;
22 | }
23 |
--------------------------------------------------------------------------------
/css/cygnet/cygnet--prefixed-for-drupal--upstream.css:
--------------------------------------------------------------------------------
1 | #drupal-off-canvas .je-float-right-linkholder {
2 | float: right;
3 | margin-left: 10px;
4 | }
5 |
6 | #drupal-off-canvas .je-modal {
7 | background-color: white;
8 | border: 1px solid black;
9 | box-shadow: 3px 3px black;
10 | position: absolute;
11 | z-index: 10;
12 | }
13 |
14 | #drupal-off-canvas .je-infobutton-icon {
15 | font-size: 16px;
16 | font-weight: bold;
17 | padding: 0.25rem;
18 | position: relative;
19 | display: inline-block;
20 | }
21 |
22 | #drupal-off-canvas .je-infobutton-tooltip {
23 | font-size: 12px;
24 | font-weight: normal;
25 | font-family: sans-serif;
26 | visibility: hidden;
27 | background-color: rgba(50, 50, 50, 0.75);
28 | margin: 0 0.25rem;
29 | color: #fafafa;
30 | padding: 0.5rem 1rem;
31 | border-radius: 0.25rem;
32 | width: 20rem;
33 | position: absolute;
34 | }
35 |
36 | #drupal-off-canvas .je-not-loaded {
37 | pointer-events: none;
38 | }
39 | #drupal-off-canvas .je-header {
40 | display: inline-block
41 | }
42 |
43 | #drupal-off-canvas .je-upload-preview img {
44 | float: left;
45 | margin: 0 0.5rem 0.5rem 0;
46 | max-width: 100%;
47 | max-height: 5rem;
48 | }
49 |
50 | #drupal-off-canvas .je-checkbox {
51 | display: inline-block;
52 | width: auto
53 | }
54 |
55 | #drupal-off-canvas .je-checkbox-control--compact {
56 | display: inline-block;
57 | margin-right: 1rem
58 | }
59 |
60 | #drupal-off-canvas .je-radio {
61 | display: inline-block;
62 | width: auto
63 | }
64 |
65 | #drupal-off-canvas .je-radio-control--compact {
66 | display: inline-block;
67 | margin-right: 1rem
68 | }
69 |
70 | #drupal-off-canvas .je-switcher {
71 | background-color: transparent;
72 | display: inline-block;
73 | font-style: italic;
74 | font-weight: normal;
75 | height: auto;
76 | width: auto;
77 | margin-bottom: 0;
78 | margin-left: 5px;
79 | padding: 0 0 0 3px;
80 | }
81 |
82 | #drupal-off-canvas .je-textarea {
83 | width: 100%;
84 | height: 300px;
85 | box-sizing: border-box
86 | }
87 |
88 | #drupal-off-canvas .je-range-control {
89 | text-align: center
90 | }
91 |
92 | #drupal-off-canvas .je-indented-panel {
93 | padding-left: 10px;
94 | margin-left: 10px;
95 | border-left: 1px solid #ccc
96 | }
97 |
98 | #drupal-off-canvas .je-indented-panel--top {
99 | padding-left: 10px;
100 | margin-left: 10px;
101 | }
102 |
103 | #drupal-off-canvas .je-tabholder {
104 | float: left;
105 | width: 130px;
106 | }
107 |
108 | #drupal-off-canvas .je-tabholder .content {
109 | margin-left: 120px;
110 | }
111 |
112 | #drupal-off-canvas .je-tabholder--top {
113 | margin-left: 10px;
114 | }
115 |
116 | #drupal-off-canvas .je-tabholder--clear {
117 | clear:both;
118 | }
119 |
120 | #drupal-off-canvas .je-tab {
121 | border: 1px solid #ccc;
122 | border-width: 1px 0 1px 1px;
123 | text-align: center;
124 | line-height: 30px;
125 | border-radius: 5px;
126 | border-bottom-right-radius: 0;
127 | border-top-right-radius: 0;
128 | font-weight: bold;
129 | cursor: pointer
130 | }
131 |
132 | #drupal-off-canvas .je-tab--top {
133 | float: left;
134 | border: 1px solid #ccc;
135 | border-width: 1px 1px 0px 1px;
136 | text-align: center;
137 | line-height: 30px;
138 | border-radius: 5px;
139 | padding-left: 5px;
140 | padding-right: 5px;
141 | border-bottom-right-radius: 0;
142 | border-bottom-left-radius: 0;
143 | font-weight: bold;
144 | cursor: pointer
145 | }
146 |
147 | #drupal-off-canvas .je-block-link {
148 | display: block
149 | }
150 |
151 | #drupal-off-canvas .je-media {
152 | width: 100%
153 | }
--------------------------------------------------------------------------------
/css/patternkit.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Miscellaneous styles for Patternkit interfaces.
3 | */
4 |
5 | /*
6 | * Implements spinning loader to display while
7 | * JSON Editor's schemas are being loaded.
8 | */
9 | .js-patternkit-loading__on {
10 | border: 4px solid #ebebeb; /* Light grey */
11 | border-top: 4px solid #0066cc; /* Blue */
12 | border-radius: 50%;
13 | width: 30px;
14 | height: 30px;
15 | margin: 40px auto;
16 | animation: spin 1s linear infinite;
17 | }
18 |
19 | @keyframes spin {
20 | 0% { transform: rotate(0deg); }
21 | 100% { transform: rotate(360deg); }
22 | }
23 |
--------------------------------------------------------------------------------
/css/prosemirror.css:
--------------------------------------------------------------------------------
1 | .ProseMirror {
2 | position: relative;
3 | }
4 |
5 | .ProseMirror {
6 | word-wrap: break-word;
7 | white-space: pre-wrap;
8 | white-space: break-spaces;
9 | -webkit-font-variant-ligatures: none;
10 | font-variant-ligatures: none;
11 | font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
12 | }
13 |
14 | .ProseMirror pre {
15 | white-space: pre-wrap;
16 | }
17 |
18 | .ProseMirror li {
19 | position: relative;
20 | }
21 |
22 | .ProseMirror-hideselection *::selection { background: transparent; }
23 | .ProseMirror-hideselection *::-moz-selection { background: transparent; }
24 | .ProseMirror-hideselection { caret-color: transparent; }
25 |
26 | .ProseMirror-selectednode {
27 | outline: 2px solid #8cf;
28 | }
29 |
30 | /* Make sure li selections wrap around markers */
31 |
32 | li.ProseMirror-selectednode {
33 | outline: none;
34 | }
35 |
36 | li.ProseMirror-selectednode:after {
37 | content: "";
38 | position: absolute;
39 | left: -32px;
40 | right: -2px; top: -2px; bottom: -2px;
41 | border: 2px solid #8cf;
42 | pointer-events: none;
43 | }
44 |
--------------------------------------------------------------------------------
/css/sceditor/famfamfam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/css/sceditor/famfamfam.png
--------------------------------------------------------------------------------
/drupalci.yml:
--------------------------------------------------------------------------------
1 | build:
2 | assessment:
3 | validate_codebase:
4 | phplint:
5 | csslint:
6 | eslint:
7 | phpcs:
8 | testing:
9 | run_tests.standard:
10 | types: 'Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional'
11 | suppress-deprecations: false
12 | run_tests.js:
13 | concurrency: 1
14 | types: 'PHPUnit-FunctionalJavascript'
15 | suppress-deprecations: false
16 |
--------------------------------------------------------------------------------
/drush.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | patternkit.commands:
3 | class: Drupal\patternkit\Commands\PatternkitCommands
4 | tags:
5 | - { name: drush.command }
6 |
--------------------------------------------------------------------------------
/fonts/materialicons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/fonts/materialicons.woff2
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const babelify = require('babelify');
3 | const browserify = require("browserify");
4 | const buffer = require('vinyl-buffer');
5 | const source = require('vinyl-source-stream');
6 | const prefixCss = require('gulp-prefix-css');
7 | const rename = require('gulp-rename');
8 |
9 | const gulpConfig = {
10 | src: './js/**es6.js',
11 | dist: './js',
12 | main: 'patternkit.jsoneditor.es6.js',
13 | lib: [
14 | './node_modules/@json-editor/json-editor/dist/**',
15 | './node_modules/ajv/dist/ajv.min.js',
16 | './node_modules/ajv/dist/ajv.min.js.map',
17 | './node_modules/handlebars/dist/handlebars.js',
18 | './node_modules/handlebars/dist/handlebars.min.js',
19 | ]
20 | };
21 |
22 | // @todo Move to Rollup from Browserify to support modular bundling.
23 | gulp.task('compile:es6', function () {
24 | return browserify(gulpConfig.dist + '/' + gulpConfig.main)
25 | .transform(babelify)
26 | .bundle()
27 | .pipe(source(gulpConfig.main.replace('.es6', '')))
28 | .pipe(buffer())
29 | .pipe(gulp.dest(gulpConfig.dist));
30 | });
31 |
32 | gulp.task('copy:lib', function() {
33 | return gulp.src(gulpConfig.lib).pipe(gulp.dest(gulpConfig.dist));
34 | });
35 |
36 | // Prefixes upstream JSON Editor's core styles so they can be used in non-shadow
37 | // DOM version of Cygnet.
38 | const drupalModalId = '#drupal-off-canvas';
39 | gulp.task('prefix-css:cygnet-theme-upstream', function(){
40 | return gulp.src('./node_modules/@json-editor/json-editor/src/style.css')
41 | .pipe(prefixCss(drupalModalId))
42 | .pipe(rename('cygnet--prefixed-for-drupal--upstream.css'))
43 | .pipe(gulp.dest('./css/cygnet'));
44 | });
45 |
46 | // Prefixes Cygnet's styles so they work when JSON Editor gets loaded outside
47 | // the shadow DOM.
48 | gulp.task('prefix-css:cygnet-theme', function(){
49 | return gulp.src('./css/cygnet/cygnet.css')
50 | .pipe(prefixCss(drupalModalId))
51 | .pipe(rename('cygnet--prefixed-for-drupal.css'))
52 | .pipe(gulp.dest('./css/cygnet'));
53 | });
54 |
55 | gulp.task('watch', function() {
56 | return gulp.watch(gulpConfig.src, 'compile:es6');
57 | });
58 |
59 | gulp.task('default', gulp.parallel(['compile:es6', 'copy:lib', 'prefix-css:cygnet-theme', 'prefix-css:cygnet-theme-upstream']));
60 |
--------------------------------------------------------------------------------
/js/README.md:
--------------------------------------------------------------------------------
1 | # Patternkit JavaScript Dependencies
2 |
3 | ## JSON Editor JS
4 |
5 | - patternkit.jsoneditor.js is built with the Gulp script from patternkit.jsoneditor.es6.js.
6 |
7 | ## Dependencies
8 |
9 | - AJV and Json Schema Editor are built using package.json's Gulp script.
10 |
11 | ## Getting Started
12 |
13 | - Use Node Version Manager
14 |
15 |
16 | nvm use
17 |
18 | - Run the build script to update the dependencies and transpile the JSON Schema Editor Patternkit JS.
19 |
20 |
21 | npm run build
22 |
--------------------------------------------------------------------------------
/js/patternkit.jsoneditor.cygnet.es6.js:
--------------------------------------------------------------------------------
1 | class cygnetTheme extends JSONEditor.AbstractTheme {
2 | getFormInputLabel (text, req) {
3 | const el = super.getFormInputLabel(text, req)
4 | el.classList.add('je-cygnet-form-input-label')
5 | return el
6 | }
7 |
8 | getFormInputDescription (text) {
9 | const el = super.getFormInputDescription(text)
10 | el.classList.add('je-cygnet-form-input-label')
11 | el.innerHTML = text
12 | return el
13 | }
14 |
15 | getIndentedPanel () {
16 | const el = super.getIndentedPanel()
17 | el.classList.add('je-cygnet-indented-panel')
18 | el.style = el.style || {};
19 | return el
20 | }
21 |
22 | getTopIndentedPanel () {
23 | return this.getIndentedPanel()
24 | }
25 |
26 | getChildEditorHolder () {
27 | const el = super.getChildEditorHolder()
28 | el.classList.add('je-cygnet-child-editor-holder')
29 | return el
30 | }
31 |
32 | getDescription (text) {
33 | const el = super.getDescription(text)
34 | el.classList.add('je-cygnet-description')
35 | el.innerHTML = text
36 | return el
37 | }
38 |
39 | // If no title, use the text as title so that we have can use the
40 | // title attr as a CSS selector to style the collapse/expand state.
41 | setButtonText (button, text, icon, title) {
42 | if (!title && text) {
43 | title = text;
44 | }
45 |
46 | if (text == "Object Properties") {
47 | text = "Properties";
48 | }
49 |
50 | return super.setButtonText(button, text, icon, title);
51 | }
52 |
53 | getHeaderButtonHolder () {
54 | const el = this.getButtonHolder()
55 | el.classList.add('je-cygnet-header-button-holder')
56 | el.style.display = 'block';
57 | return el
58 | }
59 |
60 | getTable () {
61 | const el = super.getTable()
62 | el.classList.add('je-table')
63 | return el
64 | }
65 |
66 | setGridColumnSize (el, size) {
67 | el.className = 'col-md-'+size;
68 | }
69 |
70 | addInputError (input, text) {
71 | const group = this.closest(input, '.form-control') || input.controlgroup
72 |
73 | if (!input.errmsg) {
74 | input.errmsg = document.createElement('div')
75 | input.errmsg.setAttribute('class', 'errmsg')
76 | input.errmsg.style = input.errmsg.style || {}
77 | input.errmsg.style.color = 'red'
78 | group.appendChild(input.errmsg)
79 | } else {
80 | input.errmsg.style.display = 'block'
81 | }
82 |
83 | input.errmsg.innerHTML = ''
84 | input.errmsg.appendChild(document.createTextNode(text))
85 | }
86 |
87 | removeInputError (input) {
88 | if (input.style) {
89 | input.style.borderColor = ''
90 | }
91 | if (input.errmsg) input.errmsg.style.display = 'none'
92 | }
93 |
94 | getTabHolder (propertyName) {
95 | var pName = typeof propertyName === 'undefined' ? '' : propertyName;
96 | var el = document.createElement('div');
97 | el.classList.add('je-cygnet-tabs');
98 | el.innerHTML = "
");
99 | return el;
100 | }
101 |
102 | getTab (span, tabId) {
103 | const el = document.createElement('div')
104 | el.appendChild(span)
105 | el.id = tabId
106 | el.style = el.style || {}
107 | el.classList.add('je-cygnet-tab');
108 | return el
109 | }
110 |
111 | markTabActive (row) {
112 | row.tab.classList.remove('je-cygnet-tab--inactive');
113 | row.tab.classList.add('je-cygnet-tab--active');
114 |
115 | if (typeof row.rowPane !== 'undefined') {
116 | row.rowPane.style.display = ''
117 | } else {
118 | row.container.style.display = ''
119 | }
120 | }
121 |
122 | markTabInactive (row) {
123 | row.tab.classList.remove('je-cygnet-tab--active');
124 | row.tab.classList.add('je-cygnet-tab--inactive');
125 |
126 | if (typeof row.rowPane !== 'undefined') {
127 | row.rowPane.style.display = 'none'
128 | } else {
129 | row.container.style.display = 'none'
130 | }
131 | }
132 |
133 | }
134 |
135 | export function patternkitEditorCygnet($, Drupal, JSONEditor) {
136 | 'use strict';
137 | Drupal.behaviors.patternkitEditorCygnet = {
138 | attach: function (context, settings) {
139 | if (!window.JSONEditor) {
140 | return;
141 | }
142 | cygnetTheme.rules = { }
143 | JSONEditor.defaults.themes.cygnet = cygnetTheme;
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/js/patternkit.jsoneditor.editor.array.es6.js:
--------------------------------------------------------------------------------
1 | /*globals Console:false */
2 | /*globals Drupal:false */
3 | /*globals jQuery:false */
4 | /*globals JSONEditor:false */
5 |
6 | /**
7 | * Duplicates json-editor trigger utility.
8 | *
9 | * Cannot figure out how to import it without errors.
10 | * The function is defined in @json-editor/json-editor/src/utilities.
11 | *
12 | * @param el
13 | * @param event
14 | */
15 | const trigger = function (el, event) {
16 | const e = document.createEvent('HTMLEvents')
17 | e.initEvent(event, true, true)
18 | el.dispatchEvent(e)
19 | };
20 |
21 | /**
22 | * @file PatternkitJsoneditorEditorArray class.
23 | *
24 | * @external Drupal
25 | * @external jQuery
26 | * @external JSONEditor
27 | */
28 | class PatternkitJsoneditorEditorArray extends JSONEditor.defaults.editors.array {
29 | /**
30 | * Overrides json-editor array _createToggleButton() method.
31 | *
32 | * Overrides JSONEditor's _createToggleButton() method for arrays. The only change is to
33 | * trigger toggle of section if user clicks on the label/title, not just on
34 | * the expand/collapse button. Makes hiding/showing sections much easier.
35 | */
36 | _createToggleButton () {
37 | const button = this.getButton('', 'collapse', 'button_collapse')
38 | button.classList.add('json-editor-btntype-toggle')
39 | this.title.insertBefore(button, this.title.childNodes[0])
40 |
41 | const rowHolderDisplay = this.row_holder.style.display
42 | const controlsDisplay = this.controls.style.display
43 |
44 | //
45 | // Replaces the click handler on the button (element `this.collapse_control`),
46 | // so that the section is toggled if you click either on the button or its label
47 | // (i.e., if you clicked anywhere on the title).
48 | this.title.classList.add('patternkit-jsoneditor-clickable');
49 | this.title.addEventListener('click', e => {
50 | e.preventDefault()
51 | e.stopPropagation()
52 | if (this.collapsed) {
53 | this.collapsed = false
54 | if (this.panel) this.panel.style.display = ''
55 | this.row_holder.style.display = rowHolderDisplay
56 | if (this.tabs_holder) this.tabs_holder.style.display = ''
57 | this.controls.style.display = controlsDisplay
58 | this.setButtonText(this.toggle_button, '', 'collapse', this.translate('button_collapse'))
59 | } else {
60 | this.collapsed = true
61 | this.row_holder.style.display = 'none'
62 | if (this.tabs_holder) this.tabs_holder.style.display = 'none'
63 | this.controls.style.display = 'none'
64 | if (this.panel) this.panel.style.display = 'none'
65 | this.setButtonText(this.toggle_button, '', 'expand', this.translate('button_expand'))
66 | }
67 | });
68 | //
69 |
70 | return button
71 | }
72 | }
73 |
74 | export function patternkitEditorArray($, Drupal, JSONEditor) {
75 | 'use strict';
76 | Drupal.behaviors.patternkitEditorArray = {
77 | attach: function (context, settings) {
78 | if (!window.JSONEditor) {
79 | return;
80 | }
81 | JSONEditor.defaults.editors.patternkit_editor_array = PatternkitJsoneditorEditorArray;
82 | JSONEditor.defaults.resolvers.unshift(function (schema) {
83 | if (schema.type === 'array') {
84 | return 'patternkit_editor_array';
85 | }
86 | });
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/js/patternkit.jsoneditor.prosemirror.es6.js:
--------------------------------------------------------------------------------
1 | /*globals Console:false */
2 | /*globals Drupal:false */
3 | /*globals jQuery:false */
4 | /*globals JSONEditor:false */
5 | /**
6 | * @file DrupalImageEditor class.
7 | *
8 | * @external Drupal
9 | * @external jQuery
10 | * @external JSONEditor
11 | */
12 |
13 | import {EditorState} from "prosemirror-state"
14 | import {EditorView} from "prosemirror-view"
15 | import {Schema, DOMParser} from "prosemirror-model"
16 | import {schema} from "prosemirror-schema-basic"
17 | import {addListNodes} from "prosemirror-schema-list"
18 | import {exampleSetup} from "prosemirror-example-setup"
19 |
20 | class DrupalProseMirror extends JSONEditor.defaults.editors.string {
21 |
22 | build() {
23 | // Override the format when building the base string editor.
24 | this.options.format = 'textarea';
25 | super.build();
26 | this.input_type = this.schema.format;
27 | this.input.setAttribute('data-schemaformat', this.input_type);
28 | }
29 |
30 | afterInputReady() {
31 | // Editor options.
32 | // @todo Replace JSONEditor.defaults with this.defaults.
33 | this.options = jQuery.extend({}, JSONEditor.defaults.options.drupal_prosemirror || {}, this.options.drupal_prosemirror || {});
34 |
35 | const editorSchema = new Schema({
36 | nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
37 | marks: schema.spec.marks
38 | })
39 |
40 | this.prosemirror_container = document.createElement('div');
41 | this.prosemirror_container.style.width = '100%';
42 | this.prosemirror_container.style.position = 'relative';
43 |
44 | this.input.style.display = 'none';
45 |
46 | this.prosemirror_instance = new EditorView(this.prosemirror_container, {
47 | state: EditorState.create({
48 | doc: DOMParser.fromSchema(editorSchema).parse(this.getValue()),
49 | plugins: exampleSetup({schema: editorSchema})
50 | })
51 | });
52 |
53 | this.input.parentNode.insertBefore(this.prosemirror_container, this.input);
54 |
55 | if (this.schema.readOnly || this.schema.readonly || this.schema.template) {
56 | // Set all EditorProps editable to false;
57 | }
58 |
59 | // Handle editor change.
60 |
61 | this.theme.afterInputReady(this.input);
62 | }
63 |
64 | destroy() {
65 | if(this.prosemirror_instance) {
66 | this.prosemirror_instance.destroy();
67 | this.prosemirror_instance = null;
68 | }
69 | super.destroy();
70 | }
71 |
72 | disable(always_disabled) {
73 | if (always_disabled) {
74 | this.always_disabled = true;
75 | }
76 | if (this.prosemirror_instance) {
77 | this.prosemirror_instance.setProps({editable: false});
78 | }
79 | super.disable(always_disabled);
80 | }
81 |
82 | enable() {
83 | if (this.always_disabled) {
84 | return;
85 | }
86 | if (this.prosemirror_instance) {
87 | this.prosemirror_instance.setProps({editable: true});
88 | }
89 | super.enable();
90 | }
91 |
92 | getNumColumns() {
93 | return 6;
94 | }
95 |
96 | setValue(val, initial, from_template) {
97 | const input = super.setValue(val, initial, from_template);
98 | if (input !== undefined && input.changed && this.ckeditor_instance) {
99 | this.prosemirror_instance.updateState({doc: input.value});
100 | this.refreshWatchedFieldValues();
101 | this.onChange(true);
102 | }
103 | }
104 | }
105 |
106 | export function patternkitEditorProseMirror($, Drupal, JSONEditor) {
107 | 'use strict';
108 | Drupal.behaviors.patternkitEditorProseMirror = {
109 | attach: function (context, settings) {
110 | if (!window.JSONEditor) {
111 | return;
112 | }
113 | JSONEditor.defaults.options.drupal_prosemirror = {
114 | ckeditor_config: settings.patternkitEditor.patternkitProseMirrorConfig
115 | };
116 | JSONEditor.defaults.editors.drupal_prosemirror = DrupalProseMirror;
117 | JSONEditor.defaults.resolvers.unshift(function (schema) {
118 | if (schema.type === 'string'
119 | && schema.format === 'html'
120 | && schema.options
121 | && schema.options.wysiwyg
122 | && settings.patternkitEditor.wysiwygEditorName === 'prosemirror') {
123 | return 'drupal_prosemirror';
124 | }
125 | });
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/js/patternkit.jsoneditor.quill.es6.js:
--------------------------------------------------------------------------------
1 | /*globals Console:false */
2 | /*globals Drupal:false */
3 | /*globals jQuery:false */
4 | /*globals JSONEditor:false */
5 | /*globals Quill:false */
6 | /**
7 | * @file DrupalQuill class.
8 | *
9 | * @external Drupal
10 | * @external jQuery
11 | * @external JSONEditor
12 | * @external Quill
13 | */
14 |
15 | class DrupalQuill extends JSONEditor.defaults.editors.string {
16 |
17 | build() {
18 | // Override the format when building the base string editor.
19 | this.options.format = 'textarea';
20 | super.build();
21 | this.input_type = this.schema.format;
22 | this.input.setAttribute('data-schemaformat', this.input_type);
23 | }
24 |
25 | afterInputReady() {
26 | // Editor options.
27 | // @todo Replace JSONEditor.defaults with this.defaults.
28 | this.options = jQuery.extend({}, JSONEditor.defaults.options.drupal_quill || {}, this.options.drupal_quill || {});
29 | this.options.quill_config = {
30 | modules: {
31 | toolbar: [
32 | [{ header: [1, 2, 3, 4, 5, 6, 7, false] }],
33 | ['bold', 'italic', 'underline'],
34 | ['image', 'code-block', 'video'],
35 | [{ 'indent': '-1' }, { 'indent': '+1' }],
36 | [{ 'align': [] }]
37 | ]
38 | },
39 | placeholder: '',
40 | theme: 'snow'
41 | };
42 |
43 | this.quill_container = document.createElement('div');
44 | this.quill_container.style.width = '100%';
45 | this.quill_container.style.position = 'relative';
46 |
47 | this.input.style.display = 'none';
48 |
49 | this.input.parentNode.insertBefore(this.quill_container, this.input);
50 | this.quill_instance = new Quill(this.quill_container, this.options.quill_config);
51 | this.quill_instance.setHTML = (html) => {
52 | this.quill_instance.root.innerHTML = html;
53 | }
54 | this.quill_instance.getHTML = () => {
55 | return this.quill_instance.root.innerHTML;
56 | }
57 | this.quill_instance.setHTML(this.getValue());
58 | if (this.schema.readOnly || this.schema.readonly || this.schema.template) {
59 | this.quill_instance.disable();
60 | }
61 |
62 | this.quill_instance.on('text-change', () => {
63 | this.input.value = this.quill_instance.getHTML();
64 | this.refreshValue();
65 | // Dirty means display cache is invalidated for string editors.
66 | this.is_dirty = true;
67 | this.onChange(true);
68 | });
69 |
70 | this.theme.afterInputReady(this.input);
71 | }
72 |
73 | destroy() {
74 | if(this.quill_instance) {
75 | this.quill_instance = null;
76 | }
77 | super.destroy();
78 | }
79 |
80 | disable(always_disabled) {
81 | if (always_disabled) {
82 | this.always_disabled = true;
83 | }
84 | if (this.quill_instance) {
85 | this.quill_instance.disable();
86 | }
87 | super.disable(always_disabled);
88 | }
89 |
90 | enable() {
91 | if (this.always_disabled) {
92 | return;
93 | }
94 | if (this.quill_instance) {
95 | this.quill_instance.enable(true);
96 | }
97 | super.enable();
98 | }
99 |
100 | getNumColumns() {
101 | return 6;
102 | }
103 |
104 | setValue(val, initial, from_template) {
105 | const input = super.setValue(val, initial, from_template);
106 | if (input !== undefined && input.changed && this.quill_instance) {
107 | this.quill_instance.setHTML(input.value);
108 | this.refreshWatchedFieldValues();
109 | this.onChange(true);
110 | }
111 | }
112 | }
113 |
114 | export function patternkitEditorQuill($, Drupal, JSONEditor) {
115 | 'use strict';
116 | Drupal.behaviors.patternkitEditorQuill = {
117 | attach: function (context, settings) {
118 | if (!window.JSONEditor) {
119 | return;
120 | }
121 | JSONEditor.defaults.options.drupal_quill = {
122 | quill_config: settings.patternkitEditor.patternkitQuillConfig || {}
123 | };
124 | JSONEditor.defaults.editors.drupal_quill = DrupalQuill;
125 | JSONEditor.defaults.resolvers.unshift(function (schema) {
126 | if (schema.type === 'string'
127 | && schema.format === 'html'
128 | && schema.options
129 | && schema.options.wysiwyg
130 | && settings.patternkitEditor.wysiwygEditorName == 'quill') {
131 | return 'drupal_quill';
132 | }
133 | });
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/modules/patternkit_example/README.md:
--------------------------------------------------------------------------------
1 | # PatternKit
2 | Panels-based plugin that wraps a PatternLab library of components.
3 |
4 | This code will parse a pattern library (local or through REST endpoints) to generate a list of "content types" in panels that can be drag/dropped into panel variants.
5 |
6 | If enabled, the configuration screen is simply an iframe wrapper of a simplified version of the PatternLab schema builder.
7 |
8 | When pattern configurations are saved, the template is downloaded locally (to mitigate origin failures and lock in version at time of configuration.)
9 |
10 | Rendered twigs may contain drupal tokens, which are then processed in context.
11 |
12 | ## Installation
13 | Install the patternkit module as usual, and review the important variables below to determine if you would like to change the defaults.
14 |
15 | Install the Twig library into /sites/all/libraries/Twig
16 | ```
17 | git clone git://github.com/twigphp/Twig.git -b 1.x /tmp/Twig
18 | mv /tmp/Twig/lib/Twig ${DRUPALDIR}/sites/all/libraries/
19 | rm -rf /tmp/Twig
20 | ```
21 |
22 | The patternkit module by itself only provides the glue for other modules to present components. Define one by implementing ```hook_patternkit_library```
23 |
24 | An example implementation follows
25 | ```
26 | /**
27 | * Implements hook_patternkit_library().
28 | */
29 | function webrh_patternkit_library() {
30 | $libraries = array();
31 |
32 | $namespaces = array(
33 | 'Web RH Patterns' => 'webrh/src/library',
34 | );
35 |
36 | $module_path = drupal_get_path('module', 'webrh');
37 | foreach ($namespaces as $namespace => $path) {
38 | $lib_path = $module_path . DIRECTORY_SEPARATOR . $path;
39 | $libraries[] = new PatternkitDrupalTwigLib($namespace, $lib_path);
40 | }
41 |
42 | return $libraries;
43 | }
44 | ```
45 |
46 | There are two different plugins currently available,
47 | * PatternkitRESTLib
48 | * PatternkitDrupalTwigLib
49 |
50 | Use the former for dynamic REST based components, and the latter for locally sourced.
51 |
52 | ## Important Variables
53 | * ```patternkit_cache_enabled``` - Whether or not the metadata and render cache are enabled. (Disable during development)
54 | * ```patternkit_pl_host``` - The scheme://hostname:port/ of the PatternLab library host.
55 |
56 | ## TODOs
57 | * https://github.com/drupal-pattern-lab/roadmap/issues/8 Solve the problem of mapping Drupal fields to pattern Variables.
58 | * Error handling.
59 | * Better information about available tokens.
60 | * More investigation into the appropriate handling of web component configuration.
61 | * Finalize the CSS/JS management strategy.
62 | More documentation will be added.
63 |
64 | ## Dependencies
65 | [PatternLab](https://github.com/pattern-lab/starterkit-twig-drupal-minimal)
66 | * Schema display support requires: https://github.com/pattern-lab/patternlab-php-core/issues/117
67 | *
68 |
69 | # Proposed V2 Ecosystem
70 | * Exists to solve this issue: https://github.com/drupal-pattern-lab/roadmap/issues/8
71 | * PatternLAB with Restful Extensions provides library + endpoints (Ported from PatternKit)
72 | * PatternKit provides Drupal endpoint consumer (renamed from PKPlugins)
73 |
74 | # Legacy Ecosystem
75 | * PatternKit provides library + RESTful endpoints
76 | * PKPlugins provides Drupal endpoint consumer
77 |
--------------------------------------------------------------------------------
/modules/patternkit_example/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal-pattern-lab/patternkit_example",
3 | "license": "MIT",
4 | "authors": [
5 | {
6 | "name": "Red Hat Inc.",
7 | "email": "drupal@redhat.com"
8 | }
9 | ],
10 | "require": {},
11 | "config": {},
12 | "repositories": [],
13 | "scripts": {
14 | "library:build": [
15 | ". ~/.profile && nvm install && nvm use",
16 | "npm install && npx gulp"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "env": {
4 | "browser": true
5 | },
6 | "globals": {
7 | "Drupal": true,
8 | "jQuery": true
9 | },
10 | "rules": {
11 | // Errors.
12 | "array-bracket-spacing": [2, "never"],
13 | "block-scoped-var": 2,
14 | "brace-style": [2, "stroustrup", {"allowSingleLine": true}],
15 | "comma-dangle": [2, "never"],
16 | "comma-spacing": 2,
17 | "comma-style": [2, "last"],
18 | "computed-property-spacing": [2, "never"],
19 | "curly": [2, "all"],
20 | "eol-last": 2,
21 | "eqeqeq": [2, "smart"],
22 | "guard-for-in": 2,
23 | "indent": [2, 2, {"SwitchCase": 1}],
24 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
25 | "keyword-spacing": [2, {"before": true, "after": true}],
26 | "linebreak-style": [2, "unix"],
27 | "lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
28 | "new-parens": 2,
29 | "no-array-constructor": 2,
30 | "no-caller": 2,
31 | "no-catch-shadow": 2,
32 | "no-eval": 2,
33 | "no-extend-native": 2,
34 | "no-extra-bind": 2,
35 | "no-extra-parens": [2, "functions"],
36 | "no-implied-eval": 2,
37 | "no-iterator": 2,
38 | "no-label-var": 2,
39 | "no-labels": 2,
40 | "no-lone-blocks": 2,
41 | "no-loop-func": 2,
42 | "no-multi-spaces": 2,
43 | "no-multi-str": 2,
44 | "no-native-reassign": 2,
45 | "no-nested-ternary": 2,
46 | "no-new-func": 2,
47 | "no-new-object": 2,
48 | "no-new-wrappers": 2,
49 | "no-octal-escape": 2,
50 | "no-process-exit": 2,
51 | "no-proto": 2,
52 | "no-return-assign": 2,
53 | "no-script-url": 2,
54 | "no-sequences": 2,
55 | "no-shadow-restricted-names": 2,
56 | "no-spaced-func": 2,
57 | "no-trailing-spaces": 2,
58 | "no-undef-init": 2,
59 | "no-undefined": 2,
60 | "no-unused-expressions": 2,
61 | "no-unused-vars": [2, {"vars": "all", "args": "none"}],
62 | "no-with": 2,
63 | "object-curly-spacing": [2, "never"],
64 | "one-var": [2, "never"],
65 | "quote-props": [2, "consistent-as-needed"],
66 | "quotes": [2, "single", "avoid-escape"],
67 | "semi": [2, "always"],
68 | "semi-spacing": [2, {"before": false, "after": true}],
69 | "space-before-blocks": [2, "always"],
70 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
71 | "space-in-parens": [2, "never"],
72 | "space-infix-ops": 2,
73 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
74 | "spaced-comment": [2, "always"],
75 | "strict": 2,
76 | "yoda": [2, "never"],
77 | // Warnings.
78 | "max-nested-callbacks": [1, 3],
79 | "valid-jsdoc": [1, {
80 | "prefer": {
81 | "returns": "return",
82 | "property": "prop"
83 | },
84 | "requireReturn": false
85 | }]
86 | }
87 | }
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/.nvmrc:
--------------------------------------------------------------------------------
1 | 8.9.4
2 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/.patternlabrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/.patternlabrc
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/.sass-lint.yml:
--------------------------------------------------------------------------------
1 | # Documentation for the sass-lint Linters is available at:
2 | # https://github.com/sasstools/sass-lint/tree/master/docs/rules
3 |
4 | options:
5 | formatter: stylish
6 | merge-default-rules: true
7 | rules:
8 | extends-before-mixins: 2
9 | extends-before-declarations: 2
10 | placeholder-in-extend: 2
11 | mixins-before-declarations:
12 | - 2
13 | one-declaration-per-line: 2
14 | empty-line-between-blocks:
15 | - 2
16 | - ignore-single-line-rulesets: false
17 | single-line-per-selector: 2
18 |
19 | no-attribute-selectors: 0
20 | no-color-hex: 2
21 | no-color-keywords: 2
22 | no-color-literals: 2
23 | no-combinators: 0
24 | no-debug: 2
25 | no-disallowed-properties: 2
26 | no-duplicate-properties: 2
27 | no-empty-rulesets: 2
28 | no-extends: 0
29 | no-ids: 2
30 | no-important: 2
31 | no-invalid-hex: 2
32 | no-mergeable-selectors: 2
33 | no-misspelled-properties:
34 | - 2
35 | - extra-properties:
36 | - '*font-family'
37 | - '*height'
38 | - 'interpolation-mode'
39 | - '*margin-left'
40 | - '*vertical-align'
41 | - '*width'
42 | no-qualifying-elements: 0
43 | no-trailing-whitespace: 2
44 | no-trailing-zero: 2
45 | no-transition-all: 2
46 | no-universal-selectors: 0
47 | no-url-protocols: 2
48 | no-warn: 2
49 | property-units: 0
50 |
51 | force-attribute-nesting: 0
52 | force-element-nesting: 0
53 | force-pseudo-nesting: 0
54 |
55 | class-name-format:
56 | - 2
57 | -
58 | convention: hyphenatedbem
59 | function-name-format: 2
60 | id-name-format: 2
61 | mixin-name-format:
62 | - 2
63 | - convention: hyphenatedbem
64 | placeholder-name-format:
65 | - 2
66 | - convention: hyphenatedbem
67 | variable-name-format: 2
68 |
69 | attribute-quotes: 2
70 | bem-depth: 0
71 | border-zero: 2
72 | brace-style:
73 | - 2
74 | - style: stroustrup
75 | - allow-single-line: false
76 | clean-import-paths: 2
77 | empty-args: 0
78 | hex-length: 2
79 | hex-notation: 2
80 | indentation: 2
81 | leading-zero: 2
82 | nesting-depth:
83 | - 2
84 | - max-depth: 4
85 | property-sort-order: 2
86 | pseudo-element: 0
87 | quotes: 2
88 | shorthand-values: 2
89 | url-quotes: 2
90 | variable-for-property: 2
91 | zero-unit: 2
92 |
93 | space-after-comma: 2
94 | space-before-colon: 2
95 | space-after-colon: 2
96 | space-before-brace: 2
97 | space-before-bang: 2
98 | space-after-bang: 2
99 | space-between-parens: 0
100 | space-around-operator: 0
101 |
102 | trailing-semicolon: 2
103 | final-newline: 2
104 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/README.md:
--------------------------------------------------------------------------------
1 | # Patternkit Pattern Library
2 | This is an example pattern library meant as reference to get you started with creating your own.
3 |
4 | ## Getting Started
5 | Clone this repo, and then init the dev tools.
6 |
7 | ```bash
8 | git clone https://github.com/drupal-pattern-lab/patternkit_patterns.git patternkit
9 | nvm use
10 | npm i
11 | npm run watch
12 | ```
13 |
14 | ## Requirements
15 | - [Node.js](https://nodejs.org/en/)
16 |
17 | ## Visual Language
18 | - [Material Design](https://material.io/)
19 |
20 | ## Design System
21 | - [Atomic Design](http://atomicdesign.bradfrost.com/table-of-contents/)
22 |
23 | ## Standards
24 | - https://github.com/cybtachyon/twig-standards
25 |
26 | ## Contribution Guidelines
27 | All contributions will need to be vetted by a maintainer before being committed. Make a Pull Request today!
28 |
29 | ## Maintainers
30 | - Erik "CloudNyne" Baldwin
31 | - Derek "tachyon" Reese
32 | - Jason "SiliconValet" Smith
33 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/dist/patternkit.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/dist/patternkit.css
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/dist/patternkit.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/dist/patternkit.min.js
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "patternkit",
3 | "version": "1.0.0",
4 | "description": "Example Pattern Library",
5 | "main": "Gulpfile.js",
6 | "scripts": {
7 | "test": "watch"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/drupal-pattern-lab/patternkit_patterns.git"
12 | },
13 | "keywords": [
14 | "pattern"
15 | ],
16 | "author": "Derek Reese",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/drupal-pattern-lab/patternkit_patterns/issues"
20 | },
21 | "homepage": "https://github.com/drupal-pattern-lab/patternkit_patterns#readme",
22 | "devDependencies": {
23 | "gulp-cli": "^2.0.1",
24 | "node-sass": "^4.13.1",
25 | "parcel-bundler": "^1.5.1",
26 | "sass-lint": "^1.12.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/dist/example.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example/dist/example.css
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/dist/example.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example/dist/example.min.js
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/index.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | import './src/example.scss';
5 | import example from './dist/example';
6 |
7 | var element = Object.create(HTMLElement.prototype);
8 | element.createdCallback = function () {};
9 | element.attachedCallback = function () {};
10 | element.detachedCallback = function () {};
11 | element.attributeChangedCallback = function (attr, oldVal, newVal) {};
12 | document.registerElement('example', {
13 | prototype: element
14 | });
15 | }());
16 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "category": "atom",
4 | "title": "Example",
5 | "type": "object",
6 | "format": "grid",
7 | "properties": {
8 | "text": {
9 | "title": "Text",
10 | "type": "string",
11 | "options": {
12 | "grid_columns": 4
13 | }
14 | },
15 | "formatted_text": {
16 | "title": "Formatted Text",
17 | "type": "string",
18 | "format": "html",
19 | "options": {
20 | "wysiwyg": true
21 | }
22 | },
23 | "image_url": {
24 | "title": "Image URL",
25 | "type": "string",
26 | "format": "image",
27 | "options": {
28 | "grid_columns": 6
29 | }
30 | },
31 | "hidden": {
32 | "title": "hidden",
33 | "type": "string"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.md:
--------------------------------------------------------------------------------
1 | This is an example Web Component, implemented as a Twig UI Pattern.
2 |
3 | It includes the following files:
4 |
5 | ## example.json
6 | The JSON schema file delineates the schema of the pattern. In some cases, libraries may generate these from the Twig Docblock of the pattern Twig template file.
7 |
8 | ## example.md
9 | This file. The text here should appear as pattern documentation in any Style Guide, Pattern Library, or pattern developer tool.
10 |
11 | This documentation often will include Style Rulesets, brand guidelines, designer intention, as well as usage advice.
12 |
13 | ## example.scss
14 | Optional SASS to be compiled to CSS
15 |
16 | ## example.ts
17 | Optional Typescript file to be transpiled to the final JS module. In some libraries, vanilla JS or other variants are used.
18 |
19 | ## example.twig
20 | The HTML template for the pattern. Some libraries may also use Nunjucks, JSX, or Handlebars.
21 |
22 | ## example.yml
23 | Sample data for the pattern. This can be used to generate demo sites, style guide examples, or pre-fill schema editors.
24 |
25 | ## index.js
26 | The Web Component entry point.
27 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.scss
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.ts
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.twig:
--------------------------------------------------------------------------------
1 | Sample twig template.
2 |
3 | {{- text|striptags('') -}}
4 |
5 | {{ formatted_text|raw }}
6 |
7 | {% if image_url %}
8 |
9 |
10 | This is the image for URL {{ image_url }} .
11 |
12 | {% endif %}
13 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.yml:
--------------------------------------------------------------------------------
1 | text: Sample text
2 | formatted_text: Sample formatted text.
3 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/dist/example_filtered.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/dist/example_filtered.css
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/dist/example_filtered.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/dist/example_filtered.min.js
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/index.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | import './src/example_filtered.scss';
5 | import example_filtered from './dist/example_filtered';
6 |
7 | var element = Object.create(HTMLElement.prototype);
8 | element.createdCallback = function () {};
9 | element.attachedCallback = function () {};
10 | element.detachedCallback = function () {};
11 | element.attributeChangedCallback = function (attr, oldVal, newVal) {};
12 | document.registerElement('example_filtered', {
13 | prototype: element
14 | });
15 | }());
16 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "category": "atom",
4 | "title": "Example with filtered content",
5 | "type": "object",
6 | "format": "grid",
7 | "properties": {
8 | "text": {
9 | "title": "Text",
10 | "type": "string",
11 | "options": {
12 | "grid_columns": 4
13 | }
14 | },
15 | "formatted_text": {
16 | "title": "Formatted Text",
17 | "type": "string",
18 | "description": "Only links, bold/strong, and italic/emphasis tags are allowed (assuming CKEditor is the wysiwyg plugin). You can use any attributes on these elements (including class, style, and other attributes), except ones prefixed with \"data-\". This field's schema uses the \"disallowedContent\" rule to instruct CKEditor to strip these attributes. However, there is no server-side process that strips them. See Patternkit's README for details.",
19 | "format": "html",
20 | "options": {
21 | "wysiwyg": true,
22 | "allowedContent": "a b strong em i[*](*){*}",
23 | "disallowedContent": "*[data-*]"
24 | }
25 | },
26 | "hidden": {
27 | "title": "hidden",
28 | "type": "string"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.md:
--------------------------------------------------------------------------------
1 | This is an example Web Component showing content filtered by CKEditor, implemented as a Twig UI Pattern.
2 |
3 | It includes the following files:
4 |
5 | ## example_filtered.json
6 | The JSON schema file delineates the schema of the pattern. In some cases, libraries may generate these from the Twig Docblock of the pattern Twig template file.
7 |
8 | ## example_filtered.md
9 | This file. The text here should appear as pattern documentation in any Style Guide, Pattern Library, or pattern developer tool.
10 |
11 | This documentation often will include Style Rulesets, brand guidelines, designer intention, as well as usage advice.
12 |
13 | ## example_filtered.scss
14 | Optional SASS to be compiled to CSS
15 |
16 | ## example_filtered.ts
17 | Optional Typescript file to be transpiled to the final JS module. In some libraries, vanilla JS or other variants are used.
18 |
19 | ## example_filtered.twig
20 | The HTML template for the pattern. Some libraries may also use Nunjucks, JSX, or Handlebars.
21 |
22 | ## example_filtered.yml
23 | Sample data for the pattern. This can be used to generate demo sites, style guide example_filtereds, or pre-fill schema editors.
24 |
25 | ## index.js
26 | The Web Component entry point.
27 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.scss
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.ts
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.twig:
--------------------------------------------------------------------------------
1 | Sample twig template with filtered content.
2 |
3 | {{- text|striptags('') -}}
4 |
5 | The following block allows only tags <a><b><strong><em><i>, including any attributes on them
6 | (including <style>).
7 |
8 |
9 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_filtered/src/example_filtered.yml:
--------------------------------------------------------------------------------
1 | text: Sample text
2 | formatted_text: Sample formatted text with a link .
3 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/dist/example_ref.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/dist/example_ref.css
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/dist/example_ref.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/dist/example_ref.min.js
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/index.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | import './src/example.scss';
5 | import example from './dist/example';
6 |
7 | var element = Object.create(HTMLElement.prototype);
8 | element.createdCallback = function () {};
9 | element.attachedCallback = function () {};
10 | element.detachedCallback = function () {};
11 | element.attributeChangedCallback = function (attr, oldVal, newVal) {};
12 | document.registerElement('example', {
13 | prototype: element
14 | });
15 | }());
16 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "category": "atom",
4 | "title": "Example with Reference",
5 | "type": "object",
6 | "format": "grid",
7 | "properties": {
8 | "text": {
9 | "$ref": "@patternkit/atoms/example_ref/../example/src/example.json#/properties/text"
10 | },
11 | "nested_reference": {
12 | "$ref": "@patternkit/atoms/example_ref/../example/src/example.json"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.md:
--------------------------------------------------------------------------------
1 | This is an example Web Component with a Reference and include, implemented as a Twig UI Pattern.
2 |
3 | It includes the following files:
4 |
5 | ## example_ref.json
6 | The JSON schema file delineates the schema of the pattern. In some cases, libraries may generate these from the Twig Docblock of the pattern Twig template file.
7 |
8 | ## example_ref.md
9 | This file. The text here should appear as pattern documentation in any Style Guide, Pattern Library, or pattern developer tool.
10 |
11 | This documentation often will include Style Rulesets, brand guidelines, designer intention, as well as usage advice.
12 |
13 | ## example_ref.scss
14 | Optional SASS to be compiled to CSS
15 |
16 | ## example_ref.ts
17 | Optional Typescript file to be transpiled to the final JS module. In some libraries, vanilla JS or other variants are used.
18 |
19 | ## example_ref.twig
20 | The HTML template for the pattern. Some libraries may also use Nunjucks, JSX, or Handlebars.
21 |
22 | ## example_ref.yml
23 | Sample data for the pattern. This can be used to generate demo sites, style guide examples, or pre-fill schema editors.
24 |
25 | ## index.js
26 | The Web Component entry point.
27 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.scss
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drupal-pattern-lab/patternkit/2ec7389a5d8b088d27845f0ad9f6cd215446f697/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.ts
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.twig:
--------------------------------------------------------------------------------
1 | Example twig template with include and reference.
2 |
3 | {{- text|striptags('') -}}
4 |
5 | {% include '@patternkit/atoms/example/src/example.twig' with nested_reference %}
6 |
--------------------------------------------------------------------------------
/modules/patternkit_example/lib/patternkit/src/atoms/example_ref/src/example_ref.yml:
--------------------------------------------------------------------------------
1 | text: Sample text, included
2 |
--------------------------------------------------------------------------------
/modules/patternkit_example/patternkit_example.info.yml:
--------------------------------------------------------------------------------
1 | name: Patternkit Example
2 | description: Adds Patternkit example patterns.
3 | type: module
4 | package: Presentation Framework
5 | core: 8.x
6 | core_version_requirement: ^8 || ^9
7 | dependencies:
8 | - patternkit
9 |
--------------------------------------------------------------------------------
/modules/patternkit_example/patternkit_example.libraries.yml:
--------------------------------------------------------------------------------
1 | patternkit:
2 | version: VERSION
3 | css:
4 | theme:
5 | lib/patternkit/dist/patternkit.css: {}
6 | js:
7 | lib/patternkit/dist/patternkit.min.js: {}
8 | patterns:
9 | lib/patternkit/src: {plugin: twig}
10 |
--------------------------------------------------------------------------------
/modules/patternkit_example/patternkit_example.module:
--------------------------------------------------------------------------------
1 | = 11"]
8 | },
9 | "forceAllTransforms": true
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | js/globals**.js**
3 | node_modules
4 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/erbium
2 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | The Patternkit Media Library Support module allows you to use Drupal's media
4 | library browser to select images for patterns.
5 |
6 | This module will add a button to image fields. You can click this button to open
7 | the media library browser in a modal window.
8 |
9 | ## Installation
10 |
11 | Enable this module (e.g., `drush en patternkit_media_library`).
12 |
13 | ## Configuration
14 |
15 | You can configure this module at path
16 | `/admin/config/user-interface/patternkit/media-library`.
17 |
18 | ### Bundled styles
19 |
20 | You can choose whether to load supplemental styles for the media library
21 | browser. This feature relies on templates from [Media Library Theme
22 | Reset](https://www.drupal.org/project/media_library_theme_reset).
23 |
24 | ## Example
25 |
26 | This module adds the media library browser button to patterns where both:
27 |
28 | * `type` = `string`
29 | * `format` = `image`
30 |
31 | See [example.json](modules/patternkit_example/lib/patternkit/src/atoms/example/src/example.json).
32 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal-pattern-lab/patternkit_media_library",
3 | "license": "MIT",
4 | "authors": [
5 | {
6 | "name": "Red Hat Inc.",
7 | "email": "drupal@redhat.com"
8 | }
9 | ],
10 | "require": {},
11 | "config": {},
12 | "repositories": [],
13 | "scripts": {
14 | "js:build": [
15 | ". ~/.profile && nvm install && nvm use",
16 | "npm install && npx gulp"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/config/install/patternkit_media_library.settings.yml:
--------------------------------------------------------------------------------
1 | use_styles: false
2 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/config/schema/patternkit_media_library.schema.yml:
--------------------------------------------------------------------------------
1 | patternkit_media_library.settings:
2 | type: config_object
3 | label: 'Patternkit Media Library Module Settings'
4 | mapping:
5 | use_styles:
6 | type: boolean
7 | label: 'Boolean that sets whether to load bundled styles for the media library browser.'
8 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/css/media-library-modal.css:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Supplies decent defaults for the media library interface.
4 | */
5 |
6 | /* Renders the grid mode as a grid. */
7 | .patternkit-media-library .media-library-views-form__rows {
8 | display: flex;
9 | flex-wrap: wrap;
10 | }
11 | .patternkit-media-library .media-library-item--grid {
12 | /* Displays 3 items per row because the modal's width is max 900px. */
13 | width: calc(33% - 1em);
14 | margin: 0.5em;
15 | padding: 0.5em;
16 | border: 2px solid #dbdbdb;
17 | border-radius: 2px;
18 | }
19 | .patternkit-media-library .media-library-item--grid.is-hover,
20 | .patternkit-media-library .media-library-item--grid.checked {
21 | border-color: #5a8bed;
22 | }
23 | .patternkit-media-library .media-library-item--grid .media-library-item__click-to-select-trigger {
24 | cursor: pointer;
25 | }
26 |
27 | /* Ensures each Views exposed form is displayed inline. */
28 | .patternkit-media-library .form--inline,
29 | .patternkit-media-library .views-exposed-form {
30 | display: flex;
31 | align-items: end;
32 | margin-bottom: 1em;
33 | }
34 | .patternkit-media-library .form--inline > *,
35 | .patternkit-media-library .views-exposed-form > * {
36 | margin: 0 1em 0 0;
37 | }
38 |
39 | /* Gives the Views pager some sane defaults. */
40 | .patternkit-media-library .media-library-view nav[role="navigation"] ul {
41 | display: flex;
42 | flex-wrap: wrap;
43 | align-items: flex-end;
44 | justify-content: center;
45 | margin: 1em 0;
46 | list-style: none;
47 | text-align: center;
48 | font-weight: bold;
49 | }
50 | .patternkit-media-library .media-library-view nav[role="navigation"] ul > li {
51 | margin: 0 0.5em;
52 | }
53 |
54 | .patternkit-media-library .views-display-link.is-active {
55 | font-weight: 700;
56 | }
57 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const babelify = require('babelify');
3 | const browserify = require("browserify");
4 | const buffer = require('vinyl-buffer');
5 | const source = require('vinyl-source-stream');
6 |
7 | const gulpConfig = {
8 | src: './js/**es6.js',
9 | dist: './js',
10 | main: 'patternkit.jsoneditor.media_library.es6.js'
11 | };
12 |
13 | const compileEs6 = function () {
14 | return browserify(gulpConfig.dist + '/' + gulpConfig.main)
15 | .transform(babelify)
16 | .bundle()
17 | .pipe(source(gulpConfig.main.replace('.es6', '')))
18 | .pipe(buffer())
19 | .pipe(gulp.dest(gulpConfig.dist));
20 | };
21 |
22 | gulp.task('compile:es6', compileEs6);
23 |
24 | gulp.task('watch', function() {
25 | return gulp.watch(gulpConfig.src, 'compile:es6');
26 | });
27 |
28 | gulp.task('default', compileEs6);
29 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "patternkit-media-library",
3 | "version": "1.0.0-alpha",
4 | "description": "Media Library support for Patternkit.",
5 | "main": "js/patternkit.jsoneditor.media_library.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/drupal-pattern-lab/patternkit.git"
9 | },
10 | "author": "cybtachyon",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/drupal-pattern-lab/drupal-pattern-lab/issues"
14 | },
15 | "homepage": "https://github.com/drupal-pattern-lab/drupal-pattern-lab#readme",
16 | "dependencies": {},
17 | "devDependencies": {
18 | "@babel/cli": "^7.8.4",
19 | "@babel/core": "^7.8.7",
20 | "@babel/preset-env": "^7.8.7",
21 | "babelify": "^10.0.0",
22 | "browserify": "^16.5.0",
23 | "gulp": "^4.0.2",
24 | "gulp-concat": "^2.6.1",
25 | "gulp-rename": "^2.0.0",
26 | "gulp-replace": "^1.0.0",
27 | "gulp-sourcemaps": "^2.6.5",
28 | "vinyl-buffer": "^1.0.1",
29 | "vinyl-source-stream": "^2.0.0"
30 | },
31 | "scripts": {
32 | "build": "gulp",
33 | "dev": "gulp",
34 | "eslint": "eslint -c ./.eslintrc js/src/**/*.es6.js",
35 | "eslint.fix": "eslint -c ./.eslintrc js/src/**/*.es6.js --fix",
36 | "postversion": "git push && git push --tags",
37 | "serve-test": "http-server --p 9001",
38 | "watch": "gulp watch"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.info.yml:
--------------------------------------------------------------------------------
1 | name: Patternkit Media Library Support
2 | description: Adds Media Library Support to upload fields.
3 | type: module
4 | package: Presentation Framework
5 | core: 8.x
6 | core_version_requirement: ^8 || ^9
7 | configure: patternkit.settings
8 | dependencies:
9 | - drupal:media_library
10 | - drupal:patternkit
11 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.install:
--------------------------------------------------------------------------------
1 | moduleExists('entity_browser');
15 | $media_entity_browser_installed = \Drupal::moduleHandler()->moduleExists('media_entity_browser');
16 | if (!$entity_browser_installed || !$media_entity_browser_installed) {
17 | $requirements['patternkit_media_library_recommended_modules'] = [
18 | 'title' => t('Patternkit Media Library Recommended Modules'),
19 | 'description' => t('Patternkit Media Library recommends installing Entity Browser and Media Entity Browser for the best Media Library Experience.'),
20 | 'severity' => REQUIREMENT_INFO,
21 | ];
22 | }
23 |
24 | return $requirements;
25 | }
26 |
27 | /**
28 | * Sets default value for new config item 'patternkit_media_library.settings'.
29 | */
30 | function patternkit_media_library_update_8001(&$sandbox) {
31 | $config_key = 'patternkit_media_library.settings';
32 |
33 | $config_current = \Drupal::configFactory()->getEditable($config_key);
34 | // Installs the default config only if this config is not already set.
35 | if (is_null($config_current->get('use_styles'))) {
36 | /** @var \Drupal\Core\Config\ConfigInstaller $config_installer_service */
37 | $config_installer_service = \Drupal::service('config.installer');
38 | $config_installer_service->installDefaultConfig('module', 'patternkit_media_library');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.libraries.yml:
--------------------------------------------------------------------------------
1 | patternkit.jsoneditor.media_library:
2 | version: VERSION
3 | js:
4 | js/patternkit.jsoneditor.media_library.js: {}
5 | dependencies:
6 | - core/drupalSettings
7 | - core/jquery
8 | - media_library/ui
9 | - patternkit/jsoneditor
10 |
11 | media_library_modal:
12 | version: VERSION
13 | css:
14 | theme:
15 | css/media-library-modal.css: {}
16 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.links.task.yml:
--------------------------------------------------------------------------------
1 | patternkit.settings.media_library:
2 | title: 'Media Library Settings'
3 | route_name: patternkit.settings.media_library
4 | base_route: patternkit.settings
5 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.module:
--------------------------------------------------------------------------------
1 | toString();
18 | array_unshift($form_attached['library'], 'patternkit_media_library/patternkit.jsoneditor.media_library');
19 |
20 | $config = \Drupal::config('patternkit_media_library.settings');
21 | if ($config->get('use_styles')) {
22 | $form_attached['library'][] = 'patternkit_media_library/media_library_modal';
23 | }
24 |
25 | $form['settings']['configuration']['#attached'] = $form_attached;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.routing.yml:
--------------------------------------------------------------------------------
1 | patternkit.media_library:
2 | path: '/patternkit_media_library'
3 | defaults:
4 | _controller: '\Drupal\patternkit_media_library\Controller\PatternkitMediaLibraryController::mediaLibrary'
5 | requirements:
6 | _permission: 'administer blocks,view media'
7 |
8 | patternkit.settings.media_library:
9 | path: '/admin/config/user-interface/patternkit/media-library'
10 | defaults:
11 | _form: '\Drupal\patternkit_media_library\Form\MediaLibrarySettingsForm'
12 | _title: 'Patternkit Media Library settings'
13 | _description: 'Configure Patternkit Media Library Support.'
14 | options:
15 | _admin_route: TRUE
16 | requirements:
17 | _permission: 'access administration pages'
18 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/patternkit_media_library.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | patternkit.opener.jsonlibrary:
3 | class: Drupal\patternkit_media_library\MediaLibraryJSONLibraryOpener
4 | arguments: ['@entity_type.manager', '@file_url_generator']
5 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/src/Controller/PatternkitMediaLibraryController.php:
--------------------------------------------------------------------------------
1 | query;
28 | $ml_state = MediaLibraryState::create(
29 | $query->get('media_library_opener_id'),
30 | $query->get('media_library_allowed_types', []),
31 | $query->get('media_library_selected_type'),
32 | $query->get('media_library_remaining'),
33 | $query->get('media_library_opener_parameters', [])
34 | );
35 | if (!\Drupal::hasService('media_library.ui_builder')) {
36 | throw new ServiceNotFoundException('media_library.ui_builder');
37 | }
38 | /** @var \Drupal\media_library\MediaLibraryUiBuilder $ml_ui_builder */
39 | $ml_ui_builder = \Drupal::service('media_library.ui_builder');
40 |
41 | return $ml_ui_builder->buildUi($ml_state);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/src/Form/MediaLibrarySettingsForm.php:
--------------------------------------------------------------------------------
1 | config(static::SETTINGS);
27 |
28 | $form['use_styles'] = [
29 | '#type' => 'checkbox',
30 | '#title' => t('Use bundled styles for the media library modal window'),
31 | '#description' => t('If checked, then Patternkit will load some styles for
32 | the media library modal window. This feature relies on templates
33 | provided by
34 | Media Library Theme Reset , so enable that module if you want to use
35 | these styles.'),
36 | '#default_value' => $config->get('use_styles') ?? FALSE,
37 | ];
38 |
39 | return parent::buildForm($form, $form_state);
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | protected function getEditableConfigNames() :array {
46 | return [static::SETTINGS];
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | public function getFormId() :string {
53 | return 'patternkit_media_library_settings_form';
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function submitForm(array &$form, FormStateInterface $form_state): void {
60 | $form_values = $form_state->getValues();
61 | $config = $this->config(self::SETTINGS);
62 |
63 | $config->set('use_styles', (bool) $form_values['use_styles']);
64 | $config->save();
65 |
66 | parent::submitForm($form, $form_state);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_type_manager;
43 | $this->fileUrlGenerator = $file_url_generator;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
50 | return AccessResult::allowed();
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function getSelectionResponse(MediaLibraryState $state, array $selected_ids): AjaxResponse {
57 | $response = new AjaxResponse();
58 |
59 | $parameters = $state->getOpenerParameters();
60 | if (empty($parameters['field_widget_id'])) {
61 | throw new \InvalidArgumentException('field_widget_id parameter is missing.');
62 | }
63 |
64 | $widget_id = $parameters['field_widget_id'];
65 | if (!$mid = reset($selected_ids)) {
66 | return $response;
67 | }
68 | try {
69 | /** @var \Drupal\media\Entity\Media $media */
70 | $media = $this->entityTypeManager->getStorage('media')->load($mid);
71 | $fid = $media->getSource()->getSourceFieldValue($media);
72 | $file = $this->entityTypeManager->getStorage('file')->load($fid);
73 | }
74 | catch (\Exception $exception) {
75 | return $response;
76 | }
77 | if (!$file) {
78 | return $response;
79 | }
80 | try {
81 | if ($file->hasLinkTemplate('canonical')) {
82 | $url = $file->toUrl()->setAbsolute(FALSE);
83 | }
84 | elseif ($file->access('download')) {
85 | $url = $this->fileUrlGenerator->generateString($file->getFileUri());
86 | }
87 | else {
88 | $url = $file->label();
89 | }
90 | $response->addCommand(new PatternkitEditorUpdateCommand($widget_id, $url));
91 | }
92 | catch (\Exception $exception) {
93 | return $response;
94 | }
95 |
96 | return $response;
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "patternkit",
3 | "version": "1.0.0-alpha",
4 | "description": "Drupal module enabling drag and drop of templates into layouts.",
5 | "main": "js/patternkit.jsoneditor.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/drupal-pattern-lab/patternkit.git"
9 | },
10 | "author": "cybtachyon",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/drupal-pattern-lab/patternkit/issues"
14 | },
15 | "homepage": "https://github.com/drupal-pattern-lab/patternkit#readme",
16 | "dependencies": {},
17 | "devDependencies": {
18 | "@babel/cli": "^7.12.1",
19 | "@babel/core": "^7.10.3",
20 | "@babel/preset-env": "^7.10.3",
21 | "@json-editor/json-editor": "^2.7.0",
22 | "ajv": "^6.12.2",
23 | "babelify": "^10.0.0",
24 | "browserify": "^16.5.1",
25 | "gulp": "^4.0.2",
26 | "gulp-prefix-css": "0.0.4",
27 | "gulp-rename": "^2.0.0",
28 | "gulp-replace": "^1.0.0",
29 | "gulp-sourcemaps": "^2.6.5",
30 | "handlebars": "^4.7.7",
31 | "prosemirror-example-setup": "^1.1.2",
32 | "prosemirror-model": "^1.12.0",
33 | "prosemirror-schema-basic": "^1.1.2",
34 | "prosemirror-schema-list": "^1.1.4",
35 | "prosemirror-state": "^1.3.3",
36 | "prosemirror-view": "^1.16.1",
37 | "vinyl-buffer": "^1.0.1",
38 | "vinyl-source-stream": "^2.0.0"
39 | },
40 | "scripts": {
41 | "build": "gulp",
42 | "dev": "gulp",
43 | "eslint": "eslint -c ./.eslintrc js/src/**/*.es6.js",
44 | "eslint.fix": "eslint -c ./.eslintrc js/src/**/*.es6.js --fix",
45 | "postversion": "git push && git push --tags",
46 | "serve-test": "http-server --p 9001",
47 | "watch": "gulp watch"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/patternkit.api.php:
--------------------------------------------------------------------------------
1 | moduleExists('patternkit_example') ? Url::fromRoute('help.page', ['name' => 'patternkit_example'])->toString() : '#';
18 | $patternkit_media_library = \Drupal::moduleHandler()->moduleExists('patternkit_media_library') ? Url::fromRoute('help.page', ['name' => 'patternkit_media_library'])->toString() : '#';
19 |
20 | $output = <<About
22 | The Patternkit module loads your templates, patterns, and components into Drupal as blocks where you can then add them to your pages and layouts. You can configure them with a JSON file with an identical filename in the same directory as the Twig template.
\
23 | This allows Layout Builder, Page Manager, Drupal blocks layout, or anything that uses blocks, to function as a custom page-builder app.
24 | Site builders can add Drupal tokens to Pattern blocks instead of trying to pick through Drupal variables in a template. These are displayed from assigned Context such as node, user, and language.
25 |
26 | Uses
27 |
28 | Loading a library of patterns to use with Layout Builder
29 | Users with the Administer blocks permission can add Patternkit blocks to entity layouts.
30 | Loading a directory of patterns to use with the Block Layout
31 | Users with the Administer blocks permission can add Patternkit blocks to the site block layout.
32 |
33 | HTML;
34 | // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
35 | return t($output, [
36 | ':patternkit_example' => $patternkit_example,
37 | ':patternkit_media_library' => $patternkit_media_library,
38 | ]);
39 |
40 | default:
41 | return NULL;
42 | }
43 | }
44 |
45 | /**
46 | * Implements hook_theme().
47 | */
48 | function patternkit_theme($existing, $type, $theme, $path): array {
49 | return [
50 | 'patternkit_add_list' => [
51 | 'variables' => ['content' => NULL],
52 | ],
53 | ];
54 | }
55 |
--------------------------------------------------------------------------------
/patternkit.polyfil.php:
--------------------------------------------------------------------------------
1 | $v) {
25 | ++$i;
26 | if ($k !== $i) {
27 | return FALSE;
28 | }
29 | }
30 | return TRUE;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/patternkit.routing.yml:
--------------------------------------------------------------------------------
1 | entity.patternkit_block.collection:
2 | path: '/admin/structure/block/patternkit'
3 | defaults:
4 | _title: 'Patternkit block library'
5 | _entity_list: 'patternkit_block'
6 | requirements:
7 | _permission: 'administer blocks'
8 |
9 | entity.patternkit_block.canonical:
10 | path: '/patternkit_block/{patternkit_block}'
11 | defaults:
12 | _entity_form: 'patternkit_block.edit'
13 | options:
14 | _admin_route: TRUE
15 | requirements:
16 | _permission: 'administer blocks'
17 | patternkit_block: \d+
18 |
19 | entity.patternkit_block.edit_form:
20 | path: '/patternkit_block/{patternkit_block}'
21 | defaults:
22 | _entity_form: 'patternkit_block.edit'
23 | options:
24 | _admin_route: TRUE
25 | requirements:
26 | _permission: 'administer blocks'
27 | patternkit_block: \d+
28 |
29 | entity.patternkit_block.delete_form:
30 | path: '/patternkit_block/{patternkit_block}/delete'
31 | defaults:
32 | _entity_form: 'patternkit_block.delete'
33 | _title: 'Delete'
34 | options:
35 | _admin_route: TRUE
36 | requirements:
37 | _permission: 'administer blocks'
38 | patternkit_block: \d+
39 |
40 | patternkit.add_form:
41 | path: '/patternkit_block/add/{pattern_id}'
42 | defaults:
43 | _controller: '\Drupal\patternkit\Controller\PatternkitController::addForm'
44 | _title_callback: '\Drupal\patternkit\Controller\PatternkitController::getAddFormTitle'
45 | options:
46 | _admin_route: TRUE
47 | requirements:
48 | _permission: 'administer blocks'
49 |
50 | patternkit.add_page:
51 | path: '/patternkit_block/add'
52 | defaults:
53 | _controller: '\Drupal\patternkit\Controller\PatternkitController::add'
54 | _title: 'Add Patternkit block'
55 | options:
56 | _admin_route: TRUE
57 | requirements:
58 | _permission: 'administer blocks'
59 |
60 | patternkit.api:
61 | path: '/api/patternkit'
62 | defaults:
63 | _controller: '\Drupal\patternkit\Controller\PatternkitController::apiPattern'
64 | _disable_route_normalizer: 'TRUE'
65 | requirements:
66 | _access: 'TRUE'
67 |
68 | patternkit.settings:
69 | path: '/admin/config/user-interface/patternkit'
70 | defaults:
71 | _form: '\Drupal\patternkit\Form\PatternkitSettingsForm'
72 | _title: 'Patternkit settings'
73 | _description: 'Configure Patternkit Pattern Library Support.'
74 | options:
75 | _admin_route: TRUE
76 | requirements:
77 | _permission: 'access administration pages'
78 |
79 | patternkit.settings.json_settings:
80 | path: '/admin/config/user-interface/patternkit/json'
81 | defaults:
82 | _form: '\Drupal\patternkit\Form\PatternLibraryJSONForm'
83 | _title: 'Patternkit JSON Library settings'
84 | _description: 'Configure Patternkit JSON Pattern Library Support.'
85 | options:
86 | _admin_route: TRUE
87 | requirements:
88 | _permission: 'access administration pages'
89 |
--------------------------------------------------------------------------------
/patternkit.services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | logger.channel.patternkit:
3 | parent: logger.channel_base
4 | arguments: ['patternkit']
5 | path_processor.patterns:
6 | class: Drupal\patternkit\PathProcessor\PathProcessorPatterns
7 | tags:
8 | - { name: path_processor_inbound, priority: 200 }
9 | patternkit.asset.library:
10 | class: Drupal\patternkit\Asset\Library
11 | arguments: ['@cache.discovery', '@config.factory', '@lock', '@patternkit.pattern.discovery', '@patternkit.library.namespace_resolver']
12 | deprecated: 'The "%service_id%" service is deprecated. Use "patternkit.library.namespace_resolver" or "patternkit.pattern.discovery" instead.'
13 | patternkit.asset.library.parser.base:
14 | abstract: true
15 | arguments: [ '@serialization.json', '%app.root%', '@module_handler', '@theme.manager', '@stream_wrapper_manager', '@library.libraries_directory_file_finder', '@extension.path.resolver' ]
16 | patternkit.asset.library.parser.file:
17 | parent: patternkit.asset.library.parser.base
18 | class: Drupal\patternkit\Asset\PatternLibraryParser\FilePatternLibraryParser
19 | patternkit.asset.library.parser.json:
20 | parent: patternkit.asset.library.parser.base
21 | class: Drupal\patternkit\Asset\PatternLibraryParser\JSONPatternLibraryParser
22 | patternkit.asset.library.parser.twig:
23 | parent: patternkit.asset.library.parser.base
24 | class: Drupal\patternkit\Asset\PatternLibraryParser\TwigPatternLibraryParser
25 | patternkit.library.namespace_resolver:
26 | class: Drupal\patternkit\Asset\LibraryNamespaceResolver
27 | arguments: [ '@service_container', '@library.discovery', '@cache.discovery', '@lock' ]
28 | tags:
29 | - { name: needs_destruction }
30 | patternkit.pattern.discovery:
31 | class: Drupal\patternkit\Asset\PatternDiscovery
32 | arguments: [ '@patternkit.pattern.discovery.collector', '@patternkit.library.namespace_resolver']
33 | patternkit.pattern.discovery.collector:
34 | class: Drupal\patternkit\Asset\PatternDiscoveryCollector
35 | arguments: [ '@cache.discovery', '@lock', '@patternkit.library.namespace_resolver', '@plugin.manager.library.pattern', '@logger.channel.patternkit']
36 | tags:
37 | - { name: needs_destruction }
38 | plugin.manager.library.pattern:
39 | class: Drupal\patternkit\PatternLibraryPluginManager
40 | arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler']
41 | stream_wrapper.library:
42 | class: Drupal\patternkit\StreamWrapper\LibraryStream
43 | tags:
44 | - { name: stream_wrapper, scheme: library }
45 | stream_wrapper.patternkit:
46 | class: Drupal\patternkit\StreamWrapper\PatternkitStream
47 | tags:
48 | - { name: stream_wrapper, scheme: patternkit }
49 | twig.loader.patternlibrary:
50 | class: Drupal\patternkit\Loader\PatternLibraryLoader
51 | # We use '.' instead of '@app.root' as the path for non-namespaced template
52 | # files so that they match the relative paths of templates loaded via the
53 | # theme registry or via Twig namespaces.
54 | arguments: ['.', '@logger.channel.patternkit', '@patternkit.library.namespace_resolver']
55 | tags:
56 | - { name: twig.loader, priority: 100 }
57 | patternkit.translate_layout_builder_block:
58 | class: Drupal\patternkit\Routing\PatternkitTranslateBlockFormRouteSubscriber
59 | tags:
60 | - { name: event_subscriber }
61 | plugin.manager.pattern_field_processor:
62 | class: Drupal\patternkit\PatternFieldProcessorPluginManager
63 | arguments:
64 | - '@container.namespaces'
65 | - '@cache.discovery'
66 | - '@module_handler'
67 | - '@serialization.json'
68 | - '@patternkit.schema.schema_walker_factory'
69 | - '@logger.channel.patternkit'
70 | patternkit.schema.schema_factory:
71 | class: Drupal\patternkit\Schema\SchemaFactory
72 | arguments:
73 | - '@patternkit.schema.ref_provider'
74 | patternkit.schema.ref_provider:
75 | class: Drupal\patternkit\Schema\PatternkitRefProvider
76 | arguments:
77 | - '@patternkit.pattern.discovery'
78 | - '@patternkit.library.namespace_resolver'
79 | public: false
80 | patternkit.schema.schema_walker_factory:
81 | class: Drupal\patternkit\Schema\SchemaWalkerFactory
82 | arguments:
83 | - '@patternkit.schema.schema_factory'
84 | public: false
85 |
--------------------------------------------------------------------------------
/src/AJAX/PatternkitEditorUpdateCommand.php:
--------------------------------------------------------------------------------
1 | selector = $selector;
45 | $this->value = $value;
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function render(): array {
52 | return [
53 | 'command' => 'patternkitEditorUpdate',
54 | 'selector' => $this->selector,
55 | 'value' => $this->value,
56 | ];
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/Annotation/PatternFieldProcessor.php:
--------------------------------------------------------------------------------
1 | definition);
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/Asset/LibraryInterface.php:
--------------------------------------------------------------------------------
1 | collector = $collector;
46 | $this->resolver = $resolver;
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | public function getPatternsByNamespace(string $namespace): array {
53 | assert($namespace[0] === '@', 'The pattern namespace is expected to begin with an "@" symbol.');
54 |
55 | if (!isset($this->patternDefinitions[$namespace])) {
56 | $this->patternDefinitions[$namespace] = $this->collector->get($namespace);
57 | }
58 |
59 | return $this->patternDefinitions[$namespace];
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function getPatternDefinition(string $pattern): ?array {
66 | $namespace = $this->getNamespaceFromPatternIdentifier($pattern);
67 |
68 | $patterns = $this->getPatternsByNamespace($namespace);
69 | return $patterns[$pattern] ?? NULL;
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function getPatternDefinitions(): array {
76 | $libraries = $this->resolver->getLibraryDefinitions();
77 |
78 | foreach ($libraries as $library => $definition) {
79 | // Skip this library if it doesn't define any patterns.
80 | if (!isset($definition['patterns'])) {
81 | continue;
82 | }
83 |
84 | $this->getPatternsByNamespace($library);
85 | }
86 |
87 | return $this->patternDefinitions;
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function clearCachedDefinitions(): void {
94 | $this->patternDefinitions = [];
95 | $this->collector->clear();
96 | }
97 |
98 | /**
99 | * Get the namespace from a fully-namespaced pattern identifier.
100 | *
101 | * @param string $pattern
102 | * The fully-namespaced pattern identifier to parse.
103 | *
104 | * @return string
105 | * The namespace portion of the pattern identifier.
106 | */
107 | protected function getNamespaceFromPatternIdentifier(string $pattern): string {
108 | return strstr($pattern, '/', TRUE);
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/Asset/PatternDiscoveryInterface.php:
--------------------------------------------------------------------------------
1 | serializer::decode($assets['schema']);
31 | $schema = static::schemaDereference(
32 | $schema,
33 | $pattern
34 | );
35 | $assets['schema'] = $this->serializer::encode($schema);
36 |
37 | return $assets;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function parsePatternLibraryInfo(PatternLibrary $library, string $path): array {
44 | if (!file_exists($path)) {
45 | throw new InvalidLibraryFileException("Path $path does not exist.");
46 | }
47 | $metadata = [];
48 | $info = $library->getPatternInfo();
49 | if ($path[0] === '/') {
50 | // Trim the root path plus the trailing slash.
51 | $relative_path = substr($path, strlen($this->root . '/'));
52 | }
53 | else {
54 | $relative_path = $path;
55 | }
56 | $pattern_info = $info[$relative_path];
57 | foreach (self::discoverComponents($path, ['json']) as $name => $data) {
58 | if (empty($data['json']) || !file_exists($data['json'])) {
59 | continue;
60 | }
61 | $pattern_path = trim(substr($data['json'], strlen($path), -strlen('.json')), '/\\');
62 |
63 | $library_defaults = [
64 | '$schema' => 'https://json-schema.org/schema#',
65 | 'assets' => ['schema' => $data['json']],
66 | 'category' => $pattern_info['category'] ?? 'default',
67 | 'title' => $name,
68 | 'type' => 'object',
69 | 'format' => 'grid',
70 | 'library' => $library->id(),
71 | 'libraryPluginId' => $pattern_info['plugin'],
72 | 'license' => $library->license ?? [],
73 | 'name' => $name,
74 | 'path' => $pattern_path,
75 | 'properties' => (object) [],
76 | 'required' => [],
77 | 'version' => $library->version ?? '',
78 | ];
79 | if (!empty($data['json']) && $file_contents = file_get_contents($data['json'])) {
80 | $category_guess = $library->category ?? strstr($pattern_path, DIRECTORY_SEPARATOR, TRUE);
81 | $library_defaults['category'] = $category_guess ?? $library_defaults['category'];
82 | $library_defaults['assets']['json'] = $data['json'];
83 | $pattern = $this->createPattern($name, (array) $this->serializer::decode($file_contents) + $library_defaults);
84 | }
85 | else {
86 | // Create the pattern from defaults.
87 | // @todo Have this cleverly infer defaults from the template.
88 | $pattern = $this->createPattern($name, $library_defaults);
89 | }
90 | $pattern->filename = trim(substr($data['json'], strlen($path)), '/\\');
91 | // @todo Set $pattern->url to the actual URL of the pattern.
92 | // @todo Add default of library version fallback to extension version.
93 | $pattern->version ??= 'VERSION';
94 |
95 | $metadata[$pattern->getPath()] = $pattern->toArray();
96 | }
97 |
98 | return $metadata;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/Asset/PatternLibraryParser/WebcomponentPatternLibraryParser.php:
--------------------------------------------------------------------------------
1 | get(
39 | $url,
40 | [
41 | 'headers' => ['Content-Type' => 'application/json'],
42 | 'jsondata' => $config->rawJSON,
43 | // 'timeout' => 10,.
44 | 'method' => 'POST',
45 | ]
46 | );
47 |
48 | // @todo Request failure handling.
49 | // Create the stub object.
50 | $pk_obj = (object) [
51 | 'PatternkitPattern' => $subtype,
52 | 'attachments' => [],
53 | 'body' => 'fragment.html',
54 | ];
55 |
56 | $dir = "public://patternkit/$subtype/{$config->instance_id}";
57 | if (!\Drupal::service('file_system')->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY)) {
58 | \Drupal::service('messenger')->addMessage(
59 | t(
60 | 'Unable to create folder or save metadata/assets for plugin @plugin',
61 | ['@plugin' => $subtype]
62 | ));
63 | \Drupal::logger('patternkit')->error(
64 | 'Unable to create folder or save metadata/assets for plugin @plugin',
65 | ['@plugin' => $subtype]);
66 | }
67 |
68 | // Fetch the body html artifact.
69 | $save_result = \Drupal::service('file_system')->saveData(
70 | $request->getBody()->getContents(),
71 | "$dir/body.html",
72 | FileSystemInterface::EXISTS_REPLACE
73 | );
74 |
75 | // Convert stream wrapper to relative path, if appropriate.
76 | $scheme = StreamWrapperManagerInterface::getScheme($save_result);
77 | if ($scheme && \Drupal::service('stream_wrapper_manager')->isValidScheme($scheme)) {
78 | $wrapper = \Drupal::service('stream_wrapper_manager')->getViaScheme($scheme);
79 | $save_result = $wrapper->getDirectoryPath() . "/" . \Drupal::service('stream_wrapper_manager')::getTarget(
80 | $save_result);
81 | }
82 |
83 | $pk_obj->body = $save_result;
84 |
85 | $pk_obj->attachments['js']['https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.24/webcomponents.min.js'] = [
86 | 'type' => 'external',
87 | 'scope' => 'header',
88 | 'group' => JS_DEFAULT,
89 | 'weight' => 0,
90 | ];
91 |
92 | // Add the header link rel import.
93 | $pk_obj->attachments['drupal_add_html_head_link'][] = [
94 | [
95 | 'rel' => 'import',
96 | 'href' => $pk_obj->body,
97 | ],
98 | ];
99 |
100 | if (!$save_result) {
101 | \Drupal::service('messenger')->addMessage(
102 | t('Unable to save metadata/assets for plugin @plugin',
103 | ['@plugin' => $subtype]));
104 | \Drupal::logger('patternkit')->error(
105 | 'Unable to save metadata/assets for plugin @plugin',
106 | ['@plugin' => $subtype]);
107 | // @todo Handle the failure gracefully.
108 | }
109 |
110 | return Pattern::create((array) $pk_obj);
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | public function parsePatternLibraryInfo(PatternLibrary $library, $path): array {
117 | // @todo Implement parsePatternLibraryInfo() method.
118 | return [];
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/src/Element/PatternError.php:
--------------------------------------------------------------------------------
1 | 'pattern_error',
24 | * '#pattern' => Pattern,
25 | * '#config' => [
26 | * 'text' => '[node:title]',
27 | * 'formatted_text' => 'My formatted text
',
28 | * ],
29 | * '#context' => [
30 | * 'node' => $node,
31 | * ],
32 | * '#exception' => Exception,
33 | * ];
34 | * @endcode
35 | *
36 | * @RenderElement("pattern_error")
37 | */
38 | class PatternError extends RenderElement {
39 |
40 | /**
41 | * The permission to check for including debug output in error displays.
42 | */
43 | const DEBUG_PERMISSION = 'access devel information';
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function getInfo(): array {
49 | return [
50 | '#pre_render' => [
51 | [$this, 'preRenderPatternErrorElement'],
52 | [$this, 'preRenderDebugOutput'],
53 | ],
54 | '#pattern' => NULL,
55 | '#config' => [],
56 | '#context' => [],
57 | '#exception' => NULL,
58 | ];
59 | }
60 |
61 | /**
62 | * Pattern error element pre render callback to handle basic failure display.
63 | *
64 | * @param array $element
65 | * An associative array containing the properties of the pattern element.
66 | *
67 | * @return array
68 | * The modified element.
69 | */
70 | public function preRenderPatternErrorElement(array $element): array {
71 | /** @var \Drupal\patternkit\Entity\Pattern $pattern */
72 | $pattern = $element['#pattern'];
73 |
74 | // Return an error message to display in place of the rendered pattern.
75 | $message = $this->t('Failed to render pattern %pattern (%pattern_id).', [
76 | '%pattern' => $pattern->getName(),
77 | '%pattern_id' => $pattern->getAssetId(),
78 | ]);
79 | $element['message'] = [
80 | '#markup' => $message,
81 | ];
82 |
83 | return $element;
84 | }
85 |
86 | /**
87 | * Secondary pre render callback to prepare debug information if applicable.
88 | *
89 | * @param array $element
90 | * An associative array containing the properties of the pattern element.
91 | *
92 | * @return array
93 | * The modified element.
94 | */
95 | public function preRenderDebugOutput(array $element): array {
96 | // Skip altogether if the user doesn't have access to dev output.
97 | if (!$this->shouldDisplayDebugOutput()) {
98 | return $element;
99 | }
100 |
101 | if (isset($element['#exception'])) {
102 | /** @var \Exception $exception */
103 | $exception = $element['#exception'];
104 |
105 | // Collect all debug information in a collapsed container to avoid
106 | // overwhelming the user, especially in the case of multiple failures on
107 | // a single page.
108 | $element['debug'] = [
109 | '#type' => 'details',
110 | '#title' => $this->t('Debug output'),
111 | '#open' => FALSE,
112 | ];
113 |
114 | // Expose the exception message in a formatted block for easier
115 | // parsing by developers.
116 | $element['debug']['message'] = [
117 | '#markup' => '' . $exception->getMessage() . ' ',
118 | ];
119 | }
120 |
121 | return $element;
122 | }
123 |
124 | /**
125 | * Test if debug output should be displayed.
126 | *
127 | * @return bool
128 | * TRUE if debug output should be displayed. FALSE otherwise.
129 | */
130 | protected function shouldDisplayDebugOutput(): bool {
131 | return \Drupal::currentUser()->hasPermission(static::DEBUG_PERMISSION);
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/Entity/PatternInterface.php:
--------------------------------------------------------------------------------
1 | addDefaults([
22 | '_controller' => '\Drupal\patternkit\Controller\PatternkitController::patternView',
23 | '_title_callback' => '\Drupal\patternkit\Controller\PatternkitController::patternTitle',
24 | ])
25 | ->setRequirement('patternkit_pattern', '\d+')
26 | ->setRequirement('_entity_access', 'patternkit_pattern.view');
27 | $route_collection->add('entity.patternkit_pattern.canonical', $route);
28 |
29 | return $route_collection;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/Exception/SchemaException.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
29 |
30 | return $this;
31 | }
32 |
33 | /**
34 | * Get the schema being processed at the time of the exception.
35 | *
36 | * @return \Swaggest\JsonSchema\SchemaContract|null
37 | * The schema being processed at the time of the exception or null if it
38 | * was not set.
39 | */
40 | public function getSchema(): ?SchemaContract {
41 | return $this->schema;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/Exception/SchemaReferenceException.php:
--------------------------------------------------------------------------------
1 | entity;
28 |
29 | $form = parent::form($form, $form_state);
30 |
31 | if ($this->operation == 'edit') {
32 | $form['#title'] = $this->t('Edit Patternkit block %label', ['%label' => $block->label()]);
33 | }
34 | // Override the default CSS class name, since the user-defined custom block
35 | // type name in 'TYPE-block-form' potentially clashes with third-party class
36 | // names.
37 | $form['#attributes']['class'][0] = 'block-' . Html::getClass($block->bundle()) . '-form';
38 |
39 | return $form;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function save(array $form, FormStateInterface $form_state): void {
46 | $block = $this->entity;
47 |
48 | $insert = $block->isNew();
49 | $block->setReusable();
50 | $block->save();
51 | $context = ['@type' => $block->bundle(), '%info' => $block->label()];
52 | $logger = $this->logger('patternkit');
53 | $t_args = ['@type' => $block->bundle(), '%info' => $block->label()];
54 |
55 | if ($insert) {
56 | $logger->notice('@type: added %info.', $context);
57 | $this->messenger()->addStatus($this->t('@type %info has been created.', $t_args));
58 | }
59 | else {
60 | $logger->notice('@type: updated %info.', $context);
61 | $this->messenger()->addStatus($this->t('@type %info has been updated.', $t_args));
62 | }
63 |
64 | if ($block->id()) {
65 | $form_state->setValue('id', $block->id());
66 | $form_state->set('id', $block->id());
67 | if ($insert) {
68 | if (!$theme = $block->getTheme()) {
69 | $theme = $this->config('system.theme')->get('default');
70 | }
71 | if (!$pattern = $block->getPattern()) {
72 | $block->setPattern($form_state->getValue('pattern'));
73 | }
74 | $form_state->setRedirect(
75 | 'block.admin_add',
76 | [
77 | 'plugin_id' => 'patternkit_block:' . $block->uuid(),
78 | 'theme' => $theme,
79 | ]
80 | );
81 | }
82 | else {
83 | $form_state->setRedirectUrl($block->toUrl('collection'));
84 | }
85 | }
86 | else {
87 | // In the unlikely case something went wrong on save, the block will be
88 | // rebuilt and block form redisplayed.
89 | $this->messenger()->addError($this->t('The block could not be saved.'));
90 | $form_state->setRebuild();
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/FormElement/PatternkitJson.php:
--------------------------------------------------------------------------------
1 | getId();
25 |
26 | $container = \Drupal::getContainer();
27 | $patternBlock = PatternkitBlock::create($container, $configuration, 'patternkit_block', []);
28 | // Form state not used in this method, but is required for the block form.
29 | $form_state = new FormState();
30 |
31 | $form = $patternBlock->blockForm($form, $form_state);
32 | $form['langcode'] = [
33 | '#type' => 'hidden',
34 | '#default_value' => $translation_language->getId(),
35 | ];
36 | $form['patternkit_block_rid'] = [
37 | '#type' => 'hidden',
38 | '#default_value' => $configuration['patternkit_block_rid'],
39 | ];
40 |
41 | $form['patternkit_block_id'] = [
42 | '#type' => 'hidden',
43 | '#default_value' => $configuration['patternkit_block_id'],
44 | ];
45 |
46 | $form['instance_config']['#default_value'] = $translation_config['instance_config'];
47 |
48 | return $form;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Loader/PatternLibraryLoader.php:
--------------------------------------------------------------------------------
1 | getLibraryDefinitions();
34 | }
35 | catch (\Exception $exception) {
36 | $logger->error('Error loading pattern libraries: @message', ['@message' => $exception->getMessage()]);
37 | }
38 | foreach ($libraries as $namespace => $pattern_library) {
39 | if (isset($pattern_library['patterns'])) {
40 | foreach ($pattern_library['patterns'] as $info) {
41 | if (!isset($info['data'])) {
42 | continue;
43 | }
44 |
45 | // Trim the leading symbol for registered namespaces since they are
46 | // trimmed before lookup when loading templates.
47 | $this->addPath($info['data'], ltrim($namespace, '@'));
48 | }
49 | }
50 | }
51 | }
52 |
53 | /**
54 | * Adds a path where templates are stored.
55 | *
56 | * @param string $path
57 | * A path where to look for templates.
58 | * @param string $namespace
59 | * (optional) A path name.
60 | */
61 | public function addPath($path, $namespace = self::MAIN_NAMESPACE): void {
62 | // Invalidate the cache.
63 | $this->cache = [];
64 | $this->paths[$namespace][] = rtrim($path, '/\\');
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/PathProcessor/PathProcessorPatterns.php:
--------------------------------------------------------------------------------
1 | query->has('pattern')) {
22 |
23 | $pattern_path = preg_replace('|^\/api\/patternkit\/|', '', $path);
24 | $request->query->set('pattern', $pattern_path);
25 | return '/api/patternkit';
26 | }
27 | return $path;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/PatternEditorConfig.php:
--------------------------------------------------------------------------------
1 | $value) {
18 | if (property_exists($this, (string) $property)) {
19 | $this->{$property} = $value;
20 | unset($config->{$property});
21 | }
22 | }
23 | }
24 |
25 | /**
26 | * JSON-style field values used in provisioning the editor.
27 | *
28 | * @var object
29 | */
30 | public $fields;
31 |
32 | /**
33 | * Application hostname.
34 | *
35 | * @var string
36 | */
37 | public $hostname;
38 |
39 | /**
40 | * The URL to the icon stylesheet.
41 | *
42 | * @var string
43 | */
44 | public $iconStylesheet;
45 |
46 | // phpcs:disable Drupal.NamingConventions.ValidVariableName.LowerCamelName
47 | /**
48 | * The config instance id.
49 | *
50 | * Uses underscore_case for Drupal compatibility.
51 | *
52 | * @var string
53 | */
54 | public $instance_id;
55 |
56 | // phpcs:enable Drupal.NamingConventions.ValidVariableName.LowerCamelName
57 | /**
58 | * Optional lz-compressed serialized version of the configuration.
59 | *
60 | * @var string
61 | */
62 | public $lzstring;
63 |
64 | /**
65 | * Optional pattern to use for configuring the editor.
66 | *
67 | * @var \Drupal\patternkit\Entity\Pattern
68 | */
69 | public $pkdata;
70 |
71 | // phpcs:disable Drupal.NamingConventions.ValidVariableName.LowerCamelName
72 | /**
73 | * The presentation style for the pattern.
74 | *
75 | * Uses underscore_case for Drupal compatibility.
76 | *
77 | * @var string
78 | * Typically the following:
79 | * - html
80 | */
81 | public $presentation_style;
82 |
83 | // phpcs:enable Drupal.NamingConventions.ValidVariableName.LowerCamelName
84 | /**
85 | * The raw Editor configuration JSON.
86 | *
87 | * @var string
88 | */
89 | public $rawJSON;
90 |
91 | /**
92 | * The active theme.
93 | *
94 | * @var string
95 | */
96 | public $theme;
97 |
98 | /**
99 | * The theme stylesheet.
100 | *
101 | * @var string
102 | */
103 | public $themeStylesheet;
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/PatternLibraryPluginInterface.php:
--------------------------------------------------------------------------------
1 | t('Edit @title', ['@title' => $entity->label()]);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/Plugin/Field/FieldType/SerializedData.php:
--------------------------------------------------------------------------------
1 | setLabel(new TranslatableMarkup('Serialized data'))
28 | ->setRequired(TRUE);
29 |
30 | return $properties;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public static function schema(FieldStorageDefinitionInterface $field_definition): array {
37 | return [
38 | 'columns' => [
39 | 'value' => [
40 | 'type' => 'blob',
41 | 'size' => 'big',
42 | 'serialize' => TRUE,
43 | ],
44 | ],
45 | ];
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/Plugin/Menu/LocalAction/PatternkitAddLocalAction.php:
--------------------------------------------------------------------------------
1 | getParameter('theme')) {
21 | $options['query']['theme'] = $theme;
22 | }
23 | // Adds a destination on custom block listing.
24 | if ($route_match->getRouteName() === 'entity.patternkit.collection') {
25 | $options['query']['destination'] = Url::fromRoute('')->toString();
26 | }
27 | return $options;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/Plugin/PatternFieldProcessor/PatternFieldProcessorBase.php:
--------------------------------------------------------------------------------
1 | pluginDefinition['label'];
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/Plugin/PatternFieldProcessor/PatternFieldProcessorInterface.php:
--------------------------------------------------------------------------------
1 | token = $token;
54 | $this->twig = $twig;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
61 | /** @var \Drupal\Core\Utility\Token $token */
62 | $token = $container->get('token');
63 | /** @var \Drupal\Core\Template\TwigEnvironment $twig */
64 | $twig = $container->get('twig');
65 | return new static(
66 | $configuration,
67 | $plugin_id,
68 | $plugin_definition,
69 | $token,
70 | $twig,
71 | );
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function applies(SchemaContract $propertySchema, $propertyValue = NULL): bool {
78 | return property_exists($propertySchema, 'type')
79 | && $propertySchema->type == 'string';
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function apply(SchemaContract $propertySchema, $value, $context, BubbleableMetadata $bubbleableMetadata): string {
86 | $token_groups = $this->token->scan($value);
87 | $template = $value;
88 | $template_context = [];
89 | foreach ($token_groups as $group => $tokens) {
90 | $tokenized = $this->token->generate(
91 | $group,
92 | $tokens,
93 | $context,
94 | [],
95 | $bubbleableMetadata
96 | );
97 | foreach ($tokens as $token) {
98 | // Abort token replacement if we don't have a replacement value.
99 | if (!isset($tokenized[$token])) {
100 | continue;
101 | }
102 |
103 | $placeholder = preg_replace("/[^a-z]/", '', $token);
104 | $template_context[$placeholder] = $tokenized[$token];
105 | // If the user is not using Twig templating,
106 | // wrap with a Twig write so we can process it.
107 | $token_pos = strpos($template, (string) $token);
108 | $template_use_twig = strpos($template, '{{') < $token_pos
109 | && strpos($template, '}}', $token_pos + strlen($token)) !== FALSE;
110 | if (!$template_use_twig) {
111 | $placeholder = '{{' . $placeholder . '}}';
112 | }
113 | $template = str_replace($token, $placeholder, $template);
114 | }
115 | }
116 | return (string) $this->twig->renderInline($template, $template_context);
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/Plugin/PatternFieldProcessor/WysiwygFieldProcessor.php:
--------------------------------------------------------------------------------
1 | patternkitSettings = $patternkitSettings;
54 | $this->renderer = $renderer;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
61 | /** @var \Drupal\Core\Config\ConfigFactoryInterface $configFactory */
62 | $configFactory = $container->get('config.factory');
63 | $settings = $configFactory->get('patternkit.settings');
64 | /** @var \Drupal\Core\Render\RendererInterface $renderer */
65 | $renderer = $container->get('renderer');
66 | return new static(
67 | $configuration,
68 | $plugin_id,
69 | $plugin_definition,
70 | $settings,
71 | $renderer,
72 | );
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | *
78 | * Expect a global text profile to be configured, and a property schema like
79 | * the following:
80 | *
81 | * @code
82 | * {
83 | * "type" => "string",
84 | * "format" => "html",
85 | * "options" => {
86 | * "wysiwyg" => true,
87 | * }
88 | * }
89 | * @endcode
90 | */
91 | public function applies(SchemaContract $propertySchema, $propertyValue = NULL): bool {
92 | $textProfile = $this->getTextProfile();
93 | return is_string($textProfile) && $textProfile !== ''
94 | && property_exists($propertySchema, 'type') && $propertySchema->type == 'string'
95 | && property_exists($propertySchema, 'format') && $propertySchema->format == 'html'
96 | && isset($propertySchema->options) && property_exists($propertySchema->options, 'wysiwyg')
97 | && $propertySchema->options->wysiwyg == TRUE;
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | public function apply(SchemaContract $propertySchema, $value, array $context, BubbleableMetadata $bubbleableMetadata): string {
104 | $element = [
105 | '#type' => 'processed_text',
106 | '#text' => $value,
107 | '#format' => $this->getTextProfile(),
108 | ];
109 |
110 | return (string) $this->renderer->render($element);
111 | }
112 |
113 | /**
114 | * Get the globally configured WYSIWYG text editor profile.
115 | *
116 | * @return string|null
117 | * The machine name of the text profile configured in Patternkit settings.
118 | */
119 | public function getTextProfile(): ?string {
120 | return $this->patternkitSettings->get('patternkit_json_editor_ckeditor_toolbar');
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/Plugin/PatternLibrary/PatternLibraryFile.php:
--------------------------------------------------------------------------------
1 | getParameter('app.root');
51 | /** @var \Drupal\patternkit\Asset\PatternLibraryParserInterface $file_parser */
52 | $file_parser = $container->get('patternkit.asset.library.parser.file');
53 | /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
54 | $config_factory = $container->get('config.factory');
55 | return new static($root, $file_parser, $config_factory, $configuration, $plugin_id, $plugin_definition);
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function getEditor(PatternInterface $pattern = NULL, ?PatternEditorConfig $config = NULL): string {
62 | // @todo Implement getEditor() method.
63 | return '';
64 | }
65 |
66 | /**
67 | * Overrides the JSON Library render method.
68 | *
69 | * {@inheritdoc}
70 | *
71 | * @throws \Throwable
72 | *
73 | * @todo Return render arrays for File only.
74 | */
75 | public function render(array $assets): array {
76 | $elements = [];
77 | foreach ($assets as $pattern) {
78 | if (empty($pattern->filename)) {
79 | return [];
80 | }
81 | // @todo Allow filename to cache the template contents based on settings.
82 | $template = $pattern->filename;
83 | // Add the namespace, if provided.
84 | if (!empty($pattern->url)) {
85 | $template = '@' . $pattern->url . '#/' . $template;
86 | }
87 | $namespace = '';
88 | $file = $template;
89 | // If a namespace is provided, break it up.
90 | if (strpos($template, '@') === 0) {
91 | [$namespace, $file] = explode('#', $template);
92 | }
93 | $bare = basename($file);
94 | /** @var \Drupal\Core\Template\TwigEnvironment $twig */
95 | $twig = \Drupal::service('twig');
96 | $template = $twig->load("$namespace/$pattern->filename");
97 | $elements[] = $template->render($pattern->config ?? []);
98 | }
99 | return $elements;
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/Plugin/PatternLibrary/PatternLibraryREST.php:
--------------------------------------------------------------------------------
1 | state = $state;
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function getEditor(PatternInterface $pattern = NULL,
58 | PatternEditorConfig $config = NULL): void {
59 | // @todo Implement getEditor() method.
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function getMetadata(Extension $extension, PatternLibrary $library, string $path): array {
66 | // @todo Implement getMetadata() method.
67 | return [];
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | *
73 | * This class evaluates the aggregation enabled/disabled condition on a group
74 | * by group basis by testing whether an aggregate file has been made for the
75 | * group rather than by testing the site-wide aggregation setting. This allows
76 | * this class to work correctly even if modules have implemented custom
77 | * logic for grouping and aggregating files.
78 | *
79 | * @throws \Exception
80 | */
81 | public function render(array $assets): array {
82 | $elements = [];
83 | /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */
84 | foreach ($assets as $pattern) {
85 | $config = $pattern->config ?? [];
86 | if (empty($config['presentation_style']) || empty($config['instance_id'])) {
87 | return [];
88 | }
89 | $pattern_name = $pattern->label();
90 | if ($config['presentation_style'] === 'webcomponent') {
91 | $elements[] = "<$pattern_name-pattern>$pattern_name-pattern>";
92 | }
93 | else {
94 | $filename = "public://patternkit/$pattern_name/{$config['instance_id']}/body.html";
95 | $elements[] = file_get_contents($filename);
96 | }
97 | }
98 | return $elements;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/Plugin/PatternLibrary/PatternLibraryTwig.php:
--------------------------------------------------------------------------------
1 | parser = $container->get('patternkit.asset.library.parser.twig');
38 |
39 | return $instance;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function fetchAssets(PatternInterface $pattern, ?PatternEditorConfig $config = NULL): array {
46 | return $this->parser->fetchPatternAssets($pattern, $config);
47 | }
48 |
49 | /**
50 | * Overrides the JSON Library render method.
51 | *
52 | * {@inheritdoc}
53 | *
54 | * @throws \Throwable
55 | *
56 | * @todo Return render arrays for Twig only.
57 | */
58 | public function render(array $assets): array {
59 | $elements = [];
60 | /** @var \Drupal\patternkit\Entity\Pattern $pattern */
61 | foreach ($assets as $pattern) {
62 | $template = $pattern->getTemplate();
63 | if (empty($template)) {
64 | return [];
65 | }
66 | $pattern->config ??= [];
67 | $output = [
68 | '#type' => 'inline_template',
69 | '#template' => $template,
70 | '#context' => $pattern->config,
71 | ];
72 | if ($this->getTwigEnvironment()->isDebug()) {
73 | $hash = $pattern->getHash();
74 | $asset_id = $pattern->getAssetId();
75 | $path = trim($pattern->getAssets()['twig'] ?: $this->t('Could not resolve file path.'), DRUPAL_ROOT);
76 | try {
77 | $version_value = $pattern->get('version')->getValue();
78 | $version = array_pop($version_value)['value'];
79 | }
80 | catch (\Exception $exception) {
81 | $version = $this->t('No version information available.');
82 | }
83 | $output['#template'] = "\n\n"
84 | . "\n'
85 | . "\n'
86 | . "\n'
87 | . "\n\n\n"
88 | . $output['#template']
89 | .= "\n\n\n";
90 | }
91 | $elements[] = $output;
92 | }
93 | return $elements;
94 | }
95 |
96 | /**
97 | * Get the lazy-loaded twig environment service.
98 | *
99 | * Since this plugin is loaded from the patternkit twig loader
100 | * ('@twig.loader.patternlibrary'), a circular dependency on the twig
101 | * environment is created. To resolve this, the twig enviornment is only
102 | * needed at render time for this plugin, so it is lazy loaded from the
103 | * container at that time.
104 | *
105 | * @return \Drupal\Core\Template\TwigEnvironment
106 | * The twig environment service.
107 | *
108 | * @see \Drupal\patternkit\Loader\PatternLibraryLoader
109 | */
110 | public function getTwigEnvironment(): TwigEnvironment {
111 | if (!$this->twig) {
112 | $this->twig = \Drupal::service('twig');
113 | }
114 |
115 | return $this->twig;
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/Routing/PatternkitTranslateBlockFormRouteSubscriber.php:
--------------------------------------------------------------------------------
1 | get('layout_builder.translate_block')) {
18 | $route->setDefault('_form', '\Drupal\patternkit\Form\PatternkitTranslateBlockForm');
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/Schema/DataPreProcessor/ObjectCoercionDataPreProcessor.php:
--------------------------------------------------------------------------------
1 | type == 'object' && is_array($data)) {
18 | $data = (object) $data;
19 | }
20 |
21 | return $data;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/Schema/SchemaWalker.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
37 |
38 | $schemaIterator = new SchemaIterator($schema, $values, $flags);
39 | parent::__construct($schemaIterator, $mode, $flags);
40 | }
41 |
42 | /**
43 | * Get the top-level schema being traversed.
44 | *
45 | * @return \Swaggest\JsonSchema\SchemaContract
46 | * The schema definition being traversed.
47 | */
48 | public function getSchema(): SchemaContract {
49 | return $this->schema;
50 | }
51 |
52 | /**
53 | * Get the schema for the current property in traversal.
54 | *
55 | * @return \Swaggest\JsonSchema\SchemaContract|null
56 | * The loaded schema for the current property or NULL if one was unable to
57 | * be identified.
58 | *
59 | * @throws \Drupal\patternkit\Exception\SchemaValidationException
60 | */
61 | public function getPropertySchema(): ?SchemaContract {
62 | /** @var \Drupal\patternkit\Schema\SchemaIterator $iterator */
63 | $iterator = $this->getSubIterator();
64 |
65 | if ($iterator) {
66 | return $iterator->getPropertySchema();
67 | }
68 | else {
69 | return NULL;
70 | }
71 | }
72 |
73 | /**
74 | * Set the current key to the given value and propagate it up the tree.
75 | *
76 | * @param mixed $value
77 | * The value to be set at the current iteration key.
78 | */
79 | public function setCurrentValue($value): void {
80 | // Get the current depth and traverse back up the tree, saving the
81 | // modifications at each level along the way.
82 | $currentDepth = $this->getDepth();
83 | for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) {
84 | // Get the current level iterator.
85 | /** @var \Drupal\patternkit\Schema\SchemaIterator $subIterator */
86 | $subIterator = $this->getSubIterator($subDepth);
87 | // If we are on the level we want to change, use the replacement ($value),
88 | // otherwise set the key to the parent iterator's value.
89 | $subIterator->offsetSet($subIterator->key(),
90 | ($subDepth === $currentDepth ? $value : $this->getSubIterator(($subDepth + 1))->getArrayCopy()));
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/Schema/SchemaWalkerFactory.php:
--------------------------------------------------------------------------------
1 | schemaFactory = $schemaFactory;
31 | }
32 |
33 | /**
34 | * Create a new SchemaWalker instance.
35 | *
36 | * @param \Swaggest\JsonSchema\SchemaContract $schema
37 | * The schema for reference during traversal with values.
38 | * @param array|object $values
39 | * The values to be used to traverse the schema.
40 | *
41 | * @return \Drupal\patternkit\Schema\SchemaWalker
42 | * A configured SchemaWalker instance.
43 | */
44 | public function createInstance(SchemaContract $schema, $values): SchemaWalker {
45 | return new SchemaWalker($schema, $values);
46 | }
47 |
48 | /**
49 | * Create a new SchemaWalker from a schema's JSON string.
50 | *
51 | * @param string $schema_json
52 | * The string representation of the schema to be used.
53 | * @param array|object $values
54 | * Values to be traversed against the schema.
55 | *
56 | * @return \Drupal\patternkit\Schema\SchemaWalker
57 | * A configured SchemaWalker instance.
58 | *
59 | * @throws \Drupal\patternkit\Exception\SchemaReferenceException
60 | * @throws \Drupal\patternkit\Exception\SchemaValidationException
61 | * @throws \Drupal\patternkit\Exception\SchemaException
62 | */
63 | public function createFromString(string $schema_json, $values): SchemaWalker {
64 | $schema = $this->schemaFactory->createInstance($schema_json);
65 |
66 | return $this->createInstance($schema, $values);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/StreamWrapper/LibraryStream.php:
--------------------------------------------------------------------------------
1 | uri;
23 | }
24 |
25 | [, $path] = explode('://', $uri, 2);
26 |
27 | // Remove erroneous leading or trailing, forward-slashes and backslashes.
28 | $uri = trim($path, '\/');
29 |
30 | /** @var \Drupal\Core\Asset\AssetResolverInterface $asset_resolver */
31 | $asset_resolver = \Drupal::service('asset.resolver');
32 | [$extension, $library, $type_level] = explode('/', $uri);
33 | // @todo Allow retrieving specific filenames and css levels.
34 | $filename = substr($uri, strlen($extension . $library . $type_level));
35 | [$type, $level] = array_pad(explode('.', $type_level), 2, NULL);
36 | // Resolve the attached libraries into asset collections.
37 | $assets = new AttachedAssets();
38 | $assets->setLibraries(["$extension/$library"]);
39 | switch ($type) {
40 | case 'js':
41 | $js_header_footer = $asset_resolver->getJsAssets($assets, TRUE);
42 | $query_assets = array_merge($js_header_footer[0], $js_header_footer[1]);
43 | break;
44 |
45 | case 'css':
46 | $query_assets = $asset_resolver->getCssAssets($assets, TRUE);
47 | break;
48 |
49 | default:
50 | $js = $asset_resolver->getJsAssets($assets, TRUE);
51 | $css = $asset_resolver->getCssAssets($assets, TRUE);
52 | // @todo Create a temporary {libraryname}.json that holds the data.
53 | return ['js' => $js, 'css' => $css];
54 | }
55 | [, $target] = explode('://', reset($query_assets)['data'], 2);
56 | return $target;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function getName() {
63 | return t('Library files');
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function getDescription() {
70 | return t('Library files for usage in processing and rendering.');
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/StreamWrapper/PatternkitStream.php:
--------------------------------------------------------------------------------
1 | getTempDirectory();
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function getExternalUrl() {
49 | $path = str_replace('\\', '/', $this->getTarget());
50 | return Url::fromRoute('system.patternkit', [], [
51 | 'absolute' => TRUE,
52 | 'query' => ['file' => $path],
53 | ])->toString();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/templates/patternkit-add-list.html.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * @file
4 | * Default theme implementation to present a list of Patternkit pattern blocks.
5 | *
6 | * Available content variables:
7 | * - types: A collection of all the available custom block types.
8 | * Each block type contains the following:
9 | * - link: A link to add a block of this type.
10 | * - description: A description of this custom block type.
11 | *
12 | * @ingroup themeable
13 | */
14 | #}
15 | {% spaceless %}
16 |
17 | {% for type in content.types %}
18 | {{ type.link }}
19 | {{ type.description }}
20 | {% endfor %}
21 |
22 | {% endspaceless %}
23 |
--------------------------------------------------------------------------------
/tests/modules/patternkit_test/lib:
--------------------------------------------------------------------------------
1 | ../../../modules/patternkit_example/lib
--------------------------------------------------------------------------------
/tests/modules/patternkit_test/patternkit_test.info.yml:
--------------------------------------------------------------------------------
1 | name: 'Patternkit module tests'
2 | type: module
3 | description: 'Support module for patternkit related testing.'
4 | package: Testing
5 | version: VERSION
6 |
--------------------------------------------------------------------------------
/tests/modules/patternkit_test/patternkit_test.libraries.yml:
--------------------------------------------------------------------------------
1 | patternkit_twig:
2 | version: VERSION
3 | css:
4 | theme:
5 | lib/patternkit/dist/patternkit.css: { }
6 | js:
7 | lib/patternkit/dist/patternkit.min.js: { }
8 | patterns:
9 | lib/patternkit/src: {plugin: twig}
10 |
11 | patternkit_json:
12 | version: VERSION
13 | css:
14 | theme:
15 | lib/patternkit/dist/patternkit.css: {}
16 | js:
17 | lib/patternkit/dist/patternkit.min.js: {}
18 | patterns:
19 | lib/patternkit/src: {plugin: json}
20 |
--------------------------------------------------------------------------------
/tests/modules/patternkit_test/src/Plugin/PatternFieldProcessor/ExceptionThrowerProcessor.php:
--------------------------------------------------------------------------------
1 | container->get('entity_type.manager');
53 | $this->patternBlockStorage = $entity_type_manager->getStorage('patternkit_block');
54 | $this->patternStorage = $entity_type_manager->getStorage('patternkit_pattern');
55 | }
56 |
57 | /**
58 | * Test that the settings form loads as expected.
59 | */
60 | public function testPatternkitSettingsForm() {
61 | $account = $this->drupalCreateUser(['access administration pages']);
62 | $this->drupalLogin($account);
63 |
64 | $this->drupalGet('admin/config/user-interface/patternkit');
65 |
66 | $assert = $this->assertSession();
67 |
68 | $assert->statusCodeEquals(200);
69 | $assert->pageTextNotContains('Unable to load Patternkit libraries list. Check the logs for more information.');
70 | $assert->pageTextNotContains('Settings are unavailable when Pattern libraries fail to load to prevent config errors.');
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/tests/src/Kernel/Asset/PatternDiscoveryTest.php:
--------------------------------------------------------------------------------
1 | discovery = $this->container->get('patternkit.pattern.discovery');
39 | }
40 |
41 | /**
42 | * @covers ::getPatternsByNamespace
43 | */
44 | public function testGetPatternsByNamespace() {
45 | $definitions = $this->discovery->getPatternsByNamespace('@patternkit');
46 |
47 | $this->assertIsArray($definitions);
48 | $this->assertCount(3, $definitions);
49 | $this->assertArrayHasKey('@patternkit/atoms/example/src/example', $definitions);
50 | $this->assertArrayHasKey('@patternkit/atoms/example_filtered/src/example_filtered', $definitions);
51 | $this->assertArrayHasKey('@patternkit/atoms/example_ref/src/example_ref', $definitions);
52 | }
53 |
54 | /**
55 | * @covers ::getPatternDefinitions
56 | */
57 | public function testGetPatternDefinitions() {
58 | $definitions = $this->discovery->getPatternDefinitions();
59 |
60 | $this->assertIsArray($definitions);
61 |
62 | // With these enabled modules, there should only be one namespace defined.
63 | $this->assertCount(1, $definitions);
64 | $this->assertArrayHasKey('@patternkit', $definitions);
65 |
66 | // Within the @patternkit namespace there should be 3 definitions.
67 | $this->assertArrayHasKey('@patternkit/atoms/example/src/example', $definitions['@patternkit']);
68 | $this->assertArrayHasKey('@patternkit/atoms/example_filtered/src/example_filtered', $definitions['@patternkit']);
69 | $this->assertArrayHasKey('@patternkit/atoms/example_ref/src/example_ref', $definitions['@patternkit']);
70 | }
71 |
72 | /**
73 | * @covers ::clearCachedDefinitions
74 | */
75 | public function testClearCachedDefinitions() {
76 | $cache = $this->container->get('cache.discovery');
77 | $collector = $this->container->get('patternkit.pattern.discovery.collector');
78 |
79 | $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID);
80 | $this->assertFalse($cacheEntry, 'Expected the pattern discovery cache to be empty initially.');
81 |
82 | $definitions = $this->discovery->getPatternDefinitions();
83 |
84 | // Trigger end of request destruction to persist data for testing.
85 | $collector->destruct();
86 |
87 | $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID);
88 | $this->assertIsArray($cacheEntry->data);
89 | $this->assertNotEmpty($cacheEntry->data);
90 | $this->assertArrayHasKey('@patternkit', $cacheEntry->data, 'Expected cache entry to contain "@patternkit" patterns.');
91 | $this->assertNotEmpty($cacheEntry->data['@patternkit'], 'Expected patterns to be discovered and cached in the "@patternkit" namespace.');
92 |
93 | $this->discovery->clearCachedDefinitions();
94 | $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID);
95 | $this->assertFalse($cacheEntry, 'Expected pattern cache entry to be empty after clearing definitions.');
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/tests/src/Kernel/Asset/PatternLibraryParser/PatternLibraryParserTestBase.php:
--------------------------------------------------------------------------------
1 | patternDiscovery = $this->container->get('patternkit.pattern.discovery');
50 | }
51 |
52 | /**
53 | * Test basic component discovery.
54 | *
55 | * @covers ::discoverComponents
56 | */
57 | public function testDiscoverComponents() {
58 | $module_path = $this->getModulePath('patternkit_example');
59 | $component_path = $module_path . '/lib/patternkit/src';
60 | $components = PatternLibraryParserBase::discoverComponents($component_path, ['json']);
61 | $expected_components = [
62 | 'example' =>
63 | [
64 | 'json' => $module_path . '/lib/patternkit/src/atoms/example/src/example.json',
65 | ],
66 | 'example_filtered' =>
67 | [
68 | 'json' => $module_path . '/lib/patternkit/src/atoms/example_filtered/src/example_filtered.json',
69 | ],
70 | 'example_ref' =>
71 | [
72 | 'json' => $module_path . '/lib/patternkit/src/atoms/example_ref/src/example_ref.json',
73 | ],
74 | ];
75 |
76 | $this->assertIsArray($components);
77 | $this->assertEquals($expected_components, $components);
78 | }
79 |
80 | /**
81 | * @covers ::createPattern
82 | */
83 | public function testCreatePattern() {
84 | $this->markTestIncomplete('Test not yet implemented.');
85 | }
86 |
87 | /**
88 | * Test asset fetching.
89 | *
90 | * @covers ::fetchAssets
91 | */
92 | public function testFetchAssets() {
93 | $this->markTestIncomplete('Test not yet implemented.');
94 | }
95 |
96 | /**
97 | * @covers ::fetchPatternAssets
98 | */
99 | public function testFetchPatternAssets() {
100 | $this->markTestIncomplete('Test not yet implemented.');
101 | }
102 |
103 | /**
104 | * @covers ::fetchSingleAsset
105 | */
106 | public function testFetchSingleAsset() {
107 | $this->markTestIncomplete('Test not yet implemented.');
108 | }
109 |
110 | /**
111 | * @covers ::fetchFragmentAssets
112 | */
113 | public function testFetchFragmentAssets() {
114 | $this->markTestIncomplete('Test not yet implemented.');
115 | }
116 |
117 | /**
118 | * @covers ::parsePatternLibraryInfo
119 | */
120 | public function testParsePatternLibraryInfo() {
121 | $this->markTestIncomplete('Test not yet implemented.');
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/tests/src/Kernel/Loader/PatternLibraryLoaderTest.php:
--------------------------------------------------------------------------------
1 | container->get('twig.loader.patternlibrary');
30 | $namespaces = $patternLoader->getNamespaces();
31 |
32 | $this->assertIsArray($namespaces);
33 | $this->assertNotEmpty($namespaces, 'Pattern loader namespaces were not expected to be empty.');
34 | $this->assertContains('patternkit', $namespaces, 'A "patternkit" namespace was expected for registration.');
35 | $this->assertEquals(
36 | [$this->getModulePath('patternkit_example') . '/lib/patternkit/src'],
37 | $patternLoader->getPaths('patternkit'),
38 | 'The path registered for the "patternkit" namespace did not match expectations.');
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/tests/src/Traits/JsonDecodeTrait.php:
--------------------------------------------------------------------------------
1 | getBlockManager()->createInstance($plugin_id);
61 |
62 | // Fake the form state to run submission handling.
63 | $form_state = new FormState();
64 | $form_state->setValues([
65 | 'instance_config' => $config,
66 | 'label' => '',
67 | 'reusable' => FALSE,
68 | 'presentation_style' => 'html',
69 | 'version' => NULL,
70 | ]);
71 | $block->blockSubmit([], $form_state);
72 |
73 | return $block;
74 | }
75 |
76 | /**
77 | * Place a block in an entity layout with layout builder enabled.
78 | *
79 | * @param \Drupal\Core\Entity\EntityInterface $entity
80 | * The entity to place the block into the layout display of.
81 | * @param \Drupal\patternkit\Plugin\Block\PatternkitBlock $block
82 | * The block instance to place in the layout.
83 | */
84 | protected function placePatternBlockInLayout(EntityInterface $entity, BlockPatternkitBlock $block): void {
85 | $storage = $this->getSectionStorageForEntity($entity);
86 | $section = $storage->getSection(0);
87 |
88 | $config = $block->getConfiguration();
89 | unset($config['instance_uuid']);
90 | $component = new SectionComponent($entity->uuid(), 'content', $config);
91 | $section->appendComponent($component);
92 |
93 | $storage->save();
94 | }
95 |
96 | /**
97 | * Get the block manager service.
98 | *
99 | * @return \Drupal\Core\Block\BlockManagerInterface
100 | * The block manager service.
101 | */
102 | private function getBlockManager(): BlockManagerInterface {
103 | return $this->blockManager ?: $this->blockManager = \Drupal::service('plugin.manager.block');
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/tests/src/Traits/SchemaFixtureTrait.php:
--------------------------------------------------------------------------------
1 | pathMap[$type][$name];
28 | }
29 |
30 | /**
31 | * Set a map of return values for drupalGetPath based on passed arguments.
32 | *
33 | * @param array $map
34 | * A two-dimensional array of return values keyed by:
35 | * - $type
36 | * - $name
37 | *
38 | * @return void
39 | */
40 | public function setPathMapping(array $map): void {
41 | $this->pathMap = $map;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/tests/src/Unit/Schema/TestPatternkitRefProvider.php:
--------------------------------------------------------------------------------
1 | patternSchemas = [];
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function getPatternSchema(string $asset_id): string {
33 | if (isset($this->patternSchemas[$asset_id])) {
34 | return $this->patternSchemas[$asset_id];
35 | }
36 | else {
37 | throw new SchemaReferenceException('Unknown component referenced in pattern schema: ' . $asset_id);
38 | }
39 | }
40 |
41 | /**
42 | * Register an asset and related schema for loading during tests.
43 | *
44 | * @param string $asset_id
45 | * The namespaced asset identifier used when loading a pattern.
46 | * @param string $schema_json
47 | * The schema JSON string to return for this pattern.
48 | */
49 | public function registerPatternSchema(string $asset_id, string $schema_json): void {
50 | $this->patternSchemas[$asset_id] = $schema_json;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------