mirror of
https://github.com/OpenBankProject/API-Explorer-II.git
synced 2026-02-06 10:47:04 +00:00
Message Docs json schema
This commit is contained in:
parent
f7b7dfb598
commit
6f9a5d14bd
328
src/components/JsonSchemaViewer.vue
Normal file
328
src/components/JsonSchemaViewer.vue
Normal file
@ -0,0 +1,328 @@
|
||||
<!--
|
||||
- Open Bank Project - API Explorer II
|
||||
- Copyright (C) 2023-2024, TESOBE GmbH
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- Email: contact@tesobe.com
|
||||
- TESOBE GmbH
|
||||
- Osloerstrasse 16/17
|
||||
- Berlin 13359, Germany
|
||||
-
|
||||
- This product includes software developed at
|
||||
- TESOBE (http://www.tesobe.com/)
|
||||
-
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { DocumentCopy, Check } from '@element-plus/icons-vue'
|
||||
|
||||
interface Props {
|
||||
schema: any
|
||||
copyable?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
copyable: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
refClick: [refId: string]
|
||||
}>()
|
||||
|
||||
const copied = ref(false)
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
const text = JSON.stringify(props.schema, null, 2)
|
||||
await navigator.clipboard.writeText(text)
|
||||
copied.value = true
|
||||
setTimeout(() => {
|
||||
copied.value = false
|
||||
}, 2000)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefClick = (event: Event, refId: string) => {
|
||||
event.preventDefault()
|
||||
emit('refClick', refId)
|
||||
}
|
||||
|
||||
// Function to render JSON with clickable $ref links
|
||||
const renderJsonWithRefs = (obj: any, indent: number = 0): any[] => {
|
||||
const result: any[] = []
|
||||
const indentStr = ' '.repeat(indent)
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
result.push({ type: 'value', text: String(obj) })
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof obj !== 'object') {
|
||||
const valueClass = typeof obj === 'string' ? 'json-string' :
|
||||
typeof obj === 'number' ? 'json-number' :
|
||||
typeof obj === 'boolean' ? 'json-boolean' : 'json-value'
|
||||
const displayValue = typeof obj === 'string' ? `"${obj}"` : String(obj)
|
||||
result.push({ type: 'value', text: displayValue, class: valueClass })
|
||||
return result
|
||||
}
|
||||
|
||||
const isArray = Array.isArray(obj)
|
||||
const openBracket = isArray ? '[' : '{'
|
||||
const closeBracket = isArray ? ']' : '}'
|
||||
|
||||
result.push({ type: 'bracket', text: openBracket })
|
||||
|
||||
const entries = isArray ? obj.map((val, idx) => [idx, val]) : Object.entries(obj)
|
||||
const totalEntries = entries.length
|
||||
|
||||
entries.forEach(([key, value], index) => {
|
||||
const isLast = index === totalEntries - 1
|
||||
const newIndent = indent + 1
|
||||
const newIndentStr = ' '.repeat(newIndent)
|
||||
|
||||
result.push({ type: 'newline', text: '\n' })
|
||||
result.push({ type: 'indent', text: newIndentStr })
|
||||
|
||||
// Add key for objects
|
||||
if (!isArray) {
|
||||
result.push({ type: 'key', text: `"${key}"`, class: 'json-key' })
|
||||
result.push({ type: 'separator', text: ': ' })
|
||||
}
|
||||
|
||||
// Check if this is a $ref
|
||||
if (key === '$ref' && typeof value === 'string') {
|
||||
// Extract definition name from $ref
|
||||
const refMatch = value.match(/#\/definitions\/(.+)$/)
|
||||
if (refMatch) {
|
||||
const defName = refMatch[1]
|
||||
result.push({ type: 'ref', text: `"${value}"`, href: `#def-${defName}`, defName })
|
||||
} else {
|
||||
result.push({ type: 'value', text: `"${value}"`, class: 'json-string' })
|
||||
}
|
||||
} else if (value && typeof value === 'object') {
|
||||
// Recursively render nested objects/arrays
|
||||
const nested = renderJsonWithRefs(value, newIndent)
|
||||
result.push(...nested)
|
||||
} else {
|
||||
// Render primitive values
|
||||
const valueClass = typeof value === 'string' ? 'json-string' :
|
||||
typeof value === 'number' ? 'json-number' :
|
||||
typeof value === 'boolean' ? 'json-boolean' :
|
||||
value === null ? 'json-null' : 'json-value'
|
||||
const displayValue = typeof value === 'string' ? `"${value}"` : String(value)
|
||||
result.push({ type: 'value', text: displayValue, class: valueClass })
|
||||
}
|
||||
|
||||
// Add comma if not last
|
||||
if (!isLast) {
|
||||
result.push({ type: 'comma', text: ',' })
|
||||
}
|
||||
})
|
||||
|
||||
if (totalEntries > 0) {
|
||||
result.push({ type: 'newline', text: '\n' })
|
||||
result.push({ type: 'indent', text: indentStr })
|
||||
}
|
||||
result.push({ type: 'bracket', text: closeBracket })
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const jsonElements = computed(() => {
|
||||
return renderJsonWithRefs(props.schema)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="json-schema-viewer">
|
||||
<div v-if="copyable" class="schema-header">
|
||||
<button
|
||||
@click="copyToClipboard"
|
||||
class="copy-button"
|
||||
:class="{ 'copied': copied }"
|
||||
>
|
||||
<span v-if="!copied">
|
||||
<el-icon :size="10">
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
Copy
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-icon :size="10">
|
||||
<Check />
|
||||
</el-icon>
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="schema-container">
|
||||
<pre class="schema-pre"><code class="schema-code"><template v-for="(element, index) in jsonElements" :key="index"><span
|
||||
v-if="element.type === 'ref'"
|
||||
class="json-ref"
|
||||
:title="`Jump to definition: ${element.defName}`"
|
||||
><a :href="element.href" class="ref-link" @click="handleRefClick($event, element.href)">{{ element.text }}</a></span><span
|
||||
v-else-if="element.type === 'key' || element.type === 'value'"
|
||||
:class="element.class"
|
||||
>{{ element.text }}</span><span v-else>{{ element.text }}</span></template></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-schema-viewer {
|
||||
margin: 1rem 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.schema-header {
|
||||
background: #2d2d2d;
|
||||
padding: 0.5rem 1rem;
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
background: #444;
|
||||
border: 1px solid #666;
|
||||
color: #ddd;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
background: #555;
|
||||
border-color: #777;
|
||||
}
|
||||
|
||||
.copy-button.copied {
|
||||
background: #4caf50;
|
||||
border-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.schema-container {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.schema-pre {
|
||||
margin: 0;
|
||||
padding: 1.5rem;
|
||||
background: #1e1e1e;
|
||||
color: #ddd;
|
||||
font-family: 'Fira Code', 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.schema-code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* JSON Syntax Highlighting */
|
||||
.json-key {
|
||||
color: #e06c75;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.json-string {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.json-number {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.json-boolean {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.json-null {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.json-ref {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ref-link {
|
||||
color: #61afef;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.ref-link:hover {
|
||||
color: #84c5ff;
|
||||
text-decoration-style: solid;
|
||||
background-color: rgba(97, 175, 239, 0.1);
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
.schema-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.schema-container::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.schema-container::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.schema-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
.schema-pre::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.schema-pre::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.schema-pre::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.schema-pre::-webkit-scrollbar-thumb:hover {
|
||||
background: #777;
|
||||
}
|
||||
</style>
|
||||
183
src/components/MessageDocsJsonSchemaSearchNav.vue
Normal file
183
src/components/MessageDocsJsonSchemaSearchNav.vue
Normal file
@ -0,0 +1,183 @@
|
||||
<!--
|
||||
- Open Bank Project - API Explorer II
|
||||
- Copyright (C) 2023-2024, TESOBE GmbH
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- Email: contact@tesobe.com
|
||||
- TESOBE GmbH
|
||||
- Osloerstrasse 16/17
|
||||
- Berlin 13359, Germany
|
||||
-
|
||||
- This product includes software developed at
|
||||
- TESOBE (http://www.tesobe.com/)
|
||||
-
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onBeforeMount, inject, watch } from 'vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
|
||||
import { connectors } from '../obp/message-docs'
|
||||
import { obpGroupedMessageDocsJsonSchemaKey } from '@/obp/keys'
|
||||
|
||||
let connector = connectors[0]
|
||||
const route = useRoute()
|
||||
const groupedMessageDocsJsonSchema = ref(inject(obpGroupedMessageDocsJsonSchemaKey) || {})
|
||||
const docs = ref({})
|
||||
const groups = ref({})
|
||||
const sortedKeys = ref([])
|
||||
const activeKeys = ref([])
|
||||
const messageDocKeys = ref([])
|
||||
const searchLinksColor = ref(searchLinksColorSetting)
|
||||
const form = reactive({
|
||||
search: ''
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
setDocs()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async (id) => {
|
||||
setDocs()
|
||||
}
|
||||
)
|
||||
|
||||
const isKeyFound = (keys, item) => keys.every((k) => item.toLowerCase().includes(k))
|
||||
|
||||
const filterKeys = (keys, key) => {
|
||||
const splitKey = key.split(' ').map((k) => k.toLowerCase())
|
||||
return keys.filter((title) => {
|
||||
const isGroupFound = isKeyFound(splitKey, title)
|
||||
const items = docs.value[title].filter((item) => isGroupFound || isKeyFound(splitKey, item))
|
||||
groups.value[title] = items
|
||||
return isGroupFound || items.length > 0
|
||||
})
|
||||
}
|
||||
|
||||
const searchEvent = (value) => {
|
||||
if (value) {
|
||||
messageDocKeys.value = filterKeys(activeKeys.value, value)
|
||||
} else {
|
||||
groups.value = JSON.parse(JSON.stringify(docs.value))
|
||||
messageDocKeys.value = Object.keys(groups.value)
|
||||
}
|
||||
}
|
||||
|
||||
const setDocs = () => {
|
||||
const paramConnector = route.params.id
|
||||
if (connectors.includes(paramConnector)) {
|
||||
connector = paramConnector
|
||||
}
|
||||
const messageDocsJsonSchemaData = groupedMessageDocsJsonSchema.value[connector]
|
||||
const messageDocsJsonSchema = messageDocsJsonSchemaData?.grouped || messageDocsJsonSchemaData || {}
|
||||
|
||||
docs.value = Object.keys(messageDocsJsonSchema).reduce((doc, key) => {
|
||||
doc[key] = messageDocsJsonSchema[key].map((group) => group.method_name)
|
||||
return doc
|
||||
}, {})
|
||||
groups.value = JSON.parse(JSON.stringify(docs.value))
|
||||
messageDocKeys.value = Object.keys(groups.value)
|
||||
activeKeys.value = Object.keys(groups.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-input
|
||||
v-model="form.search"
|
||||
placeholder="Search"
|
||||
:prefix-icon="Search"
|
||||
@input="searchEvent"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-collapse v-model="activeKeys">
|
||||
<el-collapse-item v-for="key in messageDocKeys" :title="key" :key="key" :name="key">
|
||||
<div class="el-tabs--right">
|
||||
<div v-for="(value, key) of groups[key]" :key="value" class="message-docs-router-tab">
|
||||
<a class="message-docs-router-link" :id="`${value}-quick-nav`" v-bind:href="`#${value}`">
|
||||
{{ value }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.api-router-link {
|
||||
width: 100%;
|
||||
margin-left: 15px;
|
||||
font-family: 'Roboto';
|
||||
text-decoration: none;
|
||||
color: #39455f;
|
||||
display: inline-block;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.api-router-tab {
|
||||
border-left: 2px solid var(--el-menu-border-color);
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.api-router-tab:hover,
|
||||
.active-api-router-tab {
|
||||
border-left: 2px solid v-bind(searchLinksColor);
|
||||
}
|
||||
|
||||
.api-router-tab:hover .api-router-link,
|
||||
.active-api-router-link {
|
||||
color: v-bind(searchLinksColor);
|
||||
}
|
||||
|
||||
.message-docs-router-link {
|
||||
margin-left: 15px;
|
||||
font-size: 13px;
|
||||
font-family: 'Roboto';
|
||||
text-decoration: none;
|
||||
color: #39455f;
|
||||
display: inline-block;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message-docs-router-tab {
|
||||
border-left: 2px solid var(--el-menu-border-color);
|
||||
line-height: 30px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.message-docs-router-tab:hover,
|
||||
.active-message-docs-router-tab {
|
||||
border-left: 2px solid v-bind(searchLinksColor);
|
||||
}
|
||||
|
||||
.message-docs-router-tab:hover .message-docs-router-link {
|
||||
color: v-bind(searchLinksColor);
|
||||
}
|
||||
</style>
|
||||
114
src/views/MessageDocsJsonSchemaListView.vue
Normal file
114
src/views/MessageDocsJsonSchemaListView.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<!--
|
||||
- Open Bank Project - API Explorer II
|
||||
- Copyright (C) 2023-2024, TESOBE GmbH
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- Email: contact@tesobe.com
|
||||
- TESOBE GmbH
|
||||
- Osloerstrasse 16/17
|
||||
- Berlin 13359, Germany
|
||||
-
|
||||
- This product includes software developed at
|
||||
- TESOBE (http://www.tesobe.com/)
|
||||
-
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { obpGroupedMessageDocsJsonSchemaKey } from '@/obp/keys'
|
||||
|
||||
const router = useRouter()
|
||||
const groupedMessageDocsJsonSchema = ref(inject(obpGroupedMessageDocsJsonSchemaKey) || {})
|
||||
|
||||
const connectorList = computed(() => {
|
||||
return Object.keys(groupedMessageDocsJsonSchema.value || {}).sort()
|
||||
})
|
||||
|
||||
function navigateToConnector(connectorId: string) {
|
||||
router.push(`/message-docs-json-schema/${connectorId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container class="message-docs-list-container">
|
||||
<el-main>
|
||||
<h1>Message Documentation - JSON Schema</h1>
|
||||
<p class="subtitle">Browse connector message documentation with JSON Schema definitions</p>
|
||||
|
||||
<div class="message-docs-list">
|
||||
<div v-if="connectorList.length === 0" class="empty-message">
|
||||
No JSON schema message documentation available
|
||||
</div>
|
||||
<div v-else>
|
||||
<a
|
||||
v-for="connector in connectorList"
|
||||
:key="connector"
|
||||
@click="navigateToConnector(connector)"
|
||||
class="message-doc-link"
|
||||
>
|
||||
{{ connector }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-docs-list-container {
|
||||
min-height: calc(100vh - 60px);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: #909399;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.message-docs-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.message-doc-link {
|
||||
padding: 12px 16px;
|
||||
color: #409eff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message-doc-link:hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #337ecc;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
439
src/views/MessageDocsJsonSchemaView.vue
Normal file
439
src/views/MessageDocsJsonSchemaView.vue
Normal file
@ -0,0 +1,439 @@
|
||||
<!--
|
||||
- Open Bank Project - API Explorer II
|
||||
- Copyright (C) 2023-2024, TESOBE GmbH
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- Email: contact@tesobe.com
|
||||
- TESOBE GmbH
|
||||
- Osloerstrasse 16/17
|
||||
- Berlin 13359, Germany
|
||||
-
|
||||
- This product includes software developed at
|
||||
- TESOBE (http://www.tesobe.com/)
|
||||
-
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onBeforeMount, inject, watch, onMounted, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import SearchNav from '../components/MessageDocsJsonSchemaSearchNav.vue'
|
||||
import { connectors } from '../obp/message-docs'
|
||||
import { obpGroupedMessageDocsJsonSchemaKey } from '@/obp/keys'
|
||||
import JsonSchemaViewer from '../components/JsonSchemaViewer.vue'
|
||||
|
||||
let connector = connectors[0]
|
||||
const route = useRoute()
|
||||
const groupedMessageDocsJsonSchema = ref(inject(obpGroupedMessageDocsJsonSchemaKey) || {})
|
||||
const messageDocsJsonSchema = ref(null as any)
|
||||
const definitions = ref(null as any)
|
||||
const definitionsPanelScrollbar = ref(null as any)
|
||||
|
||||
onBeforeMount(() => {
|
||||
setDoc()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async (id) => {
|
||||
setDoc()
|
||||
}
|
||||
)
|
||||
|
||||
const setDoc = () => {
|
||||
const paramConnector = route.params.id
|
||||
if (connectors.includes(paramConnector)) {
|
||||
connector = paramConnector
|
||||
}
|
||||
const data = groupedMessageDocsJsonSchema.value[connector]
|
||||
// Handle both old and new data structures
|
||||
if (data?.grouped) {
|
||||
messageDocsJsonSchema.value = data.grouped
|
||||
definitions.value = data.definitions
|
||||
} else {
|
||||
messageDocsJsonSchema.value = data
|
||||
definitions.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function hasSchema(value: any) {
|
||||
return value && (value.outbound_schema || value.inbound_schema)
|
||||
}
|
||||
|
||||
// Handle $ref link clicks from JsonSchemaViewer components
|
||||
function handleRefClick(href: string) {
|
||||
console.log('handleRefClick called with href:', href)
|
||||
if (!href) {
|
||||
console.log('No href provided')
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the definition ID from the href (e.g., #def-BasicGeneralContext)
|
||||
const targetId = href.substring(1) // Remove the #
|
||||
console.log('Target ID:', targetId)
|
||||
const targetElement = document.getElementById(targetId)
|
||||
console.log('Target element:', targetElement)
|
||||
console.log('Definitions panel scrollbar:', definitionsPanelScrollbar.value)
|
||||
|
||||
if (targetElement && definitionsPanelScrollbar.value) {
|
||||
// Get the scrollbar's wrap element
|
||||
const scrollWrap = definitionsPanelScrollbar.value.$refs.wrap as HTMLElement
|
||||
console.log('Scroll wrap:', scrollWrap)
|
||||
if (scrollWrap) {
|
||||
// Calculate the position of the target element relative to the scrollable container
|
||||
const containerTop = scrollWrap.getBoundingClientRect().top
|
||||
const targetTop = targetElement.getBoundingClientRect().top
|
||||
const currentScroll = scrollWrap.scrollTop
|
||||
const offset = targetTop - containerTop + currentScroll - 20 // 20px padding from top
|
||||
console.log('Scrolling to offset:', offset)
|
||||
|
||||
// Smooth scroll to the target
|
||||
scrollWrap.scrollTo({
|
||||
top: offset,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
|
||||
// Add a highlight effect
|
||||
targetElement.classList.add('highlight-definition')
|
||||
setTimeout(() => {
|
||||
targetElement.classList.remove('highlight-definition')
|
||||
}, 2000)
|
||||
} else {
|
||||
console.log('No scroll wrap found')
|
||||
}
|
||||
} else {
|
||||
console.log('Target element or scrollbar not found')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container class="message-docs-container">
|
||||
<el-aside class="search-nav" width="18%">
|
||||
<el-scrollbar>
|
||||
<SearchNav />
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-main class="message-docs-content">
|
||||
<el-scrollbar>
|
||||
<el-backtop :right="100" :bottom="100" />
|
||||
<div class="message-docs-header">
|
||||
<h1>{{ connector }}</h1>
|
||||
<p class="connector-subtitle">Message Docs - JSON Schema</p>
|
||||
<p class="version-indicator">v1.2.4 - Debug Click Events</p>
|
||||
</div>
|
||||
<div v-for="(group, key) of messageDocsJsonSchema" :key="key">
|
||||
<div v-for="(value, key) of group" :key="value">
|
||||
<el-divider></el-divider>
|
||||
<a v-bind:href="`#${value.method_name}`" :id="value.method_name">
|
||||
<h2>{{ value.method_name }}</h2>
|
||||
</a>
|
||||
<p v-if="value.description">{{ value.description }}</p>
|
||||
|
||||
<section class="topics">
|
||||
<div>
|
||||
<strong>Category: </strong>
|
||||
<el-tag type="info" round>{{ value.category || 'Uncategorized' }}</el-tag>
|
||||
</div>
|
||||
<div v-if="value.message_format">
|
||||
<strong>Message Format: </strong>
|
||||
<el-tag type="success" round>{{ value.message_format }}</el-tag>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="value.outbound_schema">
|
||||
<h3>Outbound Schema</h3>
|
||||
<JsonSchemaViewer :schema="value.outbound_schema" copyable @refClick="handleRefClick" />
|
||||
</section>
|
||||
|
||||
<section v-if="value.inbound_schema">
|
||||
<h3>Inbound Schema</h3>
|
||||
<JsonSchemaViewer :schema="value.inbound_schema" copyable @refClick="handleRefClick" />
|
||||
</section>
|
||||
|
||||
<section v-if="!hasSchema(value)">
|
||||
<p class="no-schema-message">No schema information available for this method.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<el-aside class="definitions-panel" width="28%">
|
||||
<el-scrollbar ref="definitionsPanelScrollbar">
|
||||
<div class="definitions-panel-content">
|
||||
<div class="definitions-header">
|
||||
<h2>Schema Definitions</h2>
|
||||
<p class="definitions-subtitle">
|
||||
Reference schemas used in messages
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Debug info -->
|
||||
<div v-if="false">
|
||||
{{ console.log('Definitions:', definitions) }}
|
||||
{{ console.log('Definitions keys:', definitions ? Object.keys(definitions) : 'null') }}
|
||||
{{ console.log('Message docs:', messageDocsJsonSchema) }}
|
||||
</div>
|
||||
|
||||
<div v-if="definitions && Object.keys(definitions).length > 0">
|
||||
<div v-for="(defSchema, defName) in definitions" :key="defName" class="definition-item">
|
||||
<a v-bind:href="`#def-${defName}`" :id="`def-${defName}`">
|
||||
<h3>{{ defName }}</h3>
|
||||
</a>
|
||||
<JsonSchemaViewer :schema="defSchema" copyable @refClick="handleRefClick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-definitions">
|
||||
<p>No schema definitions available</p>
|
||||
<p style="font-size: 0.8rem; margin-top: 10px;">
|
||||
Debug: definitions = {{ definitions ? 'exists' : 'null' }},
|
||||
keys = {{ definitions ? Object.keys(definitions).length : 0 }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-docs-container {
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
/* Left Sidebar - Search Navigation */
|
||||
.search-nav {
|
||||
border-right: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.search-nav :deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-nav :deep(.el-scrollbar__view) {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Main Content Area */
|
||||
.message-docs-content {
|
||||
color: #39455f;
|
||||
font-family: 'Roboto';
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.message-docs-content :deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.message-docs-content :deep(.el-scrollbar__view) {
|
||||
padding: 25px 30px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Right Sidebar - Definitions Panel */
|
||||
.definitions-panel {
|
||||
border-left: 2px solid #e4e7ed;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.definitions-panel :deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.definitions-panel :deep(.el-scrollbar__view) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.definitions-panel-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.definitions-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: #f9fafb;
|
||||
padding: 10px 0 20px 0;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.definitions-header h2 {
|
||||
color: #303133;
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Roboto';
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.definitions-subtitle {
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'Roboto';
|
||||
}
|
||||
|
||||
h2 {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
section {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'Roboto';
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #39455f;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
div {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content :deep(strong) {
|
||||
font-family: 'Roboto';
|
||||
}
|
||||
|
||||
.content :deep(a):hover {
|
||||
background-color: #39455f;
|
||||
}
|
||||
|
||||
.message-docs-header {
|
||||
padding: 20px 0;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.message-docs-header h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 0.5rem 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.connector-subtitle {
|
||||
font-size: 1rem;
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.version-indicator {
|
||||
font-size: 0.75rem;
|
||||
color: #67c23a;
|
||||
margin: 0.25rem 0 0 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.topics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.topics > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.no-schema-message {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.definition-item {
|
||||
margin-bottom: 25px;
|
||||
padding: 15px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.definition-item:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.definition-item h3 {
|
||||
color: #409eff;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-size: 1rem;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
font-weight: 600;
|
||||
font-family: 'Roboto';
|
||||
}
|
||||
|
||||
.definition-item a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.definition-item a:hover h3 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.no-definitions {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
font-family: 'Roboto';
|
||||
}
|
||||
|
||||
/* Highlight animation for scrolled-to definitions */
|
||||
@keyframes highlight-pulse {
|
||||
0% {
|
||||
background-color: rgba(64, 158, 255, 0.15);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(64, 158, 255, 0.25);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(64, 158, 255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.definition-item.highlight-definition {
|
||||
animation: highlight-pulse 0.6s ease-in-out 3;
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user