Update how tags are rendered to allow for custom styling (#1179)

* Updated how tags are rendered to allow for custom styling via space-styles

* Wrapped the existing span for a tag with an anchor element <a> to
improve ux for screen readers
* Added data-tag-name attribute to tags so that given a tag `#my-tag` it
will have the attribute `data-tag-name="my-tag"` added to the new `<a>`
element wrapper allowing for end users to target tags for styling with a
css selector such as `.sb-hashtag[data-tag-name="my-tag"]{...}`

* Updated Docs
pull/1183/head
TR Staake 2024-12-14 03:57:46 -05:00 committed by GitHub
parent a56db78194
commit 2020e85a6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 125 additions and 31 deletions

View File

@ -584,15 +584,13 @@ const NakedURL = regexParser(
}, },
); );
const Hashtag = regexParser( const Hashtag = regexParser({
{ firstCharCode: 35, // #
firstCharCode: 35, // # regex: new RegExp(`^${tagRegex.source}`),
regex: new RegExp(`^${tagRegex.source}`), nodeType: "Hashtag",
nodeType: "Hashtag", className: "sb-hashtag-text",
className: "sb-hashtag", tag: ct.HashtagTag,
tag: ct.HashtagTag, });
},
);
const TaskDeadline = regexParser({ const TaskDeadline = regexParser({
firstCharCode: 55357, // 📅 firstCharCode: 55357, // 📅

View File

@ -14,6 +14,7 @@ import {
import { Fragment, renderHtml, type Tag } from "./html_render.ts"; import { Fragment, renderHtml, type Tag } from "./html_render.ts";
import { isLocalPath } from "@silverbulletmd/silverbullet/lib/resolve"; import { isLocalPath } from "@silverbulletmd/silverbullet/lib/resolve";
import type { PageMeta } from "@silverbulletmd/silverbullet/types"; import type { PageMeta } from "@silverbulletmd/silverbullet/types";
import * as TagConstants from "../../plugs/index/constants.ts";
export type MarkdownRenderOptions = { export type MarkdownRenderOptions = {
failOnUnknown?: true; failOnUnknown?: true;
@ -335,15 +336,18 @@ function render(
body: url, body: url,
}; };
} }
case "Hashtag": case "Hashtag": {
const tagText: string = t.children![0].text!;
return { return {
name: "span", name: "a",
attrs: { attrs: {
class: "hashtag", class: "hashtag sb-hashtag",
"data-tag-name": tagText.replace("#", ""),
href: `/${TagConstants.tagPrefix}${tagText.replace("#", "")}`,
}, },
body: t.children![0].text!, body: tagText,
}; };
}
case "Task": { case "Task": {
let externalTaskRef = ""; let externalTaskRef = "";
collectNodesOfType(t, "WikiLinkPage").forEach((wikilink) => { collectNodesOfType(t, "WikiLinkPage").forEach((wikilink) => {

View File

@ -15,6 +15,7 @@ import { fencedCodePlugin } from "./fenced_code.ts";
import { frontmatterPlugin } from "./frontmatter.ts"; import { frontmatterPlugin } from "./frontmatter.ts";
import { cleanEscapePlugin } from "./escapes.ts"; import { cleanEscapePlugin } from "./escapes.ts";
import { luaDirectivePlugin } from "./lua_directive.ts"; import { luaDirectivePlugin } from "./lua_directive.ts";
import { hashtagPlugin } from "./hashtag.ts";
export function cleanModePlugins(client: Client) { export function cleanModePlugins(client: Client) {
return [ return [
@ -46,5 +47,6 @@ export function cleanModePlugins(client: Client) {
cleanCommandLinkPlugin(client), cleanCommandLinkPlugin(client),
cleanEscapePlugin(), cleanEscapePlugin(),
luaDirectivePlugin(client), luaDirectivePlugin(client),
hashtagPlugin(),
] as Extension[]; ] as Extension[];
} }

43
web/cm_plugins/hashtag.ts Normal file
View File

@ -0,0 +1,43 @@
import { syntaxTree } from "@codemirror/language";
import { Decoration } from "@codemirror/view";
import { decoratorStateField } from "./util.ts";
import * as Constants from "../../plugs/index/constants.ts";
import { extractHashtag } from "../../plug-api/lib/tags.ts";
export function hashtagPlugin() {
return decoratorStateField((state) => {
const widgets: any[] = [];
syntaxTree(state).iterate({
enter: ({ type, from, to }) => {
if (type.name !== "Hashtag") {
return;
}
const tag = state.sliceDoc(from, to);
if (tag.length === 1) {
// Invalid Hashtag, a length of 1 means its just #
return;
}
const tagName = extractHashtag(tag);
// Wrap the tag in html anchor element
widgets.push(
Decoration.mark({
tagName: "a",
class: "sb-hashtag",
attributes: {
href: `/${Constants.tagPrefix}${tagName}`,
rel: "tag",
"data-tag-name": tagName,
},
}).range(from, to),
);
},
});
return Decoration.set(widgets, true);
});
}

View File

@ -97,7 +97,7 @@ export function hideHeaderMarkPlugin() {
widgets.push( widgets.push(
Decoration.mark({ Decoration.mark({
tagName: "span", tagName: "span",
class: "sb-hashtag", class: "sb-hashtag-text",
}).range(from, from + 1), }).range(from, from + 1),
); );

View File

@ -49,7 +49,7 @@ export default function highlightStyles() {
{ tag: t.processingInstruction, class: "sb-meta" }, { tag: t.processingInstruction, class: "sb-meta" },
{ tag: t.punctuation, class: "sb-punctuation" }, { tag: t.punctuation, class: "sb-punctuation" },
{ tag: ct.HorizontalRuleTag, class: "sb-hr" }, { tag: ct.HorizontalRuleTag, class: "sb-hr" },
{ tag: ct.HashtagTag, class: "sb-hashtag" }, { tag: ct.HashtagTag, class: "sb-hashtag-text" },
{ tag: ct.NakedURLTag, class: "sb-naked-url" }, { tag: ct.NakedURLTag, class: "sb-naked-url" },
{ tag: ct.TaskDeadlineTag, class: "sb-task-deadline" }, { tag: ct.TaskDeadlineTag, class: "sb-task-deadline" },
{ tag: ct.NamedAnchorTag, class: "sb-named-anchor" }, { tag: ct.NamedAnchorTag, class: "sb-named-anchor" },

View File

@ -57,7 +57,7 @@
} }
// If a header only contains a tag, it's likely a line containging "#" which may turn into a hashtag, so style it as such instead of a header // If a header only contains a tag, it's likely a line containging "#" which may turn into a hashtag, so style it as such instead of a header
.sb-line-h1:has(> span.sb-hashtag:only-child) { .sb-line-h1:has(> a.sb-hashtag:only-child) {
font-size: 1em; font-size: 1em;
padding: 0; padding: 0;
font-weight: normal; font-weight: normal;
@ -276,6 +276,10 @@
font-size: 0.9em; font-size: 0.9em;
} }
a.sb-hashtag {
text-decoration: none;
}
.sb-strikethrough { .sb-strikethrough {
text-decoration: line-through; text-decoration: line-through;

View File

@ -22,9 +22,32 @@ Beside these, any number of additional tag-specific and custom [[Attributes]] ca
# Tags # Tags
Every object has a main `tag`, which signifies the type of object being described. In addition, any number of additional tags can be assigned as well via the `tags` attribute. You can use either the main `tag` or any of the `tags` as query sources in [[Live Queries]]  examples below. Every object has a main `tag`, which signifies the type of object being described. In addition, any number of additional tags can be assigned as well via the `tags` attribute. You can use either the main `tag` or any of the `tags` as query sources in [[Live Queries]]  examples below.
Here are the currently built-in tags: ## Styling
You can add custom styles to a tag by leveraging the `data-tag-name` attribute, [CSS Attribute Selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) and custom [[Speace Style]]'s. Every tag gets an attribute added to it called `data-tag-name` that is set to the tag name with the `#` symbol stripped out. So given the tag #my-cool-tag the `data-tag-name` attribute would look like:
`data-tag-name="my-cool-tag"`.
This allows us to do things like change the color of the #my-cool-tag to have a purple background, limegreen text and bold font by adding the following [[Space Style]]:
```css
.sb-hashtag[data-tag-name="my-cool-tag"] {
background: purple;
color: limegreen;
font-weight: bolder;
}
```
Additionally tags written using angle brackets, such as...
#<my cool tag>
...can be styled via [[Space Style]] like this:
```css
.sb-hashtag[data-tag-name="my cool tag"] {
background: purple;
color: limegreen;
font-weight: bolder;
}
```
## page
## Built-in tags
### page
Every page in your space is available via the `page` tag. You can attach _additional_ tags to a page, by either specifying them in the `tags` attribute [[Frontmatter]], or by putting additional [[Tags]] in a stand alone paragraph with no other (textual) content in them, for instance check the very first line of this page that says `#level/intermediate`. Every page in your space is available via the `page` tag. You can attach _additional_ tags to a page, by either specifying them in the `tags` attribute [[Frontmatter]], or by putting additional [[Tags]] in a stand alone paragraph with no other (textual) content in them, for instance check the very first line of this page that says `#level/intermediate`.
In addition to `ref` and `tags`, the `page` tag defines a bunch of additional attributes as can be seen in this example query: In addition to `ref` and `tags`, the `page` tag defines a bunch of additional attributes as can be seen in this example query:
@ -39,7 +62,7 @@ Note that you can also query this page using the `level/intermediate` directly:
level/intermediate level/intermediate
``` ```
## aspiring-page ### aspiring-page
[[Aspiring Pages]] are pages that are linked to, but not yet created. [[Aspiring Pages]] are pages that are linked to, but not yet created.
```query ```query
@ -47,7 +70,7 @@ aspiring-page
``` ```
## table ### table
Markdown table rows are indexed using the `table` tag, any additional tags can be added using [[Tags]] in any of its cells. Markdown table rows are indexed using the `table` tag, any additional tags can be added using [[Tags]] in any of its cells.
| Title | Description Text | | Title | Description Text |
@ -62,7 +85,7 @@ table
Table headers will be normalized by converting them to lowercase and replacing all non alphanumeric characters with `_`. Table headers will be normalized by converting them to lowercase and replacing all non alphanumeric characters with `_`.
## item ### item
List items (both bullet point and numbered items) are indexed with the `item` tag, and additional tags can be added using [[Tags]]. List items (both bullet point and numbered items) are indexed with the `item` tag, and additional tags can be added using [[Tags]].
Here is an example of a #quote item using a custom [[Attributes|attribute]]: Here is an example of a #quote item using a custom [[Attributes|attribute]]:
@ -108,7 +131,7 @@ upnext render [[Library/Core/Query/Task]]
Similar to [[#item]], `task` objects have a `parent` attribute when nested (pointing to their parent `item`), and inherit their ancestors tags in `itags`. Similar to [[#item]], `task` objects have a `parent` attribute when nested (pointing to their parent `item`), and inherit their ancestors tags in `itags`.
## taskstate ### taskstate
[[Plugs/Tasks]] support the default `x` and ` ` states (done and not done), but custom states as well. Custom states used across your space are kept in `taskstate`: [[Plugs/Tasks]] support the default `x` and ` ` states (done and not done), but custom states as well. Custom states used across your space are kept in `taskstate`:
* [NOT STARTED] Task 1 * [NOT STARTED] Task 1
@ -150,7 +173,7 @@ Which then becomes queriable via the `person` tag:
person person
``` ```
## link ### link
All page _links_ are tagged with `link`. You cannot attach additional tags to links. The main two attributes of a link are: All page _links_ are tagged with `link`. You cannot attach additional tags to links. The main two attributes of a link are:
* `toPage` the page the link is linking _to_ * `toPage` the page the link is linking _to_
@ -166,7 +189,7 @@ Here is a query that shows some links that appear in this particular page:
link where page = @page.name limit 5 link where page = @page.name limit 5
``` ```
## anchor ### anchor
[[Markdown/Anchors]] use the $myanchor notation to allow deeplinking into a page and are also indexed and queryable. It is not possible to attach additional tags to an anchor. [[Markdown/Anchors]] use the $myanchor notation to allow deeplinking into a page and are also indexed and queryable. It is not possible to attach additional tags to an anchor.
Here is an example query: Here is an example query:
@ -175,7 +198,7 @@ Here is an example query:
anchor where page = @page.name anchor where page = @page.name
``` ```
## header ### header
Headers (lines starting with `#`, `##` etc.) are indexed as well and queriable. Headers (lines starting with `#`, `##` etc.) are indexed as well and queriable.
```query ```query
@ -183,7 +206,7 @@ header where page = @page.name limit 3
``` ```
## tag ### tag
The ultimate meta tag is _tag_ itself, which indexes for all tags used, in which page they appear and what their “parent tag” is (the context of the tag: either `page`, `item` or `task`). The ultimate meta tag is _tag_ itself, which indexes for all tags used, in which page they appear and what their “parent tag” is (the context of the tag: either `page`, `item` or `task`).
Here are the tags used/defined in this page: Here are the tags used/defined in this page:
@ -192,7 +215,7 @@ Here are the tags used/defined in this page:
tag where page = @page.name select name, parent tag where page = @page.name select name, parent
``` ```
## space-config ### space-config
This stores all configuration picked up as part of [[Space Config]] This stores all configuration picked up as part of [[Space Config]]
```query ```query
@ -200,16 +223,16 @@ space-config select key
``` ```
# System tags ## System tags
The following tags are technically implemented a bit differently than the rest, but they are still available to be queried. The following tags are technically implemented a bit differently than the rest, but they are still available to be queried.
## command ### command
Enables querying of all [[Commands]] available in SilverBullet as well as their assigned keyboard shortcuts. Enables querying of all [[Commands]] available in SilverBullet as well as their assigned keyboard shortcuts.
```query ```query
command order by name limit 5 command order by name limit 5
``` ```
## syscall ### syscall
Enables querying of all [[PlugOS]] syscalls enabled in your space. Mostly useful in the context of [[Plugs]] and [[Space Script]] development. Enables querying of all [[PlugOS]] syscalls enabled in your space. Mostly useful in the context of [[Plugs]] and [[Space Script]] development.
```query ```query

View File

@ -69,6 +69,26 @@ button {
button:hover { button:hover {
/* box-shadow: #121212 0 0 0 3px, transparent 0 0 0 0; */ /* box-shadow: #121212 0 0 0 3px, transparent 0 0 0 0; */
} }
/* Add custom styling to a specific tag
--> Example Tag: #my-cool-tag */
.sb-hashtag[data-tag-name="my-cool-tag"] {
background: purple;
color: limegreen;
font-weight: bolder;
}
/* --> Example Tag with angle brackets: #<my cool tag> */
.sb-hashtag[data-tag-name="my cool tag"] {
background: purple;
color: limegreen;
font-weight: bolder;
}
/* Custom Styling to all tags
.sb-hashtag[data-tag-name] {
background: yellow;
color: goldenrod;
border: 2px dashed goldenrod; */
}
``` ```
Another example can be found in [[Page Decorations#Use case: pimp my page]] Another example can be found in [[Page Decorations#Use case: pimp my page]]