Skip to content

Commit

Permalink
Better logo and Favicon support (#878)
Browse files Browse the repository at this point in the history
* favicon

* tests

* version
  • Loading branch information
newhouse authored Aug 25, 2023
1 parent 1a37a22 commit fa25c84
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### 2.3.0
- Support Logo and Favicon Base64 embedding, as well as URL specification. https://github.com/anvilco/spectaql/issues/807

### 2.2.1
- Fix publishing and installation process. https://github.com/anvilco/spectaql/issues/876

Expand Down
18 changes: 18 additions & 0 deletions config-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@ spectaql:

# Optional path to an image to use as the logo in the top-left of the output
logoFile: path/to/logo.png
# Optional boolean indicating whether to encode the local logoFile and embed the image as Base64
# into the resulting HTML. Only applicable if providing the logoFile.
#
# Default: false
embedLogo: false
# Optional boolean indicating whether to preserve the filename of the logo provided.
# If false, the logo name will default to "logo"
preserveLogoName: false
# Optional URL to an image to use in your logo. This string will be provided as-is
# to the HTML output. Should not be used with logoFile and its related options as
# this will take precedence over the logoFile.
logoUrl: https://yoursite.com/images/logo.png
# Control the height of your logo here. Can be useful if your logo is SVG or an
# inappropriate size.
#
Expand All @@ -31,9 +40,18 @@ spectaql:

# Optional path to an image to use as a favicon for the site when it's not embedded
faviconFile: path/to/favicon.png
# Optional boolean indicating whether to encode the local favicon and embed the image as Base64
# into the resulting HTML. Only applicable if providing the faviconFile.
#
# Default: false
embedFavicon: false
# Optional boolean indicating whether to preserve the filename of the faviconFile provided.
# If false, the favicon name will default to "favicon"
preserveFaviconName: false
# Optional URL to an image to use in your favicon. This string will be provided as-is
# to the HTML output. Should not be used with faviconFile and its related options as
# this will take precedence over the faviconFile.
faviconUrl: https://yoursite.com/images/favicon.png

# Optional string specifying a path to a theme directory to use for your build.
#
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spectaql",
"version": "2.2.1",
"version": "2.3.0",
"description": "A powerful library for autogenerating static GraphQL API documentation",
"author": "Anvil Foundry Inc. <[email protected]>",
"homepage": "https://github.com/anvilco/spectaql",
Expand Down
41 changes: 29 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
normalizePathFromCwd,
pathToRoot,
readTextFile,
readFileAsBase64,
tmpFolder,
writeTextFile,
} from './spectaql/utils'
Expand Down Expand Up @@ -65,6 +66,8 @@ const spectaqlOptionDefaults = Object.freeze({
errorOnInterpolationReferenceNotFound: true,
displayAllServers: false,
resolveWithOutput: true,
embedLogo: false,
embedFavicon: false,
})

const spectaqlDirectiveDefault = Object.freeze({
Expand Down Expand Up @@ -287,19 +290,31 @@ export function resolveOptions(cliOptions) {
}

if (opts.logoFile) {
// Keep or don't keep the original logoFile name when copying to the target
opts.logoFileTargetName = opts.preserveLogoName
? path.basename(opts.logoFile)
: `logo${path.extname(opts.logoFile)}`
opts.logo = path.basename(opts.logoFileTargetName)
if (opts.embedLogo) {
opts.logoData = readFileAsBase64(opts.logoFile)
} else {
// Keep or don't keep the original logoFile name when copying to the target
opts.logoFileTargetName = opts.preserveLogoName
? path.basename(opts.logoFile)
: `logo${path.extname(opts.logoFile)}`
opts.logoImageName = path.basename(opts.logoFileTargetName)
}
} else if (opts.logoUrl) {
// Nothing special here for now
}

if (opts.faviconFile) {
// Keep or don't keep the original faviconFile name when copying to the target
opts.faviconFileTargetName = opts.preserveFaviconName
? path.basename(opts.faviconFile)
: `favicon${path.extname(opts.faviconFile)}`
opts.favicon = path.basename(opts.faviconFileTargetName)
if (opts.embedFavicon) {
opts.faviconData = readFileAsBase64(opts.faviconFile)
} else {
// Keep or don't keep the original faviconFile name when copying to the target
opts.faviconFileTargetName = opts.preserveFaviconName
? path.basename(opts.faviconFile)
: `favicon${path.extname(opts.faviconFile)}`
opts.faviconImageName = path.basename(opts.faviconFileTargetName)
}
} else if (opts.faviconUrl) {
// Nothing special here for now
}

// Set the spectaql object
Expand Down Expand Up @@ -502,10 +517,12 @@ export const run = async function (cliOptions = {}) {
copiesToTarget.unshift('js-to-target')
}
}
if (opts.logoFile) {
// Only copy the file if we are NOT embedding it as Base64
if (opts.logoFile && !opts.embedLogo) {
copiesToTarget.unshift('logo-to-target')
}
if (opts.faviconFile) {
// Only copy the file if we are NOT embedding it as Base64
if (opts.faviconFile && !opts.embedFavicon) {
copiesToTarget.unshift('favicon-to-target')
}

Expand Down
23 changes: 19 additions & 4 deletions src/spectaql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import { dynamicImport, fileExists, takeDefaultExport } from './utils'
import preProcessData from './pre-process'

async function run(opts) {
const { logo, favicon, specData: spec, themeDir } = opts
const {
logoImageName,
logoData,
logoUrl,
faviconImageName,
faviconData,
faviconUrl,
specData: spec,
themeDir,
} = opts

const {
introspection: introspectionOptions,
Expand Down Expand Up @@ -62,7 +71,9 @@ async function run(opts) {
if (customDataArrangerSuffixThatExists) {
try {
arrangeDataModule = await dynamicImport(
url.pathToFileURL(path.normalize(`${themeDir}/${customDataArrangerSuffixThatExists}`))
url.pathToFileURL(
path.normalize(`${themeDir}/${customDataArrangerSuffixThatExists}`)
)
)
} catch (err) {
console.error(err)
Expand Down Expand Up @@ -110,8 +121,12 @@ async function run(opts) {

const data = {
allOptions: opts,
logo,
favicon,
logoImageName,
logoUrl,
logoData,
faviconImageName,
faviconData,
faviconUrl,
info,
server,
headers,
Expand Down
7 changes: 7 additions & 0 deletions src/spectaql/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ export function readJSFile(pth, { normalizePath = true } = {}) {
return require(pth)
}

export function readFileAsBase64(pth, { normalizePath } = {}) {
if (normalizePath) {
pth = normalizePathFromCwd(pth)
}
return Buffer.from(fs.readFileSync(pth)).toString('base64')
}

export function fileExtensionIs(fileNameOrPath, extensionOrExtensions) {
if (typeof fileNameOrPath !== 'string') {
return false
Expand Down
2 changes: 1 addition & 1 deletion src/themes/default/views/partials/layout/head.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{#if favicon }}<link rel="icon" type="image/png" href="images/{{favicon}}" sizes="32x32">{{/if}}
{{#if faviconData }}<link rel="icon" type="image/png" href="data:image/png;base64,{{faviconData}}" sizes="32x32">{{else if faviconUrl }}<link rel="icon" type="image/png" href="{{faviconUrl}}" sizes="32x32">{{else if faviconImageName }}<link rel="icon" type="image/png" href="images/{{faviconImageName}}" sizes="32x32">{{/if}}
<title>{{firstTruthy info.x-htmlTitle info.title "GraphQL API Reference"}}</title>
{{>layout/links-scripts}}
8 changes: 6 additions & 2 deletions src/themes/default/views/partials/layout/page.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
<div id="sidebar">
<div class="sidebar-top-container">
<div id="logo">
{{#if logo}}
<img src="images/{{logo}}" title="{{info.title}}" />
{{#if logoData }}
<img src="data:image/png;base64,{{logoData}}" title="{{info.title}}" />
{{else if logoUrl}}
<img src="{{logoUrl}}" title="{{info.title}}" />
{{else if logoImageName}}
<img src="images/{{logoImageName}}" title="{{info.title}}" />
{{/if}}
</div>
<button class="close-button" type="button">
Expand Down
62 changes: 61 additions & 1 deletion test/src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ describe('index', function () {
embeddable: false,
oneFile: false,
resolveWithOutput: true,
embedLogo: false,
embedFavicon: false,
})

expect(options.targetDir.endsWith('/public')).to.be.true
Expand Down Expand Up @@ -68,7 +70,7 @@ describe('index', function () {

def('options', () => $._options)

def('config', () => ({
def('configBase', () => ({
spectaql: {
embeddable: true,
oneFile: true,
Expand All @@ -87,6 +89,8 @@ describe('index', function () {
},
}))

def('config', () => $.configBase)

it('uses config overrides and does not resolve certain things as paths', function () {
const resolveOptions = index.__get__('resolveOptions')
const options = resolveOptions($.options)
Expand Down Expand Up @@ -140,6 +144,62 @@ describe('index', function () {
expect(options.themeDir.endsWith('my-custom-theme-yo-yo')).to.be.true
})
})

describe('logo and favicon stuff', function () {
def('config', () => {
const configBase = $.configBase
configBase.spectaql = {
...configBase.spectaql,
...$.spectaqlOptions,
}

return configBase
})

context('logoFile and faviconFile are provided', function () {
def('spectaqlOptions', () => ({
logoFile: './test/fixtures/logo.png',
faviconFile: './test/fixtures/favicon.png',
}))

it('only outputs the file paths', async function () {
const resolveOptions = index.__get__('resolveOptions')
const options = resolveOptions($.options)
expect(
options.logoFile.endsWith('test/fixtures/logo.png')
).to.be.true
expect(
options.faviconFile.endsWith('test/fixtures/favicon.png')
).to.be.true

expect(options.logoData).to.not.be.ok
expect(options.faviconData).to.not.be.ok
})

context('embedLogo and embedFavicon are true', function () {
def('spectaqlOptions', () => ({
logoFile: './test/fixtures/logo.png',
embedLogo: true,
faviconFile: './test/fixtures/favicon.png',
embedFavicon: true,
}))

it('only outputs the file base64 data', async function () {
const resolveOptions = index.__get__('resolveOptions')
const options = resolveOptions($.options)
expect(
options.logoFile.endsWith('test/fixtures/logo.png')
).to.be.true
expect(
options.faviconFile.endsWith('test/fixtures/favicon.png')
).to.be.true

expect(options.logoData).to.be.ok
expect(options.faviconData).to.be.ok
})
})
})
})
})
})
})

0 comments on commit fa25c84

Please sign in to comment.