A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

212 lines
7.9 KiB

  1. {{define "collection-tags"}}<!DOCTYPE HTML>
  2. <html>
  3. <head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
  4. <meta charset="utf-8">
  5. <title>{{.Tag}} &mdash; {{.Collection.DisplayTitle}}</title>
  6. <link rel="stylesheet" type="text/css" href="/css/write.css" />
  7. {{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
  8. <link rel="shortcut icon" href="/favicon.ico" />
  9. {{if not .Collection.IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.Tag}} posts on {{.DisplayTitle}}" href="{{.CanonicalURL}}tag:{{.Tag}}/feed/" />{{end}}
  10. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  11. <link rel="canonical" href="{{.CanonicalURL}}tag:{{.Tag | tolower}}" />
  12. <meta name="generator" content="Write.as">
  13. <meta name="title" content="{{.Tag}} &mdash; {{.Collection.DisplayTitle}}">
  14. <meta name="description" content="{{.Tag}} posts on {{.Collection.DisplayTitle}}">
  15. <meta name="application-name" content="Write.as">
  16. <meta name="application-url" content="https://write.as">
  17. {{if gt .Views 1}}<meta name="twitter:label1" value="Views">
  18. <meta name="twitter:data1" value="{{largeNumFmt .Views}}">{{end}}
  19. <meta itemprop="name" content="{{.Collection.DisplayTitle}}">
  20. <meta itemprop="description" content="{{.Tag}} posts on {{.Collection.DisplayTitle}}">
  21. <meta name="twitter:card" content="summary">
  22. <meta name="twitter:site" content="@writeas__">
  23. <meta name="twitter:description" content="{{.Tag}} posts on {{.Collection.DisplayTitle}}">
  24. <meta name="twitter:title" content="{{.Tag}} &mdash; {{.Collection.DisplayTitle}}">
  25. <meta name="twitter:image" content="{{.Collection.AvatarURL}}">
  26. <meta property="og:title" content="{{.Tag}} &mdash; {{.Collection.DisplayTitle}}" />
  27. <meta property="og:site_name" content="{{.DisplayTitle}}" />
  28. <meta property="og:type" content="article" />
  29. <meta property="og:url" content="{{.CanonicalURL}}tag:{{.Tag}}" />
  30. <meta property="og:image" content="{{.Collection.AvatarURL}}">
  31. {{template "collection-meta" .}}
  32. {{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
  33. {{if .Collection.RenderMathJax}}
  34. <!-- Add mathjax logic -->
  35. {{template "mathjax" .}}
  36. {{end}}
  37. <!-- Add highlighting logic -->
  38. {{template "highlighting" . }}
  39. </head>
  40. <body id="subpage">
  41. <div id="overlay"></div>
  42. <header>
  43. <h1 dir="{{.Direction}}" id="blog-title"><a href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1>
  44. <nav>
  45. {{if .PinnedPosts}}
  46. {{range .PinnedPosts}}<a class="pinned" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL $.Host}}{{end}}">{{.DisplayTitle}}</a>{{end}}
  47. {{end}}
  48. </nav>
  49. </header>
  50. {{if .Silenced}}
  51. {{template "user-silenced"}}
  52. {{end}}
  53. {{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}}
  54. <h1>{{.Tag}}</h1>
  55. {{template "posts" .}}
  56. {{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
  57. {{if or (and .Format.Ascending (lt .CurrentPage .TotalPages)) (isRTL .Direction)}}
  58. {{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">&#8672; {{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
  59. {{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Next{{else}}Older{{end}} &#8674;</a>{{end}}
  60. {{else}}
  61. {{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">&#8672; Older</a>{{end}}
  62. {{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">Newer &#8674;</a>{{end}}
  63. {{end}}
  64. </nav>{{end}}
  65. {{if .Posts}}</section>{{else}}</div>{{end}}
  66. {{ if .Collection.ShowFooterBranding }}
  67. <footer dir="ltr">
  68. <hr>
  69. <nav>
  70. <p style="font-size: 0.9em"><a class="home pubd" href="/">{{.SiteName}}</a> &middot; powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a></p>
  71. </nav>
  72. </footer>
  73. {{ end }}
  74. </body>
  75. {{if .CanShowScript}}
  76. {{range .ExternalScripts}}<script type="text/javascript" src="{{.}}" async></script>{{end}}
  77. {{if .Collection.Script}}<script type="text/javascript">{{.ScriptDisplay}}</script>{{end}}
  78. {{end}}
  79. <script src="/js/localdate.js"></script>
  80. {{if .IsOwner}}
  81. <script src="/js/h.js"></script>
  82. <script src="/js/postactions.js"></script>
  83. {{end}}
  84. <script type="text/javascript">
  85. {{if .IsOwner}}
  86. var deleting = false;
  87. function delPost(e, id, owned) {
  88. e.preventDefault();
  89. if (deleting) {
  90. return;
  91. }
  92. // TODO: UNDO!
  93. if (window.confirm('Are you sure you want to delete this post?')) {
  94. // AJAX
  95. deletePost(id, "", function() {
  96. // Remove post from list
  97. var $postEl = document.getElementById('post-' + id);
  98. $postEl.parentNode.removeChild($postEl);
  99. // TODO: add next post from this collection at the bottom
  100. });
  101. }
  102. }
  103. var deletePost = function(postID, token, callback) {
  104. deleting = true;
  105. var $delBtn = document.getElementById('post-' + postID).getElementsByClassName('delete action')[0];
  106. $delBtn.innerHTML = '...';
  107. var http = new XMLHttpRequest();
  108. var url = "/api/posts/" + postID;
  109. http.open("DELETE", url, true);
  110. http.onreadystatechange = function() {
  111. if (http.readyState == 4) {
  112. deleting = false;
  113. if (http.status == 204) {
  114. callback();
  115. } else if (http.status == 409) {
  116. $delBtn.innerHTML = 'delete';
  117. alert("Post is synced to another account. Delete the post from that account instead.");
  118. // TODO: show "remove" button instead of "delete" now
  119. // Persist that state.
  120. // Have it remove the post locally only.
  121. } else {
  122. $delBtn.innerHTML = 'delete';
  123. alert("Failed to delete." + (http.status>=500?" Please try again.":""));
  124. }
  125. }
  126. }
  127. http.send();
  128. };
  129. var pinning = false;
  130. function pinPost(e, postID, slug, title) {
  131. e.preventDefault();
  132. if (pinning) {
  133. return;
  134. }
  135. pinning = true;
  136. var callback = function() {
  137. // Visibly remove post from collection
  138. var $postEl = document.getElementById('post-' + postID);
  139. $postEl.parentNode.removeChild($postEl);
  140. var $header = document.getElementsByTagName('header')[0];
  141. var $pinnedNavs = $header.getElementsByTagName('nav');
  142. // Add link to nav
  143. var link = '<a class="pinned" href="{{if not .SingleUser}}/{{.Alias}}/{{end}}'+slug+'">'+title+'</a>';
  144. if ($pinnedNavs.length == 0) {
  145. $header.insertAdjacentHTML("beforeend", '<nav>'+link+'</nav>');
  146. } else {
  147. $pinnedNavs[0].insertAdjacentHTML("beforeend", link);
  148. }
  149. };
  150. var $pinBtn = document.getElementById('post-' + postID).getElementsByClassName('pin action')[0];
  151. $pinBtn.innerHTML = '...';
  152. var http = new XMLHttpRequest();
  153. var url = "/api/collections/{{.Alias}}/pin";
  154. var params = [ { "id": postID } ];
  155. http.open("POST", url, true);
  156. http.setRequestHeader("Content-type", "application/json");
  157. http.onreadystatechange = function() {
  158. if (http.readyState == 4) {
  159. pinning = false;
  160. if (http.status == 200) {
  161. callback();
  162. } else if (http.status == 409) {
  163. $pinBtn.innerHTML = 'pin';
  164. alert("Post is synced to another account. Delete the post from that account instead.");
  165. // TODO: show "remove" button instead of "delete" now
  166. // Persist that state.
  167. // Have it remove the post locally only.
  168. } else {
  169. $pinBtn.innerHTML = 'pin';
  170. alert("Failed to pin." + (http.status>=500?" Please try again.":""));
  171. }
  172. }
  173. }
  174. http.send(JSON.stringify(params));
  175. };
  176. {{end}}
  177. try { // Fonts
  178. WebFontConfig = {
  179. custom: { families: [ 'Lora:400,700:latin', 'Open+Sans:400,700:latin' ], urls: [ '/css/fonts.css' ] }
  180. };
  181. (function() {
  182. var wf = document.createElement('script');
  183. wf.src = '/js/webfont.js';
  184. wf.type = 'text/javascript';
  185. wf.async = 'true';
  186. var s = document.getElementsByTagName('script')[0];
  187. s.parentNode.insertBefore(wf, s);
  188. })();
  189. } catch (e) { /* ¯\_(ツ)_/¯ */ }
  190. </script>
  191. </html>{{end}}