Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/components/FeatureList/availability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Whether a feature exists in the docs version the reader is on.
*
* features.json records the SDK version a feature was introduced in ("8.2"),
* and one shared file serves every docs version. A frozen version therefore
* lists features that did not exist in it — 86 framework entries are newer than
* 7.6, for instance. That was harmless while their links pointed at the
* unversioned API reference, which always documents the newest release. Once
* the links resolve against the reader's own line, each of those becomes a 404:
* /7.6/data-capture-sdk/… has no page for a class added in 8.2.
*/

/** (major, minor) of a version string, or null if it is not one. */
function versionParts(version: string): [number, number] | null {
const match = /^(\d+)\.(\d+)/.exec(version);
return match ? [Number(match[1]), Number(match[2])] : null;
}

/**
* True if a feature introduced in `introducedIn` exists in `docsVersion`.
*
* Compared numerically PER COMPONENT. "6.5" is earlier than "6.28", which both
* a string comparison ("6.5" > "6.28") and a float one (6.5 > 6.28) get
* backwards — and 6.28 is a real docs version, so that is not a hypothetical.
*
* `current` shows everything: the in-development docs describe the newest SDK.
* An unparseable version on either side shows the feature rather than hiding
* it, so a data-entry slip degrades to the behaviour that existed before this
* check rather than silently emptying a table.
*/
export function featureExistsIn(
introducedIn: string | undefined,
docsVersion: string | undefined,
): boolean {
if (!introducedIn || introducedIn === 'n/a') return false;
if (!docsVersion || docsVersion === 'current') return true;

const introduced = versionParts(introducedIn);
const docs = versionParts(docsVersion);
if (!introduced || !docs) return true;

const [introducedMajor, introducedMinor] = introduced;
const [docsMajor, docsMinor] = docs;
return (
introducedMajor < docsMajor ||
(introducedMajor === docsMajor && introducedMinor <= docsMinor)
);
}
45 changes: 30 additions & 15 deletions src/components/FeatureList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { FeatureListProps, Feature, Product, FilteredFeature } from './types';
import productsData from '@site/src/data/products.json';
import featuresData from '@site/src/data/features.json';
import { frameworkFromPath } from '@site/src/constants/frameworks';
import { withCurrentDocsPath } from '@site/src/constants/docsPaths';
import { apiReferenceUrl } from '@site/src/constants/docsPaths';
import { featureExistsIn } from './availability';
import { useActiveDocContext } from '@docusaurus/plugin-content-docs/client';

// Function to render description with inline code formatting
const renderDescription = (description: string) => {
Expand All @@ -31,6 +33,9 @@ const FeatureList: React.FC<FeatureListProps> = ({
className = ''
}) => {
const { siteConfig } = useDocusaurusContext();
// Which docs version the reader is on, so API links point at that version's
// API-reference line rather than at whatever released most recently.
const activeVersionName = useActiveDocContext(undefined)?.activeVersion?.name;
const [filteredFeatures, setFilteredFeatures] = useState<FilteredFeature[]>([]);
const [isLoading, setIsLoading] = useState(true);

Expand Down Expand Up @@ -65,19 +70,25 @@ const FeatureList: React.FC<FeatureListProps> = ({
// Filter by tag
if (tag && feature.tag !== tag) return false;

// Filter by framework availability if framework is specified
// Filter by framework availability if framework is specified.
// Availability is version-aware: a feature added in 8.2 does not exist
// on a 7.6 page, and now that its API link resolves against 7.6 rather
// than the unversioned reference, showing it would give a 404.
if (currentFramework) {
const frameworkInfo = feature.frameworks[currentFramework];
if (!frameworkInfo || frameworkInfo.version === 'n/a') {
return false; // Don't show features not available for this framework
if (!featureExistsIn(frameworkInfo?.version, activeVersionName)) {
return false;
}
}

return true;
})
.map((feature: Feature) => {
const isAvailable = currentFramework
? feature.frameworks[currentFramework] && feature.frameworks[currentFramework].version !== 'n/a'
const isAvailable = currentFramework
? featureExistsIn(
feature.frameworks[currentFramework]?.version,
activeVersionName,
)
: true;

return {
Expand Down Expand Up @@ -127,10 +138,12 @@ const FeatureList: React.FC<FeatureListProps> = ({

if (currentFramework && feature.frameworks[currentFramework]) {
const frameworkInfo = feature.frameworks[currentFramework];
// products.json stores version-agnostic /sdks/<framework>/... paths;
// frameworks that only exist in the unreleased docs need the current
// version prefix or they resolve into the released tree and 404.
return frameworkInfo.apiUrl ? withCurrentDocsPath(frameworkInfo.apiUrl) : null;
// features.json stores the path WITHIN the API reference; the version is
// applied here, because one shared data file serves every docs version
// and there is no single correct prefix to bake into it.
return frameworkInfo.apiUrl
? apiReferenceUrl(frameworkInfo.apiUrl, activeVersionName)
: null;
}
return null;
};
Expand Down Expand Up @@ -198,24 +211,26 @@ const FeatureList: React.FC<FeatureListProps> = ({
<td>
<div className={styles.frameworkList}>
{Object.entries(feature.frameworks)
.filter(([, info]) => info.version !== 'n/a')
.filter(([, info]) =>
featureExistsIn(info.version, activeVersionName),
)
.map(([frameworkName, frameworkInfo]) => (
// The ELEMENT changes, not just the href. Guarding the
// href alone rendered the same DOM - withCurrentDocsPath
// returns undefined for undefined and React omits the
// href alone rendered the same DOM - the URL helper
// returned undefined for undefined and React omits the
// attribute - so the chip stayed an <a> with no href:
// not keyboard-focusable, navigating nowhere, still
// showing the link hover. 17 cells in features.json
// carry no apiUrl, of which 16 reach this branch - the
// 17th is Web / 7-Segment Display, filtered out earlier
// by version === "n/a".
// by featureExistsIn (version "n/a").
// Latent only while every usage is compact mode, which
// skips this column. Same shape as the feature-name cell
// above, which swaps <a> for <h4>.
frameworkInfo.apiUrl ? (
<a
key={frameworkName}
href={withCurrentDocsPath(frameworkInfo.apiUrl)}
href={apiReferenceUrl(frameworkInfo.apiUrl, activeVersionName)}
className={styles.frameworkItem}
target="_blank"
rel="noopener noreferrer"
Expand Down
37 changes: 37 additions & 0 deletions src/constants/docsPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,40 @@ export function withCurrentDocsPath(path: string): string {
if (!match || !isUnreleasedFramework(match[1])) return path;
return `${CURRENT_DOCS_PATH}${path}`;
}

/** Where the API reference is served from. */
const DOCS_HOST = "https://docs.scandit.com";

/**
* Builds an API-reference URL for the docs version the reader is on.
*
* publish_platform() in data-capture-sdk publishes the API reference per
* major.minor LINE — /7.6/data-capture-sdk/… — plus one unversioned copy at
* /data-capture-sdk/… that tracks whatever shipped most recently.
*
* So the version matters. A frozen version linking the unversioned copy
* documents whatever released last rather than itself: during a beta window the
* released version sits at the site root while the beta owns the unversioned
* tree, and the reader on the release gets the beta's API. The current version
* links the unversioned copy, which is the convention its Markdown uses too.
*
* Data files therefore store the path WITHIN the API reference, and the version
* is applied here at render time. They cannot store the full URL: one shared
* file serves every version, so there is no single correct prefix to bake in.
*
* ("ios/…", "current") -> https://docs.scandit.com/data-capture-sdk/ios/…
* ("ios/…", "7.6.14") -> https://docs.scandit.com/7.6/data-capture-sdk/ios/…
* ("https://…", …) -> unchanged, for any entry still holding a full URL
*/
export function apiReferenceUrl(path: string, versionName?: string): string {
if (!path) return path;
if (/^https?:\/\//.test(path)) return path;
const withinReference = path.replace(/^\//, "");
const line =
versionName && versionName !== "current"
? versionName.match(/^(\d+\.\d+)\./)?.[1]
: undefined;
return line
? `${DOCS_HOST}/${line}/data-capture-sdk/${withinReference}`
: `${DOCS_HOST}/data-capture-sdk/${withinReference}`;
}
Loading
Loading