diff --git a/go.mod b/go.mod index 9386632..67621e3 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/disintegration/imaging v1.6.2 // indirect github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f // indirect github.com/go-redis/redis v6.15.9+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.4.3 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.1 // indirect diff --git a/go.sum b/go.sum index c62e5a4..e1cd0cc 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/pkg/config/variable.go b/pkg/config/variable.go index 5ba032e..1325d99 100644 --- a/pkg/config/variable.go +++ b/pkg/config/variable.go @@ -22,6 +22,7 @@ type Variable struct { Mail Mail Redis Redis Database Database + BareRTC BareRTC UseXForwardedFor bool } @@ -107,3 +108,9 @@ type Database struct { SQLite string Postgres string } + +// BareRTC chat room settings. +type BareRTC struct { + JWTSecret string + URL string +} diff --git a/pkg/controller/chat/chat.go b/pkg/controller/chat/chat.go new file mode 100644 index 0000000..51681e1 --- /dev/null +++ b/pkg/controller/chat/chat.go @@ -0,0 +1,89 @@ +package chat + +import ( + "fmt" + "net/http" + "strings" + "time" + + "code.nonshy.com/nonshy/website/pkg/config" + "code.nonshy.com/nonshy/website/pkg/photo" + "code.nonshy.com/nonshy/website/pkg/session" + "code.nonshy.com/nonshy/website/pkg/templates" + "github.com/golang-jwt/jwt/v4" +) + +// JWT claims. +type Claims struct { + // Custom claims. + IsAdmin bool `json:"op"` + Avatar string `json:"img"` + ProfileURL string `json:"url"` + + // Standard claims. Notes: + // subject = username + jwt.RegisteredClaims +} + +// Landing page for chat rooms. +func Landing() http.HandlerFunc { + tmpl := templates.Must("chat.html") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get the current user. + currentUser, err := session.CurrentUser(r) + if err != nil { + session.FlashError(w, r, "Couldn't get current user: %s", err) + templates.Redirect(w, "/") + return + } + + // Are they logging into the chat room? + var intent = r.FormValue("intent") + if intent == "join" { + // Get our Chat JWT secret. + var ( + secret = []byte(config.Current.BareRTC.JWTSecret) + chatURL = config.Current.BareRTC.URL + ) + if len(secret) == 0 || chatURL == "" { + session.FlashError(w, r, "Couldn't sign you into the chat: JWT secret key or chat URL not configured!") + templates.Redirect(w, r.URL.Path) + return + } + + // Create the JWT claims. + claims := Claims{ + IsAdmin: currentUser.IsAdmin, + Avatar: photo.URLPath(currentUser.ProfilePhoto.CroppedFilename), + ProfileURL: "/u/" + currentUser.Username, + RegisteredClaims: jwt.RegisteredClaims{ + // TODO: ExpiresAt 60 minutes to work around chat server reliability, + // should be shorter like 5 minutes. + ExpiresAt: jwt.NewNumericDate(time.Now().Add(60 * time.Minute)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: config.Title, + Subject: currentUser.Username, + ID: fmt.Sprintf("%d", currentUser.ID), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString(secret) + if err != nil { + session.FlashError(w, r, "Couldn't sign you into the chat: %s", err) + templates.Redirect(w, r.URL.Path) + return + } + + // Redirect them to the chat room. + templates.Redirect(w, strings.TrimSuffix(chatURL, "/")+"/?jwt="+ss) + return + } + + var vars = map[string]interface{}{} + if err := tmpl.Execute(w, r, vars); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) +} diff --git a/pkg/controller/index/static.go b/pkg/controller/index/static.go index 068a903..5158424 100644 --- a/pkg/controller/index/static.go +++ b/pkg/controller/index/static.go @@ -6,44 +6,16 @@ import ( "code.nonshy.com/nonshy/website/pkg/templates" ) -// Static pages. - -func About() http.HandlerFunc { - tmpl := templates.Must("about.html") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := tmpl.Execute(w, r, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func FAQ() http.HandlerFunc { - tmpl := templates.Must("faq.html") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := tmpl.Execute(w, r, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func TOS() http.HandlerFunc { - tmpl := templates.Must("tos.html") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := tmpl.Execute(w, r, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) -} - -func Privacy() http.HandlerFunc { - tmpl := templates.Must("privacy.html") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := tmpl.Execute(w, r, nil); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) +// StaticTemplate creates a simple controller that loads a Go html/template +// such as "about.html" relative to the web/templates path. +func StaticTemplate(filename string) func() http.HandlerFunc { + return func() http.HandlerFunc { + tmpl := templates.Must(filename) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := tmpl.Execute(w, r, nil); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + } } diff --git a/pkg/router/router.go b/pkg/router/router.go index a2f9242..ec87181 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -9,6 +9,7 @@ import ( "code.nonshy.com/nonshy/website/pkg/controller/admin" "code.nonshy.com/nonshy/website/pkg/controller/api" "code.nonshy.com/nonshy/website/pkg/controller/block" + "code.nonshy.com/nonshy/website/pkg/controller/chat" "code.nonshy.com/nonshy/website/pkg/controller/comment" "code.nonshy.com/nonshy/website/pkg/controller/forum" "code.nonshy.com/nonshy/website/pkg/controller/friend" @@ -25,16 +26,17 @@ func New() http.Handler { // Register controller endpoints. mux.HandleFunc("/", index.Create()) mux.HandleFunc("/favicon.ico", index.Favicon()) - mux.HandleFunc("/about", index.About()) - mux.HandleFunc("/faq", index.FAQ()) - mux.HandleFunc("/tos", index.TOS()) - mux.HandleFunc("/privacy", index.Privacy()) + mux.HandleFunc("/about", index.StaticTemplate("about.html")()) + mux.HandleFunc("/faq", index.StaticTemplate("faq.html")()) + mux.HandleFunc("/tos", index.StaticTemplate("tos.html")()) + mux.HandleFunc("/privacy", index.StaticTemplate("privacy.html")()) mux.HandleFunc("/contact", index.Contact()) mux.HandleFunc("/login", account.Login()) mux.HandleFunc("/logout", account.Logout()) mux.HandleFunc("/signup", account.Signup()) mux.HandleFunc("/forgot-password", account.ForgotPassword()) mux.HandleFunc("/settings/confirm-email", account.ConfirmEmailChange()) + mux.HandleFunc("/markdown", index.StaticTemplate("markdown.html")()) // Login Required. Pages that non-certified users can access. mux.Handle("/me", middleware.LoginRequired(account.Dashboard())) @@ -64,6 +66,7 @@ func New() http.Handler { // Certification Required. Pages that only full (verified) members can access. mux.Handle("/photo/gallery", middleware.CertRequired(photo.SiteGallery())) mux.Handle("/members", middleware.CertRequired(account.Search())) + mux.Handle("/chat", middleware.CertRequired(chat.Landing())) mux.Handle("/forum", middleware.CertRequired(forum.Landing())) mux.Handle("/forum/post", middleware.CertRequired(forum.NewPost())) mux.Handle("/forum/thread/", middleware.CertRequired(forum.Thread())) diff --git a/web/templates/account/settings.html b/web/templates/account/settings.html index 3186667..f87ca19 100644 --- a/web/templates/account/settings.html +++ b/web/templates/account/settings.html @@ -173,7 +173,7 @@ name="about_me" placeholder="A little blurb about myself">{{$User.GetProfileField "about_me"}}

- Write a bit about yourself. Markdown formatting is supported here. + Write a bit about yourself. Markdown formatting is supported here.

diff --git a/web/templates/base.html b/web/templates/base.html index 54daf1c..b4efafc 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -49,6 +49,11 @@ Home + + + Chat + + Forum diff --git a/web/templates/chat.html b/web/templates/chat.html new file mode 100644 index 0000000..9681773 --- /dev/null +++ b/web/templates/chat.html @@ -0,0 +1,64 @@ +{{define "title"}}Chat Rooms{{end}} +{{define "content"}} +
+
+
+
+

+ + Chat Rooms (Beta) + NEW! +

+
+
+
+
+ +
+
+

+ {{PrettyTitle}} has a new chat room! Come and check it out. It features some public rooms, direct + messages, and optional webcam support too. You may broadcast your video and other users may click + to watch yours, and you can open one or multiple videos broadcasted by the other chatters. +

+ +

+ This chat room is currently ready for beta testing. It's a very new app built + specifically for {{PrettyTitle}} and may still be lacking in some features and may be rough around + the edges. Give it a try and send feedback if you run into any technical + issues with the chat room! +

+ +

Chat Room Rules

+ +

+ Please observe the following rules for the chat room. +

+ + + +

+ Click on the button below to join the chat room: +

+ +

+ + Join the Chat Room Now + + +

+
+ +
+{{end}} \ No newline at end of file diff --git a/web/templates/comment/post_comment.html b/web/templates/comment/post_comment.html index 7f8fb05..92ec663 100644 --- a/web/templates/comment/post_comment.html +++ b/web/templates/comment/post_comment.html @@ -60,7 +60,7 @@ required placeholder="Message">{{.Message}}

- Markdown formatting supported. + Markdown formatting supported.

diff --git a/web/templates/forum/add_edit.html b/web/templates/forum/add_edit.html index e609c08..dc9ac4c 100644 --- a/web/templates/forum/add_edit.html +++ b/web/templates/forum/add_edit.html @@ -62,7 +62,7 @@ name="description" id="description" placeholder="A short description of the forum.">{{if .EditForum}}{{.EditForum.Description}}{{end}}

- Write a short description of the forum. Markdown formatting + Write a short description of the forum. Markdown formatting is supported here.

diff --git a/web/templates/forum/new_post.html b/web/templates/forum/new_post.html index 1c268cd..ae0cd28 100644 --- a/web/templates/forum/new_post.html +++ b/web/templates/forum/new_post.html @@ -77,7 +77,7 @@ {{if not .Forum.PermitPhotos}}required{{end}} placeholder="Message">{{.Message}}

- Markdown formatting supported. + Markdown formatting supported.

diff --git a/web/templates/forum/thread.html b/web/templates/forum/thread.html index 04e9e7d..a3827c4 100644 --- a/web/templates/forum/thread.html +++ b/web/templates/forum/thread.html @@ -342,7 +342,7 @@ {{if not .Forum.PermitPhotos}}required{{end}} placeholder="Message">

- Markdown formatting supported. + Markdown formatting supported.

diff --git a/web/templates/inbox/compose.html b/web/templates/inbox/compose.html index cdf07a0..c15a53f 100644 --- a/web/templates/inbox/compose.html +++ b/web/templates/inbox/compose.html @@ -49,7 +49,7 @@ required placeholder="Message">

- Markdown formatting supported. + Markdown formatting supported.

diff --git a/web/templates/inbox/inbox.html b/web/templates/inbox/inbox.html index ca90a1d..a9bb84c 100644 --- a/web/templates/inbox/inbox.html +++ b/web/templates/inbox/inbox.html @@ -37,6 +37,9 @@ +

+ Markdown formatting supported. +

diff --git a/web/templates/markdown.html b/web/templates/markdown.html new file mode 100644 index 0000000..2de009f --- /dev/null +++ b/web/templates/markdown.html @@ -0,0 +1,732 @@ +{{ define "title" }}Markdown Cheatsheet{{ end }} +{{ define "content" }} +
+
+
+
+

+ + Markdown Cheatsheet +

+
+
+
+
+ +
+ +

+ Markdown syntax can enable you to be more expressive with your text posts on this + website, with the ability to write **bold** text, add hyperlinks or + embed images onto your profile page, Direct Messages, forum posts and elsewhere - any place on + {{PrettyTitle}} that says "Markdown formatting supported." +

+ +

+ This is a simple reference sheet for Markdown syntax. Markdown was pioneered by John Gruber and the + de facto place to find in-depth documentation is at + https://daringfireball.net/projects/markdown/syntax + where it was originally described. +

+ +

+ Markdown is now a widely supported format across a variety of apps and websites, and there are a + few different "flavors" of Markdown with slightly varied behaviors. This website uses + GitHub Flavored Markdown, an extension of Markdown + that supports fenced code blocks, tables, and other useful features - many of which you can learn + about on this page. +

+ + + +

Block Elements

+ + +

Paragraphs and Line Breaks

+ +

A paragraph is defined as a group of lines of text separated from other groups +by at least one blank line. A hard return inside a paragraph doesn't get rendered +in the output.

+ +

Headers

+ +

There are two methods to declare a header in Markdown: "underline" it by +writing === or --- on the line directly below the +heading (for <h1> and <h2>, respectively), +or by prefixing the heading with # symbols. Usually the latter +option is the easiest, and you can get more levels of headers this way.

+ + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+
This is an H1
+=============
+
+This is an H2
+-------------
+
+

This is an H1

+

This is an H2

+
+
# This is an H1
+## This is an H2
+#### This is an H4
+
+

This is an H1

+

This is an H2

+

This is an H4

+
+ +

Blockquotes

+ +

Prefix a line of text with > to "quote" it -- like in +"e-mail syntax."

+ +

You may have multiple layers of quotes by using multiple > +symbols.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+>
+> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+> id sem consectetuer libero luctus adipiscing.
+				
+
+
+

This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

+ +

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing.

+
+
+ + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

+ + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+ id sem consectetuer libero luctus adipiscing. +
+
+
+

This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

+ +

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing.

+
+
+ + > This is the first level of quoting.
+ >
+ >> This is nested blockquote.
+ >>> A third level.
+ >
+ > Back to the first level. +
+
+
+ This is the first level of quoting. +
+ This is nested blockquote. +
+ A third level. +
+
+ Back to the first level. +
+
+ + > ## This is a header.
+ >
+ > 1. This is the first list item.
+ > 2. This is the second list item.
+ >
+ >Here's some example code:
+ >
+ >    return shell_exec("echo $input | $markdown_script"); +
+
+
+

This is a header.

+
    +
  1. This is the first list item.
  2. +
  3. This is the second list item.
  4. +
+ Here's some example code: +
return shell_exec("echo $input | $markdown_script");
+
+
+ + +

Lists

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + * Red
+ * Green
+ * Blue +
+
+
    +
  • Red
  • +
  • Green
  • +
  • Blue
  • +
+
+ + + Red
+ + Green
+ + Blue +
+
+
    +
  • Red
  • +
  • Green
  • +
  • Blue
  • +
+
+ + - Red
+ - Green
+ - Blue +
+
+
    +
  • Red
  • +
  • Green
  • +
  • Blue
  • +
+
+ + 1. Bird
+ 2. McHale
+ 3. Parish +
+
+
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+
+ + 1.  This is a list item with two paragraphs. Lorem ipsum dolor
+     sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+     mi posuere lectus.

+ +     Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+     vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+     sit amet velit.

+ + 2.  Suspendisse id sem consectetuer libero luctus adipiscing. + +

+
    +
  1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus.

    + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit.

  2. + +
  3. Suspendisse id sem consectetuer libero luctus adipiscing.
  4. +
+
+ + +

Code Blocks

+ +The typical Markdown way to write a code block is to indent each line of a paragraph with at +least 4 spaces or 1 tab character. The Rophako CMS also uses GitHub-style code blocks, where +you can use three backticks before and after the code block and then you don't need to indent +each line of the code (makes copying/pasting easier!)

+ +Like GitHub-flavored Markdown, with a fenced code block you can also specify a programming +language to get syntax highlighting for the code.

+ + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + This is a normal paragraph.

+ +     This is a code block. + +

+ This is a normal paragraph.

+ +

This is a code block
+
+ + This is a normal paragraph.

+ + ```
+ This is a GitHub style "fenced code block".
+ ``` +
+

+ This is a normal paragraph.

+ +

This is a GitHub style "fenced code block".
+
+ + ```javascript
+ document.writeln("Hello world.");
+ ``` +
+
+
document.writeln("Hello world.");
+
+ + +

Horizontal Rules

+ + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + * * *

+ ***

+ *****

+ - - -

+ --------------------------- + +

+

+


+


+


+


+
+ + + +

Span Elements

+ + +

Links

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + This is [an example](http://example.com/ "Title") inline link.

+ [This link](http://example.net/) has no title attribute. + +

+ This is an example inline link.

+ This link has no title attribute. +

+ + See my [About](/about) page for details. + + + See my About page for details. +
+ + This is [an example][id] reference-style link.

+ [id]: http://example.com/ "Optional Title Here" + +

+ This is an example reference-style link. +
+ + This is an example of an implicit reference-style link: search [Google][] for more.

+ [Google]: http://google.com/ + +

+ This is an example of an implicit reference-style link: search Google for more. +
+ + I get 10 times more traffic from [Google] [1] than from
+ [Yahoo] [2] or [Bing] [3].

+ + [1]: http://google.com/ "Google"
+ [2]: http://search.yahoo.com/ "Yahoo Search"
+ [3]: http://bing.com/ "Bing" +
+

+ I get 10 times more traffic from Google than from + Yahoo or + Bing. +
+ + +

Emphasis

+ + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + *single asterisks*

+ _single underscores_

+ **double asterisks**

+ __double underscores__ + +

+ single asterisks

+ single underscores

+ double asterisks

+ double underscores +

+ + un*frigging*believable + + + unfriggingbelievable +
+ + \*this text is surrounded by literal asterisks\* + + + *this text is surrounded by literal asterisks* +
+ + +

Code

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ + Use the `printf()` function. + + + Use the printf() function. +
+ + ``There is a literal backtick (`) here.`` + + + There is a literal backtick (`) here. +
+ + A single backtick in a code span: `` ` ``

+ A backtick-delimited string in a code span: `` `foo` `` + +

+ A single backtick in a code span: `

+ A backtick-delimited string in a code span: `foo` +

+ Please don't use any `<blink>` tags. + + Please don't use any <blink> tags. +
+ `&#8212;` is the decimal-encoded equivalent of `&mdash;`. + + &#8212; is the decimal-encoded equivalent of + &mdash;. +
+ + +

Images

+ + + + + + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ ![Alt text](/static/avatars/default.png) + + Alt text +
+ ![Alt text](/static/avatars/default.png "Optional title") + + Alt text +
+ + ![Alt text][id]

+ [id]: /static/avatars/default.png "Optional title attribute" + +

+ Alt text +
+ + +

Miscellaneous

+ + +

Automatic Links

+ +E-mail links get automatically converted into a random mess of HTML attributes to +attempt to thwart e-mail harvesting spam bots.

+ + + + + + + + + + + + + + + + + + +
Markdown SyntaxOutput
+ <http://example.com/> + + http://example.com/ +
+ <address@example.com> + + address@example.com

+ + (Source: <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58; &#97;&#100;&#100;&#114;&#101;&#115;&#115;&#64; &#101;&#120;&#97;&#109;&#112;&#108; &#101;&#46;&#99;&#111;&#109;">&#97; &#100;&#100;&#114;&#101;&#115;&#115; &#64;&#101;&#120;&#97;&#109;&#112; &#108;&#101;&#46;&#99;&#111; &#109;</a>) +

+ + +

Backslash Escapes

+ +Use backslash characters to escape any other special characters in the Markdown syntax. For example, +\* to insert a literal asterisk so that it doesn't get mistaken for e.g. emphasized text, +a list item, etc.

+ +Markdown provides backslash escapes for the following characters:

+ +

\   backslash
+`   backtick
+*   asterisk
+_   underscore
+{}  curly braces
+[]  square brackets
+()  parenthesis
+#   hash mark
++   plus sign
+-   minus sign (hyphen)
+.   dot
+!   exclamation mark
+ +
+{{ end }} diff --git a/web/templates/photo/permalink.html b/web/templates/photo/permalink.html index 89f21a3..8768539 100644 --- a/web/templates/photo/permalink.html +++ b/web/templates/photo/permalink.html @@ -158,7 +158,7 @@ placeholder="Add your comment" required>

- Markdown formatting supported. + Markdown formatting supported.