koalagator 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.md +661 -0
- data/README.md +74 -21
- data/app/assets/config/calagator/manifest.js +5 -1
- data/app/assets/images/external_sites/mastodon.png +0 -0
- data/app/assets/images/nav_marker.png +0 -0
- data/app/assets/javascripts/calagator/forms.js +7 -0
- data/app/assets/stylesheets/calagator/custom/calendar.css +137 -0
- data/app/assets/stylesheets/calagator/errors.css +2 -4
- data/app/assets/stylesheets/calagator/forms.scss +5 -0
- data/app/assets/stylesheets/calagator/layout.scss +31 -9
- data/app/assets/stylesheets/calagator/typography.scss +39 -9
- data/app/assets/stylesheets/calagator/utils.scss +44 -0
- data/app/controllers/calagator/admin/curations_controller.rb +62 -0
- data/app/controllers/calagator/admin/users_controller.rb +79 -0
- data/app/controllers/calagator/application_controller.rb +29 -5
- data/app/controllers/calagator/curations_controller.rb +32 -0
- data/app/controllers/calagator/events_controller.rb +6 -0
- data/app/controllers/calagator/paper_trail_manager_controller.rb +5 -0
- data/app/controllers/calagator/passwords_controller.rb +4 -0
- data/app/controllers/calagator/registrations_controller.rb +5 -0
- data/app/controllers/calagator/sessions_controller.rb +4 -0
- data/app/controllers/calagator/site_controller.rb +10 -0
- data/app/controllers/calagator/sources_controller.rb +2 -0
- data/app/controllers/calagator/venues_controller.rb +5 -1
- data/app/controllers/calagator/versions_controller.rb +1 -1
- data/app/controllers/paper_trail_manager/changes_controller.rb +16 -16
- data/app/helpers/calagator/application_helper.rb +32 -3
- data/app/helpers/calagator/time_range_helper.rb +1 -1
- data/app/helpers/paper_trail_manager/changes_helper.rb +7 -7
- data/app/javascript/calagator/calendar/calendar.js +82 -0
- data/app/javascript/calagator/calendar/event.js +94 -0
- data/app/javascript/calagator/calendar/lib/components.js +120 -0
- data/app/javascript/calagator/calendar/lib/utils.js +67 -0
- data/app/models/calagator/curation.rb +32 -0
- data/app/models/calagator/event/browse.rb +3 -2
- data/app/models/calagator/event/cloner.rb +1 -1
- data/app/models/calagator/event/ical_renderer.rb +1 -1
- data/app/models/calagator/event/overview.rb +3 -1
- data/app/models/calagator/event/saver.rb +6 -1
- data/app/models/calagator/event/search.rb +1 -1
- data/app/models/calagator/event/search_engine.rb +1 -1
- data/app/models/calagator/event.rb +22 -5
- data/app/models/calagator/source/parser/hcal.rb +1 -1
- data/app/models/calagator/source/parser.rb +3 -3
- data/app/models/calagator/source.rb +23 -6
- data/app/models/calagator/user.rb +50 -0
- data/app/models/calagator/venue/geocoder.rb +1 -1
- data/app/models/calagator/venue/search.rb +1 -1
- data/app/models/calagator/venue/search_engine.rb +1 -1
- data/app/models/calagator/venue.rb +20 -1
- data/app/models/concerns/calagator/event_filterable.rb +22 -0
- data/app/views/calagator/admin/curations/_form.html.erb +56 -0
- data/app/views/calagator/admin/curations/_index.html.erb +12 -0
- data/app/views/calagator/admin/curations/edit.html.erb +2 -0
- data/app/views/calagator/admin/curations/index.html.erb +21 -0
- data/app/views/calagator/admin/curations/new.html.erb +2 -0
- data/app/views/calagator/admin/index.html.erb +6 -2
- data/app/views/calagator/admin/users/_form.html.erb +28 -0
- data/app/views/calagator/admin/users/edit.html.erb +7 -0
- data/app/views/calagator/admin/users/index.html.erb +38 -0
- data/app/views/calagator/admin/users/invite.html.erb +19 -0
- data/app/views/calagator/admin/users/new.html.erb +3 -0
- data/app/views/calagator/curations/show.html.erb +17 -0
- data/app/views/calagator/events/_index.html.erb +59 -0
- data/app/views/calagator/events/_item.html.erb +10 -3
- data/app/views/calagator/events/_subnav.html.erb +7 -2
- data/app/views/calagator/events/_subnav_custom.html.erb +0 -0
- data/app/views/calagator/events/index.atom.builder +1 -1
- data/app/views/calagator/events/index.html.erb +9 -60
- data/app/views/calagator/events/show.html.erb +5 -3
- data/app/views/calagator/shared/_calendar.html.erb +7 -0
- data/app/views/calagator/shared/_subnav_curations.html.erb +5 -0
- data/app/views/calagator/shared/_subnav_pinned_venues.html.erb +5 -0
- data/app/views/calagator/site/_contact.html.erb +1 -0
- data/app/views/calagator/site/_description.html.erb +2 -2
- data/app/views/calagator/site/about.html.erb +9 -0
- data/app/views/calagator/site/closed_registrations.html.erb +2 -0
- data/app/views/calagator/site/embed.html.erb +16 -0
- data/app/views/calagator/site/index.html.erb +1 -1
- data/app/views/calagator/sources/index.html.erb +1 -1
- data/app/views/calagator/sources/show.html.erb +1 -1
- data/app/views/calagator/venues/_form.html.erb +5 -1
- data/app/views/calagator/venues/_subnav.html.erb +9 -1
- data/app/views/calagator/venues/_subnav_custom.html.erb +0 -0
- data/app/views/calagator/venues/show.html.erb +1 -1
- data/app/views/layouts/calagator/_devise.html.erb +17 -0
- data/app/views/layouts/calagator/_footer.html.erb +3 -1
- data/app/views/layouts/calagator/_head.html.erb +0 -0
- data/app/views/layouts/calagator/_header.html.erb +3 -0
- data/app/views/layouts/calagator/application.html.erb +3 -0
- data/app/views/layouts/calagator/embed.html.erb +15 -0
- data/app/views/paper_trail_manager/changes/_version.html.erb +5 -5
- data/app/views/paper_trail_manager/changes/index.atom.builder +12 -12
- data/bin/{calagator → koalagator} +12 -14
- data/config/importmap.rb +11 -0
- data/config/initializers/admin_user.rb +15 -0
- data/config/initializers/observers.rb +1 -1
- data/config/initializers/paper_trail_manager.rb +1 -1
- data/config/locales/devise.en.yml +65 -0
- data/config/routes.rb +26 -1
- data/db/migrate/20240319042449_devise_create_calagator_users.rb +43 -0
- data/db/migrate/20240319061154_add_admin_flag_to_calagator_user.rb +5 -0
- data/db/migrate/20240320043535_add_name_to_calagator_user.rb +8 -0
- data/db/migrate/20240322035554_add_created_by_to_records.rb +12 -0
- data/db/migrate/20240510051940_create_calagator_curations.rb +15 -0
- data/db/migrate/20240628055300_add_pinned_to_venue.rb +5 -0
- data/db/seeds.rb +49 -0
- data/lib/calagator/decode_html_entities_hack.rb +1 -1
- data/lib/calagator/engine.rb +16 -1
- data/lib/calagator/machine_tag.rb +1 -1
- data/lib/calagator/strip_whitespace.rb +1 -1
- data/lib/calagator/vcalendar.rb +4 -4
- data/lib/calagator/version.rb +4 -1
- data/lib/generators/calagator/install_generator.rb +9 -1
- data/lib/generators/calagator/templates/app/views/devise/registrations/edit.html.erb +48 -0
- data/lib/generators/calagator/templates/app/views/devise/registrations/new.html.erb +29 -0
- data/lib/generators/calagator/templates/config/initializers/01_calagator.rb +34 -6
- data/lib/generators/calagator/templates/config/initializers/04_devise.rb +314 -0
- data/lib/{calagator.rb → koalagator.rb} +15 -3
- data/lib/paper_trail_manager.rb +11 -11
- data/lib/theme_reader.rb +1 -1
- data/rails_template.rb +6 -6
- data/vendor/javascript/@event-calendar--core.js +10 -0
- data/vendor/javascript/@event-calendar--day-grid.js +2 -0
- data/vendor/javascript/@event-calendar--list.js +2 -0
- data/vendor/javascript/ical.js.js +2 -0
- metadata +145 -92
- data/MIT-LICENSE.txt +0 -23
- data/app/models/calagator/event/search_engine/apache_sunspot.rb +0 -106
- data/app/models/calagator/venue/search_engine/apache_sunspot.rb +0 -85
- data/lib/tasks/sunspot_reindex_calagator.rake +0 -20
- data/lib/tasks/sunspot_solr_restart_enhancements.rake +0 -20
- data/lib/wait_for_solr.rb +0 -26
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calagator
|
4
|
+
module Admin
|
5
|
+
class UsersController < Calagator::ApplicationController
|
6
|
+
require_admin
|
7
|
+
before_action :set_user, only: %i[invite show edit update destroy]
|
8
|
+
|
9
|
+
def index
|
10
|
+
@users = User.order(created_at: :desc).paginate(page: params[:page])
|
11
|
+
end
|
12
|
+
|
13
|
+
def new
|
14
|
+
@user = User.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def show
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@user = User.new(user_params)
|
22
|
+
temp_pass = SecureRandom.base36
|
23
|
+
@user.password = temp_pass
|
24
|
+
@user.password_confirmation = temp_pass
|
25
|
+
|
26
|
+
respond_to do |format|
|
27
|
+
if @user.save
|
28
|
+
token = @user.send_reset_password_instructions
|
29
|
+
format.html {
|
30
|
+
redirect_to admin_user_invite_path(@user, token: token), notice: "Successfully created user."
|
31
|
+
}
|
32
|
+
else
|
33
|
+
format.html { render :new, status: :unprocessable_entity }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def edit
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
respond_to do |format|
|
43
|
+
if @user.update(user_params)
|
44
|
+
format.html { redirect_to admin_users_path, notice: "Successfully updated user." }
|
45
|
+
else
|
46
|
+
format.html { render :edit, status: :unprocessable_entity }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy
|
52
|
+
respond_to do |format|
|
53
|
+
if @user.admin? || @user == current_user
|
54
|
+
format.html { redirect_to admin_users_path, notice: "Cannot delete an admin." }
|
55
|
+
else
|
56
|
+
@user.destroy
|
57
|
+
format.html { redirect_to admin_users_path, notice: "Successfully destroyed user." }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def invite
|
63
|
+
redirect_to admin_users_path unless params[:token].present?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def user_params
|
69
|
+
params.require(:user).permit(:email, :name, :display_name, :admin)
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_user
|
73
|
+
@user = User.find(params[:user_id] || params[:id])
|
74
|
+
rescue ActiveRecord::RecordNotFound
|
75
|
+
redirect_to admin_users_path
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# Likewise, all the methods added will be available for all controllers.
|
5
5
|
|
6
6
|
module Calagator
|
7
|
-
class ApplicationController <
|
7
|
+
class ApplicationController < ::ApplicationController
|
8
8
|
helper Calagator::EventsHelper
|
9
9
|
helper Calagator::ApplicationHelper
|
10
10
|
helper Calagator::EventsHelper
|
@@ -33,7 +33,8 @@ module Calagator
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def self.require_admin(options = {})
|
36
|
-
|
36
|
+
return devise_require_admin(options) if Calagator.devise_enabled
|
37
|
+
return false unless Calagator.admin_username && Calagator.admin_password
|
37
38
|
http_basic_authenticate_with(
|
38
39
|
**options.reverse_merge(
|
39
40
|
name: Calagator.admin_username,
|
@@ -43,6 +44,28 @@ module Calagator
|
|
43
44
|
end
|
44
45
|
private_class_method :require_admin
|
45
46
|
|
47
|
+
def self.authorize_resource(resource, options = {})
|
48
|
+
return false unless Calagator.devise_enabled
|
49
|
+
resource = resource.to_s.to_sym
|
50
|
+
return devise_require_admin(options) if Calagator.admin_resources.include?(resource)
|
51
|
+
before_action(:authenticate_user!, **options) if Calagator.user_resources.include?(resource)
|
52
|
+
end
|
53
|
+
private_class_method :authorize_resource
|
54
|
+
|
55
|
+
def self.devise_require_admin(options = {})
|
56
|
+
return false unless Calagator.devise_enabled
|
57
|
+
before_action :authenticate_user!, **options
|
58
|
+
before_action -> {
|
59
|
+
render status: 403, html: "403: Access Denied" unless current_user&.admin?
|
60
|
+
}, **options
|
61
|
+
end
|
62
|
+
private_class_method :devise_require_admin
|
63
|
+
|
64
|
+
def self.nav_section(section, options = {})
|
65
|
+
before_action -> { @nav_section = section }, **options
|
66
|
+
end
|
67
|
+
private_class_method :nav_section
|
68
|
+
|
46
69
|
#---[ Helpers ]---------------------------------------------------------
|
47
70
|
|
48
71
|
# Returns a data structure used for telling the CSS menu which part of the
|
@@ -50,10 +73,11 @@ module Calagator
|
|
50
73
|
# and their values are either "active" or nil.
|
51
74
|
def link_class
|
52
75
|
@_link_class_cache ||= {
|
53
|
-
events: (
|
76
|
+
events: (controller_name == "events" ||
|
54
77
|
controller_name == "sources" ||
|
55
|
-
controller_name == "site"
|
56
|
-
|
78
|
+
controller_name == "site" ||
|
79
|
+
controller_name == "curations") && "active",
|
80
|
+
venues: controller_name == "venues" && "active"
|
57
81
|
}
|
58
82
|
end
|
59
83
|
helper_method :link_class
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calagator
|
4
|
+
class CurationsController < Calagator::ApplicationController
|
5
|
+
before_action :set_curation, only: %i[show]
|
6
|
+
|
7
|
+
def show
|
8
|
+
@browse = Event::Browse.new(params, @curation.events)
|
9
|
+
@events = @browse.events
|
10
|
+
@browse.errors.each { |error| append_flash :failure, error }
|
11
|
+
render_events @events
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def set_curation
|
17
|
+
@curation = Curation.find_by_name(params[:id])
|
18
|
+
redirect_to root_path unless @curation.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_events(events)
|
22
|
+
respond_to do |format|
|
23
|
+
format.html # *.html.erb
|
24
|
+
format.kml # *.kml.erb
|
25
|
+
format.ics { render ics: events || Event.future.non_duplicates }
|
26
|
+
format.atom { render template: "calagator/events/index" }
|
27
|
+
format.xml { render xml: events.to_xml(root: "events", include: :venue) }
|
28
|
+
format.json { render json: events.to_json(include: :venue) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -10,8 +10,11 @@ module Calagator
|
|
10
10
|
include Calagator::DuplicateChecking::ControllerActions
|
11
11
|
require_admin only: %i[duplicates squash_many_duplicates]
|
12
12
|
|
13
|
+
authorize_resource :events, only: %i[new edit create update destroy clone]
|
13
14
|
before_action :find_and_redirect_if_locked, only: %i[edit update destroy]
|
14
15
|
|
16
|
+
nav_section :events
|
17
|
+
|
15
18
|
# GET /events
|
16
19
|
# GET /events.xml
|
17
20
|
def index
|
@@ -46,6 +49,9 @@ module Calagator
|
|
46
49
|
# POST /events.xml
|
47
50
|
def create
|
48
51
|
@event = Event.new
|
52
|
+
if Calagator.devise_enabled && current_user.present?
|
53
|
+
@event.created_by = current_user
|
54
|
+
end
|
49
55
|
create_or_update
|
50
56
|
end
|
51
57
|
|
@@ -3,6 +3,9 @@
|
|
3
3
|
module Calagator
|
4
4
|
class SiteController < Calagator::ApplicationController
|
5
5
|
# Raise exception, mostly for confirming that exception_notification works
|
6
|
+
|
7
|
+
nav_section :about, only: :about
|
8
|
+
|
6
9
|
def omfg
|
7
10
|
raise ArgumentError, "OMFG"
|
8
11
|
end
|
@@ -12,6 +15,10 @@ module Calagator
|
|
12
15
|
render plain: "hello"
|
13
16
|
end
|
14
17
|
|
18
|
+
def embed
|
19
|
+
render layout: "calagator/embed"
|
20
|
+
end
|
21
|
+
|
15
22
|
def index
|
16
23
|
@overview = Event::Overview.new
|
17
24
|
respond_to do |format|
|
@@ -34,5 +41,8 @@ module Calagator
|
|
34
41
|
@url = params[:url]
|
35
42
|
raise ArgumentError if /^javascript:/.match?(@url)
|
36
43
|
end
|
44
|
+
|
45
|
+
def closed_registrations
|
46
|
+
end
|
37
47
|
end
|
38
48
|
end
|
@@ -8,6 +8,10 @@ module Calagator
|
|
8
8
|
include DuplicateChecking::ControllerActions
|
9
9
|
require_admin only: %i[duplicates squash_many_duplicates]
|
10
10
|
|
11
|
+
authorize_resource :venues, only: %i[new edit create update destroy]
|
12
|
+
|
13
|
+
nav_section :venues
|
14
|
+
|
11
15
|
def venue
|
12
16
|
@venue ||= params[:id] ? Venue.find(params[:id]) : Venue.new
|
13
17
|
end
|
@@ -63,7 +67,7 @@ module Calagator
|
|
63
67
|
private
|
64
68
|
|
65
69
|
def show_all_if_not_found
|
66
|
-
|
70
|
+
nil if venue
|
67
71
|
rescue ActiveRecord::RecordNotFound => e
|
68
72
|
redirect_to venues_path, flash: {failure: e.to_s}
|
69
73
|
end
|
@@ -8,7 +8,7 @@ module Calagator
|
|
8
8
|
|
9
9
|
singular = @record.class.name.singularize.underscore.split("/").last
|
10
10
|
plural = @record.class.name.pluralize.underscore.split("/").last
|
11
|
-
instance_variable_set("@#{singular}", @record)
|
11
|
+
instance_variable_set(:"@#{singular}", @record)
|
12
12
|
|
13
13
|
if request.xhr?
|
14
14
|
render partial: "calagator/#{plural}/form", locals: {singular.to_sym => @record}
|
@@ -15,11 +15,11 @@ class PaperTrailManager
|
|
15
15
|
# List changes
|
16
16
|
def index
|
17
17
|
unless change_index_allowed?
|
18
|
-
flash[:error] =
|
18
|
+
flash[:error] = "You do not have permission to list changes."
|
19
19
|
return(redirect_to root_url)
|
20
20
|
end
|
21
21
|
|
22
|
-
@versions = PaperTrail::Version.order(
|
22
|
+
@versions = PaperTrail::Version.order("created_at DESC, id DESC")
|
23
23
|
@versions = @versions.where(item_type: params[:type]) if params[:type]
|
24
24
|
@versions = @versions.where(item_id: params[:id]) if params[:id]
|
25
25
|
|
@@ -31,10 +31,10 @@ class PaperTrailManager
|
|
31
31
|
@per_page = nil if @per_page.zero?
|
32
32
|
|
33
33
|
@versions = if defined?(WillPaginate)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
@versions.paginate(page: @page, per_page: @per_page)
|
35
|
+
else
|
36
|
+
@versions.page(@page).per(@per_page)
|
37
|
+
end
|
38
38
|
|
39
39
|
respond_to do |format|
|
40
40
|
format.html # index.html.erb
|
@@ -48,12 +48,12 @@ class PaperTrailManager
|
|
48
48
|
begin
|
49
49
|
@version = PaperTrail::Version.find(params[:id])
|
50
50
|
rescue ActiveRecord::RecordNotFound
|
51
|
-
flash[:error] =
|
51
|
+
flash[:error] = "No such version."
|
52
52
|
return(redirect_to action: :index)
|
53
53
|
end
|
54
54
|
|
55
55
|
unless change_show_allowed?(@version)
|
56
|
-
flash[:error] =
|
56
|
+
flash[:error] = "You do not have permission to show that change."
|
57
57
|
return(redirect_to action: :index)
|
58
58
|
end
|
59
59
|
|
@@ -68,16 +68,16 @@ class PaperTrailManager
|
|
68
68
|
begin
|
69
69
|
@version = PaperTrail::Version.find(params[:id])
|
70
70
|
rescue ActiveRecord::RecordNotFound
|
71
|
-
flash[:error] =
|
71
|
+
flash[:error] = "No such version."
|
72
72
|
return(redirect_to(changes_path))
|
73
73
|
end
|
74
74
|
|
75
75
|
unless change_revert_allowed?(@version)
|
76
|
-
flash[:error] =
|
76
|
+
flash[:error] = "You do not have permission to revert this change."
|
77
77
|
return(redirect_to changes_path)
|
78
78
|
end
|
79
79
|
|
80
|
-
if @version.event ==
|
80
|
+
if @version.event == "create"
|
81
81
|
@record = @version.item_type.constantize.find(@version.item_id)
|
82
82
|
@result = @record.destroy
|
83
83
|
else
|
@@ -86,11 +86,11 @@ class PaperTrailManager
|
|
86
86
|
end
|
87
87
|
|
88
88
|
if @result
|
89
|
-
if @version.event ==
|
90
|
-
flash[:notice] =
|
89
|
+
if @version.event == "create"
|
90
|
+
flash[:notice] = "Rolled back newly-created record by destroying it."
|
91
91
|
redirect_to changes_path
|
92
92
|
else
|
93
|
-
flash[:notice] =
|
93
|
+
flash[:notice] = "Rolled back changes to this record."
|
94
94
|
redirect_to change_item_url(@version)
|
95
95
|
end
|
96
96
|
else
|
@@ -103,8 +103,8 @@ class PaperTrailManager
|
|
103
103
|
|
104
104
|
# Return the URL for the item represented by the +version+, e.g. a Company record instance referenced by a version.
|
105
105
|
def change_item_url(version)
|
106
|
-
version_type = version.item_type.underscore.split(
|
107
|
-
send("#{version_type}_url", version.item_id)
|
106
|
+
version_type = version.item_type.underscore.split("/").last
|
107
|
+
send(:"#{version_type}_url", version.item_id)
|
108
108
|
rescue NoMethodError
|
109
109
|
nil
|
110
110
|
end
|
@@ -5,6 +5,10 @@ module Calagator
|
|
5
5
|
module ApplicationHelper
|
6
6
|
include TimeRangeHelper
|
7
7
|
|
8
|
+
def distro_name
|
9
|
+
NAME.to_s
|
10
|
+
end
|
11
|
+
|
8
12
|
# Returns HTML string of an event or venue description for display in a view.
|
9
13
|
def format_description(string)
|
10
14
|
sanitize(auto_link(upgrade_br(markdown(string))))
|
@@ -19,6 +23,21 @@ module Calagator
|
|
19
23
|
content.gsub("<br>", "<br />")
|
20
24
|
end
|
21
25
|
|
26
|
+
def display_username(user)
|
27
|
+
name = sanitize(user.name)
|
28
|
+
display_name = sanitize(user.display_name)
|
29
|
+
admin_flag = (user.admin? ? "<span title=\"Administrator\">#{Calagator.admin_icon} </span>" : nil)
|
30
|
+
raw "<span title=\"@#{name}\">#{admin_flag}#{display_name}</span>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def nav_section
|
34
|
+
@nav_section || :root
|
35
|
+
end
|
36
|
+
|
37
|
+
def active_on(*sections)
|
38
|
+
sections.map { |c| c.to_s }.include?(nav_section.to_s) ? "active" : nil
|
39
|
+
end
|
40
|
+
|
22
41
|
FLASH_TYPES = %i[success failure].freeze
|
23
42
|
|
24
43
|
def render_flash
|
@@ -48,11 +67,16 @@ module Calagator
|
|
48
67
|
else
|
49
68
|
"imported from #{link_to truncate(item.source.name, length: 40), url_for(item.source)}"
|
50
69
|
end
|
70
|
+
creator = if item&.created_by_id?
|
71
|
+
" by:<br />#{display_username(item.created_by)}"
|
72
|
+
elsif item&.created_by_name?
|
73
|
+
" by:<br />#{CGI.escapeHTML(item.created_by_name)}"
|
74
|
+
end
|
51
75
|
created = " <br /><strong>#{normalize_time(item.created_at, format: :html)}</strong>"
|
52
76
|
updated = if item.updated_at > item.created_at
|
53
77
|
" and last updated <br /><strong>#{normalize_time(item.updated_at, format: :html)}</strong>"
|
54
78
|
end
|
55
|
-
raw "This item was #{source}#{created}#{updated}."
|
79
|
+
raw "This item was #{source}#{creator}#{created}#{updated}."
|
56
80
|
end
|
57
81
|
|
58
82
|
# Caches +block+ in view only if the +condition+ is true.
|
@@ -65,10 +89,15 @@ module Calagator
|
|
65
89
|
end
|
66
90
|
end
|
67
91
|
|
68
|
-
def subnav_class_for(controller_name, action_name)
|
92
|
+
def subnav_class_for(controller_name, action_name, id_name = nil)
|
93
|
+
id_fail = false
|
94
|
+
if id_name.present?
|
95
|
+
id_fail = (id_name != params[:id])
|
96
|
+
end
|
97
|
+
|
69
98
|
css_class = "#{controller.controller_name}_#{controller.action_name}_subnav"
|
70
99
|
if [controller.controller_name, controller.action_name] == [controller_name, action_name]
|
71
|
-
css_class += " active"
|
100
|
+
css_class += " active" unless id_fail
|
72
101
|
end
|
73
102
|
css_class
|
74
103
|
end
|
@@ -155,7 +155,7 @@ module Calagator
|
|
155
155
|
|
156
156
|
def text
|
157
157
|
part.parts.reduce("") do |string, (key, value)|
|
158
|
-
prefix =
|
158
|
+
prefix = PREFIXES[[@last_key, key]] || PREFIXES[key]
|
159
159
|
suffix = SUFFIXES[key]
|
160
160
|
@last_key = key
|
161
161
|
"#{string}#{prefix}#{value}#{suffix}"
|
@@ -5,7 +5,7 @@ class PaperTrailManager
|
|
5
5
|
# Return HTML representing the +object+, which is either its text or a stylized "nil".
|
6
6
|
def text_or_nil(object)
|
7
7
|
if object.nil?
|
8
|
-
content_tag(
|
8
|
+
content_tag("em", "nil")
|
9
9
|
else
|
10
10
|
h(object)
|
11
11
|
end
|
@@ -28,18 +28,18 @@ class PaperTrailManager
|
|
28
28
|
# }
|
29
29
|
def changes_for(version)
|
30
30
|
case version.event
|
31
|
-
when
|
31
|
+
when "create", "update"
|
32
32
|
return {} unless version.changeset
|
33
33
|
|
34
34
|
version.changeset.inject({}) do |changes, (attr, (prev, curr))|
|
35
|
-
changes.store(attr, {
|
35
|
+
changes.store(attr, {previous: prev, current: curr}) && changes
|
36
36
|
end
|
37
|
-
when
|
37
|
+
when "destroy"
|
38
38
|
record = version_reify(version)
|
39
39
|
return {} unless record
|
40
40
|
|
41
41
|
record.attributes.reject { |_k, v| v.nil? }.inject({}) do |changes, (attr, value)|
|
42
|
-
changes.store(attr, {
|
42
|
+
changes.store(attr, {previous: value, current: nil}) && changes
|
43
43
|
end
|
44
44
|
else
|
45
45
|
raise ArgumentError, "Unknown event: #{version.event}"
|
@@ -67,9 +67,9 @@ class PaperTrailManager
|
|
67
67
|
# Returns HTML link for the item stored in the version, e.g. a link to a Company record stored in the version.
|
68
68
|
def change_item_link(version)
|
69
69
|
if (url = change_item_url(version))
|
70
|
-
link_to(change_title_for(version), url, class:
|
70
|
+
link_to(change_title_for(version), url, class: "change_item")
|
71
71
|
else
|
72
|
-
content_tag(:span, change_title_for(version), class:
|
72
|
+
content_tag(:span, change_title_for(version), class: "change_item")
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import {CalendarEvent} from 'calendar/event'
|
2
|
+
import {BaseComponent} from 'calendar/lib/components'
|
3
|
+
import ICAL from "ical.js";
|
4
|
+
import EventCalendar from "@event-calendar/core"
|
5
|
+
import ListView from "@event-calendar/list"
|
6
|
+
import DayGrid from "@event-calendar/day-grid"
|
7
|
+
|
8
|
+
class Calendar extends BaseComponent {
|
9
|
+
static validViews = ["dayGridMonth", "listMonth"]
|
10
|
+
static {
|
11
|
+
this.observe("src", "view")
|
12
|
+
}
|
13
|
+
|
14
|
+
calendar
|
15
|
+
|
16
|
+
constructor() {
|
17
|
+
super()
|
18
|
+
|
19
|
+
this.calendar = this._setupCalendar()
|
20
|
+
}
|
21
|
+
|
22
|
+
onSrcChanged(_, value) { this._loadICAL(value) }
|
23
|
+
|
24
|
+
onViewChanged(_, value) {
|
25
|
+
if (!Calendar.validViews.includes(value)) {
|
26
|
+
this.setAttribute('view', validViews[0])
|
27
|
+
return
|
28
|
+
}
|
29
|
+
this.calendar.setOption('view', value)
|
30
|
+
}
|
31
|
+
|
32
|
+
_loadICAL(source) {
|
33
|
+
if (source == "") { return; }
|
34
|
+
const calendar = this.calendar;
|
35
|
+
|
36
|
+
fetch(source)
|
37
|
+
.then(r => r.text())
|
38
|
+
.then(text => this._parseICAL(calendar, text))
|
39
|
+
}
|
40
|
+
|
41
|
+
_parseICAL(calendar, rawText) {
|
42
|
+
const jCal = ICAL.parse(rawText)
|
43
|
+
const parser = new ICAL.ComponentParser
|
44
|
+
|
45
|
+
const events = []
|
46
|
+
parser.onevent = (event) => {
|
47
|
+
events.push(
|
48
|
+
{
|
49
|
+
id: event.uid,
|
50
|
+
start: event.startDate.toJSDate(),
|
51
|
+
end: event.endDate.toJSDate(),
|
52
|
+
title: event.summary,
|
53
|
+
color: event.color || "#59a12d",
|
54
|
+
editable: false,
|
55
|
+
extendedProps: event
|
56
|
+
}
|
57
|
+
)
|
58
|
+
};
|
59
|
+
|
60
|
+
parser.oncomplete = () => {
|
61
|
+
calendar.setOption("events", events)
|
62
|
+
}
|
63
|
+
parser.process(jCal)
|
64
|
+
}
|
65
|
+
|
66
|
+
_setupCalendar() {
|
67
|
+
return new EventCalendar({
|
68
|
+
target: this,
|
69
|
+
props: {
|
70
|
+
plugins: [ListView, DayGrid],
|
71
|
+
options: {
|
72
|
+
view: "dayGridMonth",
|
73
|
+
events: [],
|
74
|
+
firstDay: 1,
|
75
|
+
eventContent: info => { return {domNodes: [new CalendarEvent(info.event)]} }
|
76
|
+
}
|
77
|
+
}
|
78
|
+
})
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
window.customElements.define("events-calendar", Calendar)
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class CalendarEvent extends HTMLElement {
|
2
|
+
constructor(event) {
|
3
|
+
super()
|
4
|
+
this._setupDom(event)
|
5
|
+
}
|
6
|
+
|
7
|
+
_setupDom(event) {
|
8
|
+
console.log(event)
|
9
|
+
const body = createElement("div", "event-body")
|
10
|
+
const title = createElement("h4", "event-title")
|
11
|
+
const detailsContainer = createElement("div", "event-details-container")
|
12
|
+
const detailsArrow = createElement("div", "event-details-arrow")
|
13
|
+
|
14
|
+
title.innerText = event.title
|
15
|
+
|
16
|
+
detailsArrow.append(this._createDetails(event))
|
17
|
+
detailsContainer.append(detailsArrow)
|
18
|
+
|
19
|
+
body.append(this._createTimeLabel(event.start, event.end))
|
20
|
+
body.append(title)
|
21
|
+
this.append(body)
|
22
|
+
this.append(detailsContainer)
|
23
|
+
|
24
|
+
document.addEventListener("click", () => {
|
25
|
+
if (this.matches(":hover")) {
|
26
|
+
this.setAttribute("open", "")
|
27
|
+
} else {
|
28
|
+
this.removeAttribute("open")
|
29
|
+
}
|
30
|
+
})
|
31
|
+
}
|
32
|
+
|
33
|
+
_createTimeLabel(start, end) {
|
34
|
+
const time = createElement("time", "event-time")
|
35
|
+
time.setAttribute("datetime", start.toISOString())
|
36
|
+
|
37
|
+
let innerText = start.toLocaleTimeString(undefined, {timeStyle: "short"})
|
38
|
+
if (end) {
|
39
|
+
innerText += " - " + end.toLocaleTimeString(undefined, {timeStyle: "short"})
|
40
|
+
}
|
41
|
+
|
42
|
+
time.innerText = innerText
|
43
|
+
return time
|
44
|
+
}
|
45
|
+
|
46
|
+
_createDetails(event) {
|
47
|
+
const details = createElement("div", "event-details")
|
48
|
+
const header = createElement("h4")
|
49
|
+
const description = createElement("div", "event-description")
|
50
|
+
const link = createElement("a")
|
51
|
+
|
52
|
+
header.innerText = event.title
|
53
|
+
link.setAttribute("href", event.id)
|
54
|
+
link.setAttribute("target", "_blank")
|
55
|
+
link.innerText = "View Event"
|
56
|
+
|
57
|
+
details.append(header)
|
58
|
+
|
59
|
+
if (event.extendedProps.location) {
|
60
|
+
const address = createElement("address")
|
61
|
+
const addr = event.extendedProps.location.replaceAll(" ", "")
|
62
|
+
address.innerText = addr;
|
63
|
+
|
64
|
+
details.append(address)
|
65
|
+
}
|
66
|
+
|
67
|
+
if (event.extendedProps.description) {
|
68
|
+
const desc = event.extendedProps.description.replaceAll(" ", "").split("\n")
|
69
|
+
desc.forEach(line => {
|
70
|
+
if (line == "") { return }
|
71
|
+
const p = document.createElement("p")
|
72
|
+
p.innerText = line
|
73
|
+
description.append(p)
|
74
|
+
})
|
75
|
+
}
|
76
|
+
|
77
|
+
details.append(description)
|
78
|
+
details.append(link)
|
79
|
+
|
80
|
+
return details
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
function createElement(element, ...classes) {
|
85
|
+
const instance = document.createElement(element)
|
86
|
+
if (classes.length > 0) {
|
87
|
+
instance.classList.add(classes)
|
88
|
+
}
|
89
|
+
return instance
|
90
|
+
}
|
91
|
+
|
92
|
+
window.customElements.define("calendar-event", CalendarEvent)
|
93
|
+
|
94
|
+
export {CalendarEvent}
|