Skip to content

Commit 29118e1

Browse files
authored
Add redesign feature flag (#392)
* Set up tailwindcss * Set up theme * Fix NpmRunBuild condition * Remove condition again * Add redesign feature flag and basic hljs setup * Remove cssnano and fix package.json * Revert * Revert
1 parent 30905b4 commit 29118e1

19 files changed

+1775
-104
lines changed

src/Elastic.Markdown/.postcssrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": {
3+
"@tailwindcss/postcss": {},
4+
"postcss-import": {},
5+
}
6+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
export const mergeHTMLPlugin = (function () {
2+
'use strict';
3+
4+
var originalStream;
5+
6+
/**
7+
* @param {string} value
8+
* @returns {string}
9+
*/
10+
function escapeHTML(value) {
11+
return value
12+
.replace(/&/g, '&')
13+
.replace(/</g, '&lt;')
14+
.replace(/>/g, '&gt;')
15+
.replace(/"/g, '&quot;')
16+
.replace(/'/g, '&#x27;');
17+
}
18+
19+
/* plugin itself */
20+
21+
/** @type {HLJSPlugin} */
22+
const mergeHTMLPlugin = {
23+
// preserve the original HTML token stream
24+
"before:highlightElement": ({ el }) => {
25+
originalStream = nodeStream(el);
26+
},
27+
// merge it afterwards with the highlighted token stream
28+
"after:highlightElement": ({ el, result, text }) => {
29+
if (!originalStream.length) return;
30+
31+
const resultNode = document.createElement('div');
32+
resultNode.innerHTML = result.value;
33+
result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
34+
el.innerHTML = result.value;
35+
}
36+
};
37+
38+
/* Stream merging support functions */
39+
40+
/**
41+
* @typedef Event
42+
* @property {'start'|'stop'} event
43+
* @property {number} offset
44+
* @property {Node} node
45+
*/
46+
47+
/**
48+
* @param {Node} node
49+
*/
50+
function tag(node) {
51+
return node.nodeName.toLowerCase();
52+
}
53+
54+
/**
55+
* @param {Node} node
56+
*/
57+
function nodeStream(node) {
58+
/** @type Event[] */
59+
const result = [];
60+
(function _nodeStream(node, offset) {
61+
for (let child = node.firstChild; child; child = child.nextSibling) {
62+
if (child.nodeType === 3) {
63+
offset += child.nodeValue.length;
64+
} else if (child.nodeType === 1) {
65+
result.push({
66+
event: 'start',
67+
offset: offset,
68+
node: child
69+
});
70+
offset = _nodeStream(child, offset);
71+
// Prevent void elements from having an end tag that would actually
72+
// double them in the output. There are more void elements in HTML
73+
// but we list only those realistically expected in code display.
74+
if (!tag(child).match(/br|hr|img|input/)) {
75+
result.push({
76+
event: 'stop',
77+
offset: offset,
78+
node: child
79+
});
80+
}
81+
}
82+
}
83+
return offset;
84+
})(node, 0);
85+
return result;
86+
}
87+
88+
/**
89+
* @param {any} original - the original stream
90+
* @param {any} highlighted - stream of the highlighted source
91+
* @param {string} value - the original source itself
92+
*/
93+
function mergeStreams(original, highlighted, value) {
94+
let processed = 0;
95+
let result = '';
96+
const nodeStack = [];
97+
98+
function selectStream() {
99+
if (!original.length || !highlighted.length) {
100+
return original.length ? original : highlighted;
101+
}
102+
if (original[0].offset !== highlighted[0].offset) {
103+
return (original[0].offset < highlighted[0].offset) ? original : highlighted;
104+
}
105+
106+
/*
107+
To avoid starting the stream just before it should stop the order is
108+
ensured that original always starts first and closes last:
109+
110+
if (event1 == 'start' && event2 == 'start')
111+
return original;
112+
if (event1 == 'start' && event2 == 'stop')
113+
return highlighted;
114+
if (event1 == 'stop' && event2 == 'start')
115+
return original;
116+
if (event1 == 'stop' && event2 == 'stop')
117+
return highlighted;
118+
119+
... which is collapsed to:
120+
*/
121+
return highlighted[0].event === 'start' ? original : highlighted;
122+
}
123+
124+
/**
125+
* @param {Node} node
126+
*/
127+
function open(node) {
128+
/** @param {Attr} attr */
129+
function attributeString(attr) {
130+
return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
131+
}
132+
// @ts-ignore
133+
result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>';
134+
}
135+
136+
/**
137+
* @param {Node} node
138+
*/
139+
function close(node) {
140+
result += '</' + tag(node) + '>';
141+
}
142+
143+
/**
144+
* @param {Event} event
145+
*/
146+
function render(event) {
147+
(event.event === 'start' ? open : close)(event.node);
148+
}
149+
150+
while (original.length || highlighted.length) {
151+
let stream = selectStream();
152+
result += escapeHTML(value.substring(processed, stream[0].offset));
153+
processed = stream[0].offset;
154+
if (stream === original) {
155+
/*
156+
On any opening or closing tag of the original markup we first close
157+
the entire highlighted node stack, then render the original tag along
158+
with all the following original tags at the same offset and then
159+
reopen all the tags on the highlighted stack.
160+
*/
161+
nodeStack.reverse().forEach(close);
162+
do {
163+
render(stream.splice(0, 1)[0]);
164+
stream = selectStream();
165+
} while (stream === original && stream.length && stream[0].offset === processed);
166+
nodeStack.reverse().forEach(open);
167+
} else {
168+
if (stream[0].event === 'start') {
169+
nodeStack.push(stream[0].node);
170+
} else {
171+
nodeStack.pop();
172+
}
173+
render(stream.splice(0, 1)[0]);
174+
}
175+
}
176+
return result + escapeHTML(value.substr(processed));
177+
}
178+
179+
return mergeHTMLPlugin;
180+
}());
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import hljs from "highlight.js";
2+
import {mergeHTMLPlugin} from "./hljs-merge-html-plugin";
3+
4+
hljs.registerLanguage('apiheader', function() {
5+
return {
6+
case_insensitive: true, // language is case-insensitive
7+
keywords: 'GET POST PUT DELETE HEAD OPTIONS PATCH',
8+
contains: [
9+
hljs.HASH_COMMENT_MODE,
10+
{
11+
className: "subst", // (pathname: path1/path2/dothis) color #ab5656
12+
begin: /(?<=(?:\/|GET |POST |PUT |DELETE |HEAD |OPTIONS |PATH))[^?\n\r\/]+/,
13+
}
14+
], }
15+
})
16+
17+
hljs.addPlugin(mergeHTMLPlugin);
18+
hljs.highlightAll();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#elastic-docs-v3 {
2+
h1 {
3+
@apply text-4xl text-ink font-semibold mb-6;
4+
line-height: 1.2em;
5+
letter-spacing: -0.04em;
6+
}
7+
8+
h2 {
9+
@apply text-2xl text-ink font-bold mb-6;
10+
line-height: 1.2em;
11+
letter-spacing: -0.02em;
12+
}
13+
14+
h3 {
15+
@apply text-xl text-ink font-bold mb-6;
16+
line-height: 1.2em;
17+
letter-spacing: -0.02em;
18+
}
19+
20+
p {
21+
@apply text-base text-body mb-6;
22+
line-height: 1.5em;
23+
letter-spacing: 0;
24+
}
25+
26+
a {
27+
@apply text-blue-elastic hover:underline underline-offset-4;
28+
}
29+
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
@import "legacy/pygments.css";
2-
@import "legacy/shibuya.css";
3-
@import "legacy/mystnb.css";
4-
@import "legacy/copybutton.css";
5-
@import "legacy/togglebutton.css";
6-
@import "legacy/sphinx-design.min.css";
7-
@import "legacy/custom.css";
8-
@import "legacy/atom-one-light.css";
1+
@import "tailwindcss";
2+
@import "highlight.js/styles/atom-one-dark.css";
3+
@import "./theme.css";
4+
@import "./markdown/typography.css";
5+
6+
main.markdown-content {
7+
max-width: 80ch;
8+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@theme {
2+
--color-*: initial;
3+
4+
--color-white: #FFFFFF;
5+
--color-black: #000000;
6+
7+
--color-body: #515151;
8+
9+
--color-ink: #343741;
10+
--color-ink-light: #535966;
11+
--color-ink-dark: #1C1E23;
12+
13+
--color-gray: #E6EBF2;
14+
--color-gray-light: #F5F7FA;
15+
--color-gray-dark: #D4DAE5;
16+
17+
--color-blue-elastic: #0077CC;
18+
--color-blue-sky: #36B9FF;
19+
--color-blue-midnight: #20377D;
20+
21+
--color-red-light: #FB6363;
22+
--color-red-dark: #D93333;
23+
24+
--color-green-light: #3CD278;
25+
--color-green-dark: #148742;
26+
27+
--color-teal: #00BFB3;
28+
--color-teal-light: #48EFCF;
29+
--color-teal-dark: #00857F;
30+
31+
--color-poppy: #FA744E;
32+
--color-poppy-light: #FF957D;
33+
--color-poppy-dark: #E2543D;
34+
35+
--color-pink: #F04E98;
36+
--color-pink-light: #F990C6;
37+
--color-pink-dark: #DD0A73;
38+
39+
--color-yellow: #FEC514;
40+
--color-yellow-light: #FFD836;
41+
--color-yellow-dark: #F9B110;
42+
43+
--spacing: 4px;
44+
}

src/Elastic.Markdown/Slices/Layout/_Head.cshtml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@
77
<link rel="index" title="Index" href="@Model.Link("genindex.html")"/>
88
<link rel="search" title="Search" href="@Model.Link("search.html")"/>
99
<link rel="next" title="Elastic content" href="elastic/index.html"/>
10-
<link rel="stylesheet" type="text/css" href="@Model.Static("styles.css")"/>
11-
<link media="print" rel="stylesheet" type="text/css" href="@Model.Static("print.css")"/>
10+
<link rel="stylesheet" type="text/css" href="@Model.Static("pygments.css")"/>
11+
<link rel="stylesheet" type="text/css" href="@Model.Static("shibuya.css")"/>
12+
<link rel="stylesheet" type="text/css" href="@Model.Static("mystnb.css")"/>
13+
<link rel="stylesheet" type="text/css" href="@Model.Static("copybutton.css")"/>
14+
<link rel="stylesheet" type="text/css" href="@Model.Static("togglebutton.css")"/>
15+
<link rel="stylesheet" type="text/css" href="@Model.Static("sphinx-design.min.css")"/>
16+
<link media="print" rel="stylesheet" type="text/css" href="@Model.Static("/_static/print.css")"/>
17+
<link rel="stylesheet" type="text/css" href="@Model.Static("custom.css")"/>
18+
<link rel="stylesheet" type="text/css" href="@Model.Static("atom-one-light.css")"/>
1219
<link rel="preconnect" href="https://fonts.googleapis.com">
1320
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1421
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
1522
<style>
16-
:root {
17-
--sy-f-text: "Inter", var(--sy-f-sys), var(--sy-f-cjk), sans-serif;
18-
--sy-f-heading: "Inter", var(--sy-f-sys), var(--sy-f-cjk), sans-serif;
19-
}
20-
</style>
23+
:root {
24+
--sy-f-text: "Inter", var(--sy-f-sys), var(--sy-f-cjk), sans-serif;
25+
--sy-f-heading: "Inter", var(--sy-f-sys), var(--sy-f-cjk), sans-serif;
26+
}
27+
</style>
2128
<meta property="og:type" content="website"/>
2229
<meta property="og:title" content="Elastic Docs v3"/>
2330
<meta name="twitter:card" content="summary"/>

src/Elastic.Markdown/Slices/_Layout.cshtml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,31 @@
11
@inherits RazorLayoutSlice<LayoutViewModel>
2+
@if (Model.IsRedesign)
3+
{
4+
<!DOCTYPE html>
5+
<html lang="en">
6+
<head>
7+
<title>@Model.Title</title>
8+
<link rel="stylesheet" type="text/css" href="@Model.Static("styles.css")"/>
9+
<meta charset="utf-8">
10+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
11+
<meta name="robots" content="@(Model.AllowIndexing ? "index, follow" : "noindex, nofollow")">
12+
</head>
13+
<body class="text-ink flex flex-col min-h-screen">
14+
<div id="elastic-nav"></div>
15+
<script src='https://www.elastic.co/elastic-nav.js'></script>
16+
17+
<main class="markdown-content max-w-7xl mx-auto p-6 flex-1">
18+
@await RenderBodyAsync()
19+
</main>
20+
21+
<div id="elastic-footer"></div>
22+
<script src='https://www.elastic.co/elastic-footer.js'></script>
23+
<script src="@Model.Static("main.js")"></script>
24+
</body>
25+
</html>
26+
}
27+
else
28+
{
229
<!DOCTYPE html>
330
<html lang="en" data-accent-color="blue" data-content_root="./">
431
@(await RenderPartialAsync(_Head.Create(Model)))
@@ -98,4 +125,5 @@
98125
@await RenderSectionAsync("scripts")
99126

100127
</body>
101-
</html>
128+
</html>
129+
}

src/Elastic.Markdown/Slices/_ViewModels.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class LayoutViewModel
3838
public required string? GithubEditUrl { get; set; }
3939
public required bool AllowIndexing { get; init; }
4040

41+
public bool IsRedesign => Environment.GetEnvironmentVariable("REDESIGN") == "true";
42+
4143
private MarkdownFile[]? _parents;
4244
public MarkdownFile[] Parents
4345
{
File renamed without changes.

0 commit comments

Comments
 (0)