Init version of new text modules.
This commit is contained in:
parent
83f5b6cabe
commit
14f636fec2
9 changed files with 777 additions and 492 deletions
|
@ -1599,6 +1599,12 @@ class App.ControllerForm extends App.Controller
|
|||
|
||||
lookupForm = @findForm(form)
|
||||
|
||||
# get contenteditable
|
||||
for element in lookupForm.find('[contenteditable]')
|
||||
name = $(element).data('name')
|
||||
if name
|
||||
param[name] = $(element).ceg()
|
||||
|
||||
# get form elements
|
||||
array = lookupForm.serializeArray()
|
||||
|
||||
|
|
|
@ -163,6 +163,8 @@ class LayoutRefCommunicationReply extends App.ControllerContent
|
|||
maxlength: 2500
|
||||
})
|
||||
|
||||
@$('[contenteditable]').textmodule()
|
||||
|
||||
detect_empty_textarea: =>
|
||||
if !@textarea.text()
|
||||
@add_textarea_catcher()
|
||||
|
|
|
@ -88,9 +88,6 @@ class App.TicketZoom extends App.Controller
|
|||
if newTicketRaw.updated_by_id isnt @Session.get('id')
|
||||
App.TaskManager.notify( @task_key )
|
||||
|
||||
# rerender edit box
|
||||
@editDone = false
|
||||
|
||||
# remember current data
|
||||
@ticketUpdatedAtLastCall = newTicketRaw.updated_at
|
||||
|
||||
|
@ -163,6 +160,15 @@ class App.TicketZoom extends App.Controller
|
|||
el: @el.find('.ticket-meta')
|
||||
)
|
||||
|
||||
new Edit(
|
||||
ticket: @ticket
|
||||
el: @el.find('.ticket-edit')
|
||||
#el: @el.find('.edit')
|
||||
form_meta: @form_meta
|
||||
defaults: @taskGet('article')
|
||||
ui: @
|
||||
)
|
||||
|
||||
editTicket = (el) =>
|
||||
el.append('<form class="edit"></form>')
|
||||
@editEl = el
|
||||
|
@ -170,7 +176,7 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
reset = (e) =>
|
||||
e.preventDefault()
|
||||
App.TaskManager.update( @task_key, { 'state': {} })
|
||||
@taskReset()
|
||||
show(@ticket)
|
||||
|
||||
show = (ticket) =>
|
||||
|
@ -202,7 +208,7 @@ class App.TicketZoom extends App.Controller
|
|||
form.find('[name="' + fieldNameToChange + '"]').replaceWith( newElement )
|
||||
|
||||
defaults = ticket.attributes()
|
||||
task_state = App.TaskManager.get(@task_key).state || {}
|
||||
task_state = @taskGet('ticket')
|
||||
modelDiff = @getDiff( defaults, task_state )
|
||||
#if @isRole('Customer')
|
||||
# delete defaults['state_id']
|
||||
|
@ -351,17 +357,13 @@ class App.TicketZoom extends App.Controller
|
|||
items: items
|
||||
)
|
||||
|
||||
@ArticleView()
|
||||
|
||||
if force || !@editDone
|
||||
# reset form on force reload
|
||||
if force && _.isEmpty( App.TaskManager.get(@task_key).state )
|
||||
App.TaskManager.update( @task_key, { 'state': {} })
|
||||
@editDone = true
|
||||
|
||||
# rerender widget if it hasn't changed
|
||||
if !@editWidget || _.isEmpty( App.TaskManager.get(@task_key).state )
|
||||
@editWidget = @Edit()
|
||||
# show article
|
||||
new ArticleView(
|
||||
ticket: @ticket
|
||||
ticket_article_ids: @ticket_article_ids
|
||||
el: @el.find('.ticket-article')
|
||||
ui: @
|
||||
)
|
||||
|
||||
# scroll to article if given
|
||||
if @article_id && document.getElementById( 'article-' + @article_id )
|
||||
|
@ -376,40 +378,30 @@ class App.TicketZoom extends App.Controller
|
|||
|
||||
@autosaveStart()
|
||||
|
||||
|
||||
ArticleView: =>
|
||||
# show article
|
||||
new ArticleView(
|
||||
ticket: @ticket
|
||||
ticket_article_ids: @ticket_article_ids
|
||||
el: @el.find('.ticket-article')
|
||||
ui: @
|
||||
)
|
||||
|
||||
Edit: =>
|
||||
# show edit
|
||||
new Edit(
|
||||
ticket: @ticket
|
||||
el: @el.find('.ticket-edit')
|
||||
#el: @el.find('.edit')
|
||||
form_meta: @form_meta
|
||||
task_key: @task_key
|
||||
ui: @
|
||||
)
|
||||
|
||||
autosaveStop: =>
|
||||
@autosaveLast = {}
|
||||
@clearInterval( 'autosave' )
|
||||
|
||||
autosaveStart: =>
|
||||
if !@autosaveLast
|
||||
@autosaveLast = App.TaskManager.get(@task_key).state || {}
|
||||
@autosaveLast = @taskGet()
|
||||
update = =>
|
||||
currentStore = @ticket.attributes()
|
||||
currentParams = @formParam( @el.find('.edit') )
|
||||
#console.log('AR', @formParam( @el.find('.article-add') ) )
|
||||
currentStore =
|
||||
ticket: @ticket.attributes()
|
||||
article: {
|
||||
type: ''
|
||||
body: ''
|
||||
internal: ''
|
||||
}
|
||||
currentParams =
|
||||
ticket: @formParam( @el.find('.edit') )
|
||||
article: @formParam( @el.find('.article-add') )
|
||||
|
||||
# get diff of model
|
||||
modelDiff = @getDiff( currentStore, currentParams )
|
||||
modelDiff =
|
||||
ticket: @getDiff( currentStore.ticket, currentParams.ticket )
|
||||
article: @getDiff( currentStore.article, currentParams.article )
|
||||
#console.log('modelDiff', modelDiff)
|
||||
|
||||
# get diff of last save
|
||||
|
@ -420,9 +412,9 @@ class App.TicketZoom extends App.Controller
|
|||
console.log('model DIFF ', modelDiff)
|
||||
|
||||
@autosaveLast = clone(currentParams)
|
||||
@markFormDiff( modelDiff )
|
||||
@markFormDiff( modelDiff.ticket )
|
||||
|
||||
App.TaskManager.update( @task_key, { 'state': modelDiff })
|
||||
@taskUpdateAll( modelDiff )
|
||||
@interval( update, 3000, 'autosave' )
|
||||
|
||||
getDiff: (model, params) =>
|
||||
|
@ -584,11 +576,35 @@ class App.TicketZoom extends App.Controller
|
|||
done: (r) =>
|
||||
|
||||
# reset form after save
|
||||
App.TaskManager.update( @task_key, { 'state': {} })
|
||||
@taskReset()
|
||||
|
||||
@fetch( ticket.id, true )
|
||||
)
|
||||
|
||||
taskGet: (area) =>
|
||||
@localTaskData = App.TaskManager.get(@task_key).state || {}
|
||||
if area
|
||||
if !@localTaskData[area]
|
||||
@localTaskData[area] = {}
|
||||
return @localTaskData[area]
|
||||
if !@localTaskData
|
||||
@localTaskData = {}
|
||||
@localTaskData
|
||||
|
||||
taskUpdate: (area, data) =>
|
||||
@localTaskData[area] = data
|
||||
App.TaskManager.update( @task_key, { 'state': @localTaskData })
|
||||
|
||||
taskUpdateAll: (data) =>
|
||||
@localTaskData = data
|
||||
App.TaskManager.update( @task_key, { 'state': @localTaskData })
|
||||
|
||||
taskReset: (area, data) =>
|
||||
@localTaskData =
|
||||
ticket: {}
|
||||
article: {}
|
||||
App.TaskManager.update( @task_key, { 'state': @localTaskData })
|
||||
|
||||
class TicketTitle extends App.Controller
|
||||
events:
|
||||
'blur .ticket-title-update': 'update'
|
||||
|
@ -653,38 +669,53 @@ class TicketMeta extends App.Controller
|
|||
|
||||
class Edit extends App.Controller
|
||||
elements:
|
||||
'textarea' : 'textarea'
|
||||
'.edit-control-item' : 'editControlItem'
|
||||
'.edit-controls': 'editControls'
|
||||
'.recipient-picker': 'recipientPicker'
|
||||
'.recipient-list': 'recipientList'
|
||||
'.recipient-list .list-arrow': 'recipientListArrow'
|
||||
'.js-attachment': 'attachmentHolder'
|
||||
'.js-attachment-text': 'attachmentText'
|
||||
'.bubble-placeholder-hint': 'bubblePlaceholderHint'
|
||||
'.js-textarea' : 'textarea'
|
||||
'.attachmentPlaceholder': 'attachmentPlaceholder'
|
||||
'.attachmentPlaceholder-inputHolder': 'attachmentInputHolder'
|
||||
'.attachmentPlaceholder-hint': 'attachmentHint'
|
||||
'.article-add': 'ticketEdit'
|
||||
'.attachments': 'attachmentsHolder'
|
||||
'.attachmentUpload': 'attachmentUpload'
|
||||
'.attachmentUpload-progressBar':'progressBar'
|
||||
'.js-percentage': 'progressText'
|
||||
#'.edit-control-item' : 'editControlItem'
|
||||
#'.edit-controls': 'editControls'
|
||||
#'.recipient-picker': 'recipientPicker'
|
||||
#'.recipient-list': 'recipientList'
|
||||
#'.recipient-list .list-arrow': 'recipientListArrow'
|
||||
|
||||
events:
|
||||
'click .submit': 'update'
|
||||
#'click .submit': 'update'
|
||||
'click [data-type="reset"]': 'reset'
|
||||
'click .visibility-toggle': 'toggle_visibility'
|
||||
'click .visibility-toggle': 'toggleVisibility'
|
||||
'click .pop-selectable': 'selectArticleType'
|
||||
'click .pop-selected': 'showSelectableArticleType'
|
||||
'focus textarea': 'open_textarea'
|
||||
'input textarea': 'detect_empty_textarea'
|
||||
'click .recipient-picker': 'toggle_recipients'
|
||||
'click .recipient-list': 'stopPropagation'
|
||||
'click .list-entry-type div': 'change_type'
|
||||
'submit .recipient-list form': 'add_recipient'
|
||||
'focus .js-textarea': 'open_textarea'
|
||||
'input .js-textarea': 'detect_empty_textarea'
|
||||
'dragenter': 'onDragenter'
|
||||
'dragleave': 'onDragleave'
|
||||
'drop': 'onFileDrop'
|
||||
'change input[type=file]': 'onFilePick'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@textareaHeight =
|
||||
open: 148
|
||||
closed: 38
|
||||
closed: 20
|
||||
|
||||
@dragEventCounter = 0
|
||||
@attachments = []
|
||||
|
||||
@render()
|
||||
|
||||
if @textarea.text().trim()
|
||||
@ticketEdit.addClass('is-open')
|
||||
|
||||
stopPropagation: (e) ->
|
||||
e.stopPropagation()
|
||||
|
||||
|
@ -729,74 +760,36 @@ class Edit extends App.Controller
|
|||
icon: 'note'
|
||||
},
|
||||
]
|
||||
|
||||
console.log('DEvvvvvV', @defaults)
|
||||
@html App.view('ticket_zoom/edit')(
|
||||
ticket: ticket
|
||||
type: @type
|
||||
articleTypes: articleTypes
|
||||
article: @defaults
|
||||
isCustomer: @isRole('Customer')
|
||||
)
|
||||
|
||||
@form_id = App.ControllerForm.formId()
|
||||
defaults = ticket.attributes()
|
||||
if @isRole('Customer')
|
||||
delete defaults['state_id']
|
||||
delete defaults['state']
|
||||
if !_.isEmpty( App.TaskManager.get(@task_key).state )
|
||||
defaults = App.TaskManager.get(@task_key).state
|
||||
configure_attributes = [
|
||||
{ name: 'customer_id', display: 'Recipients', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organisation/Company', minLengt: 2, disableCreateUser: false },
|
||||
]
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @el.find('.form-article-update')
|
||||
form_id: @form_id
|
||||
model: App.TicketArticle
|
||||
screen: 'edit'
|
||||
filter:
|
||||
type_id: [1,9,5]
|
||||
params: defaults
|
||||
dependency: [
|
||||
{
|
||||
bind: {
|
||||
name: 'type_id'
|
||||
relation: 'TicketArticleType'
|
||||
value: ['email']
|
||||
},
|
||||
change: {
|
||||
action: 'show'
|
||||
name: ['to', 'cc'],
|
||||
},
|
||||
},
|
||||
{
|
||||
bind: {
|
||||
name: 'type_id'
|
||||
relation: 'TicketArticleType'
|
||||
value: ['note', 'phone', 'twitter status']
|
||||
},
|
||||
change: {
|
||||
action: 'hide'
|
||||
name: ['to', 'cc'],
|
||||
},
|
||||
},
|
||||
{
|
||||
bind: {
|
||||
name: 'type_id'
|
||||
relation: 'TicketArticleType'
|
||||
value: ['twitter direct-message']
|
||||
},
|
||||
change: {
|
||||
action: 'show'
|
||||
name: ['to'],
|
||||
},
|
||||
},
|
||||
]
|
||||
controller = new App.ControllerForm(
|
||||
el: @$('.recipients')
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
)
|
||||
|
||||
# start auto save
|
||||
#@autosaveStart()
|
||||
@$('[data-name="body"]').ce({
|
||||
mode: 'textonly'
|
||||
multiline: true
|
||||
maxlength: 2500
|
||||
})
|
||||
|
||||
@form_id = App.ControllerForm.formId()
|
||||
|
||||
# show text module UI
|
||||
if !@isRole('Customer')
|
||||
textModule = new App.WidgetTextModule(
|
||||
el: @textarea
|
||||
el: @el
|
||||
data:
|
||||
ticket: ticket
|
||||
)
|
||||
|
@ -873,13 +866,16 @@ class Edit extends App.Controller
|
|||
console.log "add recipient", e
|
||||
# store recipient
|
||||
|
||||
toggle_visibility: ->
|
||||
if @el.hasClass('is-public')
|
||||
@el.removeClass('is-public')
|
||||
@el.addClass('is-internal')
|
||||
toggleVisibility: ->
|
||||
item = @$('.article-add')
|
||||
if item.hasClass('is-public')
|
||||
item.removeClass('is-public')
|
||||
item.addClass('is-internal')
|
||||
@$('[name="internal"]').val('true')
|
||||
else
|
||||
@el.addClass('is-public')
|
||||
@el.removeClass('is-internal')
|
||||
item.addClass('is-public')
|
||||
item.removeClass('is-internal')
|
||||
@$('[name="internal"]').val('')
|
||||
|
||||
showSelectableArticleType: =>
|
||||
@el.find('.pop-selector').removeClass('hide')
|
||||
|
@ -903,59 +899,60 @@ class Edit extends App.Controller
|
|||
if @type
|
||||
typeIcon.removeClass @type
|
||||
@type = type
|
||||
@$('[name="type"]').val(type)
|
||||
typeIcon.addClass @type
|
||||
|
||||
detect_empty_textarea: =>
|
||||
if !@textarea.val()
|
||||
if !@textarea.text().trim()
|
||||
@add_textarea_catcher()
|
||||
else
|
||||
@remove_textarea_catcher()
|
||||
|
||||
open_textarea: =>
|
||||
if !@textareaCatcher and !@textarea.val()
|
||||
@el.addClass('is-open')
|
||||
console.log('OT', @textareaCatcher , @textarea.text().trim() , @attachments.length)
|
||||
if !@textareaCatcher and !@textarea.text().trim() and !@attachments.length
|
||||
@ticketEdit.addClass('is-open')
|
||||
|
||||
@textarea.velocity
|
||||
properties:
|
||||
height: "#{ @textareaHeight.open - 38 }px"
|
||||
minHeight: "#{ @textareaHeight.open - 38 }px"
|
||||
marginBottom: 38
|
||||
options:
|
||||
duration: 300
|
||||
easing: 'easeOutQuad'
|
||||
complete: => @add_textarea_catcher()
|
||||
|
||||
# scroll to bottom
|
||||
@textarea.velocity "scroll",
|
||||
container: @textarea.scrollParent()
|
||||
offset: 99999
|
||||
duration: 300
|
||||
easing: 'easeOutQuad'
|
||||
queue: false
|
||||
# @textarea.velocity "scroll",
|
||||
# container: @textarea.scrollParent()
|
||||
# offset: 99999
|
||||
# duration: 300
|
||||
# easing: 'easeOutQuad'
|
||||
# queue: false
|
||||
|
||||
@editControlItem.velocity "transition.slideRightIn",
|
||||
duration: 300
|
||||
stagger: 50
|
||||
drag: true
|
||||
# @editControlItem.velocity "transition.slideRightIn",
|
||||
# duration: 300
|
||||
# stagger: 50
|
||||
# drag: true
|
||||
|
||||
# move attachment text to the left bottom (bottom happens automatically)
|
||||
|
||||
@attachmentHolder.velocity
|
||||
@attachmentPlaceholder.velocity
|
||||
properties:
|
||||
translateX: -@attachmentText.position().left + "px"
|
||||
translateX: -@attachmentInputHolder.position().left + "px"
|
||||
options:
|
||||
duration: 300
|
||||
easing: 'easeOutQuad'
|
||||
|
||||
@bubblePlaceholderHint.velocity
|
||||
@attachmentHint.velocity
|
||||
properties:
|
||||
opacity: 0
|
||||
options:
|
||||
duration: 300
|
||||
|
||||
@add_textarea_catcher()
|
||||
|
||||
add_textarea_catcher: ->
|
||||
@textareaCatcher = new App.clickCatcher
|
||||
holder: @el.offsetParent()
|
||||
holder: @ticketEdit.offsetParent()
|
||||
callback: @close_textarea
|
||||
zIndexScale: 4
|
||||
|
||||
|
@ -966,168 +963,107 @@ class Edit extends App.Controller
|
|||
|
||||
close_textarea: =>
|
||||
@remove_textarea_catcher()
|
||||
if !@textarea.val()
|
||||
if !@textarea.text().trim() && !@attachments.length
|
||||
|
||||
@textarea.velocity
|
||||
properties:
|
||||
height: "#{ @textareaHeight.closed }px"
|
||||
minHeight: "#{ @textareaHeight.closed }px"
|
||||
marginBottom: 0
|
||||
options:
|
||||
duration: 300
|
||||
easing: 'easeOutQuad'
|
||||
complete: => @el.removeClass('is-open')
|
||||
complete: => @ticketEdit.removeClass('is-open')
|
||||
|
||||
@attachmentHolder.velocity
|
||||
@attachmentPlaceholder.velocity
|
||||
properties:
|
||||
translateX: 0
|
||||
options:
|
||||
duration: 300
|
||||
easing: 'easeOutQuad'
|
||||
|
||||
@bubblePlaceholderHint.velocity
|
||||
@attachmentHint.velocity
|
||||
properties:
|
||||
opacity: 1
|
||||
options:
|
||||
duration: 300
|
||||
|
||||
@editControlItem.css('display', 'none')
|
||||
# @editControlItem.css('display', 'none')
|
||||
|
||||
autosaveStop: =>
|
||||
@clearInterval( 'autosave' )
|
||||
onDragenter: (event) =>
|
||||
# on the first event,
|
||||
# open textarea (it will only open if its closed)
|
||||
@open_textarea() if @dragEventCounter is 0
|
||||
|
||||
autosaveStart: =>
|
||||
@autosaveLast = _.clone( @ui.formDefault )
|
||||
update = =>
|
||||
currentData = @formParam( @el.find('.ticket-update') )
|
||||
diff = difference( @autosaveLast, currentData )
|
||||
if !@autosaveLast || ( diff && !_.isEmpty( diff ) )
|
||||
@autosaveLast = currentData
|
||||
@log 'notice', 'form hash changed', diff, currentData
|
||||
@el.find('.edit').addClass('form-changed')
|
||||
@el.find('.edit').find('.reset-message').show()
|
||||
@el.find('.edit').find('.reset-message').removeClass('hide')
|
||||
App.TaskManager.update( @task_key, { 'state': currentData })
|
||||
@interval( update, 3000, 'autosave' )
|
||||
@dragEventCounter++
|
||||
@ticketEdit.addClass('is-dropTarget')
|
||||
|
||||
update: (e) =>
|
||||
e.preventDefault()
|
||||
#@autosaveStop()
|
||||
params = @formParam(e.target)
|
||||
onDragleave: (event) =>
|
||||
@dragEventCounter--
|
||||
|
||||
# get ticket
|
||||
ticket = App.Ticket.fullLocal( @ticket.id )
|
||||
@ticketEdit.removeClass('is-dropTarget') if @dragEventCounter is 0
|
||||
|
||||
@log 'notice', 'update', params, ticket
|
||||
onFileDrop: (event) =>
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
files = event.originalEvent.dataTransfer.files
|
||||
@ticketEdit.removeClass('is-dropTarget')
|
||||
|
||||
# update local ticket
|
||||
@queueUpload(files)
|
||||
|
||||
# create local article
|
||||
onFilePick: (event) =>
|
||||
@open_textarea()
|
||||
@queueUpload(event.target.files)
|
||||
|
||||
queueUpload: (files) ->
|
||||
@uploadQueue ?= []
|
||||
|
||||
# find sender_id
|
||||
if @isRole('Customer')
|
||||
sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
|
||||
type = App.TicketArticleType.findByAttribute( 'name', 'web' )
|
||||
params.type_id = type.id
|
||||
params.sender_id = sender.id
|
||||
else
|
||||
sender = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
|
||||
type = App.TicketArticleType.find( params['type_id'] )
|
||||
params.sender_id = sender.id
|
||||
# add files
|
||||
for file in files
|
||||
@uploadQueue.push(file)
|
||||
|
||||
# update ticket
|
||||
for key, value of params
|
||||
ticket[key] = value
|
||||
@workOfUploadQueue()
|
||||
|
||||
# check owner assignment
|
||||
if !@isRole('Customer')
|
||||
if !ticket['owner_id']
|
||||
ticket['owner_id'] = 1
|
||||
|
||||
# check if title exists
|
||||
if !ticket['title']
|
||||
alert( App.i18n.translateContent('Title needed') )
|
||||
workOfUploadQueue: =>
|
||||
if !@uploadQueue.length
|
||||
return
|
||||
|
||||
# validate email params
|
||||
if type.name is 'email'
|
||||
file = @uploadQueue.shift()
|
||||
# console.log "working of", file, "from", @uploadQueue
|
||||
@fakeUpload file.name, file.size, @workOfUploadQueue
|
||||
|
||||
# check if recipient exists
|
||||
if !params['to'] && !params['cc']
|
||||
alert( App.i18n.translateContent('Need recipient in "To" or "Cc".') )
|
||||
return
|
||||
humanFileSize: (size) =>
|
||||
i = Math.floor( Math.log(size) / Math.log(1024) )
|
||||
return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
|
||||
# check if message exists
|
||||
if !params['body']
|
||||
alert( App.i18n.translateContent('Text needed') )
|
||||
return
|
||||
updateUploadProgress: (progress) =>
|
||||
@progressBar.width(progress + "%")
|
||||
@progressText.text(progress)
|
||||
|
||||
# check attachment
|
||||
if params['body']
|
||||
attachmentTranslated = App.i18n.translateContent('Attachment')
|
||||
attachmentTranslatedRegExp = new RegExp( attachmentTranslated, 'i' )
|
||||
if params['body'].match(/attachment/i) || params['body'].match( attachmentTranslatedRegExp )
|
||||
if !confirm( App.i18n.translateContent('You use attachment in text but no attachment is attached. Do you want to continue?') )
|
||||
#@autosaveStart()
|
||||
return
|
||||
if progress is 100
|
||||
@attachmentPlaceholder.removeClass('hide')
|
||||
@attachmentUpload.addClass('hide')
|
||||
|
||||
# submit ticket & article
|
||||
@log 'notice', 'update ticket', ticket
|
||||
fakeUpload: (fileName, fileSize, callback) ->
|
||||
@attachmentPlaceholder.addClass('hide')
|
||||
@attachmentUpload.removeClass('hide')
|
||||
|
||||
# disable form
|
||||
@formDisable(e)
|
||||
progress = 0;
|
||||
duration = fileSize / 1024
|
||||
|
||||
# validate ticket
|
||||
errors = ticket.validate(
|
||||
screen: 'edit'
|
||||
)
|
||||
if errors
|
||||
@log 'error', 'update', errors
|
||||
for i in [0..100]
|
||||
setTimeout @updateUploadProgress, i*duration/100 , i
|
||||
|
||||
@log 'error', errors
|
||||
@formValidate(
|
||||
form: e.target
|
||||
errors: errors
|
||||
screen: 'edit'
|
||||
)
|
||||
@formEnable(e)
|
||||
#@autosaveStart()
|
||||
return
|
||||
setTimeout (=>
|
||||
callback()
|
||||
@renderAttachment(fileName, fileSize)
|
||||
), duration
|
||||
|
||||
# validate article
|
||||
articleAttributes = App.TicketArticle.attributesGet( 'edit' )
|
||||
if params['body'] || ( articleAttributes['body'] && articleAttributes['body']['null'] is false )
|
||||
article = new App.TicketArticle
|
||||
params.from = @Session.get().displayName()
|
||||
params.ticket_id = ticket.id
|
||||
params.form_id = @form_id
|
||||
renderAttachment: (fileName, fileSize) =>
|
||||
@attachments.push([fileName, fileSize])
|
||||
@attachmentsHolder.append App.view('ticket_zoom/attachment')
|
||||
fileName: fileName
|
||||
fileSize: @humanFileSize(fileSize)
|
||||
|
||||
if !params['internal']
|
||||
params['internal'] = false
|
||||
|
||||
@log 'notice', 'update article', params, sender
|
||||
article.load(params)
|
||||
errors = article.validate()
|
||||
if errors
|
||||
@log 'error', 'update article', errors
|
||||
@formValidate(
|
||||
form: e.target
|
||||
errors: errors
|
||||
screen: 'edit'
|
||||
)
|
||||
@formEnable(e)
|
||||
@autosaveStart()
|
||||
return
|
||||
|
||||
ticket.article = article
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
|
||||
# reset form after save
|
||||
App.TaskManager.update( @task_key, { 'state': {} })
|
||||
|
||||
@ui.fetch( ticket.id, true )
|
||||
)
|
||||
|
||||
reset: (e) =>
|
||||
e.preventDefault()
|
||||
|
|
|
@ -2,38 +2,31 @@ class App.WidgetTextModule extends App.Controller
|
|||
constructor: ->
|
||||
super
|
||||
|
||||
@lastData = {}
|
||||
customItemTemplate = "<div><span /> <small /></div>"
|
||||
elementFactory = (element, e) ->
|
||||
template = $(customItemTemplate).find('span')
|
||||
.text(e.val).end()
|
||||
.find('small')
|
||||
.text("(" + e.keywords + ")").end()
|
||||
element.append(template)
|
||||
|
||||
@el.parent().find('textarea').sew(
|
||||
values: @reload(@data)
|
||||
token: '::'
|
||||
elementFactory: elementFactory
|
||||
)
|
||||
# remember instances
|
||||
@bindElements = []
|
||||
if @selector
|
||||
@bindElements = @$( @selector ).textmodule()
|
||||
else
|
||||
@bindElements = @$('[contenteditable]').textmodule()
|
||||
@update()
|
||||
|
||||
@subscribeId = App.TextModule.subscribe(@update, initFetch: true )
|
||||
|
||||
release: =>
|
||||
App.TextModule.unsubscribe(@subscribeId)
|
||||
|
||||
reload: (data = false) =>
|
||||
if data
|
||||
@lastData['data'] = data
|
||||
reload: (data) =>
|
||||
return if !data
|
||||
@update()
|
||||
|
||||
update: =>
|
||||
all = App.TextModule.all()
|
||||
values = [{val: '-', keywords: '-'}]
|
||||
ui = @lastData || @
|
||||
for item in all
|
||||
allRaw = App.TextModule.all()
|
||||
all = []
|
||||
ui = @data || @
|
||||
for item in allRaw
|
||||
if item.active is true
|
||||
contentNew = item.content.replace( /<%=\s{0,2}(.+?)\s{0,2}%>/g, ( all, key ) ->
|
||||
attributes = item.attributes()
|
||||
attributes.content = attributes.content.replace( /<%=\s{0,2}(.+?)\s{0,2}%>/g, ( index, key ) ->
|
||||
key = key.replace( /@/g, 'ui.data.' )
|
||||
varString = "#{key}" + ''
|
||||
# console.log( "tag replacement env: ", ui.data)
|
||||
|
@ -45,16 +38,10 @@ class App.WidgetTextModule extends App.Controller
|
|||
key = ''
|
||||
return key
|
||||
)
|
||||
value = { val: contentNew, keywords: item.keywords || item.name }
|
||||
values.push value
|
||||
|
||||
if values.length isnt 1
|
||||
values.shift()
|
||||
all.push attributes
|
||||
|
||||
# set new data
|
||||
if @el[0]
|
||||
if $(@el[0]).data()
|
||||
if $(@el[0]).data().plugin_sew
|
||||
$(@el[0]).data().plugin_sew.options.values = values
|
||||
|
||||
return values
|
||||
if @bindElements[0]
|
||||
for element in @bindElements
|
||||
if $(element).data().plugin_textmodule
|
||||
$(element).data().plugin_textmodule.collection = all
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
#
|
||||
*/
|
||||
|
||||
var DEFAULTS = {
|
||||
var pluginName = 'ce',
|
||||
defaults = {
|
||||
mode: 'richtext',
|
||||
multiline: true,
|
||||
allowKey: {
|
||||
|
@ -38,24 +39,157 @@
|
|||
73: true, // i
|
||||
85: true, // u
|
||||
}
|
||||
};
|
||||
|
||||
function Plugin( element, options ) {
|
||||
this.element = element;
|
||||
this.$element = $(element)
|
||||
|
||||
this.options = $.extend( {}, defaults, options) ;
|
||||
|
||||
this._defaults = defaults;
|
||||
this._name = pluginName;
|
||||
|
||||
this.preventInput = false
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
Plugin.prototype.init = function () {
|
||||
// process placeholder
|
||||
if ( this.options.placeholder ) {
|
||||
this.updatePlaceholder( 'add' )
|
||||
this.$element.on('focus', $.proxy(function (e) {
|
||||
this.updatePlaceholder( 'remove' )
|
||||
}, this)).on('blur', $.proxy(function (e) {
|
||||
this.updatePlaceholder( 'add' )
|
||||
}, this))
|
||||
}
|
||||
|
||||
// maxlength check
|
||||
//this.options.maxlength = 10
|
||||
if ( this.options.maxlength ) {
|
||||
this.$element.on('keydown', $.proxy(function (e) {
|
||||
console.log('maxlength', e.keyCode, this.allowKey(e))
|
||||
// check control key
|
||||
if ( this.allowKey(e) ) {
|
||||
this.maxLengthOk()
|
||||
}
|
||||
// check type ahead key
|
||||
else {
|
||||
if ( !this.maxLengthOk( true ) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}, this)).on('keyup', $.proxy(function (e) {
|
||||
// check control key
|
||||
if ( this.allowKey(e) ) {
|
||||
this.maxLengthOk()
|
||||
}
|
||||
// check type ahead key
|
||||
else {
|
||||
if ( !this.maxLengthOk( true ) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}, this)).on('focus', $.proxy(function (e) {
|
||||
this.maxLengthOk()
|
||||
}, this)).on('blur', $.proxy(function (e) {
|
||||
this.maxLengthOk()
|
||||
}, this))
|
||||
}
|
||||
|
||||
// handle enter
|
||||
this.$element.on('keydown', $.proxy(function (e) {
|
||||
console.log('keydown', e.keyCode)
|
||||
if (this.preventInput) {
|
||||
console.log('preventInput', this.preventInput)
|
||||
return
|
||||
}
|
||||
|
||||
// trap the return key being pressed
|
||||
if (e.keyCode === 13) {
|
||||
// disbale multi line
|
||||
if ( !this.options.multiline ) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
// limit check
|
||||
if ( !this.maxLengthOk( true ) ) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if ( this.options.mode === 'textonly' ) {
|
||||
document.execCommand('insertHTML', false, "\n")
|
||||
}
|
||||
else {
|
||||
document.execCommand('insertHTML', false, '<br>')
|
||||
}
|
||||
// prevent the default behaviour of return key pressed
|
||||
return false
|
||||
}
|
||||
}, this))
|
||||
|
||||
// just paste text
|
||||
if ( this.options.mode === 'textonly' ) {
|
||||
this.$element.on('paste', $.proxy(function (e) {
|
||||
var text = (e.originalEvent || e).clipboardData.getData('text/plain')
|
||||
var overlimit = false
|
||||
if (text) {
|
||||
|
||||
// replace new lines
|
||||
if ( !this.options.multiline ) {
|
||||
text = text.replace(/\n/g, '')
|
||||
text = text.replace(/\r/g, '')
|
||||
text = text.replace(/\t/g, '')
|
||||
}
|
||||
|
||||
// limit length, limit paste string
|
||||
if ( this.options.maxlength ) {
|
||||
var pasteLength = text.length
|
||||
var currentLength = this.$element.text().length
|
||||
var overSize = ( currentLength + pasteLength ) - this.options.maxlength
|
||||
if ( overSize > 0 ) {
|
||||
text = text.substr( 0, pasteLength - overSize )
|
||||
overlimit = true
|
||||
}
|
||||
}
|
||||
|
||||
// insert new text
|
||||
e.preventDefault()
|
||||
document.execCommand('inserttext', false, text)
|
||||
this.maxLengthOk( overlimit )
|
||||
}
|
||||
|
||||
}, this))
|
||||
}
|
||||
|
||||
// disable rich text b/u/i
|
||||
if ( this.options.mode === 'textonly' ) {
|
||||
this.$element.on('keydown', $.proxy(function (e) {
|
||||
if ( this.richTextKey(e) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}, this))
|
||||
}
|
||||
};
|
||||
|
||||
// add/remove placeholder
|
||||
var updatePlaceholder = function(target, type) {
|
||||
var options = target.data('ce.options')
|
||||
var text = target.text().trim()
|
||||
var placeholder = '<span class="placeholder">' + options.placeholder + '</span>'
|
||||
Plugin.prototype.updatePlaceholder = function(type) {
|
||||
var text = this.$element.text().trim()
|
||||
var placeholder = '<span class="placeholder">' + this.options.placeholder + '</span>'
|
||||
|
||||
// add placholder if no text exists
|
||||
if ( type === 'add') {
|
||||
if ( !text ) {
|
||||
target.html( placeholder )
|
||||
this.$element.html( placeholder )
|
||||
}
|
||||
}
|
||||
|
||||
// empty placeholder text
|
||||
else {
|
||||
if ( text === options.placeholder ) {
|
||||
if ( text === this.options.placeholder ) {
|
||||
setTimeout(function(){
|
||||
document.execCommand('selectAll', false, '');
|
||||
document.execCommand('delete', false, '');
|
||||
|
@ -66,178 +200,85 @@
|
|||
}
|
||||
}
|
||||
|
||||
// max length check
|
||||
var maxLengthOk = function(field, typeAhead) {
|
||||
var options = field.data('ce.options')
|
||||
if (!options) {
|
||||
return true
|
||||
// disable/enable input
|
||||
Plugin.prototype.input = function(type) {
|
||||
if (type === 'off') {
|
||||
this.preventInput = true
|
||||
}
|
||||
else {
|
||||
this.preventInput = false
|
||||
}
|
||||
}
|
||||
|
||||
var length = field.text().length
|
||||
// max length check
|
||||
Plugin.prototype.maxLengthOk = function(typeAhead) {
|
||||
var length = this.$element.text().length
|
||||
if (typeAhead) {
|
||||
length = length + 1
|
||||
}
|
||||
if ( length > options.maxlength ) {
|
||||
field.addClass('invalid')
|
||||
setTimeout(function(){
|
||||
field.removeClass('invalid')
|
||||
}, 1000);
|
||||
if ( length > this.options.maxlength ) {
|
||||
this.$element.addClass('invalid')
|
||||
setTimeout($.proxy(function(){
|
||||
this.$element.removeClass('invalid')
|
||||
}, this), 1000)
|
||||
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// check if key is allowed, even if length limit is reached
|
||||
var allowKey = function(e) {
|
||||
var options = $(e.target).data('ce.options')
|
||||
|
||||
if ( options.allowKey[ e.keyCode ] ) {
|
||||
Plugin.prototype.allowKey = function(e) {
|
||||
if ( this.options.allowKey[ e.keyCode ] ) {
|
||||
return true
|
||||
}
|
||||
if ( ( e.ctrlKey || e.metaKey ) && options.extraAllowKey[ e.keyCode ] ) {
|
||||
if ( ( e.ctrlKey || e.metaKey ) && this.options.extraAllowKey[ e.keyCode ] ) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check if rich text key is pressed
|
||||
var richTextKey = function(e) {
|
||||
var options = $(e.target).data('ce.options')
|
||||
|
||||
if ( ( e.ctrlKey || e.metaKey ) && options.richTextFormatKey[ e.keyCode ] ) {
|
||||
Plugin.prototype.richTextKey = function(e) {
|
||||
if ( ( e.ctrlKey || e.metaKey ) && this.options.richTextFormatKey[ e.keyCode ] ) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// get correct val if textbox
|
||||
$.fn.ceg = function(option) {
|
||||
var options = this.data('ce.options')
|
||||
|
||||
updatePlaceholder( this, 'remove' )
|
||||
// get value
|
||||
Plugin.prototype.value = function() {
|
||||
this.updatePlaceholder( 'remove' )
|
||||
|
||||
// get text
|
||||
if ( options.mode === 'textonly' ) {
|
||||
if ( this.options.mode === 'textonly' ) {
|
||||
|
||||
// strip html signes if multi line exists
|
||||
if ( options.multiline ) {
|
||||
text = this.html()
|
||||
if ( this.options.multiline ) {
|
||||
text = this.$element.html()
|
||||
text = text.replace(/<br>/g, "\n") // new line as br
|
||||
text = text.replace(/<div>/g, "\n") // in some caes, new line als div
|
||||
text = $("<div>" + text + "</div>").text().trim()
|
||||
return text
|
||||
}
|
||||
return this.text().trim()
|
||||
return this.$element.text().trim()
|
||||
}
|
||||
return this.html().trim()
|
||||
return this.$element.html().trim()
|
||||
}
|
||||
|
||||
$.fn.ce = function(option) {
|
||||
var options = $.extend({}, DEFAULTS, option)
|
||||
options.placeholder = options.placeholder || this.data('placeholder')
|
||||
|
||||
// store options
|
||||
this.data('ce.options', options)
|
||||
|
||||
// process placeholder
|
||||
if ( options.placeholder ) {
|
||||
updatePlaceholder( this, 'add' )
|
||||
this.bind('focus', function (e) {
|
||||
updatePlaceholder( $(e.target), 'remove' )
|
||||
}).bind('blur', function (e) {
|
||||
updatePlaceholder( $(e.target), 'add' )
|
||||
})
|
||||
}
|
||||
|
||||
// maxlength check
|
||||
if ( options.maxlength ) {
|
||||
this.bind('keydown', function (e) {
|
||||
|
||||
// check control key
|
||||
if ( allowKey(e) ) {
|
||||
maxLengthOk( $(e.target) )
|
||||
}
|
||||
|
||||
// check type ahead key
|
||||
else {
|
||||
if ( !maxLengthOk( $(e.target), true ) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}).bind('keyup', function (e) {
|
||||
|
||||
// check control key
|
||||
if ( allowKey(e) ) {
|
||||
maxLengthOk( $(e.target) )
|
||||
}
|
||||
|
||||
// check type ahead key
|
||||
else {
|
||||
if ( !maxLengthOk( $(e.target), true ) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}).bind('focus', function (e) {
|
||||
maxLengthOk( $(e.target) )
|
||||
}).bind('blur', function (e) {
|
||||
maxLengthOk( $(e.target) )
|
||||
})
|
||||
}
|
||||
|
||||
// just paste text
|
||||
if ( options.mode === 'textonly' ) {
|
||||
this.bind('paste', function (e) {
|
||||
var text = (e.originalEvent || e).clipboardData.getData('text/plain')
|
||||
var overlimit = false
|
||||
if (text) {
|
||||
|
||||
// replace new lines
|
||||
if ( !options.multiline ) {
|
||||
text = text.replace(/\n/g, '')
|
||||
text = text.replace(/\r/g, '')
|
||||
text = text.replace(/\t/g, '')
|
||||
}
|
||||
|
||||
// limit length, limit paste string
|
||||
if ( options.maxlength ) {
|
||||
var pasteLength = text.length
|
||||
var currentLength = $(e.target).text().length
|
||||
var overSize = ( currentLength + pasteLength ) - options.maxlength
|
||||
if ( overSize > 0 ) {
|
||||
text = text.substr( 0, pasteLength - overSize )
|
||||
overlimit = true
|
||||
}
|
||||
}
|
||||
|
||||
// insert new text
|
||||
e.preventDefault()
|
||||
document.execCommand('inserttext', false, text)
|
||||
maxLengthOk( $(e.target), overlimit )
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// disable rich text b/u/i
|
||||
if ( options.mode === 'textonly' ) {
|
||||
this.bind('keydown', function (e) {
|
||||
if ( richTextKey(e) ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// disable multi line
|
||||
if ( !options.multiline ) {
|
||||
this.bind('keydown', function (e) {
|
||||
switch ( e.keyCode ) {
|
||||
case 13: // enter
|
||||
e.preventDefault()
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
$.fn[pluginName] = function ( options ) {
|
||||
return this.each(function () {
|
||||
if (!$.data(this, 'plugin_' + pluginName)) {
|
||||
$.data(this, 'plugin_' + pluginName,
|
||||
new Plugin( this, options ));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// get correct val if textbox
|
||||
$.fn.ceg = function() {
|
||||
var plugin = $.data(this[0], 'plugin_' + pluginName)
|
||||
return plugin.value()
|
||||
}
|
||||
|
||||
}(jQuery));
|
||||
|
|
297
app/assets/javascripts/app/lib/base/jquery.textmodule.js
Normal file
297
app/assets/javascripts/app/lib/base/jquery.textmodule.js
Normal file
|
@ -0,0 +1,297 @@
|
|||
(function ($, window, undefined) {
|
||||
|
||||
/*
|
||||
# mode: textonly/richtext / disable b/i/u/enter + strip on paste
|
||||
# pasteOnlyText: true
|
||||
# maxlength: 123
|
||||
# multiline: true / disable enter + strip on paste
|
||||
# placeholder: 'some placeholder'
|
||||
#
|
||||
*/
|
||||
|
||||
var pluginName = 'textmodule',
|
||||
defaults = {}
|
||||
|
||||
function Plugin( element, options ) {
|
||||
this.element = element
|
||||
this.$element = $(element)
|
||||
|
||||
this.options = $.extend( {}, defaults, options)
|
||||
|
||||
this._defaults = defaults
|
||||
this._name = pluginName
|
||||
|
||||
this.collection = []
|
||||
this.active = false
|
||||
this.buffer = ''
|
||||
|
||||
// check if ce exists
|
||||
if ( $.data(element, 'plugin_ce') ) {
|
||||
this.ce = $.data(element, 'plugin_ce')
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
Plugin.prototype.init = function () {
|
||||
this.baseTemplate()
|
||||
|
||||
this.$element.on('keydown', $.proxy(function (e) {
|
||||
|
||||
// esc
|
||||
if ( e.keyCode === 27 ) {
|
||||
this.close()
|
||||
}
|
||||
|
||||
// navigate through widget
|
||||
if ( this.isActive() ) {
|
||||
console.log('WIDGET IS OPEN', e.keyCode)
|
||||
|
||||
// enter
|
||||
if ( e.keyCode === 13 ) {
|
||||
e.preventDefault()
|
||||
var id = this.$widget.find('.dropdown-menu li.active a').data('id')
|
||||
console.log('ID', id)
|
||||
this.take(id)
|
||||
}
|
||||
|
||||
// arrow keys
|
||||
if ( e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40 ) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// up
|
||||
if ( e.keyCode === 38 ) {
|
||||
if ( !this.$widget.find('.dropdown-menu li.active')[0] ) {
|
||||
var top = this.$widget.find('.dropdown-menu li').last().addClass('active').position().top
|
||||
this.$widget.find('.dropdown-menu').scrollTop( top );
|
||||
}
|
||||
else {
|
||||
var prev = this.$widget.find('.dropdown-menu li.active').removeClass('active').prev()
|
||||
var top = 300
|
||||
if ( prev[0] ) {
|
||||
top = prev.addClass('active').position().top
|
||||
}
|
||||
this.$widget.find('.dropdown-menu').scrollTop( top );
|
||||
}
|
||||
}
|
||||
|
||||
// down
|
||||
if ( e.keyCode === 40 ) {
|
||||
if ( !this.$widget.find('.dropdown-menu li.active')[0] ) {
|
||||
var top = this.$widget.find('.dropdown-menu li').first().addClass('active').position().top
|
||||
this.$widget.find('.dropdown-menu').scrollTop( top );
|
||||
|
||||
}
|
||||
else {
|
||||
var next = this.$widget.find('.dropdown-menu li.active').removeClass('active').next()
|
||||
var top = 300
|
||||
if ( next[0] ) {
|
||||
top = next.addClass('active').position().top
|
||||
}
|
||||
console.log('scrollTop', top, top-30)
|
||||
this.$widget.find('.dropdown-menu').scrollTop( top );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}, this ))
|
||||
|
||||
this.$element.on('keydown', $.proxy(function (e) {
|
||||
|
||||
// backspace
|
||||
if ( e.keyCode === 8 && this.buffer ) {
|
||||
if ( this.buffer === '::' ) {
|
||||
this.close()
|
||||
}
|
||||
this.buffer = this.buffer.substr( 0, this.buffer.length-1 )
|
||||
console.log('BS', this.buffer)
|
||||
this.result( this.buffer.substr(2,this.buffer.length) )
|
||||
}
|
||||
}, this ))
|
||||
|
||||
this.$element.on('keypress', $.proxy(function (e) {
|
||||
var value = this.$element.text()
|
||||
console.log('BUFF', this.buffer, e.keyCode, String.fromCharCode(e.which) )
|
||||
a = $.proxy(function() {
|
||||
|
||||
// shift
|
||||
if ( e.keyCode === 16 ) {
|
||||
return
|
||||
}
|
||||
|
||||
// enter :
|
||||
if ( e.keyCode === 58 ) {
|
||||
this.buffer = this.buffer + ':'
|
||||
}
|
||||
|
||||
if ( this.buffer && this.buffer.substr(0,2) === '::' ) {
|
||||
|
||||
|
||||
var sign = String.fromCharCode(e.which)
|
||||
if ( e.keyCode !== 58 ) {
|
||||
this.buffer = this.buffer + sign
|
||||
}
|
||||
console.log('BUFF HINT', this.buffer, this.buffer.length, e.which)
|
||||
|
||||
this.result( this.buffer.substr(2,this.buffer.length) )
|
||||
|
||||
if (!this.isActive()) {
|
||||
this.open()
|
||||
}
|
||||
|
||||
}
|
||||
}, this)
|
||||
setTimeout(a, 400);
|
||||
|
||||
}, this)).on('focus', $.proxy(function (e) {
|
||||
this.close()
|
||||
}, this)).on('blur', $.proxy(function (e) {
|
||||
this.close()
|
||||
}, this))
|
||||
|
||||
};
|
||||
|
||||
// create base template
|
||||
Plugin.prototype.baseTemplate = function() {
|
||||
this.$element.after('<div class="shortcut dropdown"><ul class="dropdown-menu" style="width: 360px; max-height: 200px;"><li><a>-</a></li></ul></div>')
|
||||
this.$widget = this.$element.next()
|
||||
this.updatePosition()
|
||||
}
|
||||
|
||||
// get cursor position
|
||||
Plugin.prototype.getCaretPosition = function() {
|
||||
document.execCommand('insertHTML', false, '<span id="hidden"></span>');
|
||||
var hiddenNode = document.getElementById('hidden');
|
||||
if (!hiddenNode) {
|
||||
return 0;
|
||||
}
|
||||
var position = $(hiddenNode).position()
|
||||
hiddenNode.parentNode.removeChild(hiddenNode)
|
||||
return position
|
||||
}
|
||||
|
||||
// update widget position
|
||||
Plugin.prototype.updatePosition = function() {
|
||||
this.$widget.find('.dropdown-menu').scrollTop( 300 );
|
||||
var position = this.getCaretPosition()
|
||||
var heightTextarea = this.$element.height()
|
||||
var widgetHeight = this.$widget.find('ul').height() + 40
|
||||
console.log('position', position)
|
||||
console.log('heightTextarea', heightTextarea)
|
||||
console.log('widgetHeight', widgetHeight)
|
||||
this.$widget.css('top', position.top - heightTextarea - widgetHeight)
|
||||
if ( !this.isActive() ) {
|
||||
this.$widget.css('left', position.left)
|
||||
}
|
||||
}
|
||||
|
||||
// open widget
|
||||
Plugin.prototype.open = function() {
|
||||
this.active = true
|
||||
if (this.ce) {
|
||||
this.ce.input('off')
|
||||
}
|
||||
this.$widget.addClass('open')
|
||||
}
|
||||
|
||||
// close widget
|
||||
Plugin.prototype.close = function() {
|
||||
this.active = false
|
||||
this.cutInput()
|
||||
if (this.ce) {
|
||||
this.ce.input('on')
|
||||
}
|
||||
this.$widget.removeClass('open')
|
||||
}
|
||||
|
||||
// check if widget is active/open
|
||||
Plugin.prototype.isActive = function() {
|
||||
return this.active
|
||||
}
|
||||
|
||||
// select text module and insert into text
|
||||
Plugin.prototype.take = function(id) {
|
||||
if (!id) {
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
for (var i = 0; i < this.collection.length; i++) {
|
||||
var item = this.collection[i]
|
||||
if ( item.id == id ) {
|
||||
var content = item.content + "\n"
|
||||
this.cutInput()
|
||||
document.execCommand('insertHTML', false, content)
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cut out search string from text
|
||||
Plugin.prototype.cutInput = function() {
|
||||
if (!this.buffer) {
|
||||
return
|
||||
}
|
||||
var sel = window.getSelection();
|
||||
var range = sel.getRangeAt(0);
|
||||
var clone = range.cloneRange();
|
||||
clone.setStart(range.startContainer, range.startOffset - this.buffer.length);
|
||||
clone.setEnd(range.startContainer, range.startOffset);
|
||||
clone.deleteContents();
|
||||
this.buffer = ''
|
||||
}
|
||||
|
||||
// render result
|
||||
Plugin.prototype.result = function(term) {
|
||||
|
||||
var result = _.filter( this.collection, function(item) {
|
||||
reg = new RegExp( term, 'i' )
|
||||
if ( item.name && item.name.match( reg ) ) {
|
||||
return item
|
||||
}
|
||||
if ( item.keywords && item.keywords.match( reg ) ) {
|
||||
return item
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
this.$widget.find('ul').html('')
|
||||
console.log('result', term, result)
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var item = result[i]
|
||||
template = "<li><a href=\"#\" class=\"u-textTruncate\" data-id=" + item.id + ">" + item.name
|
||||
if (item.keywords) {
|
||||
template = template + " (" + item.keywords + ")"
|
||||
}
|
||||
template = template + "</a></li>"
|
||||
this.$widget.find('ul').append(template)
|
||||
}
|
||||
if ( !result[0] ) {
|
||||
this.$widget.find('ul').append("<li><a href='#'>-</a></li>")
|
||||
}
|
||||
this.$widget.find('ul li').on(
|
||||
'click',
|
||||
function(e) {
|
||||
console.log(31231)
|
||||
e.preventDefault()
|
||||
var id = $(e.target).data('id')
|
||||
console.log('99', id)
|
||||
}
|
||||
)
|
||||
this.updatePosition()
|
||||
}
|
||||
|
||||
|
||||
$.fn[pluginName] = function ( options ) {
|
||||
return this.each(function () {
|
||||
if (!$.data(this, 'plugin_' + pluginName)) {
|
||||
$.data(this, 'plugin_' + pluginName,
|
||||
new Plugin( this, options ));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}(jQuery, window));
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div class="ticket-article"></div>
|
||||
|
||||
<div class="ticket-edit is-public"></div>
|
||||
<div class="ticket-edit"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<form class="article-add <% if @formChanged: %>form-changed<% end %>">
|
||||
<form class="article-add <% if @article.internal: %>is-internal<% else: %>is-public<% end %>">
|
||||
<input type="hidden" name="type" value="<%= @article.type %>">
|
||||
<input type="hidden" name="internal" value="<%= @article.internal %>">
|
||||
<div class="bubble-grid horizontal">
|
||||
<div class="vertical center edit-controls">
|
||||
<%- App.User.fullLocal( @S('id') ).avatar(false, 'right', 'zIndex-5') %>
|
||||
<div class="dark pop-select zIndex-7 edit-control-item">
|
||||
<div class="dark pop-select zIndex-7 edit-control-item" style="display: block;">
|
||||
<div class="pop-selected u-clickable centered">
|
||||
<div class="gray <%- @type %> channel icon"></div>
|
||||
<div class="gray <%- @article.type %> channel icon"></div>
|
||||
</div>
|
||||
<div class="pop-selector hide">
|
||||
<div class="horizontal">
|
||||
|
@ -16,48 +18,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-positionOrigin zIndex-7 edit-control-item">
|
||||
<div class="recipient-picker u-clickable horizontal centered">
|
||||
<div class="recipients icon"></div>
|
||||
<div class="recipient-count">3</div>
|
||||
</div>
|
||||
<div class="recipient-list hide">
|
||||
<div class="list-arrow"></div>
|
||||
<div class="list-head horizontal">
|
||||
<%- @T('Recipients') %>
|
||||
<div class="align-right"><%- @T('type') %></div>
|
||||
</div>
|
||||
<div class="list-entry horizontal centered">
|
||||
<div class="avatar" style="background-image: url(http://berta9.express.ge/31/performer/Paul%20van%20Dyk/.photo/34_paul_van_dyk_01.jpg)"></div>
|
||||
<div class="list-entry-name flex">Hans Peter Baxxter</div>
|
||||
<div class="list-entry-type u-clickable horizontal">
|
||||
<div class="active" data-value="to">To</div>
|
||||
<div data-value="cc" title="<%- @T('carbon copy') %>">Cc</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-entry horizontal centered">
|
||||
<div class="avatar" style="background-image: url(https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/48.jpg)"></div>
|
||||
<div class="list-entry-name flex">Julia Maier</div>
|
||||
<div class="list-entry-type u-clickable horizontal">
|
||||
<div class="active" data-value="to">To</div>
|
||||
<div data-value="cc" title="<%- @T('carbon copy') %>">Cc</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-entry horizontal centered">
|
||||
<div class="avatar" style="background-image: url(https://s3.amazonaws.com/uifaces/faces/twitter/sindresorhus/48.jpg)"></div>
|
||||
<div class="list-entry-name flex">Remo Batlogg</div>
|
||||
<div class="list-entry-type u-clickable horizontal">
|
||||
<div class="active" data-value="to">To</div>
|
||||
<div data-value="cc" title="<%- @T('carbon copy') %>">Cc</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="list-edit">
|
||||
<input type="email" class="list-entry" placeholder="<%- @T('Add recipients..') %>"></input>
|
||||
<input type="submit" tabindex="-1"></input>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visibility-toggle zIndex-7 u-clickable edit-control-item">
|
||||
<div class="visibility-toggle zIndex-7 u-clickable edit-control-item" style="display: block;">
|
||||
<div class="internal-visibility centered" title="<%- @T("unset internal") %>">
|
||||
<div class="internal visibility icon"></div>
|
||||
</div>
|
||||
|
@ -67,20 +28,76 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex article-content zIndex-5 bubble-gap">
|
||||
<!--
|
||||
<label class="recipients"><%- @T('Recipients') %>
|
||||
<div class="avatar" style="background-image: url(https://pbs.twimg.com/profile_images/1216362658/DSC_0084-p_normal.jpg)"></div>
|
||||
<div class="avatar" style="background-image: url(https://pbs.twimg.com/profile_images/1216362658/DSC_0084-p_bigger.jpg)"></div>
|
||||
</label>
|
||||
-->
|
||||
<div class="internal-border">
|
||||
<div class="text-bubble">
|
||||
<div class="bubble-arrow"></div>
|
||||
<textarea rows="1"></textarea>
|
||||
<div class="js-textarea ticketEdit-body" contenteditable="true" data-name="body"><%= @article.body %></div>
|
||||
<!-- .text-bubble grows with textarea (and expanding clone) -->
|
||||
<div class="article-attachment js-attachment u-unclickable">
|
||||
<span class="bubble-placeholder-hint">Antwort eingeben oder</span>
|
||||
<span class="u-highlight u-clickable edit-upload-button js-attachment-text">
|
||||
Dateien wählen..
|
||||
<input multiple="multiple" type="file" name="file" style="position: absolute; right: 0px; top: 0px; font-family: Arial; font-size: 118px; margin: 0px; padding: 0px; cursor: pointer; opacity: 0;">
|
||||
</span>
|
||||
|
||||
<div class="shortcut dropdown">
|
||||
<ul class="dropdown-menu" style="width: 240px; max-height: 200px;">
|
||||
<li><a href="#">shortcut 1</a></li>
|
||||
<li><a href="#">shortcut 2</a></li>
|
||||
<li><a href="#">shortcut 3</a></li>
|
||||
<li><a href="#">shortcut 4</a></li>
|
||||
<li><a href="#">shortcut 5</a></li>
|
||||
<li><a href="#">shortcut 6</a></li>
|
||||
<li><a href="#">shortcut 7</a></li>
|
||||
<li><a href="#">shortcut 8</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="attachments"></div>
|
||||
<!--
|
||||
</div>
|
||||
<div class="attachment horizontal">
|
||||
<div class="attachment-name u-highlight">sega-genesis-box.gif</div>
|
||||
<div class="attachment-size">2.4mb</div>
|
||||
<div class="attachment-delete js-delete align-right u-clickable">
|
||||
<div class="delete icon"></div><%- @T('Delete File') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachment horizontal">
|
||||
<div class="attachment-name u-highlight">license-key.txt</div>
|
||||
<div class="attachment-size">7kb</div>
|
||||
<div class="attachment-delete js-delete align-right u-clickable">
|
||||
<div class="delete icon"></div><%- @T('Delete File') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="article-attachment u-unclickable">
|
||||
<div class="attachmentPlaceholder">
|
||||
<span class="attachmentPlaceholder-hint">Antwort eingeben oder</span>
|
||||
<span class="attachmentPlaceholder-inputHolder u-highlight u-clickable">
|
||||
Dateien wählen..
|
||||
<input multiple="multiple" type="file" name="file" style="position: absolute; right: 0px; top: 0px; font-family: Arial; font-size: 118px; margin: 0px; padding: 0px; cursor: pointer; opacity: 0;">
|
||||
</span>
|
||||
</div>
|
||||
<div class="attachmentUpload hide u-clickable">
|
||||
<div class="horizontal">
|
||||
<div class="u-highlight">
|
||||
<%- @T(' Uploading ') %> (<span class="js-percentage">0</span>%) ...
|
||||
</div>
|
||||
<div class="attachmentUpload-cancel align-right js-cancel u-clickable">
|
||||
<div class="delete icon"></div><%- @T('Cancel Upload') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachmentUpload-progressBar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fit dropArea">
|
||||
<div class="dropArea-inner fit centered">
|
||||
<%- @T('Drop Files here') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -2508,7 +2508,6 @@ footer {
|
|||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
margin: -5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.is-internal .internal-border {
|
||||
|
@ -2844,7 +2843,7 @@ footer {
|
|||
.ticket-edit textarea,
|
||||
.ticketEdit-body {
|
||||
width: 100%;
|
||||
/*height: 38px;*/
|
||||
position: relative;
|
||||
min-height: 20px;
|
||||
vertical-align: bottom;
|
||||
border: none;
|
||||
|
|
Loading…
Reference in a new issue