Browse Source

Initial knowledge base support.

Martin Edenhofer 5 years ago
parent
commit
97d14a93b3

+ 1 - 2
.eslintrc

@@ -57,7 +57,7 @@ rules:
   no-case-declarations: 2
   no-div-regex: 2
   no-else-return: 0
-  no-empty-label: 2
+  no-labels: 2
   no-empty-pattern: 2
   no-eq-null: 2
   no-eval: 2
@@ -69,7 +69,6 @@ rules:
   no-implied-eval: 2
   no-invalid-this: 0
   no-iterator: 2
-  no-labels: 0
   no-lone-blocks: 2
   no-loop-func: 2
   no-magic-number: 0

+ 8 - 0
Gemfile

@@ -31,6 +31,9 @@ gem 'eventmachine'
 # core - password security
 gem 'argon2', '1.1.5'
 
+# core - state machine
+gem 'aasm'
+
 # performance - Memcached
 gem 'dalli'
 
@@ -105,6 +108,9 @@ gem 'telephone_number'
 # feature - SMS
 gem 'twilio-ruby'
 
+# feature - ordering
+gem 'acts_as_list'
+
 # integrations
 gem 'clearbit'
 gem 'net-ldap'
@@ -136,7 +142,9 @@ group :development, :test do
   gem 'pry-stack_explorer'
 
   # test frameworks
+  gem 'rails-controller-testing'
   gem 'rspec-rails'
+  gem 'shoulda-matchers'
   gem 'test-unit'
 
   # test DB

+ 14 - 0
Gemfile.lock

@@ -47,6 +47,8 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
+    aasm (5.0.0)
+      concurrent-ruby (~> 1.0)
     actioncable (5.1.7)
       actionpack (= 5.1.7)
       nio4r (~> 2.0)
@@ -94,6 +96,8 @@ GEM
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
       tzinfo (~> 1.1)
+    acts_as_list (0.9.16)
+      activerecord (>= 3.0)
     addressable (2.5.2)
       public_suffix (>= 2.0.2, < 4.0)
     arel (8.0.0)
@@ -374,6 +378,10 @@ GEM
       bundler (>= 1.3.0)
       railties (= 5.1.7)
       sprockets-rails (>= 2.0.0)
+    rails-controller-testing (1.0.4)
+      actionpack (>= 5.0.1.x)
+      actionview (>= 5.0.1.x)
+      activesupport (>= 5.0.1.x)
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
@@ -450,6 +458,8 @@ GEM
       childprocess (>= 0.5, < 2.0)
       rubyzip (~> 1.2, >= 1.2.2)
     shellany (0.0.1)
+    shoulda-matchers (4.0.1)
+      activesupport (>= 4.2.0)
     simple_oauth (0.3.1)
     simplecov (0.16.1)
       docile (~> 1.1)
@@ -530,9 +540,11 @@ PLATFORMS
   ruby
 
 DEPENDENCIES
+  aasm
   activerecord-import
   activerecord-nulldb-adapter
   activerecord-session_store
+  acts_as_list
   argon2 (= 1.1.5)
   autodiscover!
   autoprefixer-rails
@@ -592,6 +604,7 @@ DEPENDENCIES
   puma
   rack-livereload
   rails (= 5.1.7)
+  rails-controller-testing
   rails-observers
   rb-fsevent
   rchardet (>= 1.8.0)
@@ -603,6 +616,7 @@ DEPENDENCIES
   rubyntlm!
   sassc-rails
   selenium-webdriver
+  shoulda-matchers
   simplecov
   simplecov-rcov
   slack-notifier

+ 25 - 0
LICENSE-3RD-PARTY.txt

@@ -163,3 +163,28 @@ Source: https://gist.github.com/sbrin/6801034
 Copyright: 2015, sbrin - https://github.com/sbrin
 License: MIT license
 -----------------------------------------------------------------------------
+ant-design icon font
+Source: https://github.com/ant-design/ant-design
+Copyright: 2015-present Alipay.com, https://www.alipay.com/
+License: MIT license
+-----------------------------------------------------------------------------
+Font Awesome icon font
+Source: http://fontawesome.io/
+Copyright: Font Awesome by Dave Gandy - http://fontawesome.io
+License: SIL OFL 1.1
+-----------------------------------------------------------------------------
+Simple line icons font
+Source: https://github.com/thesabbir/simple-line-icons
+Copyright: 2016 Sabbir Ahmed & All Contributors
+License: MIT license
+-----------------------------------------------------------------------------
+Ionicons icon font
+Source: https://github.com/ionic-team/ionicons
+Copyright: 2016 Drifty (http://drifty.com/)
+License: MIT license
+-----------------------------------------------------------------------------
+Material icon font
+Source: https://github.com/google/material-design-icons
+Copyright: Google
+License: Apache License, Version 2.0
+-----------------------------------------------------------------------------

+ 95 - 30
LICENSE-ICONS-3RD-PARTY.json

@@ -29,8 +29,13 @@
         "url": "",
         "license": "MIT"
     },
+    "reply.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
     "weibo-button.svg": {
-        "author": "",
+        "author": "Weibo",
         "url": "",
         "license": ""
     },
@@ -159,11 +164,46 @@
         "url": "",
         "license": "MIT"
     },
+    "rearange.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "external.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
     "mood-sad.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "radio.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "radio-checked.svg": {
         "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
+    "knowledge-base.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "eye.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "document.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
     "low-priority.svg": {
         "author": "Felix Niklas",
         "url": "",
@@ -180,9 +220,9 @@
         "license": "MIT"
     },
     "inactive-user.svg": {
-        "author": "R\u00e9my M\u00e9dard",
-        "url": "https:\/\/thenounproject.com\/search\/?q=user&i=10314",
-        "license": "CC 3.0 Attribution"
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
     },
     "inactive-organization.svg": {
         "author": "Felix Niklas",
@@ -194,6 +234,16 @@
         "url": "",
         "license": "MIT"
     },
+    "important.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "reply-all.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
     "paperclip.svg": {
         "author": "Cheesefork",
         "url": "https:\/\/thenounproject.com\/search\/?q=attachment&i=197956",
@@ -204,68 +254,63 @@
         "url": "",
         "license": "MIT"
     },
-    "file-word.svg": {
-        "author": "Felix Niklas",
+    "lock.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-unknown.svg": {
-        "author": "Felix Niklas",
+    "lock-open.svg": {
+        "author": "Zammad",
         "url": "",
         "license": "MIT"
     },
-    "file-powerpoint.svg": {
+    "forward.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-pdf.svg": {
+    "file-word.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-excel.svg": {
+    "file-unknown.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-email.svg": {
+    "file-powerpoint.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "file-code.svg": {
-        "author": "Felix Niklas",
+    "file-pdf.svg": {
+        "author": "Adobe",
         "url": "",
-        "license": "MIT"
+        "license": ""
     },
-    "file-archive.svg": {
+    "file-excel.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "reply.svg": {
+    "file-email.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "reply-all.svg": {
+    "file-code.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
-    "lock.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
-    "forward.svg": {
+    "file-archive.svg": {
         "author": "Felix Niklas",
         "url": "",
         "license": "MIT"
     },
     "office365-button.svg": {
-        "author": "",
+        "author": "Office 365",
         "url": "",
         "license": ""
     },
@@ -589,6 +634,31 @@
         "url": "",
         "license": "MIT"
     },
+    "checkmark.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "chain.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "bold.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
+    "checkbox.svg": {
+        "author": "Zammad",
+        "url": "",
+        "license": "MIT"
+    },
+    "checkbox-indeterminate.svg": {
+        "author": "Felix Niklas",
+        "url": "",
+        "license": "MIT"
+    },
     "checkbox-checked.svg": {
         "author": "Zammad",
         "url": "",
@@ -614,11 +684,6 @@
         "url": "",
         "license": "MIT"
     },
-    "checkmark.svg": {
-        "author": "Zammad",
-        "url": "",
-        "license": "MIT"
-    },
     "chat.svg": {
         "author": "Felix Niklas",
         "url": "",

+ 19 - 14
app/assets/javascripts/app/controllers/_application_controller.coffee

@@ -88,8 +88,10 @@ class App.Controller extends Spine.Controller
     for callId in idsToCancel
       App.Ajax.abort(callId)
 
+  # release Spine's event handling
   release: ->
-    # release custom bindings after it got removed from dom
+    @off()
+    @stopListening()
 
   # add @title methode to set title
   title: (name, translate = false) ->
@@ -452,6 +454,7 @@ class App.ControllerModal extends App.Controller
   buttonCancel: false
   buttonCancelClass: 'btn--text btn--subtle'
   buttonSubmit: true
+  includeForm: true
   headPrefix: ''
   shown: true
   closeOnAnyClick: false
@@ -516,6 +519,7 @@ class App.ControllerModal extends App.Controller
       buttonClass:       @buttonClass
       centerButtons:     @centerButtons
       leftButtons:       @leftButtons
+      includeForm:       @includeForm
     ))
     modal.find('.modal-body').html(content)
     if !@initRenderingDone
@@ -554,18 +558,19 @@ class App.ControllerModal extends App.Controller
     if @small
       @el.addClass('modal--small')
 
-    @el.modal(
-      keyboard:  @keyboard
-      show:      true
-      backdrop:  @backdrop
-      container: @container
-    ).on(
-      'show.bs.modal':   @localOnShow
-      'shown.bs.modal':  @localOnShown
-      'hide.bs.modal':   @localOnClose
-      'hidden.bs.modal': @localOnClosed
-      'dismiss.bs.modal': @localOnCancel
-    )
+    @el
+      .on(
+        'show.bs.modal':   @localOnShow
+        'shown.bs.modal':  @localOnShown
+        'hide.bs.modal':   @localOnClose
+        'hidden.bs.modal': @localOnClosed
+        'dismiss.bs.modal': @localOnCancel
+      ).modal(
+        keyboard:  @keyboard
+        show:      true
+        backdrop:  @backdrop
+        container: @container
+      )
 
     if @closeOnAnyClick
       @el.on('click', =>
@@ -604,7 +609,7 @@ class App.ControllerModal extends App.Controller
 
   onShown: (e) =>
     if @autoFocusOnFirstInput
-      @$('input:not([disabled]):not([type="hidden"]):not(".btn"), textarea').first().focus()
+      @$('input:not([disabled]):not([type="hidden"]):not(".btn"):not([type="radio"]:not(:checked)), textarea').first().focus()
     @initalFormParams = @formParams()
 
   localOnClose: (e) =>

+ 46 - 7
app/assets/javascripts/app/controllers/_application_controller_form.coffee

@@ -1,4 +1,9 @@
 class App.ControllerForm extends App.Controller
+  fullFormSubmitLabel: 'Submit'
+  fullFormSubmitAdditionalClasses: ''
+  fullFormButtonsContainerClass: ''
+  fullFormAdditionalButtons: [] # [{className: 'js-class', text: 'Label'}]
+
   constructor: (params) ->
     super
     for key, value of params
@@ -71,7 +76,9 @@ class App.ControllerForm extends App.Controller
     App.Log.debug 'ControllerForm', 'formGen', @model.configure_attributes
 
     # check if own fieldset should be generated
-    if @noFieldset
+    # forced when the form is a grid form because flex-wrap doesn't work on fieldsets
+    # source: https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers
+    if @noFieldset || @grid
       fieldset = @el
     else
       fieldset = $('<fieldset></fieldset>')
@@ -127,7 +134,24 @@ class App.ControllerForm extends App.Controller
     if @fullForm
       if !@formClass
         @formClass = ''
-      fieldset = $('<form class="' + @formClass + '" autocomplete="off"><button class="btn">' + App.i18n.translateContent('Submit') + '</button></form>').prepend(fieldset)
+
+      fieldset = $("<form class='form #{@formClass}' autocomplete='off'>").prepend(fieldset)
+      container = $("<div class='form-buttons #{@fullFormButtonsContainerClass}'>")
+
+      for buttonConfig in @fullFormAdditionalButtons
+        btn = $("<button class='btn #{buttonConfig.className}'>").text(buttonConfig.text)
+        if buttonConfig.disabled
+          btn.prop('disabled', true)
+        container.append(btn)
+
+      $("<button type=submit class='btn #{@fullFormSubmitAdditionalClasses}\' value=\"#{@fullFormSubmitLabel}\"></button>")
+        .text(App.i18n.translateContent(@fullFormSubmitLabel))
+        .appendTo(container)
+
+      container.appendTo(fieldset)
+
+      #fieldset = $("<form class=\"#{@formClass}\" autocomplete=\"off\"><div class='horizontal #{@fullFormButtonsContainerClass}'><input type=submit class=\"btn #{@fullFormSubmitAdditionalClasses}\" value=\"#{label}\"></div></form>").prepend(fieldset)
+      #fieldset = $("<form class=\"#{@formClass}\" autocomplete=\"off\"><input type=submit class=\"btn #{@fullFormSubmitAdditionalClasses}\" value=\"#{label}\"></form>").prepend(fieldset)
 
     # bind form events
     if @events
@@ -258,11 +282,15 @@ class App.ControllerForm extends App.Controller
     # set params value
     if @params
 
-      # check if we have a references
       parts = attribute.name.split '::'
-      if parts[0] && parts[1]
-        if @params[ parts[0] ] && parts[1] of @params[ parts[0] ]
-          attribute.value = @params[ parts[0] ][ parts[1] ]
+
+      if parts.length > 1
+        deepValue = parts.reduce((memo, elem) ->
+          memo?[elem]
+        , @params)
+
+        if deepValue isnt undefined
+          attribute.value = deepValue
 
       # set params value to default
       if attribute.name of @params
@@ -426,11 +454,16 @@ class App.ControllerForm extends App.Controller
     )
 
   # get all params of the form
-  @params: (form) ->
+  # set clearAccessories to true to remove inline image resizing handles
+  @params: (form, clearAccessories = false) ->
     param = {}
 
     lookupForm = @findForm(form)
 
+    if clearAccessories
+      # remove inline image resizing handles
+      lookupForm.find('.richtext.form-control').trigger('click')
+
     # get contenteditable
     for element in lookupForm.find('[contenteditable]')
       name = $(element).data('name')
@@ -656,6 +689,9 @@ class App.ControllerForm extends App.Controller
       # set forms to read only during communication with backend
       lookupForm.find('button, input, select, textarea').prop('readonly', true)
 
+      # disable radio and checbkox buttons
+      lookupForm.find('input[type=checkbox], input[type=radio]').prop('disabled', true)
+
       # disable additionals submits
       lookupForm.find('button').prop('disabled', true)
     else
@@ -678,6 +714,9 @@ class App.ControllerForm extends App.Controller
       # enable fields again
       lookupForm.find('button, input, select, textarea').prop('readonly', false)
 
+      # enable radio and checbkox buttons
+      lookupForm.find('input[type=checkbox], input[type=radio]').prop('disabled', false)
+
       # enable submits again
       lookupForm.find('button').prop('disabled', false)
     else

+ 2 - 0
app/assets/javascripts/app/controllers/_application_controller_generic.coffee

@@ -403,6 +403,8 @@ class App.ControllerTabs extends App.Controller
       subHeader: @subHeader
       tabs: @tabs
       addTab: @addTab
+      headerSwitchName: @headerSwitchName
+      headerSwitchChecked: @headerSwitchChecked
     )
 
     # insert content

+ 51 - 0
app/assets/javascripts/app/controllers/_application_controller_reorder_modal.coffee

@@ -0,0 +1,51 @@
+class App.ControllerReorderModal extends App.ControllerModal
+  head: 'Drag to reorder'
+  content: ->
+    view = $(App.view('reorder_modal')())
+
+    table = new App.ControllerTable(
+      baseColWidth: null
+      dndCallback: ->
+        true
+      overview: ['title']
+      attribute_list: [
+        { name: 'title', display: 'Name' }
+      ]
+      objects: @items
+    )
+
+    view.find('.js-table-container').html(table.el)
+
+    view
+
+  onShown: ->
+    super
+    @$('.js-submit').focus()
+
+  save: ->
+    ids = @$('tr.item').toArray().map (el) -> parseInt(el.dataset.id)
+
+    @$('.alert').addClass('hidden')
+
+    @formDisable(@el)
+
+    @ajax(
+      id: 'reorder_save'
+      type: 'PATCH'
+      data: JSON.stringify({ordered_ids: ids})
+      url: @url
+      processData: true
+      success: (data, status, xhr) =>
+        App.Collection.loadAssets(data)
+        App.Event.trigger 'knowledge_base::sidebar::rerender'
+        @close()
+      error: (xhr) =>
+        data = JSON.parse(xhr.responseText)
+        @$('.alert--danger').removeClass('hidden').text(data.error)
+        @formEnable(@el)
+    )
+
+  onSubmit: ->
+    super
+    @save()
+

+ 2 - 1
app/assets/javascripts/app/controllers/_application_controller_table.coffee

@@ -116,6 +116,7 @@ class App.ControllerTable extends App.Controller
   shownPage: 0
 
   destroy: false
+  customActions: []
 
   columnsLength: undefined
   headers: undefined
@@ -544,7 +545,7 @@ class App.ControllerTable extends App.Controller
 
     # get header data
     @headers = []
-    @actions = []
+    @actions = [].concat @customActions
     availableWidth = @availableWidth
     for item in @overviewAttributes
       headerFound = false

Some files were not shown because too many files changed in this diff