Skip to content

Commit 0e83aa5

Browse files
committed
Adds API endpoint for $ref support
feat(PathProcessorPatterns)
1 parent 11a2edf commit 0e83aa5

13 files changed

+249
-57
lines changed

js/patternkit.jsoneditor.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @file
3+
* Provides Twig Pattern Library Editing Functionality.
4+
*
5+
* @external Drupal
6+
*
7+
* @external jQuery
8+
*
9+
* @external JSONEditor
10+
*/
11+
12+
(function ($, Drupal) {
13+
Drupal.behaviors.patternkitEditor = {
14+
attach: function (context, settings) {
15+
var $target = $('#editor-shadow-injection-target', context);
16+
$target.once('patternkit-editor').each(function () {
17+
var shadow = this.attachShadow({mode: 'open'});
18+
shadow.innerHTML = '<link rel="stylesheet" id="theme_stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"><link rel="stylesheet" id="icon_stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.3/css/font-awesome.css"><div id="editor_holder"></div>';
19+
20+
var data = {};
21+
data.schema = JSON.parse(drupalSettings.patternkitEditor.schemaJson);
22+
data.starting = JSON.parse(drupalSettings.patternkitEditor.startingJson);
23+
24+
if (data.starting !== null) {
25+
JSONEditor.defaults.options.startval = data.starting;
26+
}
27+
28+
// Override how references are resolved.
29+
JSONEditor.prototype._loadExternalRefs = function (schema, callback) {
30+
var self = this;
31+
var refs = this._getExternalRefs(schema);
32+
33+
var done = 0, waiting = 0, callback_fired = false;
34+
35+
$.each(refs, function (url) {
36+
if (self.refs[url]) {
37+
return;
38+
}
39+
if (!self.options.ajax) {
40+
throw "Must set ajax option to true to load external ref " + url;
41+
}
42+
self.refs[url] = 'loading';
43+
waiting++;
44+
45+
var r = new XMLHttpRequest();
46+
var uri = drupalSettings.path.baseUrl + url + '/schema';
47+
48+
r.open("GET", uri, true);
49+
r.onreadystatechange = function () {
50+
if (r.readyState !== 4) {
51+
return;
52+
}
53+
// Request succeeded.
54+
if (r.status === 200) {
55+
var response;
56+
try {
57+
response = JSON.parse(r.responseText);
58+
}
59+
catch (e) {
60+
window.console.log(e);
61+
throw "Failed to parse external ref " + url;
62+
}
63+
if (!response || typeof response !== "object") {
64+
throw "External ref does not contain a valid schema - " + url;
65+
}
66+
67+
self.refs[url] = response;
68+
self._loadExternalRefs(response,function () {
69+
done++;
70+
if (done >= waiting && !callback_fired) {
71+
callback_fired = true;
72+
callback();
73+
}
74+
});
75+
}
76+
// Request failed.
77+
else {
78+
window.console.log(r);
79+
throw "Failed to fetch ref via ajax- " + url;
80+
}
81+
};
82+
r.send();
83+
});
84+
85+
if (!waiting) {
86+
callback();
87+
}
88+
};
89+
90+
// Initialize the editor with a JSON schema.
91+
var editor = new JSONEditor(
92+
$target[0].shadowRoot.getElementById('editor_holder'), {
93+
schema: data.schema,
94+
theme: 'bootstrap3',
95+
iconlib: 'fontawesome4',
96+
keep_oneof_values: false,
97+
disable_edit_json: true,
98+
disable_collapse: true,
99+
ajax: true,
100+
refs: { }
101+
}
102+
);
103+
JSONEditor.plugins.sceditor.emoticonsEnabled = false;
104+
105+
editor.on('change', function () {
106+
document.getElementById('schema_instance_config').value = JSON.stringify(editor.getValue());
107+
108+
});
109+
});
110+
}
111+
};
112+
})(jQuery, Drupal);

patternkit.libraries.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ patternkit.editor:
22
version: VERSION
33
js:
44
js/jsoneditor.min.js: {minified: true}
5-
js/twigeditor.js: {}
5+
js/patternkit.jsoneditor.js: {}
66
dependencies:
77
- core/drupalSettings
88
- core/jquery

patternkit.routing.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ patternkit.add_page:
2323
_admin_route: TRUE
2424
requirements:
2525
_permission: 'administer blocks'
26-
patternkit.api.schema:
27-
path: 'api/patternkit/{library}/{pattern}/schema'
26+
patternkit.api:
27+
path: '/api/patternkit'
2828
defaults:
29-
_controller: '\Drupal\patternkit\Controller\PatternkitController::apiPatternSchema'
29+
_controller: '\Drupal\patternkit\Controller\PatternkitController::apiPattern'
30+
requirements:
31+
_access: 'TRUE'
3032
patternkit.settings:
31-
path: 'admin/config/user-interface/patternkit'
33+
path: '/admin/config/user-interface/patternkit'
3234
defaults:
3335
_form: '\Drupal\patternkit\Form\PatternkitSettingsForm'
3436
_title: 'Patternkit settings'

patternkit.services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ services:
22
logger.channel.patternkit:
33
parent: logger.channel_base
44
arguments: ['patternkit']
5+
path_processor.patterns:
6+
class: Drupal\patternkit\PathProcessor\PathProcessorPatterns
7+
tags:
8+
- { name: path_processor_inbound, priority: 200 }
59
patternkit.library.discovery:
610
class: Drupal\patternkit\PatternkitLibraryDiscovery
711
arguments: ['@patternkit.library.discovery.collector']

src/Controller/PatternkitController.php

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
use Drupal\Core\Url;
1111
use Drupal\patternkit\Pattern;
1212
use Exception;
13+
use RuntimeException;
14+
use function str_replace;
1315
use Symfony\Component\DependencyInjection\ContainerInterface;
1416
use Symfony\Component\HttpFoundation\JsonResponse;
1517
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpFoundation\Response;
1619

1720
/**
1821
* Controller routines for block example routes.
@@ -153,17 +156,53 @@ public function addForm($pattern_id, Request $request): array {
153156
/**
154157
* Returns the JSON-encoded Patternkit schema for the provided pattern.
155158
*
156-
* @param string $library
157-
* The name of the pattern library to search.
159+
* @param \Symfony\Component\HttpFoundation\Request $request
160+
* The request object.
161+
* @param string $pattern
162+
* The name of the pattern to use for retrieving the schema.
163+
*
164+
* @return \Symfony\Component\HttpFoundation\Response
165+
* The schema response.
166+
*/
167+
public function apiPattern(Request $request, $pattern = NULL): Response {
168+
$pattern = $request->query->get('pattern') ?? $pattern;
169+
if (!$pattern) {
170+
return new Response();
171+
}
172+
$test_len = strlen('/schema');
173+
if (substr_compare($pattern, '/schema', strlen($pattern) - $test_len, $test_len) === 0) {
174+
return $this->apiPatternSchema(substr($pattern, 0, -$test_len));
175+
}
176+
$asset_id = str_replace('/', '.', $pattern);
177+
try {
178+
$response = $this->libraryDiscovery->getLibraryAsset($asset_id);
179+
if ($response === NULL) {
180+
throw new RuntimeException("Unable to locate $pattern.");
181+
}
182+
}
183+
catch (Exception $exception) {
184+
$response = ['error' => $exception->getMessage()];
185+
}
186+
return new JsonResponse($response);
187+
}
188+
189+
/**
190+
* Returns the JSON-encoded Patternkit schema for the provided pattern.
191+
*
158192
* @param string $pattern
159193
* The name of the pattern to use for retrieving the schema.
160194
*
161195
* @return \Symfony\Component\HttpFoundation\JsonResponse
162196
* The schema response.
163197
*/
164-
public function apiPatternSchema($library, $pattern): JsonResponse {
198+
public function apiPatternSchema($pattern): JsonResponse {
199+
$asset_id = str_replace('/', '.', $pattern);
165200
try {
166-
$response = $this->libraryDiscovery->getLibraryAsset("$library.$pattern");
201+
$pattern_asset = $this->libraryDiscovery->getLibraryAsset($asset_id);
202+
if ($pattern_asset === NULL) {
203+
throw new RuntimeException("Unable to locate $pattern.");
204+
}
205+
$response = $pattern_asset->schema ?? [];
167206
}
168207
catch (Exception $exception) {
169208
$response = ['error' => $exception->getMessage()];
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Drupal\patternkit\PathProcessor;
4+
5+
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
6+
use Symfony\Component\HttpFoundation\Request;
7+
8+
/**
9+
* Defines a path processor to rewrite pattern URLs.
10+
*
11+
* As the route system does not allow arbitrary amount of parameters convert
12+
* the pattern path to a query parameter on the request.
13+
*/
14+
class PathProcessorPatterns implements InboundPathProcessorInterface {
15+
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
public function processInbound($path, Request $request): string {
20+
if (strpos($path, '/api/patternkit') === 0
21+
&& !$request->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+
}

src/Pattern.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
namespace Drupal\patternkit;
44

5-
use function is_string;
6-
use function PHPSTORM_META\type;
7-
85
/**
96
* A collection of a JSON schema and renderable markup.
107
*
@@ -59,7 +56,9 @@ class Pattern {
5956
/**
6057
* The configuration object containing token data.
6158
*
62-
* @var object
59+
* This only exists once a pattern has been configured.
60+
*
61+
* @var object|array
6362
*/
6463
public $config;
6564

@@ -78,7 +77,7 @@ class Pattern {
7877
public $body;
7978

8079
/**
81-
* The pattern template file.
80+
* The filesystem pattern template file path relative to the library.
8281
*
8382
* @var string
8483
*/
@@ -91,10 +90,19 @@ class Pattern {
9190
*/
9291
public $html;
9392

93+
/**
94+
* The library path to the pattern root directory.
95+
*
96+
* @var string
97+
*
98+
* @code 'atoms/example/src/' @endcode
99+
*/
100+
public $path;
101+
94102
/**
95103
* The JSON Schema for the pattern.
96104
*
97-
* @var object
105+
* @var object|array
98106
*/
99107
public $schema;
100108

@@ -117,6 +125,8 @@ class Pattern {
117125
/**
118126
* The API URL for the pattern.
119127
*
128+
* In Twig libraries this is often the namespace.
129+
*
120130
* @var string
121131
*/
122132
public $url;

src/PatternLibraryCollector.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Drupal\Core\Serialization\Yaml;
1616
use Drupal\Core\Theme\ThemeManagerInterface;
1717
use Drupal\patternkit\Form\PatternkitSettingsForm;
18+
use function str_replace;
1819
use Symfony\Component\DependencyInjection\ContainerInterface;
1920
use Drupal\Core\Cache\CacheCollector;
2021
use Drupal\Core\Lock\LockBackendInterface;
@@ -442,13 +443,13 @@ protected function getLibraryMetadata(): array {
442443
$plugin_id = $info['plugin'] ?? 'twig';
443444
/** @var \Drupal\patternkit\PatternLibraryPluginInterface $plugin */
444445
$plugin = $this->libraryPluginManager->createInstance($plugin_id);
446+
$metadata[$library_name]['name'] = $library_name;
445447
$metadata[$library_name] += $info;
446448
/** @var \Drupal\patternkit\Pattern $pattern */
447-
foreach ($plugin->getMetadata($extension, $library_name, $info['data']) as $pattern) {
448-
$pattern_id = $pattern->getId();
449-
$category = $pattern->category;
449+
foreach ($plugin->getMetadata($extension, $metadata[$library_name], $info['data']) as $pattern_path => $pattern) {
450450
$pattern->setLibraryPluginId($plugin_id);
451-
$metadata[$library_name]['patterns']["$library_name.$category.$pattern_id"] = $pattern;
451+
$cache_id = str_replace('/', '.', trim($pattern_path, '@'));
452+
$metadata[$library_name]['patterns'][$cache_id] = $pattern;
452453
}
453454
}
454455
}

src/PatternLibraryJSONParserTrait.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,11 @@ public static function schemaDereference($properties, array &$metadata) {
4040
if ($property !== '$ref') {
4141
continue;
4242
}
43-
$pattern = strstr($value, '.json', TRUE);
44-
$ref = substr($value, strlen("$pattern.json"));
45-
if (!isset($metadata[$pattern])) {
43+
if (!isset($metadata[$value])) {
4644
unset($properties[$property]);
4745
continue;
4846
}
49-
$properties[$property] = $metadata[$pattern]->url . $ref;
47+
$properties[$property] = 'api/patternkit/' . trim($value, '@');
5048
}
5149
return $properties;
5250
}

0 commit comments

Comments
 (0)