haveapi 0.32.1 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -8
- data/README.md +3 -422
- data/Rakefile +17 -7
- data/benchmarks/performance_compare.rb +27 -6
- data/doc/cs/client-examples.md +535 -0
- data/doc/cs/create-client.md +118 -0
- data/doc/cs/json-schema.html +1182 -0
- data/doc/cs/protocol.md +595 -0
- data/doc/cs/readme.md +432 -0
- data/doc/cs/typed-input-validation.md +122 -0
- data/doc/{client-examples.md → en/client-examples.md} +20 -10
- data/doc/{documentation-ui.md → en/documentation-ui.md} +42 -6
- data/doc/{performance.md → en/performance.md} +11 -0
- data/doc/{protocol.md → en/protocol.md} +2 -2
- data/doc/en/readme.md +423 -0
- data/haveapi.gemspec +2 -2
- data/lib/haveapi/client_example.rb +18 -5
- data/lib/haveapi/client_examples/curl.rb +5 -5
- data/lib/haveapi/client_examples/dynamic_helpers.rb +10 -10
- data/lib/haveapi/client_examples/go_client.rb +1 -1
- data/lib/haveapi/client_examples/http.rb +4 -4
- data/lib/haveapi/client_examples/js_client.rb +10 -10
- data/lib/haveapi/client_examples/php_client.rb +24 -25
- data/lib/haveapi/client_examples/python_client.rb +11 -11
- data/lib/haveapi/client_examples/ruby_cli.rb +6 -9
- data/lib/haveapi/client_examples/ruby_client.rb +24 -16
- data/lib/haveapi/client_examples/rust_client.rb +1 -1
- data/lib/haveapi/client_examples/ts_generated_client.rb +1 -1
- data/lib/haveapi/documentation_context.rb +14 -2
- data/lib/haveapi/documentation_helpers.rb +92 -15
- data/lib/haveapi/documentation_locale.rb +65 -0
- data/lib/haveapi/documentation_oauth.rb +12 -12
- data/lib/haveapi/documentation_search.rb +5 -5
- data/lib/haveapi/documentation_validators.rb +43 -33
- data/lib/haveapi/locales/cs.yml +468 -0
- data/lib/haveapi/locales/en.yml +467 -0
- data/lib/haveapi/public/css/documentation.css +8 -4
- data/lib/haveapi/public/js/documentation.js +73 -20
- data/lib/haveapi/server.rb +32 -47
- data/lib/haveapi/shared_documentation_helpers.rb +53 -5
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/doc_layout.erb +3 -2
- data/lib/haveapi/views/doc_sidebars/create-client.erb +10 -10
- data/lib/haveapi/views/doc_sidebars/json-schema.erb +2 -2
- data/lib/haveapi/views/doc_sidebars/protocol.erb +24 -24
- data/lib/haveapi/views/documentation/search_form.erb +5 -4
- data/lib/haveapi/views/documentation/search_results.erb +6 -6
- data/lib/haveapi/views/index.erb +3 -3
- data/lib/haveapi/views/main_layout.erb +4 -4
- data/lib/haveapi/views/shared_documentation.erb +8 -5
- data/lib/haveapi/views/shared_sidebar.erb +11 -11
- data/lib/haveapi/views/version_page/auth_body.erb +7 -7
- data/lib/haveapi/views/version_page/client_auth.erb +1 -1
- data/lib/haveapi/views/version_page/client_example.erb +1 -1
- data/lib/haveapi/views/version_page/client_init.erb +1 -1
- data/lib/haveapi/views/version_page/client_overview.erb +7 -5
- data/lib/haveapi/views/version_page/example_panels.erb +5 -2
- data/lib/haveapi/views/version_page/resource_body.erb +24 -24
- data/lib/haveapi/views/version_page/resource_directory.erb +2 -2
- data/lib/haveapi/views/version_page/usage.erb +5 -5
- data/lib/haveapi/views/version_page.erb +11 -11
- data/lib/haveapi/views/version_sidebar/auth_nav.erb +2 -2
- data/lib/haveapi/views/version_sidebar/authentication_control.erb +8 -8
- data/lib/haveapi/views/version_sidebar/authentication_status.erb +1 -1
- data/lib/haveapi/views/version_sidebar/brand.erb +16 -5
- data/lib/haveapi/views/version_sidebar.erb +33 -18
- data/spec/client_examples/assets/documentation_test.cjs +107 -9
- data/spec/client_examples/comment_capture.rb +72 -0
- data/spec/client_examples/comments_spec.rb +162 -0
- data/spec/client_examples/context_spec.rb +18 -2
- data/spec/client_examples/detail_spec.rb +68 -0
- data/spec/documentation/client_overview_spec.rb +60 -0
- data/spec/documentation/example_selection_spec.rb +192 -0
- data/spec/documentation/language_settings_spec.rb +132 -0
- data/spec/documentation/locale_spec.rb +367 -0
- data/spec/documentation/navigation_spec.rb +61 -3
- data/spec/documentation/oauth_spec.rb +42 -0
- data/spec/documentation/search_index_routes_spec.rb +8 -4
- data/spec/documentation/search_spec.rb +1 -1
- data/spec/documentation/shared_pages_spec.rb +63 -2
- data/spec/documentation/validators_spec.rb +25 -0
- data/spec/extensions/exception_mailer_spec.rb +2 -2
- data/spec/packaging_spec.rb +68 -9
- data/spec/performance/event_baseline_spec.rb +1 -1
- data/spec/performance/source_versions_spec.rb +111 -2
- metadata +24 -11
- /data/doc/{create-client.md → en/create-client.md} +0 -0
- /data/doc/{documentation-oauth.md → en/documentation-oauth.md} +0 -0
- /data/doc/{json-schema.html → en/json-schema.html} +0 -0
- /data/doc/{typed-input-validation.md → en/typed-input-validation.md} +0 -0
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
|
|
4
4
|
var storageKey = 'haveapi.docs.preferences.v1';
|
|
5
5
|
|
|
6
|
+
var messages = Object.create(null);
|
|
7
|
+
|
|
8
|
+
function setDocumentationMessages(value) {
|
|
9
|
+
messages = value && typeof value === 'object' && !Array.isArray(value) ? value : Object.create(null);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function documentationMessage(key, values) {
|
|
13
|
+
var text = typeof messages[key] === 'string' ? messages[key] : key;
|
|
14
|
+
return text.replace(/%\{([a-z_]+)\}/g, function (placeholder, name) {
|
|
15
|
+
return values && Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : placeholder;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function documentationLanguageURL(href, locale) {
|
|
20
|
+
if (['en', 'cs'].indexOf(locale) < 0) return null;
|
|
21
|
+
try {
|
|
22
|
+
var destination = new URL(href);
|
|
23
|
+
if (!/^https?:$/.test(destination.protocol)) return null;
|
|
24
|
+
destination.searchParams.set('doc_lang', locale);
|
|
25
|
+
return destination.href;
|
|
26
|
+
} catch (error) { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
function preferences(value) {
|
|
7
30
|
var source = value && typeof value === 'object' ? value : {};
|
|
8
31
|
var clients = Object.create(null);
|
|
@@ -131,8 +154,8 @@
|
|
|
131
154
|
var button = doc.createElement('button');
|
|
132
155
|
button.type = 'button';
|
|
133
156
|
button.setAttribute('data-copy-example', '');
|
|
134
|
-
button.setAttribute('aria-label', '
|
|
135
|
-
button.title = '
|
|
157
|
+
button.setAttribute('aria-label', documentationMessage('copy_example'));
|
|
158
|
+
button.title = documentationMessage('copy_example');
|
|
136
159
|
var icon = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
137
160
|
icon.setAttribute('viewBox', '0 0 24 24');
|
|
138
161
|
icon.setAttribute('aria-hidden', 'true');
|
|
@@ -160,7 +183,7 @@
|
|
|
160
183
|
var current = generation;
|
|
161
184
|
copyText(code.textContent, win, doc, navigator).then(function (success) {
|
|
162
185
|
if (current !== generation || visibleExampleCode(panel) !== code) return;
|
|
163
|
-
status.textContent = success ? '
|
|
186
|
+
status.textContent = success ? documentationMessage('copied') : documentationMessage('copy_failed');
|
|
164
187
|
});
|
|
165
188
|
});
|
|
166
189
|
});
|
|
@@ -324,12 +347,13 @@
|
|
|
324
347
|
if (!/^https?:$/.test(base.protocol) || base.origin !== current.origin || base.username || base.password
|
|
325
348
|
|| base.search || base.hash || !base.pathname.endsWith('/') || url.origin !== base.origin
|
|
326
349
|
|| url.pathname !== base.pathname || url.username || url.password || url.hash
|
|
327
|
-
|| Array.from(url.searchParams).length !==
|
|
350
|
+
|| Array.from(url.searchParams).length !== (url.searchParams.has('doc_lang') ? 3 : 2)
|
|
351
|
+
|| (url.searchParams.has('doc_lang') && (url.searchParams.getAll('doc_lang').length !== 1 || ['en', 'cs'].indexOf(url.searchParams.get('doc_lang')) < 0)) || url.searchParams.getAll('page').length !== 1
|
|
328
352
|
|| url.searchParams.get('page') !== 'resources' || url.searchParams.getAll('search_index').length !== 1
|
|
329
353
|
|| typeof version !== 'string' || !version || (key !== '' && !/^[a-f0-9]{64}$/.test(key))) return null;
|
|
330
354
|
if (url.searchParams.get('search_index') !== (key || 'current')) return null;
|
|
331
355
|
if (bootstrap) { url.searchParams.set('search_index', 'current'); key = ''; }
|
|
332
|
-
return { url: url.href, origin: base.origin, base_path: base.pathname, version: version, key: key };
|
|
356
|
+
return { url: url.href, origin: base.origin, base_path: base.pathname, version: version, key: key, locale: url.searchParams.get('doc_lang') };
|
|
333
357
|
} catch (error) { return null; }
|
|
334
358
|
}
|
|
335
359
|
|
|
@@ -355,6 +379,7 @@
|
|
|
355
379
|
var index = envelope.index;
|
|
356
380
|
shape(index, ['format', 'version', 'base_path', 'locale', 'visibility', 'resources', 'actions', 'parameters']);
|
|
357
381
|
if (index.format !== 1 || index.version !== expected.version || index.base_path !== expected.base_path
|
|
382
|
+
|| (expected.locale && index.locale !== expected.locale)
|
|
358
383
|
|| ['anonymous', 'authenticated'].indexOf(index.visibility) < 0) fail();
|
|
359
384
|
['version', 'base_path', 'locale', 'visibility'].forEach(function (field) { string(index[field]); });
|
|
360
385
|
if (!Array.isArray(index.resources) || !Array.isArray(index.actions) || !Array.isArray(index.parameters)) fail();
|
|
@@ -462,7 +487,7 @@
|
|
|
462
487
|
var link = doc.createElement('a');
|
|
463
488
|
var name = result.kind === 'resource' ? result.resource_path.join('.') : result.name;
|
|
464
489
|
link.href = searchResultURL(index, result, origin);
|
|
465
|
-
link.textContent =
|
|
490
|
+
link.textContent = documentationMessage(result.kind) + ': ';
|
|
466
491
|
var code = doc.createElement('code');
|
|
467
492
|
code.textContent = name;
|
|
468
493
|
link.appendChild(code);
|
|
@@ -473,8 +498,8 @@
|
|
|
473
498
|
} else {
|
|
474
499
|
context.push(result.resource_path.join('.') + (result.action_name !== null ? ' · ' + result.action_name : ''));
|
|
475
500
|
}
|
|
476
|
-
if (result.kind === 'parameter') context.push(
|
|
477
|
-
if (result.authentication !== null) context.push('
|
|
501
|
+
if (result.kind === 'parameter') context.push(documentationMessage(result.direction) + ' · ' + result.type);
|
|
502
|
+
if (result.authentication !== null) context.push(documentationMessage('authentication', { provider: result.authentication }));
|
|
478
503
|
if (result.kind !== 'resource' && typeof result.label === 'string' && result.label !== result.name) context.push(result.label);
|
|
479
504
|
context.forEach(function (text) {
|
|
480
505
|
var detail = doc.createElement('span');
|
|
@@ -523,7 +548,7 @@
|
|
|
523
548
|
|
|
524
549
|
// Pure helpers keep saved-state and legacy-link contracts independently testable.
|
|
525
550
|
if (typeof module !== 'undefined' && module.exports) {
|
|
526
|
-
module.exports = { preferences: preferences, readPreferences: readPreferences, clientChoice: clientChoice,
|
|
551
|
+
module.exports = { setDocumentationMessages: setDocumentationMessages, documentationLanguageURL: documentationLanguageURL, documentationMessage: documentationMessage, preferences: preferences, readPreferences: readPreferences, clientChoice: clientChoice,
|
|
527
552
|
detailLevels: detailLevels, effectiveDetail: effectiveDetail, exampleDetail: exampleDetail, copyText: copyText,
|
|
528
553
|
exampleCopyControls: exampleCopyControls, disclosureMenus: disclosureMenus,
|
|
529
554
|
legacyTarget: legacyTarget, loginTarget: loginTarget,
|
|
@@ -561,12 +586,27 @@
|
|
|
561
586
|
try { return element ? JSON.parse(element.textContent) : fallback; }
|
|
562
587
|
catch (error) { return fallback; }
|
|
563
588
|
};
|
|
589
|
+
setDocumentationMessages(json('[data-documentation-messages]', {}));
|
|
564
590
|
var metadata = json('[data-documentation-clients]', []);
|
|
565
591
|
var clients = Array.isArray(metadata) ? metadata.filter(function (client, index, list) {
|
|
566
592
|
return client && typeof client.id === 'string' && typeof client.language === 'string'
|
|
567
593
|
&& typeof client.language_label === 'string' && client.id && client.language
|
|
568
594
|
&& list.findIndex(function (entry) { return entry && entry.id === client.id; }) === index;
|
|
569
595
|
}) : [];
|
|
596
|
+
all('[data-example-preferences]').forEach(function (form) {
|
|
597
|
+
try {
|
|
598
|
+
var selected = JSON.parse(form.getAttribute('data-example-selection'));
|
|
599
|
+
var client = selected && clients.find(function (entry) { return entry.id === selected.client && entry.language === selected.language; });
|
|
600
|
+
if (client) {
|
|
601
|
+
state.language = client.language;
|
|
602
|
+
state.clients[client.language] = client.id;
|
|
603
|
+
state.detail = selected.detail === 'thorough' ? 'thorough' : 'simple';
|
|
604
|
+
}
|
|
605
|
+
} catch (error) { /* Invalid optional selection leaves saved preferences available. */ }
|
|
606
|
+
form.addEventListener('submit', function (event) { event.preventDefault(); });
|
|
607
|
+
var apply = form.querySelector('[data-apply-examples]');
|
|
608
|
+
if (apply) apply.hidden = true;
|
|
609
|
+
});
|
|
570
610
|
var sidebar = root.querySelector('[data-docs-sidebar]');
|
|
571
611
|
var examples = all('[data-example]');
|
|
572
612
|
var sections = all('[data-doc-section]');
|
|
@@ -685,8 +725,8 @@
|
|
|
685
725
|
all('[data-detail-select]').forEach(function (select) {
|
|
686
726
|
var levels = choice ? detailLevels(choice) : [];
|
|
687
727
|
select.replaceChildren();
|
|
688
|
-
levels.forEach(function (detail) { option(select, detail, detail === 'thorough' ? '
|
|
689
|
-
(select.closest('[data-detail-control]') || select).hidden =
|
|
728
|
+
levels.forEach(function (detail) { option(select, detail, documentationMessage(detail === 'thorough' ? 'thorough' : 'simple')); });
|
|
729
|
+
(select.closest('[data-detail-control]') || select).hidden = levels.length < 2;
|
|
690
730
|
select.disabled = levels.length < 2;
|
|
691
731
|
select.value = effectiveDetail(state, choice);
|
|
692
732
|
});
|
|
@@ -696,8 +736,8 @@
|
|
|
696
736
|
if (status) {
|
|
697
737
|
var fallback = choice && ((state.language && state.language !== choice.language)
|
|
698
738
|
|| (state.clients[choice.language] && state.clients[choice.language] !== choice.id));
|
|
699
|
-
status.textContent = fallback ? '
|
|
700
|
-
+ (choice.variant ? ' (' + choice.variant + ')' : '')
|
|
739
|
+
status.textContent = fallback ? documentationMessage('saved_example_unavailable', { client: choice.language_label
|
|
740
|
+
+ (choice.variant ? ' (' + choice.variant + ')' : '') }) : '';
|
|
701
741
|
status.hidden = !fallback;
|
|
702
742
|
}
|
|
703
743
|
}
|
|
@@ -724,7 +764,7 @@
|
|
|
724
764
|
unavailable = document.createElement('p');
|
|
725
765
|
unavailable.setAttribute('data-example-unavailable', '');
|
|
726
766
|
unavailable.className = 'docs-notice';
|
|
727
|
-
unavailable.textContent = '
|
|
767
|
+
unavailable.textContent = documentationMessage('example_unavailable');
|
|
728
768
|
example.appendChild(unavailable);
|
|
729
769
|
}
|
|
730
770
|
unavailable.hidden = !!chosen;
|
|
@@ -762,6 +802,20 @@
|
|
|
762
802
|
});
|
|
763
803
|
}
|
|
764
804
|
|
|
805
|
+
all('[data-documentation-language]').forEach(function (form) {
|
|
806
|
+
var select = form.querySelector('[name="doc_lang"]');
|
|
807
|
+
if (!select) return;
|
|
808
|
+
function navigate(event) {
|
|
809
|
+
var destination = documentationLanguageURL(window.location.href, select.value);
|
|
810
|
+
if (!destination) return;
|
|
811
|
+
if (event) event.preventDefault();
|
|
812
|
+
window.location.assign(destination);
|
|
813
|
+
}
|
|
814
|
+
form.addEventListener('submit', navigate);
|
|
815
|
+
select.addEventListener('change', navigate);
|
|
816
|
+
var submit = form.querySelector('[data-documentation-language-submit]');
|
|
817
|
+
if (submit) submit.hidden = true;
|
|
818
|
+
});
|
|
765
819
|
all('[data-language-select]').forEach(function (select) {
|
|
766
820
|
select.addEventListener('change', function () { state.language = select.value; persist(); updatePreservingPosition(select); });
|
|
767
821
|
});
|
|
@@ -841,9 +895,8 @@
|
|
|
841
895
|
list.appendChild(renderSearchResult(document, snapshot.index, result, window.location.origin));
|
|
842
896
|
});
|
|
843
897
|
preview.replaceChildren(list);
|
|
844
|
-
searchStatus.textContent = !results.results.length ? '
|
|
845
|
-
: results.
|
|
846
|
-
: results.results.length + (results.results.length === 1 ? ' result.' : ' results.');
|
|
898
|
+
searchStatus.textContent = documentationMessage(!results.results.length ? 'search_empty' : results.more ? 'search_more'
|
|
899
|
+
: results.results.length === 1 ? 'search_one' : 'search_count', { count: results.results.length });
|
|
847
900
|
suggestions.hidden = false;
|
|
848
901
|
}
|
|
849
902
|
function intent() {
|
|
@@ -853,18 +906,18 @@
|
|
|
853
906
|
var config = searchConfiguration(form.action, form.getAttribute('data-search-index-url'),
|
|
854
907
|
form.getAttribute('data-search-view-key'), form.getAttribute('data-search-version'), window.location.href, bootstrapSearch);
|
|
855
908
|
if (!config) {
|
|
856
|
-
if (query) { suggestions.hidden = false; searchStatus.textContent = '
|
|
909
|
+
if (query) { suggestions.hidden = false; searchStatus.textContent = documentationMessage('search_failed'); }
|
|
857
910
|
return;
|
|
858
911
|
}
|
|
859
912
|
var loaded = indexLoader.peek(config);
|
|
860
913
|
if (loaded) { if (query && Array.from(query).length <= 256) render(loaded, query); return; }
|
|
861
|
-
if (query) { suggestions.hidden = false; searchStatus.textContent = '
|
|
914
|
+
if (query) { suggestions.hidden = false; searchStatus.textContent = documentationMessage('search_loading'); }
|
|
862
915
|
indexLoader.load(config).then(function (snapshot) {
|
|
863
916
|
if (current !== generation || input.value.trim() !== query || !query || Array.from(query).length > 256) return;
|
|
864
917
|
render(snapshot, query);
|
|
865
918
|
}).catch(function () {
|
|
866
919
|
if (current === generation && query) {
|
|
867
|
-
searchStatus.textContent = '
|
|
920
|
+
searchStatus.textContent = documentationMessage('search_failed');
|
|
868
921
|
suggestions.hidden = false;
|
|
869
922
|
}
|
|
870
923
|
});
|
data/lib/haveapi/server.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'cgi'
|
|
|
5
5
|
require 'haveapi/hooks'
|
|
6
6
|
require 'haveapi/documentation_clients'
|
|
7
7
|
require 'haveapi/documentation_helpers'
|
|
8
|
+
require 'haveapi/documentation_locale'
|
|
8
9
|
require 'haveapi/documentation_validators'
|
|
9
10
|
require 'haveapi/documentation_search'
|
|
10
11
|
require 'haveapi/shared_documentation_helpers'
|
|
@@ -237,7 +238,9 @@ module HaveAPI
|
|
|
237
238
|
end
|
|
238
239
|
|
|
239
240
|
def doc(file)
|
|
240
|
-
|
|
241
|
+
path = documentation_article_path(file)
|
|
242
|
+
shared_documentation_links(markdown(File.read(path), with_toc_data: true, tables: true,
|
|
243
|
+
fenced_code_blocks: true, lax_spacing: true))
|
|
241
244
|
end
|
|
242
245
|
|
|
243
246
|
def base_url
|
|
@@ -255,7 +258,7 @@ module HaveAPI
|
|
|
255
258
|
@documentation_context ||= HaveAPI::ClientExample::Context.new(
|
|
256
259
|
version: @v, host:, base_url: "#{base_url}#{root.end_with?('/') ? root : "#{root}/"}",
|
|
257
260
|
version_url: "#{base_url}#{root.end_with?('/') ? root : "#{root}/"}v#{@v}/",
|
|
258
|
-
description: @help
|
|
261
|
+
description: @help, locale: documentation_locale
|
|
259
262
|
)
|
|
260
263
|
profile = if client
|
|
261
264
|
settings.api_server.documentation_client_profiles(@v)[client.code&.to_sym]
|
|
@@ -274,7 +277,8 @@ module HaveAPI
|
|
|
274
277
|
end
|
|
275
278
|
|
|
276
279
|
def documentation_clients
|
|
277
|
-
|
|
280
|
+
# The server's @documentation_clients owns profiles, not this request list.
|
|
281
|
+
@documentation_available_clients ||= HaveAPI::ClientExample.clients.select { |client| client.available?(documentation_context(client)) } # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
278
282
|
end
|
|
279
283
|
|
|
280
284
|
def documentation_panels(kind, auth_method: nil, auth_description: nil, **changes)
|
|
@@ -513,6 +517,7 @@ module HaveAPI
|
|
|
513
517
|
helpers ServerHelpers
|
|
514
518
|
helpers DocHelpers
|
|
515
519
|
helpers DocumentationHelpers
|
|
520
|
+
helpers DocumentationLocale
|
|
516
521
|
helpers DocumentationSearch::Helpers
|
|
517
522
|
helpers SharedDocumentationHelpers
|
|
518
523
|
helpers DocumentationOAuth::Helpers
|
|
@@ -561,23 +566,25 @@ module HaveAPI
|
|
|
561
566
|
[version, documentation_authenticated?(version)]
|
|
562
567
|
end
|
|
563
568
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
569
|
+
with_documentation_locale do
|
|
570
|
+
@api = settings.api_server.describe(Context.new(
|
|
571
|
+
settings.api_server,
|
|
572
|
+
user: auth_users_by_version[settings.api_server.default_version],
|
|
573
|
+
params:,
|
|
574
|
+
auth_users_by_version:
|
|
575
|
+
))
|
|
576
|
+
|
|
577
|
+
if single_version
|
|
578
|
+
version = settings.api_server.default_version
|
|
579
|
+
@documentation_index_account = {
|
|
580
|
+
version:, user: auth_users_by_version[version],
|
|
581
|
+
authentication: @api[:versions].fetch(version).fetch(:authentication)
|
|
582
|
+
}
|
|
583
|
+
end
|
|
578
584
|
|
|
579
|
-
|
|
580
|
-
|
|
585
|
+
content_type 'text/html'
|
|
586
|
+
render_documentation_index
|
|
587
|
+
end
|
|
581
588
|
end
|
|
582
589
|
|
|
583
590
|
@sinatra.options @root do
|
|
@@ -621,36 +628,14 @@ module HaveAPI
|
|
|
621
628
|
|
|
622
629
|
# Shared documentation keeps the existing content and URL aliases.
|
|
623
630
|
@sinatra.get "#{@root}doc" do
|
|
624
|
-
|
|
625
|
-
end
|
|
626
|
-
|
|
627
|
-
@sinatra.get "#{@root}doc/readme" do
|
|
628
|
-
render_shared_readme
|
|
629
|
-
end
|
|
630
|
-
|
|
631
|
-
@sinatra.get "#{@root}doc/json-schema" do
|
|
632
|
-
begin
|
|
633
|
-
content = File.read(File.join(settings.root, '../../doc/json-schema.html'))
|
|
634
|
-
rescue Errno::ENOENT
|
|
635
|
-
halt 404
|
|
636
|
-
end
|
|
637
|
-
render_shared_documentation(content:, title: 'JSON schema', sidebar: erb(:'doc_sidebars/json-schema'))
|
|
631
|
+
with_documentation_locale { render_shared_article('readme') }
|
|
638
632
|
end
|
|
639
633
|
|
|
640
|
-
@sinatra.get %r{#{@root}doc/([
|
|
641
|
-
|
|
634
|
+
@sinatra.get %r{#{@root}doc/([a-z0-9-]+)(\.md)?} do |file, extension|
|
|
635
|
+
halt 404 if extension && %w[readme json-schema].include?(file)
|
|
642
636
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
rescue Errno::ENOENT
|
|
646
|
-
halt 404
|
|
647
|
-
end
|
|
648
|
-
sidebar = begin
|
|
649
|
-
erb :"doc_sidebars/#{file}"
|
|
650
|
-
rescue Errno::ENOENT
|
|
651
|
-
nil
|
|
652
|
-
end
|
|
653
|
-
render_shared_documentation(content:, title: file.humanize, sidebar:)
|
|
637
|
+
file = 'readme' if file == 'index'
|
|
638
|
+
with_documentation_locale { render_shared_article(file) }
|
|
654
639
|
end
|
|
655
640
|
|
|
656
641
|
# Retain old documentation links without engaging browser credentials.
|
|
@@ -693,7 +678,7 @@ module HaveAPI
|
|
|
693
678
|
|
|
694
679
|
headers 'Cache-Control' => 'private, no-store'
|
|
695
680
|
query = request.query_string
|
|
696
|
-
halt 400, '
|
|
681
|
+
halt 400, documentation_t('errors.query') if query.bytes.any? { |byte| byte < 32 || byte == 127 }
|
|
697
682
|
destination = url(prefix, false)
|
|
698
683
|
destination += "?#{query}" unless query.empty?
|
|
699
684
|
halt 308, { 'Location' => destination }, ''
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module HaveAPI
|
|
2
2
|
module SharedDocumentationHelpers
|
|
3
3
|
def render_documentation_index
|
|
4
|
-
render_neutral_documentation(content: erb(:index), title: '
|
|
4
|
+
render_neutral_documentation(content: erb(:index), title: documentation_t('labels.api_versions'), page: 'versions')
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def render_shared_documentation(content:, title:, sidebar: nil)
|
|
@@ -9,9 +9,57 @@ module HaveAPI
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def render_shared_readme
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
render_shared_article('readme')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def documentation_article_path(article, extension: 'md')
|
|
16
|
+
halt 404 unless article.match?(/\A[a-z0-9-]+\z/) && %w[md html].include?(extension)
|
|
17
|
+
|
|
18
|
+
directory = File.expand_path('../../doc', __dir__)
|
|
19
|
+
locale = documentation_locale
|
|
20
|
+
path = File.join(directory, locale, "#{article}.#{extension}")
|
|
21
|
+
unless File.file?(path)
|
|
22
|
+
locale = 'en'
|
|
23
|
+
path = File.join(directory, locale, "#{article}.#{extension}")
|
|
24
|
+
end
|
|
25
|
+
halt 404 unless File.file?(path)
|
|
26
|
+
|
|
27
|
+
@documentation_article_locale = locale
|
|
28
|
+
path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def render_shared_article(article)
|
|
32
|
+
@documentation_article_id = article
|
|
33
|
+
content = if article == 'json-schema'
|
|
34
|
+
File.read(documentation_article_path(article, extension: 'html'))
|
|
35
|
+
else
|
|
36
|
+
doc(article)
|
|
37
|
+
end
|
|
38
|
+
title = documentation_t("articles.#{article.tr('-', '_')}", default: article.humanize)
|
|
39
|
+
sidebar = begin
|
|
40
|
+
erb :"doc_sidebars/#{article}"
|
|
41
|
+
rescue Errno::ENOENT
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
render_shared_documentation(content:, title:, sidebar:)
|
|
45
|
+
rescue Errno::ENOENT
|
|
46
|
+
halt 404
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def shared_documentation_links(content)
|
|
50
|
+
content.gsub(/(href|src)="([^"#]*)(#[^"]*)?"/) do |attribute|
|
|
51
|
+
kind = Regexp.last_match(1)
|
|
52
|
+
path = Regexp.last_match(2)
|
|
53
|
+
fragment = Regexp.last_match(3).to_s
|
|
54
|
+
if path.match?(%r{\A(?:(?:\.\./)*(?:en/|cs/)?|/doc/(?:en/|cs/)?)[a-z0-9-]+\.md\z})
|
|
55
|
+
article = File.basename(path, '.md')
|
|
56
|
+
%(#{kind}="#{escape_html(shared_documentation_url("doc/#{article}"))}#{fragment}")
|
|
57
|
+
elsif path.match?(%r{\A(?:\.\./)+[a-z0-9-]+\.png\z})
|
|
58
|
+
%(#{kind}="#{escape_html(shared_documentation_url("doc/#{File.basename(path)}"))}#{fragment}")
|
|
59
|
+
else
|
|
60
|
+
attribute
|
|
61
|
+
end
|
|
62
|
+
end
|
|
15
63
|
end
|
|
16
64
|
|
|
17
65
|
def shared_documentation_url(path = '')
|
|
@@ -47,7 +95,7 @@ module HaveAPI
|
|
|
47
95
|
def shared_documentation_tables(content)
|
|
48
96
|
# Trusted repository HTML is rendered before this presentation-only pass.
|
|
49
97
|
content.gsub(%r{<table\b[^>]*>.*?</table>}m) do |table|
|
|
50
|
-
%(<div class="docs-table-scroll" role="region" aria-label="
|
|
98
|
+
%(<div class="docs-table-scroll" role="region" aria-label="#{escape_html(documentation_t('accessibility.documentation_table'))}" tabindex="0">#{table}</div>)
|
|
51
99
|
end
|
|
52
100
|
end
|
|
53
101
|
end
|
data/lib/haveapi/version.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<% yield %>
|
|
2
2
|
<% if @documentation_ui %>
|
|
3
3
|
<div data-docs data-page="<%= @documentation_page %>" data-resource="<%= escape_html(@documentation_resource && @documentation_resource[:key]) %>">
|
|
4
|
-
<a class="docs-skip-link" href="#docs-main"
|
|
4
|
+
<a class="docs-skip-link" href="#docs-main"><%= escape_html(documentation_t('accessibility.skip')) %></a>
|
|
5
5
|
<div class="docs-mobile-toolbar">
|
|
6
|
-
<button type="button" data-nav-toggle aria-controls="docs-navigation" aria-expanded="false"
|
|
6
|
+
<button type="button" data-nav-toggle aria-controls="docs-navigation" aria-expanded="false"><%= escape_html(documentation_t('accessibility.navigation')) %></button>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="docs-layout">
|
|
9
9
|
<aside class="docs-sidebar" id="docs-navigation" data-docs-sidebar><%= @sidebar %></aside>
|
|
10
10
|
<main class="docs-main" id="docs-main" tabindex="-1"><%= @content %></main>
|
|
11
11
|
</div>
|
|
12
|
+
<script type="application/json" data-documentation-messages><%= documentation_json(documentation_t('browser')) %></script>
|
|
12
13
|
<script type="application/json" data-documentation-clients><%= documentation_json(@help ? documentation_client_metadata : []) %></script>
|
|
13
14
|
<script type="application/json" data-legacy-anchors><%= documentation_json(@documentation_page == 'overview' ? documentation_legacy_anchors : {}) %></script>
|
|
14
15
|
</div>
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<h2
|
|
1
|
+
<h2><%= escape_html(documentation_t('labels.contents')) %></h2>
|
|
2
2
|
<ul class="top-level">
|
|
3
|
-
<li><a href="#client-definition"
|
|
4
|
-
<li><a href="#design-rules"
|
|
3
|
+
<li><a href="#client-definition"><%= escape_html(documentation_t('toc.client_definition')) %></a></li>
|
|
4
|
+
<li><a href="#design-rules"><%= escape_html(documentation_t('toc.design_rules')) %></a></li>
|
|
5
5
|
<li>
|
|
6
|
-
<a href="#necessary-features-to-implement"
|
|
6
|
+
<a href="#necessary-features-to-implement"><%= escape_html(documentation_t('toc.necessary_features_to_implement')) %></a>
|
|
7
7
|
<ul>
|
|
8
|
-
<li><a href="#resource-tree"
|
|
9
|
-
<li><a href="#input-output-parameters"
|
|
10
|
-
<li><a href="#authentication"
|
|
11
|
-
<li><a href="#object-like-access"
|
|
8
|
+
<li><a href="#resource-tree"><%= escape_html(documentation_t('toc.resource_tree')) %></a></li>
|
|
9
|
+
<li><a href="#input-output-parameters"><%= escape_html(documentation_t('toc.input_output_parameters')) %></a></li>
|
|
10
|
+
<li><a href="#authentication"><%= escape_html(documentation_t('labels.authentication')) %></a></li>
|
|
11
|
+
<li><a href="#object-like-access"><%= escape_html(documentation_t('toc.object_like_access')) %></a></li>
|
|
12
12
|
</ul>
|
|
13
13
|
</li>
|
|
14
14
|
<li>
|
|
15
|
-
<a href="#supplemental-features"
|
|
15
|
+
<a href="#supplemental-features"><%= escape_html(documentation_t('toc.supplemental_features')) %></a>
|
|
16
16
|
<ul>
|
|
17
|
-
<li><a href="#client-side-validations"
|
|
17
|
+
<li><a href="#client-side-validations"><%= escape_html(documentation_t('toc.client_side_validations')) %></a></li>
|
|
18
18
|
</ul>
|
|
19
19
|
</li>
|
|
20
20
|
</ul>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<h2
|
|
1
|
+
<h2><%= escape_html(documentation_t('labels.contents')) %></h2>
|
|
2
2
|
<ul class="top-level">
|
|
3
|
-
<li><a href="#envelope"
|
|
3
|
+
<li><a href="#envelope"><%= escape_html(documentation_t('schema.envelope')) %></a></li>
|
|
4
4
|
<li><a href="#all">OPTIONS /</a></li>
|
|
5
5
|
<li><a href="#versions">OPTIONS /?describe=versions</a></li>
|
|
6
6
|
<li><a href="#default">OPTIONS /?describe=default</a></li>
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
<h2
|
|
1
|
+
<h2><%= escape_html(documentation_t('labels.contents')) %></h2>
|
|
2
2
|
<ul class="top-level">
|
|
3
|
-
<li><a href="#protocol-definition"
|
|
4
|
-
<li><a href="#self-description"
|
|
5
|
-
<li><a href="#envelope"
|
|
3
|
+
<li><a href="#protocol-definition"><%= escape_html(documentation_t('toc.protocol_definition')) %></a></li>
|
|
4
|
+
<li><a href="#self-description"><%= escape_html(documentation_t('toc.self_description')) %></a></li>
|
|
5
|
+
<li><a href="#envelope"><%= escape_html(documentation_t('schema.envelope')) %></a></li>
|
|
6
6
|
<li>
|
|
7
|
-
<a href="#description-format"
|
|
7
|
+
<a href="#description-format"><%= escape_html(documentation_t('toc.description_format')) %></a>
|
|
8
8
|
<ul>
|
|
9
|
-
<li><a href="#api-version"
|
|
9
|
+
<li><a href="#api-version"><%= escape_html(documentation_t('toc.version')) %></a></li>
|
|
10
10
|
<li>
|
|
11
|
-
<a href="#authentication"
|
|
11
|
+
<a href="#authentication"><%= escape_html(documentation_t('labels.authentication')) %></a>
|
|
12
12
|
<ul>
|
|
13
|
-
<li><a href="#http-basic-authentication"
|
|
14
|
-
<li><a href="#token-authentication"
|
|
13
|
+
<li><a href="#http-basic-authentication"><%= escape_html(documentation_t('toc.http_basic_authentication')) %></a></li>
|
|
14
|
+
<li><a href="#token-authentication"><%= escape_html(documentation_t('toc.token_authentication')) %></a></li>
|
|
15
15
|
</ul>
|
|
16
16
|
</li>
|
|
17
|
-
<li><a href="#resources"
|
|
17
|
+
<li><a href="#resources"><%= escape_html(documentation_t('labels.resources')) %></a></li>
|
|
18
18
|
<li>
|
|
19
|
-
<a href="#actions"
|
|
19
|
+
<a href="#actions"><%= escape_html(documentation_t('labels.actions')) %></a>
|
|
20
20
|
<ul>
|
|
21
|
-
<li><a href="#layouts"
|
|
22
|
-
<li><a href="#namespace"
|
|
21
|
+
<li><a href="#layouts"><%= escape_html(documentation_t('toc.layouts')) %></a></li>
|
|
22
|
+
<li><a href="#namespace"><%= escape_html(documentation_t('labels.namespace')) %></a></li>
|
|
23
23
|
</ul>
|
|
24
24
|
</li>
|
|
25
25
|
<li>
|
|
26
|
-
<a href="#parameters"
|
|
26
|
+
<a href="#parameters"><%= escape_html(documentation_t('toc.parameters')) %></a>
|
|
27
27
|
<ul>
|
|
28
|
-
<li><a href="#data-types"
|
|
29
|
-
<li><a href="#resource-association"
|
|
28
|
+
<li><a href="#data-types"><%= escape_html(documentation_t('toc.data_types')) %></a></li>
|
|
29
|
+
<li><a href="#resource-association"><%= escape_html(documentation_t('toc.resource_associations')) %></a></li>
|
|
30
30
|
</ul>
|
|
31
31
|
</li>
|
|
32
|
-
<li><a href="#examples"
|
|
33
|
-
<li><a href="#list-api-versions"
|
|
34
|
-
<li><a href="#describe-default-version"
|
|
35
|
-
<li><a href="#describe-the-whole-api"
|
|
32
|
+
<li><a href="#examples"><%= escape_html(documentation_t('labels.examples')) %></a></li>
|
|
33
|
+
<li><a href="#list-api-versions"><%= escape_html(documentation_t('toc.list_api_versions')) %></a></li>
|
|
34
|
+
<li><a href="#describe-default-version"><%= escape_html(documentation_t('toc.describe_default_version')) %></a></li>
|
|
35
|
+
<li><a href="#describe-the-whole-api"><%= escape_html(documentation_t('toc.describe_the_whole_api')) %></a></li>
|
|
36
36
|
</ul>
|
|
37
37
|
</li>
|
|
38
|
-
<li><a href="#authorization"
|
|
39
|
-
<li><a href="#input-output-formats"
|
|
40
|
-
<li><a href="#request"
|
|
41
|
-
<li><a href="#response"
|
|
38
|
+
<li><a href="#authorization"><%= escape_html(documentation_t('toc.authorization')) %></a></li>
|
|
39
|
+
<li><a href="#input-output-formats"><%= escape_html(documentation_t('toc.input_output_formats')) %></a></li>
|
|
40
|
+
<li><a href="#request"><%= escape_html(documentation_t('toc.request')) %></a></li>
|
|
41
|
+
<li><a href="#response"><%= escape_html(documentation_t('toc.response')) %></a></li>
|
|
42
42
|
</ul>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<form data-documentation-search method="get" action="<%= escape_html(action) %>" data-search-version="<%= escape_html(version) %>" data-search-index-url="<%= escape_html(index_url) %>" data-search-view-key="<%= escape_html(view_key) %>">
|
|
2
2
|
<input type="hidden" name="page" value="resources">
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
<
|
|
3
|
+
<input type="hidden" name="doc_lang" value="<%= escape_html(documentation_locale) %>">
|
|
4
|
+
<label for="<%= escape_html(id) %>-query"><%= escape_html(documentation_t('search.label', version: version)) %></label>
|
|
5
|
+
<input type="search" name="q" data-search-query id="<%= escape_html(id) %>-query" value="<%= escape_html(query) %>" placeholder="<%= escape_html(documentation_t('search.placeholder')) %>" maxlength="256">
|
|
6
|
+
<button type="submit"><%= escape_html(documentation_t('search.submit')) %></button>
|
|
7
|
+
<div data-search-suggestions role="region" aria-label="<%= escape_html(documentation_t('search.suggestions')) %>" hidden>
|
|
7
8
|
<p data-search-status role="status"></p>
|
|
8
9
|
<div data-search-preview></div>
|
|
9
10
|
</div>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<section data-search-results aria-label="
|
|
2
|
-
<h2
|
|
1
|
+
<section data-search-results aria-label="<%= escape_html(documentation_t('search.results')) %>">
|
|
2
|
+
<h2><%= escape_html(documentation_t('search.results_for', query: search[:query])) %></h2>
|
|
3
3
|
<p data-search-status role="status">
|
|
4
4
|
<% if search[:results].empty? %>
|
|
5
|
-
|
|
5
|
+
<%= escape_html(documentation_t('browser.search_empty')) %>
|
|
6
6
|
<% elsif search[:more] %>
|
|
7
|
-
|
|
7
|
+
<%= escape_html(documentation_t('browser.search_more', count: search[:results].length)) %>
|
|
8
8
|
<% else %>
|
|
9
|
-
<%= search[:results].length
|
|
9
|
+
<%= escape_html(documentation_t(search[:results].length == 1 ? 'browser.search_one' : 'browser.search_count', count: search[:results].length)) %>
|
|
10
10
|
<% end %>
|
|
11
11
|
</p>
|
|
12
12
|
<ol>
|
|
13
13
|
<% search[:results].each do |result| %>
|
|
14
14
|
<li>
|
|
15
|
-
<a href="<%= escape_html(documentation_search_result_url(result)) %>"><%= escape_html(result[:kind]
|
|
15
|
+
<a href="<%= escape_html(documentation_search_result_url(result)) %>"><%= escape_html(documentation_t("browser.#{result[:kind]}")) %>: <code><%= escape_html(documentation_search_result_title(result)) %></code></a>
|
|
16
16
|
<% documentation_search_result_context(result).each do |context| %><span><%= escape_html(context) %></span><% end %>
|
|
17
17
|
</li>
|
|
18
18
|
<% end %>
|
data/lib/haveapi/views/index.erb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<h1
|
|
1
|
+
<h1><%= escape_html(documentation_t('labels.api_versions')) %></h1>
|
|
2
2
|
<ul>
|
|
3
3
|
<% @api[:versions].each do |v, info| %>
|
|
4
4
|
<% next if v == :default %>
|
|
5
5
|
<li>
|
|
6
|
-
<a href="<%= escape_html(url(info[:help], false)) %>"
|
|
7
|
-
<%= '
|
|
6
|
+
<a href="<%= escape_html(url(info[:help], false)) %>"><%= escape_html(documentation_t('version.label', version: v)) %></a>
|
|
7
|
+
<%= escape_html(documentation_t('version.default')) if v == @api[:default_version] %>
|
|
8
8
|
</li>
|
|
9
9
|
<% end %>
|
|
10
10
|
</ul>
|