ui for hashtag picker

This commit is contained in:
ansuz 2017-09-07 12:45:07 +02:00
parent 1b97996ef8
commit 15e24ebe4f
5 changed files with 258 additions and 7 deletions

View File

@ -41,6 +41,7 @@
"bootstrap": "#v4.0.0-alpha.6",
"diff-dom": "2.1.1",
"nthen": "^0.1.5",
"open-sans-fontface": "^1.4.2"
"open-sans-fontface": "^1.4.2",
"bootstrap-tokenfield": "^0.12.1"
}
}

View File

@ -85,6 +85,20 @@
.dialog {
padding: 12px;
/*
div.tokenfield {
.token {
//border: 1px solid red;
//color: red;
}
color: @colortheme_light-base;
background-color: @alertify-dialog-bg;
input[id$="tokenfield"][type="text"].token-input {
background-color: @alertify-dialog-bg !important;
}
}*/
}
.dialog, .alert {
@ -308,6 +322,5 @@
pointer-events: auto;
}
}
}
}

View File

@ -0,0 +1,94 @@
.tokenfield_main () {
.tokenfield {
.unselectable () {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.unselectable();
height: auto;
min-height: 34px;
padding-bottom: 0px;
&.focus {
border-color: #66afe9;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.token {
box-sizing: border-box;
border-radius: 3px;
display: inline-block;
border: 1px solid #d9d9d9;
background-color: #ededed;
white-space: nowrap;
margin: -1px 5px 5px 0;
vertical-align: center;
cursor: default;
color: #222;
&:hover {
border-color: #b9b9b9;
}
&.invalid {
background: none;
border: 1px solid transparent;
border-radius: 0;
border-bottom: 1px dotted #d9534f;
}
&.invalid.active {
background: #ededed;
border: 1px solid #ededed;
border-radius: 3px;
}
.token-label {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 4px;
vertical-align: center;
}
.close {
font-family: Arial;
display: inline-block;
line-height: 100%;
font-size: 1.1em;
margin-left: 5px;
float: none;
height: 100%;
vertical-align: center;
padding-right: 4px;
}
&.active {
border-color: #52a8ec;
border-color: rgba(82, 168, 236, 0.8);
}
&.duplicate {
border-color: #ebccd1;
}
}
.token-input {
background: none;
width: 0%; //60px;
min-width: 60px;
border: 0;
padding: 0;
margin-bottom: 6px;
box-shadow: none;
max-width: 100%;
&:focus {
border-color: transparent;
outline: 0;
box-shadow: none;
}
}
&.disabled {
cursor: not-allowed;
background-color: #eeeeee;
}
}
}

View File

@ -7,9 +7,10 @@ define([
'/common/notify.js',
'/common/visible.js',
'/common/tippy.min.js',
'/common/hyperscript.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy) {
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy, h) {
var UI = {};
/*
@ -20,11 +21,17 @@ define([
// set notification timeout
Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000;
var findCancelButton = UI.findCancelButton = function () {
var findCancelButton = UI.findCancelButton = function (root) {
if (root) {
return $(root).find('button.cancel').last();
}
return $('button.cancel').last();
};
var findOKButton = UI.findOKButton = function () {
var findOKButton = UI.findOKButton = function (root) {
if (root) {
return $(root).find('button.ok').last();
}
return $('button.ok').last();
};
@ -33,7 +40,6 @@ define([
switch (e.which) {
case 27: // cancel
if (typeof(no) === 'function') { no(e); }
no();
break;
case 13: // enter
if (typeof(yes) === 'function') { yes(e); }
@ -363,5 +369,126 @@ define([
}
};
UI.tokenField = function (target) {
var t = {
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
t.getTokens = function () {
return $t.tokenfield('getTokens').map(function (token) {
return token.value;
});
};
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
var val;
if (t.getTokens().some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
}
});
return t;
};
t.setTokens = function (tokens) {
$t.tokenfield('setTokens',
tokens.map(function (token) {
return {
value: token,
label: token,
};
}));
};
t.focus = function () {
var $temp = $t.closest('.tokenfield').find('.token-input');
$temp.css('width', '20%');
$t.tokenfield('focusInput', $temp[0]);
};
return t;
};
var dialog = UI.dialog = {};
dialog.okButton = function () {
return h('button.ok', { tabindex: '2', }, Messages.okButton);
};
dialog.cancelButton = function () {
return h('button.cancel', { tabindex: '1'}, Messages.cancelButton);
};
dialog.message = function (text) {
return h('p.message', text);
};
dialog.textInput = function (opt) {
return h('input', opt || {
placeholder: '',
type: 'text',
'class': 'cp-text-input',
});
};
dialog.nav = function (content) {
return h('nav', content || [
dialog.cancelButton(),
dialog.okButton(),
]);
};
dialog.frame = function (content) {
return h('div.alertify', [
h('div.dialog', [
h('div', content),
])
]);
};
dialog.tagPrompt = function (tags, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
dialog.message('make some tags'), // TODO translate
input,
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
UI.warn('Duplicate tag: ' + val); // TODO translate
});
var close = Util.once(function () {
var $t = $(tagger).fadeOut(150, function () { $t.remove(); });
});
var listener = listenForKeys(function () {}, function () {
close();
stopListening(listener);
});
var CB = Util.once(cb);
findOKButton(tagger).click(function () {
var tokens = field.getTokens();
close();
CB(tokens);
});
findCancelButton(tagger).click(function () {
close();
CB(null);
});
// :(
setTimeout(function () {
field.setTokens(tags);
field.focus();
});
return tagger;
};
return UI;
});

View File

@ -81,6 +81,8 @@ define([
common.addTooltips = UI.addTooltips;
common.clearTooltips = UI.clearTooltips;
common.importContent = UI.importContent;
common.tokenField = UI.tokenField;
common.dialog = UI.dialog;
// import common utilities for export
common.find = Util.find;
@ -1380,6 +1382,20 @@ define([
})
.click(prepareFeedback(type));
break;
case 'hashtag':
button = $('<button>', {
'class': 'fa fa-hashtag',
})
.click(prepareFeedback(type))
.click(function () {
// TODO fetch pad tags before presenting dialog to user
var dialog = Cryptpad.dialog.tagPrompt([], function (tags) {
if (!Array.isArray(tags)) { return; }
console.error(tags);
// TODO do something with the tags the user entered
});
document.body.appendChild(dialog);
});
default:
button = $('<button>', {
'class': "fa fa-question",