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
|
||||
js/bootstrap.bundle.min.js.map img/bootstrap-icons.svg
|
||||
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
|
||||
From subtlepatterns: http://subtlepatterns.com/bright-squares/
|
||||
* 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
|
||||
Book database.Book
|
||||
SubmissionID string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func editHandler(h handler) {
|
||||
|
@ -78,6 +79,10 @@ func editHandler(h handler) {
|
|||
data.Book = book
|
||||
data.S = GetStatus(h)
|
||||
data.SubmissionID = submissionID
|
||||
data.Tags, err = h.db.GetTags()
|
||||
if err != nil {
|
||||
log.Error("Error getting tags: ", err)
|
||||
}
|
||||
author := ""
|
||||
if len(book.Authors) > 0 {
|
||||
author = " by " + book.Authors[0]
|
||||
|
|
|
@ -33,6 +33,7 @@ type DB interface {
|
|||
IncDownloads(ID string) error
|
||||
GetDownloadCounter(ID string) (int, error)
|
||||
GetFrontPage() FrontPage
|
||||
GetTags() ([]string, error)
|
||||
AddSubmission(submission Submission, userName string) (id int, err error)
|
||||
UpdateSubmission(id int, 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()
|
||||
}
|
||||
|
||||
func (db *roDB) GetTags() ([]string, error) {
|
||||
return db.db.GetTags()
|
||||
}
|
||||
|
||||
func (db *roDB) AddSubmission(submission Submission, userName string) (id int, err error) {
|
||||
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) {
|
||||
err = db.sql.Model(&books).
|
||||
Column("book.*").
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
{{template "header.html" .S}}
|
||||
|
||||
<!-- TODO: tokenfield -->
|
||||
|
||||
{{$submissionID := .SubmissionID}}
|
||||
{{with .Book}}
|
||||
<div class="row">
|
||||
|
@ -108,6 +106,27 @@
|
|||
</div>
|
||||
{{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}}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<link href="/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||
<link href="/css/bootstrap.min.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}}" />
|
||||
|
||||
<title>{{.Title}}</title>
|
||||
|
|
Reference in a new issue