edit submissions (with tags)
This commit is contained in:
parent
b7fc462c14
commit
d52b149d04
@ -3,28 +3,76 @@
|
||||
module Handler.EditSubmission where
|
||||
|
||||
import Import
|
||||
import Handler.Common (checkIfCanEdit)
|
||||
import Handler.SubmissionView
|
||||
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3, bfs)
|
||||
|
||||
import Data.Text as T
|
||||
|
||||
getEditSubmissionR :: SubmissionId -> Handler Html
|
||||
getEditSubmissionR submissionId = do
|
||||
(formWidget, formEnctype) <- generateFormPost editSubmissionForm
|
||||
submission <- runDB $ get404 submissionId
|
||||
tags <- runDB $ getTags submissionId
|
||||
let mTagsAsText = case tags of
|
||||
[] -> Nothing
|
||||
_ -> Just $ T.intercalate ", " $ Import.map tagName $ Import.catMaybes tags
|
||||
(formWidget, formEnctype) <- generateFormPost $ editSubmissionForm (submissionDescription submission) mTagsAsText
|
||||
doEditSubmission formWidget formEnctype submissionId
|
||||
|
||||
postEditSubmissionR :: SubmissionId -> Handler Html
|
||||
postEditSubmissionR submissionId = do
|
||||
((result, formWidget), formEnctype) <- runFormPost editSubmissionForm
|
||||
doEditSubmission formWidget formEnctype submissionId
|
||||
submission <- runDB $ get404 submissionId
|
||||
((result, _), _) <- runFormPost $ editSubmissionForm (submissionDescription submission) Nothing
|
||||
let FormSuccess (description, tags) = result
|
||||
isEditable <- checkIfCanEdit submissionId
|
||||
if isEditable
|
||||
then
|
||||
runDB $ do
|
||||
update submissionId [SubmissionDescription =. description]
|
||||
|
||||
sts <- selectList [SubmissionTagSubmission ==. submissionId] []
|
||||
let currentTagIds = Import.map (submissionTagTag . entityVal) sts
|
||||
|
||||
addTags submissionId tags currentTagIds
|
||||
|
||||
return ()
|
||||
else
|
||||
do
|
||||
setMessage $ toHtml ("Only owner can edit a submission!!!" :: Text)
|
||||
return ()
|
||||
getEditSubmissionR submissionId
|
||||
|
||||
|
||||
addTags submissionId tagsAsText existingOnes = do
|
||||
let newTags = case tagsAsText of
|
||||
Just tags' -> Import.map T.strip $ T.split (== ',') tags'
|
||||
Nothing -> []
|
||||
mTs <- mapM (\t -> getBy $ UniqueTagName t) newTags
|
||||
let tids = Import.map entityKey $ Import.catMaybes mTs
|
||||
|
||||
deleteWhere [SubmissionTagSubmission ==. submissionId, SubmissionTagTag /<-. tids]
|
||||
|
||||
_ <- mapM (\tid -> insert $ SubmissionTag submissionId tid Nothing) (Import.filter (not . (`elem` existingOnes)) tids)
|
||||
return ()
|
||||
|
||||
|
||||
|
||||
doEditSubmission formWidget formEnctype submissionId = do
|
||||
submission <- runDB $ get404 submissionId
|
||||
submissionFull <- getFullInfo (Entity submissionId submission)
|
||||
let view = queryResult submissionFull
|
||||
|
||||
tagsAvailable <- runDB $ selectList [] [Asc TagName]
|
||||
let tagsAvailableAsJSON = toJSON $ Import.map (tagName . entityVal) tagsAvailable
|
||||
|
||||
defaultLayout $ do
|
||||
setTitle "Edit a submission"
|
||||
$(widgetFile "edit-submission")
|
||||
|
||||
editSubmissionForm :: Form (Text, Maybe Text)
|
||||
editSubmissionForm = renderBootstrap3 BootstrapBasicForm $ (,)
|
||||
<$> areq textField (bfs MsgSubmissionDescription) Nothing
|
||||
<*> aopt textField (tagsfs MsgSubmissionTags) Nothing
|
||||
editSubmissionForm :: Text -> Maybe Text -> Form (Text, Maybe Text)
|
||||
editSubmissionForm description mTags = renderBootstrap3 BootstrapBasicForm $ (,)
|
||||
<$> areq textField (bfs MsgSubmissionDescription) (Just description)
|
||||
<*> aopt textField (tagsfs MsgSubmissionTags) (Just mTags)
|
||||
|
||||
tagsfs :: RenderMessage site msg => msg -> FieldSettings site
|
||||
tagsfs msg = attrs { fsAttrs = ("data-role"::Text,"tagsinput"::Text):(fsAttrs attrs)}
|
||||
|
@ -3,6 +3,7 @@ module Handler.Query where
|
||||
import Import
|
||||
|
||||
import Handler.Shared
|
||||
import Handler.SubmissionView
|
||||
import PersistSHA1
|
||||
|
||||
import Database.Persist.Sql
|
||||
@ -11,24 +12,6 @@ import Data.Text as T(pack)
|
||||
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3,
|
||||
withSmallInput)
|
||||
|
||||
data FullSubmissionInfo = FullSubmissionInfo {
|
||||
fsiSubmissionId :: SubmissionId,
|
||||
fsiSubmission :: Submission,
|
||||
fsiUser :: User,
|
||||
fsiRepo :: Repo,
|
||||
fsiChallenge :: Challenge }
|
||||
|
||||
getFullInfo :: Entity Submission -> Handler FullSubmissionInfo
|
||||
getFullInfo (Entity submissionId submission) = do
|
||||
repo <- runDB $ get404 $ submissionRepo submission
|
||||
user <- runDB $ get404 $ submissionSubmitter submission
|
||||
challenge <- runDB $ get404 $ submissionChallenge submission
|
||||
return $ FullSubmissionInfo {
|
||||
fsiSubmissionId = submissionId,
|
||||
fsiSubmission = submission,
|
||||
fsiUser = user,
|
||||
fsiRepo = repo,
|
||||
fsiChallenge = challenge }
|
||||
|
||||
findSubmissions :: Text -> Handler [FullSubmissionInfo]
|
||||
findSubmissions sha1Prefix = do
|
||||
@ -69,14 +52,5 @@ processQuery query = do
|
||||
setTitle "query results"
|
||||
$(widgetFile "query-results")
|
||||
|
||||
queryResult submission = do
|
||||
$(widgetFile "query-result")
|
||||
where commitSha1AsText = fromSHA1ToText $ submissionCommit $ fsiSubmission submission
|
||||
submitter = formatSubmitter $ fsiUser submission
|
||||
publicSubmissionBranch = getPublicSubmissionBranch $ fsiSubmissionId submission
|
||||
publicSubmissionRepo = getReadOnlySubmissionUrl $ challengeName $ fsiChallenge submission
|
||||
browsableUrl = browsableGitRepoBranch (challengeName $ fsiChallenge submission) publicSubmissionBranch
|
||||
stamp = T.pack $ show $ submissionStamp $ fsiSubmission submission
|
||||
|
||||
queryForm :: Form Text
|
||||
queryForm = renderBootstrap3 BootstrapBasicForm $ areq textField (fieldSettingsLabel MsgGitCommitSha1) Nothing
|
||||
|
40
Handler/SubmissionView.hs
Normal file
40
Handler/SubmissionView.hs
Normal file
@ -0,0 +1,40 @@
|
||||
module Handler.SubmissionView where
|
||||
|
||||
import Import
|
||||
import Handler.Shared
|
||||
import PersistSHA1
|
||||
|
||||
import Data.Text as T(pack)
|
||||
|
||||
data FullSubmissionInfo = FullSubmissionInfo {
|
||||
fsiSubmissionId :: SubmissionId,
|
||||
fsiSubmission :: Submission,
|
||||
fsiUser :: User,
|
||||
fsiRepo :: Repo,
|
||||
fsiChallenge :: Challenge }
|
||||
|
||||
getFullInfo :: Entity Submission -> Handler FullSubmissionInfo
|
||||
getFullInfo (Entity submissionId submission) = do
|
||||
repo <- runDB $ get404 $ submissionRepo submission
|
||||
user <- runDB $ get404 $ submissionSubmitter submission
|
||||
challenge <- runDB $ get404 $ submissionChallenge submission
|
||||
return $ FullSubmissionInfo {
|
||||
fsiSubmissionId = submissionId,
|
||||
fsiSubmission = submission,
|
||||
fsiUser = user,
|
||||
fsiRepo = repo,
|
||||
fsiChallenge = challenge }
|
||||
|
||||
queryResult submission = do
|
||||
$(widgetFile "query-result")
|
||||
where commitSha1AsText = fromSHA1ToText $ submissionCommit $ fsiSubmission submission
|
||||
submitter = formatSubmitter $ fsiUser submission
|
||||
publicSubmissionBranch = getPublicSubmissionBranch $ fsiSubmissionId submission
|
||||
publicSubmissionRepo = getReadOnlySubmissionUrl $ challengeName $ fsiChallenge submission
|
||||
browsableUrl = browsableGitRepoBranch (challengeName $ fsiChallenge submission) publicSubmissionBranch
|
||||
stamp = T.pack $ show $ submissionStamp $ fsiSubmission submission
|
||||
|
||||
getTags submissionId = do
|
||||
sts <- selectList [SubmissionTagSubmission ==. submissionId] []
|
||||
tags <- mapM get $ Import.map (submissionTagTag . entityVal) sts
|
||||
return tags
|
@ -78,4 +78,9 @@ Tag
|
||||
name Text
|
||||
description Text Maybe
|
||||
UniqueTagName name
|
||||
SubmissionTag
|
||||
submission SubmissionId
|
||||
tag TagId
|
||||
accepted Bool Maybe
|
||||
UniqueSubmissionTag submission tag
|
||||
-- By default this file is used in Model.hs (which is imported by Foundation.hs)
|
||||
|
@ -47,6 +47,7 @@ library
|
||||
Handler.Presentation
|
||||
Handler.Tags
|
||||
Handler.EditSubmission
|
||||
Handler.SubmissionView
|
||||
|
||||
if flag(dev) || flag(library-only)
|
||||
cpp-options: -DDEVELOPMENT
|
||||
|
212
static/css/tagify.css
Normal file
212
static/css/tagify.css
Normal file
@ -0,0 +1,212 @@
|
||||
tags {
|
||||
display: block;
|
||||
border: 1px solid #DDD;
|
||||
padding-right: 0.3em 0.5em;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
tags:hover {
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
tags tag {
|
||||
display: inline-block;
|
||||
margin: 5px 0 5px 5px;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
-webkit-transition: .13s ease-out;
|
||||
transition: .13s ease-out;
|
||||
-webkit-animation: .3s tags--bump 1 ease-out;
|
||||
animation: .3s tags--bump 1 ease-out;
|
||||
}
|
||||
|
||||
tags tag > div {
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
padding: 0.3em 0.5em;
|
||||
color: black;
|
||||
-webkit-transition: .13s ease-out;
|
||||
transition: .13s ease-out;
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
||||
tags tag > div > span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
-webkit-transition: .1s;
|
||||
transition: .1s;
|
||||
}
|
||||
|
||||
tags tag > div::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #E5E5E5;
|
||||
border-radius: 3px;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
-webkit-transition: 80ms ease;
|
||||
transition: 80ms ease;
|
||||
}
|
||||
|
||||
tags tag:hover div::before {
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
left: -2px;
|
||||
background: #D3E2E2;
|
||||
box-shadow: 0 0 0 0 #D39494 inset;
|
||||
}
|
||||
|
||||
tags tag.tagify--noAnim {
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
tags tag.tagify--hide {
|
||||
width: 0 !important;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
-webkit-transition: .3s;
|
||||
transition: .3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
tags tag.tagify--mark div::before {
|
||||
-webkit-animation: .3s tagify--pulse 2 ease-out;
|
||||
animation: .3s tagify--pulse 2 ease-out;
|
||||
}
|
||||
|
||||
tags tag.tagify--notAllowed div > span {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
tags tag.tagify--notAllowed div::before {
|
||||
background: rgba(211, 148, 148, 0.44);
|
||||
-webkit-transition: .2s;
|
||||
transition: .2s;
|
||||
}
|
||||
|
||||
tags tag x {
|
||||
font: 14px/14px Serif;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
text-align: center;
|
||||
border-radius: 50px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: -webkit-calc(0.5em - 2px);
|
||||
right: calc(0.5em - 2px);
|
||||
top: 50%;
|
||||
cursor: pointer;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
-webkit-transition: .2s ease-out;
|
||||
transition: .2s ease-out;
|
||||
}
|
||||
|
||||
tags tag x::after {
|
||||
content: "\00D7";
|
||||
}
|
||||
|
||||
tags tag x:hover {
|
||||
color: white;
|
||||
background: #c77777;
|
||||
}
|
||||
|
||||
tags tag x:hover + div > span {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
tags tag x:hover + div::before {
|
||||
background: rgba(211, 148, 148, 0.44);
|
||||
-webkit-transition: .2s;
|
||||
transition: .2s;
|
||||
}
|
||||
|
||||
tags input,tags textarea {
|
||||
border: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
tags > div {
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
margin: 5px;
|
||||
padding: 0.3em 0.5em;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tags > div > input {
|
||||
display: block;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
tags > div > input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
tags > div > input.placeholder ~ span {
|
||||
opacity: .5;
|
||||
-webkit-transform: none;
|
||||
-ms-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
tags > div > span {
|
||||
opacity: 0;
|
||||
line-height: 1.8;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
-webkit-transform: translatex(6px);
|
||||
-ms-transform: translatex(6px);
|
||||
transform: translatex(6px);
|
||||
-webkit-transition: .15s ease-out;
|
||||
transition: .15s ease-out;
|
||||
}
|
||||
|
||||
@-webkit-keyframes tags--bump {
|
||||
30% {
|
||||
box-shadow: 0 0 0 4px #E5E5E5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tags--bump {
|
||||
30% {
|
||||
box-shadow: 0 0 0 4px #E5E5E5;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes tagify--pulse {
|
||||
25% {
|
||||
background: rgba(211, 148, 148, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tagify--pulse {
|
||||
25% {
|
||||
background: rgba(211, 148, 148, 0.6);
|
||||
}
|
||||
}
|
238
static/js/tagify.js
Normal file
238
static/js/tagify.js
Normal file
@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Tagify - jQuery tags input plugin
|
||||
* By Yair Even-Or (2016)
|
||||
* Don't sell this code. (c)
|
||||
* https://github.com/yairEO/tagify
|
||||
*/
|
||||
function Tagify( input, settings ){
|
||||
// protection
|
||||
if( !input ){
|
||||
console.warn('Tagify: ', 'invalid input element ', input)
|
||||
return this;
|
||||
}
|
||||
|
||||
settings = typeof settings == 'object' ? settings : {}; // make sure settings is an 'object'
|
||||
|
||||
this.settings = {
|
||||
duplicates : settings.duplicates || false, // flag - allow tuplicate tags
|
||||
enforeWhitelist : settings.enforeWhitelist || false, // flag - should ONLY use tags allowed in whitelist
|
||||
autocomplete : settings.autocomplete || true, // flag - show native suggeestions list as you type
|
||||
whitelist : settings.whitelist || [], // is this list has any items, then only allow tags from this list
|
||||
blacklist : settings.blacklist || [] // a list of non-allowed tags
|
||||
};
|
||||
|
||||
this.id = Math.random().toString(36).substr(2,9), // almost-random ID (because, fuck it)
|
||||
this.value = []; // An array holding all the (currently used) tags
|
||||
this.DOM = {}; // Store all relevant DOM elements in an Object
|
||||
this.build(input);
|
||||
this.events();
|
||||
}
|
||||
|
||||
Tagify.prototype = {
|
||||
build : function( input ){
|
||||
var that = this,
|
||||
value = input.value;
|
||||
|
||||
this.DOM.originalInput = input;
|
||||
this.DOM.scope = document.createElement('tags');
|
||||
this.DOM.scope.innerHTML = '<div><input list="tagsSuggestions'+ this.id +'" class="placeholder"/><span>'+ input.placeholder +'</span></div>';
|
||||
|
||||
this.DOM.input = this.DOM.scope.querySelector('input');
|
||||
input.parentNode.insertBefore(this.DOM.scope, input);
|
||||
this.DOM.scope.appendChild(input);
|
||||
|
||||
// if "autocomplete" flag on toggeled & "whitelist" has items, build suggestions list
|
||||
if( this.settings.autocomplete && this.settings.whitelist.length )
|
||||
this.buildDataList();
|
||||
|
||||
// if the original input already had any value (tags)
|
||||
if( value )
|
||||
this.addTag(value).forEach(function(tag){
|
||||
tag && tag.classList.add('tagify--noAnim');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* DOM events binding
|
||||
*/
|
||||
events : function(){
|
||||
var events = {
|
||||
// event name / event callback / element to be listening to
|
||||
focus : ['onFocusBlur' , 'input'],
|
||||
blur : ['onFocusBlur' , 'input'],
|
||||
input : ['onInput' , 'input'],
|
||||
keydown : ['onKeydown' , 'input'],
|
||||
click : ['onClickScope' , 'scope']
|
||||
};
|
||||
|
||||
for( var e in events )
|
||||
this.DOM[events[e][1]].addEventListener(e, this.callbacks[events[e][0]].bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* DOM events callbacks
|
||||
*/
|
||||
callbacks : {
|
||||
onFocusBlur : function(e){
|
||||
var text = e.target.value.trim();
|
||||
|
||||
if( e.type == "focus" )
|
||||
e.target.className = 'input';
|
||||
else if( e.type == "blur" && text == "" ){
|
||||
e.target.className = 'input placeholder';
|
||||
this.DOM.input.removeAttribute('style');
|
||||
}
|
||||
},
|
||||
|
||||
onKeydown : function(e){
|
||||
var s = e.target.value;
|
||||
if( e.key == "Backspace" && (s == "" || s.charCodeAt(0) == 8203) ){
|
||||
this.removeTag( this.DOM.scope.querySelectorAll('tag:not(.tagify--hide)').length - 1 );
|
||||
}
|
||||
if( e.key == "Escape" ){
|
||||
e.target.value = '';
|
||||
e.target.blur();
|
||||
}
|
||||
if( e.key == "Enter" ){
|
||||
e.preventDefault(); // solves Chrome bug - http://stackoverflow.com/a/20398191/104380
|
||||
if( this.addTag(s) )
|
||||
e.target.value = '';
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
onInput : function(e){
|
||||
var value = e.target.value,
|
||||
lastChar = value[value.length - 1];
|
||||
|
||||
e.target.style.width = ((e.target.value.length + 1) * 7) + 'px';
|
||||
|
||||
if( value.indexOf(',') != -1 ){
|
||||
this.addTag( value );
|
||||
e.target.value = ''; // clear the input field's value
|
||||
}
|
||||
},
|
||||
|
||||
onClickScope : function(e){
|
||||
if( e.target.tagName == "TAGS" )
|
||||
this.DOM.input.focus();
|
||||
if( e.target.tagName == "X" ){
|
||||
this.removeTag( this.getNodeIndex(e.target.parentNode) );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Build tags suggestions using HTML datalist
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
buildDataList : function(){
|
||||
var OPTIONS = "",
|
||||
i,
|
||||
datalist = "<datalist id='tagsSuggestions"+ this.id +"'> \
|
||||
<label> \
|
||||
select from the list: \
|
||||
<select> \
|
||||
<option value=''></option> \
|
||||
[OPTIONS] \
|
||||
</select> \
|
||||
</label> \
|
||||
</datalist>";
|
||||
|
||||
for( i=this.settings.whitelist.length; i--; )
|
||||
OPTIONS += "<option>"+ this.settings.whitelist[i] +"</option>";
|
||||
|
||||
datalist = datalist.replace('[OPTIONS]', OPTIONS); // inject the options string in the right place
|
||||
this.DOM.input.insertAdjacentHTML('afterend', datalist); // append the datalist HTML string in the Tags
|
||||
|
||||
return datalist;
|
||||
},
|
||||
|
||||
getNodeIndex : function( node ){
|
||||
var index = 0;
|
||||
while( (node = node.previousSibling) )
|
||||
if (node.nodeType != 3 || !/^\s*$/.test(node.data))
|
||||
index++;
|
||||
return index;
|
||||
},
|
||||
|
||||
markTagByValue : function(value){
|
||||
var tagIdx = this.value.findIndex(function(item){ return value.toLowerCase() === item.toLowerCase() }),
|
||||
tag = this.DOM.scope.querySelectorAll('tag')[tagIdx];
|
||||
|
||||
if( tag ){
|
||||
tag.classList.add('tagify--mark');
|
||||
setTimeout(function(){ tag.classList.remove('tagify--mark') }, 2000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* make sure the tag, or words in it, is not in the blacklist
|
||||
*/
|
||||
isTagBlacklisted : function(v){
|
||||
v = v.split(' ');
|
||||
return this.settings.blacklist.filter(function(x){ return v.indexOf(x) != -1 }).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* make sure the tag, or words in it, is not in the blacklist
|
||||
*/
|
||||
isTagWhitelisted : function(v){
|
||||
return this.settings.whitelist.indexOf(v) != -1;
|
||||
},
|
||||
|
||||
addTag : function( value ){
|
||||
var that = this;
|
||||
|
||||
this.DOM.input.removeAttribute('style');
|
||||
|
||||
value = value.trim();
|
||||
if( !value ) return;
|
||||
|
||||
return value.split(',').filter(function(v){ return !!v }).map(function(v){
|
||||
var tagElm = document.createElement('tag');
|
||||
v = v.trim();
|
||||
|
||||
if( !that.settings.duplicates && that.markTagByValue(v) )
|
||||
return false;
|
||||
|
||||
// check against blacklist & whitelist (if enforced)
|
||||
if( that.isTagBlacklisted(v) || (that.settings.enforeWhitelist && !that.isTagWhitelisted(v)) ){
|
||||
tagElm.classList.add('tagify--notAllowed');
|
||||
setTimeout(function(){ that.removeTag(that.getNodeIndex(tagElm)) }, 1000);
|
||||
}
|
||||
|
||||
// the space below is important - http://stackoverflow.com/a/19668740/104380
|
||||
tagElm.innerHTML = "<x></x><div><span title='"+ v +"'>"+ v +" </span></div>";
|
||||
that.DOM.scope.insertBefore(tagElm, that.DOM.input.parentNode);
|
||||
|
||||
that.value.push(v);
|
||||
that.update();
|
||||
return tagElm;
|
||||
});
|
||||
},
|
||||
|
||||
removeTag : function( idx ){
|
||||
var tagElm = this.DOM.scope.children[idx];
|
||||
if( !tagElm) return;
|
||||
|
||||
tagElm.style.width = parseFloat(window.getComputedStyle(tagElm).width) + 'px';
|
||||
document.body.clientTop; // force repaint for the width to take affect before the "hide" class below
|
||||
tagElm.classList.add('tagify--hide');
|
||||
|
||||
// manual timeout (hack, since transitionend cannot be used because of hover)
|
||||
setTimeout(function(){
|
||||
tagElm.parentNode.removeChild(tagElm);
|
||||
}, 400);
|
||||
|
||||
this.value.splice(idx, 1);
|
||||
this.update();
|
||||
},
|
||||
|
||||
// update the origianl (hidden) input field's value
|
||||
update : function(){
|
||||
this.DOM.originalInput.value = this.value.join(', ');
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@ $newline never
|
||||
integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="/static/css/tagify.css">
|
||||
<script src="/static/js/tagify.js">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
^{pageHead pc}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<p>#{show $ submissionStamp $ submission}
|
||||
^{view}
|
||||
|
||||
<form method=post action=@{EditSubmissionR submissionId}#form enctype=#{formEnctype}>
|
||||
^{formWidget}
|
||||
|
6
templates/edit-submission.julius
Normal file
6
templates/edit-submission.julius
Normal file
@ -0,0 +1,6 @@
|
||||
var input = document.querySelector('input[data-role=tagsinput]'),
|
||||
tagify = new Tagify( input, {
|
||||
whitelist: #{tagsAvailableAsJSON},
|
||||
autocomplete: true,
|
||||
enforeWhitelist: true});
|
||||
input.style.display = 'none';
|
Loading…
Reference in New Issue
Block a user