diff --git a/munge_aggregates.js b/munge_aggregates.js index 732e9cd42..26cbc52f4 100644 --- a/munge_aggregates.js +++ b/munge_aggregates.js @@ -18,6 +18,51 @@ if (!dbFile || !hugoOutput) { process.exit(1); } +/** + * @param {string} u a spec URL like "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header" + * @returns the spec's parent, or "null" if it's a top-level spec + */ +const computeParent = (u) => { + const url = new URL(u); + const segments = url.pathname.split('/').filter(Boolean); + + // if there's a hash, consider it as a segment + if (url.hash) segments.push(url.hash.substring(1)); + + if (segments.length <= 1) { + return "null"; + } + + const parent = segments.slice(0, -1).join('/'); + return `${url.protocol}//${url.host}/${parent}` +}; + +/** + * @param {string} u a spec URL like "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header" + * @returns the spec's name, or the hash if it's a top-level spec and whether it was found in a hash + */ +const computeName = (u) => { + const url = new URL(u); + + if (url.hash) { + return { + isHashed: true, + name: url.hash.substring(1), + }; + } + + const segments = url.pathname.split('/').filter(Boolean); + + if (segments.length === 0) { + throw new Error(`Invalid spec URL: ${u}`); + } + + return { + isHashed: false, + name: segments[segments.length - 1], + }; +}; + const main = async () => { let db = new sqlite3.Database(dbFile, (err) => { if (err) { @@ -58,6 +103,8 @@ const main = async () => { `; const testsRows = await all(testsQuery); const groups = {}; + const flatTestGroups = {}; // used for specs generation. + for (const row of testsRows) { const { versions, full_name, name, parent_test_full_name } = row; const slug = slugify(full_name); @@ -66,10 +113,115 @@ const main = async () => { groups[parent_test_full_name] = {}; } - groups[parent_test_full_name][full_name] = { versions: versions?.split(',') || [], name, full_name, slug }; + const g = { versions: versions?.split(',') || [], name, full_name, slug }; + + groups[parent_test_full_name][full_name] = g; + flatTestGroups[full_name] = g; } outputJSON("data/testgroups.json", groups); + // Query to fetch all test specs + const specsQuery = ` + SELECT + spec_url as full_name, + GROUP_CONCAT(DISTINCT test_run_version) AS versions + FROM TestSpecs + GROUP BY full_name + ORDER BY full_name + `; + const specsRows = await all(specsQuery); + const specs = {}; + const flatSpecs = {}; + + for (const row of specsRows) { + const { versions, full_name } = row; + let current = full_name; + + while (current !== "null") { + const slug = slugify(current); + const parent = computeParent(current); + const { name, isHashed } = computeName(current) + + if (!specs[parent]) { + specs[parent] = {}; + } + + flatSpecs[current] = true + + specs[parent][current] = { + versions: versions?.split(',') || [], + spec_full_name: current, + slug, + name, + isHashed, + }; + + current = parent; + } + } + outputJSON("data/specs.json", specs); + + const descendTheSpecsTree = (current, path) => { + Object.entries(specs[current] || {}) + .forEach(([key, spec]) => { + const addSpecs = (current) => { + let hashes = [...(current.specs || []), spec.name]; + hashes = [...new Set(hashes)]; // deduplicate + return { ...current, hashes } + }; + + // To reproduce the structure of URLs and hashes, we update existing specs pages + if (spec.isHashed) { + const p = path.join("/"); + outputFrontmatter( + `content/specs/${p}/_index.md`, + addSpecs + ); + // We assume there are no recursion / children for hashes + return + } + + const newPath = [...path, spec.name]; + const p = newPath.join("/"); + + outputFrontmatter(`content/specs/${p}/_index.md`, { + ...spec, + title: spec.name, + }); + + descendTheSpecsTree(key, newPath); + }) + } + + descendTheSpecsTree("null", []) + + // Aggregate test results per specs + const specsTestGroups = {}; + + for (const fullName of Object.keys(flatSpecs)) { + // list all the test names for a given spec. + // we prefix search the database for spec_urls starting with the spec name + const specsQuery = ` + SELECT + test_full_name + FROM TestSpecs + WHERE spec_url LIKE ? + ORDER BY test_full_name + `; + const tests = await all(specsQuery, [fullName + '%']); + + const s = tests.map(x => x.test_full_name) + .reduce((acc, name) => { + return { + ...acc, + [name]: flatTestGroups[name] + } + }, {}); + specsTestGroups[fullName] = s; + } + + outputJSON("data/specsgroups.json", specsTestGroups); + // Query to fetch all stdouts const logsQuery = ` SELECT @@ -269,6 +421,7 @@ const slugify = (str) => { .replace(/_+/g, '_') // remove consecutive underscores .replace(/[\/]/g, "__") .replace(/[^a-z0-9 -]/g, '-') // remove non-alphanumeric characters + .replace(/-+/g, '-') // remove consecutive dashes } const outputJSON = (p, data) => { @@ -283,7 +436,7 @@ const outputJSON = (p, data) => { fs.writeFileSync(fullPath, json); } -const outputFrontmatter = (p, data) => { +const outputFrontmatter = (p, dataOrUpdate) => { const fullPath = `${hugoOutput}/${p}`; // TODO: implement update frontmatter @@ -303,7 +456,12 @@ const outputFrontmatter = (p, data) => { content.content = existing.content; content.data = existing.data; } - content.data = { ...content.data, ...data }; + + if (typeof dataOrUpdate === "function") { + content.data = dataOrUpdate(content.data); + } else { + content.data = { ...content.data, ...dataOrUpdate }; + } const md = matter.stringify(content.content, content.data); fs.writeFileSync(fullPath, md); @@ -322,4 +480,4 @@ main() .catch((e) => { console.error(e); process.exit(1); - }) \ No newline at end of file + }) diff --git a/munge_sql.js b/munge_sql.js index c45773f44..601cdefe7 100644 --- a/munge_sql.js +++ b/munge_sql.js @@ -100,6 +100,28 @@ const main = async () => { ); `) + // Create the SPECS + await run(` + CREATE TABLE IF NOT EXISTS TestSpecs ( + test_run_implementation_id TEXT, + test_run_version TEXT, + test_full_name TEXT, + + spec_url TEXT, + + PRIMARY KEY (test_run_implementation_id, test_run_version, test_full_name, spec_url), + + -- test run + FOREIGN KEY (test_run_implementation_id, test_run_version) + REFERENCES TestRun (implementation_id, version), + + -- test result + FOREIGN KEY (test_run_implementation_id, test_run_version, test_full_name) + REFERENCES TestResult (test_run_implementation_id, test_run_version, full_name) + ); + `); + + for (const file of files) { const fileName = file.split("/").slice(-1)[0].split(".")[0]; const implemId = fileName; @@ -146,6 +168,16 @@ const main = async () => { VALUES (?, ?, ?, ?) `, [implemId, version, fullName, test.output]); + const specsArray = test.meta?.specs || []; + for (const specUrl of specsArray) { + // add `https://` if the specs don't have it + const cleanSpecUrl = specUrl.startsWith("http") ? specUrl : `https://${specUrl}`; + + await run(` + INSERT INTO TestSpecs (test_run_implementation_id, test_run_version, test_full_name, spec_url) + VALUES (?, ?, ?, ?) + `, [implemId, version, fullName, cleanSpecUrl]); + } } } diff --git a/tests/path_gateway_dag_test.go b/tests/path_gateway_dag_test.go index 9c4d5e42a..2c4c9828e 100644 --- a/tests/path_gateway_dag_test.go +++ b/tests/path_gateway_dag_test.go @@ -42,7 +42,7 @@ func TestGatewayJsonCbor(t *testing.T) { }, { Name: "GET UnixFS file with JSON bytes is returned with application/json Content-Type - with headers", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#accept-request-header", Hint: ` ## Quick regression check for JSON stored on UnixFS: ## it has nothing to do with DAG-JSON and JSON codecs, @@ -479,7 +479,7 @@ func TestNativeDag(t *testing.T) { Response: Expect(). Headers( Header("Content-Type").Hint("expected Content-Type").Equals("application/vnd.ipld.dag-{{format}}", row.Format), - Header("Content-Length").Spec("specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header").Hint("includes Content-Length").Equals("{{length}}", len(dagTraversal.RawData())), + Header("Content-Length").Spec("https://specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header").Hint("includes Content-Length").Equals("{{length}}", len(dagTraversal.RawData())), Header("Content-Disposition").Hint("includes Content-Disposition").Contains(`{{disposition}}; filename="{{cid}}.{{format}}"`, row.Disposition, dagTraversalCID, row.Format), Header("X-Content-Type-Options").Hint("includes nosniff hint").Contains("nosniff"), ), @@ -553,7 +553,7 @@ func TestNativeDag(t *testing.T) { }, { Name: Fmt("HEAD {{name}} with only-if-cached for missing block returns HTTP 412 Precondition Failed", row.Name), - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#only-if-cached", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#only-if-cached", Request: Request(). Path("/ipfs/{{cid}}", missingCID). Header("Cache-Control", "only-if-cached"). diff --git a/tests/path_gateway_tar_test.go b/tests/path_gateway_tar_test.go index d6131d0c7..15f5c3805 100644 --- a/tests/path_gateway_tar_test.go +++ b/tests/path_gateway_tar_test.go @@ -76,7 +76,7 @@ func TestTar(t *testing.T) { }, { Name: "GET TAR with explicit ?filename= succeeds with modified Content-Disposition header", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#content-disposition-response-header", Request: Request(). Path("/ipfs/{{cid}}", dirCID). Query("filename", "testтест.tar"). diff --git a/tests/path_gateway_unixfs_test.go b/tests/path_gateway_unixfs_test.go index c0e6fc500..ae3705d82 100644 --- a/tests/path_gateway_unixfs_test.go +++ b/tests/path_gateway_unixfs_test.go @@ -202,7 +202,7 @@ func TestGatewayCache(t *testing.T) { // ========== { Name: "GET for /ipfs/ file with matching Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()). Headers( @@ -213,7 +213,7 @@ func TestGatewayCache(t *testing.T) { }, { Name: "GET for /ipfs/ dir with index.html file with matching Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/root4/", fixture.MustGetCid()). Headers( @@ -224,7 +224,7 @@ func TestGatewayCache(t *testing.T) { }, { Name: "GET for /ipfs/ file with matching third Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()). Headers( @@ -235,7 +235,7 @@ func TestGatewayCache(t *testing.T) { }, { Name: "GET for /ipfs/ file with matching weak Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()). Headers( @@ -246,7 +246,7 @@ func TestGatewayCache(t *testing.T) { }, { Name: "GET for /ipfs/ file with wildcard Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/root4/index.html", fixture.MustGetCid()). Headers( @@ -257,7 +257,7 @@ func TestGatewayCache(t *testing.T) { }, { Name: "GET for /ipfs/ dir listing with matching weak Etag in If-None-Match returns 304 Not Modified", - Spec: "specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", + Spec: "https://specs.ipfs.tech/http-gateways/path-gateway/#if-none-match-request-header", Request: Request(). Path("/ipfs/{{cid}}/root2/root3/", fixture.MustGetCid()). Headers( diff --git a/tests/redirects_file_test.go b/tests/redirects_file_test.go index 9322c74ff..64e46d246 100644 --- a/tests/redirects_file_test.go +++ b/tests/redirects_file_test.go @@ -15,7 +15,7 @@ import ( ) func TestRedirectsFileSupport(t *testing.T) { - tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/web-redirects-file/") + tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/web-redirects-file/") fixture := car.MustOpenUnixfsCar("redirects_file/redirects.car") redirectDir := fixture.MustGetNode("examples") redirectDirCID := redirectDir.Base32Cid() @@ -166,8 +166,8 @@ func TestRedirectsFileSupport(t *testing.T) { Contains("could not parse _redirects:"), Contains(`forced redirects (or "shadowing") are not supported`), ), - ).Spec("specs.ipfs.tech/http-gateways/web-redirects-file/#no-forced-redirects"), - Spec: "specs.ipfs.tech/http-gateways/web-redirects-file/#error-handling", + ).Spec("https://specs.ipfs.tech/http-gateways/web-redirects-file/#no-forced-redirects"), + Spec: "https://specs.ipfs.tech/http-gateways/web-redirects-file/#error-handling", }, { Name: "invalid file: request for $TOO_LARGE_REDIRECTS_DIR_HOSTNAME/not-found returns error about too large redirects file", @@ -182,7 +182,7 @@ func TestRedirectsFileSupport(t *testing.T) { Contains("redirects file size cannot exceed"), ), ), - Spec: "specs.ipfs.tech/http-gateways/web-redirects-file/#max-file-size", + Spec: "https://specs.ipfs.tech/http-gateways/web-redirects-file/#max-file-size", }, }...) diff --git a/tests/trustless_gateway_car_test.go b/tests/trustless_gateway_car_test.go index 5a5114c63..5f0aa7e3a 100644 --- a/tests/trustless_gateway_car_test.go +++ b/tests/trustless_gateway_car_test.go @@ -408,7 +408,7 @@ func TestTrustlessCarDagScopeAll(t *testing.T) { func TestTrustlessCarEntityBytes(t *testing.T) { tooling.LogTestGroup(t, GroupBlockCar) - tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/trustless-gateway/#entity-bytes-request-query-parameter") + tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/trustless-gateway/#entity-bytes-request-query-parameter") singleLayerHamtMultiBlockFilesFixture := car.MustOpenUnixfsCar("trustless_gateway_car/single-layer-hamt-with-multi-block-files.car") subdirWithMixedBlockFiles := car.MustOpenUnixfsCar("trustless_gateway_car/subdir-with-mixed-block-files.car") diff --git a/tests/trustless_gateway_raw_test.go b/tests/trustless_gateway_raw_test.go index 8bef78ea4..2ba80f869 100644 --- a/tests/trustless_gateway_raw_test.go +++ b/tests/trustless_gateway_raw_test.go @@ -14,7 +14,7 @@ import ( func TestTrustlessRaw(t *testing.T) { tooling.LogTestGroup(t, GroupBlockCar) - tooling.LogSpecs(t, "specs.ipfs.tech/http-gateways/trustless-gateway/#block-responses-application-vnd-ipld-raw") + tooling.LogSpecs(t, "https://specs.ipfs.tech/http-gateways/trustless-gateway/#block-responses-application-vnd-ipld-raw") fixture := car.MustOpenUnixfsCar("gateway-raw-block.car") diff --git a/www/content/_index.md b/www/content/_index.md index 631f9a21f..9f3d9c2a5 100644 --- a/www/content/_index.md +++ b/www/content/_index.md @@ -11,28 +11,26 @@ menu: }}" class="w-32 h-32" />

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt sagittis arcu, in tempus nisi molestie at. Suspendisse imperdiet viverra fringilla. Sed eleifend elementum sem. Phasellus orci lectus, laoreet in sapien vulputate, bibendum cursus neque. Quisque luctus dictum ligula, sit amet sagittis lacus consectetur eget. Phasellus non diam sem. Duis pellentesque tellus quis dolor sodales, vitae faucibus nulla ornare. Proin eget odio eu orci tristique volutpat. Nunc non vehicula neque. Maecenas volutpat mollis sem eget vestibulum. -

+ +IPFS Gateway Conformance - a vendor-agnostic gateway conformance test suite for users and implementers of IPFS Gateways. We ensure compliance with [specs.ipfs.tech](https://specs.ipfs.tech/http-gateways/). Find us on [Github](https://github.com/ipfs/gateway-conformance). + +

-
+
}}" - class="no-underline bg-blue-200 hover:bg-blue-600 text-blue-800 hover:text-blue-200 text-xl px-8 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-200 focus:ring-offset-2"> - Current Dashboard + class="no-underline bg-blue-200 hover:bg-blue-600 text-blue-800 hover:text-blue-200 text-xl px-8 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-200 focus:ring-offset-2"> + Testing Dashboard + + }}" + class="no-underline bg-blue-200 hover:bg-blue-600 text-blue-800 hover:text-blue-200 text-xl px-8 py-4 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-200 focus:ring-offset-2"> + IPFS Specs Dashboard
-## List of Gateway Implementation Tested - -{{< gateways-links >}} - -## List of Specs Tested - -{{< specs-links >}} - -## Related Projects: +## Related - [Conformance Test Suite](https://github.com/ipfs/gateway-conformance) - [IPFS Specs](https://specs.ipfs.tech) diff --git a/www/content/specs/_index.md b/www/content/specs/_index.md index 5820da4cb..0ac06476f 100644 --- a/www/content/specs/_index.md +++ b/www/content/specs/_index.md @@ -1,4 +1,3 @@ --- +title: Specification Dashboard --- - -This is the entry-point for the conformance gateway dashboard when coming from the specs. \ No newline at end of file diff --git a/www/themes/conformance/assets/style.css b/www/themes/conformance/assets/style.css index c399cc1ac..4fd839cdb 100644 --- a/www/themes/conformance/assets/style.css +++ b/www/themes/conformance/assets/style.css @@ -573,6 +573,14 @@ video { margin-bottom: auto; } +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + .ml-4 { margin-left: 1rem; } @@ -589,6 +597,14 @@ video { margin-top: 4rem; } +.mt-2 { + margin-top: 0.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + .block { display: block; } @@ -609,6 +625,10 @@ video { display: table; } +.grid { + display: grid; +} + .hidden { display: none; } @@ -669,6 +689,10 @@ video { table-layout: fixed; } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + .flex-col { flex-direction: column; } @@ -685,6 +709,10 @@ video { justify-content: space-between; } +.gap-4 { + gap: 1rem; +} + .gap-x-2 { -moz-column-gap: 0.5rem; column-gap: 0.5rem; @@ -736,6 +764,10 @@ video { border-radius: 0.25rem; } +.rounded-full { + border-radius: 9999px; +} + .rounded-lg { border-radius: 0.5rem; } @@ -802,6 +834,10 @@ video { padding: 0.375rem; } +.p-4 { + padding: 1rem; +} + .p-6 { padding: 1.5rem; } @@ -884,6 +920,21 @@ video { text-align: right; } +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-5xl { + font-size: 3rem; + line-height: 1; +} + .text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -899,6 +950,10 @@ video { line-height: 1rem; } +.font-bold { + font-weight: 700; +} + .font-medium { font-weight: 500; } @@ -920,6 +975,16 @@ video { color: rgb(245 158 11 / var(--tw-text-opacity)); } +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + .text-blue-800 { --tw-text-opacity: 1; color: rgb(30 64 175 / var(--tw-text-opacity)); @@ -969,12 +1034,46 @@ video { text-decoration-line: none; } +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .shadow-sm { --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring-1 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-inset { + --tw-ring-inset: inset; +} + +.ring-blue-700\/10 { + --tw-ring-color: rgb(29 78 216 / 0.1); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + article { line-height: 1.625; --tw-text-opacity: 1; @@ -1169,6 +1268,11 @@ article th { background-color: rgb(37 99 235 / var(--tw-bg-opacity)); } +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + .hover\:bg-slate-200:hover { --tw-bg-opacity: 1; background-color: rgb(226 232 240 / var(--tw-bg-opacity)); @@ -1179,6 +1283,11 @@ article th { color: rgb(191 219 254 / var(--tw-text-opacity)); } +.hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + .hover\:text-gray-500:hover { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity)); @@ -1224,11 +1333,29 @@ article th { } } +@media (min-width: 768px) { + .md\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + @media (min-width: 1024px) { .lg\:mt-24 { margin-top: 6rem; } + .lg\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; diff --git a/www/themes/conformance/layouts/partials/breadcrumbs.html b/www/themes/conformance/layouts/partials/breadcrumbs.html index 04ff8e9a5..bf6652897 100644 --- a/www/themes/conformance/layouts/partials/breadcrumbs.html +++ b/www/themes/conformance/layouts/partials/breadcrumbs.html @@ -1,60 +1,49 @@ -
-
+ +{{ end }} + +
+
diff --git a/www/themes/conformance/layouts/partials/result-table.html b/www/themes/conformance/layouts/partials/result-table.html index 46378a927..7a6efb0f5 100644 --- a/www/themes/conformance/layouts/partials/result-table.html +++ b/www/themes/conformance/layouts/partials/result-table.html @@ -19,6 +19,18 @@ {{ $requestedTestGroup := (default "null" .full_name) }} {{ $requestedVersion := (default "" .version) }} {{ $requestedImplem := (default "" .implementation_id) }} +{{ $requestedSpecs := (default "" .spec_full_name) }} + +{{ $testGroups := (index site.Data.testgroups $requestedTestGroup) }} + +{{/* When you have a request specs, list all the test groups it defines */}} +{{ if (isset . "spec_full_name") }} + {{ if (isset . "full_name") }} + {{ errorf "Cannot specify both 'full_name' and 'spec_full_name' in the same shortcode" }} + {{ end }} + + {{ $testGroups = (index site.Data.specsgroups $requestedSpecs) }} +{{ end }} @@ -95,7 +107,7 @@ {{ end }} {{ end }} - {{ range $name, $content := (index site.Data.testgroups $requestedTestGroup) }} + {{ range $name, $content := $testGroups }}
+ {{ range $name, $details := site.Data.specs.null }} +
  • + + {{ $name }} + +
  • + {{ end }} + diff --git a/www/themes/conformance/layouts/specs/list.html b/www/themes/conformance/layouts/specs/list.html new file mode 100644 index 000000000..8fb9a618b --- /dev/null +++ b/www/themes/conformance/layouts/specs/list.html @@ -0,0 +1,86 @@ +{{ define "specpill" }} + + specs + +{{ end }} + + +{{define "main"}} + +
    +

    + {{.Title}} + {{ template "specpill" (default "https://specs.ipfs.tech/" .Params.spec_full_name) }} +

    + + {{.TableOfContents}} + + {{.Content}} + + {{ $params := .Params }} + + + {{ with .Params.hashes }} +
    + + {{ range . }} + {{ $newSpec := (printf "%s/#%s" $params.spec_full_name .) }} + +
    +

    + #{{ . }} + {{ template "specpill" $newSpec }} +

    + + {{ partial "result-table.html" (merge $params (dict "spec_full_name" $newSpec))}} +
    + {{ end }} +
    + {{ end }} + + + {{ if (gt (len .Data.Pages) 0) }} +

    Sub-Specs

    +
    + + {{ range .Data.Pages }} + + {{ .Title }} + + {{ end }} +
    + {{ end }} + + +

    + Main Dashboard +

    +
    + {{ partial "result-table.html" .Params }} +
    + +
    + + + + + +{{with .PrevInSection}}Prev {{.Title}}{{end}} +{{with .NextInSection}}Next {{.Title}}{{end}} + + +{{$related := .Site.RegularPages.Related . | first 5}} {{with $related}} +

    See Also

    + +{{end}} {{end}}