Add tokenfield tag editor
This commit is contained in:
parent
6a3da59c75
commit
02f711aa97
10 changed files with 148 additions and 3 deletions
|
@ -80,6 +80,8 @@ file with the exception of:
|
||||||
* css/bootstrap.bundle.min.css css/bootstrap.bundle.min.css.map js/bootstrap.bundle.min.js
|
* css/bootstrap.bundle.min.css css/bootstrap.bundle.min.css.map js/bootstrap.bundle.min.js
|
||||||
js/bootstrap.bundle.min.js.map img/bootstrap-icons.svg
|
js/bootstrap.bundle.min.js.map img/bootstrap-icons.svg
|
||||||
From the bootstrap framework: https://getbootstrap.com/
|
From the bootstrap framework: https://getbootstrap.com/
|
||||||
|
* css/tokenfield.css css/tokenfield.css.map js/tokenfield.min.js
|
||||||
|
From tokenfield: https://github.com/KaneCohen/tokenfield
|
||||||
* img/bright_squares.png
|
* img/bright_squares.png
|
||||||
From subtlepatterns: http://subtlepatterns.com/bright-squares/
|
From subtlepatterns: http://subtlepatterns.com/bright-squares/
|
||||||
* css/FredokaOne.ttf css/PTSerif.ttf
|
* css/FredokaOne.ttf css/PTSerif.ttf
|
||||||
|
|
97
css/tokenfield.css
Normal file
97
css/tokenfield.css
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
.tokenfield {
|
||||||
|
position: relative; }
|
||||||
|
.tokenfield:before, .tokenfield:after {
|
||||||
|
content: " ";
|
||||||
|
display: table; }
|
||||||
|
.tokenfield:after {
|
||||||
|
clear: both; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 6px 12px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.42857;
|
||||||
|
color: #555555;
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||||
|
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens:focus {
|
||||||
|
border-color: #66afe9;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); }
|
||||||
|
.tokenfield.tokenfield-mode-tokens::-moz-placeholder {
|
||||||
|
color: #999;
|
||||||
|
opacity: 1; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens:-ms-input-placeholder {
|
||||||
|
color: #999; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens::-webkit-input-placeholder {
|
||||||
|
color: #999; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens::-ms-expand {
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens[disabled], .tokenfield.tokenfield-mode-tokens[readonly],
|
||||||
|
fieldset[disabled] .tokenfield.tokenfield-mode-tokens {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
opacity: 1; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens[disabled],
|
||||||
|
fieldset[disabled] .tokenfield.tokenfield-mode-tokens {
|
||||||
|
cursor: not-allowed; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens .focused {
|
||||||
|
box-shadow: 0 0 0 1px #337ab7 inset; }
|
||||||
|
.tokenfield.tokenfield-mode-tokens .selected {
|
||||||
|
background: rgba(0, 0, 0, 0.1); }
|
||||||
|
.tokenfield .tokenfield-set > ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none; }
|
||||||
|
.tokenfield .tokenfield-set > ul > li {
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.5;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
background: rgba(0, 0, 0, 0.08); }
|
||||||
|
.tokenfield .tokenfield-set > ul > li:hover {
|
||||||
|
color: black;
|
||||||
|
background: rgba(0, 0, 0, 0.16); }
|
||||||
|
.tokenfield .tokenfield-set > ul > li .item-remove {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9285em;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(0, 0, 0, 0.4); }
|
||||||
|
.tokenfield .tokenfield-input {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
float: left; }
|
||||||
|
.tokenfield .tokenfield-suggest {
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
top: 100%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 10;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
box-sizing: content-box; }
|
||||||
|
.tokenfield .tokenfield-suggest > ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none; }
|
||||||
|
.tokenfield .tokenfield-suggest > ul > li {
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=tokenfield.css.map */
|
11
css/tokenfield.css.map
Normal file
11
css/tokenfield.css.map
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"file": "tokenfield.css",
|
||||||
|
"sources": [
|
||||||
|
"../lib/scss/tokenfield.scss",
|
||||||
|
"../lib/scss/mixins.scss",
|
||||||
|
"../lib/scss/variables.scss"
|
||||||
|
],
|
||||||
|
"names": [],
|
||||||
|
"mappings": "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"
|
||||||
|
}
|
1
js/tokenfield.min.js
vendored
Normal file
1
js/tokenfield.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -59,6 +59,7 @@ type editData struct {
|
||||||
S Status
|
S Status
|
||||||
Book database.Book
|
Book database.Book
|
||||||
SubmissionID string
|
SubmissionID string
|
||||||
|
Tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func editHandler(h handler) {
|
func editHandler(h handler) {
|
||||||
|
@ -78,6 +79,10 @@ func editHandler(h handler) {
|
||||||
data.Book = book
|
data.Book = book
|
||||||
data.S = GetStatus(h)
|
data.S = GetStatus(h)
|
||||||
data.SubmissionID = submissionID
|
data.SubmissionID = submissionID
|
||||||
|
data.Tags, err = h.db.GetTags()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting tags: ", err)
|
||||||
|
}
|
||||||
author := ""
|
author := ""
|
||||||
if len(book.Authors) > 0 {
|
if len(book.Authors) > 0 {
|
||||||
author = " by " + book.Authors[0]
|
author = " by " + book.Authors[0]
|
||||||
|
|
|
@ -33,6 +33,7 @@ type DB interface {
|
||||||
IncDownloads(ID string) error
|
IncDownloads(ID string) error
|
||||||
GetDownloadCounter(ID string) (int, error)
|
GetDownloadCounter(ID string) (int, error)
|
||||||
GetFrontPage() FrontPage
|
GetFrontPage() FrontPage
|
||||||
|
GetTags() ([]string, error)
|
||||||
AddSubmission(submission Submission, userName string) (id int, err error)
|
AddSubmission(submission Submission, userName string) (id int, err error)
|
||||||
UpdateSubmission(id int, status string, book *Book) error
|
UpdateSubmission(id int, status string, book *Book) error
|
||||||
UpdateSubmissionByBook(bookID string, status string, book *Book) error
|
UpdateSubmissionByBook(bookID string, status string, book *Book) error
|
||||||
|
|
|
@ -109,6 +109,10 @@ func (db *roDB) GetFrontPage() FrontPage {
|
||||||
return db.db.GetFrontPage()
|
return db.db.GetFrontPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *roDB) GetTags() ([]string, error) {
|
||||||
|
return db.db.GetTags()
|
||||||
|
}
|
||||||
|
|
||||||
func (db *roDB) AddSubmission(submission Submission, userName string) (id int, err error) {
|
func (db *roDB) AddSubmission(submission Submission, userName string) (id int, err error) {
|
||||||
return 0, errors.New("RO database")
|
return 0, errors.New("RO database")
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,10 @@ func (db *pgDB) frontPageUpdater() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) GetTags() ([]string, error) {
|
||||||
|
return db.frontPage.Tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *pgDB) getVisitedBooks(num int) (books []Book, err error) {
|
func (db *pgDB) getVisitedBooks(num int) (books []Book, err error) {
|
||||||
err = db.sql.Model(&books).
|
err = db.sql.Model(&books).
|
||||||
Column("book.*").
|
Column("book.*").
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
{{template "header.html" .S}}
|
{{template "header.html" .S}}
|
||||||
|
|
||||||
<!-- TODO: tokenfield -->
|
|
||||||
|
|
||||||
{{$submissionID := .SubmissionID}}
|
{{$submissionID := .SubmissionID}}
|
||||||
{{with .Book}}
|
{{with .Book}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -108,6 +106,27 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- TODO: tokenfield -->
|
<script src="/js/tokenfield.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var tf = new Tokenfield({
|
||||||
|
el: document.querySelector('#tags'),
|
||||||
|
items: [
|
||||||
|
{{range $i, $tag := .Tags}}
|
||||||
|
{id: 1000 + {{$i}}, name: '{{$tag}}'},
|
||||||
|
{{end}}
|
||||||
|
],
|
||||||
|
setItems: [
|
||||||
|
{{range $i, $tag := .Book.Tags}}
|
||||||
|
{id: {{$i}}, name: '{{$tag}}'},
|
||||||
|
{{end}}
|
||||||
|
],
|
||||||
|
delimiters: [","],
|
||||||
|
newItems: true
|
||||||
|
});
|
||||||
|
tf.on('change', function() {
|
||||||
|
const tag_list = tf.getItems().map((i) => i.name).join();
|
||||||
|
document.querySelector('#tags').value = tag_list;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
{{template "footer.html" .S}}
|
{{template "footer.html" .S}}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<link href="/img/favicon.ico" rel="icon" type="image/x-icon" />
|
<link href="/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/css/custom.css" rel="stylesheet">
|
<link href="/css/custom.css" rel="stylesheet">
|
||||||
|
<link href="/css/tokenfield.css" rel="stylesheet">
|
||||||
<link rel="alternate" type="application/rss+xml" title="{{.Title}}" href="/search/?fmt=rss&q={{.Search}}" />
|
<link rel="alternate" type="application/rss+xml" title="{{.Title}}" href="/search/?fmt=rss&q={{.Search}}" />
|
||||||
|
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
|
|
Reference in a new issue