mirror of
https://github.com/journeyman-cc/smeagol.git
synced 2026-04-12 18:05:06 +00:00
Tidy-up - mainly because I discovered I was generating pages which did not
fully validate as HTML. Fixed.
This commit is contained in:
parent
1e7ad73fa4
commit
09aa1bdc90
10 changed files with 68 additions and 52 deletions
|
|
@ -20,7 +20,7 @@ Security is now greatly improved. There is a file called *passwd* in the *resour
|
||||||
{:admin {:password "admin" :email "admin@localhost"}
|
{:admin {:password "admin" :email "admin@localhost"}
|
||||||
:adam {:password "secret" :email "adam@localhost"}}
|
:adam {:password "secret" :email "adam@localhost"}}
|
||||||
|
|
||||||
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, and is no longer in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
|
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, nor in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
|
||||||
|
|
||||||
There's still no mechanism to add a new user to the system through the user interface; you do sill have to do that by editing the password file in an editor.
|
There's still no mechanism to add a new user to the system through the user interface; you do sill have to do that by editing the password file in an editor.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||||
[org.clojure/core.memoize "0.5.6"]
|
[org.clojure/core.memoize "0.5.6"]
|
||||||
[lib-noir "0.9.4" :exclusions [org.clojure/tools.reader]]
|
[lib-noir "0.9.4" :exclusions [org.clojure/tools.reader]]
|
||||||
|
[com.cemerick/url "0.1.1"]
|
||||||
[ring-server "0.3.1"]
|
[ring-server "0.3.1"]
|
||||||
[selmer "0.7.2"]
|
[selmer "0.7.2"]
|
||||||
[com.taoensso/timbre "3.3.1" :exclusions [org.clojure/tools.reader]]
|
[com.taoensso/timbre "3.3.1" :exclusions [org.clojure/tools.reader]]
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Security is now greatly improved. There is a file called *passwd* in the *resour
|
||||||
{:admin {:password "admin" :email "admin@localhost"}
|
{:admin {:password "admin" :email "admin@localhost"}
|
||||||
:adam {:password "secret" :email "adam@localhost"}}
|
:adam {:password "secret" :email "adam@localhost"}}
|
||||||
|
|
||||||
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, and is no longer in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
|
that is to say, the username is a keyword and the corresponding password is a string. However, since version 0.5.0, users can now change their own passwords, and when the user changes their password their new password is encrypted using the [scrypt](http://www.tarsnap.com/scrypt.html) one-way encryption scheme. The password file is now no longer either in the *resources/public* directory so cannot be downloaded through the browser, nor in the git archive to which the Wiki content is stored, so that even if that git archive is remotely clonable an attacker cannot get the password file that way.
|
||||||
|
|
||||||
There's still no mechanism to add a new user to the system through the user interface; you do sill have to do that by editing the password file in an editor.
|
There's still no mechanism to add a new user to the system through the user interface; you do sill have to do that by editing the password file in an editor.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
This is the header. There isn't yet much in it. You could [edit](edit?content=_header) it to provide internal navigation or branding.
|
This is the header. There isn't yet much in it. You could [edit](edit?page=_header) it to provide internal navigation or branding.
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
This is the left bar. There's nothing in it yet. You could [edit](edit?content=_left-bar) it to provide internal navigation or branding.
|
This is the left bar. There's nothing in it yet. You could [edit](edit?page=_left-bar) it to provide internal navigation or branding.
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ li.nav-item a:active { background: gray; color: white; }
|
||||||
/* padding: 0.1em 10%; */
|
/* padding: 0.1em 10%; */
|
||||||
bottom:0;
|
bottom:0;
|
||||||
position:fixed;
|
position:fixed;
|
||||||
|
vertical-align: top;
|
||||||
z-index:150;
|
z-index:150;
|
||||||
_position:absolute;
|
_position:absolute;
|
||||||
_top:expression(eval(document.documentElement.scrollTop+
|
_top:expression(eval(document.documentElement.scrollTop+
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ body {
|
||||||
|
|
||||||
#nav-icon {
|
#nav-icon {
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-menu {
|
#nav-menu {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>{{title}}</title>
|
<title>{{title}}</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<title>{{title}}</title>
|
|
||||||
<link href="{{servlet-context}}/css/phone.css" media="only screen and (max-device-width: 480px)" rel="stylesheet" type="text/css" />
|
<link href="{{servlet-context}}/css/phone.css" media="only screen and (max-device-width: 480px)" rel="stylesheet" type="text/css" />
|
||||||
<link href="{{servlet-context}}/css/tablet.css" media="only screen and (min-device-width: 481px) and (max-device-width: 1024px)" rel="stylesheet" type="text/css" />
|
<link href="{{servlet-context}}/css/tablet.css" media="only screen and (min-device-width: 481px) and (max-device-width: 1024px)" rel="stylesheet" type="text/css" />
|
||||||
<link href="{{servlet-context}}/css/standard.css" media="screen and (min-device-width: 1025px)" rel="stylesheet" type="text/css" />
|
<link href="{{servlet-context}}/css/standard.css" media="screen and (min-device-width: 1025px)" rel="stylesheet" type="text/css" />
|
||||||
|
|
@ -11,25 +10,25 @@
|
||||||
<link href="{{servlet-context}}/css/states.css" rel="stylesheet" type="text/css" />
|
<link href="{{servlet-context}}/css/states.css" rel="stylesheet" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- navbar -->
|
|
||||||
<div id="nav">
|
|
||||||
{% if user %}
|
|
||||||
<p class="user" id="user">You are logged in as {{user}} | <a href="passwd">change password</a></p>
|
|
||||||
{% endif %}
|
|
||||||
<img id="nav-icon" src="{{servlet-context}}/img/threelines.png" alt="Menu"/>
|
|
||||||
<ul id="nav-menu" class="nav">
|
|
||||||
<li class="{{wiki-selected}}"><a href="{{servlet-context}}/">Home</a></li>
|
|
||||||
<li class="{{edit-selected}}"><a href="{{servlet-context}}/edit?page={{title}}">Edit this page</a></li>
|
|
||||||
<li class="{{auth-selected}}"><a href="{{servlet-context}}/auth">
|
|
||||||
{% if user %}
|
|
||||||
Log out
|
|
||||||
{% else %}
|
|
||||||
Log in
|
|
||||||
{% endif %}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="header" class="wiki">
|
<div id="header" class="wiki">
|
||||||
|
<!-- navbar -->
|
||||||
|
<div id="nav">
|
||||||
|
{% if user %}
|
||||||
|
<p class="user" id="user">You are logged in as {{user}} | <a href="passwd">change password</a></p>
|
||||||
|
{% endif %}
|
||||||
|
<img id="nav-icon" src="{{servlet-context}}/img/threelines.png" alt="Menu"/>
|
||||||
|
<ul id="nav-menu" class="nav">
|
||||||
|
<li class="{{wiki-selected}}"><a href="{{servlet-context}}/">Home</a></li>
|
||||||
|
<li class="{{edit-selected}}"><a href="{{servlet-context}}/edit?page={{title}}">Edit this page</a></li>
|
||||||
|
<li class="{{auth-selected}}"><a href="{{servlet-context}}/auth">
|
||||||
|
{% if user %}
|
||||||
|
Log out
|
||||||
|
{% else %}
|
||||||
|
Log in
|
||||||
|
{% endif %}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
{{header|safe}}
|
{{header|safe}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -56,10 +55,10 @@
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div id="credits">
|
<div id="credits">
|
||||||
One Wiki to rule them all ||
|
One Wiki to rule them all ||
|
||||||
<img height="16" width="16" align="top" src="img/smeagol.png"/>Smeagol wiki engine || Built with <a href="http://www.luminusweb.net/">LuminusWeb</a> ||
|
<img height="16" width="16" alt="one wiki to rule them all" src="img/smeagol.png"/>Smeagol wiki engine || Built with <a href="http://www.luminusweb.net/">LuminusWeb</a> ||
|
||||||
<img height="16" width="16" align="top" src="img/clojure-icon.gif"/> Powered by <a href="http://clojure.org">Clojure</a> ||
|
<img height="16" width="16" alt="Clojure" src="img/clojure-icon.gif"/> Powered by <a href="http://clojure.org">Clojure</a> ||
|
||||||
<img height="16" width="16" align="top" src="img/github-logo-transparent.png"/>Find me/fork me on <a href="https://github.com/simon-brooke/smeagol">Github</a> ||
|
<img height="16" width="16" alt="GitHub" src="img/github-logo-transparent.png"/>Find me/fork me on <a href="https://github.com/simon-brooke/smeagol">Github</a> ||
|
||||||
<img height="16" width="16" align="top" src="img/gnu.small.png"/>Licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU General Public License version 2.0</a>
|
<img height="16" width="16" alt="Free Software Foundation" src="img/gnu.small.png"/>Licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU General Public License version 2.0</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
(ns smeagol.history
|
(ns smeagol.history
|
||||||
(:require [clj-jgit.porcelain :as git]
|
(:require [taoensso.timbre :as timbre]
|
||||||
|
[clj-jgit.porcelain :as git]
|
||||||
[clj-jgit.internal :as i]
|
[clj-jgit.internal :as i]
|
||||||
[clj-jgit.querying :as q])
|
[clj-jgit.querying :as q])
|
||||||
(:import [org.eclipse.jgit.api Git]
|
(:import [org.eclipse.jgit.api Git]
|
||||||
[org.eclipse.jgit.lib Repository ObjectId]
|
[org.eclipse.jgit.lib Repository ObjectId]
|
||||||
[org.eclipse.jgit.revwalk RevCommit RevTree RevWalk]
|
[org.eclipse.jgit.revwalk RevCommit RevTree RevWalk]
|
||||||
[org.eclipse.jgit.treewalk TreeWalk AbstractTreeIterator CanonicalTreeParser]
|
[org.eclipse.jgit.treewalk TreeWalk AbstractTreeIterator CanonicalTreeParser]
|
||||||
[org.eclipse.jgit.treewalk.filter PathFilter]
|
[org.eclipse.jgit.treewalk.filter PathFilter]
|
||||||
[org.eclipse.jgit.diff DiffEntry DiffFormatter]))
|
[org.eclipse.jgit.diff DiffEntry DiffFormatter]))
|
||||||
|
|
||||||
;; Smeagol: a very simple Wiki engine
|
;; Smeagol: a very simple Wiki engine
|
||||||
;; Copyright (C) 2014 Simon Brooke
|
;; Copyright (C) 2014 Simon Brooke
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
"If this `log-entry` contains a reference to this `file-path`, return the entry;
|
"If this `log-entry` contains a reference to this `file-path`, return the entry;
|
||||||
else nil."
|
else nil."
|
||||||
[^String log-entry ^String file-path]
|
[^String log-entry ^String file-path]
|
||||||
|
(timbre/info (format "searching '%s' for '%s'" log-entry file-path))
|
||||||
(cond
|
(cond
|
||||||
(not
|
(not
|
||||||
(empty?
|
(empty?
|
||||||
|
|
@ -60,19 +62,19 @@
|
||||||
reader (.newObjectReader (.getRepository repo))]
|
reader (.newObjectReader (.getRepository repo))]
|
||||||
(try
|
(try
|
||||||
(.reset result reader (.getId tree))
|
(.reset result reader (.getId tree))
|
||||||
(finally
|
(finally
|
||||||
(.release reader)
|
(.release reader)
|
||||||
(.dispose walk)))
|
(.dispose walk)))
|
||||||
result))
|
result))
|
||||||
|
|
||||||
(defn diff
|
(defn diff
|
||||||
"Find the diff in the file at `file-path` within the repository at
|
"Find the diff in the file at `file-path` within the repository at
|
||||||
`git-directory-path` between versions `older` and `newer` or between the specified
|
`git-directory-path` between versions `older` and `newer` or between the specified
|
||||||
`version` and the current version of the file. Returns the diff as a string.
|
`version` and the current version of the file. Returns the diff as a string.
|
||||||
|
|
||||||
Based on JGit Cookbook ShowFileDiff."
|
Based on JGit Cookbook ShowFileDiff."
|
||||||
([^String git-directory-path ^String file-path ^String version]
|
([^String git-directory-path ^String file-path ^String version]
|
||||||
(diff git-directory-path file-path version
|
(diff git-directory-path file-path version
|
||||||
(:id (first (find-history git-directory-path file-path)))))
|
(:id (first (find-history git-directory-path file-path)))))
|
||||||
([^String git-directory-path ^String file-path ^String older ^String newer]
|
([^String git-directory-path ^String file-path ^String older ^String newer]
|
||||||
(let [git-r (git/load-repo git-directory-path)
|
(let [git-r (git/load-repo git-directory-path)
|
||||||
|
|
@ -86,8 +88,8 @@
|
||||||
%)
|
%)
|
||||||
(.call
|
(.call
|
||||||
(.setOutputStream
|
(.setOutputStream
|
||||||
(.setPathFilter
|
(.setPathFilter
|
||||||
(.setNewTree
|
(.setNewTree
|
||||||
(.setOldTree (.diff git-r) old-parse)
|
(.setOldTree (.diff git-r) old-parse)
|
||||||
new-parse)
|
new-parse)
|
||||||
(PathFilter/create file-path))
|
(PathFilter/create file-path))
|
||||||
|
|
@ -110,9 +112,8 @@
|
||||||
(.addTree tw tree)
|
(.addTree tw tree)
|
||||||
(.setRecursive tw true)
|
(.setRecursive tw true)
|
||||||
(.setFilter tw (PathFilter/create file-path))
|
(.setFilter tw (PathFilter/create file-path))
|
||||||
(if (not (.next tw))
|
(if (not (.next tw))
|
||||||
(throw (IllegalStateException.
|
(throw (IllegalStateException.
|
||||||
(str "Did not find expected file '" file-path "'"))))
|
(str "Did not find expected file '" file-path "'"))))
|
||||||
(.copyTo (.open repo (.getObjectId tw 0)) out)
|
(.copyTo (.open repo (.getObjectId tw 0)) out)
|
||||||
(.toString out)))
|
(.toString out)))
|
||||||
|
|
||||||
|
|
@ -19,7 +19,9 @@
|
||||||
(:use clojure.walk)
|
(:use clojure.walk)
|
||||||
(:require [compojure.core :refer :all]
|
(:require [compojure.core :refer :all]
|
||||||
[clj-jgit.porcelain :as git]
|
[clj-jgit.porcelain :as git]
|
||||||
|
[cemerick.url :refer (url url-encode url-decode)]
|
||||||
[markdown.core :as md]
|
[markdown.core :as md]
|
||||||
|
[clojure.java.io :as cjio]
|
||||||
[noir.io :as io]
|
[noir.io :as io]
|
||||||
[noir.response :as response]
|
[noir.response :as response]
|
||||||
[noir.util.route :as route]
|
[noir.util.route :as route]
|
||||||
|
|
@ -35,8 +37,18 @@
|
||||||
"Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
|
"Rewrite text in `html-src` surrounded by double square brackets as a local link into this wiki."
|
||||||
[html-src]
|
[html-src]
|
||||||
(clojure.string/replace html-src #"\[\[[^\[\]]*\]\]"
|
(clojure.string/replace html-src #"\[\[[^\[\]]*\]\]"
|
||||||
#(let [text (clojure.string/replace %1 #"[\[\]]" "")]
|
#(let [text (clojure.string/replace %1 #"[\[\]]" "")
|
||||||
(str "<a href='wiki?page=" text "'>" text "</a>"))))
|
encoded (url-encode text)]
|
||||||
|
(timbre/debug (format "URL encode: '%s' -> '%s'" text encoded))
|
||||||
|
(format "<a href='wiki?page=%s'>%s</a>" encoded text))))
|
||||||
|
|
||||||
|
(defn get-git-repo
|
||||||
|
"Get the git repository for my content, creating it if necessary"
|
||||||
|
[]
|
||||||
|
(let [path (str (io/resource-path) "/content/")
|
||||||
|
repo (cjio/as-file (str path ".git"))]
|
||||||
|
(if (.exists repo) (git/load-repo repo)
|
||||||
|
(git/git-init path))))
|
||||||
|
|
||||||
(defn process-source
|
(defn process-source
|
||||||
"Process `source-text` and save it to the specified `file-path`, committing it
|
"Process `source-text` and save it to the specified `file-path`, committing it
|
||||||
|
|
@ -47,15 +59,15 @@
|
||||||
file-name (str page ".md")
|
file-name (str page ".md")
|
||||||
file-path (str (io/resource-path) "/content/" file-name)
|
file-path (str (io/resource-path) "/content/" file-name)
|
||||||
exists? (.exists (clojure.java.io/as-file file-path))
|
exists? (.exists (clojure.java.io/as-file file-path))
|
||||||
git-repo (git/load-repo (str (io/resource-path) "/content/.git"))
|
git-repo (get-git-repo)
|
||||||
user (session/get :user)
|
user (session/get :user)
|
||||||
email (auth/get-email user)
|
email (auth/get-email user)
|
||||||
summary (str user ": " (or (:summary params) "no summary"))]
|
summary (format "%s: %s" user (or (:summary params) "no summary"))]
|
||||||
(timbre/info (str "Saving " user "'s changes (" summary ") to " page))
|
(timbre/info (format "Saving %s's changes ('%s') to %s" user summary page))
|
||||||
(spit file-path source-text)
|
(spit file-path source-text)
|
||||||
(if (not exists?) (git/git-add git-repo file-name))
|
(if (not exists?) (git/git-add git-repo file-name))
|
||||||
(git/git-commit git-repo summary {:name user :email email})
|
(git/git-commit git-repo summary {:name user :email email})
|
||||||
(response/redirect (str "/wiki?page=" page))
|
(response/redirect (str "/wiki?page=" (url-encode page)))
|
||||||
))
|
))
|
||||||
|
|
||||||
(defn edit-page
|
(defn edit-page
|
||||||
|
|
@ -66,7 +78,8 @@
|
||||||
src-text (:src params)
|
src-text (:src params)
|
||||||
page (or (:page params) "Introduction")
|
page (or (:page params) "Introduction")
|
||||||
file-path (str (io/resource-path) "content/" page ".md")
|
file-path (str (io/resource-path) "content/" page ".md")
|
||||||
exists? (.exists (clojure.java.io/as-file file-path))]
|
exists? (.exists (cjio/as-file file-path))]
|
||||||
|
(if (not exists?) (timbre/info (format "File '%s' not found; creating a new file" file-path)))
|
||||||
(cond src-text (process-source params)
|
(cond src-text (process-source params)
|
||||||
true
|
true
|
||||||
(layout/render "edit.html"
|
(layout/render "edit.html"
|
||||||
|
|
@ -82,7 +95,7 @@
|
||||||
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
|
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
|
||||||
[request]
|
[request]
|
||||||
(let [params (keywordize-keys (:params request))
|
(let [params (keywordize-keys (:params request))
|
||||||
page (or (:content params) (:page params) "Introduction")
|
page (or (:page params) "Introduction")
|
||||||
file-name (str "/content/" page ".md")
|
file-name (str "/content/" page ".md")
|
||||||
file-path (str (io/resource-path) file-name)
|
file-path (str (io/resource-path) file-name)
|
||||||
exists? (.exists (clojure.java.io/as-file file-path))]
|
exists? (.exists (clojure.java.io/as-file file-path))]
|
||||||
|
|
@ -101,7 +114,7 @@
|
||||||
if any. If none, error?"
|
if any. If none, error?"
|
||||||
[request]
|
[request]
|
||||||
(let [params (keywordize-keys (:params request))
|
(let [params (keywordize-keys (:params request))
|
||||||
page (or (:page params) "Introduction")
|
page (url-decode (or (:page params) "Introduction"))
|
||||||
file-name (str page ".md")
|
file-name (str page ".md")
|
||||||
repo-path (str (io/resource-path) "/content/")]
|
repo-path (str (io/resource-path) "/content/")]
|
||||||
(layout/render "history.html"
|
(layout/render "history.html"
|
||||||
|
|
@ -115,7 +128,7 @@
|
||||||
"Render a specific historical version of a page"
|
"Render a specific historical version of a page"
|
||||||
[request]
|
[request]
|
||||||
(let [params (keywordize-keys (:params request))
|
(let [params (keywordize-keys (:params request))
|
||||||
page (or (:page params) "Introduction")
|
page (url-decode (or (:page params) "Introduction"))
|
||||||
version (:version params)
|
version (:version params)
|
||||||
file-name (str page ".md")
|
file-name (str page ".md")
|
||||||
repo-path (str (io/resource-path) "/content/")]
|
repo-path (str (io/resource-path) "/content/")]
|
||||||
|
|
@ -136,7 +149,7 @@
|
||||||
"Render a diff between two versions of a page"
|
"Render a diff between two versions of a page"
|
||||||
[request]
|
[request]
|
||||||
(let [params (keywordize-keys (:params request))
|
(let [params (keywordize-keys (:params request))
|
||||||
page (or (:page params) "Introduction")
|
page (url-decode (or (:page params) "Introduction"))
|
||||||
version (:version params)
|
version (:version params)
|
||||||
file-name (str page ".md")
|
file-name (str page ".md")
|
||||||
repo-path (str (io/resource-path) "/content/")]
|
repo-path (str (io/resource-path) "/content/")]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue