Skip to content

Commit

Permalink
[gem-book] <pre> support line number
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Dec 31, 2023
1 parent 6da5c66 commit d79c8ff
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 71 deletions.
5 changes: 1 addition & 4 deletions packages/gem-book/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
/*
!.*
!/*/
!template.html
gem-book.json
25 changes: 25 additions & 0 deletions packages/gem-book/docs/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { GemBookElement } from 'gem-book';

customElements.whenDefined('gem-book').then(() => {
const { GemBookPluginElement } = customElements.get('gem-book') as typeof GemBookElement;
const { Gem, theme } = GemBookPluginElement;
const { html } = Gem;

class MyPlugin extends GemBookPluginElement {
render() {
return html`
<style>
:host {
display: block;
border-radius: ${theme.normalRound};
background: rgba(${theme.textColorRGB}, 0.05);
padding: 1rem;
}
</style>
Hello, World
`;
}
}

customElements.define('my-plugin-hello', MyPlugin);
});
2 changes: 1 addition & 1 deletion packages/gem-book/docs/zh/002-guide/007-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ render('这是一个 `<gbp-sandpack>` 例子', document.getElementById('root'));

[插槽](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot)能让你自定义 `<gem-book>` 的内容,目前支持的插槽有 `sidebar-before`, `main-before`, `main-after`, `nav-inside`, `logo-after`

<gbp-raw src="docs/template.html" range="8--3"></gbp-raw>
<gbp-raw src="docs/template.html" range="8--4"></gbp-raw>

> [!NOTE]
> 可以使用 `--template` 指定模板文件
Expand Down
10 changes: 5 additions & 5 deletions packages/gem-book/docs/zh/003-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ yarn add gem-book

用于显示远端代码,如果提供的 `src` 只包含路径,则会从当前项目的 GitHub 上读取内容(受 [`sourceDir`](./002-guide/003-cli.md#--source-dir)[`sourceBranch`](./002-guide/003-cli.md#--source-branch) 影响),比如:

<gbp-raw src="package.json" range="2-3,-6--4"><gbp-raw>
<gbp-raw src="package.json" range="2-3,-6--4" highlight="89"><gbp-raw>

```md
<!-- `range` 指定显示的范围,支持使用负数 -->

<gbp-raw src="package.json" range="2-3,-6--4"><gbp-raw>
<gbp-raw src="package.json" range="2-3,-6--4" highlight="89"><gbp-raw>
```

## `<gbp-media>`
Expand All @@ -56,17 +56,17 @@ yarn add gem-book

## `<gbp-import>`

动态导入模块,这可以用来按需加载插件
动态导入模块,这可以用来按需加载插件,比如下面这个自定义元素是动态编译并加载的:

```md
<gbp-import src="docs/hello.ts"></gbp-import>

<my-plugin-hello></my-plugin-hello>
```

```md
<gbp-import src="docs/hello.ts"></gbp-import>

<my-plugin-hello></my-plugin-hello>
```

## `<gbp-docsearch>`

Expand Down
1 change: 1 addition & 0 deletions packages/gem-book/src/element/elements/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export class Main extends GemElement {
width: 100%;
box-sizing: border-box;
z-index: 1;
container-type: inline-size;
}
:host > :first-child {
margin-top: 0;
Expand Down
184 changes: 130 additions & 54 deletions packages/gem-book/src/element/elements/pre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ const langAliases: Record<string, string> = {
yml: 'yaml',
};

const IGNORE_LINE = 2;

/**
* @customElement gem-book-pre
*/
Expand All @@ -218,9 +220,18 @@ export class Pre extends GemElement {
@attribute filename: string;
@attribute status: 'hidden' | 'active' | '';
@boolattribute editable: boolean;
@boolattribute linenumber: boolean;

@refobject codeRef: RefObject<HTMLElement>;

get #range() {
return this.range || '1-';
}

get #linenumber() {
return !!this.range || this.linenumber;
}

constructor() {
super();
new MutationObserver(() => this.update()).observe(this, {
Expand All @@ -230,15 +241,8 @@ export class Pre extends GemElement {
});
}

#getStartIndex(start: number, arr: any[]) {
return start < 0 ? arr.length + start + 1 : start;
}

#getEndIndex(end: number, arr: any[]) {
return end < 0 ? arr.length + end + 1 : end || arr.length;
}

#getRanges(range: string) {
#getRanges(range: string, lines: string[]) {
const len = lines.length;
const ranges = range.split(/,\s*/);
return ranges.map((range) => {
// 第二位可以省略,第一位不行
Expand All @@ -249,23 +253,32 @@ export class Pre extends GemElement {
// -2- => (-2)-max
// 2--2 => 2-(-2)
// -3--2 => (-3)-(-2)
const [start, end = start] = range.split(/(?<!-|^)-/);
return [parseInt(start) || 1, parseInt(end) || 0];
const [startStr, endStr = startStr] = range.split(/(?<!-|^)-/);
const [start, end] = [parseInt(startStr) || 1, parseInt(endStr) || 0];
// 包含首尾
return [start < 0 ? len + start + 1 : start, end < 0 ? len + end + 1 : end || len];
});
}

#getParts(s: string) {
const lines = s.split(/\n|\r\n/);
const parts = this.range
? this.#getRanges(this.range).map(([start, end]) => {
let result = '';
for (let i = this.#getStartIndex(start, lines) - 1; i < this.#getEndIndex(end, lines); i++) {
result += (lines[i] || '') + '\n';
}
return result;
})
: [s];
return parts.join('\n...\n\n');
const ranges = this.#getRanges(this.#range, lines);
const highlightLineSet = new Set(
this.highlight
? this.#getRanges(this.highlight, lines)
.map(([start, end]) => Array.from({ length: end - start + 1 }, (_, i) => start + i))
.flat()
: [],
);
const lineNumbersParts = Array.from<unknown, number[]>(ranges, () => []);
const parts = ranges.map(([start, end], index) => {
return Array.from({ length: end - start + 1 }, (_, i) => {
const j = start + i - 1;
lineNumbersParts[index].push(j + 1);
return lines[j];
}).join('\n');
});
return { parts, ranges, lineNumbersParts, highlightLineSet };
}

#composing = false;
Expand Down Expand Up @@ -359,12 +372,21 @@ export class Pre extends GemElement {
const content = Prism.languages[this.codelang]
? Prism.highlight(this.textContent || '', Prism.languages[this.codelang], this.codelang)
: this.innerHTML;
this.codeRef.element.innerHTML = this.#getParts(content);
const { parts, lineNumbersParts } = this.#getParts(content);
this.codeRef.element.innerHTML = parts.reduce(
(p, c, i) =>
p +
`<span class="code-ignore token comment"> @@ ${lineNumbersParts[i - 1].at(-1)! + 1}-${
lineNumbersParts[i].at(0)! - 1
} @@</span>` +
c,
);
this.#setOffset();
});
}

render() {
const { parts, lineNumbersParts, highlightLineSet } = this.#getParts(this.textContent || '');
// Safari 精度问题所以使用整数像素单位
const lineHeight = '24px';
const padding = '1em';
Expand All @@ -374,9 +396,14 @@ export class Pre extends GemElement {
display: none;
}
:host {
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: currentColor;
position: relative;
display: block;
display: flex;
font-size: 0.875em;
font-family: ${theme.codeFont};
line-height: ${lineHeight};
background: rgba(${theme.textColorRGB}, 0.05);
--comment-color: var(--code-comment-color, #6e6e6e);
--section-color: var(--code-section-color, #c9252d);
Expand All @@ -388,33 +415,74 @@ export class Pre extends GemElement {
--keyword-color: var(--code-keyword-color, #93219e);
--attribute-color: var(--code-attribute-color, #4646c6);
}
.gem-highlight {
display: block;
position: absolute;
pointer-events: none;
background: black;
opacity: 0.05;
width: 100%;
.gem-code,
.linenumber {
box-sizing: border-box;
min-height: 100%;
height: max-content;
}
.gem-code {
height: 100%;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: currentColor;
font-family: inherit;
flex-grow: 1;
box-sizing: border-box;
display: block;
font-family: ${theme.codeFont};
text-align: left;
white-space: pre;
tab-size: 2;
padding: 1em;
padding: ${padding};
hyphens: none;
overflow: auto;
overflow-clip-box: content-box;
box-shadow: none;
border: none;
background: transparent;
scrollbar-width: thin;
outline: none;
caret-color: ${theme.textColor};
}
.linenumber {
display: inline-flex;
flex-direction: column;
flex-shrink: 0;
padding: 1em;
min-width: 1em;
text-align: right;
border-right: 1px solid ${theme.borderColor};
color: rgba(${theme.textColorRGB}, 0.5);
user-select: none;
}
.linenumber-ignore,
.code-ignore {
display: flex;
place-items: center;
height: calc(${IGNORE_LINE} * ${lineHeight});
user-select: none;
}
.code-ignore {
place-content: start;
}
.linenumber-ignore {
place-content: center;
}
.linenumber-ignore::before {
content: '';
width: 2px;
height: 2px;
background: currentColor;
border-radius: 1em;
box-shadow:
0 0.34em,
0 -0.34em;
}
.gem-highlight {
display: block;
position: absolute;
pointer-events: none;
background: rgba(${theme.textColorRGB}, 0.05);
width: 100%;
height: ${lineHeight};
}
.token.comment,
.token.prolog,
.token.doctype,
Expand Down Expand Up @@ -490,18 +558,32 @@ export class Pre extends GemElement {
}
}
</style>
${this.highlight
? this.#getRanges(this.highlight).map(
([start, end]) => html`
<span
class="gem-highlight"
style=${styleMap({
top: `calc(${start - 1} * ${lineHeight} + ${padding})`,
height: `calc(${end - start + 1} * ${lineHeight})`,
})}
></span>
`,
)
${lineNumbersParts
.reduce((p, c) => p.concat(Array(IGNORE_LINE)).concat(c))
.map((linenumber, index) =>
highlightLineSet.has(linenumber)
? html`
<span
class="gem-highlight"
style=${styleMap({
top: `calc(${index} * ${lineHeight} + ${padding})`,
})}
></span>
`
: '',
)}
${this.#linenumber
? html`
<div class="linenumber">
${lineNumbersParts.map(
(numbers, index, arr) => html`
${numbers.map((n) => html`<span>${n}</span>`)}${arr.length - 1 !== index
? html`<span class="linenumber-ignore"></span>`
: ''}
`,
)}
</div>
`
: ''}
<code
ref=${this.codeRef.ref}
Expand All @@ -510,14 +592,8 @@ export class Pre extends GemElement {
@compositionstart=${this.#compositionstartHandle}
@compositionend=${this.#compositionendHandle}
@input=${this.#onInput}
>${this.#getParts(this.textContent || '')}</code
>${parts.join('\n'.repeat(IGNORE_LINE + 1))}</code
>
<style>
code {
padding: ${padding};
line-height: ${lineHeight};
}
</style>
`;
}
}
2 changes: 1 addition & 1 deletion packages/gem-book/src/element/helper/default-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const defaultTheme = {
sidebarWidthSmall: '270px',

sidebarWidth: '304px',
maxMainWidth: '46rem',
maxMainWidth: '48rem',
headerHeight: '56px',
normalRound: '0.5rem',
smallRound: '0.25rem',
Expand Down
Loading

0 comments on commit d79c8ff

Please sign in to comment.