diff --git a/packages/bruno-app/src/components/RequestPane/QueryEditor/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/QueryEditor/StyledWrapper.js
index ce8bca94f..df8c80407 100644
--- a/packages/bruno-app/src/components/RequestPane/QueryEditor/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestPane/QueryEditor/StyledWrapper.js
@@ -22,28 +22,43 @@ const StyledWrapper = styled.div`
}
}
- .cm-s-monokai span.cm-property,
- .cm-s-monokai span.cm-attribute {
- color: #9cdcfe !important;
- }
-
- .cm-s-monokai span.cm-property,
- .cm-s-monokai span.cm-attribute {
- color: #9cdcfe !important;
- }
-
- .cm-s-monokai span.cm-string {
- color: #ce9178 !important;
- }
-
- .cm-s-monokai span.cm-number {
- color: #b5cea8 !important;
- }
-
- .cm-s-monokai span.cm-atom {
- color: #569cd6 !important;
+ .cm-s-default, .cm-s-monokai {
+ span.cm-def {
+ color: ${(props) => props.theme.codemirror.tokens.definition} !important;
+ }
+ span.cm-property {
+ color: ${(props) => props.theme.codemirror.tokens.property} !important;
+ }
+ span.cm-string {
+ color: ${(props) => props.theme.codemirror.tokens.string} !important;
+ }
+ span.cm-number {
+ color: ${(props) => props.theme.codemirror.tokens.number} !important;
+ }
+ span.cm-atom {
+ color: ${(props) => props.theme.codemirror.tokens.atom} !important;
+ }
+ span.cm-variable, span.cm-variable-2 {
+ color: ${(props) => props.theme.codemirror.tokens.variable} !important;
+ }
+ span.cm-keyword {
+ color: ${(props) => props.theme.codemirror.tokens.keyword} !important;
+ }
+ span.cm-comment {
+ color: ${(props) => props.theme.codemirror.tokens.comment} !important;
+ }
+ span.cm-operator {
+ color: ${(props) => props.theme.codemirror.tokens.operator} !important;
+ }
+ span.cm-tag {
+ color: ${(props) => props.theme.codemirror.tokens.tag} !important;
+ }
+ span.cm-tag.cm-bracket {
+ color: ${(props) => props.theme.codemirror.tokens.tagBracket} !important;
+ }
}
+ /* Variable validation colors */
.cm-variable-valid {
color: ${(props) => props.theme.codemirror.variable.valid};
}
@@ -51,17 +66,10 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.codemirror.variable.invalid};
}
+
.CodeMirror-search-hint {
display: inline;
}
-
- .cm-s-default span.cm-property {
- color: #1f61a0 !important;
- }
-
- .cm-s-default span.cm-variable {
- color: #397d13 !important;
- }
`;
export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/RequestTabPanel/StyledWrapper.js b/packages/bruno-app/src/components/RequestTabPanel/StyledWrapper.js
index 7485e7f62..7bf6bb5f8 100644
--- a/packages/bruno-app/src/components/RequestTabPanel/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestTabPanel/StyledWrapper.js
@@ -96,6 +96,93 @@ const StyledWrapper = styled.div`
div.doc-explorer-rhs {
display: flex;
}
+
+ // GraphQL docs color overrides
+ .doc-explorer-back {
+ color: ${(props) => props.theme.textLink};
+
+ &:before {
+ border-left-color: ${(props) => props.theme.textLink};
+ border-top-color: ${(props) => props.theme.textLink};
+ }
+ }
+
+ .doc-explorer-contents {
+ border-top-color: ${(props) => props.theme.border.border2};
+ }
+
+ .doc-type-description code,
+ .doc-category code {
+ color: ${(props) => props.theme.codemirror.tokens.keyword};
+ background-color: ${(props) => props.theme.background.surface0};
+ border-color: ${(props) => props.theme.border.border1};
+ }
+
+ .doc-category-title {
+ border-bottom-color: ${(props) => props.theme.border.border1};
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+
+ .doc-category-item {
+ color: ${(props) => props.theme.colors.text.subtext2};
+ }
+
+ .keyword {
+ color: ${(props) => props.theme.codemirror.tokens.property};
+ }
+
+ .type-name {
+ color: ${(props) => props.theme.codemirror.tokens.atom};
+ }
+
+ .field-name {
+ color: ${(props) => props.theme.codemirror.tokens.property};
+ }
+
+ .field-short-description {
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+
+ .enum-value {
+ color: ${(props) => props.theme.textLink};
+ }
+
+ .arg-name {
+ color: ${(props) => props.theme.colors.text.purple};
+ }
+
+ .arg-default-value {
+ color: ${(props) => props.theme.colors.text.green};
+ }
+
+ .doc-deprecation {
+ background: ${(props) => props.theme.status.warning.background};
+ box-shadow: inset 0 0 1px ${(props) => props.theme.status.warning.border};
+ color: ${(props) => props.theme.colors.text.muted};
+
+ &:before {
+ color: ${(props) => props.theme.status.warning.text};
+ }
+ }
+
+ .show-btn {
+ border-color: ${(props) => props.theme.border.border2};
+ background: ${(props) => props.theme.background.surface0};
+ color: ${(props) => props.theme.text};
+ }
+
+ .search-box {
+ border-bottom-color: ${(props) => props.theme.border.border1};
+ }
+
+ .search-box-clear {
+ background-color: ${(props) => props.theme.overlay.overlay1};
+ color: ${(props) => props.theme.colors.text.white};
+
+ &:hover {
+ background-color: ${(props) => props.theme.overlay.overlay2};
+ }
+ }
}
`;
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/StyledWrapper.js
index f54b8450a..746ee8165 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/StyledWrapper.js
@@ -10,12 +10,15 @@ const StyledWrapper = styled.div`
.CodeMirror {
height: 100%;
font-size: ${(props) => props.theme.font.size.sm};
+ background: ${(props) => props.theme.modal.bg};
line-height: 1.5;
padding: 0;
+ background: transparent !important;
+ border: none;
.CodeMirror-gutters {
- background: ${(props) => props.theme.codemirror.gutter.bg};
- border-right: 1px solid ${(props) => props.theme.codemirror.border};
+ background: transparent !important;
+ border-right: none;
}
.CodeMirror-linenumber {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js
index a33d41749..fc0ccf841 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js
@@ -47,7 +47,7 @@ const CodeView = ({ language, item }) => {
onCopy={() => toast.success('Copied to clipboard!')}
>
-
+
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeViewToolbar/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeViewToolbar/StyledWrapper.js
index 617cb5ff2..5b60dadb0 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeViewToolbar/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeViewToolbar/StyledWrapper.js
@@ -5,9 +5,7 @@ const StyledWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
- padding: 8px 12px;
- background: ${(props) => props.theme.requestTabPanel.card.bg};
- border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
+ background: ${(props) => props.theme.modal.bg};
gap: 12px;
flex-shrink: 0;
}
@@ -45,6 +43,8 @@ const StyledWrapper = styled.div`
cursor: pointer;
transition: all 0.2s ease;
appearance: none;
+ outline: none;
+ box-shadow: none;
&:hover {
border-color: ${(props) => props.theme.input.focusBorder};
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js
index 9c02ee4d2..ea77fe0fc 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js
@@ -1,11 +1,10 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
- margin: -1.5rem -1rem;
height: 50vh;
display: flex;
flex-direction: column;
- background-color: ${(props) => props.theme.background.base};
+ background-color: ${(props) => props.theme.modal.bg};
.code-generator {
display: flex;
@@ -15,9 +14,11 @@ const StyledWrapper = styled.div`
.editor-container {
flex: 1;
+ min-height: 0;
overflow: hidden;
position: relative;
- background: ${(props) => props.theme.bg};
+ background: ${(props) => props.theme.modal.bg};
+ margin-top: 0.5rem;
}
.error-message {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/StyledWrapper.js
index a761d48d0..6c55d5200 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/StyledWrapper.js
@@ -1,47 +1,39 @@
import styled from 'styled-components';
-import { rgba } from 'polished';
const StyledWrapper = styled.div`
- .info-card {
- border-radius: ${(props) => props.theme.border.radius.base};
- background-color: ${(props) => rgba(props.theme.accents.primary, 0.06)};
- border: 1px solid ${(props) => rgba(props.theme.accents.primary, 0.2)};
-
- .info-icon {
- color: ${(props) => props.theme.accents.primary};
- }
-
- .info-title {
- font-weight: 500;
+ .content {
+ .title {
+ font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.text};
}
- .info-description {
+ .description {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.muted};
+ line-height: 1.6;
+ }
+
+ .features {
+ li {
+ font-size: ${(props) => props.theme.font.size.sm};
+ color: ${(props) => props.theme.text};
+ }
+
+ .check-icon {
+ color: ${(props) => props.theme.colors.text.green};
+ }
+ }
+
+ .note {
+ font-size: ${(props) => props.theme.font.size.xs};
+ color: ${(props) => props.theme.colors.text.muted};
line-height: 1.5;
}
}
- .feature-item {
- border-radius: ${(props) => props.theme.border.radius.base};
- background-color: ${(props) => props.theme.background.base};
- border: 1px solid ${(props) => props.theme.border.border0};
- font-size: ${(props) => props.theme.font.size.sm};
- color: ${(props) => props.theme.text};
-
- .feature-icon {
- color: ${(props) => props.theme.colors.text.green};
- }
- }
-
- .note-section {
- border-radius: ${(props) => props.theme.border.radius.base};
- background-color: ${(props) => rgba(props.theme.colors.text.warning, 0.06)};
- border: 1px solid ${(props) => rgba(props.theme.colors.text.warning, 0.2)};
+ .text-warning {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.warning};
- line-height: 1.5;
}
`;
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.js
index f04b6b02a..6a12d0ef4 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.js
@@ -1,59 +1,92 @@
-import React from 'react';
-import Modal from 'components/Modal';
-import { IconCheck, IconInfoCircle, IconAlertTriangle, IconLoader2 } from '@tabler/icons';
-import StyledWrapper from './StyledWrapper';
-import { cloneDeep } from 'lodash';
-import { transformCollectionToSaveToExportAsFile } from 'utils/collections/index';
+import React, { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
-import { findCollectionByUid, areItemsLoading } from 'utils/collections/index';
-import { brunoToOpenCollection } from '@usebruno/converters';
-import { sanitizeName } from 'utils/common/regex';
+import { cloneDeep } from 'lodash';
import * as FileSaver from 'file-saver';
import jsyaml from 'js-yaml';
import toast from 'react-hot-toast';
+import { IconBook, IconCheck, IconAlertTriangle, IconLoader2 } from '@tabler/icons';
+
+import Modal from 'components/Modal';
+import StyledWrapper from './StyledWrapper';
import { useApp } from 'providers/App';
+import { transformCollectionToSaveToExportAsFile, findCollectionByUid, areItemsLoading } from 'utils/collections/index';
+import { brunoToOpenCollection } from '@usebruno/converters';
+import { sanitizeName } from 'utils/common/regex';
import { escapeHtml } from 'utils/response';
+const CDN_BASE_URL = 'https://cdn.opencollection.com';
+
+const FEATURES = [
+ 'Standalone HTML file - no server required',
+ 'Interactive API playground',
+ 'Host on any static file server'
+];
+
+const escapeForTemplate = (content) =>
+ content.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
+
+const buildHtmlDocument = (collectionName, yamlContent) => `
+
+
+
+
+
${collectionName} - API Documentation
+
+
+
+
+
+
+
+
+`;
+
+const CollectionNotFound = ({ onClose }) => (
+
+
+
+
+ Collection not found. It may have been deleted or is no longer available.
+
+
+
+);
+
const GenerateDocumentation = ({ onClose, collectionUid }) => {
const { version } = useApp();
- const collection = useSelector((state) => findCollectionByUid(state.collections.collections, collectionUid));
- const isCollectionLoading = collection ? areItemsLoading(collection) : false;
+ const collection = useSelector((state) =>
+ findCollectionByUid(state.collections.collections, collectionUid)
+ );
- if (!collection) {
- return (
-
-
-
-
-
- Collection not found. It may have been deleted or is no longer available.
-
-
-
-
- );
- }
+ const isLoading = useMemo(
+ () => (collection ? areItemsLoading(collection) : false),
+ [collection]
+ );
- const generateHtmlDocumentation = () => {
+ const handleGenerate = useCallback(() => {
try {
const collectionCopy = cloneDeep(collection);
const transformedCollection = transformCollectionToSaveToExportAsFile(collectionCopy);
const openCollection = brunoToOpenCollection(transformedCollection);
- if (!openCollection.extensions) {
- openCollection.extensions = {};
- }
- if (!openCollection.extensions.bruno) {
- openCollection.extensions.bruno = {};
- }
- openCollection.extensions.bruno.exportedAt = new Date().toISOString();
- openCollection.extensions.bruno.exportedUsing = version ? `Bruno/${version}` : 'Bruno';
+ openCollection.extensions = {
+ ...openCollection.extensions,
+ bruno: {
+ ...openCollection.extensions?.bruno,
+ exportedAt: new Date().toISOString(),
+ exportedUsing: version ? `Bruno/${version}` : 'Bruno'
+ }
+ };
const yamlContent = jsyaml.dump(openCollection, {
indent: 2,
@@ -62,117 +95,67 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
sortKeys: false
});
- const escapedYaml = yamlContent
- .replace(/\\/g, '\\\\')
- .replace(/`/g, '\\`')
- .replace(/\$/g, '\\$');
+ const htmlContent = buildHtmlDocument(
+ escapeHtml(collection.name),
+ escapeForTemplate(yamlContent)
+ );
- const escapedCollectionName = escapeHtml(collection.name);
+ const fileName = `${sanitizeName(collection.name)}-documentation.html`;
+ FileSaver.saveAs(new Blob([htmlContent], { type: 'text/html' }), fileName);
- const htmlContent = `
-
-
-
-
-
${escapedCollectionName} - API Documentation
-
-
-
-
-
-
-
-
-`;
-
- const sanitizedName = sanitizeName(collection.name);
- const fileName = `${sanitizedName}-documentation.html`;
- const fileBlob = new Blob([htmlContent], { type: 'text/html' });
-
- FileSaver.saveAs(fileBlob, fileName);
toast.success('Documentation generated successfully');
onClose();
} catch (error) {
console.error('Error generating documentation:', error);
toast.error('Failed to generate documentation');
}
- };
+ }, [collection, version, onClose]);
+
+ if (!collection) {
+ return
;
+ }
return (
-
-
-
- {isCollectionLoading ? (
-
- ) : (
-
- )}
-
-
-
- {isCollectionLoading ? 'Loading collection...' : 'Interactive API Documentation'}
-
-
- {isCollectionLoading
- ? 'Please wait while the collection is being loaded.'
- : 'Generate a standalone HTML file containing interactive documentation for your API collection. This file can be hosted anywhere or shared with your team.'}
-
-
+ {isLoading ? (
+
+
+ Loading collection...
+ ) : (
+
+
+
+ Interactive API Documentation
+
+
+ Generate a standalone HTML file containing interactive documentation for your API collection.
+ This file can be hosted anywhere or shared with your team.
+
- {!isCollectionLoading && (
- <>
-
-
-
- Standalone HTML file - no server required
-
-
-
- Interactive API playground
-
-
-
- Host on any static file server
-
-
+
+ {FEATURES.map((feature) => (
+
+
+ {feature}
+
+ ))}
+
-
-
-
- The generated file uses OpenCollection CDN for rendering. An internet connection is required when viewing the documentation.
-
-
- >
- )}
-
+
+ The generated file does not embed all assets. It loads OpenCollection’s JavaScript and CSS files from a CDN when viewing docs, which requires an internet connection.
+
+
+ )}
);