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 = $("