Keep original source when highlighting (#480)

This commit is contained in:
Jonatan Kłosko 2021-07-28 19:35:36 +02:00 committed by GitHub
parent 969b204b58
commit 3216727858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 40 deletions

View file

@ -13,6 +13,10 @@ solely client-side operations.
@apply hidden;
}
[phx-hook="Highlight"][data-highlighted] > [data-source] {
@apply hidden;
}
/* === Session === */
[data-element="session"]:not([data-js-insert-mode])

View file

@ -1,5 +1,6 @@
import { getAttributeOrThrow } from "../lib/attribute";
import { highlight } from "../cell/live_editor/monaco";
import { findChildOrThrow } from "../lib/utils";
/**
* A hook used to highlight source code in the root element.
@ -7,18 +8,45 @@ import { highlight } from "../cell/live_editor/monaco";
* Configuration:
*
* * `data-language` - language of the source code
*
* The element should have two children:
*
* * one annotated with `data-source` attribute, it should contain
* the source code to be highlighted
*
* * one annotated with `data-target` where the highlighted code
* is rendered
*/
const Highlight = {
mounted() {
this.props = getProps(this);
this.state = {
sourceElement: null,
originalElement: null,
};
highlightIn(this.el, this.props.language);
this.state.sourceElement = findChildOrThrow(this.el, "[data-source]");
this.state.targetElement = findChildOrThrow(this.el, "[data-target]");
highlightInto(
this.state.targetElement,
this.state.sourceElement,
this.props.language
).then(() => {
this.el.setAttribute("data-highlighted", "true");
});
},
updated() {
this.props = getProps(this);
highlightIn(this.el, this.props.language);
highlightInto(
this.state.targetElement,
this.state.sourceElement,
this.props.language
).then(() => {
this.el.setAttribute("data-highlighted", "true");
});
},
};
@ -28,11 +56,11 @@ function getProps(hook) {
};
}
function highlightIn(element, language) {
const code = element.innerText;
function highlightInto(targetElement, sourceElement, language) {
const code = sourceElement.innerText;
highlight(code, language).then((html) => {
element.innerHTML = html;
return highlight(code, language).then((html) => {
targetElement.innerHTML = html;
});
}

View file

@ -103,3 +103,15 @@ export function throttle(fn, windowMs) {
}
};
}
export function findChildOrThrow(element, selector) {
const child = element.querySelector(selector);
if (!child) {
throw new Error(
`expected a child matching ${selector}, but none was found`
);
}
return child;
}

View file

@ -4,7 +4,7 @@ import {
parseBoolean,
parseInteger,
} from "../lib/attribute";
import { getLineHeight } from "../lib/utils";
import { findChildOrThrow, getLineHeight } from "../lib/utils";
/**
* A hook used to render text lines as a virtual list,
@ -37,21 +37,8 @@ const VirtualizedLines = {
this.state.lineHeight = getLineHeight(this.el);
this.state.templateElement = this.el.querySelector("[data-template]");
if (!this.state.templateElement) {
throw new Error(
"VirtualizedLines must have a child with data-template attribute"
);
}
this.state.contentElement = this.el.querySelector("[data-content]");
if (!this.state.contentElement) {
throw new Error(
"VirtualizedLines must have a child with data-content attribute"
);
}
this.state.templateElement = findChildOrThrow(this.el, "[data-template]");
this.state.contentElement = findChildOrThrow(this.el, "[data-content]");
const config = hyperListConfig(
this.state.contentElement,

View file

@ -214,4 +214,26 @@ defmodule LivebookWeb.Helpers do
</button>
"""
end
@doc """
Renders a highlighted code snippet.
## Examples
<.code_preview
source_id="my-snippet"
language="elixir"
source="System.version()" />
"""
def code_preview(assigns) do
~H"""
<div class="markdown">
<pre><code
class="tiny-scrollbar"
id={"#{@source_id}-highlight"}
phx-hook="Highlight"
data-language={@language}><div id={@source_id} data-source><%= @source %></div><div data-target></div></code></pre>
</div>
"""
end
end

View file

@ -88,12 +88,10 @@ defmodule LivebookWeb.SessionLive.BinComponent do
</span>
</div>
</div>
<div class="markdown">
<pre><code
id={"bin-cell-#{cell.id}-source"}
phx-hook="Highlight"
data-language={Cell.type(cell)}><%= cell.source %></code></pre>
</div>
<.code_preview
source_id={"bin-cell-#{cell.id}-source"}
language={Cell.type(cell)}
source={cell.source} />
</div>
<% end %>
<%= if length(@matching_entries) > @limit do %>

View file

@ -47,13 +47,10 @@ defmodule LivebookWeb.SessionLive.ExportElixirComponent do
</span>
</div>
</div>
<div class="markdown">
<pre><code
class="tiny-scrollbar"
id="export-notebook-source"
phx-hook="Highlight"
data-language="elixir"><%= @source %></code></pre>
</div>
<.code_preview
source_id="export-notebook-source"
language="elixir"
source={@source} />
</div>
</div>
"""

View file

@ -37,11 +37,10 @@ defmodule LivebookWeb.SessionLive.ExportLiveMarkdownComponent do
</div>
</div>
<div class="markdown">
<pre><code
class="tiny-scrollbar"
id="export-notebook-source"
phx-hook="Highlight"
data-language="markdown"><%= @source %></code></pre>
<.code_preview
source_id="export-notebook-source"
language="markdown"
source={@source} />
</div>
</div>
"""