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

GFM compatible alerts #53

Merged
merged 14 commits into from
Jan 25, 2024
119 changes: 63 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,69 +67,76 @@ Extensions can be used by adding them to the options.

```java
Options options = new Options();
options.addExtension(new NotificationExtension());
String html = Marked.marked("! This is an info message", options);
// => <div class="notification_info"><p>This is an info message</p></div>
options.addExtension(new GFMAlertExtension());
String html = Marked.marked("> [!NOTE]\n> This is a note!", options);
```

### Notification extension
### GFMAlert extension

The notification extension helps you to add information messages to your markdown content.
Keep in mind, you still need the CSS to style the messages as desired.
Support for github like [alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts).

#### Info message
```text
! This is an info message
```
```html
<div class="notification_info">
<p>This is an info message</p>
</div>
```

#### Success message
```text
!v This is a success message
```
```html
<div class="notification_success">
<p>This is a success message</p>
</div>
```

#### Warning message
```text
!! This is a warning message
```
```html
<div class="notification_warning">
<p>This is a warning message</p>
</div>
```
For styling, some project-specific CSS is required. SVG icons are embedded but can be replaced by configuration.

#### Error message
```text
!x This is an error message
```
```html
<div class="notification_error">
<p>This is an error message</p>
</div>
```
#### Usage

#### Multiline notifications
Notifications can span multiple lines.
```java
Options options = new Options();
GFMAlertExtension gfmAlerts = new GFMAlertExtension();
// Override default title for note alert
gfmAlerts.addTitle(GFMAlerts.Alert.NOTE, "Notice");
// Override default icon for note alert
gfmAlerts.setIcon(GFMAlerts.Alert.NOTE, "");
options.addExtension(gfmAlerts);
String html = Marked.marked("> [!NOTE]\n> This is a note!", options);
```

#### Supported alert types

**Note**
```markdown
> [!NOTE]
> Useful information that users should know, even when skimming content.
```
**Note HTML**
> [!NOTE]
> Useful information that users should know, even when skimming content.

**Tip**
```markdown
> [!TIP]
> Helpful advice for doing things better or more easily.
```
**Note HTML**
> [!TIP]
> Helpful advice for doing things better or more easily.

**Important**
```markdown
> [!IMPORTANT]
> Key information users need to know to achieve their goal.
```
**Important HTML**
> [!IMPORTANT]
> Key information users need to know to achieve their goal.

**Warning**
```markdown
> [!WARNING]
> Urgent info that needs immediate user attention to avoid problems.
```
**Warning HTML**
> [!WARNING]
> Urgent info that needs immediate user attention to avoid problems.

**Caution**
```markdown
> [!CAUTION]
> Advises about risks or negative outcomes of certain actions.
```
**Caution HTML**
> [!CAUTION]
> Advises about risks or negative outcomes of certain actions.

```text
! This is an info message
! That spans over several lines
```
```html
<div class="notification_info">
<p>This is an info message
That spans over several lines</p>
</div>
```

## for Developers

Expand Down
24 changes: 12 additions & 12 deletions src/main/java/io/github/gitbucket/markedj/Lexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,6 @@ protected void token(String src, boolean top, boolean bq, LexerContext context){
}
}

// blockquote
{
List<String> cap = rules.get("blockquote").exec(src);
if(!cap.isEmpty()){
src = src.substring(cap.get(0).length());
context.pushToken(new BlockquoteStartToken());
token(cap.get(0).replaceAll("(?m)^ *> ?", ""), top, true, context);
context.pushToken(new BlockquoteEndToken());
continue;
}
}

{
Extension.LexResult result = null;
for (Extension extension : options.getExtensions()) {
Expand All @@ -185,6 +173,18 @@ protected void token(String src, boolean top, boolean bq, LexerContext context){
}
}

// blockquote
{
List<String> cap = rules.get("blockquote").exec(src);
if(!cap.isEmpty()){
src = src.substring(cap.get(0).length());
context.pushToken(new BlockquoteStartToken());
token(cap.get(0).replaceAll("(?m)^ *> ?", ""), top, true, context);
context.pushToken(new BlockquoteEndToken());
continue;
}
}

// list
{
List<String> cap = rules.get("list").exec(src);
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/io/github/gitbucket/markedj/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class Options {
"colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6",
"i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong",
"sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
"ul", "input", "del", "hr")
"ul", "input", "del", "hr", "svg", "path")
.addAttributes("a", "href", "title")
.addProtocols("a", "href", "http", "https", "mailto", "ftp", "#")
.addAttributes("blockquote", "cite")
Expand All @@ -34,7 +34,10 @@ public class Options {
.addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width")
.addAttributes("ul", "type")
.addAttributes("input", "type", "checked", "name", "value", "disabled")
.addAttributes(":all", "id", "class", "style");
.addAttributes("svg", "class", "viewBox", "version", "width", "height", "aria-hidden")
.addAttributes("path", "d")
.addAttributes(":all", "id", "class", "style")
;

private List<Extension> extensions = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.notification;
package io.github.gitbucket.markedj.extension.gfm.alert;

import io.github.gitbucket.markedj.token.Token;

/**
*
* @author t.marx
*/
public class NotificationEndToken implements Token {
public class GFMAlertEndToken implements Token {

public static String TYPE = "GFMAlertEndToken";

@Override
public String getType() {
return "NotificationEndToken";
return TYPE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.gfm.alert;

import io.github.gitbucket.markedj.Lexer;
import io.github.gitbucket.markedj.Parser;
import io.github.gitbucket.markedj.extension.Extension;
import io.github.gitbucket.markedj.extension.TokenConsumer;
import io.github.gitbucket.markedj.rule.FindFirstRule;
import io.github.gitbucket.markedj.rule.Rule;
import io.github.gitbucket.markedj.token.Token;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

/**
*
* @author t.marx
*/
public class GFMAlertExtension implements Extension {

public static String EXPRESSION = "(?s)(?m)\\A^> \\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\](.+?)(^\n|\\Z)";

private static final Rule RULE = new FindFirstRule(EXPRESSION);

private final Map<GFMAlerts.Alert, String> titles = new HashMap<>();

private final Map<GFMAlerts.Alert, String> icons = new HashMap<>();

/**
* Creates the extension with default titles and icons.
*/
public GFMAlertExtension () {
titles.put(GFMAlerts.Alert.TIP, "Tip");
titles.put(GFMAlerts.Alert.NOTE, "Note");
titles.put(GFMAlerts.Alert.IMPORTANT, "Important");
titles.put(GFMAlerts.Alert.WARNING, "Warning");
titles.put(GFMAlerts.Alert.CAUTION, "Caution");

icons.put(GFMAlerts.Alert.TIP, GFMAlerts.Icons.TIP);
icons.put(GFMAlerts.Alert.NOTE, GFMAlerts.Icons.NOTE);
icons.put(GFMAlerts.Alert.IMPORTANT, GFMAlerts.Icons.IMPORTANT);
icons.put(GFMAlerts.Alert.WARNING, GFMAlerts.Icons.WARNING);
icons.put(GFMAlerts.Alert.CAUTION, GFMAlerts.Icons.CAUTION);
}

/**
* Adds the title for an alert.
*
* @param alert
* @param title
*/
public void addTitle (final GFMAlerts.Alert alert, final String title ) {
titles.put(alert, title);
}

/**
* Adds a avg icon for a alert.
* @param alert
* @param icon
*/
public void addIcon (final GFMAlerts.Alert alert, final String icon ) {
icons.put(alert, icon);
}

@Override
public LexResult lex(String source, final Lexer.LexerContext context, final TokenConsumer consumer) {
List<String> cap = RULE.exec(source);
boolean tokenFound = false;
if (!cap.isEmpty()) {
// we have detected several contiguous lines of notifications
// ensure that all are of same kind
String allNotificationsLines = cap.get(0);

String content = cap.get(2);

content = content.replaceAll("(?m)^ *> ?", "");

source = source.substring(allNotificationsLines.length());
context.pushToken(new GFMAlertStartToken(cap.get(1)));
consumer.token(content, false, false, context);
context.pushToken(new GFMAlertEndToken());

tokenFound = true;
}
return new LexResult(source, tokenFound);
}

@Override
public boolean handlesToken(String token) {
return GFMAlertStartToken.TYPE.equals(token);
}

@Override
public String parse(Parser.ParserContext context, Function<Parser.ParserContext, String> tok) {
GFMAlertStartToken t = (GFMAlertStartToken) context.currentToken();
StringBuilder body = new StringBuilder();
while (true) {
Token n = context.nextToken();
if (n == null || n.getType().equals(GFMAlertEndToken.TYPE)) {
break;
}
body.append(tok.apply(context));
}
return render(body.toString(), t.getNotification());
}

private String render(String message, GFMAlerts.Alert alert) {

if (!message.startsWith("<p>")) {
message = String.format("<p>%s</p>", message);
}

return String.format("<div class=\"markdown-alert markdown-alert-%s\"><p class=\"markdown-alert-title\">%s%s</p>\n%s</div>",
alert.name().toLowerCase(Locale.ENGLISH),
icons.get(alert),
titles.get(alert),
message
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.notification;
package io.github.gitbucket.markedj.extension.gfm.alert;

import io.github.gitbucket.markedj.token.Token;

/**
*
* @author t.marx
*/
public class NotificationStartToken implements Token {
protected static String TYPE = "NotificationStartToken";
public class GFMAlertStartToken implements Token {
protected static String TYPE = "GFMAlertStartToken";

private Notifications.Notification notification;
private GFMAlerts.Alert alert;

public NotificationStartToken(String type) {
this.notification = Notifications.Notification.fromString(type);
public GFMAlertStartToken(String type) {
this.alert = GFMAlerts.Alert.fromString(type);
}

@Override
public String getType() {
return TYPE;
}

public Notifications.Notification getNotification() {
return notification;
public GFMAlerts.Alert getNotification() {
return alert;
}
}
Loading