diff --git a/app/assets/javascripts/sitewide/atwho_users.js b/app/assets/javascripts/sitewide/atwho_users.js deleted file mode 100644 index 29249b3b1..000000000 --- a/app/assets/javascripts/sitewide/atwho_users.js +++ /dev/null @@ -1,72 +0,0 @@ -(function() { - 'use strict'; - - $(document).on( - 'focus', - '[data-atwho-users-edit]', - function() { - if (_.isUndefined($(this).data('atwho'))) { - $(this) - .atwho({ - at: '@', - callbacks: { - remoteFilter: function(query, callback) { - $.getJSON( - '/organizations/1/atwho_users.json', - {query: query}, - function(data) { - callback(data.users); - } - ); - }, - tplEval: function(_tpl, map) { - var res; - try { - res = ''; - res += '
  • '; - res += ''; - res += ' '; - res += ''; - res += map.full_name; - res += ''; - res += ' '; - res += ''; - res += ' '; - res += ''; - res += map.email; - res += ''; - res += '
  • '; - } catch (_error) { - res = ''; - } - return res; - }, - highlighter: function(li, query) { - var li2 = $(li); - li2.addClass('highlighted'); - var prevVal = - li2 - .find('[data-full-name]') - .html(); - var newVal = - prevVal - .replace(query, '' + query + ''); - li2.find('[data-full-name]').html(newVal); - prevVal = - li2 - .find('[data-email]') - .html(); - newVal = - prevVal - .replace(query, '' + query + ''); - li2.find('[data-email]').html(newVal); - return li2.html(); - } - }, - insertTpl: '[${atwho-at}${full_name}~${id}]', - limit: 5, - startWithSpace: true - }); - } - }); -})(); diff --git a/app/assets/javascripts/sitewide/atwho_users.js.erb b/app/assets/javascripts/sitewide/atwho_users.js.erb new file mode 100644 index 000000000..91adb1838 --- /dev/null +++ b/app/assets/javascripts/sitewide/atwho_users.js.erb @@ -0,0 +1,110 @@ +(function() { + 'use strict'; + + $(document).on( + 'focus', + '[data-atwho-users-edit]', + function() { + // Only initialize if URL is present and + // atwho is not initialized yet + if ( + $(document.body).is('[data-atwho-users-url]') && + _.isUndefined($(this).data('atwho')) + ) { + var dataUrl = $(document.body).attr('data-atwho-users-url'); + + $(this) + .atwho({ + at: '@', + callbacks: { + remoteFilter: function(query, callback) { + $.getJSON( + dataUrl, + {query: query}, + function(data) { + callback(data.users); + } + ); + }, + sorter: function(query, items, _searchKey) { + // Sorting is already done on server-side + return items; + }, + tplEval: function(_tpl, map) { + var res; + try { + res = ''; + res += '
  • '; + res += ''; + res += ''; + res += map.full_name; + res += ''; + res += ''; + res += ' '; + res += '·'; + res += ' '; + res += ''; + res += map.email; + res += ''; + res += ''; + res += '
  • '; + } catch (_error) { + res = ''; + } + return res; + }, + highlighter: function(li, query) { + function highlight(el, sel, re) { + var prevVal = el.find(sel).html(); + var newVal = prevVal.replace(re, '$&'); + el.find(sel).html(newVal); + } + + if (!query) { + return li; + } + + var $li = $(li); + var re = new RegExp(query, 'gi'); + highlight($li, '[data-val=full-name]', re); + highlight($li, '[data-val=email]', re); + return $li[0].outerHTML; + }, + beforeInsert: function(value, li) { + var res = ''; + res += '[@' + li.attr('data-full-name'); + res += '~' + li.attr('data-id') + ']'; + return res; + } + }, + headerTpl: + '
    ' + + '
    <%= I18n.t("atwho.users.title") %>
    ' + + '
    ' + + '
    ' + + '<%= I18n.t("atwho.users.navigate_1") %> ' + + '<%= I18n.t("atwho.users.navigate_2") %>' + + '
    ' + + '
    ' + + '<%= I18n.t("atwho.users.confirm_1") %> ' + + '<%= I18n.t("atwho.users.confirm_2") %>' + + '
    ' + + '
    ' + + '<%= I18n.t("atwho.users.dismiss_1") %> ' + + '<%= I18n.t("atwho.users.dismiss_2") %>' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '
    ', + limit: <%= Constants::ATWHO_SEARCH_LIMIT %>, + startsWithSpace: true, + acceptSpaceBar: true + }); + } + } + ); +})(); diff --git a/app/assets/stylesheets/constants.scss b/app/assets/stylesheets/constants.scss index b887e7c14..136383817 100644 --- a/app/assets/stylesheets/constants.scss +++ b/app/assets/stylesheets/constants.scss @@ -25,6 +25,7 @@ $color-emperor: #555; $color-mine-shaft: #333; $color-nero: #262626; $color-black: #000; +$color-cloud: rgba(0, 0, 0, .1); // Miscelaneous colors $color-mystic: #eaeff2; diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 34ffed6e9..9e4e8ae6f 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -1716,10 +1716,10 @@ th.custom-field .modal-tooltiptext { margin-top: 18px; background: $color-white; color: $color-black; - border: 1px solid #DDD; + border: 1px solid $color-emperor; border-radius: 3px; - box-shadow: 0 0 5px rgba(0,0,0,0.1); - min-width: 120px; + box-shadow: 0 0 5px $color-cloud; + min-width: 520px; max-height: 200px; overflow: auto; z-index: 11110 !important; @@ -1756,13 +1756,54 @@ th.custom-field .modal-tooltiptext { li { display: block; padding: 5px 10px; - border-bottom: 1px solid #DDD; + border-bottom: 1px solid $color-emperor; cursor: pointer; } } } // +.atwho-header-user { + padding-top: 7px; + padding-bottom: 7px; + height: 34px; + background-color: $color-gallery; + border-bottom: 1px solid $color-emperor; + clear: both; + + > div { + display: inline; + } + + .title { + float: left; + margin-left: 15px; + } + + .help { + float: right; + + div { + display: inline; + margin-right: 15px; + font-size: smaller; + } + + div strong { + color: $color-black; + } + + .dismiss { + color: $color-emperor; + } + + .dismiss:hover { + color: $color-black; + cursor: pointer; + } + } +} + .atwho-li-user { .avatar { diff --git a/app/controllers/at_who_controller.rb b/app/controllers/at_who_controller.rb index 6db7ba23f..2b4db9afa 100644 --- a/app/controllers/at_who_controller.rb +++ b/app/controllers/at_who_controller.rb @@ -10,8 +10,12 @@ class AtWhoController < ApplicationController .limit(Constants::ATWHO_SEARCH_LIMIT) .as_json - # Add avatars, convert to JSON + # Add avatars, Base62, convert to JSON res.each do |user_obj| + user_obj['full_name'] = + user_obj['full_name'] + .truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) + user_obj['id'] = user_obj['id'].base62_encode user_obj['img_url'] = avatar_path(user_obj['id'], :icon_small) end diff --git a/app/models/organization.rb b/app/models/organization.rb index 7efdf67a4..63c83cbd5 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -62,13 +62,13 @@ class Organization < ActiveRecord::Base .strip .gsub('_', '\\_') .gsub('%', '\\%') - .split(/\s+/) - .map { |t| '%' + t + '%' } else a_query = query end - users.where_attributes_like(attributes, a_query) + users + .where.not(confirmed_at: nil) + .where_attributes_like(attributes, a_query) end # Writes to user log diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cf6b352c0..61fbf0c6d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,7 +14,12 @@ <%= csrf_meta_tags %> - + + data-atwho-users-url="<%= atwho_users_organization_path(current_organization) %>" + <% end %> + > diff --git a/config/locales/en.yml b/config/locales/en.yml index a4cfb68cf..f26b0c85d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1451,6 +1451,16 @@ en: assign_user_to_organization: "%{assigned_user} was added as %{role} to team %{organization} by %{assigned_by_user}." unassign_user_from_organization: "%{unassigned_user} was removed from team %{organization} by %{unassigned_by_user}." + atwho: + users: + title: "People" + navigate_1: "up/down" + navigate_2: "to navigate" + confirm_1: "enter/tab" + confirm_2: "to confirm" + dismiss_1: "esc" + dismiss_2: "to dismiss" + # This section contains general words that can be used in any parts of # application. diff --git a/db/migrate/20170105162500_add_index_to_users_full_name.rb b/db/migrate/20170105162500_add_index_to_users_full_name.rb new file mode 100644 index 000000000..539634924 --- /dev/null +++ b/db/migrate/20170105162500_add_index_to_users_full_name.rb @@ -0,0 +1,5 @@ +class AddIndexToUsersFullName < ActiveRecord::Migration + def change + add_index :users, :full_name + end +end diff --git a/db/schema.rb b/db/schema.rb index 1504e1b50..fb3e1e700 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161125153600) do +ActiveRecord::Schema.define(version: 20170105162500) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -639,20 +639,20 @@ ActiveRecord::Schema.define(version: 20161125153600) do add_index "user_projects", ["user_id"], name: "index_user_projects_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "full_name", null: false - t.string "initials", null: false - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "full_name", null: false + t.string "initials", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "avatar_file_name" t.string "avatar_content_type" t.integer "avatar_file_size" @@ -661,7 +661,7 @@ ActiveRecord::Schema.define(version: 20161125153600) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.string "time_zone", default: "UTC" + t.string "time_zone", default: "UTC" t.string "invitation_token" t.datetime "invitation_created_at" t.datetime "invitation_sent_at" @@ -669,18 +669,21 @@ ActiveRecord::Schema.define(version: 20161125153600) do t.integer "invitation_limit" t.integer "invited_by_id" t.string "invited_by_type" - t.integer "invitations_count", default: 0 - t.integer "tutorial_status", default: 0, null: false - t.boolean "assignments_notification", default: true - t.boolean "recent_notification", default: true - t.boolean "assignments_notification_email", default: false - t.boolean "recent_notification_email", default: false + t.integer "invitations_count", default: 0 + t.integer "tutorial_status", default: 0, null: false + t.boolean "assignments_notification", default: true + t.boolean "recent_notification", default: true + t.boolean "assignments_notification_email", default: false + t.boolean "recent_notification_email", default: false t.integer "current_organization_id" - t.boolean "system_message_notification_email", default: false + t.boolean "system_message_notification_email", default: false + t.string "authentication_token", limit: 30 end + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["full_name"], name: "index_users_on_full_name", using: :btree add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true, using: :btree add_index "users", ["invitations_count"], name: "index_users_on_invitations_count", using: :btree add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree