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.
Files changed (32) hide show
  1. package/docs/flags/flag_peru.svg +10 -0
  2. package/docs/images/serverest_logo.svg +116 -0
  3. package/package.json +4 -1
  4. package/src/app.js +1 -1
  5. package/src/controllers/produtos-controller.js +63 -65
  6. package/src/controllers/usuarios-controller.js +3 -4
  7. package/src/data/carrinhos.db +8 -0
  8. package/src/data/produtos.db +6 -0
  9. package/src/data/usuarios.db +2 -0
  10. package/src/middlewares/error-handler.js +6 -0
  11. package/src/services/carrinhos-service.js +30 -22
  12. package/src/services/produtos-service.js +84 -0
  13. package/src/services/usuarios-service.js +6 -2
  14. package/src/swagger/index.js +62 -0
  15. package/src/swagger/scripts/01-config.js +10 -0
  16. package/src/swagger/scripts/02-language-storage.js +55 -0
  17. package/src/swagger/scripts/03-release-toast.js +158 -0
  18. package/src/swagger/scripts/04-ui-translations.js +92 -0
  19. package/src/swagger/scripts/05-swagger-spec.js +110 -0
  20. package/src/swagger/scripts/06-language-switcher.js +68 -0
  21. package/src/swagger/scripts/07-doc-lifecycle.js +163 -0
  22. package/src/swagger/styles/language-switcher.css +64 -0
  23. package/src/swagger/{custom-css.css → styles/release-toast.css} +1 -60
  24. package/src/swagger/styles/topbar.css +56 -0
  25. package/src/swagger/swagger-sw.js +2 -2
  26. package/src/swagger/translations/en.js +23 -0
  27. package/src/swagger/translations/es.js +23 -0
  28. package/src/swagger/translations/pt-BR.js +23 -0
  29. package/docs/flags/flag_spain.svg +0 -2552
  30. package/docs/images/serverest_logo.png +0 -0
  31. package/src/swagger/custom-js.js +0 -647
  32. 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
+ })()