serverest 3.1.0 → 3.2.0
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.
- package/docs/flags/flag_peru.svg +10 -0
- package/docs/images/serverest_logo.svg +116 -0
- package/package.json +4 -1
- package/src/app.js +1 -1
- package/src/controllers/produtos-controller.js +63 -65
- package/src/controllers/usuarios-controller.js +3 -4
- package/src/data/carrinhos.db +8 -0
- package/src/data/produtos.db +6 -0
- package/src/data/usuarios.db +2 -0
- package/src/middlewares/error-handler.js +6 -0
- package/src/services/carrinhos-service.js +30 -22
- package/src/services/produtos-service.js +84 -0
- package/src/services/usuarios-service.js +6 -2
- package/src/swagger/index.js +62 -0
- package/src/swagger/scripts/01-config.js +10 -0
- package/src/swagger/scripts/02-language-storage.js +55 -0
- package/src/swagger/scripts/03-release-toast.js +158 -0
- package/src/swagger/scripts/04-ui-translations.js +92 -0
- package/src/swagger/scripts/05-swagger-spec.js +110 -0
- package/src/swagger/scripts/06-language-switcher.js +68 -0
- package/src/swagger/scripts/07-doc-lifecycle.js +163 -0
- package/src/swagger/styles/language-switcher.css +64 -0
- package/src/swagger/{custom-css.css → styles/release-toast.css} +1 -60
- package/src/swagger/styles/topbar.css +56 -0
- package/src/swagger/swagger-sw.js +2 -2
- package/src/swagger/translations/en.js +23 -0
- package/src/swagger/translations/es.js +23 -0
- package/src/swagger/translations/pt-BR.js +23 -0
- package/docs/flags/flag_spain.svg +0 -2552
- package/docs/images/serverest_logo.png +0 -0
- package/src/swagger/custom-js.js +0 -647
- package/src/swagger/customization.js +0 -25
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const flagUrls = {
|
|
2
|
+
'pt-BR': '/flags/flag_brazil.svg',
|
|
3
|
+
en: '/flags/flag_uk.svg',
|
|
4
|
+
es: '/flags/flag_peru.svg'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const supportedLanguages = [
|
|
8
|
+
{ code: 'pt-BR', label: 'Português do Brasil' },
|
|
9
|
+
{ code: 'es', label: 'Español' },
|
|
10
|
+
{ code: 'en', label: 'English' }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
function getPreferredLanguage () {
|
|
14
|
+
return window.localStorage.getItem('swaggerLanguage') || 'pt-BR'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setPreferredLanguage (code) {
|
|
18
|
+
window.localStorage.setItem('swaggerLanguage', code)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getLanguageFromQuery () {
|
|
22
|
+
const url = new URL(window.location.href)
|
|
23
|
+
return url.searchParams.get('lang') || ''
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getCurrentLanguageCode () {
|
|
27
|
+
const queryLang = getLanguageFromQuery()
|
|
28
|
+
const preferred = getPreferredLanguage()
|
|
29
|
+
return supportedLanguages.some(l => l.code === queryLang)
|
|
30
|
+
? queryLang
|
|
31
|
+
: (supportedLanguages.some(l => l.code === preferred) ? preferred : 'pt-BR')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function preloadFlags () {
|
|
35
|
+
if (typeof document === 'undefined' || !document.head) return
|
|
36
|
+
Object.values(flagUrls).forEach(href => {
|
|
37
|
+
const link = document.createElement('link')
|
|
38
|
+
link.rel = 'preload'
|
|
39
|
+
link.as = 'image'
|
|
40
|
+
link.href = href
|
|
41
|
+
document.head.appendChild(link)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function preloadSwaggerJson () {
|
|
46
|
+
if (typeof document === 'undefined' || !document.head) return
|
|
47
|
+
const code = getCurrentLanguageCode()
|
|
48
|
+
const href = '/swagger.json?lang=' + encodeURIComponent(code)
|
|
49
|
+
const link = document.createElement('link')
|
|
50
|
+
link.rel = 'preload'
|
|
51
|
+
link.as = 'fetch'
|
|
52
|
+
link.href = href
|
|
53
|
+
link.crossOrigin = ''
|
|
54
|
+
document.head.appendChild(link)
|
|
55
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const releaseToastTranslations = {
|
|
2
|
+
'pt-BR': {
|
|
3
|
+
title: 'Nova release disponível',
|
|
4
|
+
link: 'Ver release',
|
|
5
|
+
closeLabel: 'Fechar aviso de release',
|
|
6
|
+
meta: (latest, current) => 'v' + latest + ' (v' + current + ' instalada)'
|
|
7
|
+
},
|
|
8
|
+
en: {
|
|
9
|
+
title: 'New release available',
|
|
10
|
+
link: 'View release',
|
|
11
|
+
closeLabel: 'Close release notice',
|
|
12
|
+
meta: (latest, current) => 'v' + latest + ' (v' + current + ' installed)'
|
|
13
|
+
},
|
|
14
|
+
es: {
|
|
15
|
+
title: 'Nueva versión disponible',
|
|
16
|
+
link: 'Ver versión',
|
|
17
|
+
closeLabel: 'Cerrar aviso de versión',
|
|
18
|
+
meta: (latest, current) => 'v' + latest + ' (v' + current + ' instalada)'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getReleaseToastTranslation (language) {
|
|
23
|
+
return releaseToastTranslations[language] || releaseToastTranslations['pt-BR']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderReleaseToast (releaseDataOverride) {
|
|
27
|
+
const info = releaseDataOverride !== undefined ? releaseDataOverride : releaseInfo
|
|
28
|
+
const current = normalizeVersion(currentVersion)
|
|
29
|
+
let latestVersion = normalizeVersion(info && (info.version || info.tag))
|
|
30
|
+
if (forceBanner) {
|
|
31
|
+
latestVersion = normalizeVersion('999.999.999')
|
|
32
|
+
}
|
|
33
|
+
if (!latestVersion || (!current && !forceBanner)) return
|
|
34
|
+
if (latestVersion === current && !forceBanner) return
|
|
35
|
+
if (document.querySelector('.release-toast')) return
|
|
36
|
+
const root = document.querySelector('.swagger-ui') || document.body
|
|
37
|
+
|
|
38
|
+
const toast = document.createElement('div')
|
|
39
|
+
toast.className = 'release-toast'
|
|
40
|
+
toast.setAttribute('aria-live', 'polite')
|
|
41
|
+
toast.dataset.latestVersion = latestVersion
|
|
42
|
+
toast.dataset.currentVersion = current
|
|
43
|
+
|
|
44
|
+
const content = document.createElement('div')
|
|
45
|
+
content.className = 'release-toast__content'
|
|
46
|
+
|
|
47
|
+
const title = document.createElement('div')
|
|
48
|
+
title.className = 'release-toast__title'
|
|
49
|
+
const titleIcon = document.createElement('span')
|
|
50
|
+
titleIcon.className = 'release-toast__title-icon'
|
|
51
|
+
titleIcon.textContent = '⚡'
|
|
52
|
+
const translation = getReleaseToastTranslation(getPreferredLanguage())
|
|
53
|
+
const titleText = document.createElement('span')
|
|
54
|
+
titleText.textContent = translation.title
|
|
55
|
+
title.appendChild(titleIcon)
|
|
56
|
+
title.appendChild(titleText)
|
|
57
|
+
|
|
58
|
+
const meta = document.createElement('div')
|
|
59
|
+
meta.className = 'release-toast__meta'
|
|
60
|
+
meta.textContent = translation.meta(latestVersion, current)
|
|
61
|
+
|
|
62
|
+
const link = document.createElement('a')
|
|
63
|
+
link.className = 'release-toast__link'
|
|
64
|
+
link.href = (info && info.url) ? info.url : 'https://github.com/ServeRest/ServeRest/releases'
|
|
65
|
+
link.textContent = translation.link
|
|
66
|
+
link.target = '_blank'
|
|
67
|
+
link.rel = 'noopener noreferrer'
|
|
68
|
+
|
|
69
|
+
const close = document.createElement('button')
|
|
70
|
+
close.type = 'button'
|
|
71
|
+
close.className = 'release-toast__close'
|
|
72
|
+
close.setAttribute('aria-label', translation.closeLabel)
|
|
73
|
+
close.textContent = '×'
|
|
74
|
+
close.addEventListener('click', function () {
|
|
75
|
+
toast.remove()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
content.appendChild(title)
|
|
79
|
+
content.appendChild(meta)
|
|
80
|
+
content.appendChild(link)
|
|
81
|
+
toast.appendChild(content)
|
|
82
|
+
toast.appendChild(close)
|
|
83
|
+
root.appendChild(toast)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function updateReleaseToastLanguage (language) {
|
|
87
|
+
const toast = document.querySelector('.release-toast')
|
|
88
|
+
if (!toast) return
|
|
89
|
+
const translation = getReleaseToastTranslation(language)
|
|
90
|
+
const latestVersion = toast.dataset.latestVersion || ''
|
|
91
|
+
const current = toast.dataset.currentVersion || ''
|
|
92
|
+
const title = toast.querySelector('.release-toast__title span:last-child')
|
|
93
|
+
const meta = toast.querySelector('.release-toast__meta')
|
|
94
|
+
const link = toast.querySelector('.release-toast__link')
|
|
95
|
+
const close = toast.querySelector('.release-toast__close')
|
|
96
|
+
if (title) title.textContent = translation.title
|
|
97
|
+
if (meta) meta.textContent = translation.meta(latestVersion, current)
|
|
98
|
+
if (link) link.textContent = translation.link
|
|
99
|
+
if (close) close.setAttribute('aria-label', translation.closeLabel)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const RELEASE_CACHE_KEY = 'serverest_github_release'
|
|
103
|
+
const RELEASE_CACHE_TTL_MS = 10 * 60 * 1000
|
|
104
|
+
|
|
105
|
+
function getCachedRelease (requireFresh) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = window.localStorage.getItem(RELEASE_CACHE_KEY)
|
|
108
|
+
if (!raw) return null
|
|
109
|
+
const parsed = JSON.parse(raw)
|
|
110
|
+
if (!parsed || !parsed.data) return null
|
|
111
|
+
if (requireFresh && (!parsed.cachedAt || (Date.now() - parsed.cachedAt) > RELEASE_CACHE_TTL_MS)) return null
|
|
112
|
+
return parsed.data
|
|
113
|
+
} catch (_) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runReleaseCheck () {
|
|
119
|
+
renderReleaseToast()
|
|
120
|
+
const cached = getCachedRelease(true)
|
|
121
|
+
if (cached && cached.tag_name && cached.html_url) {
|
|
122
|
+
renderReleaseToast({
|
|
123
|
+
tag: cached.tag_name,
|
|
124
|
+
version: normalizeVersion(cached.tag_name),
|
|
125
|
+
url: cached.html_url
|
|
126
|
+
})
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
fetch('https://api.github.com/repos/ServeRest/ServeRest/releases/latest', {
|
|
130
|
+
headers: { Accept: 'application/json' }
|
|
131
|
+
})
|
|
132
|
+
.then(r => r.json())
|
|
133
|
+
.then(data => {
|
|
134
|
+
if (data.tag_name && data.html_url) {
|
|
135
|
+
try {
|
|
136
|
+
window.localStorage.setItem(RELEASE_CACHE_KEY, JSON.stringify({
|
|
137
|
+
data: { tag_name: data.tag_name, html_url: data.html_url },
|
|
138
|
+
cachedAt: Date.now()
|
|
139
|
+
}))
|
|
140
|
+
} catch (_) {}
|
|
141
|
+
renderReleaseToast({
|
|
142
|
+
tag: data.tag_name,
|
|
143
|
+
version: normalizeVersion(data.tag_name),
|
|
144
|
+
url: data.html_url
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
.catch(() => {
|
|
149
|
+
const stale = getCachedRelease(false)
|
|
150
|
+
if (stale && stale.tag_name && stale.html_url) {
|
|
151
|
+
renderReleaseToast({
|
|
152
|
+
tag: stale.tag_name,
|
|
153
|
+
version: normalizeVersion(stale.tag_name),
|
|
154
|
+
url: stale.html_url
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* uiLabelTranslations injetado no build a partir de src/swagger/translations/*.js */
|
|
2
|
+
const uiLabelTranslations = [
|
|
3
|
+
__UI_LABEL_TRANSLATIONS__
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
const buildExactMap = (targetLang) => {
|
|
7
|
+
const map = new Map()
|
|
8
|
+
uiLabelTranslations.forEach(labels => {
|
|
9
|
+
const target = labels[targetLang]
|
|
10
|
+
if (!target) return
|
|
11
|
+
Object.keys(labels).forEach(sourceLang => {
|
|
12
|
+
if (sourceLang === targetLang) return
|
|
13
|
+
const source = labels[sourceLang]
|
|
14
|
+
if (!source || source === target) return
|
|
15
|
+
map.set(source, target)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
return map
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const uiTranslations = {
|
|
22
|
+
'pt-BR': {
|
|
23
|
+
exact: buildExactMap('pt-BR'),
|
|
24
|
+
replace: [
|
|
25
|
+
{ regex: /\binteger\b/g, value: 'inteiro' }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
en: {
|
|
29
|
+
exact: buildExactMap('en'),
|
|
30
|
+
replace: []
|
|
31
|
+
},
|
|
32
|
+
es: {
|
|
33
|
+
exact: buildExactMap('es'),
|
|
34
|
+
replace: [
|
|
35
|
+
{ regex: /\binteger\b/g, value: 'entero' }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let originalTextNodes = new WeakMap()
|
|
41
|
+
|
|
42
|
+
function resetTranslationCache () {
|
|
43
|
+
originalTextNodes = new WeakMap()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldTranslateNode (node) {
|
|
47
|
+
if (!node || !node.nodeValue || !node.nodeValue.trim()) return false
|
|
48
|
+
const parent = node.parentElement
|
|
49
|
+
if (!parent) return false
|
|
50
|
+
if (parent.closest('.lang-switcher')) return false
|
|
51
|
+
return !parent.closest('code, pre, textarea, input')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function translateNodeText (node, language) {
|
|
55
|
+
const config = uiTranslations[language] || uiTranslations['pt-BR']
|
|
56
|
+
if (!originalTextNodes.has(node)) {
|
|
57
|
+
originalTextNodes.set(node, node.nodeValue)
|
|
58
|
+
}
|
|
59
|
+
const original = originalTextNodes.get(node)
|
|
60
|
+
const trimmed = original.trim()
|
|
61
|
+
if (config.exact.has(trimmed)) {
|
|
62
|
+
const updated = original.replace(trimmed, config.exact.get(trimmed))
|
|
63
|
+
if (updated !== node.nodeValue) {
|
|
64
|
+
node.nodeValue = updated
|
|
65
|
+
}
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
let updated = original
|
|
69
|
+
config.replace.forEach(({ regex, value }) => {
|
|
70
|
+
updated = updated.replace(regex, value)
|
|
71
|
+
})
|
|
72
|
+
if (updated !== original) {
|
|
73
|
+
node.nodeValue = updated
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
if (node.nodeValue !== original) {
|
|
77
|
+
node.nodeValue = original
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyTranslations (language) {
|
|
82
|
+
const root = document.querySelector('.swagger-ui')
|
|
83
|
+
if (!root || !window.NodeFilter || !document.createTreeWalker) return
|
|
84
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
85
|
+
acceptNode: node => (shouldTranslateNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT)
|
|
86
|
+
})
|
|
87
|
+
const nodes = []
|
|
88
|
+
while (walker.nextNode()) {
|
|
89
|
+
nodes.push(walker.currentNode)
|
|
90
|
+
}
|
|
91
|
+
nodes.forEach(node => translateNodeText(node, language))
|
|
92
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const swaggerSpecCache = {}
|
|
2
|
+
|
|
3
|
+
function updateSpecUrlInDom (specUrlString) {
|
|
4
|
+
const root = document.querySelector('.swagger-ui')
|
|
5
|
+
if (!root) return
|
|
6
|
+
root.querySelectorAll('input').forEach(input => {
|
|
7
|
+
if (input.value && input.value.includes('swagger.json')) {
|
|
8
|
+
input.value = specUrlString
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
root.querySelectorAll('a[href*="swagger.json"]').forEach(a => {
|
|
12
|
+
if (a.getAttribute('href') && a.getAttribute('href').includes('swagger.json')) {
|
|
13
|
+
a.setAttribute('href', specUrlString)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
if (root.querySelectorAll) {
|
|
17
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false)
|
|
18
|
+
const textNodes = []
|
|
19
|
+
while (walker.nextNode()) textNodes.push(walker.currentNode)
|
|
20
|
+
textNodes.forEach(node => {
|
|
21
|
+
if (node.nodeValue && node.nodeValue.includes('swagger.json?lang=')) {
|
|
22
|
+
node.nodeValue = node.nodeValue.replace(
|
|
23
|
+
/https?:\/\/[^"\s]+\/swagger\.json\?lang=[^"\s&]+/g,
|
|
24
|
+
specUrlString
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function prefetchSwaggerSpecs () {
|
|
32
|
+
const code = getCurrentLanguageCode()
|
|
33
|
+
const url = '/swagger.json?lang=' + encodeURIComponent(code)
|
|
34
|
+
fetch(url)
|
|
35
|
+
.then(r => r.json())
|
|
36
|
+
.then(spec => { swaggerSpecCache[code] = spec })
|
|
37
|
+
.catch(() => {})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function updateSwaggerSpec (language, shouldRefreshOpenOps = false) {
|
|
41
|
+
const specUrl = new URL('/swagger.json', window.location.origin)
|
|
42
|
+
specUrl.searchParams.set('lang', language)
|
|
43
|
+
updateSpecUrlInDom(specUrl.toString())
|
|
44
|
+
const applySpec = (spec) => {
|
|
45
|
+
if (!window.ui || !window.ui.specActions) return
|
|
46
|
+
resetTranslationCache()
|
|
47
|
+
window.ui.specActions.updateSpec(JSON.stringify(spec))
|
|
48
|
+
if (window.ui.specActions.updateUrl) {
|
|
49
|
+
window.ui.specActions.updateUrl(specUrl.toString())
|
|
50
|
+
}
|
|
51
|
+
setTimeout(() => updateSpecUrlInDom(specUrl.toString()), 0)
|
|
52
|
+
if (window.ui.specActions.updateJsonSpec) {
|
|
53
|
+
window.ui.specActions.updateJsonSpec(spec)
|
|
54
|
+
}
|
|
55
|
+
let parsedSpec = null
|
|
56
|
+
if (window.ui.specActions.parseToJson) {
|
|
57
|
+
parsedSpec = window.ui.specActions.parseToJson(JSON.stringify(spec))
|
|
58
|
+
}
|
|
59
|
+
if (parsedSpec && window.ui.specActions.updateJsonSpec) {
|
|
60
|
+
window.ui.specActions.updateJsonSpec(parsedSpec)
|
|
61
|
+
}
|
|
62
|
+
if (window.ui.specActions.updateResolved) {
|
|
63
|
+
window.ui.specActions.updateResolved(parsedSpec || spec)
|
|
64
|
+
}
|
|
65
|
+
if (window.ui.specActions.invalidateResolvedSubtreeCache) {
|
|
66
|
+
window.ui.specActions.invalidateResolvedSubtreeCache()
|
|
67
|
+
}
|
|
68
|
+
if (window.ui.specActions.resolveSpec) {
|
|
69
|
+
const resolveResult = window.ui.specActions.resolveSpec(specUrl.toString())
|
|
70
|
+
if (resolveResult && typeof resolveResult.catch === 'function') {
|
|
71
|
+
resolveResult.catch(() => {})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (shouldRefreshOpenOps) {
|
|
75
|
+
refreshOpenOperations()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const cached = swaggerSpecCache[language]
|
|
79
|
+
if (cached) {
|
|
80
|
+
applySpec(cached)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
fetch(specUrl.toString())
|
|
84
|
+
.then(response => response.json())
|
|
85
|
+
.then(spec => {
|
|
86
|
+
swaggerSpecCache[language] = spec
|
|
87
|
+
applySpec(spec)
|
|
88
|
+
})
|
|
89
|
+
.catch(() => {})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function refreshOpenOperations () {
|
|
93
|
+
const openOperations = document.querySelectorAll('.opblock.is-open .opblock-summary')
|
|
94
|
+
if (!openOperations.length) return
|
|
95
|
+
const requestResolvedSubtree = window.ui?.specActions?.requestResolvedSubtree
|
|
96
|
+
if (requestResolvedSubtree) {
|
|
97
|
+
openOperations.forEach(summary => {
|
|
98
|
+
const opblock = summary.closest('.opblock')
|
|
99
|
+
if (!opblock) return
|
|
100
|
+
const methodText = opblock.querySelector('.opblock-summary-method')?.textContent?.trim() || ''
|
|
101
|
+
const pathText = opblock.querySelector('.opblock-summary-path')?.textContent?.trim() || ''
|
|
102
|
+
if (!methodText || !pathText) return
|
|
103
|
+
requestResolvedSubtree(['paths', pathText, methodText.toLowerCase()])
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
openOperations.forEach(summary => summary.click())
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
openOperations.forEach(summary => summary.click())
|
|
109
|
+
}, 0)
|
|
110
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const lastLanguageKey = 'swaggerLastLanguage'
|
|
2
|
+
|
|
3
|
+
function renderLanguageSwitcher (parent) {
|
|
4
|
+
if (document.querySelector('.lang-switcher')) return
|
|
5
|
+
const root = parent || document.querySelector('.swagger-ui')
|
|
6
|
+
if (!root) return
|
|
7
|
+
const currentLang = getPreferredLanguage()
|
|
8
|
+
root.setAttribute('data-docs-lang', currentLang)
|
|
9
|
+
const wrapper = document.createElement('div')
|
|
10
|
+
wrapper.className = 'lang-switcher'
|
|
11
|
+
wrapper.setAttribute('aria-label', 'Seleção de idioma')
|
|
12
|
+
let glowTimeoutId = null
|
|
13
|
+
supportedLanguages.forEach(language => {
|
|
14
|
+
const button = document.createElement('button')
|
|
15
|
+
button.type = 'button'
|
|
16
|
+
button.className = 'lang-switcher__button'
|
|
17
|
+
button.setAttribute('data-lang', language.code)
|
|
18
|
+
button.setAttribute('aria-label', language.label)
|
|
19
|
+
button.setAttribute('title', language.label)
|
|
20
|
+
const flagUrl = flagUrls[language.code]
|
|
21
|
+
if (flagUrl) {
|
|
22
|
+
const img = document.createElement('img')
|
|
23
|
+
img.className = 'lang-switcher__flag'
|
|
24
|
+
img.src = flagUrl
|
|
25
|
+
img.alt = ''
|
|
26
|
+
img.width = 20
|
|
27
|
+
img.height = 14
|
|
28
|
+
button.appendChild(img)
|
|
29
|
+
} else {
|
|
30
|
+
button.textContent = language.code
|
|
31
|
+
}
|
|
32
|
+
if (language.code === currentLang) {
|
|
33
|
+
button.classList.add('is-active')
|
|
34
|
+
}
|
|
35
|
+
button.addEventListener('click', () => {
|
|
36
|
+
if (language.code === getPreferredLanguage()) return
|
|
37
|
+
const previousLanguage = getPreferredLanguage()
|
|
38
|
+
window.sessionStorage.setItem(lastLanguageKey, previousLanguage)
|
|
39
|
+
setPreferredLanguage(language.code)
|
|
40
|
+
root.setAttribute('data-docs-lang', language.code)
|
|
41
|
+
if (glowTimeoutId) clearTimeout(glowTimeoutId)
|
|
42
|
+
wrapper.classList.remove('lang-switcher--glow')
|
|
43
|
+
wrapper.removeAttribute('data-glow-lang')
|
|
44
|
+
wrapper.offsetHeight
|
|
45
|
+
wrapper.classList.add('lang-switcher--glow')
|
|
46
|
+
wrapper.setAttribute('data-glow-lang', language.code)
|
|
47
|
+
glowTimeoutId = setTimeout(() => {
|
|
48
|
+
glowTimeoutId = null
|
|
49
|
+
wrapper.classList.remove('lang-switcher--glow')
|
|
50
|
+
wrapper.removeAttribute('data-glow-lang')
|
|
51
|
+
}, 1000)
|
|
52
|
+
const url = new URL(window.location.href)
|
|
53
|
+
url.searchParams.set('lang', language.code)
|
|
54
|
+
window.history.replaceState({}, '', url.toString())
|
|
55
|
+
wrapper.querySelectorAll('.lang-switcher__button').forEach(el => {
|
|
56
|
+
el.classList.toggle('is-active', el.getAttribute('data-lang') === language.code)
|
|
57
|
+
})
|
|
58
|
+
resetTranslationCache()
|
|
59
|
+
const shouldRefresh = setRefreshFlagFromState(language.code)
|
|
60
|
+
updateSwaggerSpec(language.code, shouldRefresh)
|
|
61
|
+
applyTranslations(language.code)
|
|
62
|
+
setTimeout(() => applyTranslations(language.code), 100)
|
|
63
|
+
updateReleaseToastLanguage(language.code)
|
|
64
|
+
})
|
|
65
|
+
wrapper.appendChild(button)
|
|
66
|
+
})
|
|
67
|
+
root.appendChild(wrapper)
|
|
68
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
let endpointBlockOpen = false
|
|
2
|
+
|
|
3
|
+
function hasOpenOperationHash () {
|
|
4
|
+
return typeof window.location.hash === 'string' && window.location.hash.includes('/')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function setEndpointBlockOpen (open) {
|
|
8
|
+
endpointBlockOpen = !!open
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function shouldRefreshEndpointBlockNow (currentLanguage) {
|
|
12
|
+
if (!endpointBlockOpen) return false
|
|
13
|
+
const lastLang = window.sessionStorage.getItem(lastLanguageKey)
|
|
14
|
+
return !lastLang || lastLang !== currentLanguage
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setRefreshFlagFromState (currentLanguage) {
|
|
18
|
+
return shouldRefreshEndpointBlockNow(currentLanguage)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const docsVersionKey = 'serverest-docs-version'
|
|
22
|
+
|
|
23
|
+
function clearCachesAndReloadForNewVersion (newVersion) {
|
|
24
|
+
const unregister = typeof navigator !== 'undefined' && navigator.serviceWorker && navigator.serviceWorker.getRegistrations
|
|
25
|
+
? navigator.serviceWorker.getRegistrations().then(regs => Promise.all(regs.map(r => r.unregister())))
|
|
26
|
+
: Promise.resolve()
|
|
27
|
+
const clearCaches = typeof caches !== 'undefined' && caches.keys
|
|
28
|
+
? caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k))))
|
|
29
|
+
: Promise.resolve()
|
|
30
|
+
Promise.all([unregister, clearCaches]).then(() => {
|
|
31
|
+
window.sessionStorage.setItem(docsVersionKey, newVersion)
|
|
32
|
+
window.location.reload()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function checkVersionThenObserve () {
|
|
37
|
+
const root = document.querySelector('.swagger-ui')
|
|
38
|
+
if (root) {
|
|
39
|
+
runVersionCheckAndObserve()
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
const mo = new MutationObserver(function () {
|
|
43
|
+
if (document.querySelector('.swagger-ui')) {
|
|
44
|
+
mo.disconnect()
|
|
45
|
+
runVersionCheckAndObserve()
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
mo.observe(document.documentElement, { childList: true, subtree: true })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function runVersionCheckAndObserve () {
|
|
52
|
+
const normalizedCurrent = normalizeVersion(currentVersion)
|
|
53
|
+
const storedVersion = window.sessionStorage.getItem(docsVersionKey)
|
|
54
|
+
if (storedVersion && storedVersion !== normalizedCurrent) {
|
|
55
|
+
clearCachesAndReloadForNewVersion(normalizedCurrent)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (!storedVersion) {
|
|
59
|
+
window.sessionStorage.setItem(docsVersionKey, normalizedCurrent)
|
|
60
|
+
}
|
|
61
|
+
observe()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function registerServiceWorker () {
|
|
65
|
+
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
66
|
+
navigator.serviceWorker.register('/swagger-sw.js').catch(() => {})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function observe () {
|
|
71
|
+
const root = document.querySelector('.swagger-ui')
|
|
72
|
+
if (!root) {
|
|
73
|
+
setTimeout(observe, 500)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
registerServiceWorker()
|
|
77
|
+
if (window.performance && window.performance.getEntriesByType) {
|
|
78
|
+
const navEntries = window.performance.getEntriesByType('navigation')
|
|
79
|
+
if (navEntries && navEntries[0] && navEntries[0].type === 'reload') {
|
|
80
|
+
window.sessionStorage.removeItem(lastLanguageKey)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const queryLanguage = getLanguageFromQuery()
|
|
84
|
+
const preferred = getPreferredLanguage()
|
|
85
|
+
const language = queryLanguage || preferred
|
|
86
|
+
if (language !== preferred) {
|
|
87
|
+
window.sessionStorage.setItem(lastLanguageKey, preferred)
|
|
88
|
+
setPreferredLanguage(language)
|
|
89
|
+
}
|
|
90
|
+
if (!queryLanguage) {
|
|
91
|
+
const url = new URL(window.location.href)
|
|
92
|
+
url.searchParams.set('lang', language)
|
|
93
|
+
window.history.replaceState({}, '', url.toString())
|
|
94
|
+
}
|
|
95
|
+
let translationScheduled = false
|
|
96
|
+
const scheduleTranslations = () => {
|
|
97
|
+
if (translationScheduled) return
|
|
98
|
+
translationScheduled = true
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
translationScheduled = false
|
|
101
|
+
applyTranslations(getPreferredLanguage())
|
|
102
|
+
}, 0)
|
|
103
|
+
}
|
|
104
|
+
const observer = new MutationObserver(scheduleTranslations)
|
|
105
|
+
observer.observe(root, { childList: true, subtree: true })
|
|
106
|
+
root.addEventListener('click', event => {
|
|
107
|
+
if (!event.isTrusted) return
|
|
108
|
+
const summary = event.target.closest ? event.target.closest('.opblock-summary') : null
|
|
109
|
+
if (!summary) return
|
|
110
|
+
const opblock = summary.closest('.opblock')
|
|
111
|
+
if (!opblock) return
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
if (opblock.classList.contains('is-open')) {
|
|
114
|
+
setEndpointBlockOpen(true)
|
|
115
|
+
window.sessionStorage.setItem(lastLanguageKey, getPreferredLanguage())
|
|
116
|
+
} else {
|
|
117
|
+
setEndpointBlockOpen(false)
|
|
118
|
+
}
|
|
119
|
+
}, 0)
|
|
120
|
+
})
|
|
121
|
+
renderLanguageSwitcher(root)
|
|
122
|
+
setupTopbarLogo(root)
|
|
123
|
+
scheduleTranslations()
|
|
124
|
+
if (hasOpenOperationHash()) {
|
|
125
|
+
setEndpointBlockOpen(true)
|
|
126
|
+
}
|
|
127
|
+
const shouldRefreshEndpointBlock = setRefreshFlagFromState(language)
|
|
128
|
+
updateSwaggerSpec(language, shouldRefreshEndpointBlock)
|
|
129
|
+
runReleaseCheck()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setupTopbarLogo (root) {
|
|
133
|
+
function run () {
|
|
134
|
+
const wrapper = root.querySelector('.topbar-wrapper')
|
|
135
|
+
const link = wrapper && wrapper.querySelector('.link')
|
|
136
|
+
if (!wrapper || !link) return
|
|
137
|
+
if (document.getElementById('serverest-logo')) return
|
|
138
|
+
const logo = document.createElement('div')
|
|
139
|
+
logo.id = 'serverest-logo'
|
|
140
|
+
logo.className = 'topbar-logo'
|
|
141
|
+
wrapper.insertBefore(logo, link)
|
|
142
|
+
link.style.display = 'none'
|
|
143
|
+
link.removeAttribute('href')
|
|
144
|
+
link.setAttribute('aria-hidden', 'true')
|
|
145
|
+
}
|
|
146
|
+
run()
|
|
147
|
+
setTimeout(run, 300)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function init () {
|
|
151
|
+
preloadFlags()
|
|
152
|
+
preloadSwaggerJson()
|
|
153
|
+
prefetchSwaggerSpecs()
|
|
154
|
+
if (document.body) renderReleaseToast()
|
|
155
|
+
checkVersionThenObserve()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (document.readyState === 'loading') {
|
|
159
|
+
document.addEventListener('DOMContentLoaded', init)
|
|
160
|
+
} else {
|
|
161
|
+
init()
|
|
162
|
+
}
|
|
163
|
+
})()
|