diff --git a/.gitignore b/.gitignore index b0a831463..deb7e02ec 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ spec/dummy/tmp/ /spec/dummy/public/system/* /spec/dummy/webdav/* -/.idea/ \ No newline at end of file +/.idea/ +.ruby-version diff --git a/app/assets/javascripts/droom.js.coffee b/app/assets/javascripts/droom.js.coffee index 01c52f921..7eebb069b 100644 --- a/app/assets/javascripts/droom.js.coffee +++ b/app/assets/javascripts/droom.js.coffee @@ -8,6 +8,7 @@ #= require sortablejs/Sortable #= require jquery.cookie/jquery.cookie #= require ep-jquery-tokeninput/src/jquery.tokeninput +#= require imagesloaded/imagesloaded.pkgd #= require droom/lib/jquery.datepicker #= require droom/lib/jquery.animate-colors @@ -25,6 +26,7 @@ #= require droom/widgets #= require droom/editors #= require droom/grid +#= require droom/document_editor #= require_self @@ -67,8 +69,10 @@ jQuery ($) -> @find_including_self('[data-action="toggle"]').toggle() @find_including_self('[data-action="alternate"]').alternator() @find_including_self('[data-action="replace"]').replace_with_remote_content() + @find_including_self('[data-action="update_content"]').update_main_content() @find_including_self('[data-action="autofetch"]').replace_with_remote_content ".holder", {force: true} @find_including_self('[data-action="slide"]').sliding_link() + @find_including_self('[data-action="proxy"]').click_proxy() @find_including_self('[data-action="fit"]').self_sizes() @find_including_self('form[data-action="filter"]').filter_form() @find_including_self('form[data-action="quick_search"]').quick_search_form() @@ -116,8 +120,11 @@ jQuery ($) -> @find_including_self('.sortable_files').sortable_files() @find_including_self('[data-draggable]').draggable() @find_including_self('.gridbox:not(.notice)').gridBox() - @find_including_self('.notice').notice() @find_including_self('.tagger').tagger() @find_including_self('form.search.quick').quick_search_form() + @find_including_self('#noticeboard').imagesLoaded => + console.log "imagesLoaded ", @ + $('.notice').notice() + @ diff --git a/app/assets/javascripts/droom/actions.js.coffee b/app/assets/javascripts/droom/actions.js.coffee index 744817ecf..62e064613 100644 --- a/app/assets/javascripts/droom/actions.js.coffee +++ b/app/assets/javascripts/droom/actions.js.coffee @@ -4,18 +4,18 @@ # The basic approach here is PJAX: our remote calls always return chunks of html. There are no client-side templates # or model classes. Instead we have defined a set of actions, by which page elements can interact with the server in # structured ways. A tag will declare that it triggers an action, and may also declare that the action will replace or -# affect other elements. +# affect other elements. # # Many elements are refreshable, and an action that affects them will trigger their refreshment. Editing an event is # a simple remote form operation with several consequences for the page: # # link_to t(:edit_event), edit_event_url(event), :class => 'edit minimal', :data => { -# :action => "popup", -# :replaced => "#event_#{event.id}", +# :action => "popup", +# :replaced => "#event_#{event.id}", # :affected => ".minimonth" # } # -# When the `popup` action is complete - that is, the server returns a response with no form in it - the final response +# When the `popup` action is complete - that is, the server returns a response with no form in it - the final response # will replace the original event container and the small calendar display will be refreshed to show the possibly revised # date of the event. # @@ -50,7 +50,7 @@ jQuery ($) -> if @_url @_container.addClass('working') - params = + params = dataType: "html" beforeSend: @prep success: @replace @@ -87,7 +87,7 @@ jQuery ($) -> # attribute. The delete link next to an event, for example: # # link_to t(:remove), event_url(event), :method => 'delete', :data => { - # :confirm => t(:confirm_delete_event, :name => event.name), + # :confirm => t(:confirm_delete_event, :name => event.name), # :removes => ".holder", # :affected => ".minimonth"} # @@ -99,9 +99,13 @@ jQuery ($) -> affected = $(@).attr('data-affected') $(@).remote on_success: (response) => - $(@).parents(removed).first().fadeOut 'fast', () -> - $(@).remove() - $(affected).trigger "refresh" + if $(@).parents('.menu').first().length == 1 + $(@).parents('.menu').first().fadeOut 'fast', () -> + $(removed).remove() + else + $(@).parents(removed).first().fadeOut 'fast', () -> + $(@).remove() + $(affected).trigger "refresh" # The 'remove_all' action takes out on success anything matching the given selector: @@ -114,7 +118,7 @@ jQuery ($) -> @each -> removed = $(@).attr('data-removed') affected = $(@).attr('data-affected') - + $(@).remote on_success: (response) => $(removed).fadeOut 'fast', () -> @@ -156,6 +160,9 @@ jQuery ($) -> response ?= e replaced = if container is "self" then $el else $el.self_or_ancestor(container).last() replacement = $(response).insertAfter(replaced) + console.log 'response', response + console.log 'replaced', replaced + console.log 'replacement', replacement replaced?.remove() replacement.activate() replacement.hide().slideDown() if options.slide @@ -166,6 +173,24 @@ jQuery ($) -> $.rails.handleRemote($el) + # *update_main_content* is used in services page, action from popup and effected on main content + $.fn.update_main_content = (selector, opts) -> + selector ?= '.holder' + options = $.extend { force: false, confirm: false, slide: false, pjax: true, credentials: false }, opts + @each -> + $el = $(@) + container = selector + $el.remote + credentials: options.credentials + pjax: options.pjax + on_success: (e, response) => + response ?= e + className = $(response).attr "class" + id = $(response).attr "id" + @_selector = $("[data-menu=\"#{id}\"]") + $(@_selector).removeClass().addClass(className) + $(".menu").hide() + # The reinviter is an ordinary remote link. # @@ -258,7 +283,7 @@ jQuery ($) -> $.fn.setter = () -> @each -> new Setter(@) - + class Setter constructor: (element, @_root, @_property) -> @_link = $(element) @@ -273,7 +298,7 @@ jQuery ($) -> data = {} value = if @_link.hasClass('yes') then @_negative else @_positive data[@_root] = {} - data[@_root][@_property] = value + data[@_root][@_property] = value data['_method'] = "PUT" data @@ -328,7 +353,7 @@ jQuery ($) -> else @_showing = $(@_selector).is(":visible") @store() - + apply: (e) => e.preventDefault() if e if @_showing then @show() else @hide() @@ -349,19 +374,19 @@ jQuery ($) -> @_container.text(@_showing_text) @_showing = true @store() - + slideUp: => $(@_selector).slideUp () => @hide() @_container.trigger('toggled') - + hide: => $(@_selector).hide() @_container.removeClass('showing') @_container.text(@_hiding_text) @_showing = false @store() - + store: () => if @_remembered value = if @_showing then "showing" else "hidden" @@ -390,10 +415,10 @@ jQuery ($) -> @preview = @container.find(@options.preview) @switch.click @toggle @set() - + set: () => if @container.hasClass("open") then @show() else @hide() - + toggle: (e) => e.preventDefault() if e if @container.hasClass("open") then @hide() else @show() @@ -401,7 +426,7 @@ jQuery ($) -> hide: () => @container.removeClass('open') @preview.show().css('position', 'relative') - @body.stop().slideUp + @body.stop().slideUp duration: 'slow' easing: 'glide' complete: => @@ -448,7 +473,7 @@ jQuery ($) -> - # The *alternator* action is a more extreme toggle. It allows an element to declare its alternate: + # The *alternator* action is a more extreme toggle. It allows an element to declare its alternate: # when a link within the element is clicked, it will be removed from the DOM and its alternate # inserted. Usually the relation is reciprocal, so that another link in the alternate will bring # the original element back. @@ -468,13 +493,13 @@ jQuery ($) -> @_container.after(@_alternate) @_container.remove() @_alternate.find('a').click @revert - + revert: (e) => e.preventDefault() if e @_alternate.before(@_container) @_alternate.remove() @_container.find('a').click @flip - + $.fn.alternator = -> @each -> new Alternator(@) @@ -484,7 +509,7 @@ jQuery ($) -> $.fn.hover_table = -> @each -> $(@).find('td').hover_cell() - + $.fn.hover_cell = -> @each -> classes = @className.split(' ').join('.') @@ -554,14 +579,14 @@ jQuery ($) -> @_defilter.show() else @_defilter.hide() - + setQuery: (e) => kc = e.which # delete, backspace, alphanumerics, number pad, punctuation if (kc is 8) or (kc is 46) or (47 < kc < 91) or (96 < kc < 112) or (kc > 145) @setDefilter() @filter() - + clearQuery: (e) => e.preventDefault() if e @_container.val("") @@ -576,7 +601,7 @@ jQuery ($) -> # A text input that sizes to fit will adjust its font size to suit the length of its content. - # At the moment this is done just by fitting a curve, but it ought really to be based on a + # At the moment this is done just by fitting a curve, but it ought really to be based on a # calculation of area occupied. $.size_to_fit = (e) -> @@ -587,10 +612,10 @@ jQuery ($) -> 'font-size': "#{size}em" width: 532 height: 290 - , + , queue: false duration: 100 - + $.fn.self_sizes = () -> @each -> $(@).bind "keyup", $.size_to_fit diff --git a/app/assets/javascripts/droom/ajax.js.coffee b/app/assets/javascripts/droom/ajax.js.coffee index cb62f6c1e..5925e9397 100644 --- a/app/assets/javascripts/droom/ajax.js.coffee +++ b/app/assets/javascripts/droom/ajax.js.coffee @@ -3,7 +3,7 @@ # This is a wrapper around the standard jquer ujs ajax machinery. it gives us a more fine-grained set of callbacks # and a central place to do some universal work like setting pjax headers and telling elements to wait. # -# The value of the remote: calls becomes more clear when we need to add more callbacks, eg from a widget like the +# The value of the remote: calls becomes more clear when we need to add more callbacks, eg from a widget like the # filepicker, but perhaps they can disappear now that we've hacked up the ujs a bit. # @@ -13,7 +13,7 @@ jQuery ($) -> $.fn.remote = (opts) -> @each -> new Remote @, opts - + class Remote constructor: (element, opts) -> @_control = $(element) @@ -21,7 +21,7 @@ jQuery ($) -> @_control.attr('data-remote', true) @_control.attr('data-type', 'html') @_fily = false - + # catch the standard jquery_ujs events and route them to our status handlers, @_control.on 'ajax:beforeSend', @pend @_control.on 'ajax:error', @fail @@ -38,7 +38,7 @@ jQuery ($) -> @_control.on 'remote:cancel', @_options.on_cancel @activate() - activate: () => + activate: () => @_control.find('a.cancel').click @cancel @_control.trigger 'remote:prepare' @@ -58,6 +58,11 @@ jQuery ($) -> @_control.trigger "remote:progress", prog fail: (event, xhr, status) => + if xhr.status == 409 + $(event.currentTarget).children('p.error')?.text(xhr.responseText) + $('input[type="submit"]').css("background-color", "#9b9b8e") + if xhr.status == 401 + window.location.reload() event.stopPropagation() @_control.removeClass('waiting').addClass('erratic') @_control.find('input[type=submit]').removeClass('waiting') @@ -80,12 +85,3 @@ jQuery ($) -> e.preventDefault() if e @_control.trigger 'remote:cancel' @_form?.remove() - - - - - - - - - diff --git a/app/assets/javascripts/droom/document_editor.js b/app/assets/javascripts/droom/document_editor.js new file mode 100644 index 000000000..64382c394 --- /dev/null +++ b/app/assets/javascripts/droom/document_editor.js @@ -0,0 +1,62 @@ +function DocumentEditorForm(e) { + e.preventDefault(); + + var form = e.target; + var formData = new FormData(e.target); + var replacedId = $('#document_id').val(); + + $.ajax({ + url: form.action, + type: 'PUT', + success: function(data) { + var element = $(`#document_${replacedId}`); + var filename = data.file_file_name; + var extension = filename.split('.')[1]; + var shortname = filename; + + if (filename.length > 10) { + shortname = filename.substring(0, 25) + "..."; + } + + var styles = ''; + if (extension == 'pdf') { + styles = { + 'background-position': '0 -48px', + 'color': 'red' + } + } else if (extension == 'doc' || extension == 'docx') { + styles = { + 'background-position': '0 -96px', + 'color': '#1683ab' + } + } else if (extension == 'xls' || extension == 'xlsx') { + styles = { + 'background-position': '0 -144px', + 'color': '#369620' + } + } else if (extension == 'mp4' || extension == 'mov' || extension == 'ogg') { + styles = { + 'background-position': '0 -192px', + 'color': '#642195' + } + } + + element.find('a.document').attr('herf', `/folders/${data.folder_id}/documents/${data.id}`); + element.find('a.document').addClass(extension).css(styles) + element.find('span.shortened').attr('title', data.file_file_name).text(shortname); + + $('.popup .closer').click(); + + element.css('background-color', '#acebb1') + setTimeout(function(){ + element.css('background-color', ''); + }, 1000); + }, + data: formData, + cache: false, + contentType: false, + processData: false + }).fail(function (jqXHR, textStatus, error) { + $('p.error').text(jqXHR.responseText) + }); +} diff --git a/app/assets/javascripts/droom/editors.js.coffee b/app/assets/javascripts/droom/editors.js.coffee index 34cf7de22..5f4548006 100644 --- a/app/assets/javascripts/droom/editors.js.coffee +++ b/app/assets/javascripts/droom/editors.js.coffee @@ -25,27 +25,45 @@ jQuery ($) -> allowMultiParagraphSelection: true buttons: [ name: 'bold' - contentDefault: '' + contentDefault: '' , name: 'italic' - contentDefault: '' + contentDefault: '' , name: 'anchor' - contentDefault: '' + contentDefault: '' , name: 'orderedlist' - contentDefault: '' + contentDefault: '' , name: 'unorderedlist' - contentDefault: '' + contentDefault: '' , name: 'h2' - contentDefault: '' + contentDefault: '' , name: 'h3' - contentDefault: '' + contentDefault: '' , name: 'removeFormat' - contentDefault: '' + contentDefault: '' ] - diff --git a/app/assets/javascripts/droom/grid.js.coffee b/app/assets/javascripts/droom/grid.js.coffee index e6feecc9d..0dc918109 100644 --- a/app/assets/javascripts/droom/grid.js.coffee +++ b/app/assets/javascripts/droom/grid.js.coffee @@ -5,12 +5,19 @@ $.fn.autoGrid = -> $.fn.gridBox = -> @each -> $el = $(@) - contents = $el.find('.content') row = 20 space = 20 - rows_touched = Math.ceil(contents.outerHeight() / (row + space)) - $el.css "grid-row-end", "span #{rows_touched}" - $el.addClass('ready') + sizer = -> + contents = $el.find('.content') + console.log "sizer!", contents, contents.outerHeight() + rows_touched = Math.ceil(contents.outerHeight() / (row + space)) + 2 + $el.css "grid-row-end", "span #{rows_touched}" + $el.addClass('ready') + sizer() + $el.find('img').on 'load', => + console.log "resizer!" + sizer() + $.fn.highlight = -> if @offset() diff --git a/app/assets/javascripts/droom/lib/assets.js.coffee.erb b/app/assets/javascripts/droom/lib/assets.js.coffee.erb index ee00b9855..189c946ed 100644 --- a/app/assets/javascripts/droom/lib/assets.js.coffee.erb +++ b/app/assets/javascripts/droom/lib/assets.js.coffee.erb @@ -1,5 +1,7 @@ jQuery ($) -> $.withZxcbvn = (fn) -> - url = "<%= URI.join(Settings.url, asset_path('droom/zxcvbn.js')) %>" - $.getScript(url).done (response) => fn?(response) + based_url = 'https://data.croucher.org.hk/' + asset_path = based_url + "<%=asset_path('droom/zxcvbn.js')%>" + + $.getScript(asset_path).done (response) => fn?(response) diff --git a/app/assets/javascripts/droom/popups.js.coffee b/app/assets/javascripts/droom/popups.js.coffee index 02db7d93a..901f6af30 100644 --- a/app/assets/javascripts/droom/popups.js.coffee +++ b/app/assets/javascripts/droom/popups.js.coffee @@ -26,6 +26,7 @@ jQuery ($) -> @_iteration = 0 @_affected = @_link.data('affected') @_replaced = @_link.data('replaced') + @_removed = @_link.data('removed') @_aftered = @_link.data('appended') @_befored = @_link.data('prepended') @_reporter = @_link.data('reporter') @@ -104,6 +105,8 @@ jQuery ($) -> addition = $(data) $(@_befored).before(addition) addition.activate().signal_confirmation() + if @_removed? + $(@_removed).remove() if @_replaced? replacement = $(data) $(@_replaced).after(replacement) diff --git a/app/assets/javascripts/droom/uploader.js.coffee b/app/assets/javascripts/droom/uploader.js.coffee index 9f2990643..0f44cafdc 100644 --- a/app/assets/javascripts/droom/uploader.js.coffee +++ b/app/assets/javascripts/droom/uploader.js.coffee @@ -25,7 +25,7 @@ jQuery ($) -> @enable() @_catcher.on "sorting", @disable @_catcher.on "not_sorting", @enable - + enable: => @_active = true @_catcher.on "dragenter", @lookAvailable @@ -35,7 +35,7 @@ jQuery ($) -> @_active = false @_catcher.off "dragenter", @lookAvailable @_catcher.off "drop", @catchFiles - + resetFilefield: () => @_filefield?.remove() @_filefield = $('').appendTo(@_form) @@ -43,7 +43,11 @@ jQuery ($) -> triggerFilefield: (e) => e?.preventDefault() - @_filefield.click() # won't work in IE + @_filefield.click() + + removeSection: (e) => + e?.preventDefault() + readFilefield: => @readFiles @_filefield[0].files @@ -183,9 +187,14 @@ class Upload error: () => console.log "error", @_xhr + msg = if @_xhr.response then @_xhr.response else @_.statusText + @_options.on_error?() @_li.addClass('erratic') - @_li.append $('').text(@_xhr.statusText) + @_li.append $('').text(msg) + @_li.append $('').text('x') + $('.delete').on 'click', -> + $('.uploading').css('display','none'); @_li.signal_error() cancel: () => @@ -193,5 +202,3 @@ class Upload @_options.on_cancel?() @_li.fadeOut () -> $(@).remove() - - diff --git a/app/assets/javascripts/droom/widgets.js.coffee b/app/assets/javascripts/droom/widgets.js.coffee index c0ef0b8e2..f389c749d 100644 --- a/app/assets/javascripts/droom/widgets.js.coffee +++ b/app/assets/javascripts/droom/widgets.js.coffee @@ -1,23 +1,35 @@ -# This is a collection of interface elements. They're all self-contained. Most are attached to a +# This is a collection of interface elements. They're all self-contained. Most are attached to a # form element and cause its value to change, but there are also some standalone widgets that live # on the page and follow their own rules. jQuery ($) -> + # The date_picker uses a lightly customised version of jquery.datepicker to present a nice year/month/day interface + # below a date field that can also be filled in manually. The format is always dd-mm-yyyy. + # $.fn.date_picker = ()-> @each -> new DatePicker(@) class DatePicker @month_names: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + constructor: (element) -> @_container = $(element) + view = @_container.attr("data-view") ? 'days' if @_container.is("input") @_field = @_container - event = 'focus' + @_container = @_field.clone().attr('name', 'display-date').insertAfter(@_field) + if iso_date = @_field.val() + [y,m,d] = iso_date.split('-') + @_container.val [d, DatePicker.month_names[m-1], y].join(' ') + @_field.hide() + @_event = 'focus' + @_simple = true else @_field = @_container.find('input') - event = 'click' + @_event = 'click' + @_simple = false @_mon = @_container.find('span.mon') @_dom = @_container.find('span.dom') @_year = @_container.find('span.year') @@ -26,9 +38,9 @@ jQuery ($) -> calendars: 1 date: initial_date current: initial_date - view: 'days' + view: view position: 'bottom' - showOn: event + showOn: @_event onChange: @setDate getDate: () => @@ -36,16 +48,19 @@ jQuery ($) -> new Date(Date.parse(value)) setDate: (date) => + $('.datepicker').hide() if date - $('.datepicker').hide() d = $.zeroPad(date.getDate()) - m = DatePicker.month_names[date.getMonth()] + m = date.getMonth() y = date.getFullYear() - dateString = [y, m, d].join('-') - @_field.val(dateString) - @_dom?.text(d) - @_mon?.text(m) - @_year?.text(y) + realDateString = [y, $.zeroPad(m+1), $.zeroPad(d)].join('-') + @_field.val(realDateString) + if @_simple + @_container.val [d, DatePicker.month_names[m], y].join(' ') + else + @_dom.text(d) + @_mon.text(DatePicker.month_names[m]) + @_year.text(y) class TimePicker @@ -83,15 +98,16 @@ jQuery ($) -> - $.fn.file_picker = () -> @each -> new FilePicker @ - $.fn.click_proxy = (target_selector) -> - this.bind "click", (e) -> - e.preventDefault() - $(target_selector).click() + $.fn.click_proxy = () -> + @each -> + target = $(@).attr('data-affected') + $(@).bind "click", (e) -> + e.preventDefault() if e + $(target).trigger "click" class FilePicker constructor: (element) -> @@ -100,14 +116,14 @@ jQuery ($) -> @_link = @_container.find('a[data-action="pick"]') @_filefield = @_container.find('input[type="file"]') @_file = null - @_filename = "" + @_filename = $('input.name').val() ? "" @_ext = "" @_fields = @_container.siblings('.non-file-data') @_form.bind 'remote:upload', @initProgress @_form.bind 'remote:progress', @progress @_link.bind 'click', @picker @_filefield.bind 'change', @picked - + picker: (e) => e.preventDefault() if e @_filefield.click() @@ -126,7 +142,13 @@ jQuery ($) -> display: () => @_link.addClass(@_ext) if @_ext in @extensions() - @_form.find('input.name').val(@_filename) if $('input.name').val() is @_previous_filename + + if $('input.name').val() is @_previous_filename + @_form.find('input.name').val(@_filename).change() + if @_form.find('#document-info') + @_form.find('input.filename').val(@_filename.split('.')[0]) + @_form.find('input.extension').val(@_filename.split('.')[1]) + initProgress: (e, xhr, settings) => if @_file? @@ -193,7 +215,7 @@ jQuery ($) -> @_field = $(element) @_container = $('
') @_value = @_field.val() - @_value = + @_value = for i in [1..5] do (i) => star = $('') @@ -213,7 +235,7 @@ jQuery ($) -> @unhover() i = parseInt(star.attr('data-score')) @_stars.slice(0, i).addClass('hovered') - + unhover: (e, star) => @_stars.removeClass('hovered') @@ -320,17 +342,17 @@ jQuery ($) -> new PasswordMeter(@) @ - class PasswordMeter constructor: (element) -> @_container = $(element) @_warnings = @_container.find('[data-role="warnings"]') + @_suggestions = @_container.find('[data-role="suggestions"]') @_gauge = @_container.find('[data-role="gauge"]') @_score = @_container.find('[data-role="score"]') @_notes = @_container.find('[data-role="notes"]') @_original_warning = @_warnings.html() @_original_notes = @_notes.html() - @_ready = false + @_zxcvbn_ready = false $.withZxcbvn => @_ready = true @@ -343,7 +365,7 @@ jQuery ($) -> tooShort: () => @clear() @_container.addClass('s0') - @_warnings.text("Password too short. Please continue.") + @_warnings.text("Password too short.") check: (value) => if @_ready @@ -352,13 +374,11 @@ jQuery ($) -> result.score display: (result) => - @_container.removeClass('s0 s1 s2 s3 s4 acceptable').addClass('s' + result.score) - @_warnings.text("") - if result.score < 3 - @_warnings.text("Warning: " + result.feedback.warning) if result.feedback?.warning + if result.score < 2 + @_warnings.text(result.feedback.warning) if result.feedback?.warning + @_suggestions.text(result.feedback?.suggestions) else - @_container.addClass('acceptable') - + @_container.removeClass('s0 s1 s2 s3 s4 acceptable') @@ -402,7 +422,7 @@ jQuery ($) -> $.fn.quick_search_form = (options) -> @each -> - defaults = + defaults = fast: true into: "#found" auto: false @@ -422,7 +442,7 @@ jQuery ($) -> @ $.fn.faceting_search = (options) -> - defaults = + defaults = fast: ".facet" history: true threshold: 4 @@ -438,7 +458,7 @@ jQuery ($) -> threshold: 3 history: false } - + constructor: (element, opts) -> @_form = $(element) @_options = $.extend @constructor.default_options, opts @@ -490,7 +510,7 @@ jQuery ($) -> @submit(e) if (k >= 46 and k <= 90) or (k >= 96 and k <= 111) or k is 8 @changed(e) - + changed: (e) => @submit_soon() unless @_inactive @@ -544,7 +564,7 @@ jQuery ($) -> qs ?= @serialize() url = window.location.pathname + "?" + qs title = document.title + ' search' - state = + state = html: results qs: qs history.pushState state, title, url @@ -674,7 +694,7 @@ jQuery ($) -> localStorage?.setItem("show_folder_#{@_label}", "open") @_container.addClass('open') @_list.stop().slideDown("fast") - + hide: (e) => e.preventDefault() if e localStorage?.setItem("show_folder_#{@_label}", "closed") @@ -687,7 +707,7 @@ jQuery ($) -> $.fn.slug_field = -> @each -> new SlugField(@) - + class SlugField constructor: (element) -> @_field = $(element) @@ -697,10 +717,10 @@ jQuery ($) -> @_base.bind "keyup", @update @_previous_base = @_base.val() @set() - + update: (e) => @set() if $.significantKeypress(e.which) - + set: () => old_base = @_previous_base old_slug = @_field.val() @@ -726,8 +746,8 @@ jQuery ($) -> @each -> new Calendar(@) @ - - # The calendar changer action usually belongs only to the next and previous buttons that sit in the + + # The calendar changer action usually belongs only to the next and previous buttons that sit in the # calendar table, but you can also put links on the page to specific months. # $.fn.calendar_changer = -> @@ -741,7 +761,7 @@ jQuery ($) -> @ # calendar_search links push their text into the suggestion box by way of the calendar.search function. - # + # $.fn.calendar_search = -> @click (e) -> e.preventDefault() if e @@ -766,20 +786,20 @@ jQuery ($) -> # The page turner is a pagination link that retrieves a page of results remotely - # then slides it into view from either the right or left depending on its relation + # then slides it into view from either the right or left depending on its relation # to the current page. $.fn.sliding_link = () -> @each -> new Slider(@) - + class Slider constructor: (element) -> @_link = $(element) @_selector = @_link.attr('data-affected') || @defaultSelector() @_direction = @getDirection() @_page = @getPage() - + # build viewport and sliding frame around the the content-holding @_page @_frame = @_page.parents('.scroller').first() unless @_frame.length @@ -794,21 +814,21 @@ jQuery ($) -> @_width = @_page.width() @_link.remote on_success: @receive - + getPage: => @_link.parents(@_selector).first() - + getDirection: => @_link.attr "data-direction" defaultSelector: () => '.scrap' - + receive: (e, r) => response = $(r) @sweep response response.activate() - + sweep: (r) => @_old_page = @_page @_viewport.css("overflow", "hidden") @@ -818,7 +838,7 @@ jQuery ($) -> else @_frame.prepend(r) @_viewport.scrollLeft(@_width).animate {scrollLeft: 0}, 'slow', 'glide', @cleanup - + cleanup: () => @_viewport.scrollLeft(0) @_old_page.remove() @@ -833,10 +853,10 @@ jQuery ($) -> constructor: (element) -> super @_page_number = parseInt(@_link.parent().siblings('.current').text()) - + defaultSelector: () => '.paginated' - + getDirection: => if @_link.attr "rel" @_direction = if @_link.attr("rel") == "next" then "right" else "left" @@ -945,16 +965,16 @@ jQuery ($) -> @_table.find('a.next, a.previous').calendar_changer() @_table.find('a.day').click @searchForDay @_table.find('a.month').click @searchForMonth - + cache: (year, month, table) => @_cache[year] ?= {} @_cache[year][month] ?= table - + cached: (year, month) => @_cache[year] ?= {} @_cache[year][month] - - # The calendar is intrinsically refreshable: it responds to a 'refresh' event by calling this method + + # The calendar is intrinsically refreshable: it responds to a 'refresh' event by calling this method # to reload the currently displayed month/year. # refresh_in_place: () => @@ -963,13 +983,13 @@ jQuery ($) -> dataType: "html" url: "/events/calendar.js?month=#{encodeURIComponent(@_month)}&year=#{encodeURIComponent(@_year)}" success: @update_quietly - + update_quietly: (response) => @_container.find('a').removeClass('waiting') @_scroller.find('table').remove() @_scroller.append(response) @init() - + show: (year, month) => if cached = @cached(year, month) @update(cached, year, month) @@ -980,12 +1000,12 @@ jQuery ($) -> url: "/events/calendar.js?month=#{encodeURIComponent(month)}&year=#{encodeURIComponent(year)}" success: (response) => @update(response, year, month) - + update: (response, year, month) => @_container.find('a').removeClass('waiting') direction = "left" if ((year * 12) + month) > ((@_year * 12) + @_month) @sweep response, direction - + sweep: (table, direction) => old = @_scroller.find('table') if direction == 'left' @@ -999,11 +1019,11 @@ jQuery ($) -> @_container.scrollLeft(@_width).animate {scrollLeft: 0}, 'fast', () => old.remove() @init() - + monthName: () => months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] months[@_month-1] - + searchForm: => @_form ?= $('#suggestions') @@ -1028,7 +1048,7 @@ jQuery ($) -> # and new objects. # # The suggestion box itself is just `suggestible`. Other inputs are generally more restricted. - # + # $.fn.suggestible = (options) -> options = $.extend( submit_form: true @@ -1151,7 +1171,7 @@ jQuery ($) -> reset: () => @dropdown.reset().hide() - + get: (e, force) => @wait() query = @prompt.val() @@ -1175,7 +1195,7 @@ jQuery ($) -> blank_re = new RegExp("(" + @blanks.join("|") + ")") return blank_re.test(query) false - + suggest: (suggestions) => @unwait() @dropdown.show(suggestions) @@ -1202,7 +1222,7 @@ jQuery ($) -> unwait: () => @button.removeClass "waiting" @prompt.removeClass "waiting" - + @@ -1248,10 +1268,10 @@ jQuery ($) -> @get_preview(id) @get_thumbnail(id) @options.afterSelect?.call(@, value) - + get_preview: (id) => $.get "/videos/#{id}.js", @show_preview, 'html' - + show_preview: (response) => @preview_holder.empty().show().html(response) @@ -1283,7 +1303,7 @@ jQuery ($) -> top: @hook.position().top + @hook.outerHeight() - 2 left: @hook.position().left width: width - + populate: (items) => @reset() if items.length > 0 @@ -1300,7 +1320,7 @@ jQuery ($) -> @select(value, id) $("").addClass(item.type).append(link).appendTo(@drop) @items = @drop.find("a") - + reset: () => @drop.empty() @@ -1317,24 +1337,24 @@ jQuery ($) -> cancel: (e) => @hide() @options.on_cancel?() - + show: (values) => @place() @populate(values) if values unless @visible @drop.stop().fadeIn "fast" @visible = true - + hide: () => if @visible @drop.stop().fadeOut "fast" @visible = false - # the keyup and keydown handlers will first check for local significacne (ie it's a movement or action key + # the keyup and keydown handlers will first check for local significacne (ie it's a movement or action key # that we recognise). If there is none, they call the supplied callback, if any. Event cancellation is up to # the called function: we don't stop propagation here. # - # On keydown we look for keys that have to be intercepted: enter, mostly. Perhaps also tab, but that's not + # On keydown we look for keys that have to be intercepted: enter, mostly. Perhaps also tab, but that's not # very reliable. # keydown: (e) => @@ -1345,7 +1365,7 @@ jQuery ($) -> else @options.on_keydown?(e) return true - + actionKey: (kc) => switch kc # we may also want to catch tab here @@ -1415,4 +1435,3 @@ jQuery ($) -> unHighlight: (i) => if item = @items?.get(i) $(item).removeClass("hover") - \ No newline at end of file diff --git a/app/assets/javascripts/ed/views/_helpers.coffee b/app/assets/javascripts/ed/views/_helpers.coffee index 976bd0adf..e2785178c 100644 --- a/app/assets/javascripts/ed/views/_helpers.coffee +++ b/app/assets/javascripts/ed/views/_helpers.coffee @@ -104,7 +104,7 @@ class Ed.Views.AssetInserter extends Ed.View # Wrap around an embedded asset to present controls for editing or replacing it. # The editor is responsible for adding pickers, stylers, importers and so on. # We also catch some events here and pass them on, so as to present a broad target, -# including click to select and drop to upload. +# including click to select and drop to upload. # class Ed.Views.AssetEditor extends Ed.View defaultSize: "full" @@ -608,28 +608,47 @@ class Ed.Views.Toolbar extends Ed.View allowMultiParagraphSelection: true buttons: [ name: 'bold' - contentDefault: '' + contentDefault: '' , name: 'italic' - contentDefault: '' + contentDefault: '' , name: 'anchor' - contentDefault: '' + contentDefault: '' , name: 'orderedlist' - contentDefault: '' + contentDefault: '' , name: 'unorderedlist' - contentDefault: '' + contentDefault: '' , name: 'h2' - contentDefault: '' + contentDefault: '' , name: 'h3' - contentDefault: '' + contentDefault: '' , name: 'removeFormat' - contentDefault: '' + contentDefault: '' ] @@ -679,7 +698,7 @@ class Ed.Views.ImageWeighter extends Ed.Views.MenuView events: "click @ui.head": "toggleMenu" - bindings: + bindings: "input.weight": "main_image_weighting" @@ -695,7 +714,7 @@ class Ed.Views.ProgressBar extends Ed.View template: _.noop tagName: "span" className: "ed-progress" - + bindings: ":el": observe: "progressing" @@ -757,9 +776,3 @@ class Ed.Views.Helper extends Ed.View @$el.removeClass 'showing' else @$el.addClass 'showing' - - - - - - diff --git a/app/assets/stylesheets/droom.css.sass b/app/assets/stylesheets/droom.css.sass index ca54aa265..b5565fbb5 100644 --- a/app/assets/stylesheets/droom.css.sass +++ b/app/assets/stylesheets/droom.css.sass @@ -731,9 +731,13 @@ ul.downloads padding: 0 list-style: none +a.no-bg-image + background-image: none !important + padding: 6px 0 0 0 !important + a.document display: inline-block - padding: 6px 0 0 28px + padding: 6px 0 0 34px min-height: 20px line-height: 22px color: $mid @@ -1067,6 +1071,14 @@ ul.checklist &.robot display: none +span.delete + margin: 10px; + background: red; + padding: 5px; + font-size: 15px; + border-radius: 100%; + cursor: pointer; + @import droom/columns @import droom/datepicker diff --git a/app/assets/stylesheets/droom/_definitions.css.sass b/app/assets/stylesheets/droom/_definitions.css.sass index 13d7b732c..c3ea012ee 100644 --- a/app/assets/stylesheets/droom/_definitions.css.sass +++ b/app/assets/stylesheets/droom/_definitions.css.sass @@ -12,6 +12,7 @@ $coolgrey9: #747679 $coolgrey10: #616265 $coolgrey11: #4d4e53 $black6: #52555f +$pearl_black: #101820 $rubine: #d1005d $blue: #2c91c9 $darkblue: #226c95 @@ -21,6 +22,7 @@ $reddish: #d34a4a $darkred: #b92a23 $purple: #a27ec3 $red: #ed1c24 +$yellow: #ffa500 $palered: lighten($red, 30) $lightred: #C0504D diff --git a/app/assets/stylesheets/droom/_events.sass b/app/assets/stylesheets/droom/_events.sass index 57158925c..3b487cbd3 100644 --- a/app/assets/stylesheets/droom/_events.sass +++ b/app/assets/stylesheets/droom/_events.sass @@ -44,9 +44,16 @@ div.event, form.event, div.project p.practicalities margin: 0 margin-top: 0 - color: $mid + color: $pearl_black span.admin margin-left: 20px + span.location + display: block + padding-bottom: 5px + div.detail + display: flex + flex-wrap: nowrap + padding-top: 5px p.visibility float: right margin: 10px 16px 0 10px @@ -128,5 +135,3 @@ div.timepicker @media (max-width: 700px) .datemark margin-left: 0 - - \ No newline at end of file diff --git a/app/assets/stylesheets/droom/_forms.css.sass b/app/assets/stylesheets/droom/_forms.css.sass index b92af2a91..81fa2e809 100644 --- a/app/assets/stylesheets/droom/_forms.css.sass +++ b/app/assets/stylesheets/droom/_forms.css.sass @@ -1,5 +1,4 @@ div.popup - input[type="text"], input[type="password"], input[type="email"], input[type="tel"], input[type="url"], input[type="date"], input[type="time"], textarea, [contenteditable]:not([contenteditable=false]), padding: 8px border: 1px solid $pale @@ -30,6 +29,7 @@ input[type="text"], input[type="password"], input[type="email"], input[type="tel &[disabled] color: $pale background-color: #fff + background-image: none border-color: $verypale opacity: 0.75 &.valid, &.invalid @@ -40,6 +40,11 @@ input[type="text"], input[type="password"], input[type="email"], input[type="tel background-position: right 3px &.invalid background-position: right -97px + &.waiting + background: + image: image-url('droom/spinner.gif') + position: right 3px top 3px + repeat: no-repeat &::-webkit-input-placeholder color: $pale font-weight: lighter @@ -76,6 +81,8 @@ input[type="text"], input[type="password"], input[type="email"], input[type="tel select position: relative -webkit-appearance: none + -webkit-border-radius: 0 + -webkit-padding: 8px -webkit-padding-end: 24px font-size: 1.2em border: 1px solid $pale @@ -90,13 +97,11 @@ select image: image-url('droom/symbols/select.svg') size: 10px repeat: no-repeat - &:hover - color: $hover &.small font-size: 0.9em -webkit-padding-end: 16px -input[type="submit"] +input[type="submit"], button.confirm +button($green) display: inline-block transition: width 0.5s ease-out, height 0.5s ease-out, font-size 0.5s ease-out, padding 0.5s ease-out @@ -114,10 +119,28 @@ input[type="submit"] image: image-url('droom/spinner.gif') &.positive +button($green, white) - &.negative + &.negative, &.dangerous +button($red, white) + &.edit + +button($blue, white) + &.publish + +button($purple, white) + &.reset + +button($pale, white) + padding: 9px 14px + &.new + +button($green, white) + &.back + +button($mid, white) + &.shortcut + +button($reddish, white) + &.minor + +button($pale, white) + &.cancel + +button($pale, white) &.large font-size: 1.5em + padding: 20px &.next float: right &.disabled @@ -125,6 +148,9 @@ input[type="submit"] color: $mid box-shadow: none +input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, textarea:-webkit-autofill, textarea:-webkit-autofill:hover, textarea:-webkit-autofill:focus, select:-webkit-autofill, select:-webkit-autofill:hover, select:-webkit-autofill:focus + transition: background-color 5000s ease-in-out 0s + #margin input[type="text"], input[type="email"], input[type="password"] box-sizing: border-box @@ -144,9 +170,15 @@ input[type="submit"] min-height: 4em fieldset.disabled + label + color: $pale input[type="submit"] +button(white, $pale) +span.disabled, p.disabled + label + color: $pale + label &.required font-weight: bold @@ -158,6 +190,8 @@ label display: inline-block vertical-align: top white-space: nowrap + margin-right: 6px + overflow: hidden .error color: $error @@ -206,6 +240,13 @@ form.waiting textarea line-height: 1.5 + &.small + font-size: 85% + &.medium + height: 142px + &.long + height: 360px + // checkbox cosmetics diff --git a/app/assets/stylesheets/droom/_menus.css.sass b/app/assets/stylesheets/droom/_menus.css.sass index b4bda3ddf..3c051d7ab 100644 --- a/app/assets/stylesheets/droom/_menus.css.sass +++ b/app/assets/stylesheets/droom/_menus.css.sass @@ -1,28 +1,33 @@ -div.menu - position: absolute - z-index: 50 - display: none - box-shadow: 0 4px 6px $shadow - background-color: $admin - ul.actions - padding: 0 - margin: 8px 10px 3px 5px - list-style: none - font-size: 14px - li - a - color: white !important - min-height: 16px - &.add - +menu_link("droom/menu/smalladd.png") - &.edit - +menu_link("droom/menu/smalledit.png") - &.delete - +menu_link("droom/menu/smalldelete.png") - &.download - +menu_link("droom/menu/smalldownload.png") - &.send - +menu_link("droom/menu/smalledit.png") +// div.menu +// position: absolute +// z-index: 50 +// display: none +// box-shadow: 0 4px 6px $shadow +// background-color: $admin +// ul.actions +// padding: 0 +// margin: 8px 10px 3px 5px +// list-style: none +// font-size: 14px +// text-align: justify +// li +// a +// color: white !important +// min-height: 16px +// &.add +// +menu_link("droom/menu/smalladd.png") +// &.edit +// +menu_link("droom/menu/smalledit.png") +// &.delete +// +menu_link("droom/menu/smalldelete.png") +// &.download +// +menu_link("droom/menu/smalldownload.png") +// &.send +// +menu_link("droom/menu/smalledit.png") +// &.all +// +menu_link("droom/menu/smalladd.png") +// &.read_only +// +menu_link("droom/menu/smalledit.png") a.menu display: inline-block diff --git a/app/assets/stylesheets/droom/_pages.css.sass b/app/assets/stylesheets/droom/_pages.css.sass index 3944b816f..506b4d442 100644 --- a/app/assets/stylesheets/droom/_pages.css.sass +++ b/app/assets/stylesheets/droom/_pages.css.sass @@ -2,6 +2,15 @@ $page_width: 86% $page_max_width: 65rem h1.pagetitle + div.button-group + display: inline-block + width: 54% + float: right + span.action + font-weight: lighter + font-size: 16px + letter-spacing: 0.2px + padding-left: 20px a.breadhead:before content: "< " @@ -410,4 +419,4 @@ aside#page_controls padding-top: 3em margin-top: 3em border-top: 2px solid $dark - background-color: $coolgrey00 \ No newline at end of file + background-color: $coolgrey00 diff --git a/app/assets/stylesheets/droom/_popups.css.sass b/app/assets/stylesheets/droom/_popups.css.sass index 3fc661ad1..87d7827b6 100644 --- a/app/assets/stylesheets/droom/_popups.css.sass +++ b/app/assets/stylesheets/droom/_popups.css.sass @@ -29,7 +29,6 @@ div.popup +pale margin-top: 0 -div.popup #event, #document, #user, #group, #event_type, #membership, #folder max-width: 580px > p @@ -38,6 +37,7 @@ div.popup div.popup position: absolute overflow-x: hidden + overflow-y: auto top: 10px left: 55px min-height: 100px @@ -45,6 +45,7 @@ div.popup max-width: 80vw width: 580px height: auto + padding: 0 z-index: 6000 background-color: $coolgrey00 box-shadow: 1px 3px 8px $shadow @@ -55,6 +56,7 @@ div.popup opacity: 0.8 &.help width: 360px + .header position: relative box-sizing: border-box @@ -64,16 +66,21 @@ div.popup background-color: $text padding: 8px 10px 4px 20px cursor: move + z-index: 6010 h2 position: relative height: 24px line-height: 27px + box-sizing: border-box margin: 0 margin-right: 40px font-weight: normal font-size: 0.9em color: white white-space: nowrap + span.context + font-weight: normal + color: $verypale a.closer display: block float: right @@ -114,10 +121,6 @@ div.popup margin-top: 0.5em .header - position: relative - top: 0 - left: 0 - width: 100% a.next, a.prev, span.next, span.prev position: relative display: inline-block diff --git a/app/assets/stylesheets/droom/_symbols.sass b/app/assets/stylesheets/droom/_symbols.sass index 83da468da..fcab8898a 100644 --- a/app/assets/stylesheets/droom/_symbols.sass +++ b/app/assets/stylesheets/droom/_symbols.sass @@ -16,6 +16,22 @@ svg.icon margin: 0 &+span display: inline-block + &.pint-sized + width: 20px + height: 18px + &.pint-sized2 + width: 20px + height: 20px + margin-top: -2px + margin-left: 2px + @media not all and (min-resolution:.001dpcm) + @supports (-webkit-appearance:none) and (stroke-color:transparent) + height: 16px + margin-left: 0 + @-moz-document url-prefix() + height: 20px + margin: 2px + &.small width: 20px height: 20px @@ -23,7 +39,12 @@ svg.icon &+span min-height: 20px width: calc(100% - 32px) - + &.medium + width: 24px + height: 30px + &+span + min-height: 30px + width: calc(100% - 22px) svg.prefix, svg.suffix width: 1.2em height: 1.2em @@ -71,4 +92,4 @@ svg.red h1 a.edit svg.icon - vertical-align: bottom \ No newline at end of file + vertical-align: bottom diff --git a/app/assets/stylesheets/droom/_toggles.sass b/app/assets/stylesheets/droom/_toggles.sass index 4cad7934d..d4297cfbd 100644 --- a/app/assets/stylesheets/droom/_toggles.sass +++ b/app/assets/stylesheets/droom/_toggles.sass @@ -70,11 +70,13 @@ table.toggles td.toggle min-height: 32px text-align: center - span.no, span.yes, a.yes, a.no + span.no, span.yes, a.yes, a.no, a.read +icon("droom/minisymbols.png") margin: 5px 0 0 3px &.yes background-position: 0 -384px + &.read + background-position: 0 -455px &.no background-position: 0 -528px opacity: 0.3 @@ -89,7 +91,7 @@ table.toggles opacity: 0.3 a.no:hover background-position: 0 -648px - a.yes:hover + a.yes:hover, a.read:hover background-position: 0 -480px tr.unconfirmed td, th, td.name @@ -97,4 +99,3 @@ table.toggles a color: $pale +hover - diff --git a/app/assets/stylesheets/droom/_uploader.css.sass b/app/assets/stylesheets/droom/_uploader.css.sass index 6f83ee42c..69ff14988 100644 --- a/app/assets/stylesheets/droom/_uploader.css.sass +++ b/app/assets/stylesheets/droom/_uploader.css.sass @@ -46,10 +46,10 @@ ul.uploads height: 26px width: 28px margin-top: -2px - background: - repeat: no-repeat - position: 0 0 - image: image-url('droom/icons.png') + // background: + // repeat: no-repeat + // position: 0 0 + // image: image-url('droom/icons.png') span.filename color: $pale display: inline-block @@ -184,6 +184,3 @@ ul.uploads width: 120px padding: 5px margin: 20px 0 0 0 - - - diff --git a/app/assets/stylesheets/droom/_users.sass b/app/assets/stylesheets/droom/_users.sass index 2c1e01735..7b29daf16 100644 --- a/app/assets/stylesheets/droom/_users.sass +++ b/app/assets/stylesheets/droom/_users.sass @@ -189,6 +189,7 @@ form.user image: image_url('droom/missing/user.png') div.description + z-index: 100 position: relative display: block float: left @@ -222,4 +223,3 @@ form.user #account width: 90% margin: 40px auto - \ No newline at end of file diff --git a/app/channels/droom/changes_channel.rb b/app/channels/droom/changes_channel.rb new file mode 100644 index 000000000..491184740 --- /dev/null +++ b/app/channels/droom/changes_channel.rb @@ -0,0 +1,13 @@ +module Droom + class ChangesChannel < Channel + + def subscribed + stream_from "changes" + end + + def unsubscribed + # No cleanup + end + + end +end \ No newline at end of file diff --git a/app/channels/droom/channel.rb b/app/channels/droom/channel.rb new file mode 100644 index 000000000..b60d74e15 --- /dev/null +++ b/app/channels/droom/channel.rb @@ -0,0 +1,5 @@ +module Droom + class Channel < ActionCable::Channel::Base + + end +end diff --git a/app/channels/droom/connection.rb b/app/channels/droom/connection.rb new file mode 100644 index 000000000..adeb99085 --- /dev/null +++ b/app/channels/droom/connection.rb @@ -0,0 +1,20 @@ +module Droom + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + end + + protected + + def find_verified_user + if user = env['warden'].user + user + else + reject_unauthorized_connection + end + end + + end +end diff --git a/app/controllers/droom/address_types_controller.rb b/app/controllers/droom/address_types_controller.rb index f4eb7662e..c67bd0e82 100644 --- a/app/controllers/droom/address_types_controller.rb +++ b/app/controllers/droom/address_types_controller.rb @@ -17,7 +17,7 @@ def new end def create - @address_type.update_attributes(address_type_params) + @address_type.update(address_type_params) respond_with @address_type end @@ -26,7 +26,7 @@ def edit end def update - @address_type.update_attributes(address_type_params) + @address_type.update(address_type_params) respond_with @address_type end diff --git a/app/controllers/droom/api/api_controller.rb b/app/controllers/droom/api/api_controller.rb index ad54340a4..9d1a8f07e 100644 --- a/app/controllers/droom/api/api_controller.rb +++ b/app/controllers/droom/api/api_controller.rb @@ -27,19 +27,6 @@ def blew_up(exception) render json: { errors: exception.message }.to_json, status: :internal_server_error end - def echo_auth - Rails.logger.warn "??? token_and_options: #{ActionController::HttpAuthentication::Token.token_and_options(request).inspect}" - Rails.logger.warn " token auth header is #{request.headers["HTTP_AUTHORIZATION"]}" - end - - def echo_user_status - Rails.logger.warn ">>> user_signed_in? is #{user_signed_in?.inspect}" - if user_signed_in? - Rails.logger.warn " current_user is #{current_user.inspect}" - Rails.logger.warn " permissions: is #{current_user.permission_codes.inspect}" - end - end - def name_from_controller params[:controller].sub("Controller", "").underscore.split('/').last end diff --git a/app/controllers/droom/api/events_controller.rb b/app/controllers/droom/api/events_controller.rb index 686004def..4d5c705f2 100644 --- a/app/controllers/droom/api/events_controller.rb +++ b/app/controllers/droom/api/events_controller.rb @@ -14,7 +14,7 @@ def show end def update - @event.update_attributes(event_params) + @event.update(event_params) render json: @event end diff --git a/app/controllers/droom/api/images_controller.rb b/app/controllers/droom/api/images_controller.rb index 6e817ea8b..e1f5a862c 100644 --- a/app/controllers/droom/api/images_controller.rb +++ b/app/controllers/droom/api/images_controller.rb @@ -15,7 +15,7 @@ def show end def update - if @image.update_attributes(image_params) + if @image.update(image_params) return_image else return_errors diff --git a/app/controllers/droom/api/organisations_controller.rb b/app/controllers/droom/api/organisations_controller.rb index c2ed3f901..a49ce9210 100644 --- a/app/controllers/droom/api/organisations_controller.rb +++ b/app/controllers/droom/api/organisations_controller.rb @@ -12,7 +12,7 @@ def show end def update - if @organisation.update_attributes(organisation_params) + if @organisation.update(organisation_params) return_organisation else return_errors diff --git a/app/controllers/droom/api/sessions_controller.rb b/app/controllers/droom/api/sessions_controller.rb index 1f1770007..f2223f8e3 100644 --- a/app/controllers/droom/api/sessions_controller.rb +++ b/app/controllers/droom/api/sessions_controller.rb @@ -3,6 +3,7 @@ class SessionsController < Devise::SessionsController include Droom::Concerns::LocalApi respond_to :json + # skip_before_action :authenticate_user!, raise: false skip_before_action :verify_authenticity_token, raise: false before_action :set_access_control_headers @@ -22,9 +23,9 @@ def create end # This is called on every request by a remote service. - # Lots of care has to be taken here, to respond quickly but lapse correctly, + # Care has to be taken here, to respond quickly but lapse correctly, # and never to set up a cascade of mutual enquiry. - # also must make sure that we check, not sign in. Signing in will create a new session id... + # also must make sure that we check, *not sign in*, as signing in would create a new session id. # def authenticate token = params[:tok] @@ -38,6 +39,7 @@ def authenticate render json: { errors: "Session timed out" }, status: :unauthorized else bypass_sign_in @user + @user.set_last_request_at! render json: @user, serializer: Droom::UserAuthSerializer end else @@ -62,10 +64,12 @@ def deauthenticate end end - def api_controller? true end + def devise_controller? + true + end end end \ No newline at end of file diff --git a/app/controllers/droom/api/users_controller.rb b/app/controllers/droom/api/users_controller.rb index 181f10cfb..cf2da270a 100644 --- a/app/controllers/droom/api/users_controller.rb +++ b/app/controllers/droom/api/users_controller.rb @@ -3,6 +3,7 @@ class UsersController < Droom::Api::ApiController before_action :get_users, only: [:index] before_action :find_or_create_user, only: [:create] + skip_before_action :assert_local_request!, only: [:update_timezone] load_resource find_by: :uid, class: "Droom::User" def index @@ -19,8 +20,17 @@ def whoami render json: current_user end + # This is a background call to request the user information necessary for session creation. + # It usually happens on acceptable of an invitation, or some other situation where + # a remote object is triggering user confirmation or automatic login. + # + def authenticable + @user.ensure_unique_session_id! + render json: @user, serializer: Droom::UserAuthSerializer + end + def update - @user.update_attributes(user_params) + @user.update(user_params) render json: @user end @@ -42,6 +52,14 @@ def reindex head :ok end + def update_timezone + if params[:timezone] + timezone = Timezones.find_by_key(params[:timezone]) + current_user.update(timezone: timezone) + return current_user.timezone + end + end + protected def find_or_create_user @@ -51,6 +69,9 @@ def find_or_create_user end if params[:user][:email].present? @user ||= Droom::User.where(email: params[:user][:email]).first + unless @user + @user ||= Droom::Email.where(email: params[:user][:email]).first.try(:user) + end end end params = user_params @@ -71,7 +92,7 @@ def get_users end def user_params - params.require(:user).permit(:uid, :person_uid, :title, :family_name, :given_name, :chinese_name, :honours, :affiliation, :email, :phone, :mobile, :description, :address, :post_code, :correspondence_address, :country_code, :organisation_id, :female, :defer_confirmation, :send_confirmation, :password, :password_confirmation, :confirmed, :confirmed_at, :image_data, :image_name) + params.require(:user).permit(:uid, :person_uid, :title, :family_name, :given_name, :chinese_name, :honours, :affiliation, :email, :phone, :mobile, :description, :address, :post_code, :correspondence_address, :country_code, :organisation_id, :female, :defer_confirmation, :send_confirmation, :password, :password_confirmation, :confirmed, :confirmed_at, :image_data, :image_name, :last_request_at) end end diff --git a/app/controllers/droom/api/venues_controller.rb b/app/controllers/droom/api/venues_controller.rb index a41b4f348..4fc3dcbd9 100644 --- a/app/controllers/droom/api/venues_controller.rb +++ b/app/controllers/droom/api/venues_controller.rb @@ -14,7 +14,7 @@ def show end def update - @venue.update_attributes(venue_params) + @venue.update(venue_params) render json: @venue end diff --git a/app/controllers/droom/api/videos_controller.rb b/app/controllers/droom/api/videos_controller.rb index 73e1ad53a..f482d8190 100644 --- a/app/controllers/droom/api/videos_controller.rb +++ b/app/controllers/droom/api/videos_controller.rb @@ -14,7 +14,7 @@ def show end def update - if @video.update_attributes(video_params) + if @video.update(video_params) return_video else return_errors diff --git a/app/controllers/droom/concerns/controller_helpers.rb b/app/controllers/droom/concerns/controller_helpers.rb index b3a0f36e7..0b92e121e 100644 --- a/app/controllers/droom/concerns/controller_helpers.rb +++ b/app/controllers/droom/concerns/controller_helpers.rb @@ -20,18 +20,19 @@ module Droom::Concerns::ControllerHelpers before_action :check_user_is_confirmed, except: [:cors_check, :setup], unless: :devise_controller? before_action :check_user_setup, except: [:cors_check, :setup], unless: :devise_controller? before_action :check_user_has_organisation, except: [:cors_check, :setup_organisation], unless: :devise_controller? - before_action :check_data_room_permission, except: [:cors_check, :set_password] + before_action :check_data_room_permission, except: [:cors_check, :set_password, :setup], unless: :devise_controller? before_action :note_current_user, except: [:cors_check] before_action :set_section, except: [:cors_check] before_action :set_access_control_headers skip_before_action :verify_authenticity_token, only: [:cors_check], raise: false - after_action :update_auth_cookie, unless: :api_controller? + after_action :update_auth_cookie, except: [:cors_check], unless: :api_controller? layout :no_layout_if_pjax end + # Usually overridden in a base ApiController # def api_controller? @@ -47,7 +48,7 @@ def html_request? def cors_check head :ok end - + def set_access_control_headers if request.env["HTTP_ORIGIN"].present? && Droom.cors_domains.empty? || Droom.cors_domains.include?(request.env["HTTP_ORIGIN"]) headers['Access-Control-Allow-Origin'] = request.env["HTTP_ORIGIN"] @@ -121,8 +122,10 @@ def check_data_room_permission # def update_auth_cookie if user_signed_in? && current_user.unique_session_id? + Rails.logger.warn "✅ setting auth_cookie after #{request.fullpath}" Droom::AuthCookie.new(warden.cookies).set(current_user) else + Rails.logger.warn "⚠️ unsetting auth_cookie after #{request.fullpath}" Droom::AuthCookie.new(warden.cookies).unset end end @@ -146,14 +149,16 @@ def set_exception_context ## Error responses # def not_authorized(exception) + Rails.logger.warn "⚠️ not_authorized" respond_to do |format| - format.html { render :file => "#{Rails.root}/public/401.html", :status => :forbidden, :layout => false } + format.html { render :file => "#{Rails.root}/public/403.html", :status => :forbidden, :layout => false } format.js { head :unauthorized } format.json { head :unauthorized } end end def not_allowed(exception) + Rails.logger.warn "⚠️ not_allowed" respond_to do |format| format.html { render :file => "#{Rails.root}/public/403.html", :status => :forbidden, :layout => false } format.js { head :forbidden } @@ -162,6 +167,7 @@ def not_allowed(exception) end def not_found(exception) + Rails.logger.warn "⚠️ not_found" @error = exception.message Honeybadger.notify(exception) respond_to do |format| @@ -183,6 +189,7 @@ def check_user_is_confirmed end def prompt_for_confirmation + Rails.logger.warn "⚠️ prompt_for_confirmation" render template: "/devise/registrations/confirm", locals: {resource: current_user} end @@ -194,6 +201,7 @@ def check_user_setup end def prompt_for_setup + Rails.logger.warn "⚠️ prompt_for_setup" render template: "/droom/users/setup", locals: {user: current_user} end @@ -210,11 +218,13 @@ def check_user_has_organisation end def prompt_for_organisation + Rails.logger.warn "⚠️ prompt_for_organisation" @organisations = Droom::Organisation.matching_email(current_user.email) render template: "/droom/users/setup_organisation" end def await_organisation_approval + Rails.logger.warn "⚠️ await_organisation_approval" @organisations = Droom::Organisation.matching_email(current_user.email) render template: "/droom/users/await_organisation_approval" end @@ -285,4 +295,4 @@ def default_layout Droom.config.layout end -end \ No newline at end of file +end diff --git a/app/controllers/droom/concerns/searchable.rb b/app/controllers/droom/concerns/searchable.rb index 1729e065a..a6c3ebcdb 100644 --- a/app/controllers/droom/concerns/searchable.rb +++ b/app/controllers/droom/concerns/searchable.rb @@ -1,3 +1,5 @@ +# This is used in the organisations controller and made available to non-droom classes. + module Droom::Concerns::Searchable extend ActiveSupport::Concern diff --git a/app/controllers/droom/confirmations_controller.rb b/app/controllers/droom/confirmations_controller.rb index ed42fb49b..77327458a 100644 --- a/app/controllers/droom/confirmations_controller.rb +++ b/app/controllers/droom/confirmations_controller.rb @@ -26,7 +26,7 @@ def show # def update if self.resource = resource_class.where(id: params[:id], confirmation_token: params[resource_name][:confirmation_token]).first - result = resource.update_attributes(permitted_params) + result = resource.update(permitted_params) if result && resource.password_match? set_flash_message :notice, :confirmed resource.confirm! diff --git a/app/controllers/droom/dashboard_controller.rb b/app/controllers/droom/dashboard_controller.rb index 31435f2b1..ecd65b20a 100644 --- a/app/controllers/droom/dashboard_controller.rb +++ b/app/controllers/droom/dashboard_controller.rb @@ -9,4 +9,4 @@ def index end end -end \ No newline at end of file +end diff --git a/app/controllers/droom/documents_controller.rb b/app/controllers/droom/documents_controller.rb index 29fc3f6ce..89c6f5140 100644 --- a/app/controllers/droom/documents_controller.rb +++ b/app/controllers/droom/documents_controller.rb @@ -5,6 +5,8 @@ class DocumentsController < Droom::DroomController before_action :get_folder, except: [:index, :suggest, :reposition] before_action :select_documents, only: [:index, :suggest] load_and_authorize_resource :document, :class => Droom::Document, :through => :folder, :shallow => true, except: [:index, :suggest] + before_action :find_by_name, only: [:create] + def index respond_with @documents do |format| @@ -29,35 +31,55 @@ def new end def create - @document.save! - if %w{listing simple}.include?(params[:view]) - render :partial => params[:view] + if @data.exists? + render json: 'File with this name already exists!', status: 409 else - render :partial => 'listing' + @document.save! + if %w{listing simple}.include?(params[:view]) + render :partial => params[:view] + else + render :partial => 'listing' + end end end - + def edit render end - + def update - @document.update_attributes(document_params) - render :partial => 'listing', :object => @document + attributes = document_params + attributes[:name] = params[:filename] + '.' + params[:extension] + @data = Document.where(name: attributes[:name], folder_id: params[:folder_id]) + + @document.assign_attributes(attributes) + @document.file.instance_write(:file_name, @document.name) + + if @document.description_changed? || @data.blank? + @document.save + render json: @document.to_json + else + render json: 'File with this name already exists!', status: 409 + end end def reposition - @document.update_attributes(reposition_params) + @document.update(reposition_params) head :ok end def destroy @document.destroy + # @document.enqueue_for_croucher_deindexing # calling search_client method head :ok end protected + def find_by_name + @data = Document.where(name: document_params[:name], folder_id: params[:folder_id]) + end + def select_documents if params[:q].present? @q = params[:q] @@ -92,7 +114,7 @@ def select_documents # ui can check for @searching if the default list is not a useful browser. @documents = Document.search terms, fields: fields, where: criteria, order: order, per_page: @show, page: @page, highlight: highlight, aggs: aggregations end - + def document_params if params[:document] params.require(:document).permit(:name, :file, :description, :folder_id, :position) @@ -112,6 +134,6 @@ def reposition_params def get_folder @folder = Droom::Folder.find(params[:folder_id]) end - + end -end \ No newline at end of file +end diff --git a/app/controllers/droom/droom_controller.rb b/app/controllers/droom/droom_controller.rb index 0c365a1bb..ed6bae3a5 100644 --- a/app/controllers/droom/droom_controller.rb +++ b/app/controllers/droom/droom_controller.rb @@ -3,5 +3,18 @@ class DroomController < ActionController::Base include Droom::Concerns::ControllerHelpers helper Droom::DroomHelper helper ApplicationHelper + + before_action :set_timezone + + protected + + def set_timezone + if user_signed_in? && current_user.timezone.present? + cookies[:timezone] = current_user.timezone + else + cookies.delete :timezone + end + end + end end diff --git a/app/controllers/droom/enquiries_controller.rb b/app/controllers/droom/enquiries_controller.rb index 05de6d8ae..393344e55 100644 --- a/app/controllers/droom/enquiries_controller.rb +++ b/app/controllers/droom/enquiries_controller.rb @@ -32,13 +32,13 @@ def edit end def update - @enquiry.update_attributes(enquiry_params) + @enquiry.update(enquiry_params) respond_with @enquiry end def create @enquiry.request = request - if @enquiry.update_attributes(enquiry_params) + if @enquiry.update(enquiry_params) render else render template: 'edit' diff --git a/app/controllers/droom/event_types_controller.rb b/app/controllers/droom/event_types_controller.rb index ca44b43cf..c491be320 100644 --- a/app/controllers/droom/event_types_controller.rb +++ b/app/controllers/droom/event_types_controller.rb @@ -24,12 +24,12 @@ def edit end def update - @event_type.update_attributes(event_type_params) + @event_type.update(event_type_params) render :partial => 'event_type' end def create - if @event_type.update_attributes(event_type_params) + if @event_type.update(event_type_params) render :partial => "created" else respond_with @event_type diff --git a/app/controllers/droom/events_controller.rb b/app/controllers/droom/events_controller.rb index fb63c4d97..d96b49171 100644 --- a/app/controllers/droom/events_controller.rb +++ b/app/controllers/droom/events_controller.rb @@ -34,7 +34,7 @@ class Array def to_ics to_icalendar.to_ical end - + def to_icalendar cal = Icalendar::Calendar.new self.flatten.each do |item| @@ -51,6 +51,7 @@ def past end def show + @event_invitation = Droom::Invitation.where(user_id: current_user.id, event_id: @event.id).first if @event respond_with @event do |format| format.js { render :partial => 'droom/events/event' } format.zip { send_file @event.documents_zipped.path, :type => 'application/zip', :disposition => 'attachment', :filename => "#{@event.slug}.zip" } @@ -71,7 +72,7 @@ def create end def update - if @event.update_attributes(event_params) + if @event.update(event_params) render :partial => "event" else respond_with @event @@ -84,7 +85,7 @@ def destroy end protected - + def get_my_events @events = Droom::Event.accessible_by(current_ability) if Droom.config.separate_calendars? @@ -105,12 +106,12 @@ def get_events @events = paginated(@events.future_and_current.order('start ASC')) end end - + def build_event @event = Droom::Event.new(event_params) @event.created_by = current_user end - + # NB. the stored timezone parameter is just an interface convenience: we use it to display a consistent form. # The event start and finish dates are stored as datetimes with zones. # @@ -135,7 +136,7 @@ def composite_dates end end end - + def event_params if params[:event] params.require(:event).permit(:name, :description, :event_set_id, :event_type_id, :calendar_id, :all_day, :master_id, :url, :start, :finish, :timezone, :venue_id, :venue_name) @@ -158,4 +159,4 @@ def authenticate_from_param end end -end \ No newline at end of file +end diff --git a/app/controllers/droom/folders_controller.rb b/app/controllers/droom/folders_controller.rb index 1de47ff38..93a021474 100644 --- a/app/controllers/droom/folders_controller.rb +++ b/app/controllers/droom/folders_controller.rb @@ -1,11 +1,12 @@ module Droom class FoldersController < Droom::DroomController respond_to :html, :json, :js - + before_action :get_root_folders, :only => [:index] before_action :get_parent_folder, :only => [:new, :create] + before_action :find_by_name, only: [:create, :update] load_and_authorize_resource - + def index @folders = @folders.populated unless current_user.admin? respond_with @folders do |format| @@ -14,56 +15,96 @@ def index } end end - + def show respond_with @folder do |format| - format.js { + format.js { render :partial => 'droom/folders/folder' } end end - + def new respond_with @folder end def create - @folder.update_attributes(folder_params) - respond_with @folder do |format| - format.js { render :partial => "droom/folders/folder" } + if @data.exists? + render json: 'Folder with this name already exists!', status: 409 + else + @folder.update(folder_params) + respond_with @folder do |format| + format.js { render :partial => "droom/folders/folder" } + end end end - + def edit respond_with @folder end - + def update - @folder.update_attributes(folder_params) - respond_with @folder do |format| - format.js { render :partial => "droom/folders/folder" } + @folder.assign_attributes(folder_params) + if @folder.name_changed? && @data.exists? + render json: 'Folder with this name already exists!', status: 409 + else + @folder.save + respond_with @folder do |format| + format.js { render :partial => "droom/folders/folder" } + end end end - + def destroy @folder.destroy head :ok end - def with_parent - + def move_folder + respond_with @folder end - + + def moved + if params.include?('new_parent_id') && params.include?('id') + folder = Droom::Folder.find(params[:id]) + folder.parent_id = params[:new_parent_id] + folder.save + end + head :ok + end + + def child_folders + if params.include?('target_parent_id') + target_parent_id = params[:target_parent_id] + mapped_children = '' + if target_parent_id != '' && folder = Droom::Folder.find(target_parent_id) + child_folders = folder.children + if child_folders.any? + mapped_children = {} + child_folders.map{|child| + mapped_children[child.id] = child.name + } + end + end + end + render json: mapped_children + end + protected - + + def find_by_name + @data = Folder.where(name: folder_params[:name]) + @data = @data.where(parent_id: folder_params[:parent_id]) if folder_params[:parent_id].present? + end + def folder_params params.require(:folder).permit(:name, :slug, :parent_id) end - + def get_root_folders @folders = Droom::Folder.roots end - + def get_parent_folder if @parent = Droom::Folder.find_by(id: params[:folder_id]) @folder = @parent.children.build @@ -71,7 +112,7 @@ def get_parent_folder @folder = Droom::Folder.new end end - + def get_folder_tree @child_map = Droom::Folder.non_roots.each_with_object({}) do |f, children| children[f.parent_id] ||= [] @@ -83,4 +124,4 @@ def get_folder_tree end end end -end \ No newline at end of file +end diff --git a/app/controllers/droom/group_invitations_controller.rb b/app/controllers/droom/group_invitations_controller.rb index 5882a071b..243ac6d62 100644 --- a/app/controllers/droom/group_invitations_controller.rb +++ b/app/controllers/droom/group_invitations_controller.rb @@ -1,7 +1,7 @@ module Droom class GroupInvitationsController < Droom::DroomController respond_to :js, :html - + load_and_authorize_resource :event, :class => Droom::Event load_and_authorize_resource :group_invitation, :through => :event, :class => Droom::GroupInvitation @@ -9,18 +9,25 @@ def destroy @group_invitation.destroy head :ok end - + def index @event = Droom::Event.find(params[:event_id]) render :partial => 'attending_groups' end - + def new respond_with @group_invitation end - + def create - if @group_invitation.update_attributes(group_invitation_params) + Droom::GroupInvitationJob.perform_now(group_invitation_params[:group_id], params[:event_id]) + + if GroupInvitation.find_by(group_id: group_invitation_params[:group_id], event_id: params[:event_id]) + Rails.logger.warn "⚠️ Email sent to already invited group!." + return true + end + + if @group_invitation.update(group_invitation_params) render :partial => "created" else respond_with @group_invitation @@ -28,7 +35,7 @@ def create end protected - + def group_invitation_params params.require(:group_invitation).permit(:event_id, :group_id) end diff --git a/app/controllers/droom/group_permissions_controller.rb b/app/controllers/droom/group_permissions_controller.rb index 196b1e3eb..82d4cf92f 100644 --- a/app/controllers/droom/group_permissions_controller.rb +++ b/app/controllers/droom/group_permissions_controller.rb @@ -1,25 +1,57 @@ module Droom class GroupPermissionsController < Droom::DroomController respond_to :js, :html - + load_and_authorize_resource :group, :class => Droom::Group load_and_authorize_resource :group_permission, :through => :group, :class => Droom::GroupPermission - + def create @group_permission.save render :partial => 'droom/group_permissions/toggle' end + def upsert + @group_permission = Droom::GroupPermission.find_or_initialize_by(group_permission_params) + @group_permission.delete_permissions(params[:read_only]) + @group_permission.save + + html_tag = "" + render html: html_tag.html_safe + end + + def delete_by_ids + get_gp_permissions(group_permission_params) + @group_permission.try(:destroy_all) + @read_group_permission.try(:destroy_all) + + html_tag = "" + render html: html_tag.html_safe + end + def destroy @group_permission.destroy render :partial => 'droom/group_permissions/toggle' end protected - + def group_permission_params params.require(:group_permission).permit(:permission_id, :group_id) end + def get_gp_permissions(params) + gp_klass = Droom::GroupPermission + perm_klass = Droom::Permission + + permission = perm_klass.find(params[:permission_id]) + read_permission = permission.get_read_permission + + @group_permission = gp_klass.where(params) + unless @group_permission.present? + params[:permission_id] = read_permission.id + @read_group_permission = gp_klass.where(params) + end + end + end end diff --git a/app/controllers/droom/groups_controller.rb b/app/controllers/droom/groups_controller.rb index d71158ecf..7e7ac603b 100644 --- a/app/controllers/droom/groups_controller.rb +++ b/app/controllers/droom/groups_controller.rb @@ -31,12 +31,12 @@ def edit end def update - @group.update_attributes(group_params) + @group.update(group_params) render :partial => 'group' end def create - if @group.update_attributes(group_params) + if @group.update(group_params) render :partial => "created" else respond_with @group diff --git a/app/controllers/droom/helps_controller.rb b/app/controllers/droom/helps_controller.rb index 25e1f2902..42c875bf1 100644 --- a/app/controllers/droom/helps_controller.rb +++ b/app/controllers/droom/helps_controller.rb @@ -11,7 +11,7 @@ def new end def create - if @help.update_attributes(help_params) + if @help.update(help_params) redirect_to droom.help_url(@help.slug) else render action: :new @@ -19,7 +19,7 @@ def create end def update - if @help.update_attributes(help_params) + if @help.update(help_params) redirect_to droom.help_url(@help.slug) else render action: :edit diff --git a/app/controllers/droom/invitations_controller.rb b/app/controllers/droom/invitations_controller.rb index da8e3d719..75ef49625 100644 --- a/app/controllers/droom/invitations_controller.rb +++ b/app/controllers/droom/invitations_controller.rb @@ -20,7 +20,7 @@ def new end def create - if @invitation.update_attributes(invitation_params) + if @invitation.update(invitation_params) render :partial => "created" else respond_with @invitation @@ -29,12 +29,24 @@ def create def accept @invitation.update_attribute(:response, 2) - render :partial => "droom/invitations/invitation" + if params[:event_invitation] + @event = @invitation.event + @event_invitation = Droom::Invitation.where(user_id: current_user.id, event_id: @event.id).first if @event + render :partial => "droom/events/event_invitation" + else + render :partial => "droom/invitations/invitation" + end end def refuse @invitation.update_attribute(:response, 0) - render :partial => "droom/invitations/invitation" + if params[:event_invitation] + @event = @invitation.event + @event_invitation = Droom::Invitation.where(user_id: current_user.id, event_id: @event.id).first if @event + render :partial => "droom/events/event_invitation" + else + render :partial => "droom/invitations/invitation" + end end def toggle diff --git a/app/controllers/droom/organisation_types_controller.rb b/app/controllers/droom/organisation_types_controller.rb index a378bb90f..a2c0ff88b 100644 --- a/app/controllers/droom/organisation_types_controller.rb +++ b/app/controllers/droom/organisation_types_controller.rb @@ -24,12 +24,12 @@ def edit end def update - @organisation_type.update_attributes(organisation_type_params) + @organisation_type.update(organisation_type_params) render :partial => 'organisation_type' end def create - if @organisation_type.update_attributes(organisation_type_params) + if @organisation_type.update(organisation_type_params) render :partial => "created" else respond_with @organisation_type diff --git a/app/controllers/droom/organisations_controller.rb b/app/controllers/droom/organisations_controller.rb index 8f6d5dc92..f1f831153 100644 --- a/app/controllers/droom/organisations_controller.rb +++ b/app/controllers/droom/organisations_controller.rb @@ -2,6 +2,7 @@ module Droom class OrganisationsController < Droom::DroomController include Droom::Concerns::Searchable helper Droom::DroomHelper + respond_to :html, :js load_and_authorize_resource before_action :set_view, only: [:show, :edit, :update, :create] @@ -22,13 +23,13 @@ def pending end def create - @organisation.update_attributes(organisation_params) + @organisation.update(organisation_params) @organisation.approve!(current_user) respond_with @organisation end def update - @organisation.update_attributes(organisation_params) + @organisation.update(organisation_params) respond_with @organisation end @@ -42,8 +43,9 @@ def disapprove redirect_to organisation_url end + # always an ajax call so for now we only confirm. def merge - @other_org = Droom::Organisation.find(params[:other_id]) + @other_org = Droom::Organisation.find(merge_params[:other_id]) @other_org.subsume(@organisation) head :no_content end @@ -63,6 +65,10 @@ def organisation_params end end + def merge_params + params.require(:organisation).permit(:other_id) + end + def set_view @view = params[:view] if %w{page listed gridded quick full status users pending subsume}.include?(params[:view]) end diff --git a/app/controllers/droom/pages_controller.rb b/app/controllers/droom/pages_controller.rb index 81b1879d6..b0646d4eb 100644 --- a/app/controllers/droom/pages_controller.rb +++ b/app/controllers/droom/pages_controller.rb @@ -6,7 +6,7 @@ class PagesController < Droom::DroomController def new @page = Droom::Page.new(slug: params[:slug]) - render layout: Droom.pages_layout + render layout: Droom.page_layout end def show @@ -18,7 +18,7 @@ def edit end def create - if @page.update_attributes(page_params) + if @page.update(page_params) redirect_to droom.page_url(@page) else render action: :new @@ -26,7 +26,7 @@ def create end def update - if @page.update_attributes(page_params) + if @page.update(page_params) redirect_to droom.page_url(@page) else render action: :edit @@ -68,4 +68,4 @@ def page_params end end -end \ No newline at end of file +end diff --git a/app/controllers/droom/permissions_controller.rb b/app/controllers/droom/permissions_controller.rb index cb3f83b29..398a99e1f 100644 --- a/app/controllers/droom/permissions_controller.rb +++ b/app/controllers/droom/permissions_controller.rb @@ -28,7 +28,7 @@ def edit end def update - @permission.update_attributes(permission_params) + @permission.update(permission_params) respond_with @service, @permission end diff --git a/app/controllers/droom/preferences_controller.rb b/app/controllers/droom/preferences_controller.rb index 9a2628bc2..0c488eae4 100644 --- a/app/controllers/droom/preferences_controller.rb +++ b/app/controllers/droom/preferences_controller.rb @@ -6,13 +6,13 @@ class PreferencesController < Droom::DroomController load_and_authorize_resource :through => :current_user def create - @preference.update_attributes(params[:preference]) + @preference.update(params[:preference]) @preference.save render :partial => "preference" end def update - @preference.update_attributes(params[:preference]) + @preference.update(params[:preference]) @preference.save render :partial => "preference" end diff --git a/app/controllers/droom/scraps_controller.rb b/app/controllers/droom/scraps_controller.rb index 7cee7f3f3..6434fcc5b 100644 --- a/app/controllers/droom/scraps_controller.rb +++ b/app/controllers/droom/scraps_controller.rb @@ -25,12 +25,12 @@ def edit end def update - @scrap.update_attributes(scrap_params(@scraptype)) + @scrap.update(scrap_params(@scraptype)) respond_with(@scrap) end def create - @scrap.update_attributes(scrap_params(@scraptype)) + @scrap.update(scrap_params(@scraptype)) respond_with(@scrap) end diff --git a/app/controllers/droom/services_controller.rb b/app/controllers/droom/services_controller.rb index ada2af5eb..9c990efbc 100644 --- a/app/controllers/droom/services_controller.rb +++ b/app/controllers/droom/services_controller.rb @@ -21,7 +21,7 @@ def new end def create - @service.update_attributes(service_params) + @service.update(service_params) respond_with @service end @@ -30,7 +30,7 @@ def edit end def update - @service.update_attributes(service_params) + @service.update(service_params) respond_with @service end diff --git a/app/controllers/droom/users/passwords_controller.rb b/app/controllers/droom/users/passwords_controller.rb index ada75be25..3d012a3a7 100644 --- a/app/controllers/droom/users/passwords_controller.rb +++ b/app/controllers/droom/users/passwords_controller.rb @@ -2,8 +2,9 @@ module Droom::Users class PasswordsController < Devise::PasswordsController respond_to :html, :json before_action :set_access_control_headers - skip_before_action :require_no_authentication, only: [:completed] + skip_before_action :require_no_authentication, only: [:completed, :edit] before_action :remember_original_destination, only: [:new] + before_action :clear_session, only: [:edit] def show render @@ -13,6 +14,19 @@ def completed render end + def clear_session + original_token = params[:reset_password_token] + reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token) + sign_out(resource_name) + unless Droom::User.find_by_reset_password_token(reset_password_token) + redirect_to droom.expired_reset_password_token_url + end + end + + def expired_reset_password_token + + end + def after_resetting_password_path_for(resource) droom.complete_confirmation_url end diff --git a/app/controllers/droom/users/registrations_controller.rb b/app/controllers/droom/users/registrations_controller.rb index f5874103f..87ade3ba0 100644 --- a/app/controllers/droom/users/registrations_controller.rb +++ b/app/controllers/droom/users/registrations_controller.rb @@ -1,3 +1,5 @@ +require 'net/https' + module Droom::Users class RegistrationsController < Devise::RegistrationsController before_action :set_access_control_headers @@ -16,6 +18,24 @@ def new end end + def create + if helpers.check_recaptcha? + min_score = 0.5 + secret_key = ENV['RECAPTCHA_SECRET_KEY'] + token = params[:recaptcha_token] + + uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}") + response = Net::HTTP.get_response(uri) + json = JSON.parse(response.body) + result = json['success'] && json['score'] > min_score && json['action'] == 'submit' + + unless result + return redirect_to signup_url + end + end + super + end + def after_sign_up_path_for(resource) root_url end diff --git a/app/controllers/droom/users_controller.rb b/app/controllers/droom/users_controller.rb index db47385ad..7b1b04571 100644 --- a/app/controllers/droom/users_controller.rb +++ b/app/controllers/droom/users_controller.rb @@ -18,10 +18,15 @@ def index @users = paginated(@users, params[:pp].presence || 24) respond_with @users do |format| format.js { render :partial => 'droom/users/users' } - format.vcf { render :vcf => @users.map(&:to_vcf) } end end + def download + @users = @users.internal.in_name_order.includes(:emails, :phones, :addresses) + @users = @users.matching(params[:q]) unless params[:q].blank? + render :vcf => @users.map(&:to_vcf) + end + def show respond_with @user end @@ -60,7 +65,8 @@ def edit # This has to handle small preference updates over js and large account-management forms over html. # def update - if @user.update_attributes(user_params) + @user.delete_user_permissions(user_params[:group_ids]) unless user_params[:group_ids].blank? + if @user.update(user_params) respond_with @user, location: user_url(view: @view) do |format| format.js { head :no_content } end @@ -85,7 +91,7 @@ def setup flash[:notice] = t(:password_set) redirect_to params[:destination].presence || droom.dashboard_url else - render + raise Droom::AccessDenied end else render template: "/droom/users/request_password" @@ -93,7 +99,7 @@ def setup end def set_organisation - if current_user.update_attributes(set_organisation_params) + if current_user.update(set_organisation_params) redirect_to params[:destination].presence || droom.dashboard_url else render template: "/droom/users/setup_organisation" @@ -124,14 +130,14 @@ def search_users filters[:groups] = params[:account_group] if params[:account_group].present? filters[:account_confirmation] = params[:account_confirmed] if params[:account_confirmed].present? filters[:organisation] = params[:organisation] if params[:organisation].present? - + query = params[:q].presence || '*' arguments = { where: filters, aggs: [:groups, :account_confirmation, :organisation], order: {name: :asc} } - + if params[:show] == "all" arguments[:limit] = 1000 else @@ -166,9 +172,11 @@ def user_params :country_code, :mobile, :female, - :image + :image, + :timezone, + group_ids: [] ] - + if current_user.organisation_admin? permitted_params += [ :organisation_admin, @@ -181,8 +189,7 @@ def user_params :organisation_id, :organisation_admin, :send_confirmation, - :defer_confirmation, - group_ids: [] + :defer_confirmation ] end @@ -201,11 +208,11 @@ def user_params end def setup_params - params.require(:user).permit(:title, :given_name, :family_name, :chinese_name, :honours, :password, :password_confirmation) + params.require(:user).permit(:title, :given_name, :family_name, :chinese_name, :honours, :password, :password_confirmation, :timezone) end def set_organisation_params - params.require(:user).permit(:organisation_id, organisation_attributes: [:name, :chinese_name, :url, :organisation_type_id, :description, :tags]) + params.require(:user).permit(:organisation_id, organisation_attributes: [:name, :chinese_name, :url, :organisation_type_id, :description, :tags, :owner_id]) end def set_view diff --git a/app/controllers/droom/venues_controller.rb b/app/controllers/droom/venues_controller.rb index 3a4159765..ed9d3f343 100644 --- a/app/controllers/droom/venues_controller.rb +++ b/app/controllers/droom/venues_controller.rb @@ -17,7 +17,7 @@ def show end def update - @venue.update_attributes(params[:venue]) + @venue.update(params[:venue]) respond_with @venue end diff --git a/app/helpers/droom/droom_helper.rb b/app/helpers/droom/droom_helper.rb index 4a31db162..1a9f1e118 100644 --- a/app/helpers/droom/droom_helper.rb +++ b/app/helpers/droom/droom_helper.rb @@ -75,12 +75,12 @@ def allowed?(permission_code) current_user.admin? || current_user.permitted?(permission_code) end - def action_menulink(thing, html_options={}) + def action_menulink(thing, html_options={}, group=nil) if can?(:edit, thing) classname = thing.class.to_s.underscore.split('/').last html_options.reverse_merge!({ :class => "", - :data => {:menu => "#{classname}_#{thing.id}"} + :data => {:menu => "#{classname}_#{thing.id}#{group.try(:id)}"} }) html_options[:class] << ' menu' link_to t(:edit), "#", html_options if can?(:edit, thing) @@ -210,5 +210,28 @@ def url_for_date(date) def day_names ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] end + + def check_recaptcha? + if ENV['RECAPTCHA_CHECK'] && ENV['RECAPTCHA_CHECK'] == 'true' + true + else + false + end + end + + def recaptcha_execute(action) + id = "recaptcha_token_#{SecureRandom.hex(10)}" + + raw %Q{ + + + } + end end end diff --git a/app/jobs/droom/group_invitation_job.rb b/app/jobs/droom/group_invitation_job.rb new file mode 100644 index 000000000..ba1cd3123 --- /dev/null +++ b/app/jobs/droom/group_invitation_job.rb @@ -0,0 +1,15 @@ +module Droom + class GroupInvitationJob < ActiveJob::Base + queue_as :default + + def perform(group_id, event_id) + group = Droom::Group.find(group_id) + event = Droom::Event.find(event_id) + group.users.find_in_batches(batch_size: 50).each do |users| + users.each do |user| + Droom::GroupInvitationMailer.send_invitation(user, event).deliver + end + end + end + end +end diff --git a/app/jobs/droom/index_document_job.rb b/app/jobs/droom/index_document_job.rb index 481e0f5f5..5ccfc0a9f 100644 --- a/app/jobs/droom/index_document_job.rb +++ b/app/jobs/droom/index_document_job.rb @@ -1,3 +1,5 @@ +require 'droom' + module Droom class IndexDocumentJob < ActiveJob::Base diff --git a/app/mailers/droom/group_invitation_mailer.rb b/app/mailers/droom/group_invitation_mailer.rb new file mode 100644 index 000000000..261938909 --- /dev/null +++ b/app/mailers/droom/group_invitation_mailer.rb @@ -0,0 +1,14 @@ +module Droom + class GroupInvitationMailer < ActionMailer::Base + layout Droom.email_layout + default from: %{#{Droom.email_from_name} <#{Droom.email_from}>} + helper ApplicationHelper + + def send_invitation(user, event) + @user = user + @event = event + @subject = "Invitation to #{@event.name}" + mail(to: @user.email, subject: @subject) + end + end +end diff --git a/app/models/droom/ability.rb b/app/models/droom/ability.rb index 0d2d26c6d..53bc485c6 100644 --- a/app/models/droom/ability.rb +++ b/app/models/droom/ability.rb @@ -25,46 +25,59 @@ def initialize(user) if !Droom.require_internal_organisation? || user.internal? - if !Droom.require_login_permission? || user.permitted?('droom.login') + if user.data_room_user? can :read, :dashboard - can :read, Droom::Event - can :read, Droom::Scrap - can :read, Droom::Venue - can :read, Droom::User - can :read, Droom::Group - can :read, Droom::Organisation can :index, :suggestions # If someone has been allowed to create something, they are generally allowed to edit or remove it. # This rule must sit after the user rules because users have no created_by_id column. # - can :manage, [Droom::Event, Droom::Document, Droom::Scrap], :created_by_id => user.id + # can :manage, [Droom::Event, Droom::Document, Droom::Scrap], :created_by_id => user.id # Then other abilities are determined by permissions. Our permissions are relatively abstract and # not closely coupled to Cancan abilities. Here we map them onto more concrete operations. # if user.permitted?('droom.calendar') can :manage, Droom::Event + can :manage, Droom::EventType can :manage, Droom::EventSet can :manage, Droom::Venue can :manage, Droom::Invitation can :manage, Droom::GroupInvitation can :manage, Droom::AgendaCategory + elsif user.permitted?('droom.calendar.read') + can :read, Droom::Event + can :read, Droom::EventType + can :read, Droom::EventSet + can :read, Droom::Venue + can :read, Droom::Invitation + can :read, Droom::GroupInvitation + can :read, Droom::AgendaCategory end if user.permitted?('droom.directory') can :manage, Droom::Group can :manage, Droom::Organisation can :manage, Droom::User + elsif user.permitted?('droom.directory.read') + can :read, Droom::Group + can :read, Droom::Organisation + can :read, Droom::User end if user.permitted?('droom.library') can :manage, Droom::Folder can :manage, Droom::Document + elsif user.permitted?('droom.library.read') + can :read, Droom::Folder + can :read, Droom::Document end if user.permitted?('droom.stream') can :create, Droom::Scrap + can :read, Droom::Scrap + elsif user.permitted?('droom.stream.read') + can :read, Droom::Scrap end if user.permitted?('droom.enquiry') @@ -83,8 +96,6 @@ def initialize(user) end end - can :read, Droom::Scrap - else # What can an external user do? Nothing, by default, but the main app can add permissions. @@ -96,4 +107,4 @@ def initialize(user) end end end -end \ No newline at end of file +end diff --git a/app/models/droom/calendar.rb b/app/models/droom/calendar.rb index 6a7c0d706..be774d10c 100644 --- a/app/models/droom/calendar.rb +++ b/app/models/droom/calendar.rb @@ -1,7 +1,7 @@ module Droom class Calendar < Droom::DroomRecord include Droom::Concerns::Slugged - + belongs_to :created_by, :class_name => "Droom::User" has_many :events diff --git a/app/models/droom/concerns/address_book_property.rb b/app/models/droom/concerns/address_book_property.rb index fb97bf4ed..b710a1405 100644 --- a/app/models/droom/concerns/address_book_property.rb +++ b/app/models/droom/concerns/address_book_property.rb @@ -34,4 +34,8 @@ def undefault_others end end + def address_type_name + address_type.name if address_type + end + end diff --git a/app/models/droom/concerns/changes_notified.rb b/app/models/droom/concerns/changes_notified.rb new file mode 100644 index 000000000..e35d45875 --- /dev/null +++ b/app/models/droom/concerns/changes_notified.rb @@ -0,0 +1,39 @@ +module Droom::Concerns::ChangesNotified + extend ActiveSupport::Concern + + included do + after_create :notify_of_creation + after_update :notify_of_update + after_destroy :notify_of_destruction + end + + def timestamp + (try(:updated_at) || try(:created_at) || Time.now).to_f + end + + def notify_of_change(event, additional_data={}) + if Droom.config.enable_pubsub? + time = event == 'destroyed' ? Time.now.to_i : timestamp + change_data = { + event: event, + timestamp: time, + object_class: self.class.to_s.underscore, + object_id: id, + } + Droom::ChangesChannel.broadcast_to 'changes', change_data.merge(additional_data) + end + end + + def notify_of_creation(additional_data={}) + notify_of_change "created", additional_data + end + + def notify_of_update(additional_data={}) + notify_of_change "updated", additional_data + end + + def notify_of_destruction(additional_data={}) + notify_of_change "destroyed", additional_data + end + +end \ No newline at end of file diff --git a/app/models/droom/concerns/published.rb b/app/models/droom/concerns/published.rb index 762a8a2f6..2cb08dba2 100644 --- a/app/models/droom/concerns/published.rb +++ b/app/models/droom/concerns/published.rb @@ -23,7 +23,7 @@ def illustrated? def publish! unless publishing? - update_attributes({ + update({ published_title: title, published_subtitle: subtitle, published_intro: intro, diff --git a/app/models/droom/document.rb b/app/models/droom/document.rb index d18ed352e..283301d11 100644 --- a/app/models/droom/document.rb +++ b/app/models/droom/document.rb @@ -45,6 +45,8 @@ class Document < Droom::DroomRecord scope :latest, -> limit { order("droom_documents.updated_at DESC, droom_documents.created_at DESC").limit(limit) } + scope :unindexed, -> { where(indexed_at: nil) } + def attach_to(holder) self.folder = holder.folder end @@ -106,9 +108,8 @@ def file_bucket ## Search # - searchkick _all: false, callbacks: false, default_fields: [:name, :content], highlight: [:name, :content] - attr_accessor :updating_index - after_save :enqueue_for_indexing, unless: :updating_index? + searchkick callbacks: false, default_fields: [:name, :content], highlight: [:name, :content] + after_save :enqueue_for_indexing def search_data { @@ -157,29 +158,28 @@ def inherit_confidentiality end def enqueue_for_indexing! + Rails.logger.debug "⚠️ enqueue_for_indexing Droom::Document #{id}" Droom::IndexDocumentJob.perform_later(id, Time.now.to_i) end def enqueue_for_indexing - if name_changed? || file_file_name_changed? || file_fingerprint_changed? - Droom::IndexDocumentJob.perform_later(id, Time.now.to_i) + if saved_change_to_name? || saved_change_to_file_file_name? || saved_change_to_file_fingerprint? + enqueue_for_indexing! end end def update_index! - unless self.updating_index - self.updating_index = true - with_local_file do |path| - @file_content = Yomu.new(path).text - self.reindex - end - self.update_column(:indexed_at, Time.now) - self.updating_index = false + with_local_file do |path| + @file_content = Yomu.new(path).text + self.reindex + self.secondary_reindex end + self.update_column(:indexed_at, Time.now) + true end - def updating_index? - !!updating_index + def secondary_reindex + # noop here end # Pass block to perform operations with a local file, which will be diff --git a/app/models/droom/droom_record.rb b/app/models/droom/droom_record.rb index 2622190ea..23fd73250 100644 --- a/app/models/droom/droom_record.rb +++ b/app/models/droom/droom_record.rb @@ -1,6 +1,7 @@ module Droom class DroomRecord < ActiveRecord::Base - include Droom::Folders + include Droom::Concerns::ChangesNotified + include Droom::Folders # TODO please can we get rid of this now? self.abstract_class = true end end \ No newline at end of file diff --git a/app/models/droom/event.rb b/app/models/droom/event.rb index c61da3582..8744e519b 100644 --- a/app/models/droom/event.rb +++ b/app/models/droom/event.rb @@ -27,6 +27,9 @@ class Event < Droom::DroomRecord has_folder :within => :event_type #... and subfolders via agenda_categories after_destroy :destroy_related_folder + around_update :update_folder_name + + after_save :set_parent_folder_id validates :start, :presence => true, :date => true validates :finish, :date => {:after => :start, :allow_nil => true} @@ -57,7 +60,7 @@ class Event < Droom::DroomRecord scope :future_and_current, -> { where(['(finish > :now) OR (finish IS NULL AND start > :now)', :now => Time.zone.now]) } scope :finished, -> { where(['(finish < :now) OR (finish IS NULL AND start < :now)', :now => Time.zone.now]) } - + scope :unbegun, -> { where(['start > :now', :now => Time.zone.now])} scope :by_finish, -> { order("finish ASC") } @@ -96,7 +99,7 @@ class Event < Droom::DroomRecord .group("droom_events.id") } - scope :matching, -> fragment { + scope :matching, -> fragment { fragment = "%#{fragment}%" where('droom_events.name like :f OR droom_events.description like :f', :f => fragment) } @@ -122,19 +125,19 @@ def self.in_month(year, month) # numbers. eg calendar.occurrences.in_mo finish = start + 1.month between(start, finish) end - + def self.in_week(year, week) # numbers, with a commercial week: eg calendar.occurrences.in_week(2010, 35) start = DateTime.commercial(year, week) finish = start + 1.week between(start, finish) end - + def self.on_day (year, month, day) # numbers: eg calendar.occurrences.on_day(2010, 12, 12) start = DateTime.civil(year, month, day) finish = start + 1.day between(start, finish) end - + def self.in_span(span) # Chronic::Span between(span.begin, span.end) end @@ -160,7 +163,7 @@ def invite(user) def attach(doc) self.documents << doc end - + # We store the start and end points of the event as a single DateTime value to make comparison simple. # The setters for date and time are overridden to pass strings through chronic's natural language parser # and to treat numbers as epoch seconds. These should all work as you'd expect: @@ -178,19 +181,19 @@ def finish=(value) write_attribute :finish, parse_date(value) end - # For interface purposes we often want to separate date and time parts. These getters will return the + # For interface purposes we often want to separate date and time parts. These getters will return the # corresponding Date or Time object. # # The `tod` gem makes time handling a bit more intuitive by concealing the date part of a Time object. # - + def start tz = timezone || Time.zone if start = read_attribute(:start) start.in_time_zone(tz) end end - + def start_time Tod::TimeOfDay(start) if start end @@ -198,7 +201,7 @@ def start_time def start_date start.to_date if start end - + def finish tz = timezone || Time.zone if finish = read_attribute(:finish) @@ -233,7 +236,7 @@ def duration 0 end end - + def venue_name venue.name if venue end @@ -245,7 +248,7 @@ def venue_name=(name) def find_or_create_agenda_category(category) agenda_categories.where(category_id: category.id).first_or_create end - + def categories_for_selection cats = categories.map{|c| [c.name, c.id] } cats.unshift(['', '']) @@ -266,7 +269,7 @@ def visible_to?(user) return false if self.confidential?# || Droom.events_private_by_default? return true end - + def detail_visible_to?(user) return true if self.public? return false unless user @@ -275,7 +278,7 @@ def detail_visible_to?(user) return false if self.private? return true end - + def has_anyone? invitations.any? end @@ -299,7 +302,7 @@ def continuing? def finished? start < Time.zone.now && (!finish || finish < Time.zone.now) end - + def url_with_protocol if url? && url !~ /^https?:\/\// "http://#{url}" @@ -362,13 +365,21 @@ def as_search_result :id => id } end - + def folder_name "#{name} (#{month_name} #{year})" end protected + # Set event_type.folder.id to event.folder.parent_id if event.event_type changed + # + def set_parent_folder_id + if event_type && event_type.folder + folder.update(parent_id: event_type.folder.id) + end + end + # This is mostly for ical/webcal distributions but we also use it in the API. # def set_uuid @@ -398,5 +409,16 @@ def destroy_related_folder end end + def update_folder_name + is_changed = self.name_changed? + yield + if is_changed + if event_folder = self.folder + event_folder.name = self.folder_name + event_folder.save + end + end + end + end -end \ No newline at end of file +end diff --git a/app/models/droom/folder.rb b/app/models/droom/folder.rb index f746190db..16ec663ae 100644 --- a/app/models/droom/folder.rb +++ b/app/models/droom/folder.rb @@ -1,7 +1,7 @@ module Droom class Folder < Droom::DroomRecord include ActsAsTree - include Droom::Concerns::Slugged + # don't use Slugged: we need to apply a dynamic parent scope. belongs_to :created_by, :class_name => "Droom::User" belongs_to :holder, :polymorphic => true @@ -9,11 +9,9 @@ class Folder < Droom::DroomRecord has_many :personal_folders, :dependent => :destroy acts_as_tree :order => "droom_folders.name ASC" + before_validation :set_properties validates :slug, :presence => true, :uniqueness => { :scope => :parent_id } - before_validation :set_properties - before_validation :slug_from_name - default_scope -> { includes(:documents) } scope :all_private, -> { where("#{table_name}.private = 1") } @@ -21,7 +19,10 @@ class Folder < Droom::DroomRecord scope :all_public, -> { where("#{table_name}.public = 1 AND #{table_name}.private <> 1 OR #{table_name}.private IS NULL") } scope :not_public, -> { where("#{table_name}.public <> 1 OR #{table_name}.private = 1)") } scope :by_name, -> { order("#{table_name}.name ASC") } - scope :other_than, -> folders {where.not(id: folders.map(&:id))} + scope :other_than, -> folders { + folders = [folders].flatten + where.not(id: folders.map(&:id)) + } scope :visible_to, -> user { if user select('droom_folders.*') @@ -134,7 +135,7 @@ def distribute_confidentiality children.each {|folder| folder.set_confidentiality!(confidential?) } end - protected + protected def set_properties if holder @@ -145,10 +146,26 @@ def set_properties end self.slug ||= holder.slug end - # folders originally only had slugs, so this happens from time to time + + # pass new or existing slug through uniqueness check as it may have come from user or holder + base = slug.presence || name || "Folder" + self.slug = unique_slug(base) + + # folders originally only had slugs, so this could happen too self.name ||= self.slug - self.public = !holder && (!parent || parent.public?) - true + end + + # Protect against slug-collision within parent folder scope. + # + def unique_slug(base) + slug = base + addendum = 0 + skope = parent ? parent.children : Folder.loose + while skope.other_than(self).find_by(slug: slug) + addendum += 1 + slug = "#{base}_#{addendum}" + end + slug end end diff --git a/app/models/droom/group_permission.rb b/app/models/droom/group_permission.rb index 7735e76ec..5f379fa2b 100644 --- a/app/models/droom/group_permission.rb +++ b/app/models/droom/group_permission.rb @@ -4,7 +4,7 @@ class GroupPermission < Droom::DroomRecord belongs_to :permission has_many :user_permissions, :dependent => :destroy after_save :create_user_permissions - + # This is set up such that a personal permission created by group membership can be deleted # while still leaving in place a personal permission that was granted separately. # @@ -14,16 +14,26 @@ def create_user_permissions end end + def delete_permissions(read_only = false) + read_permission = self.permission.get_read_permission + read_gp_permissions = self.class.find_by(group_id: self.group_id, permission_id: read_permission.try(:id)) + self.permission_id = read_permission.id if read_only == 'true' + + # delete users permissions + self.user_permissions.destroy_all + read_gp_permissions.user_permissions.destroy_all if read_gp_permissions.present? + end + def create_permission_for(user) user_permissions.where(:user_id => user.id, :permission_id => permission.id).first_or_create end - + def self.by_group_id all.each_with_object({}) do |gp, hash| hash[gp.group_id] ||= {} hash[gp.group_id][gp.permission.id] = gp end end - + end -end \ No newline at end of file +end diff --git a/app/models/droom/mailing_list_membership.rb b/app/models/droom/mailing_list_membership.rb index 5bb736193..dbdc13bd0 100644 --- a/app/models/droom/mailing_list_membership.rb +++ b/app/models/droom/mailing_list_membership.rb @@ -71,12 +71,12 @@ class MailingListMembership < Droom::DroomRecord # # If no such connection is defined, we will use the local `droom_mailing_list_memberships` table. # - begin - establish_connection :"mailman_#{Rails.env}" - set_table_name Droom.mailman_table_name - rescue ActiveRecord::AdapterNotSpecified - Rails.logger.warn "Droom: No mailman connection configured. Using #{Rails.env} database." - end + # begin + # establish_connection :"mailman_#{Rails.env}" + # set_table_name Droom.mailman_table_name + # rescue ActiveRecord::AdapterNotSpecified + # Rails.logger.warn "Droom: No mailman connection configured. Using #{Rails.env} database." + # end ## Associations # @@ -111,8 +111,8 @@ class MailingListMembership < Droom::DroomRecord private def set_defaults - self.bi_lastnotice = 0 - self.bi_date = 0 + self.bi_lastnotice = '0000-00-00' + self.bi_date = '0000-00-00' self.ack = true self.nomail = !Droom.mailing_lists_active_by_default? self.digest = Droom.mailing_lists_digest_by_default? diff --git a/app/models/droom/organisation.rb b/app/models/droom/organisation.rb index 11078b6a3..b26d04e87 100644 --- a/app/models/droom/organisation.rb +++ b/app/models/droom/organisation.rb @@ -35,11 +35,17 @@ class Organisation < Droom::DroomRecord attr_accessor :other_id def self.for_selection(with_external=false) - organisations = order("name asc") + organisations = approved.order("name asc") organisations = organisations.where(external: false) unless with_external organisations.map{|f| [f.name, f.id] }.unshift(['', '']) end + def self.for_selection_with_owner(with_external=false) + organisations = approved.order("name asc").includes(:owner) + organisations = organisations.where(external: false) unless with_external + organisations.map{|o| ["#{o.name} (#{o.owner_name || 'No owner'})", o.id] }.unshift(['', '']) + end + def self.matching_email(email) domain = email.split('@').last where(joinable: true, email_domain: domain) @@ -63,7 +69,7 @@ def self.disapprove_all # def approve!(approving_user=nil) unless approved? - self.update_attributes({ + self.update({ approved_at: Time.now, approved_by: approving_user, disapproved_at: nil, @@ -77,7 +83,7 @@ def approve!(approving_user=nil) # def approve unless approved? - self.update_attributes({ + self.update({ approved_at: Time.now, disapproved_at: nil, disapproved_by: nil @@ -91,7 +97,7 @@ def approved? def disapprove!(user) unless disapproved? - self.update_attributes({ + self.update({ disapproved_at: Time.now, disapproved_by: user, approved_at: nil, @@ -216,11 +222,18 @@ def subsume(org) self.tags << org.tags self.chinese_name = org.chinese_name unless chinese_name? self.description = org.description unless description? + self.logo = org.logo unless logo? + self.owner = org.owner unless owner + subsume_other_resources(org) self.save - chinese_name.destroy + org.destroy end end + def subsume_other_resources(org) + # noop here + end + def capture_owner unless owner if user = users.first @@ -238,6 +251,7 @@ def search_data { name: name || "", chinese_name: chinese_name || "", + owner_name: owner_name, description: description, tags: tag_names, all_tags: tags_with_synonyms, @@ -248,5 +262,9 @@ def search_data } end + def owner_name + owner.name if owner + end + end end \ No newline at end of file diff --git a/app/models/droom/permission.rb b/app/models/droom/permission.rb index 29be0b062..ed59b0cc7 100644 --- a/app/models/droom/permission.rb +++ b/app/models/droom/permission.rb @@ -5,13 +5,26 @@ class Permission < Droom::DroomRecord has_many :user_permissions, :dependent => :destroy acts_as_list :scope => :service_id before_save :set_slug - + validates :slug, :uniqueness => true - + + def get_read_permission + self.class.find_by(name: "#{self.name}.read") + end + + def define_permission_color(group_permission, group, read_permission) + group_read_permission = Droom::GroupPermission.find_by(group_id: group.id, permission_id: read_permission.id) + color = 'no' + color = 'read' if group_read_permission && !group_read_permission.destroyed? + color = 'yes' if group_permission && !group_permission.destroyed? + + color + end + protected - + def set_slug self.slug = [service.slug, self.name].join('.') end end -end \ No newline at end of file +end diff --git a/app/models/droom/user.rb b/app/models/droom/user.rb index 26db3400e..f53a7eedd 100644 --- a/app/models/droom/user.rb +++ b/app/models/droom/user.rb @@ -1,3 +1,5 @@ +require 'vcard' + module Droom class User < Droom::DroomRecord include Droom::Concerns::Imaged @@ -30,7 +32,7 @@ class User < Droom::DroomRecord accepts_nested_attributes_for :organisation before_validation :ensure_uid! - before_save :ensure_authentication_token + before_save :ensure_authentication_token! before_save :org_admin_if_alone after_save :send_confirmation_if_directed @@ -79,6 +81,12 @@ def defer_confirmation? defer_confirmation && defer_confirmation != "false" end + # For users of peripheral services we can leave it up to them to require or offer confirmation. + # + def confirmation_required? + !confirmed? && data_room_user? + end + # send_confirmation? is called after save by our own later confirmation mechanism. # If the send_confirmation flag has been set, we confirm. # @@ -111,7 +119,7 @@ def names? # Our old user accounts store passwords as salted sha512 digests. Current standard uses BCrypt # so we migrate user accounts across in this rescue block whenever we hear BCrypt grumbling about the old hash. - + def valid_password?(password) begin super(password) @@ -129,7 +137,7 @@ def valid_password?(password) return false end end - end + end # This is called from a warden hook during every authenticated request, either to the data room or its API. # The value of last_request_at is used to invalidate stale sessions. @@ -152,6 +160,7 @@ def reset_session_ids! end def clear_session_ids! + Rails.logger.warn "⚠️ clear_session_ids!" self.update_columns({ session_id: "", unique_session_id: "" @@ -175,7 +184,7 @@ def authenticate_safely(attribute, token) Devise.secure_compare(send(attribute), token) end - def ensure_authentication_token + def ensure_authentication_token! if authentication_token.blank? self.authentication_token = generate_authentication_token end @@ -184,6 +193,7 @@ def ensure_authentication_token def ensure_unique_session_id! unless unique_session_id.present? + Rails.logger.warn "✅ calling update_unique_session_id!" update_unique_session_id!(Devise.friendly_token) end unique_session_id @@ -229,7 +239,7 @@ def external? end def internal? - !organisation && !organisation.external? + organisation && !organisation.external? end @@ -358,7 +368,7 @@ def documents # Can hold multiple emails, phones and addresses for each user. # Address book data is simple and always nested. # - has_many :emails + has_many :emails, :dependent => :destroy accepts_nested_attributes_for :emails, :allow_destroy => true has_many :phones accepts_nested_attributes_for :phones, :allow_destroy => true @@ -584,7 +594,7 @@ def self.for_selection ## Names # def title_ordinary? - ['Mr', 'Ms', 'Mrs', '', nil].include?(title) + title.nil? || ['mr', 'ms', 'mrs', 'mr.', 'ms.', 'mrs.', ''].include?(title.downcase.strip) end def title_if_it_matters @@ -615,19 +625,34 @@ def full_name def to_vcf @vcard ||= Vcard::Vcard::Maker.make2 do |maker| maker.add_name do |n| - n.given = name || "" + n.family = family_name || "" + n.given = given_name || "" + n.prefix = title unless title_ordinary? + end + emails.each do |email| + if email.email? + location = email.address_type_name || 'home' + maker.add_email email.email { |e| t.location = location.downcase } + end + end + phones.each do |phone| + if phone.phone? + location = phone.address_type_name || 'cell' + location = 'cell' if location == 'Mobile' + maker.add_tel phone.phone { |e| t.location = location.downcase } + end + end + addresses.each do |address| + location = address.address_type_name || 'home' + maker.add_addr {|a| + a.location = location.downcase + a.country = address.country_code || "" + a.region = address.region || "" + a.locality = address.city || "" + a.street = "#{address.line_1}, #{address.line_2}" + a.postalcode = address.postal_code || "" + } end - maker.add_addr {|a| - a.location = 'home' # until we do this properly with multiple contact sets - a.country = post_country || "" - a.region = post_region || "" - a.locality = post_city || "" - a.street = "#{post_line1}, #{post_line2}" - a.postalcode = post_code || "" - } - maker.add_tel phone { |t| t.location = 'home' } unless phone.blank? - maker.add_tel mobile { |t| t.location = 'cell' } unless mobile.blank? - maker.add_email email { |e| t.location = 'home' } end @vcard.to_s end @@ -854,7 +879,7 @@ def subsume(other_user) def subsume!(other_user) Droom::User.transaction do - %w{emails phones addresses memberships organisations scraps documents invitations memberships user_permissions dropbox_tokens dropbox_documents personal_folders}.each do |association| + %w{emails phones addresses memberships scraps documents invitations memberships user_permissions dropbox_tokens dropbox_documents personal_folders}.each do |association| self.send(association.to_sym) << other_user.send(association.to_sym) end %w{encrypted_password password_salt family_name given_name chinese_name title gender organisation_id description image}.each do |property| @@ -869,6 +894,12 @@ def subsume!(other_user) end end + def delete_user_permissions(group_ids = []) + self.user_permissions.each do |perm| + group_id = perm.group_permission.group_id + perm.delete unless group_ids.map(&:to_i).include?(group_id) + end + end protected diff --git a/app/serializers/droom/user_auth_serializer.rb b/app/serializers/droom/user_auth_serializer.rb index 9b91dcd3a..b74a25377 100644 --- a/app/serializers/droom/user_auth_serializer.rb +++ b/app/serializers/droom/user_auth_serializer.rb @@ -17,7 +17,6 @@ class Droom::UserAuthSerializer < ActiveModel::Serializer :password_set, :images - def name object.colloquial_name end diff --git a/app/serializers/droom/user_serializer.rb b/app/serializers/droom/user_serializer.rb index 5c7130d1d..d2cf7933c 100644 --- a/app/serializers/droom/user_serializer.rb +++ b/app/serializers/droom/user_serializer.rb @@ -1,6 +1,12 @@ +# This is user as data object: all the fields we need to manage or present the user. +# It does not include authentication information. +# +# Also note btw that the address book is squashed down to one email, one phone and one mobile. +# We don't yet support remote management of all that detail, but users can update their listing, +# which has the effect of adding a new preferred address but not deleting the old. +# class Droom::UserSerializer < ActiveModel::Serializer attributes :uid, - :authentication_token, :status, :title, :given_name, diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 2787f3fd8..8935c6459 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,6 +1,12 @@ +- if check_recaptcha? + %script{src: "https://www.google.com/recaptcha/api.js?render=#{ENV['RECAPTCHA_SITE_KEY']}"} + = form_for(resource, as: resource_name, url: droom.register_path, method: "post") do |f| = render "devise/shared/error_messages", resource: resource = f.email_field :email, autofocus: true, autocomplete: "email", placeholder: t("placeholders.registration.email"), class: "half" - = f.submit t('registration.submit') + = f.submit t('registration.submit'), id: 'submitWithRecaptcha' + + - if check_recaptcha? + = recaptcha_execute('submit') diff --git a/app/views/droom/dashboard/_events.html.haml b/app/views/droom/dashboard/_events.html.haml index 8d1b7f43c..ea362c881 100644 --- a/app/views/droom/dashboard/_events.html.haml +++ b/app/views/droom/dashboard/_events.html.haml @@ -10,9 +10,10 @@ - if calendar.events.future_and_current.count > 5 %span.addendum =t(:click_for_more, count: calendar.events.future_and_current.count - 5) - - if can?(:create, Droom::Event) - %span.action + %span.action + - if can?(:create, Droom::Event) = link_to t(:add_event), droom.new_event_url, :class => "add", :data => {:action => "popup", :affected => "#events"} + = link_to t(:subscribe_to_calendar), subscribe_events_url(current_user.authentication_token, protocol: "webcal", format: "ics"), class: "subscribe" - if events.any? = render :partial => "droom/events/event", :collection => events, :locals => {:brief => true} @@ -24,4 +25,4 @@ - else %p.nothing - = t :no_events \ No newline at end of file + = t :no_events diff --git a/app/views/droom/dashboard/_past_events.haml b/app/views/droom/dashboard/_past_events.haml index 29c0a30b1..2d612a2ce 100644 --- a/app/views/droom/dashboard/_past_events.haml +++ b/app/views/droom/dashboard/_past_events.haml @@ -6,7 +6,7 @@ %section #past_events %h2.section - = link_to main_app.calendar_url do + = link_to droom.past_events_url do = t(:recent_events) - if calendar.events.past.count > 5 %span.addendum diff --git a/app/views/droom/dashboard/_stream_events.html.haml b/app/views/droom/dashboard/_stream_events.html.haml new file mode 100644 index 000000000..394cc9b01 --- /dev/null +++ b/app/views/droom/dashboard/_stream_events.html.haml @@ -0,0 +1,11 @@ +#suggestion_box + +- if calendar = Droom::Calendar.where(name: 'stream').first + #other + %h2.section + = t(:external_events) + - if can?(:create, Droom::Event) + %span.action + = link_to t(:add_external_event), droom.new_event_url(event: {calendar_id: calendar.id}), :class => "add minimal", :data => {:action => "popup", :affected => "#minor_events"} + + = render "droom/calendars/event_list", calendar: calendar, events: calendar.events.by_date_descending.limit(10) diff --git a/app/views/droom/documents/_form.html.haml b/app/views/droom/documents/_form.html.haml index 3deca0246..071ad4965 100644 --- a/app/views/droom/documents/_form.html.haml +++ b/app/views/droom/documents/_form.html.haml @@ -1,5 +1,6 @@ -= form_for [@folder, @document], :html => { :multipart => true, :class => 'edit document', :remote => true, :data => {:type => :html}} do |f| - += form_for [@folder, @document], :html => { :multipart => true, :class => 'edit document', :remote => true, :data => {:type => :html}, onsubmit: 'DocumentEditorForm(event)'} do |f| + = hidden_field_tag :authenticity_token, form_authenticity_token + = hidden_field_tag :document_id, @document.id %p.slug = @folder.path + "/" @@ -10,8 +11,12 @@ = t :click_to_choose_file .non-file-data - %p.name - = f.text_field :name, :class => 'name', :placeholder => t(:"helpers.label.document.name"), :autofocus => true + %p.error{style: 'color: red'} + %p#document-info.name + - name, extension = @document.name.split('.') if @document.name + = f.hidden_field :name, :class => 'name' + = text_field_tag :filename, name, class: 'filename', :placeholder => t(:"helpers.label.document.name"), :autofocus => true, style: "width: 75%" + = text_field_tag :extension, extension, class: 'extension', readonly: true, style: "width: 20%; background: #eee" %p.description = f.text_area :description, :class => 'description', :placeholder => t(:"helpers.label.document.description") diff --git a/app/views/droom/documents/_listing.html.haml b/app/views/droom/documents/_listing.html.haml index 4c31f72a4..b485002f1 100644 --- a/app/views/droom/documents/_listing.html.haml +++ b/app/views/droom/documents/_listing.html.haml @@ -5,7 +5,13 @@ - character_count = narrow ? 28 : 64 %li.document{:id => "document_#{document.id}", data: {narrow: narrow, doc_id: document.id}} - = link_to shorten(document.name, character_count, ''), droom.folder_document_url(document.folder, document), class: "document #{document.file_extension.downcase}", target: "_blank" + - extension = document.file_extension.downcase + = link_to droom.folder_document_url(document.folder, document), class: "document #{extension == 'pdf' ? 'no-bg-image' : ''} #{extension}", target: "_blank" do + - if extension == 'pdf' + %svg.icon.medium + %use{"xlink:href" => "#pdf_symbol"} + %span + = shorten(document.name, character_count, '') - unless omit_menu = action_menulink(document) = action_menu(document) diff --git a/app/views/droom/events/_attachments.html.haml b/app/views/droom/events/_attachments.html.haml index 2c6c24957..0fcbc84ec 100644 --- a/app/views/droom/events/_attachments.html.haml +++ b/app/views/droom/events/_attachments.html.haml @@ -1,8 +1,8 @@ - event ||= @event - folder ||= event.folder - .col.attachments{data: {droppable: droom.folder_documents_path(folder), queue: "#queue_to_#{folder.id}", refreshes: "#event_#{event.id}"}} - %h3 - = t :attachments + - if folder.documents.count > 0 || folder.children.count > 0 + %h3{style: "font-size: 1.17rem; font-weight: bold;"} + = t :attachments %ul.uploads{id: "queue_to_#{folder.id}"} = render :partial => "droom/folders/contents", :locals => {folder: folder, narrow: false, drop_refreshes: ""} diff --git a/app/views/droom/events/_event.html.haml b/app/views/droom/events/_event.html.haml index 23a94b65f..baf95f1fd 100644 --- a/app/views/droom/events/_event.html.haml +++ b/app/views/droom/events/_event.html.haml @@ -1,12 +1,13 @@ - @seen_events ||= [] - event ||= @event +- event_invitation ||= @event_invitation - here ||= event == @event - full_attachments ||= false - show_folder = event.folder && can?(:read, event.folder) - event_master_id = event.master_id || event.id - if @seen_events[event_master_id] - - repeating = true + - repeating = true - brief = true - @seen_events[event_master_id] = true; @@ -16,7 +17,7 @@ - cssclasses << 'invited' if event.attended_by?(current_user) - div_data = {refreshing: event_path(event, format: :js)} -- if show_folder && !full_attachments +- if show_folder - div_data[:droppable] = droom.folder_documents_path(event.folder) - div_data[:queue] = "#queue_to_#{event.folder.id}" - div_data[:refreshes] = "#event_#{event.id}" @@ -24,10 +25,10 @@ %div{:class => cssclasses.join(' '), id: "event_#{event.id}", data: div_data} = link_to event_url(event) do .datemark - %span.mon= event.start.strftime('%b') - %span.dom= event.start.strftime('%d') + %span.mon= datetime_by_timezone(event.start, 'date').strftime('%b') + %span.dom= datetime_by_timezone(event.start, 'date').strftime('%d') - if @year || event.start.year != Date.today.year - %span.year= event.start.strftime('%Y') + %span.year= datetime_by_timezone(event.start, 'date').strftime('%Y') .summary .heading @@ -37,27 +38,41 @@ = action_menu(event) %p.practicalities - %time - = l(event.start_time, :format => :natural) - - - if event.finish? - = t :to - %time - = l(event.finish_time, :format => :natural) - - if event.timezone == "London" - = "(#{t(:uk_time)})" - - if event.venue %span.location - = t(:at) + %svg.icon.pint-sized + %use{"xlink:href" => "#location_symbol"} - if event.venue.url - = link_to event.venue.definite_name + ".", event.venue.url + = link_to event.venue.definite_name, event.venue.url - elsif Droom.show_venue_map? - = link_to event.venue.definite_name + ".", venues_url(:id => event.venue.id) + = link_to event.venue.definite_name, venues_url(:id => event.venue.id) - else - = event.venue.definite_name + "." + = event.venue.definite_name + %span.time + %svg.icon.pint-sized2 + %use{"xlink:href" => "#clock_symbol"} + %time + = l(datetime_by_timezone(event.start, 'time'), :format => :natural) + + - if event.finish? + = t :to + %time + = l(datetime_by_timezone(event.finish, 'time'), :format => :natural) + + - if !current_user.timezone.present? && event.timezone == "London" + = "(#{t(:uk_time)})" + + - if current_user.timezone + - city = current_user.timezone.split(",").map{|x| x.split("/").last} + - ev_timezone = event.timezone === 'Hong Kong' ? 'Hongkong' : event.timezone + - if city && !city.include?(ev_timezone) + %i.note{style: "display: block;"} + = "#{event.start.strftime('%b %d')}, #{l(event.start_time, :format => :natural)} (#{ev_timezone == 'London' ? t(:uk_time) : t(:hong_koong_time)})" .detail + - if event.description? + .content{style: "width:100%"} + = sanitize(event.description) - if show_folder && !full_attachments - if event.folder.populated? || can?(:read, event.folder) .attachments @@ -65,10 +80,12 @@ %ul.uploads{id: "queue_to_#{event.folder.id}"} = render :partial => "droom/folders/contents", :locals => {:folder => event.folder, :open => true, :flat => true, :narrow => true, :limit => 3, :omit_menu => true, :for_more => event_url(event)} - - if event.description? - = sanitize(event.description) - if show_folder && full_attachments .addenda = render :partial => 'droom/events/attachments', :locals => {:event => event} + - if event_invitation + %h3{style: "padding-top: 1.5rem"} + = t :invite_event + = render :partial => 'droom/events/event_invitation', :locals => {:event => event} = render :partial => 'droom/events/invitations', :locals => {:event => event} diff --git a/app/views/droom/events/_event_invitation.html.haml b/app/views/droom/events/_event_invitation.html.haml new file mode 100644 index 000000000..0c58aa8d5 --- /dev/null +++ b/app/views/droom/events/_event_invitation.html.haml @@ -0,0 +1,20 @@ +- invitation ||= @event_invitation +- event ||= @event + +- if invitation + .invitations + .invitation{:class => invitation.status, :id => "invitation_#{invitation.id}"} + - if invitation.accepted? + %span + = t(:invite_event_accepted) + - elsif invitation.refused? + %span + = t(:invite_event_refused) + - else + %span + = t(:invite_event_text) + %span.action + - unless invitation.accepted? + = link_to t(:accept), accept_event_invitation_url(invitation.event, invitation, event_invitation: true), :class => 'accept', :data => {:action => "replace", :method => "put", :replaced => "#invitation_#{invitation.id}"} + - unless invitation.refused? + = link_to t(:refuse), refuse_event_invitation_url(invitation.event, invitation, event_invitation: true), :class => 'refuse', :data => {:action => "replace", :method => "put", :replaced => "#invitation_#{invitation.id}"} diff --git a/app/views/droom/events/_shortcuts.html.haml b/app/views/droom/events/_shortcuts.html.haml new file mode 100644 index 000000000..30f0bd024 --- /dev/null +++ b/app/views/droom/events/_shortcuts.html.haml @@ -0,0 +1,9 @@ +- years = Droom::Event.pluck(:start).compact.map(&:year).uniq.sort.reverse + +%p.navigation + = link_to_unless_current "Future events", droom.events_url + \| + = link_to_unless_current "Past events", droom.past_events_url + - years.each do |y| + \| + = link_to_unless_current y, events_url(year: y) diff --git a/app/views/droom/events/index.html.haml b/app/views/droom/events/index.html.haml index 545164d20..704ad87b9 100644 --- a/app/views/droom/events/index.html.haml +++ b/app/views/droom/events/index.html.haml @@ -1,15 +1,24 @@ -#calendar - = render :partial => 'events' +- if @year + - title = t(:events_in, period: @year) +- elsif @direction == "past" + - title = t(:past_events) +- else + - title = t(:calendar) - content_for :title do - - if @year - = t(:events_in, period: @year) - - elsif @direction == "past" - = t(:past_events) - - else - = t(:calendar) - + = "#{title}" + .button-group + %span.action + - if can?(:create, Droom::Event) + = link_to t(:add_event), droom.new_event_url, :class => "add", :data => {:action => "popup", :affected => "#events"} + = link_to t(:subscribe_to_calendar), subscribe_events_url(current_user.authentication_token, protocol: "webcal", format: "ics"), class: "subscribe" + - content_for :margin do = render :partial => "droom/shared/calendar_holder" = render :partial => "droom/invitations/invitations" - = render :partial => "droom/events/scrap_events" + = render :partial => "droom/dashboard/stream_events" + + +#calendar + = render :partial => 'shortcuts' + = render :partial => 'events' diff --git a/app/views/droom/folders/_action_menu.html.haml b/app/views/droom/folders/_action_menu.html.haml index 188cc68ed..e8f85a2c2 100644 --- a/app/views/droom/folders/_action_menu.html.haml +++ b/app/views/droom/folders/_action_menu.html.haml @@ -7,6 +7,8 @@ = link_to t(:add_document), '#', class: "add", id: "upload_to_folder_#{folder.id}", data: {role: "upload-file"} %li = link_to t(:add_subfolder), droom.new_folder_folder_url(folder), :class => "add", :data => {:action => "popup", :type => "html", :affected => "#folder_#{folder.id}"} + %li + = link_to t(:move_folder), droom.move_folder_folder_url(folder), :class => "edit", :id => "move_folder_#{folder.id}", :data => {:action => "popup", :type => "html", :affected => "#folders"} %li = link_to t(:delete_folder), droom.folder_url(folder), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#folder_#{folder.id}", :confirm => t(:confirm_delete_folder, :path => folder.path)} - if folder.populated? diff --git a/app/views/droom/folders/_contents.html.haml b/app/views/droom/folders/_contents.html.haml index 67fdfcece..1f08a1042 100644 --- a/app/views/droom/folders/_contents.html.haml +++ b/app/views/droom/folders/_contents.html.haml @@ -16,7 +16,7 @@ - if limit && document_count > 0 && limit < document_count %li.files{data: lidata} %ul - = render :partial => 'droom/documents/listing', :collection => folder.documents.limit(limit) + = render :partial => 'droom/documents/listing', :collection => folder.documents.limit(limit), locals: {narrow: narrow} - if for_more %li.more = link_to t(:more_documents_count, :count => document_count - limit), for_more diff --git a/app/views/droom/folders/_form.html.haml b/app/views/droom/folders/_form.html.haml index 715949997..a6c4c4bff 100644 --- a/app/views/droom/folders/_form.html.haml +++ b/app/views/droom/folders/_form.html.haml @@ -1,6 +1,7 @@ = form_for @folder, :html => {:class => 'edit folder'} do |f| = f.hidden_field :parent_id - + + %p.error{style: 'color: red'} %p.name = f.label :name %br @@ -13,7 +14,7 @@ - else \/ %br - = f.text_field :slug, :class => "slug", :placeholder => t("helpers.label.folder.slug"), data: {role: 'slug'} + = f.text_field :slug, :class => "slug", :placeholder => t("helpers.label.folder.slug"), data: {role: 'slug'}, readonly: true, style: "background: #eee" .buttons = f.submit t(:save_folder) diff --git a/app/views/droom/folders/_move_folder.html.haml b/app/views/droom/folders/_move_folder.html.haml new file mode 100644 index 000000000..94c6cf947 --- /dev/null +++ b/app/views/droom/folders/_move_folder.html.haml @@ -0,0 +1,72 @@ +%form.edit.folder{action: "/folders/#{@folder.id}/moved", :method => 'put'} + = hidden_field_tag :new_parent_id, '' + = select_tag 'folders', options_from_collection_for_select(Droom::Folder.roots, "id", "name"), id: 'folders_list_1', class: 'folders_list', prompt: "Please Select a Root Folder" + #child_folders + .buttons + %input{name: "submit", type: "submit", value: t(:save_folder)} + = t :or + = link_to t(:cancel), '#', :class => 'cancel' + +:javascript + $(document).ready( function() { + + var fetch_child_folder = function(current_element){ + current_element_id = current_element.attr('id'); + var new_parent_id = current_element.children("option:selected").val(); + var ids = $('.folders_list').map(function() { + return $(this).attr('id'); + }).get(); + + var current_element_index = $.inArray(current_element_id, ids); + var array_size = ids.size; + elements_to_remove = ids.slice(current_element_index+1,array_size); + + for ( var index = 0, length = elements_to_remove.length; index <= length; index++ ) { + remove = elements_to_remove[index]; + var id_to_remove = ids.indexOf(remove); + if (id_to_remove !== -1) { + ids.splice(id_to_remove, 1); + } + $('#'+remove).remove(); + if(index == length){ + elements_to_remove = []; + } + } + + $("#new_parent_id").val(new_parent_id); + + selected_parent_id = current_element.children("option:selected").val(); + $.ajax({url: "/child_folders?target_parent_id="+selected_parent_id, success: function(result){ + var $option = $("", { + "value": '', + "html": "Please Select a Sub-Folder" + }); + if(result != ''){ + options = [$option]; + $.each(result, function (key, value) { + options.push($option.clone().val(key).html(value)); + }); + + new_length = $(".folders_list").length + 1; + new_select_id = "folders_list_" + new_length; + + $('', { + name: 'child_folders_list', + id: new_select_id, + class: 'folders_list', + append: options + }).appendTo('#child_folders'); + + $('#'+ new_select_id).on('change', function(e){ + fetch_child_folder($(this)); + }); + } + }}); + }; + + $('#folders_list_1').on('change', function(e){ + fetch_child_folder($(this)); + }); + }); + + diff --git a/app/views/droom/folders/move_folder.html.haml b/app/views/droom/folders/move_folder.html.haml new file mode 100644 index 000000000..03ad2ec3d --- /dev/null +++ b/app/views/droom/folders/move_folder.html.haml @@ -0,0 +1,3 @@ +#folder + = render 'droom/shared/popup_header', title: t(:move_folder) + = render :partial => 'move_folder' \ No newline at end of file diff --git a/app/views/droom/group_invitation_mailer/send_invitation.html.haml b/app/views/droom/group_invitation_mailer/send_invitation.html.haml new file mode 100644 index 000000000..100fade08 --- /dev/null +++ b/app/views/droom/group_invitation_mailer/send_invitation.html.haml @@ -0,0 +1,31 @@ +- timezone = @user.timezone.present? ? @user.timezone : 'Hong Kong' +%p + Dear + = @user.formal_name + ',' +%p + You are invited to join following event from Croucher Foundation. +%p + %strong + = @event.name + %br/ + DateTime: + = "#{datetime_by_timezone(@event.start, 'date', @user).strftime('%b %d, %Y')}, #{l(datetime_by_timezone(@event.start, 'time', @user), :format => :natural)} (#{timezone} Time)" + %br/ + Place: + = @event.venue.definite_name +%p + Please click below link to view the event detail: +%p + = link_to "Click here to view the event detail", event_url(@event) +%p + Please don’t hesitate to contact me if you have any difficulties accessing the site or finding the information + you need. If anything goes wrong or could be improved, feel free also to contact our web builder + directly: his name is Aung Kyaw Phyo and he can be reached at + = mail_to("akp@binarylab.io") + "." + +%p + Regards, + %br + David Foster + %br + = link_to "Croucher Foundation", "http://www.croucher.org.hk/", style: "color: black;" diff --git a/app/views/droom/group_permissions/_action_menu.html.haml b/app/views/droom/group_permissions/_action_menu.html.haml new file mode 100644 index 000000000..8601edb99 --- /dev/null +++ b/app/views/droom/group_permissions/_action_menu.html.haml @@ -0,0 +1,16 @@ +- group_permission ||= @group_permission +- if group_permission.present? + - permission ||= group_permission.permission + - group ||= group_permission.group + - linkid ||= "#{group.slug}_#{permission.slug.gsub('.', '_')}" + +- params = { :group_permission => {:permission_id => permission.id, group_id: group.id}, :linkid => linkid } + +.menu{:data => {:for => "#{linkid}"}} + %ul.actions + %li + = link_to t(:all_permissions), upsert_group_group_permissions_url(group, params.merge(:classname => 'yes')), :id => linkid, :class => 'all', :method => :post, :data => {:action => "update_content", :replaced => "##{linkid}"} + %li + = link_to t(:read_permission), upsert_group_group_permissions_url(group, params.merge(:read_only => true, :classname => 'read')), :id => linkid, :class => 'read_only', :method => :post, :data => {:action => "update_content", :replaced => "##{linkid}"} + %li + = link_to t(:no_permission), delete_group_group_permissions_url(group, params.merge(:classname => 'no')), :id => linkid, :class => 'delete', :method => :delete, :data => {:action => "update_content", :replaced => "##{linkid}"} diff --git a/app/views/droom/group_permissions/_toggle.html.haml b/app/views/droom/group_permissions/_toggle.html.haml index 32f0dcdc0..c6ebadcc1 100644 --- a/app/views/droom/group_permissions/_toggle.html.haml +++ b/app/views/droom/group_permissions/_toggle.html.haml @@ -1,6 +1,7 @@ - group_permission ||= @group_permission - group ||= @group - permission ||= @permission +- read_permission ||= @read_permission - unless group_permission - if @group_permissions @@ -12,7 +13,13 @@ - permission ||= group_permission.permission - linkid = "#{group.slug}_#{permission.slug.gsub('.', '_')}" -- if group_permission && !group_permission.destroyed? - = link_to t(:disallow), group_group_permission_url(group, group_permission), :id => linkid, :class => "yes", :method => :delete, :data => {:action => "replace", :replaced => "##{linkid}"} -- else - = link_to t(:allow), group_group_permissions_url(group, :group_permission => {:permission_id => permission.id}), :id => linkid, :class => "no", :method => :post, :data => {:action => "replace", :replaced => "##{linkid}"} +-if read_permission.present? + - classname = permission.define_permission_color(group_permission, group, read_permission) + = link_to t(:disallow), "#", :class => "#{classname}", :data => { :menu => "#{linkid}" } + = render :partial => "droom/group_permissions/action_menu", :locals => { :group_permission => group_permission, :group => group, :permission => permission, :read_permission => read_permission, :linkid => linkid } + +-else + - if group_permission && !group_permission.destroyed? + = link_to t(:disallow), group_group_permission_url(group, group_permission), :id => linkid, :class => "yes", :method => :delete, :data => {:action => "replace", :replaced => "##{linkid}"} + - else + = link_to t(:allow), group_group_permissions_url(group, :group_permission => {:permission_id => permission.id}), :id => linkid, :class => "no", :method => :post, :data => {:action => "replace", :replaced => "##{linkid}"} diff --git a/app/views/droom/groups/_users.html.haml b/app/views/droom/groups/_users.html.haml index 03eff5b9d..d0fb8f3d9 100644 --- a/app/views/droom/groups/_users.html.haml +++ b/app/views/droom/groups/_users.html.haml @@ -3,6 +3,6 @@ .paginated %ul.people - = render partial: "droom/users/show/listed", collection: users + = render partial: "droom/users/show/listed", collection: users, locals: {group: group} .pagination.sliding< = paginate(users, params: {controller: 'groups', action: "show", id: group.id, format: :js}) \ No newline at end of file diff --git a/app/views/droom/memberships/_form.html.haml b/app/views/droom/memberships/_form.html.haml index 7404bbb52..10f2d0719 100644 --- a/app/views/droom/memberships/_form.html.haml +++ b/app/views/droom/memberships/_form.html.haml @@ -10,7 +10,7 @@ - user = Droom::User.new = form_for [@group, @membership], :html => {:class => 'edit user'} do |f| = f.fields_for :user, user do |uf| - = render partial: "droom/users/edit/user_fields", locals: {f: uf, user: user} + = render partial: "droom/users/edit/user_fields", locals: {f: uf, user: user, timezone_id: "membership_timezone"} %p.admin = uf.check_box :defer_confirmation = uf.label :defer_confirmation @@ -22,5 +22,5 @@ #choose_user %p{style: "margin: 20px"} The person-picker will be here shortly. - You can also add people to groups on the - = link_to('user admin page', admin_users_url) + '.' \ No newline at end of file + You can also add people to groups on the + = link_to('user admin page', admin_users_url) + '.' diff --git a/app/views/droom/organisations/_action_menu.html.haml b/app/views/droom/organisations/_action_menu.html.haml index bf14b6a07..a7798c676 100644 --- a/app/views/droom/organisations/_action_menu.html.haml +++ b/app/views/droom/organisations/_action_menu.html.haml @@ -8,6 +8,6 @@ %li = link_to t(:add_user), droom.new_organisation_user_url(organisation), :class => 'add', :data => {:action => "popup", :affected => "#organisation_#{organisation.id}"} %li - = link_to t(:subsume_organisation), droom.edit_organisation_url(organisation, view: "subsume"), :class => 'delete', :data => {:action => "popup", :removed => "#organisation_#{organisation.id}"} + = link_to t(:subsume_organisation), droom.edit_organisation_url(organisation, view: "subsume"), :class => 'delete', :data => {:action => "popup", :affected => "#organisations"} %li = link_to t(:delete_organisation), droom.organisation_url(organisation), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#organisation_#{organisation.id}", :confirm => t(:confirm_delete_organisation, :name => organisation.name)} diff --git a/app/views/droom/organisations/_organisations.html.haml b/app/views/droom/organisations/_organisations.html.haml index b84f12073..d50d78b2f 100644 --- a/app/views/droom/organisations/_organisations.html.haml +++ b/app/views/droom/organisations/_organisations.html.haml @@ -4,7 +4,7 @@ - if @organisations.any? - %div{class: cssclass, data: {refreshing: true, url: "#{droom.organisations_path}.js"}} + %div#organisations{class: cssclass, data: {refreshing: true, url: "#{droom.organisations_path(page: @page, per_page: @show)}.js"}} = render partial: "droom/organisations/show/#{view}", collection: @organisations .pagination.lower diff --git a/app/views/droom/organisations/edit/_subsume.html.haml b/app/views/droom/organisations/edit/_subsume.html.haml index 9c9f1e42e..650804481 100644 --- a/app/views/droom/organisations/edit/_subsume.html.haml +++ b/app/views/droom/organisations/edit/_subsume.html.haml @@ -4,17 +4,19 @@ #edit_organisation = render 'droom/shared/popup_header', title: title - = form_for [droom, organisation], url: merge_organisation_url(organisation), :html => {:class => 'edit organisation'} do |f| + = form_for [droom, organisation], url: merge_organisation_url(organisation), method: 'put', html: {:class => 'edit organisation'} do |f| %p - You are about to reassign all the users, assets and history of + You are about to transfer all the users, assets and history of %strong = organisation.name - to the organisation you choose below. Please be very careful: the subsumed organisation will vanish completely. + into the organisation you choose below. Please be careful: the + = organisation.name + organisation will vanish completely and its users will have a new affiliation. %p.organisation = f.label :other_id - = f.select :other_id, Droom::Organisation.for_selection(true) + = f.select :other_id, Droom::Organisation.for_selection_with_owner(true) .buttons = f.submit t(:subsume_organisation) diff --git a/app/views/droom/pages/edit/_form.html.haml b/app/views/droom/pages/edit/_form.html.haml index 84874f975..80c274503 100644 --- a/app/views/droom/pages/edit/_form.html.haml +++ b/app/views/droom/pages/edit/_form.html.haml @@ -86,3 +86,6 @@ %svg.prefix %use{"xlink:href" => "#reuse_symbol"} recycle button to choose a previously uploaded asset. + + += render "droom/shared/ed_symbols" diff --git a/app/views/droom/scraps/index.html.haml b/app/views/droom/scraps/index.html.haml index 595db1f6f..49cc14ccd 100644 --- a/app/views/droom/scraps/index.html.haml +++ b/app/views/droom/scraps/index.html.haml @@ -4,5 +4,8 @@ - content_for :standfirst do = render :partial => "noticeboard_introduction" -= render :partial => "noticeboard" + - if can? :create, Droom::Scrap + %p.admin + = link_to t(:add_notice), droom.new_scrap_url, :class => "add", :remote => true, :data => {:action => "popup", :type => "html", :affected => "#noticeboard"} += render :partial => "noticeboard" diff --git a/app/views/droom/scraps/notices/_image.html.haml b/app/views/droom/scraps/notices/_image.html.haml index e16048e8f..1a527a9bf 100644 --- a/app/views/droom/scraps/notices/_image.html.haml +++ b/app/views/droom/scraps/notices/_image.html.haml @@ -1,8 +1,7 @@ -- if notice.url? - = link_to notice.url_with_protocol do - %img{src: notice.image.url(:notice)} -- else - %img{src: notice.image.url(:notice)} - .content + - if notice.url? + = link_to notice.url_with_protocol do + %img{src: notice.image.url(:notice)} + - else + %img{src: notice.image.url(:notice)} = render "droom/scraps/notices/body", notice: notice, caption_limit: 300 \ No newline at end of file diff --git a/app/views/droom/services/_services.html.haml b/app/views/droom/services/_services.html.haml index 548481304..24bf6cfc5 100644 --- a/app/views/droom/services/_services.html.haml +++ b/app/views/droom/services/_services.html.haml @@ -6,20 +6,23 @@ = link_to t(:add_service), new_service_url, :class => 'add', :data => {:action => 'popup', :affected => '#services'} - if can?(:create, Droom::Group) = link_to t(:add_group), new_group_url, :class => 'add', :data => {:action => 'popup', :affected => '#services'} - + - @services.each do |service| - - width = service.permissions.count + - read_permissions = service.permissions.select{ |p| p.name.include?('.read')} + - width = service.permissions.count - read_permissions.length %th.d{:colspan => width > 1 ? width : 1, :class => "s_#{service.id}"} %h4 = service.name = action_menulink(service, :class => "small") = action_menu(service) - + %tr.rotated %th.spacer - @services.each do |service| - - width = service.permissions.count + - read_permissions = service.permissions.select{ |p| p.name.include?('.read')} + - width = service.permissions.count - read_permissions.length - service.permissions.each_with_index do |perm, i| + - next if perm.name.include?('.read') %td{:class => "s_#{service.id} p_#{perm.id} #{'d' if i == width-1}".strip} = action_menulink(perm, :class => "small") %span.rotated @@ -32,12 +35,17 @@ %td.name = truncate(group.name, :length => 16) - @services.each do |service| - - width = service.permissions.count + - read_permissions = service.permissions.select{ |p| p.name.include?('.read') } + - width = service.permissions.count - read_permissions.length - service.permissions.each_with_index do |perm, i| + + - next if perm.name.include?('.read') + - read_permission = perm.get_read_permission + %td.toggle{:class => "s_#{service.id} p_#{perm.id} #{'d' if i == width-1}".strip} - = render :partial => 'droom/group_permissions/toggle', :locals => {:group => group, :permission => perm} + = render :partial => 'droom/group_permissions/toggle', :locals => {:group => group, :permission => perm, :read_permission => read_permission} - if @services.empty? .everyone %p - No services are defined. \ No newline at end of file + No services are defined. diff --git a/app/views/droom/shared/_ed_symbols.html.haml b/app/views/droom/shared/_ed_symbols.html.haml new file mode 100644 index 000000000..8dea34107 --- /dev/null +++ b/app/views/droom/shared/_ed_symbols.html.haml @@ -0,0 +1,125 @@ +#svg_holder + %svg{xmlns: "http://www.w3.org/2000/svg", style: "display: none;"} + // Ed + %symbol#save_symbol{viewBox: "0 0 48 48"} + %path{d: "M13,28.4c-0.6-0.6-0.6-1.6,0-2.2l9.9-9.9c0.5-0.6,1.6-0.6,2.2,0l10.1,9.9c0.6,0.6,0.6,1.6,0,2.2c-0.3,0.3-0.8,0.4-1.2,0.4 c-0.5,0-0.9-0.1-1.2-0.4l-7.2-7.2l0,14.8c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6l0-14.8l-7.2,7.2C14.6,29,13.6,29,13,28.4z M0.3,23.9c0-6.3,2.5-12.3,7-16.8c4.5-4.3,10.5-6.8,16.8-6.8c6.5,0,12.5,2.4,17,7c4.3,4.5,6.8,10.5,6.8,16.9 c-0.2,6.3-2.6,12.3-7.2,16.8c-4.4,4.3-10.4,6.8-16.7,6.8c-6.4,0-12.4-2.5-16.9-7C2.7,36.2,0.3,30.2,0.3,23.9z M10.6,8.5l26.9,0 c-3.8-3.2-8.5-5-13.5-5S14.3,5.3,10.6,8.5z M3.4,24.1c0,5.5,2.2,10.7,6,14.6c3.8,4,9,6.1,14.6,6.1c5.4,0,10.6-2.2,14.4-6 c7.5-7.4,8.1-19,1.9-27.1c-0.1,0.1-0.3,0.1-0.5,0.1l-31.6,0c-0.2,0-0.4-0.1-0.5-0.1C5,15.2,3.5,19.5,3.4,24.1z"} + %symbol#save_button_symbol{viewBox: "0 0 48 48"} + %path{d: "M3.6,11.8c-2.2,3.6-3.3,7.8-3.3,12.1c0,6.3,2.4,12.3,6.8,16.9c4.5,4.5,10.5,7,16.9,7c6.3,0,12.3-2.5,16.7-6.8 c4.6-4.5,7-10.5,7.2-16.8c0-4.5-1.2-8.7-3.4-12.4L3.6,11.8z M35.2,28.4c-0.3,0.3-0.8,0.4-1.2,0.4c-0.5,0-0.9-0.1-1.2-0.4l-7.2-7.2 l0,14.8c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6l0-14.8l-7.2,7.2c-0.6,0.6-1.6,0.6-2.2,0c-0.6-0.6-0.6-1.6,0-2.2l9.9-9.9 c0.5-0.6,1.6-0.6,2.2,0l10.1,9.9C35.8,26.8,35.8,27.8,35.2,28.4z M6,8.5C6.4,8,6.8,7.6,7.3,7.1c4.5-4.3,10.5-6.8,16.8-6.8 c6.5,0,12.5,2.4,17,7c0.4,0.4,0.7,0.8,1.1,1.2L6,8.5z"} + %symbol#revert_symbol{viewBox: "0 0 48 48"} + %path{d: "M39.9,8.5C35.6,4.3,30,1.9,24,1.9c-5.9,0-11.5,2.3-15.6,6.4C4.1,12.5,1.8,18.1,1.7,24c0,6,2.3,11.6,6.5,15.8 c4.2,4.3,9.8,6.6,15.9,6.6c5.9,0,11.5-2.3,15.6-6.4C48.4,31.4,48.5,17.3,39.9,8.5z M37.5,37.9c-3.6,3.6-8.4,5.6-13.5,5.6 c-5.2,0-10.1-2-13.7-5.7c-3.6-3.7-5.6-8.5-5.6-13.7c0-5.1,2.1-10,5.7-13.6c3.6-3.6,8.4-5.6,13.5-5.6c5.2,0,10.1,2,13.7,5.7 C45.2,18.2,45.1,30.4,37.5,37.9z M37.4,24.2c0,0.8-0.7,1.5-1.5,1.5H12.2c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h23.6 C36.7,22.7,37.3,23.3,37.4,24.2z"} + %symbol#revert_button_symbol{viewBox: "0 0 48 48"} + %path{d: "M39.9,8.5C35.6,4.3,30,1.9,24,1.9c-5.9,0-11.5,2.3-15.6,6.4C4.1,12.5,1.8,18.1,1.7,24c0,6,2.3,11.6,6.5,15.8 c4.2,4.3,9.8,6.6,15.9,6.6c5.9,0,11.5-2.3,15.6-6.4C48.4,31.4,48.5,17.3,39.9,8.5z M37.4,24.2c0,0.8-0.7,1.5-1.5,1.5H12.2 c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h23.6C36.7,22.7,37.3,23.3,37.4,24.2z"} + + %symbol#download_symbol{viewBox: "0 0 48 48"} + %path{d: "M35.2,19.7c0.6,0.6,0.6,1.6,0,2.2l-9.9,9.9c-0.5,0.6-1.6,0.6-2.2,0L13,21.9c-0.6-0.6-0.6-1.6,0-2.2c0.3-0.3,0.8-0.4,1.2-0.4 c0.5,0,0.9,0.1,1.2,0.4l7.2,7.2V12.1c0-0.9,0.7-1.6,1.6-1.6c0.9,0,1.6,0.7,1.6,1.6v14.8l7.2-7.2C33.6,19.1,34.6,19.1,35.2,19.7z M47.9,24.2c0,6.3-2.5,12.3-7,16.8c-4.5,4.3-10.5,6.8-16.8,6.8c-6.5,0-12.5-2.4-17-7c-4.3-4.5-6.8-10.5-6.8-16.9 c0.2-6.3,2.6-12.3,7.2-16.8c4.4-4.3,10.4-6.8,16.7-6.8c6.4,0,12.4,2.5,16.9,7C45.5,11.9,47.9,17.9,47.9,24.2z M37.6,39.6H10.7 c3.8,3.2,8.5,5,13.5,5S33.9,42.8,37.6,39.6z M44.8,24c0-5.5-2.2-10.7-6-14.6c-3.8-4-9-6.1-14.6-6.1c-5.4,0-10.6,2.2-14.4,6 c-7.5,7.4-8.1,19-1.9,27.1c0.1-0.1,0.3-0.1,0.5-0.1H40c0.2,0,0.4,0.1,0.5,0.1C43.2,32.9,44.7,28.6,44.8,24z"} + %symbol#download_button_symbol{viewBox: "0 0 48 48"} + %path{d: "M44.6,36.3c2.2-3.6,3.3-7.8,3.3-12.1c0-6.3-2.4-12.3-6.8-16.9c-4.5-4.5-10.5-7-16.9-7c-6.3,0-12.3,2.5-16.7,6.8 c-4.6,4.5-7,10.5-7.2,16.8c0,4.5,1.2,8.7,3.4,12.4H44.6z M13,19.7c0.3-0.3,0.8-0.4,1.2-0.4c0.5,0,0.9,0.1,1.2,0.4l7.2,7.2V12.1 c0-0.9,0.7-1.6,1.6-1.6c0.9,0,1.6,0.7,1.6,1.6v14.8l7.2-7.2c0.6-0.6,1.6-0.6,2.2,0c0.6,0.6,0.6,1.6,0,2.2l-9.9,9.9 c-0.5,0.6-1.6,0.6-2.2,0L13,21.9C12.4,21.3,12.4,20.3,13,19.7z M42.2,39.6c-0.4,0.5-0.8,0.9-1.3,1.4c-4.5,4.3-10.5,6.8-16.8,6.8 c-6.5,0-12.5-2.4-17-7C6.7,40.4,6.4,40,6,39.6H42.2z"} + + %symbol#insert_symbol{viewBox: "0 0 48 48"} + %polygon.fg{points: "39.7,22.7 25.5,22.7 25.5,8.5 22.9,8.5 22.9,22.7 8.7,22.7 8.7,25.3 22.9,25.3 22.9,39.5 25.5,39.5 25.5,25.3 39.7,25.3"} + + %symbol#image_symbol{viewBox: "0 0 100 100"} + %path{d: "M50,0.8C22.5,1,0.6,23.2,0.8,50.3c0,5,0.9,10,2.3,14.5c0,0.7,0.2,1.3,0.7,1.9c6.9,18.9,25,32.5,46.3,32.5h0.3 C63.5,99.1,75.8,94,85,84.6s14.3-21.8,14.2-34.9C99,22.8,77,0.8,50,0.8z M69.6,16c0,2.8-2.3,5.1-5.1,5.1c-2.8,0-5.1-2.3-5.1-5.1 s2.3-5.1,5.1-5.1C67.3,10.8,69.6,13.1,69.6,16z M50.2,92.6H50c-17.9,0-33.4-11.3-39.6-27l26.1-22.6l31.7,45.6 C62.7,91.1,56.6,92.6,50.2,92.6z M80.3,80c-2.1,2.1-4.3,3.8-6.6,5.5l-15.2-22l14.1-12.1l14.1,20.3C84.9,74.6,82.8,77.3,80.3,80z M89.8,65.1l-14-20.3c-0.5-0.8-1.3-1.2-2.2-1.3c-0.9-0.1-1.8,0.1-2.5,0.8l-16.3,14L39.7,36.3c-0.5-0.8-1.3-1.2-2.2-1.3 c-0.9-0.1-1.8,0.1-2.5,0.8L8.3,58.9c-0.5-2.7-0.9-5.7-1-8.6C7.2,26.7,26.2,7.6,49.7,7.3c2.3,0,4.5,0.2,6.6,0.5 C54.2,10,53,12.8,53,16c0,6.3,5.1,11.5,11.5,11.5c6.2,0,11.3-4.9,11.5-11C86,24.2,92.4,36.2,92.6,49.8C92.7,55,91.7,60.2,89.8,65.1z"} + %symbol#image_button_symbol{viewBox: "0 0 100 100"} + %path.fg{d: "M93.9,71.8c3.4-6.7,5.3-14.2,5.3-22.1C99,23.3,76.5,0.8,49.9,0.8c-7.3,0-14.2,1.6-20.5,4.5c-0.8,0.3-1.4,0.7-2.2,1.1 c-15.9,8.2-26.7,24.8-26.6,44c0,5.4,1,10.5,2.5,15.3c0,0.2,0.1,0.4,0.2,0.7c0.1,0.4,0.3,0.8,0.4,1.2C11,86,28.9,99.2,49.8,99.2h0.3 C69.1,99.2,85.8,87.8,93.9,71.8z M64.4,8.5c4.1,0,7.4,3.3,7.4,7.4s-3.3,7.4-7.4,7.4c-4.1,0-7.4-3.3-7.4-7.4S60.3,8.5,64.4,8.5z M10.1,65.9l25.8-22.3c0.2-0.2,0.5-0.2,0.7-0.2c0.1,0,0.4,0.1,0.7,0.3l31.2,45c-5.6,2.7-11.8,4.2-18.3,4.2h-0.2 C32,92.9,16.5,81.7,10.1,65.9z M80.5,80.1c-2,2-4.2,3.8-6.5,5.4L58.6,63.3l13.2-11.5c0.2-0.2,0.5-0.2,0.7-0.2c0.1,0,0.4,0.1,0.7,0.3 l14,19.3C85.4,74.5,83.1,77.5,80.5,80.1z"} + + %symbol#video_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6 c-5.5,0-10.7-2.1-14.6-6.1s-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5s8.9-6,14.4-6c5.5,0,10.7,2.1,14.6,6.1 C46.6,17.8,46.5,30.8,38.4,38.8z M38.1,16.4c0-0.7-0.6-1.3-1.3-1.3c-8.6-0.6-17-0.6-25.6,0c-0.7,0-1.3,0.6-1.3,1.3 c-0.3,5.2-0.3,10.5,0,15.7c0,0.7,0.6,1.3,1.3,1.3c8.6,0.6,17,0.6,25.6,0c0.7,0,1.3-0.6,1.3-1.3C38.4,26.8,38.4,21.6,38.1,16.4z M29,24.6L21.3,29c-0.3,0.2-0.6,0-0.6-0.4v-8.9c0-0.3,0.3-0.6,0.6-0.4l7.7,4.4C29.3,24,29.3,24.4,29,24.6z"} + %symbol#video_button_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M41,7.4c-4.6-4.5-10.6-7-17-7c-6.3,0-12.3,2.5-16.7,6.8c-4.6,4.5-7,10.5-7.2,16.8c0,6.4,2.5,12.4,6.9,16.9 c4.5,4.6,10.5,7,17,7c6.3,0,12.3-2.5,16.7-6.8C50.1,31.9,50.2,16.8,41,7.4z M38.1,32.1c0,0.7-0.6,1.3-1.3,1.3 c-8.6,0.6-17.1,0.6-25.7,0c-0.7,0-1.3-0.6-1.3-1.3c-0.3-5.2-0.3-10.5,0-15.8c0-0.7,0.6-1.3,1.3-1.3c8.6-0.6,17.1-0.6,25.7,0 c0.7,0,1.3,0.6,1.3,1.3C38.5,21.6,38.5,26.8,38.1,32.1z M29,24.6L21.3,29c-0.3,0.2-0.6,0-0.6-0.4v-8.9c0-0.3,0.3-0.6,0.6-0.4 l7.7,4.4C29.3,24,29.3,24.4,29,24.6z"} + + %symbol#document_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6s-10.7-2.1-14.6-6.1 c-3.9-4-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5c3.9-3.9,8.9-6,14.4-6s10.7,2.1,14.6,6.1C46.6,17.8,46.5,30.8,38.4,38.8z M36.8,26.7 c-0.8-1.5-3.3-2.3-7.1-2.3H29c-1-1.2-2.1-2.4-2.8-3.3L26,20.8c-0.3-0.4-0.6-0.7-0.8-1.1c0.5-1.5,0.8-2.8,0.9-3.7 c0.2-2.5-0.1-4.1-0.9-4.9c-0.6-0.6-1.4-0.8-2.2-0.6c-0.5,0.1-1.3,0.6-1.7,1.9c-0.6,1.7-0.4,4.8,1.3,7.6c-0.7,1.8-1.7,3.9-2.8,5.9 c-2.1,0.8-3.8,1.7-5.1,2.9c-1.7,1.6-2.3,3.3-1.8,4.5c0.3,0.8,1.1,1.3,2,1.3c0.6,0,1.3-0.2,1.9-0.6c1.4-0.9,3.3-3.8,4.6-6.2 c1.5-0.5,3.3-0.8,5.3-1l1.4-0.1c1.9,2,3.5,3.1,5,3.4l0.1,0c0.3,0,0.5,0.1,0.8,0.1c1.3,0,2.3-0.5,2.8-1.4 C37.1,28.1,37.1,27.4,36.8,26.7z M34.7,27.7c-0.1,0.1-0.3,0.1-0.7,0.1c-0.1,0-0.3,0-0.5,0c-0.6-0.1-1.2-0.5-2-1 C33.5,27,34.4,27.5,34.7,27.7z M15.1,32.3c0.1-0.3,0.4-0.9,1.2-1.8c0.4-0.4,0.9-0.7,1.3-1c-0.9,1.4-1.7,2.3-2.1,2.6 C15.3,32.2,15.2,32.3,15.1,32.3z M24.2,22.2c0.1,0.1,0.2,0.2,0.3,0.3c0.5,0.6,1.1,1.3,1.7,2c-0.9,0.1-2.1,0.3-3.3,0.5 C23.3,24,23.8,23.1,24.2,22.2z M23.7,15.8c0,0.2-0.1,0.5-0.1,0.8c-0.4-1.3-0.4-2.6-0.1-3.5c0-0.1,0.1-0.2,0.1-0.3 C23.7,13.3,23.9,14.1,23.7,15.8z"} + %symbol#document_button_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M24.6,22.3c0.7,0.9,1.5,1.9,2.4,2.9h-0.1c-1.2,0.1-2.9,0.4-4.9,0.8c0.8-1.5,1.5-3.1,2.1-4.5C24.2,21.8,24.3,22.1,24.6,22.3z M23.8,14.5c0.2-3.1-0.3-3.9-0.4-4c-0.1,0-0.3,0.3-0.5,0.8c-0.5,1.4-0.2,3.4,0.5,5.3C23.6,15.7,23.8,15,23.8,14.5z M12.8,34.6 C12.8,34.6,12.8,34.6,12.8,34.6c0.4,0,0.7-0.1,1-0.4c0.7-0.5,2-2,3.3-4.2c-1.1,0.5-2,1.2-2.7,1.9C12.9,33.5,12.7,34.4,12.8,34.6z M40.7,41c-4.4,4.4-10.3,6.8-16.6,6.8c-6.5,0-12.4-2.4-16.9-7C2.7,36.3,0.3,30.4,0.3,24c0.1-6.3,2.5-12.2,7.1-16.7 C11.8,2.9,17.7,0.5,24,0.5c6.4,0,12.3,2.5,16.9,7C50.1,16.9,50,31.9,40.7,41z M38.9,27.7c-1-1.7-3.9-2.6-8.2-2.6c-0.3,0-0.6,0-1,0 c-1.2-1.3-2.4-2.8-3.4-4.1c-0.5-0.6-1-1.2-1.4-1.8v0c0.6-1.9,1-3.4,1.1-4.5c0.2-2.9-0.1-4.8-1-5.7c-0.6-0.6-1.5-0.9-2.3-0.6 c-0.6,0.2-1.4,0.7-1.9,2.1c-0.7,2.1-0.4,5.9,1.6,8.9c-0.9,2.3-2.1,4.9-3.4,7.4c-2.5,0.9-4.6,2.1-6.1,3.5c-1.9,1.9-2.7,3.7-2.1,5.1 c0.4,0.9,1.1,1.4,2.1,1.4c0.7,0,1.4-0.2,2.1-0.7c1.8-1.2,4.2-5.1,5.4-7.4c2.6-0.8,5.2-1.1,6.5-1.3c0.6,0,1.2-0.1,1.8-0.1 c2.3,2.4,4.2,3.7,5.9,4c0.4,0,0.7,0.1,1,0.1c1.4,0,2.6-0.5,3.1-1.5C39.3,29.2,39.3,28.4,38.9,27.7z M31.9,27.3 c1.3,1.1,2.3,1.7,3.2,1.9c0.2,0,0.5,0,0.6,0c0.7,0,1.1-0.2,1.2-0.4c0,0,0,0,0-0.1C36.7,28.2,35.3,27.4,31.9,27.3z"} + + %symbol#quote_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6 c-5.5,0-10.7-2.1-14.6-6.1s-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5s8.9-6,14.4-6c5.5,0,10.7,2.1,14.6,6.1 C46.6,17.8,46.5,30.8,38.4,38.8z M17.8,15.5c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2 c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C12.7,17.9,15,15.5,17.8,15.5z M30.3,15.5c3.2,0,5.7,3,5.7,7 c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C25.1,17.9,27.5,15.5,30.3,15.5z"} + %symbol#quote_button_symbol{viewBox: "0 0 48 48"} + %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M17.8,15.5c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4 c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C12.7,17.9,15,15.5,17.8,15.5z M30.3,15.5 c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6 C25.1,17.9,27.5,15.5,30.3,15.5z"} + + %symbol#close_symbol{viewBox: "0 0 48 48"} + %path{d: "M41.2,7.1c-0.6-0.6-1.7-0.6-2.3,0L24.1,21.9L9.3,6.9C8.7,6.3,7.7,6.3,7,6.9C6.4,7.5,6.4,8.6,7,9.2l14.8,15l-15,14.8 c-0.6,0.6-0.6,1.7,0,2.3c0.4,0.4,0.8,0.4,1.3,0.4c0.4,0,0.8,0,1.3-0.4l14.8-14.8l14.8,14.8c0.4,0.4,0.8,0.4,1.3,0.4s0.8,0,1.3-0.4 c0.6-0.6,0.6-1.7,0-2.3l-15-14.8L41.2,9.4C41.8,8.8,41.8,7.7,41.2,7.1z"} + + + %symbol#right_arrow_symbol{viewBox: "0 0 100 100"} + %path{d: "M76.7,47.6c1.2,1.2,1.2,3.6,0,4.8L55.9,73.1c-1.3,1.3-3.4,1.3-4.8,0c-0.7-0.6-1-1.4-1-2.3c0-0.8,0.3-1.7,1-2.3l15-15.1H25 c-1.8,0-3.3-1.6-3.3-3.3c0-1.8,1.4-3.3,3.3-3.3h41.1L51,31.6c-1.3-1.3-1.3-3.4,0-4.8s3.4-1.3,4.8,0L76.7,47.6z M100,49.7V50 c0,27.4-22.3,49.8-49.7,50C22.8,100.2,0.2,77.9,0,50C0,22.6,22.3,0.2,49.7,0c13.3-0.1,26,5,35.4,14.4l0,0 C94.7,23.8,99.9,36.3,100,49.7z M93.2,49.7c-0.1-11.6-4.7-22.3-12.9-30.6c-8.1-8-19-12.4-30.6-12.3c-23.7,0.1-43,19.6-43,43.6 c0.1,23.9,19.7,43.1,43.6,43C74,93.1,93.2,73.7,93.2,50L93.2,49.7z"} + %symbol#left_arrow_symbol{viewBox: "0 0 100 100"} + %path{d: "M23.3,52.4c-1.2-1.2-1.2-3.6,0-4.8l20.8-20.8c1.3-1.3,3.4-1.3,4.8,0c0.7,0.6,1,1.4,1,2.3c0,0.8-0.3,1.7-1,2.3l-15,15.1H75 c1.8,0,3.3,1.6,3.3,3.3c0,1.8-1.4,3.3-3.3,3.3H33.9L49,68.4c1.3,1.3,1.3,3.4,0,4.8s-3.4,1.3-4.8,0L23.3,52.4z M0,50.3V50 C0,22.6,22.3,0.2,49.7,0C77.2-0.2,99.8,22.1,100,50c0,27.4-22.3,49.8-49.7,50c-13.3,0.1-26-5-35.4-14.4l0,0 C5.3,76.2,0.1,63.7,0,50.3z M6.8,50.3c0.1,11.6,4.7,22.3,12.9,30.6c8.1,8,19,12.4,30.6,12.3c23.7-0.1,43-19.6,43-43.6 c-0.1-23.9-19.7-43.1-43.6-43C26,6.9,6.8,26.3,6.8,50L6.8,50.3z"} + %symbol#popout_symbol{viewBox: "0 0 100 100"} + %path{d: "M67.1,29.4c1.7,0,3.4,1.6,3.4,3.4l0,29.4c0,1.9-1.5,3.4-3.4,3.4c-0.9,0.1-1.7-0.3-2.4-0.9c-0.6-0.6-0.9-1.4-0.9-2.4L63.7,41 L34.7,70c-1.3,1.3-3.5,1.3-4.7,0c-1.3-1.3-1.3-3.4,0-4.7L59,36.3l-21.4,0c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4L67.1,29.4z M85.1,14.4l0.2,0.2c19.4,19.4,19.4,51,0.2,70.5c-19.3,19.6-51.1,19.8-70.9,0.2c-19.4-19.4-19.4-51-0.2-70.5C23.8,5.4,36.3,0,49.7,0 l0,0C63-0.1,75.6,5.1,85.1,14.4z M80.3,19.2C72.1,11.1,61.2,6.7,49.6,6.7c-11.4,0.1-22.2,4.6-30.3,12.9C2.6,36.4,2.7,63.8,19.7,80.8 c17,16.8,44.4,16.6,61.2-0.4c16.6-16.9,16.4-44.2-0.3-61L80.3,19.2z"} + + %symbol#first_symbol{viewBox: "0 0 48 64"} + %path{d: "M29.1,9.62a1.61,1.61,0,0,1,2.27,2.27L19.26,24,31.37,36.11a1.61,1.61,0,1,1-2.27,2.27L15.85,25.14a1.61,1.61,0,0,1,0-2.27Z"} + %symbol#last_symbol{viewBox: "0 0 48 64"} + %path{d: "M20.13,9.7A1.59,1.59,0,0,0,17.88,12l12,12-12,12a1.59,1.59,0,1,0,2.25,2.25L33.25,25.07a1.59,1.59,0,0,0,0-2.25Z"} + %symbol#prev_symbol{viewBox: "0 0 48 64"} + %path{d: "M27.91,9.62a1.61,1.61,0,1,1,2.27,2.27L18.07,24,30.18,36.11a1.61,1.61,0,1,1-2.27,2.27L14.66,25.14a1.61,1.61,0,0,1,0-2.27Z"} + %symbol#next_symbol{viewBox: "0 0 48 64"} + %path{d: "M20.13,9.7A1.59,1.59,0,0,0,17.88,12l12,12-12,12a1.59,1.59,0,1,0,2.25,2.25L33.25,25.07a1.59,1.59,0,0,0,0-2.25Z"} + + %symbol#edit_symbol{viewBox: "0 0 48 64"} + %path{d: "M11.5,32l4.4,4.4l-6.3,1.8L11.5,32z M33.4,9.1c0.1-0.1,0.2-0.1,0.3-0.1c0.1,0,0.2,0.1,0.3,0.1l4.9,4.9 c0.1,0.1,0.1,0.3,0.1,0.3c0,0.1,0,0.2-0.1,0.3l-3.6,3.6l-5.6-5.6L33.4,9.1z M27.7,14.7l5.6,5.6L18.6,35L13,29.4L27.7,14.7z M7.5,42 c0.1,0,0.3,0,0.4-0.1l10.3-3c0.5-0.1,1.1-0.4,1.5-0.9L41,16.8c0.6-0.6,1-1.5,1-2.5c0-0.9-0.3-1.8-1-2.4L36.1,7 c-1.3-1.3-3.6-1.3-4.9,0L10,28.3c-0.4,0.4-0.7,0.9-0.9,1.5l-3,10.3c-0.1,0.5,0,1.1,0.4,1.5C6.7,41.9,7.1,42,7.5,42L7.5,42z"} + + + // positioning controls for inserted assets + %symbol#left_symbol{viewBox: "0 0 48 48"} + %rect.fg{x:"13.3", y: "13.9", width: "26.6", height: "2.3"} + %rect.fg{x:"24.1", y: "18.4", width: "15.8", height: "2.3"} + %rect.fg{x:"24.1", y: "23", width: "15.8", height: "2.3"} + %rect.fg{x:"24.1", y: "27.6" , width: "15.8", height: "2.3"} + %rect.fg{x:"13.3", y: "32.2", width: "26.6", height: "2.3"} + %rect.fg{x:"8.9", y: "18.4", width: "13.4", height: "11.4"} + + %symbol#right_symbol{viewBox: "0 0 48 48"} + %rect.fg{x: "8.9", y: "13.9", width: "26.6", height: "2.3"} + %rect.fg{x: "8.9", y: "18.4", width: "15.8", height: "2.3"} + %rect.fg{x: "8.9", y: "23", width: "15.8", height: "2.3"} + %rect.fg{x: "8.9", y: "27.6", width: "15.8", height: "2.3"} + %rect.fg{x: "8.9", y: "32.2", width: "26.6", height: "2.3"} + %rect.fg{x: "26.5", y: "18.4", width: "13.4", height: "11.4"} + + %symbol#full_symbol{viewBox: "0 0 48 48"} + %rect.fg{x: "10.7", y: "13.7", width: "26.6", height: "2.3"} + %rect.fg{x: "10.7", y: "32", width: "26.6", height: "2.3"} + %rect.fg{x: "10.7", y: "18.2", width: "26.6", height: "11.4"} + + %symbol#wide_symbol{viewBox: "0 0 48 48"} + %rect.fg{x: "10.7", y: "13.7", width: "26.6", height: "2.3"} + %rect.fg{x: "10.7", y: "32", width: "26.6", height: "2.3"} + %rect.fg{x: "6.5", y: "18.2", width: "34.9", height: "11.4"} + + %symbol#hero_button{viewBox: "0 0 48 48"} + %path.bg{d: "M40.3,7.6c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.6,40.3,7.6z"} + %path.fg{d: "M4.7,36.5C8.8,42.7,15.9,46.9,24,46.9s15.2-4.2,19.3-10.4H4.7z" } + %path.fg{d: "M43.6,11.9c-4-6.6-11.3-11-19.6-11S8.4,5.3,4.4,11.9H43.6z" } + + // Toolbar buttons + %symbol#bold_button{viewBox: "0 0 48 48"} + %path.fg{d: "M26.5,18.4c0-1.6-1.3-2.4-2.8-2.4H21v5.1h2.7C25.4,21.1,26.5,20.1,26.5,18.4z"} + %path.fg{d: "M24.7,25.7H21v5.7h4c1.9,0,3.2-1.1,3.2-3C28.3,26.8,27.1,25.7,24.7,25.7z"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M25.7,35.9h-7.1 c-1.6,0-2.7-1-2.7-2.6V14c0-1.6,1.1-2.6,2.7-2.6h6.6c3.6,0,6.5,2.4,6.5,6c0,2.4-1,4-3.2,5v0.1c2.9,0.4,5.2,2.8,5.2,5.8 C33.7,33.3,30.4,35.9,25.7,35.9z"} + + %symbol#italic_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M32.1,13.9l-9.4,19.5 c-0.8,1.7-2.7,2.7-4.7,2.7c-1.9,0-2.9-1-2.1-2.7l9.4-19.5c0.8-1.7,2.7-2.7,4.7-2.7C31.9,11.2,32.9,12.2,32.1,13.9z"} + + %symbol#link_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M32.1,13.9l-9.4,19.5 c-0.8,1.7-2.7,2.7-4.7,2.7c-1.9,0-2.9-1-2.1-2.7l9.4-19.5c0.8-1.7,2.7-2.7,4.7-2.7C31.9,11.2,32.9,12.2,32.1,13.9z"} + + %symbol#ol_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M12.4,11.8h1.2c0.4,0,0.6,0.3,0.6,0.6v4.7 c0,0.4-0.3,0.6-0.6,0.6s-0.6-0.2-0.6-0.6v-4.2h-0.5c-0.4,0-0.6-0.3-0.6-0.6C11.8,12.1,12,11.8,12.4,11.8z M13.3,36.2 c-1.2,0-2-0.9-2-1.6c0-0.3,0.3-0.6,0.6-0.6c0.6,0,0.4,1,1.4,1c0.4,0,0.8-0.3,0.8-0.8c0-1.2-1.4-0.3-1.4-1.3c0-0.9,1.2-0.3,1.2-1.2 c0-0.3-0.2-0.6-0.6-0.6c-0.8,0-0.7,0.8-1.2,0.8c-0.3,0-0.5-0.3-0.5-0.6c0-0.6,0.9-1.3,1.8-1.3c1.2,0,1.8,0.9,1.8,1.5 c0,0.5-0.2,1-0.7,1.3c0.6,0.3,1,0.8,1,1.5C15.4,35.4,14.5,36.2,13.3,36.2z M14.9,26.9h-2.8c-0.4,0-0.6-0.2-0.6-0.5 c0-0.2,0.1-0.3,0.2-0.4c0.7-0.8,1.4-1.6,2.1-2.5c0.1-0.2,0.3-0.5,0.3-0.8c0-0.3-0.3-0.6-0.6-0.6c-1,0-0.5,1.3-1.3,1.3 c-0.4,0-0.6-0.3-0.6-0.6c0-1,0.9-1.9,1.9-1.9s1.8,0.7,1.8,1.7c0,1.2-1.3,2.3-2,3.2h1.5c0.4,0,0.6,0.2,0.6,0.5 C15.5,26.7,15.2,26.9,14.9,26.9z M33.9,36.1H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 S35.5,36.1,33.9,36.1z M33.9,26.9H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9S35.5,26.9,33.9,26.9z M33.9,17.7H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9S35.5,17.7,33.9,17.7z"} + + %symbol#ul_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M14.2,36.1c-1.6,0-2.9-1.3-2.9-2.9 c0-1.6,1.3-2.9,2.9-2.9c1.6,0,2.9,1.3,2.9,2.9C17.1,34.8,15.8,36.1,14.2,36.1z M14.2,26.9c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9 c1.6,0,2.9,1.3,2.9,2.9S15.8,26.9,14.2,26.9z M14.2,17.7c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9c1.6,0,2.9,1.3,2.9,2.9 C17.1,16.4,15.8,17.7,14.2,17.7z M33.9,36.1H21.8c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 C36.8,34.8,35.5,36.1,33.9,36.1z M33.9,26.9H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 S35.5,26.9,33.9,26.9z M33.9,17.7H21.8c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 C36.8,16.4,35.5,17.7,33.9,17.7z"} + + %symbol#h1_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M34.6,33.7 c0,1.7-1.1,2.7-2.6,2.7s-2.6-1-2.6-2.7v-7.5H18.7v7.5c0,1.7-1.1,2.7-2.6,2.7s-2.6-1-2.6-2.7V14.3c0-1.7,1.1-2.7,2.6-2.7 s2.6,1,2.6,2.7v7.3h10.8v-7.3c0-1.7,1.1-2.7,2.6-2.7s2.6,1,2.6,2.7V33.7z"} + + %symbol#h2_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M33.2,33.3 c0,1.6-1,2.7-2.5,2.7s-2.5-1.1-2.5-2.7v-8.6c0-2.3-1.4-3.4-3.2-3.4c-2,0-3.2,1.5-3.2,3.4v8.6c0,1.6-1,2.7-2.5,2.7s-2.5-1.1-2.5-2.7 V12.2c0-1.6,1-2.7,2.5-2.7s2.5,1.1,2.5,2.7v6.7h0.1c1-1.3,3-1.7,4.5-1.7c3.9,0,6.8,2.5,6.8,6.7V33.3z"} + + %symbol#anchor_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M23.2,36.1 c-1.6,1.6-3.7,2.6-6,2.5c-2.2-0.1-4.1-0.9-5.6-2.5c-3-3.2-2.8-8.2,0.3-11.3l4.9-4.9c0.1-0.1,0.2-0.2,0.3-0.2c0.4-0.4,1-0.8,1.6-1.1 c1.5-0.8,3.2-1.1,4.9-0.9c0.4,0,0.8,0.1,1.2,0.2c0.4,0.1,0.8,0.2,1.1,0.4c0.8,0.4,1.5,0.9,2.2,1.5c0.8,0.8,0.7,2.2-0.1,3l-1,1 c-0.1,0.2-0.3,0.3-0.5,0.4c-0.2-0.7-0.6-1.4-1.1-1.9c-0.6-0.6-1.4-1-2.2-1.2c-0.3-0.1-0.6-0.1-0.9-0.1h-0.2 c-0.4,0.1-0.7,0.1-1.1,0.2c-0.7,0.2-1.4,0.6-1.9,1.1l-4.9,4.9c-0.7,0.7-1.3,1.7-1.4,2.8c-0.1,1.2,0.3,2.5,1.2,3.4 c0.9,0.9,2.1,1.4,3.4,1.2c1.1-0.1,2-0.6,2.8-1.4l0.9-0.9c0.5-0.5,1.2-0.6,1.8-0.4c0.4,0.1,0.8,0.2,1.2,0.2c0.4,0.1,0.7,0.1,1.1,0.1 c0.4,0,0.8-0.1,1.2-0.1L23.2,36.1z M36.1,23.3l-4.9,4.9c-0.1,0.1-0.2,0.2-0.3,0.2c-0.4,0.4-1,0.9-1.6,1.2c-1.2,0.6-2.5,0.9-3.8,0.9 c-0.4,0-0.7,0-1.1-0.1c-0.4,0-0.8-0.1-1.2-0.2c-0.4-0.1-0.8-0.2-1.1-0.4c-0.8-0.4-1.5-0.9-2.2-1.5c-0.8-0.8-0.8-2.2,0.1-3l1-1 c0.2-0.2,0.4-0.3,0.6-0.4c0.2,0.7,0.6,1.4,1.1,1.9c0.6,0.6,1.4,1,2.2,1.2c0.3,0.1,0.6,0.1,0.9,0.1h0.1c0.4-0.1,0.7-0.1,1.1-0.2 c0.7-0.2,1.4-0.6,1.9-1.1l4.9-4.9c1.6-1.6,1.9-4.2,0.4-5.9c-0.9-1.1-2.2-1.7-3.7-1.5c-1.1,0.1-2.1,0.6-2.8,1.4l-1,1 c-0.4,0.4-0.9,0.5-1.4,0.3c-0.4-0.1-0.8-0.2-1.2-0.2c-0.9-0.1-1.8-0.1-2.7,0.1l3.7-3.7c1.6-1.6,3.7-2.6,6-2.5 c2.2,0.1,4.1,0.9,5.6,2.5C39.5,15,39.3,20.1,36.1,23.3z"} + + %symbol#clear_button{viewBox: "0 0 48 48"} + %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M26.8,24.1c2.8,2.8,5.7,5.7,8.5,8.5 c0.7,0.7,0.8,1.8,0.1,2.5c-0.7,0.8-1.8,0.8-2.6,0.1c-0.1-0.1-0.1-0.1-0.2-0.2c-2.8-2.8-5.5-5.5-8.4-8.4c-0.1-0.1-0.1-0.1-0.2-0.2 c-0.1,0.1-0.1,0.1-0.2,0.2c-2.8,2.8-5.7,5.7-8.5,8.5c-0.8,0.8-2,0.8-2.7-0.1c-0.6-0.7-0.5-1.7,0.1-2.4c0.5-0.5,1-1,1.5-1.5 c2.4-2.4,4.8-4.8,7.1-7.1c0-0.1,0.1-0.1,0.2-0.1c-0.1-0.1-0.1-0.2-0.2-0.2c-2.8-2.8-5.7-5.7-8.5-8.5c-0.7-0.7-0.8-1.8-0.1-2.5 c0.7-0.8,1.8-0.8,2.6-0.1c0.1,0.1,0.1,0.1,0.2,0.2c2.8,2.8,5.5,5.5,8.4,8.4c0.1,0.1,0.1,0.1,0.2,0.3c0.1-0.1,0.1-0.2,0.2-0.2 c2.8-2.8,5.7-5.7,8.5-8.5c0.7-0.9,2-0.8,2.7,0c0.6,0.7,0.5,1.7-0.1,2.4c-0.2,0.2-0.6,0.6-0.8,0.8c-2.6,2.6-5.2,5.2-7.8,7.8 c-0.1,0-0.1,0.1-0.2,0.1C26.7,24,26.8,24.1,26.8,24.1z"} diff --git a/app/views/droom/shared/_symbols.html.haml b/app/views/droom/shared/_symbols.html.haml index c2b79fcb2..dfcc25d14 100644 --- a/app/views/droom/shared/_symbols.html.haml +++ b/app/views/droom/shared/_symbols.html.haml @@ -39,8 +39,7 @@ %path{d: "M24.1,1.9c-12.3,0-22.2,9.9-22.2,22.2c0,12.3,9.9,22.2,22.2,22.2c12.3,0,22.2-9.9,22.2-22.2C46.3,11.8,36.3,1.9,24.1,1.9z M35,19.3L21.2,33.1c-0.3,0.3-0.7,0.4-1.1,0.4c-0.4,0-0.8-0.1-1.1-0.4l-5.9-5.9c-0.1-0.2-0.4-0.6-0.4-1.1c0-0.6,0.4-1.1,0.9-1.3 c0.6-0.2,1.3,0,1.7,0.4l4.9,4.9L33,17.2c0.4-0.4,1-0.6,1.6-0.4c0.6,0.2,0.9,0.7,0.9,1.4C35.5,18.7,35.1,19.1,35,19.3z"} %symbol#cross_symbol{viewBox: "0 0 48 48"} - %path{d: "M24.1,44.8c-11.4,0-20.7-9.3-20.7-20.7S12.7,3.4,24.1,3.4s20.7,9.3,20.7,20.7S35.5,44.8,24.1,44.8z M24.1,1.9 c-12.3,0-22.2,9.9-22.2,22.2s9.9,22.2,22.2,22.2s22.2-9.9,22.2-22.2S36.3,1.9,24.1,1.9z"} - %path{d: "M32.2,16c-0.3-0.3-0.8-0.3-1.1,0L24.1,23l-7-7.1c-0.3-0.3-0.8-0.3-1.1,0c-0.3,0.3-0.3,0.8,0,1.1l7,7.1l-7.1,7 c-0.3,0.3-0.3,0.8,0,1.1c0.2,0.2,0.4,0.2,0.6,0.2s0.4,0,0.6-0.2l7-7l7,7c0.2,0.2,0.4,0.2,0.6,0.2s0.4,0,0.6-0.2 c0.3-0.3,0.3-0.8,0-1.1l-7.1-7l7-7C32.5,16.8,32.5,16.3,32.2,16z"} + %path{d: "M7.5,7C16.8-2,31.8-1.9,41,7.5c9,9.3,9,24.1-0.2,33.3c-0.1,0.1-0.1,0.1-0.2,0.2 c-9.1,9-24.1,9-33.3-0.2c-0.1-0.1-0.1-0.1-0.3-0.3C-1.9,31.3-1.9,16.2,7.5,7z M43.7,18.3c-1-3.5-2.8-6.6-5.5-9.2 C30.3,1.7,18,1.6,10.1,8.9c-7.8,7.3-8.8,19.5-1.9,27.9c3.2,3.9,7.2,6.4,12.2,7.2c6.9,1.1,12.9-0.8,18.1-5.5c0,0,0,0,0.1-0.1 c0,0,0,0,0.1-0.1c2.7-2.7,4.4-5.8,5.3-9.4C44.7,25.5,44.6,21.9,43.7,18.3z M34.1,14c0.5,0.6,0.4,1.5-0.1,2.1 c-0.2,0.2-0.5,0.5-0.7,0.7c-2.3,2.3-4.6,4.6-6.9,6.9c-0.1,0-0.1,0.1-0.2,0.1c0.1,0.1,0.2,0.2,0.2,0.2c2.5,2.5,5,5,7.5,7.5 c0.6,0.6,0.7,1.6,0.1,2.2c-0.6,0.7-1.6,0.7-2.3,0.1c-0.1-0.1-0.1-0.1-0.2-0.2c-2.5-2.5-4.9-4.9-7.4-7.4c-0.1-0.1-0.1-0.1-0.2-0.2 c-0.1,0.1-0.1,0.1-0.2,0.2c-2.5,2.5-5,5-7.5,7.5c-0.7,0.7-1.8,0.7-2.4-0.1c-0.5-0.6-0.4-1.5,0.1-2.1c0.4-0.4,0.9-0.9,1.3-1.3 c2.1-2.1,4.2-4.2,6.3-6.3c0-0.1,0.1-0.1,0.2-0.1c-0.1-0.1-0.1-0.2-0.2-0.2c-2.5-2.5-5-5-7.5-7.5c-0.6-0.6-0.7-1.6-0.1-2.2 c0.6-0.7,1.6-0.7,2.3-0.1c0.1,0.1,0.1,0.1,0.2,0.2c2.5,2.5,4.9,4.9,7.4,7.4c0.1,0.1,0.1,0.1,0.2,0.4c0.1-0.1,0.1-0.2,0.2-0.2 c2.5-2.5,5-5,7.5-7.5C32.3,13.3,33.5,13.4,34.1,14z"} %symbol#cross_button_symbol{viewBox: "0 0 48 48"} %path{d: "M24.1,1.9c-12.3,0-22.2,9.9-22.2,22.2c0,12.3,9.9,22.2,22.2,22.2c12.3,0,22.2-9.9,22.2-22.2C46.3,11.8,36.3,1.9,24.1,1.9z M32.5,32.4c-0.3,0.3-0.6,0.3-0.8,0.3c-0.2,0-0.5,0-0.8-0.3l-6.8-6.8l-6.8,6.8c-0.3,0.3-0.6,0.3-0.8,0.3s-0.5,0-0.8-0.3 c-0.4-0.4-0.4-1.1,0-1.5l6.9-6.8l-6.8-6.9c-0.4-0.4-0.4-1.1,0-1.5c0.4-0.4,1.1-0.4,1.5,0l6.8,6.9l6.8-6.8c0.4-0.4,1.1-0.4,1.5,0 s0.4,1.1,0,1.5l-6.8,6.8l6.9,6.8C32.9,31.3,32.9,32,32.5,32.4z"} @@ -108,129 +107,12 @@ %path{d: "M50,6.3C25.3,6.3,5.2,26.4,5,51C4.9,63,9.5,74.3,18,82.8c8.4,8.6,19.7,13.3,31.7,13.4H50c24.7,0,44.8-20.1,45-44.7 C95.2,26.8,75.1,6.5,50,6.3z M49.8,90.2c-9.5-0.1-18.4-3.5-25.4-9.7v-0.1v-0.5c0-4.7,1.4-9.1,4-13c2.4-3.4,5.6-6.1,9.3-7.8 c3.3,2.8,7.6,4.5,12.3,4.5s9-1.7,12.3-4.5c3.7,1.7,6.9,4.4,9.3,7.8c2.6,3.8,4,8.3,4,13v0.5C68.7,86.4,59.7,90.1,49.8,90.2L49.8,90.2 L49.8,90.2z M50,58c-7.3,0-13.3-6-13.3-13.3s6-13.4,13.3-13.4s13.3,6,13.3,13.3S57.3,58,50,58z M81,74.8c-0.7-4-2.3-7.7-4.6-11.1 c-2.6-3.8-6.1-6.9-10.2-9c1.8-2.9,2.9-6.4,2.9-10c0-10.5-8.6-19.1-19.1-19.1s-19.1,8.6-19.1,19.1c0,3.7,1,7.1,2.9,10 c-4,2.1-7.5,5.2-10.2,9c-2.3,3.4-3.9,7.1-4.6,11.1c-5.2-6.8-8-15.1-7.9-23.8c0.1-21.3,17.6-38.7,39.2-38.7 C71.8,12.4,89.1,30,89,51.5C88.9,60.2,85.9,68.2,81,74.8z"} %symbol#email_symbol{viewBox: "0 0 100 100"} %path{d: "M50.5,6.2c24.8,0.4,44.7,20.6,44.7,45.4C95,76.1,74.7,96.3,49.9,96.3c-0.2,0-0.4,0-0.6,0c-24.6-0.2-44.5-20.2-44.5-45 c0-0.2,0-0.4,0-0.8C5,26.1,25.5,5.8,50.5,6.2z M85.4,67.5c4.8-10.9,4.8-21.8,0-32.6c-6.1,5.5-12,10.9-17.9,16.2 C73.4,56.8,79.3,62.1,85.4,67.5z M82.5,72.8c-6.5-5.9-13-11.6-19.3-17.6l-0.2,0.2c-3.6,3.2-7.3,6.5-10.9,9.9 c-1.3,1.1-2.9,1.1-4.2,0c-2.9-2.5-5.7-5.2-8.6-7.6c-1-0.8-1.7-1.5-2.7-2.3c-6.5,5.9-13,11.6-19.5,17.6c8.4,11.6,19.5,17.8,34,17.4 C64.4,89.8,74.7,83.5,82.5,72.8z M49.9,59.3c10.9-9.9,21.8-19.7,32.4-29.6C66.7,6.2,32.7,6.6,17.5,29.9 C28.1,39.6,39,49.5,49.9,59.3z M32.3,51.3C26.4,45.7,20.5,40.4,14.4,35c-4.8,10.1-4.8,22.9,0,32.4C20.5,62.1,26.4,56.6,32.3,51.3z"} - - // Ed - %symbol#save_symbol{viewBox: "0 0 48 48"} - %path{d: "M13,28.4c-0.6-0.6-0.6-1.6,0-2.2l9.9-9.9c0.5-0.6,1.6-0.6,2.2,0l10.1,9.9c0.6,0.6,0.6,1.6,0,2.2c-0.3,0.3-0.8,0.4-1.2,0.4 c-0.5,0-0.9-0.1-1.2-0.4l-7.2-7.2l0,14.8c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6l0-14.8l-7.2,7.2C14.6,29,13.6,29,13,28.4z M0.3,23.9c0-6.3,2.5-12.3,7-16.8c4.5-4.3,10.5-6.8,16.8-6.8c6.5,0,12.5,2.4,17,7c4.3,4.5,6.8,10.5,6.8,16.9 c-0.2,6.3-2.6,12.3-7.2,16.8c-4.4,4.3-10.4,6.8-16.7,6.8c-6.4,0-12.4-2.5-16.9-7C2.7,36.2,0.3,30.2,0.3,23.9z M10.6,8.5l26.9,0 c-3.8-3.2-8.5-5-13.5-5S14.3,5.3,10.6,8.5z M3.4,24.1c0,5.5,2.2,10.7,6,14.6c3.8,4,9,6.1,14.6,6.1c5.4,0,10.6-2.2,14.4-6 c7.5-7.4,8.1-19,1.9-27.1c-0.1,0.1-0.3,0.1-0.5,0.1l-31.6,0c-0.2,0-0.4-0.1-0.5-0.1C5,15.2,3.5,19.5,3.4,24.1z"} - %symbol#save_button_symbol{viewBox: "0 0 48 48"} - %path{d: "M3.6,11.8c-2.2,3.6-3.3,7.8-3.3,12.1c0,6.3,2.4,12.3,6.8,16.9c4.5,4.5,10.5,7,16.9,7c6.3,0,12.3-2.5,16.7-6.8 c4.6-4.5,7-10.5,7.2-16.8c0-4.5-1.2-8.7-3.4-12.4L3.6,11.8z M35.2,28.4c-0.3,0.3-0.8,0.4-1.2,0.4c-0.5,0-0.9-0.1-1.2-0.4l-7.2-7.2 l0,14.8c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6l0-14.8l-7.2,7.2c-0.6,0.6-1.6,0.6-2.2,0c-0.6-0.6-0.6-1.6,0-2.2l9.9-9.9 c0.5-0.6,1.6-0.6,2.2,0l10.1,9.9C35.8,26.8,35.8,27.8,35.2,28.4z M6,8.5C6.4,8,6.8,7.6,7.3,7.1c4.5-4.3,10.5-6.8,16.8-6.8 c6.5,0,12.5,2.4,17,7c0.4,0.4,0.7,0.8,1.1,1.2L6,8.5z"} - %symbol#revert_symbol{viewBox: "0 0 48 48"} - %path{d: "M39.9,8.5C35.6,4.3,30,1.9,24,1.9c-5.9,0-11.5,2.3-15.6,6.4C4.1,12.5,1.8,18.1,1.7,24c0,6,2.3,11.6,6.5,15.8 c4.2,4.3,9.8,6.6,15.9,6.6c5.9,0,11.5-2.3,15.6-6.4C48.4,31.4,48.5,17.3,39.9,8.5z M37.5,37.9c-3.6,3.6-8.4,5.6-13.5,5.6 c-5.2,0-10.1-2-13.7-5.7c-3.6-3.7-5.6-8.5-5.6-13.7c0-5.1,2.1-10,5.7-13.6c3.6-3.6,8.4-5.6,13.5-5.6c5.2,0,10.1,2,13.7,5.7 C45.2,18.2,45.1,30.4,37.5,37.9z M37.4,24.2c0,0.8-0.7,1.5-1.5,1.5H12.2c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h23.6 C36.7,22.7,37.3,23.3,37.4,24.2z"} - %symbol#revert_button_symbol{viewBox: "0 0 48 48"} - %path{d: "M39.9,8.5C35.6,4.3,30,1.9,24,1.9c-5.9,0-11.5,2.3-15.6,6.4C4.1,12.5,1.8,18.1,1.7,24c0,6,2.3,11.6,6.5,15.8 c4.2,4.3,9.8,6.6,15.9,6.6c5.9,0,11.5-2.3,15.6-6.4C48.4,31.4,48.5,17.3,39.9,8.5z M37.4,24.2c0,0.8-0.7,1.5-1.5,1.5H12.2 c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h23.6C36.7,22.7,37.3,23.3,37.4,24.2z"} - - %symbol#download_symbol{viewBox: "0 0 48 48"} - %path{d: "M35.2,19.7c0.6,0.6,0.6,1.6,0,2.2l-9.9,9.9c-0.5,0.6-1.6,0.6-2.2,0L13,21.9c-0.6-0.6-0.6-1.6,0-2.2c0.3-0.3,0.8-0.4,1.2-0.4 c0.5,0,0.9,0.1,1.2,0.4l7.2,7.2V12.1c0-0.9,0.7-1.6,1.6-1.6c0.9,0,1.6,0.7,1.6,1.6v14.8l7.2-7.2C33.6,19.1,34.6,19.1,35.2,19.7z M47.9,24.2c0,6.3-2.5,12.3-7,16.8c-4.5,4.3-10.5,6.8-16.8,6.8c-6.5,0-12.5-2.4-17-7c-4.3-4.5-6.8-10.5-6.8-16.9 c0.2-6.3,2.6-12.3,7.2-16.8c4.4-4.3,10.4-6.8,16.7-6.8c6.4,0,12.4,2.5,16.9,7C45.5,11.9,47.9,17.9,47.9,24.2z M37.6,39.6H10.7 c3.8,3.2,8.5,5,13.5,5S33.9,42.8,37.6,39.6z M44.8,24c0-5.5-2.2-10.7-6-14.6c-3.8-4-9-6.1-14.6-6.1c-5.4,0-10.6,2.2-14.4,6 c-7.5,7.4-8.1,19-1.9,27.1c0.1-0.1,0.3-0.1,0.5-0.1H40c0.2,0,0.4,0.1,0.5,0.1C43.2,32.9,44.7,28.6,44.8,24z"} - %symbol#download_button_symbol{viewBox: "0 0 48 48"} - %path{d: "M44.6,36.3c2.2-3.6,3.3-7.8,3.3-12.1c0-6.3-2.4-12.3-6.8-16.9c-4.5-4.5-10.5-7-16.9-7c-6.3,0-12.3,2.5-16.7,6.8 c-4.6,4.5-7,10.5-7.2,16.8c0,4.5,1.2,8.7,3.4,12.4H44.6z M13,19.7c0.3-0.3,0.8-0.4,1.2-0.4c0.5,0,0.9,0.1,1.2,0.4l7.2,7.2V12.1 c0-0.9,0.7-1.6,1.6-1.6c0.9,0,1.6,0.7,1.6,1.6v14.8l7.2-7.2c0.6-0.6,1.6-0.6,2.2,0c0.6,0.6,0.6,1.6,0,2.2l-9.9,9.9 c-0.5,0.6-1.6,0.6-2.2,0L13,21.9C12.4,21.3,12.4,20.3,13,19.7z M42.2,39.6c-0.4,0.5-0.8,0.9-1.3,1.4c-4.5,4.3-10.5,6.8-16.8,6.8 c-6.5,0-12.5-2.4-17-7C6.7,40.4,6.4,40,6,39.6H42.2z"} - - %symbol#insert_symbol{viewBox: "0 0 48 48"} - %polygon.fg{points: "39.7,22.7 25.5,22.7 25.5,8.5 22.9,8.5 22.9,22.7 8.7,22.7 8.7,25.3 22.9,25.3 22.9,39.5 25.5,39.5 25.5,25.3 39.7,25.3"} - - %symbol#image_symbol{viewBox: "0 0 100 100"} - %path{d: "M50,0.8C22.5,1,0.6,23.2,0.8,50.3c0,5,0.9,10,2.3,14.5c0,0.7,0.2,1.3,0.7,1.9c6.9,18.9,25,32.5,46.3,32.5h0.3 C63.5,99.1,75.8,94,85,84.6s14.3-21.8,14.2-34.9C99,22.8,77,0.8,50,0.8z M69.6,16c0,2.8-2.3,5.1-5.1,5.1c-2.8,0-5.1-2.3-5.1-5.1 s2.3-5.1,5.1-5.1C67.3,10.8,69.6,13.1,69.6,16z M50.2,92.6H50c-17.9,0-33.4-11.3-39.6-27l26.1-22.6l31.7,45.6 C62.7,91.1,56.6,92.6,50.2,92.6z M80.3,80c-2.1,2.1-4.3,3.8-6.6,5.5l-15.2-22l14.1-12.1l14.1,20.3C84.9,74.6,82.8,77.3,80.3,80z M89.8,65.1l-14-20.3c-0.5-0.8-1.3-1.2-2.2-1.3c-0.9-0.1-1.8,0.1-2.5,0.8l-16.3,14L39.7,36.3c-0.5-0.8-1.3-1.2-2.2-1.3 c-0.9-0.1-1.8,0.1-2.5,0.8L8.3,58.9c-0.5-2.7-0.9-5.7-1-8.6C7.2,26.7,26.2,7.6,49.7,7.3c2.3,0,4.5,0.2,6.6,0.5 C54.2,10,53,12.8,53,16c0,6.3,5.1,11.5,11.5,11.5c6.2,0,11.3-4.9,11.5-11C86,24.2,92.4,36.2,92.6,49.8C92.7,55,91.7,60.2,89.8,65.1z"} - %symbol#image_button_symbol{viewBox: "0 0 100 100"} - %path.fg{d: "M93.9,71.8c3.4-6.7,5.3-14.2,5.3-22.1C99,23.3,76.5,0.8,49.9,0.8c-7.3,0-14.2,1.6-20.5,4.5c-0.8,0.3-1.4,0.7-2.2,1.1 c-15.9,8.2-26.7,24.8-26.6,44c0,5.4,1,10.5,2.5,15.3c0,0.2,0.1,0.4,0.2,0.7c0.1,0.4,0.3,0.8,0.4,1.2C11,86,28.9,99.2,49.8,99.2h0.3 C69.1,99.2,85.8,87.8,93.9,71.8z M64.4,8.5c4.1,0,7.4,3.3,7.4,7.4s-3.3,7.4-7.4,7.4c-4.1,0-7.4-3.3-7.4-7.4S60.3,8.5,64.4,8.5z M10.1,65.9l25.8-22.3c0.2-0.2,0.5-0.2,0.7-0.2c0.1,0,0.4,0.1,0.7,0.3l31.2,45c-5.6,2.7-11.8,4.2-18.3,4.2h-0.2 C32,92.9,16.5,81.7,10.1,65.9z M80.5,80.1c-2,2-4.2,3.8-6.5,5.4L58.6,63.3l13.2-11.5c0.2-0.2,0.5-0.2,0.7-0.2c0.1,0,0.4,0.1,0.7,0.3 l14,19.3C85.4,74.5,83.1,77.5,80.5,80.1z"} - - %symbol#video_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6 c-5.5,0-10.7-2.1-14.6-6.1s-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5s8.9-6,14.4-6c5.5,0,10.7,2.1,14.6,6.1 C46.6,17.8,46.5,30.8,38.4,38.8z M38.1,16.4c0-0.7-0.6-1.3-1.3-1.3c-8.6-0.6-17-0.6-25.6,0c-0.7,0-1.3,0.6-1.3,1.3 c-0.3,5.2-0.3,10.5,0,15.7c0,0.7,0.6,1.3,1.3,1.3c8.6,0.6,17,0.6,25.6,0c0.7,0,1.3-0.6,1.3-1.3C38.4,26.8,38.4,21.6,38.1,16.4z M29,24.6L21.3,29c-0.3,0.2-0.6,0-0.6-0.4v-8.9c0-0.3,0.3-0.6,0.6-0.4l7.7,4.4C29.3,24,29.3,24.4,29,24.6z"} - %symbol#video_button_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M41,7.4c-4.6-4.5-10.6-7-17-7c-6.3,0-12.3,2.5-16.7,6.8c-4.6,4.5-7,10.5-7.2,16.8c0,6.4,2.5,12.4,6.9,16.9 c4.5,4.6,10.5,7,17,7c6.3,0,12.3-2.5,16.7-6.8C50.1,31.9,50.2,16.8,41,7.4z M38.1,32.1c0,0.7-0.6,1.3-1.3,1.3 c-8.6,0.6-17.1,0.6-25.7,0c-0.7,0-1.3-0.6-1.3-1.3c-0.3-5.2-0.3-10.5,0-15.8c0-0.7,0.6-1.3,1.3-1.3c8.6-0.6,17.1-0.6,25.7,0 c0.7,0,1.3,0.6,1.3,1.3C38.5,21.6,38.5,26.8,38.1,32.1z M29,24.6L21.3,29c-0.3,0.2-0.6,0-0.6-0.4v-8.9c0-0.3,0.3-0.6,0.6-0.4 l7.7,4.4C29.3,24,29.3,24.4,29,24.6z"} - - %symbol#document_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6s-10.7-2.1-14.6-6.1 c-3.9-4-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5c3.9-3.9,8.9-6,14.4-6s10.7,2.1,14.6,6.1C46.6,17.8,46.5,30.8,38.4,38.8z M36.8,26.7 c-0.8-1.5-3.3-2.3-7.1-2.3H29c-1-1.2-2.1-2.4-2.8-3.3L26,20.8c-0.3-0.4-0.6-0.7-0.8-1.1c0.5-1.5,0.8-2.8,0.9-3.7 c0.2-2.5-0.1-4.1-0.9-4.9c-0.6-0.6-1.4-0.8-2.2-0.6c-0.5,0.1-1.3,0.6-1.7,1.9c-0.6,1.7-0.4,4.8,1.3,7.6c-0.7,1.8-1.7,3.9-2.8,5.9 c-2.1,0.8-3.8,1.7-5.1,2.9c-1.7,1.6-2.3,3.3-1.8,4.5c0.3,0.8,1.1,1.3,2,1.3c0.6,0,1.3-0.2,1.9-0.6c1.4-0.9,3.3-3.8,4.6-6.2 c1.5-0.5,3.3-0.8,5.3-1l1.4-0.1c1.9,2,3.5,3.1,5,3.4l0.1,0c0.3,0,0.5,0.1,0.8,0.1c1.3,0,2.3-0.5,2.8-1.4 C37.1,28.1,37.1,27.4,36.8,26.7z M34.7,27.7c-0.1,0.1-0.3,0.1-0.7,0.1c-0.1,0-0.3,0-0.5,0c-0.6-0.1-1.2-0.5-2-1 C33.5,27,34.4,27.5,34.7,27.7z M15.1,32.3c0.1-0.3,0.4-0.9,1.2-1.8c0.4-0.4,0.9-0.7,1.3-1c-0.9,1.4-1.7,2.3-2.1,2.6 C15.3,32.2,15.2,32.3,15.1,32.3z M24.2,22.2c0.1,0.1,0.2,0.2,0.3,0.3c0.5,0.6,1.1,1.3,1.7,2c-0.9,0.1-2.1,0.3-3.3,0.5 C23.3,24,23.8,23.1,24.2,22.2z M23.7,15.8c0,0.2-0.1,0.5-0.1,0.8c-0.4-1.3-0.4-2.6-0.1-3.5c0-0.1,0.1-0.2,0.1-0.3 C23.7,13.3,23.9,14.1,23.7,15.8z"} - %symbol#document_button_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M24.6,22.3c0.7,0.9,1.5,1.9,2.4,2.9h-0.1c-1.2,0.1-2.9,0.4-4.9,0.8c0.8-1.5,1.5-3.1,2.1-4.5C24.2,21.8,24.3,22.1,24.6,22.3z M23.8,14.5c0.2-3.1-0.3-3.9-0.4-4c-0.1,0-0.3,0.3-0.5,0.8c-0.5,1.4-0.2,3.4,0.5,5.3C23.6,15.7,23.8,15,23.8,14.5z M12.8,34.6 C12.8,34.6,12.8,34.6,12.8,34.6c0.4,0,0.7-0.1,1-0.4c0.7-0.5,2-2,3.3-4.2c-1.1,0.5-2,1.2-2.7,1.9C12.9,33.5,12.7,34.4,12.8,34.6z M40.7,41c-4.4,4.4-10.3,6.8-16.6,6.8c-6.5,0-12.4-2.4-16.9-7C2.7,36.3,0.3,30.4,0.3,24c0.1-6.3,2.5-12.2,7.1-16.7 C11.8,2.9,17.7,0.5,24,0.5c6.4,0,12.3,2.5,16.9,7C50.1,16.9,50,31.9,40.7,41z M38.9,27.7c-1-1.7-3.9-2.6-8.2-2.6c-0.3,0-0.6,0-1,0 c-1.2-1.3-2.4-2.8-3.4-4.1c-0.5-0.6-1-1.2-1.4-1.8v0c0.6-1.9,1-3.4,1.1-4.5c0.2-2.9-0.1-4.8-1-5.7c-0.6-0.6-1.5-0.9-2.3-0.6 c-0.6,0.2-1.4,0.7-1.9,2.1c-0.7,2.1-0.4,5.9,1.6,8.9c-0.9,2.3-2.1,4.9-3.4,7.4c-2.5,0.9-4.6,2.1-6.1,3.5c-1.9,1.9-2.7,3.7-2.1,5.1 c0.4,0.9,1.1,1.4,2.1,1.4c0.7,0,1.4-0.2,2.1-0.7c1.8-1.2,4.2-5.1,5.4-7.4c2.6-0.8,5.2-1.1,6.5-1.3c0.6,0,1.2-0.1,1.8-0.1 c2.3,2.4,4.2,3.7,5.9,4c0.4,0,0.7,0.1,1,0.1c1.4,0,2.6-0.5,3.1-1.5C39.3,29.2,39.3,28.4,38.9,27.7z M31.9,27.3 c1.3,1.1,2.3,1.7,3.2,1.9c0.2,0,0.5,0,0.6,0c0.7,0,1.1-0.2,1.2-0.4c0,0,0,0,0-0.1C36.7,28.2,35.3,27.4,31.9,27.3z"} - - %symbol#quote_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M38.4,38.8c-3.8,3.8-8.9,6-14.4,6 c-5.5,0-10.7-2.1-14.6-6.1s-6-9-6-14.6c0-5.4,2.2-10.6,6.1-14.5s8.9-6,14.4-6c5.5,0,10.7,2.1,14.6,6.1 C46.6,17.8,46.5,30.8,38.4,38.8z M17.8,15.5c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2 c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C12.7,17.9,15,15.5,17.8,15.5z M30.3,15.5c3.2,0,5.7,3,5.7,7 c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C25.1,17.9,27.5,15.5,30.3,15.5z"} - %symbol#quote_button_symbol{viewBox: "0 0 48 48"} - %path.fg{d: "M40.9,7.5c-4.6-4.5-10.5-7-16.9-7c-6.3,0-12.2,2.4-16.6,6.8c-4.6,4.5-7,10.4-7.1,16.7c0,6.4,2.4,12.3,6.9,16.8 c4.5,4.6,10.4,7,16.9,7c6.3,0,12.2-2.4,16.6-6.8C50,31.9,50.1,16.9,40.9,7.5z M17.8,15.5c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4 c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6C12.7,17.9,15,15.5,17.8,15.5z M30.3,15.5 c3.2,0,5.7,3,5.7,7c0,5.2-4.8,10.4-9.8,10.4c-0.7,0-1.4-0.6-1.4-1.2c0-2.6,3.7-0.8,4.6-6.5c-2.7-0.7-4.3-2.4-4.3-4.6 C25.1,17.9,27.5,15.5,30.3,15.5z"} - - %symbol#close_symbol{viewBox: "0 0 48 48"} - %path{d: "M41.2,7.1c-0.6-0.6-1.7-0.6-2.3,0L24.1,21.9L9.3,6.9C8.7,6.3,7.7,6.3,7,6.9C6.4,7.5,6.4,8.6,7,9.2l14.8,15l-15,14.8 c-0.6,0.6-0.6,1.7,0,2.3c0.4,0.4,0.8,0.4,1.3,0.4c0.4,0,0.8,0,1.3-0.4l14.8-14.8l14.8,14.8c0.4,0.4,0.8,0.4,1.3,0.4s0.8,0,1.3-0.4 c0.6-0.6,0.6-1.7,0-2.3l-15-14.8L41.2,9.4C41.8,8.8,41.8,7.7,41.2,7.1z"} - - - %symbol#right_arrow_symbol{viewBox: "0 0 100 100"} - %path{d: "M76.7,47.6c1.2,1.2,1.2,3.6,0,4.8L55.9,73.1c-1.3,1.3-3.4,1.3-4.8,0c-0.7-0.6-1-1.4-1-2.3c0-0.8,0.3-1.7,1-2.3l15-15.1H25 c-1.8,0-3.3-1.6-3.3-3.3c0-1.8,1.4-3.3,3.3-3.3h41.1L51,31.6c-1.3-1.3-1.3-3.4,0-4.8s3.4-1.3,4.8,0L76.7,47.6z M100,49.7V50 c0,27.4-22.3,49.8-49.7,50C22.8,100.2,0.2,77.9,0,50C0,22.6,22.3,0.2,49.7,0c13.3-0.1,26,5,35.4,14.4l0,0 C94.7,23.8,99.9,36.3,100,49.7z M93.2,49.7c-0.1-11.6-4.7-22.3-12.9-30.6c-8.1-8-19-12.4-30.6-12.3c-23.7,0.1-43,19.6-43,43.6 c0.1,23.9,19.7,43.1,43.6,43C74,93.1,93.2,73.7,93.2,50L93.2,49.7z"} - %symbol#left_arrow_symbol{viewBox: "0 0 100 100"} - %path{d: "M23.3,52.4c-1.2-1.2-1.2-3.6,0-4.8l20.8-20.8c1.3-1.3,3.4-1.3,4.8,0c0.7,0.6,1,1.4,1,2.3c0,0.8-0.3,1.7-1,2.3l-15,15.1H75 c1.8,0,3.3,1.6,3.3,3.3c0,1.8-1.4,3.3-3.3,3.3H33.9L49,68.4c1.3,1.3,1.3,3.4,0,4.8s-3.4,1.3-4.8,0L23.3,52.4z M0,50.3V50 C0,22.6,22.3,0.2,49.7,0C77.2-0.2,99.8,22.1,100,50c0,27.4-22.3,49.8-49.7,50c-13.3,0.1-26-5-35.4-14.4l0,0 C5.3,76.2,0.1,63.7,0,50.3z M6.8,50.3c0.1,11.6,4.7,22.3,12.9,30.6c8.1,8,19,12.4,30.6,12.3c23.7-0.1,43-19.6,43-43.6 c-0.1-23.9-19.7-43.1-43.6-43C26,6.9,6.8,26.3,6.8,50L6.8,50.3z"} - %symbol#popout_symbol{viewBox: "0 0 100 100"} - %path{d: "M67.1,29.4c1.7,0,3.4,1.6,3.4,3.4l0,29.4c0,1.9-1.5,3.4-3.4,3.4c-0.9,0.1-1.7-0.3-2.4-0.9c-0.6-0.6-0.9-1.4-0.9-2.4L63.7,41 L34.7,70c-1.3,1.3-3.5,1.3-4.7,0c-1.3-1.3-1.3-3.4,0-4.7L59,36.3l-21.4,0c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4L67.1,29.4z M85.1,14.4l0.2,0.2c19.4,19.4,19.4,51,0.2,70.5c-19.3,19.6-51.1,19.8-70.9,0.2c-19.4-19.4-19.4-51-0.2-70.5C23.8,5.4,36.3,0,49.7,0 l0,0C63-0.1,75.6,5.1,85.1,14.4z M80.3,19.2C72.1,11.1,61.2,6.7,49.6,6.7c-11.4,0.1-22.2,4.6-30.3,12.9C2.6,36.4,2.7,63.8,19.7,80.8 c17,16.8,44.4,16.6,61.2-0.4c16.6-16.9,16.4-44.2-0.3-61L80.3,19.2z"} - - %symbol#first_symbol{viewBox: "0 0 48 64"} - %path{d: "M29.1,9.62a1.61,1.61,0,0,1,2.27,2.27L19.26,24,31.37,36.11a1.61,1.61,0,1,1-2.27,2.27L15.85,25.14a1.61,1.61,0,0,1,0-2.27Z"} - %symbol#last_symbol{viewBox: "0 0 48 64"} - %path{d: "M20.13,9.7A1.59,1.59,0,0,0,17.88,12l12,12-12,12a1.59,1.59,0,1,0,2.25,2.25L33.25,25.07a1.59,1.59,0,0,0,0-2.25Z"} - %symbol#prev_symbol{viewBox: "0 0 48 64"} - %path{d: "M27.91,9.62a1.61,1.61,0,1,1,2.27,2.27L18.07,24,30.18,36.11a1.61,1.61,0,1,1-2.27,2.27L14.66,25.14a1.61,1.61,0,0,1,0-2.27Z"} - %symbol#next_symbol{viewBox: "0 0 48 64"} - %path{d: "M20.13,9.7A1.59,1.59,0,0,0,17.88,12l12,12-12,12a1.59,1.59,0,1,0,2.25,2.25L33.25,25.07a1.59,1.59,0,0,0,0-2.25Z"} - - %symbol#edit_symbol{viewBox: "0 0 48 64"} - %path{d: "M11.5,32l4.4,4.4l-6.3,1.8L11.5,32z M33.4,9.1c0.1-0.1,0.2-0.1,0.3-0.1c0.1,0,0.2,0.1,0.3,0.1l4.9,4.9 c0.1,0.1,0.1,0.3,0.1,0.3c0,0.1,0,0.2-0.1,0.3l-3.6,3.6l-5.6-5.6L33.4,9.1z M27.7,14.7l5.6,5.6L18.6,35L13,29.4L27.7,14.7z M7.5,42 c0.1,0,0.3,0,0.4-0.1l10.3-3c0.5-0.1,1.1-0.4,1.5-0.9L41,16.8c0.6-0.6,1-1.5,1-2.5c0-0.9-0.3-1.8-1-2.4L36.1,7 c-1.3-1.3-3.6-1.3-4.9,0L10,28.3c-0.4,0.4-0.7,0.9-0.9,1.5l-3,10.3c-0.1,0.5,0,1.1,0.4,1.5C6.7,41.9,7.1,42,7.5,42L7.5,42z"} - - - // positioning controls for inserted assets - %symbol#left_symbol{viewBox: "0 0 48 48"} - %rect.fg{x:"13.3", y: "13.9", width: "26.6", height: "2.3"} - %rect.fg{x:"24.1", y: "18.4", width: "15.8", height: "2.3"} - %rect.fg{x:"24.1", y: "23", width: "15.8", height: "2.3"} - %rect.fg{x:"24.1", y: "27.6" , width: "15.8", height: "2.3"} - %rect.fg{x:"13.3", y: "32.2", width: "26.6", height: "2.3"} - %rect.fg{x:"8.9", y: "18.4", width: "13.4", height: "11.4"} - - %symbol#right_symbol{viewBox: "0 0 48 48"} - %rect.fg{x: "8.9", y: "13.9", width: "26.6", height: "2.3"} - %rect.fg{x: "8.9", y: "18.4", width: "15.8", height: "2.3"} - %rect.fg{x: "8.9", y: "23", width: "15.8", height: "2.3"} - %rect.fg{x: "8.9", y: "27.6", width: "15.8", height: "2.3"} - %rect.fg{x: "8.9", y: "32.2", width: "26.6", height: "2.3"} - %rect.fg{x: "26.5", y: "18.4", width: "13.4", height: "11.4"} - - %symbol#full_symbol{viewBox: "0 0 48 48"} - %rect.fg{x: "10.7", y: "13.7", width: "26.6", height: "2.3"} - %rect.fg{x: "10.7", y: "32", width: "26.6", height: "2.3"} - %rect.fg{x: "10.7", y: "18.2", width: "26.6", height: "11.4"} - - %symbol#wide_symbol{viewBox: "0 0 48 48"} - %rect.fg{x: "10.7", y: "13.7", width: "26.6", height: "2.3"} - %rect.fg{x: "10.7", y: "32", width: "26.6", height: "2.3"} - %rect.fg{x: "6.5", y: "18.2", width: "34.9", height: "11.4"} - - %symbol#hero_button{viewBox: "0 0 48 48"} - %path.bg{d: "M40.3,7.6c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.6,40.3,7.6z"} - %path.fg{d: "M4.7,36.5C8.8,42.7,15.9,46.9,24,46.9s15.2-4.2,19.3-10.4H4.7z" } - %path.fg{d: "M43.6,11.9c-4-6.6-11.3-11-19.6-11S8.4,5.3,4.4,11.9H43.6z" } - - // Toolbar buttons - %symbol#bold_button{viewBox: "0 0 48 48"} - %path.fg{d: "M26.5,18.4c0-1.6-1.3-2.4-2.8-2.4H21v5.1h2.7C25.4,21.1,26.5,20.1,26.5,18.4z"} - %path.fg{d: "M24.7,25.7H21v5.7h4c1.9,0,3.2-1.1,3.2-3C28.3,26.8,27.1,25.7,24.7,25.7z"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M25.7,35.9h-7.1 c-1.6,0-2.7-1-2.7-2.6V14c0-1.6,1.1-2.6,2.7-2.6h6.6c3.6,0,6.5,2.4,6.5,6c0,2.4-1,4-3.2,5v0.1c2.9,0.4,5.2,2.8,5.2,5.8 C33.7,33.3,30.4,35.9,25.7,35.9z"} - - %symbol#italic_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M32.1,13.9l-9.4,19.5 c-0.8,1.7-2.7,2.7-4.7,2.7c-1.9,0-2.9-1-2.1-2.7l9.4-19.5c0.8-1.7,2.7-2.7,4.7-2.7C31.9,11.2,32.9,12.2,32.1,13.9z"} - - %symbol#link_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M32.1,13.9l-9.4,19.5 c-0.8,1.7-2.7,2.7-4.7,2.7c-1.9,0-2.9-1-2.1-2.7l9.4-19.5c0.8-1.7,2.7-2.7,4.7-2.7C31.9,11.2,32.9,12.2,32.1,13.9z"} - - %symbol#ol_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M12.4,11.8h1.2c0.4,0,0.6,0.3,0.6,0.6v4.7 c0,0.4-0.3,0.6-0.6,0.6s-0.6-0.2-0.6-0.6v-4.2h-0.5c-0.4,0-0.6-0.3-0.6-0.6C11.8,12.1,12,11.8,12.4,11.8z M13.3,36.2 c-1.2,0-2-0.9-2-1.6c0-0.3,0.3-0.6,0.6-0.6c0.6,0,0.4,1,1.4,1c0.4,0,0.8-0.3,0.8-0.8c0-1.2-1.4-0.3-1.4-1.3c0-0.9,1.2-0.3,1.2-1.2 c0-0.3-0.2-0.6-0.6-0.6c-0.8,0-0.7,0.8-1.2,0.8c-0.3,0-0.5-0.3-0.5-0.6c0-0.6,0.9-1.3,1.8-1.3c1.2,0,1.8,0.9,1.8,1.5 c0,0.5-0.2,1-0.7,1.3c0.6,0.3,1,0.8,1,1.5C15.4,35.4,14.5,36.2,13.3,36.2z M14.9,26.9h-2.8c-0.4,0-0.6-0.2-0.6-0.5 c0-0.2,0.1-0.3,0.2-0.4c0.7-0.8,1.4-1.6,2.1-2.5c0.1-0.2,0.3-0.5,0.3-0.8c0-0.3-0.3-0.6-0.6-0.6c-1,0-0.5,1.3-1.3,1.3 c-0.4,0-0.6-0.3-0.6-0.6c0-1,0.9-1.9,1.9-1.9s1.8,0.7,1.8,1.7c0,1.2-1.3,2.3-2,3.2h1.5c0.4,0,0.6,0.2,0.6,0.5 C15.5,26.7,15.2,26.9,14.9,26.9z M33.9,36.1H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 S35.5,36.1,33.9,36.1z M33.9,26.9H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9S35.5,26.9,33.9,26.9z M33.9,17.7H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9S35.5,17.7,33.9,17.7z"} - - %symbol#ul_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M14.2,36.1c-1.6,0-2.9-1.3-2.9-2.9 c0-1.6,1.3-2.9,2.9-2.9c1.6,0,2.9,1.3,2.9,2.9C17.1,34.8,15.8,36.1,14.2,36.1z M14.2,26.9c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9 c1.6,0,2.9,1.3,2.9,2.9S15.8,26.9,14.2,26.9z M14.2,17.7c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9c1.6,0,2.9,1.3,2.9,2.9 C17.1,16.4,15.8,17.7,14.2,17.7z M33.9,36.1H21.8c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 C36.8,34.8,35.5,36.1,33.9,36.1z M33.9,26.9H21.8c-1.6,0-2.9-1.3-2.9-2.9s1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 S35.5,26.9,33.9,26.9z M33.9,17.7H21.8c-1.6,0-2.9-1.3-2.9-2.9c0-1.6,1.3-2.9,2.9-2.9h12.2c1.6,0,2.9,1.3,2.9,2.9 C36.8,16.4,35.5,17.7,33.9,17.7z"} - - %symbol#h1_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M34.6,33.7 c0,1.7-1.1,2.7-2.6,2.7s-2.6-1-2.6-2.7v-7.5H18.7v7.5c0,1.7-1.1,2.7-2.6,2.7s-2.6-1-2.6-2.7V14.3c0-1.7,1.1-2.7,2.6-2.7 s2.6,1,2.6,2.7v7.3h10.8v-7.3c0-1.7,1.1-2.7,2.6-2.7s2.6,1,2.6,2.7V33.7z"} - - %symbol#h2_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M33.2,33.3 c0,1.6-1,2.7-2.5,2.7s-2.5-1.1-2.5-2.7v-8.6c0-2.3-1.4-3.4-3.2-3.4c-2,0-3.2,1.5-3.2,3.4v8.6c0,1.6-1,2.7-2.5,2.7s-2.5-1.1-2.5-2.7 V12.2c0-1.6,1-2.7,2.5-2.7s2.5,1.1,2.5,2.7v6.7h0.1c1-1.3,3-1.7,4.5-1.7c3.9,0,6.8,2.5,6.8,6.7V33.3z"} - - %symbol#anchor_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M23.2,36.1 c-1.6,1.6-3.7,2.6-6,2.5c-2.2-0.1-4.1-0.9-5.6-2.5c-3-3.2-2.8-8.2,0.3-11.3l4.9-4.9c0.1-0.1,0.2-0.2,0.3-0.2c0.4-0.4,1-0.8,1.6-1.1 c1.5-0.8,3.2-1.1,4.9-0.9c0.4,0,0.8,0.1,1.2,0.2c0.4,0.1,0.8,0.2,1.1,0.4c0.8,0.4,1.5,0.9,2.2,1.5c0.8,0.8,0.7,2.2-0.1,3l-1,1 c-0.1,0.2-0.3,0.3-0.5,0.4c-0.2-0.7-0.6-1.4-1.1-1.9c-0.6-0.6-1.4-1-2.2-1.2c-0.3-0.1-0.6-0.1-0.9-0.1h-0.2 c-0.4,0.1-0.7,0.1-1.1,0.2c-0.7,0.2-1.4,0.6-1.9,1.1l-4.9,4.9c-0.7,0.7-1.3,1.7-1.4,2.8c-0.1,1.2,0.3,2.5,1.2,3.4 c0.9,0.9,2.1,1.4,3.4,1.2c1.1-0.1,2-0.6,2.8-1.4l0.9-0.9c0.5-0.5,1.2-0.6,1.8-0.4c0.4,0.1,0.8,0.2,1.2,0.2c0.4,0.1,0.7,0.1,1.1,0.1 c0.4,0,0.8-0.1,1.2-0.1L23.2,36.1z M36.1,23.3l-4.9,4.9c-0.1,0.1-0.2,0.2-0.3,0.2c-0.4,0.4-1,0.9-1.6,1.2c-1.2,0.6-2.5,0.9-3.8,0.9 c-0.4,0-0.7,0-1.1-0.1c-0.4,0-0.8-0.1-1.2-0.2c-0.4-0.1-0.8-0.2-1.1-0.4c-0.8-0.4-1.5-0.9-2.2-1.5c-0.8-0.8-0.8-2.2,0.1-3l1-1 c0.2-0.2,0.4-0.3,0.6-0.4c0.2,0.7,0.6,1.4,1.1,1.9c0.6,0.6,1.4,1,2.2,1.2c0.3,0.1,0.6,0.1,0.9,0.1h0.1c0.4-0.1,0.7-0.1,1.1-0.2 c0.7-0.2,1.4-0.6,1.9-1.1l4.9-4.9c1.6-1.6,1.9-4.2,0.4-5.9c-0.9-1.1-2.2-1.7-3.7-1.5c-1.1,0.1-2.1,0.6-2.8,1.4l-1,1 c-0.4,0.4-0.9,0.5-1.4,0.3c-0.4-0.1-0.8-0.2-1.2-0.2c-0.9-0.1-1.8-0.1-2.7,0.1l3.7-3.7c1.6-1.6,3.7-2.6,6-2.5 c2.2,0.1,4.1,0.9,5.6,2.5C39.5,15,39.3,20.1,36.1,23.3z"} - - %symbol#clear_button{viewBox: "0 0 48 48"} - %path.fg{d: "M40.3,7.8c-9-9-23.5-9-32.5,0s-9,23.5,0,32.5s23.5,9,32.5,0S49.2,16.8,40.3,7.8z M26.8,24.1c2.8,2.8,5.7,5.7,8.5,8.5 c0.7,0.7,0.8,1.8,0.1,2.5c-0.7,0.8-1.8,0.8-2.6,0.1c-0.1-0.1-0.1-0.1-0.2-0.2c-2.8-2.8-5.5-5.5-8.4-8.4c-0.1-0.1-0.1-0.1-0.2-0.2 c-0.1,0.1-0.1,0.1-0.2,0.2c-2.8,2.8-5.7,5.7-8.5,8.5c-0.8,0.8-2,0.8-2.7-0.1c-0.6-0.7-0.5-1.7,0.1-2.4c0.5-0.5,1-1,1.5-1.5 c2.4-2.4,4.8-4.8,7.1-7.1c0-0.1,0.1-0.1,0.2-0.1c-0.1-0.1-0.1-0.2-0.2-0.2c-2.8-2.8-5.7-5.7-8.5-8.5c-0.7-0.7-0.8-1.8-0.1-2.5 c0.7-0.8,1.8-0.8,2.6-0.1c0.1,0.1,0.1,0.1,0.2,0.2c2.8,2.8,5.5,5.5,8.4,8.4c0.1,0.1,0.1,0.1,0.2,0.3c0.1-0.1,0.1-0.2,0.2-0.2 c2.8-2.8,5.7-5.7,8.5-8.5c0.7-0.9,2-0.8,2.7,0c0.6,0.7,0.5,1.7-0.1,2.4c-0.2,0.2-0.6,0.6-0.8,0.8c-2.6,2.6-5.2,5.2-7.8,7.8 c-0.1,0-0.1,0.1-0.2,0.1C26.7,24,26.8,24.1,26.8,24.1z"} - - + %symbol#location_symbol{viewBox: "0 0 24 24",fill: "#101820", stroke: "#101820", "stroke-width": ".1"} + %g{transform: "translate(4 1)"} + %circle{cx: "8", cy: "8.14", r: "2"} + %path{d: "M8 .14a8 8 0 0 0-8 8c0 3.77 5 14 8 14s8-10.23 8-14a8 8 0 0 0-8-8zm0 20c-1.49-.56-6-8.51-6-12a6 6 0 1 1 12 0c0 3.46-4.51 11.41-6 11.99v.01z"} + %symbol#clock_symbol{viewBox: "-1 -1 18 18", fill: "#101820", width: "16", height: "16", stroke: "#101820", "stroke-width": "0.7"} + -# %path{d: "M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z", stroke: "#101820", "stroke-width": "1"} + -# %path{d: "M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z", stroke: "#101820", "stroke-width": "1"} + %path{d: "M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"} + %path{d: "M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"} diff --git a/app/views/droom/users/_action_menu.html.haml b/app/views/droom/users/_action_menu.html.haml index 7a4f39645..7ce846a00 100644 --- a/app/views/droom/users/_action_menu.html.haml +++ b/app/views/droom/users/_action_menu.html.haml @@ -1,17 +1,20 @@ - event ||= false - group ||= false +- data_for = "user_#{user.id}" +- if group + - data_for = "user_#{user.id}#{group.id}" - if can?(:edit, user) - .menu{:data => {:for => "user_#{user.id}"}} + .menu{:data => {:for => data_for}} %ul.actions %li - = link_to t(:edit_profile), droom.edit_user_url(user), :class => 'edit', :data => {:action => "popup", :affected => "#user_#{user.id}"} + = link_to t(:edit_profile), droom.edit_user_url(user), :class => 'edit', :data => {:action => "popup", :affected => "#user_#{user.id}#{group.try(:id)}"} %li - = link_to t(:edit_account), droom.edit_user_url(user, view: 'preferences'), :class => 'edit', :data => {:action => "popup", :affected => "#user_#{user.id}"} + = link_to t(:edit_account), droom.edit_user_url(user, view: 'preferences'), :class => 'edit', :data => {:action => "popup", :affected => "#user_#{user.id}#{group.try(:id)}"} - unless user == current_user %li - = link_to t(:delete_user), droom.user_url(user), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#user_#{user.id}", :confirm => t(:confirm_delete_user, :name => user.full_name)} + = link_to t(:delete_user), droom.user_url(user), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#user_#{user.id}#{group.try(:id)}", :confirm => t(:confirm_delete_user, :name => user.full_name)} - if !user.confirmed? && can?(:reinvite, user) %li @@ -19,5 +22,4 @@ - if group && membership = user.membership_of(group) %li - = link_to t(:expel, name: group.name), droom.membership_url(membership), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#user_#{user.id}"} - + = link_to t(:expel, name: group.name), droom.membership_url(membership), :method => 'delete', :class => 'delete', :data => {:action => "remove", :removed => "#user_#{user.id}#{group.try(:id)}"} \ No newline at end of file diff --git a/app/views/droom/users/edit/_admin_password_fields.html.haml b/app/views/droom/users/edit/_admin_password_fields.html.haml index 39ecbeb48..276e7c7a0 100644 --- a/app/views/droom/users/edit/_admin_password_fields.html.haml +++ b/app/views/droom/users/edit/_admin_password_fields.html.haml @@ -12,8 +12,15 @@ %p.manage_password %span.col.password> = f.label :password, class: "required" - = f.password_field :password, placeholder: t("placeholders.user.password"), required: user.new_record?, data: {role: "password"} + = f.password_field :password, placeholder: t("placeholders.user.password"), required: user.new_record?, data: {role: "password"}, autocomplete: false, readonly: true, onfocus: "this.removeAttribute('readonly');" + %span.col.confirmation{:data => {:role => "confirmation"}}> %br = f.password_field :password_confirmation, placeholder: "", required: true + %span.col.password + .note Timezone is used to convert and display event's times according to your selected timezone here. + = f.label :timezone, class: "required" + = f.select :timezone, Timezones.for_selection.map{ |key, value| [value, key] }, {} + :javascript + $('#user_timezone').selectize(); diff --git a/app/views/droom/users/edit/_groups.html.haml b/app/views/droom/users/edit/_groups.html.haml index d82b403c5..a9f4d84ae 100644 --- a/app/views/droom/users/edit/_groups.html.haml +++ b/app/views/droom/users/edit/_groups.html.haml @@ -5,6 +5,7 @@ = form_for user, :html => {class: 'poppedup'}, data: {remote: true, type: :html} do |f| = hidden_field_tag :view, @view + = hidden_field_tag "user[group_ids][]", nil - groups = Droom::Group.all - if groups.any? @@ -15,4 +16,4 @@ = check_box_tag "user[group_ids][]", group.id, user.group_ids.include?(group.id), id: "user_group_ids_#{group.id}" = group.name - = render partial: 'droom/users/edit/buttons', locals: {f: f} \ No newline at end of file + = render partial: 'droom/users/edit/buttons', locals: {f: f} diff --git a/app/views/droom/users/edit/_password_fields.html.haml b/app/views/droom/users/edit/_password_fields.html.haml index 1b5226d94..ef0b23828 100644 --- a/app/views/droom/users/edit/_password_fields.html.haml +++ b/app/views/droom/users/edit/_password_fields.html.haml @@ -5,11 +5,6 @@ - fclass << " required" unless user.password_set? %fieldset{data: {role: 'password'}} - %p.password_quality.floating{data: {role: "meter"}} - %span.instructions{data: {role: "warnings"}} - %span.meter{data: {role: "gauge"}} - %svg - %use{"xlink:href" => "#warning_symbol"} %p.manage_password %span.col.password> @@ -21,6 +16,12 @@ %br = f.password_field :password_confirmation, placeholder: "", required: true + %p.password_quality.floating{data: {role: "meter"}} + %span.instructions{data: {role: "warnings"}} + %span.meter{data: {role: "gauge"}} + %svg + %use{"xlink:href" => "#warning_symbol"} + - if with_submit %p.buttons{:data => {:role => "confirmation"}}> = f.submit t(:set_password) diff --git a/app/views/droom/users/edit/_personal.html.haml b/app/views/droom/users/edit/_personal.html.haml index 064705c36..c1785f5c1 100644 --- a/app/views/droom/users/edit/_personal.html.haml +++ b/app/views/droom/users/edit/_personal.html.haml @@ -58,5 +58,14 @@ = f.label :gender, t(:"helpers.label.user.gender_m"), value: "m" %td{valign: 'top'} = f.date_select :dob, {order: [:day, :month, :year], prompt: { day: 'Select day', month: 'Select month', year: 'Select year' }, start_year: 1940, end_year: Date.today.year} + %tr + %td + Timezone + %tr + %td{colspan: "2"} + = f.select :timezone, Timezones.for_selection.map{ |key, value| [value, key] }, {} + :javascript + $('#user_timezone').selectize(); + = render partial: 'droom/users/edit/buttons', locals: {f: f} diff --git a/app/views/droom/users/edit/_profile.html.haml b/app/views/droom/users/edit/_profile.html.haml index eee340812..de62b42bc 100644 --- a/app/views/droom/users/edit/_profile.html.haml +++ b/app/views/droom/users/edit/_profile.html.haml @@ -13,7 +13,7 @@ = render 'droom/shared/popup_header', title: title = form_for user, :html => {:class => 'edit user', :autocomplete => false} do |f| - = render partial: 'droom/users/edit/user_fields', locals: {f: f, user: user} + = render partial: 'droom/users/edit/user_fields', locals: {f: f, user: user, timezone_id: "profile_timezone"} .buttons - if user == current_user diff --git a/app/views/droom/users/edit/_simple.html.haml b/app/views/droom/users/edit/_simple.html.haml index c437e32f3..f859de25a 100644 --- a/app/views/droom/users/edit/_simple.html.haml +++ b/app/views/droom/users/edit/_simple.html.haml @@ -14,7 +14,18 @@ = form_for user, :html => {:class => 'edit user', :autocomplete => false} do |f| - = render partial: 'droom/users/edit/user_fields', locals: {f: f, user: user, minimal: true} + = render partial: 'droom/users/edit/user_fields', locals: {f: f, user: user, minimal: true, timezone_id: "simple_timezone"} + -# = render partial: 'droom/users/edit/groups', locals: {f: f, user: user, minimal: true} + = hidden_field_tag "user[group_ids][]", nil + + - groups = Droom::Group.all + - if groups.any? + %div{style: 'width: 100%;'} + = f.label(:group_ids, class: "important") + ":" + - groups.each do |group| + %label.inline{for: "user_group_ids_#{group.id}"} + = check_box_tag "user[group_ids][]", group.id, user.group_ids.include?(group.id), id: "user_group_ids_#{group.id}" + = group.name - unless @user.confirmed? = f.hidden_field :send_confirmation, value: true @@ -35,4 +46,4 @@ - else = f.submit t(:save_profile) = t :or - = link_to t(:cancel), '#', :class => 'cancel' \ No newline at end of file + = link_to t(:cancel), '#', :class => 'cancel' diff --git a/app/views/droom/users/edit/_user_fields.html.haml b/app/views/droom/users/edit/_user_fields.html.haml index 5163ee724..b11d55316 100644 --- a/app/views/droom/users/edit/_user_fields.html.haml +++ b/app/views/droom/users/edit/_user_fields.html.haml @@ -1,5 +1,6 @@ - user ||= @user - minimal ||= false +- timezone_id ||= "user_timezone" - if user.image? - img = user.image.url(:thumb) @@ -19,3 +20,10 @@ .description = f.text_area :description, class: "rte", data: {placeholder: t("placeholders.user.description")} + + - if user.id + .note Timezone is used to convert and display event's times according to your selected timezone here. + = f.select :timezone, Timezones.for_selection.map{ |key, value| [value, key] }, {}, id: timezone_id + + :javascript + $("##{timezone_id}").selectize(); diff --git a/app/views/droom/users/passwords/expired_reset_password_token.html.haml b/app/views/droom/users/passwords/expired_reset_password_token.html.haml new file mode 100644 index 000000000..97dfd0b77 --- /dev/null +++ b/app/views/droom/users/passwords/expired_reset_password_token.html.haml @@ -0,0 +1,10 @@ +%h1.pagetitle + =t :expired_token +%br/ + +%h4 + = t(:want_to_reset_again) + +%br += link_to 'Yes', new_password_path(resource_name), style: "background-color: #74b87a; border: none; color: white; padding: 9px 27px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer;" += link_to 'No', root_url, style: "background-color: #f44336; border: none; color: white; padding: 9px 27px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer;" \ No newline at end of file diff --git a/app/views/droom/users/passwords/show.html.haml b/app/views/droom/users/passwords/show.html.haml index 8088340d4..91b1a1e5e 100644 --- a/app/views/droom/users/passwords/show.html.haml +++ b/app/views/droom/users/passwords/show.html.haml @@ -3,4 +3,4 @@ =t :message_sent %p - = t :password_reset_message_sent, sender: Settings.email.address + = t :password_reset_message_sent_html, sender: Settings.email.address diff --git a/app/views/droom/users/setup.html.haml b/app/views/droom/users/setup.html.haml index db7d9cc6a..6b41c1075 100644 --- a/app/views/droom/users/setup.html.haml +++ b/app/views/droom/users/setup.html.haml @@ -26,5 +26,10 @@ = t "user_setup.set_password" = render "droom/users/edit/password_fields", f: f, user: user, with_label: false, with_submit: false + %h3 + = t "user_setup.time_zone" + .note Timezone is used to convert and display event's times according to your selected timezone here. + = f.select :timezone, Timezones.for_selection.map{ |key, value| [value, key] }, {} + %p.buttons{:data => {:role => "confirmation"}}> = f.submit t :continue diff --git a/app/views/droom/users/show/_account_info.html.haml b/app/views/droom/users/show/_account_info.html.haml index bb4dd7987..10ca22a44 100644 --- a/app/views/droom/users/show/_account_info.html.haml +++ b/app/views/droom/users/show/_account_info.html.haml @@ -4,7 +4,7 @@ %section{class: cssclass, data: {refreshing: true, url: droom.user_url(@user, view: 'account_info', format: :js)}} - if @user && can?(:edit, @user) %span.user_controls - = link_to t(:edit_user_profile_symbol), droom.edit_user_url(@user, view: 'account_info'), class: "edit", data: {action: 'popup', affected: "section.account_info"} + = link_to t(:edit_user_profile_symbol), droom.edit_user_url(@user, view: 'preferences'), class: "edit", data: {action: 'popup', affected: "section.account_info"} %h2.section = t :user_profile_account diff --git a/app/views/droom/users/show/_personal.html.haml b/app/views/droom/users/show/_personal.html.haml index e2919dff4..87f79c602 100644 --- a/app/views/droom/users/show/_personal.html.haml +++ b/app/views/droom/users/show/_personal.html.haml @@ -49,3 +49,7 @@ = 'Gender: ' %b = @user.gender.present? ? ( @user.gender == 'm' ? 'Male' : 'Female') : t(:not_known) + .right_pane_contents + = 'Timezone: ' + %b + = @user.timezone.present? ? shorten(@user.timezone, 64, ",") : t(:not_known) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 731623ae8..34294e61f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -256,7 +256,7 @@ def user_deserialize(key) end # We used to set shared domain cookie on sign in here - # but it proved impossible, or anyway very unreliable, to try and do that after session_limitable had set its unique_session_id. + # but it proved impossible, or anyway very unreliable, to make sure that happened after session_limitable had set its unique_session_id. # so now the cookie is set or updated on every request, in an after_action. # Unset session id and shared domain cookie on sign out. diff --git a/config/locales/en.yml b/config/locales/en.yml index 1797c629e..d990c8f15 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,7 +3,7 @@ en: Noticeboard: "Latest Notices" - accept: "accept" + accept: "Accept" access_control: "Access control" account: "Account" account_emails: "You can sign in or request a password reset with any email address that we have on record." @@ -35,6 +35,7 @@ en: add_root_folder: "Add root folder" add_service: "Add service" add_subfolder: Add sub-folder + move_folder: "Move folder" add_to_stream: "add to stream" add_user: "Add User" added_to: "has been added to '%{group}'." @@ -42,11 +43,12 @@ en: advanced_search: "Search terms" advanced_search_link: "...advanced search" allow: "Allow" + all_permissions: 'All Permissions' and_invited: "and invited to log in." apps: data_room: "Data%{sender}."
+ password_reset_message_sent_html: "A message has been sent to your email address containing a link that will let you reset your password. Please allow a few minutes for it to arrive, and keep an eye on your spam folder. The address it will seem to come from is %{sender}."
password_reset_welcome: "Please use the form below to set a new password for your account. A confirmation box will appear when your password meets the requirements, and the submit button will go green and become clickable when the passwords match."
password_set: "Thank you. Your password has been set and your account is fully active."
+ past_events: "Past Events"
pending_organisations_introduction:
zero: "No applications"
one: "Review one application"
@@ -520,6 +530,7 @@ en:
quick_search: "Quick search"
quick_search: "Quick search"
quick_search_prompt: "Type anything here"
+ read_permission: "Read Permission"
recent_events: "Past events"
register_organisation: "Apply to register your organisation"
reject: "reject"
@@ -611,6 +622,7 @@ en:
check_name: "Please check that your name is correct"
give_name: "Please give your name"
set_password: "and choose a strong password"
+ time_zone: "Please choose a time zone"
user_updated: "Preferences for %{name} have been updated."
users_introduction: "This is a list of everyone known to the data room. It may include many people who do not have access to the data room itself but whom we authenticate for access to other services."
venue_name: "Venue name"
@@ -619,6 +631,7 @@ en:
pagination:
previous: "«"
next: "»"
+ want_to_reset_again: "Do you want to generate new reset password token ?"
welcome_preamble: "Welcome to the Croucher Foundation. We notice that this is the first time you have used your Croucher Foundation account so before we go any further, please choose a password."
would_you_like_to_return: "You probably want to return to"
year: "YEAR"
diff --git a/config/routes.rb b/config/routes.rb
index e77adc8ab..182dbfef1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -13,7 +13,9 @@
resources :users do
post 'reindex', on: :member, as: :reindex
get "whoami" , on: :collection, as: :whoami
+ get "authenticable", on: :member, as: :authenticable
end
+ put "update_timezone" => 'users#update_timezone', as: 'update_timezone'
resources :events
resources :venues
resources :images
@@ -38,6 +40,7 @@
devise_scope :user do
+ get "/users/expired_reset_password_token" => "users/passwords#expired_reset_password_token", as: :expired_reset_password_token
get "/signup" => "users/registrations#new", as: :signup
post '/register' => 'users/registrations#create', as: :register
get "/users/registrations/confirm" => "users/registrations#confirm", as: :confirm_registration
@@ -51,6 +54,7 @@
delete '/api/users/sign_out' => 'api/sessions#destroy', as: :api_sign_out
get '/api/authenticate/:tok' => 'api/sessions#authenticate', as: 'authenticate'
get '/api/deauthenticate/:tok' => 'api/sessions#deauthenticate', as: 'deauthenticate'
+ get '/api/users/authenticable/:id' => 'api/users#authenticable', as: 'authenticable'
end
resources :helps
@@ -97,10 +101,14 @@
resources :folders do
get "dropbox", on: :member, as: :dropbox
+ get "move_folder", on: :member
+ put "moved", on: :member
resources :documents
resources :folders
end
+ get "child_folders" => "folders#child_folders"
+
resources :organisations do
resources :users
collection do
@@ -117,6 +125,7 @@
get "activity" => "users#activity", as: :activity
get :preferences, on: :member, as: :preferences
put :preference, on: :member, as: :set_preference
+ get :download, on: :collection
get :admin, on: :collection
put :setup, on: :collection
put :reinvite, on: :member
@@ -129,7 +138,10 @@
resources :groups do
resources :memberships
- resources :group_permissions
+ resources :group_permissions do
+ post :upsert, on: :collection
+ delete :delete_by_ids, on: :collection, as: :delete
+ end
end
resources :event_types
diff --git a/droom.gemspec b/droom.gemspec
index f66db296d..d2fc2bf55 100644
--- a/droom.gemspec
+++ b/droom.gemspec
@@ -17,9 +17,10 @@ Gem::Specification.new do |s|
s.test_files = Dir["spec/**/*"]
- s.add_dependency "rails", "~> 5.2"
+ s.add_dependency "rails", "~> 6.1"
s.add_dependency "responders"
s.add_dependency "acts_as_tree"
+ s.add_dependency "acts_as_list"
s.add_dependency "active_model_serializers"
s.add_dependency "api-pagination"
@@ -27,8 +28,8 @@ Gem::Specification.new do |s|
s.add_dependency "settingslogic"
s.add_dependency "request_store"
- s.add_dependency "devise"
- s.add_dependency "devise-security"
+ s.add_dependency "devise", "4.7.3"
+ s.add_dependency "devise-security", "0.15.0"
s.add_dependency "devise_zxcvbn"
s.add_dependency "cancancan"
@@ -53,6 +54,7 @@ Gem::Specification.new do |s|
s.add_dependency "rdiscount"
s.add_dependency "searchkick"
+ s.add_dependency "elasticsearch"
s.add_dependency "yomu"
s.add_dependency "typhoeus"
diff --git a/lib/droom.rb b/lib/droom.rb
index 9f10eb087..c8276f24e 100644
--- a/lib/droom.rb
+++ b/lib/droom.rb
@@ -10,9 +10,6 @@
module Droom
- # Droom configuration is handled by accessors on the Droom base module.
- # Boolean items also offer the interrogative form.
-
mattr_accessor :config
@@config = Droom::Config.new
diff --git a/lib/droom/config.rb b/lib/droom/config.rb
index 4a3360d41..726f5dc11 100644
--- a/lib/droom/config.rb
+++ b/lib/droom/config.rb
@@ -57,7 +57,8 @@ class Config
:mc_api_key,
:mc_news_template,
:mc_news_list,
- :session_timeout
+ :session_timeout,
+ :enable_pubsub
def home_url
@home_url ||= "http://example.com"
@@ -287,7 +288,11 @@ def default_permissions
end
def session_timeout
- @@session_timeout ||= 15.minutes
+ @@session_timeout ||= 1.hour
+ end
+
+ def enable_pubsub?
+ !!@enable_pubsub
end
diff --git a/lib/droom/version.rb b/lib/droom/version.rb
index a332e5aa5..97dde8131 100644
--- a/lib/droom/version.rb
+++ b/lib/droom/version.rb
@@ -1,3 +1,3 @@
module Droom
- VERSION = "0.12.3"
+ VERSION = "0.12.4"
end
diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity
index 8901bd844..fac457aa4 100644
--- a/node_modules/.yarn-integrity
+++ b/node_modules/.yarn-integrity
@@ -1,5 +1,5 @@
{
- "systemParams": "darwin-x64-72",
+ "systemParams": "darwin-x64-64",
"modulesFolders": [
"node_modules"
],
@@ -13,7 +13,7 @@
"clipboard@^2.0.0",
"ep-jquery-tokeninput@^1.8.10",
"honeybadger@^1.3.0",
- "imagesloaded@^4.1.3",
+ "imagesloaded@^4.1.4",
"jquery-circle-progress@^1.2.2",
"jquery-deserialize@^2.0.0-rc1",
"jquery.cookie@^1.4.1",
@@ -68,7 +68,7 @@
"har-validator@~5.0.3": "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd",
"honeybadger@^1.3.0": "https://registry.yarnpkg.com/honeybadger/-/honeybadger-1.3.0.tgz#e9f01314d52a26a6d615d65485a0a4d4e50ec8f8",
"http-signature@~1.2.0": "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1",
- "imagesloaded@^4.1.3": "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7",
+ "imagesloaded@^4.1.4": "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7",
"is-typedarray@~1.0.0": "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a",
"isstream@~0.1.2": "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a",
"jquery-circle-progress@^1.2.2": "https://registry.yarnpkg.com/jquery-circle-progress/-/jquery-circle-progress-1.2.2.tgz#260e9130ac8e2b5572eaa7a93b9e8a6b27bc8eea",
diff --git a/package.json b/package.json
index ea422b054..ca250355c 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"clipboard": "^2.0.0",
"ep-jquery-tokeninput": "^1.8.10",
"honeybadger": "^1.3.0",
- "imagesloaded": "^4.1.3",
+ "imagesloaded": "^4.1.4",
"jquery": "^3.2.1",
"jquery-circle-progress": "^1.2.2",
"jquery-deserialize": "^2.0.0-rc1",
diff --git a/spec/dummy/db/migrate/20130724124775_user_properties.droom.rb b/spec/dummy/db/migrate/20130724124775_user_properties.droom.rb
index 40e31c929..544ee193c 100644
--- a/spec/dummy/db/migrate/20130724124775_user_properties.droom.rb
+++ b/spec/dummy/db/migrate/20130724124775_user_properties.droom.rb
@@ -25,7 +25,7 @@ def change
Droom::User.reset_column_information
Droom::Person.all.each do |p|
if u = Droom::User.find(p.user_id)
- u.update_attributes p.attributes.slice(*%w{organisation_id phone description description post_line1 post_line2 post_city post_region post_country post_code mobile image})
+ u.update p.attributes.slice(*%w{organisation_id phone description description post_line1 post_line2 post_city post_region post_country post_code mobile image})
end
end
diff --git a/test/jobs/droom/group_invitation_job_test.rb b/test/jobs/droom/group_invitation_job_test.rb
new file mode 100644
index 000000000..4f49d3a80
--- /dev/null
+++ b/test/jobs/droom/group_invitation_job_test.rb
@@ -0,0 +1,9 @@
+require "test_helper"
+
+module Droom
+ class GroupInvitationJobTest < ActiveJob::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+ end
+end
diff --git a/test/mailers/droom/group_invitation_mailer_test.rb b/test/mailers/droom/group_invitation_mailer_test.rb
new file mode 100644
index 000000000..babe4a6df
--- /dev/null
+++ b/test/mailers/droom/group_invitation_mailer_test.rb
@@ -0,0 +1,9 @@
+require "test_helper"
+
+module Droom
+ class GroupInvitationMailerTest < ActionMailer::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+ end
+end
diff --git a/test/mailers/previews/droom/group_invitation_mailer_preview.rb b/test/mailers/previews/droom/group_invitation_mailer_preview.rb
new file mode 100644
index 000000000..6be437857
--- /dev/null
+++ b/test/mailers/previews/droom/group_invitation_mailer_preview.rb
@@ -0,0 +1,6 @@
+module Droom
+ # Preview all emails at http://localhost:3000/rails/mailers/group_invitation_mailer
+ class GroupInvitationMailerPreview < ActionMailer::Preview
+
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 080f21d3d..851e6d47e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -227,7 +227,7 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
-imagesloaded@^4.1.3:
+imagesloaded@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7"
integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA==