diff --git a/client/options.go b/client/options.go index dbca8c0e..7f6232f8 100644 --- a/client/options.go +++ b/client/options.go @@ -72,6 +72,11 @@ func WithAttach(attach string) PublishOption { return WithHeader("X-Attach", attach) } +// WithMarkdown instructs the server to interpret the message body as Markdown +func WithMarkdown() PublishOption { + return WithHeader("X-Markdown", "yes") +} + // WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment func WithFilename(filename string) PublishOption { return WithHeader("X-Filename", filename) diff --git a/cmd/publish.go b/cmd/publish.go index 0179f9fa..390e5f67 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -31,6 +31,7 @@ var flagsPublish = append( &cli.StringFlag{Name: "icon", Aliases: []string{"i"}, EnvVars: []string{"NTFY_ICON"}, Usage: "URL to use as notification icon"}, &cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"}, &cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"}, + &cli.BoolFlag{Name: "markdown", Aliases: []string{"md"}, EnvVars: []string{"NTFY_MARKDOWN"}, Usage: "Message is formatted as Markdown"}, &cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"}, &cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"}, &cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"}, @@ -95,6 +96,7 @@ func execPublish(c *cli.Context) error { icon := c.String("icon") actions := c.String("actions") attach := c.String("attach") + markdown := c.Bool("attach") filename := c.String("filename") file := c.String("file") email := c.String("email") @@ -140,6 +142,9 @@ func execPublish(c *cli.Context) error { if attach != "" { options = append(options, client.WithAttach(attach)) } + if markdown { + options = append(options, client.WithMarkdown()) + } if filename != "" { options = append(options, client.WithFilename(filename)) } diff --git a/docs/publish.md b/docs/publish.md index 905508fe..b20f66e6 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -138,7 +138,7 @@ a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an Tags = "warning,skull" } Body = "Remote access to phils-laptop detected. Act right away." - } + } Invoke-RestMethod @Request ``` @@ -623,34 +623,108 @@ them with a comma, e.g. `tag1,tag2,tag3`. as [RFC 2047](https://datatracker.ietf.org/doc/html/rfc2047#section-2), e.g. `tag1,=?UTF-8?B?8J+HqfCfh6o=?=` ([base64](https://en.wikipedia.org/wiki/Base64)), or `=?UTF-8?Q?=C3=84pfel?=,tag2` ([quoted-printable](https://en.wikipedia.org/wiki/Quoted-printable)). -## Markdown +## Markdown formatting _Supported on:_ :material-firefox: -You can format messages using [Markdown](https://www.markdownguide.org/basic-syntax/). 🤩 +You can format messages using [Markdown](https://www.markdownguide.org/basic-syntax/) 🤩. That means you can use +**bold**, *italicized*, or _underlined text_, links, images, and more. Supported Markdown features (web app only for now): -By default, messages sent to ntfy are rendered as plain text. To enable Markdown, set the `X-Markdown` header (or any of -its aliases: `Markdown`, or `md`) to `true` (or `1` or `yes`), or set the `Content-Type` header to `text/markdown`. +- [Emphasis](https://www.markdownguide.org/basic-syntax/#emphasis) such as **bold** (`**bold**`), *italic* (`*italic*`), _underline_ (`_underline_`) +- [Links](https://www.markdownguide.org/basic-syntax/#links) (`[some tool](https://ntfy.sh)`) +- [Images](https://www.markdownguide.org/basic-syntax/#images) (`![some image](https://bing.com/logo.png)`) +- [Code blocks](https://www.markdownguide.org/basic-syntax/#code-blocks) (` ```code blocks``` `) and [inline code](https://www.markdownguide.org/basic-syntax/#inline-code) (`` `inline code` ``) +- [Headings](https://www.markdownguide.org/basic-syntax/#headings) (`# headings`, `## headings`, etc.) +- [Lists](https://www.markdownguide.org/basic-syntax/#lists) (`- lists`, `1. lists`, etc.) +- [Blockquotes](https://www.markdownguide.org/basic-syntax/#blockquotes) (`> blockquotes`) +- [Horizontal rules](https://www.markdownguide.org/basic-syntax/#horizontal-rules) (`---`) -Supported Markdown features: +By default, messages sent to ntfy are rendered as plain text. To enable Markdown, set the `X-Markdown` header (or any of +its aliases: `Markdown`, or `md`) to `true` (or `1` or `yes`), or set the `Content-Type` header to `text/markdown`. +As of today, **Markdown is only supported in the web app.** Here's an example of how to enable Markdown formatting: -- **bold** (`**bold**`) -- *italic* (`*italic*`) -- [links](https://www.markdownguide.org/basic-syntax/#links) (`[links](https://www.markdownguide.org/basic-syntax/#links)`) -- [images](https://www.markdownguide.org/basic-syntax/#images) (`![images](https://www.markdownguide.org/basic-syntax/#images)`) -- [code blocks](https://www.markdownguide.org/basic-syntax/#code-blocks) (`` `code blocks` ``) -- [inline code](https://www.markdownguide.org/basic-syntax/#inline-code) (`` `inline code` ``) -- [headings](https://www.markdownguide.org/basic-syntax/#headings) (`# headings`) -- [lists](https://www.markdownguide.org/basic-syntax/#lists) (`- lists`) -- [blockquotes](https://www.markdownguide.org/basic-syntax/#blockquotes) (`> blockquotes`) -- [horizontal rules](https://www.markdownguide.org/basic-syntax/#horizontal-rules) (`---`) +=== "Command line (curl)" + ``` + curl \ + -d "Look ma, **bold text**, *italics*, _underlined text_, ..." \ + -H "Markdown: yes" \ + ntfy.sh/mytopic + ``` -XXXXXXXXXXXXXXXXXXXXXx -- examples -- supported only on Web for now +=== "ntfy CLI" + ``` + ntfy publish \ + mytopic \ + --markdown \ + "Look ma, **bold text**, *italics*, _underlined text_, ..." + ``` -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXxx +=== "HTTP" + ``` http + POST /mytopic HTTP/1.1 + Host: ntfy.sh + Markdown: yes + Look ma, **bold text**, *italics*, _underlined text_, ... + ``` +=== "JavaScript" + ``` javascript + fetch('https://ntfy.sh/mytopic', { + method: 'POST', // PUT works too + body: 'Look ma, **bold text**, *italics*, _underlined text_, ...', + headers: { 'Markdown': 'yes' } + }) + ``` + +=== "Go" + ``` go + http.Post("https://ntfy.sh/mytopic", "text/markdown", + strings.NewReader("Look ma, **bold text**, *italics*, _underlined text_, ...")) + + // or + req, _ := http.NewRequest("POST", "https://ntfy.sh/mytopic", + strings.NewReader("Look ma, **bold text**, *italics*, _underlined text_, ...")) + req.Header.Set("Markdown", "yes") + http.DefaultClient.Do(req) + ``` + +=== "PowerShell" + ``` powershell + $Request = @{ + Method = "POST" + URI = "https://ntfy.sh/mytopic" + Body = "Look ma, **bold text**, *italics*, _underlined text_, ..." + Headers = @{ + Markdown = "yes" + } + } + Invoke-RestMethod @Request + ``` + +=== "Python" + ``` python + requests.post("https://ntfy.sh/mytopic", + data="Look ma, **bold text**, *italics*, _underlined text_, ..." + headers={ "Markdown": "yes" })) + ``` + +=== "PHP" + ``` php-inline + file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([ + 'http' => [ + 'method' => 'POST', // PUT also works + 'header' => 'Content-Type: text/markdown', // ! + 'content' => 'Look ma, **bold text**, *italics*, _underlined text_, ...' + ] + ])); + ``` + +Here's what that looks like in the web app: + +
+ ![markdown](static/img/web-markdown.png){ width=500 } +
Markdown formatting in the web app
+
## Scheduled delivery _Supported on:_ :material-android: :material-apple: :material-firefox: diff --git a/docs/static/img/web-markdown.png b/docs/static/img/web-markdown.png new file mode 100644 index 00000000..4e914fe8 Binary files /dev/null and b/docs/static/img/web-markdown.png differ diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index bd319dc5..e152cf20 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -15,6 +15,8 @@ import { IconButton, Box, Button, + useTheme, + ThemeProvider, } from "@mui/material"; import * as React from "react"; import { useEffect, useState } from "react"; @@ -37,7 +39,6 @@ import priority5 from "../img/priority-5.svg"; import logoOutline from "../img/ntfy-outline.svg"; import AttachmentIcon from "./AttachmentIcon"; import { useAutoSubscribe } from "./hooks"; -import prefs from "../app/Prefs"; const priorityFiles = { 1: priority1, @@ -174,7 +175,8 @@ const MarkdownContainer = styled("div")` p, pre, ul, - ol { + ol, + blockquote { margin: 0; } @@ -182,14 +184,19 @@ const MarkdownContainer = styled("div")` line-height: 1.2; } - blockquote { - margin: 0; - padding-inline: 1rem; - background: ${(theme) => (theme.mode === "light" ? "#f1f1f1" : "#aeaeae")}; + blockquote, + pre { + border-radius: 3px; + background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")}; + } + + pre { + padding: 0.9rem; } ul, - ol { + ol, + blockquote { padding-inline: 1rem; }