Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for multiple source highlighting #100

Merged
merged 2 commits into from
Jul 2, 2024
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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,21 @@ Strictly, the constructor of `SourceCodeViewer` receives a map with arbitrary va
This feature supports highlighting a source code fragment in order to emphasize a section of the code snippet.
The highlighted fragment is automatically scrolled into view.

A fragment is highlighted either by calling `SourceCodeViewer.highlight(name)` or when hovering a component that has been configured with `SourceCodeViewer.highlightOnHover(component,name)`, where `name` is the name of the fragment. `SourceCodeViewer.highlight(null)` turn off the highlighting.
`SourceCodeViewer.highlightOnClick` allows configuring a click listener that turns highlight on.
A fragment is highlighted either by calling `SourceCodeViewer.highlight(filenameAndId)` or when clicking/hovering a component that has been configured with `SourceCodeViewer.highlightOnClick` or `SourceCodeViewer.highlightOnHover`, where `filenameAndId` is the name of the fragment. If the component is in an additional source file, `filenameAndId` can be given as a string in the format `filename#id`. If no `'#'` is present, it is assumed that the identifier corresponds to a block in the first source panel. `SourceCodeViewer.highlight(null)` turns off the highlighting.

In the source code, a fragment is delimited by `// begin-block name` and `// end-block` comments. Nested fragments are not supported.
In the source code, a fragment is delimited by `// begin-block filenameAndId` and `// end-block` comments. Nested fragments are not supported.
The `// begin-block` and `// end-block` comments are removed after post-processing.

```
// begin-block first
Div first = new Div(new Text("First"));
Div first = new Div(new Text("Highlight block in first panel"));
SourceCodeViewer.highlightOnHover(first, "first");
add(first);
// end-block

Div other = new Div(new Text("Highlight additional source"));
SourceCodeViewer.highlightOnHover(other, "AdditionalSource.java#other");
add(other);
```

<!-- FROM https://github.com/FlowingCode/CommonsDemo/pull/62 -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import elemental.json.JsonValue;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class MultiSourceCodeViewer extends Div {

Expand All @@ -15,13 +17,22 @@ public class MultiSourceCodeViewer extends Div {

private SourceCodeViewer codeViewer;
private Tab selectedTab;
private Tabs tabs;

public MultiSourceCodeViewer(List<SourceCodeTab> sourceCodeTabs, Map<String, String> properties) {
if (sourceCodeTabs.size() > 1) {
Tabs tabs = new Tabs(createTabs(sourceCodeTabs));
tabs = new Tabs(createTabs(sourceCodeTabs));
tabs.addSelectedChangeListener(ev -> onTabSelected(ev.getSelectedTab()));
add(tabs);
selectedTab = tabs.getSelectedTab();

getElement().addEventListener("fragment-request", ev -> {
JsonValue filename = ev.getEventData().get("event.detail.filename");
findTabWithFilename(Optional.ofNullable(filename).map(JsonValue::asString).orElse(null))
.ifPresent(tab -> {
tabs.setSelectedTab(tab);
});
}).addEventData("event.detail.filename");
} else {
selectedTab = createTab(sourceCodeTabs.get(0));
}
Expand Down Expand Up @@ -89,7 +100,7 @@ private String getExtension(String filename) {
}

private void onTabSelected(Tab tab) {
this.selectedTab = tab;
selectedTab = tab;

String url = (String) ComponentUtil.getData(tab, DATA_URL);
String language = (String) ComponentUtil.getData(tab, DATA_LANGUAGE);
Expand All @@ -104,4 +115,15 @@ public SourcePosition getSourcePosition() {
return (SourcePosition) ComponentUtil.getData(selectedTab, DATA_POSITION);
}

private Optional<Tab> findTabWithFilename(String filename) {
if (tabs != null) {
return tabs.getChildren().filter(Tab.class::isInstance).map(Tab.class::cast).filter(tab -> {
String url = (String) ComponentUtil.getData(tab, DATA_URL);
return filename == null || getFilename(url).equals(filename);
}).findFirst();
} else {
return Optional.empty();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,54 @@ private void setProperties(Map<String, String> properties) {
}
}

public static void highlightOnHover(Component c, String id) {
/**
* Highlights the block identified by {@code filenameAndId} when the component is hovered over.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param c The component that triggers the highlight action when hovered over.
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/
public static void highlightOnHover(Component c, String filenameAndId) {
c.addAttachListener(ev -> {
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlightOnHover(this,$0)", id);
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlightOnHover(this,$0)", filenameAndId);
});
}

public static <T extends HasElement & ClickNotifier<?>> void highlightOnClick(T c, String id) {
/**
* Highlight block {@code id} when the component is clicked.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param c The component that triggers the highlight action when clicked.
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/
public static <T extends HasElement & ClickNotifier<?>> void highlightOnClick(T c,
String filenameAndId) {
c.addClickListener(ev -> {
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", id);
c.getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", filenameAndId);
});
}

public static void highlight(String id) {
UI.getCurrent().getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", id);
/**
* Highlights the block identified by {@code filenameAndId}.
* <p>
* If the component is in an additional source, {@code filenameAndId} can be given as a string in
* the format {@code filename#id}. If no {@code '#'} is present, it is assumed that the identifier
* corresponds to a block in the first source panel.
*
* @param filenameAndId The identifier string that combines filename and id separated by
* {@code '#'}.
*/

public static void highlight(String filenameAndId) {
UI.getCurrent().getElement().executeJs("Vaadin.Flow.fcCodeViewerConnector.highlight($0)", filenameAndId);
}

}
30 changes: 24 additions & 6 deletions src/main/resources/META-INF/resources/frontend/code-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Demo
* %%
* Copyright (C) 2020 - 2023 Flowing Code
* Copyright (C) 2020 - 2024 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,6 +51,8 @@ export class CodeViewer extends LitElement {

private __license : Element[] = [];

private __highlightedBlock : string | null = null;

env: any = {};

createRenderRoot() {
Expand Down Expand Up @@ -234,6 +236,7 @@ pre[class*="language-"] {
(window as any).Prism.highlightAllUnder(self);
self.__license.reverse().forEach(e=>self.querySelector('pre code')?.prepend(e));
self.process(code);
self._highlight(self.__highlightedBlock, false);
}};
xhr.open('GET', sourceUrl, true);
xhr.send();
Expand Down Expand Up @@ -435,20 +438,35 @@ pre[class*="language-"] {
}

/** @deprecated Use highlight(id: string|null) instead */
highligth(id:string|null) {
this.highlight(id);
highligth(filenameAndId:string|null) {
this.highlight(filenameAndId);
}

//highlight a marked block
highlight(id:string|null) {
highlight(filenameAndId:string|null) {
this._highlight(filenameAndId, true);
}

//highlight a marked block. If request is true, dispatch a fragment request if the block is not found.
private _highlight(filenameAndId:string|null, request:boolean) {
this.__highlightedBlock=filenameAndId;
const div = this.querySelector('.highlight') as HTMLElement;

div.style.removeProperty('top');
div.style.removeProperty('height');
if (id!==null) {
if (filenameAndId!==null) {

var ss = filenameAndId!.split('#',2);
var id = ss!.pop();
var filename = ss!.pop();

var begin = this.querySelector('.begin-'+id) as HTMLElement;
var end = this.querySelector('.end-'+id) as HTMLElement;
if (begin && end && begin.offsetTop<=end.offsetTop) {
if (!begin || !end) {
if (request) {
this.dispatchEvent(new CustomEvent('fragment-request', {bubbles: true, detail: {filename}}));
}
} else if (begin.offsetTop<=end.offsetTop) {
var top = begin.offsetTop;
var height = end.offsetTop+end.offsetHeight-top;
div.style.top= `calc( ${top}px + 0.75em)`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.flowingcode.vaadin.addons.demo;

public class AdditionalSources {
// begin-block fragment
// this class has more sources
// end-block fragment
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,34 @@
*/
package com.flowingcode.vaadin.addons.demo;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route(value = "demo/multisource", layout = Demo.class)
@PageTitle("Demo with multiple sources")
// show-source @DemoSource
// show-source @DemoSource(clazz = AdditionalSources.class)
// show-source @DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
// show-source @DemoSource(clazz = Demo.class)
@DemoSource
@DemoSource(clazz = AdditionalSources.class)
@DemoSource("/src/test/resources/META-INF/resources/frontend/multi-source-demo.css")
@DemoSource(clazz = Demo.class)
@StyleSheet("./multi-source-demo.css")
public class MultiSourceDemo extends Div {
public MultiSourceDemo() {
Span span = new Span("This is the main source");
span.addClassName("custom-style");
add(span);

// begin-block main
Div div = new Div("This is the main source");
div.addClassName("custom-style");
SourceCodeViewer.highlightOnHover(div, "main");
add(div);
// end-block

Button button1 = new Button("Highlight code in AdditionalSources");
SourceCodeViewer.highlightOnClick(button1, "AdditionalSources.java#fragment");
add(button1);
}

}
Loading