├── .gitignore ├── .npmrc ├── .prettierignore ├── vendor └── holidays │ ├── Gemfile │ ├── lib │ ├── holidays │ │ ├── version.rb │ │ ├── errors.rb │ │ ├── definition │ │ │ ├── entity │ │ │ │ ├── custom_method.rb │ │ │ │ └── test.rb │ │ │ ├── decorator │ │ │ │ ├── custom_method_proc.rb │ │ │ │ ├── custom_method_source.rb │ │ │ │ └── test.rb │ │ │ ├── validator │ │ │ │ ├── custom_method.rb │ │ │ │ └── region.rb │ │ │ ├── context │ │ │ │ ├── merger.rb │ │ │ │ └── load.rb │ │ │ └── repository │ │ │ │ ├── custom_methods.rb │ │ │ │ ├── regions.rb │ │ │ │ └── cache.rb │ │ ├── core_extensions │ │ │ └── time.rb │ │ ├── finder │ │ │ └── rules │ │ │ │ └── in_region.rb │ │ └── factory │ │ │ └── date_calculator.rb │ └── generated_definitions │ │ ├── vi.rb │ │ ├── ecbtarget.rb │ │ ├── tn.rb │ │ ├── ma.rb │ │ └── nerc.rb │ ├── bin │ ├── setup │ └── console │ ├── definitions │ ├── spec │ │ ├── spec_helper.rb │ │ ├── data │ │ │ ├── invalid │ │ │ │ └── months │ │ │ │ │ ├── missing │ │ │ │ │ └── no_months.yaml │ │ │ │ │ └── malformed │ │ │ │ │ └── bad.yaml │ │ │ └── valid │ │ │ │ └── simple.yaml │ │ └── coverage_report.rb │ ├── Makefile │ ├── Gemfile │ ├── lib │ │ └── validation │ │ │ ├── error.rb │ │ │ ├── definition_validator.rb │ │ │ └── custom_method_validator.rb │ ├── doc │ │ └── architecture │ │ │ └── README.md │ ├── README.md │ ├── LICENSE │ └── vi.yaml │ ├── test │ ├── data │ │ ├── test_custom_govt_holiday_defs.yaml │ │ ├── test_invalid_region.rb │ │ ├── test_region.rb │ │ ├── test_single_custom_holiday_defs.yaml │ │ ├── test_custom_informal_holidays_defs.yaml │ │ ├── test_multiple_custom_holiday_defs.yaml │ │ ├── test_single_custom_holiday_with_custom_procs.yaml │ │ ├── test_custom_year_range_holiday_defs.yaml │ │ ├── test_multiple_regions_with_conflicts_region_1.yaml │ │ └── test_multiple_regions_with_conflicts_region_2.yaml │ ├── defs │ │ ├── test_defs_in.rb │ │ ├── test_defs_sa.rb │ │ ├── test_defs_sg.rb │ │ ├── test_defs_unitednations.rb │ │ ├── test_defs_id.rb │ │ ├── test_defs_ae.rb │ │ ├── test_defs_fed_ex.rb │ │ ├── test_defs_vi.rb │ │ ├── test_defs_tn.rb │ │ ├── test_defs_ecbtarget.rb │ │ ├── test_defs_ph.rb │ │ ├── test_defs_ng.rb │ │ ├── test_defs_cr.rb │ │ ├── test_defs_ke.rb │ │ ├── test_defs_nerc.rb │ │ ├── test_defs_ups.rb │ │ ├── test_defs_fedex.rb │ │ ├── test_defs_ma.rb │ │ └── test_defs_at.rb │ ├── integration │ │ ├── README.md │ │ ├── test_custom_informal_holidays.rb │ │ ├── test_available_regions.rb │ │ ├── test_nonstandard_regions.rb │ │ └── test_multiple_regions_with_conflict.rb │ ├── holidays │ │ ├── date_calculator │ │ │ └── test_day_of_month.rb │ │ ├── definition │ │ │ ├── context │ │ │ │ ├── test_merger.rb │ │ │ │ └── test_load.rb │ │ │ └── repository │ │ │ │ └── test_custom_methods.rb │ │ └── finder │ │ │ └── rules │ │ │ └── test_in_region.rb │ ├── coverage_report.rb │ └── test_helper.rb │ ├── doc │ └── REFERENCES │ ├── Gemfile.lock │ ├── Makefile │ ├── LICENSE │ └── holidays.gemspec ├── .prettierrc.cjs ├── .template-lintrc.cjs ├── stylelint.config.mjs ├── .streerc ├── assets ├── stylesheets │ ├── common │ │ ├── user-preferences.scss │ │ ├── discourse-calendar-holidays.scss │ │ ├── discourse-post-event-upcoming-events.scss │ │ ├── discourse-post-event-preview.scss │ │ ├── discourse-post-event-bulk-invite-modal.scss │ │ └── discourse-post-event-core-ext.scss │ ├── mobile │ │ ├── discourse-post-event-invitees.scss │ │ ├── discourse-post-event-core-ext.scss │ │ ├── discourse-post-event.scss │ │ └── discourse-calendar.scss │ ├── desktop │ │ ├── discourse-post-event-invitees.scss │ │ └── discourse-calendar.scss │ └── colors.scss └── javascripts │ └── discourse │ ├── admin-calendar-route-map.js │ ├── templates │ ├── discourse-post-event-upcoming-events.gjs │ ├── discourse-post-event-upcoming-events-index.gjs │ ├── discourse-post-event-upcoming-events-mine.gjs │ └── admin-plugins-calendar.gjs │ ├── controllers │ ├── discourse-post-event-upcoming-events-mine.js │ ├── discourse-post-event-upcoming-events-index.js │ └── admin-plugins-calendar.js │ ├── adapters │ ├── discourse-post-event-adapter.js │ ├── discourse-post-event-invitee.js │ ├── discourse-post-event-reminder.js │ └── discourse-post-event-event.js │ ├── connectors │ ├── header-topic-title-suffix │ │ └── event-date-container.gjs │ ├── before-topic-list-body │ │ └── category-calendar.gjs │ ├── discovery-list-container-top │ │ └── category-events-calendar.gjs │ └── topic-list-after-title │ │ └── event-badge.gjs │ ├── models │ ├── discourse-post-event-reminder.js │ ├── discourse-post-event-event-stats.js │ ├── discourse-post-event-invitee.js │ └── discourse-post-event-invitees.js │ ├── discourse-event-upcoming-events-route-map.js │ ├── helpers │ ├── format-future-date.js │ └── format-event-name.js │ ├── pre-initializers │ └── transformers.js │ ├── components │ ├── discourse-post-event │ │ ├── description.gjs │ │ ├── location.gjs │ │ ├── chat-channel.gjs │ │ ├── url.gjs │ │ ├── creator.gjs │ │ └── event-status.gjs │ ├── group-timezones │ │ └── new-day.gjs │ ├── modal │ │ └── post-event-invitees │ │ │ └── user.gjs │ ├── event-field.gjs │ ├── admin-holidays-list.gjs │ ├── bulk-invite-sample-csv-file.gjs │ ├── toggle-invitees.gjs │ └── region-input.js │ ├── lib │ ├── guess-best-date-format.js │ ├── calendar-locale.js │ ├── full-calendar-default-options.js │ ├── colors.js │ ├── add-recurrent-events.js │ ├── popover.js │ └── discourse-markdown │ │ └── discourse-post-event-block.js │ ├── services │ └── discourse-post-event-service.js │ ├── routes │ ├── discourse-post-event-upcoming-events-index.js │ └── discourse-post-event-upcoming-events-mine.gjs │ ├── initializers │ ├── disable-sort.js │ ├── add-upcoming-events-to-sidebar.js │ └── event-relative-date.js │ └── api-initializers │ └── discourse-group-timezones.gjs ├── .rubocop.yml ├── Gemfile ├── eslint.config.mjs ├── lib ├── discourse_calendar │ ├── engine.rb │ └── site_settings │ │ ├── map_events_title_json_schema.rb │ │ └── map_event_tag_colors_json_schema.rb ├── discourse_post_event │ ├── engine.rb │ ├── post_extension.rb │ ├── export_csv_file_extension.rb │ ├── rrule_generator.rb │ └── export_csv_controller_extension.rb ├── calendar_settings_validator.rb ├── group_timezones.rb ├── calendar_validator.rb ├── event_validator.rb ├── holiday_status.rb └── tasks │ └── javascript.rake ├── spec ├── system │ ├── core_features_spec.rb │ ├── page_objects │ │ └── discourse_calendar │ │ │ ├── upcoming_events.rb │ │ │ ├── post_event_form.rb │ │ │ └── bulk_invite_modal.rb │ ├── disable_sort_spec.rb │ ├── group_timezones_spec.rb │ └── category_calendar_spec.rb ├── fabricators │ ├── calendar_event_fabricator.rb │ └── event_fabricator.rb ├── lib │ ├── calendar_settings_validator_spec.rb │ └── group_timezones_spec.rb ├── integration │ ├── topic_spec.rb │ ├── curently_away_report_spec.rb │ └── invitee_spec.rb ├── requests │ └── site_spec.rb ├── serializers │ ├── user_serializer_spec.rb │ └── discourse_post_event │ │ └── post_serializer_spec.rb ├── services │ └── discourse_post_event │ │ └── chat_channel_sync_spec.rb └── jobs │ └── regular │ └── discourse_post_event │ └── bump_topic_spec.rb ├── app ├── controllers │ └── discourse_post_event │ │ ├── upcoming_events_controller.rb │ │ └── discourse_post_event_controller.rb ├── serializers │ ├── user_timezone_serializer.rb │ └── discourse_post_event │ │ ├── invitee_list_serializer.rb │ │ ├── invitee_serializer.rb │ │ └── event_stats_serializer.rb ├── models │ └── discourse_calendar │ │ └── disabled_holiday.rb └── services │ └── discourse_calendar │ └── holiday.rb ├── translator.yml ├── .github └── workflows │ └── discourse-plugin.yml ├── config └── locales │ ├── server.en_GB.yml │ ├── server.te.yml │ ├── server.bs_BA.yml │ ├── server.lv.yml │ ├── server.th.yml │ ├── server.bg.yml │ ├── server.sq.yml │ ├── server.sr.yml │ ├── client.en_GB.yml │ ├── server.zh_TW.yml │ ├── server.gl.yml │ ├── server.vi.yml │ ├── server.et.yml │ ├── server.sw.yml │ ├── server.ug.yml │ ├── server.nb_NO.yml │ ├── server.be.yml │ ├── server.ca.yml │ ├── server.hy.yml │ ├── server.hr.yml │ ├── server.el.yml │ ├── server.sk.yml │ ├── server.ur.yml │ ├── server.id.yml │ ├── server.pt.yml │ ├── server.ko.yml │ ├── server.ro.yml │ ├── client.sr.yml │ ├── client.sq.yml │ ├── server.fa_IR.yml │ └── client.be.yml ├── db └── migrate │ ├── 20211216124303_add_timezone_to_calendar_events.rb │ ├── 20250526154632_add_recurrence_until.rb │ ├── 20250616101944_add_location_to_event.rb │ ├── 20250616101945_add_description_to_event.rb │ ├── 20221223210225_add_minimal_option_to_calendar_event.rb │ ├── 20250602114410_add_show_local_time.rb │ ├── 20220228163400_adds_timezone_to_discourse_post_event_event.rb │ ├── 20240513140542_add_closed_to_discourse_post_event.rb │ ├── 20200810190429_drop_reminders_table.rb │ ├── 20200810185432_refactor_reminders.rb │ ├── 20200409181607_remove_display_invitees.rb │ ├── 20200729094848_add_url_column_to_event.rb │ ├── 20200310200000_remove_timezone_custom_field.rb │ ├── 20200812193122_add_recurrence_to_events.rb │ ├── 20250520042223_add_chat_fields_to_events.rb │ ├── 20200805133257_add_custom_fields_to_event.rb │ ├── 20200809154642_create_reminders_table.rb │ ├── 20200409102642_rename_setting_to_discourse_post_event.rb │ ├── 20200327195549_add_topic_custom_field_post_event_date_index.rb │ ├── 20200926144256_add_unique_index_to_topic_event_ends_at_custom_field.rb │ ├── 20231123233308_delete_duplicated_holidays.rb │ ├── 20200805073343_drop_old_discourse_calendar_tables.rb │ ├── 20200409102639_drop_incorrect_future_schema_migrations.rb │ ├── 20220604200919_create_disabled_holidays.rb │ ├── 20200409120815_rename_topic_custom_field_topic_post_event_starts_at_index.rb │ ├── 20200409102641_create_invitees_table.rb │ ├── 20201110225115_create_post_event_dates_table.rb │ ├── 20190724181542_add_on_holiday_index.rb │ ├── 20200409102640_create_post_events_table.rb │ ├── 20231124021939_delete_similar_holidays.rb │ ├── 20221121165352_add_type_field_to_events_reminders.rb │ └── 20220724130519_fix_post_event_timezones.rb ├── test └── javascripts │ ├── helpers │ └── get-event-by-text.js │ ├── acceptance │ ├── sort-event-topics-test.js │ └── topic-calendar-events-test.js │ └── lib │ └── raw-event-helper-test.js ├── package.json ├── jobs └── regular │ └── discourse_post_event │ ├── bump_topic.rb │ └── event_started.rb ├── .discourse-compatibility └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /gems 3 | /auto_generated 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict = true 2 | auto-install-peers = false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | assets/stylesheets/vendor/*.scss 2 | public/ 3 | -------------------------------------------------------------------------------- /vendor/holidays/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/prettier"); 2 | -------------------------------------------------------------------------------- /.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/template-lint"); 2 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/version.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | VERSION = '8.4.1' 3 | end 4 | -------------------------------------------------------------------------------- /stylelint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ["@discourse/lint-configs/stylelint"], 3 | }; 4 | -------------------------------------------------------------------------------- /vendor/holidays/bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.streerc: -------------------------------------------------------------------------------- 1 | --print-width=100 2 | --plugins=plugin/trailing_comma,plugin/disable_auto_ternary 3 | --ignore-files=vendor/* 4 | -------------------------------------------------------------------------------- /assets/stylesheets/common/user-preferences.scss: -------------------------------------------------------------------------------- 1 | .user-preferences { 2 | .region details { 3 | min-width: 175px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-discourse: stree-compat.yml 3 | AllCops: 4 | Exclude: 5 | - vendor/**/* 6 | - gems/**/* 7 | -------------------------------------------------------------------------------- /vendor/holidays/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "holidays" 5 | 6 | require "irb" 7 | IRB.start 8 | -------------------------------------------------------------------------------- /assets/stylesheets/mobile/discourse-post-event-invitees.scss: -------------------------------------------------------------------------------- 1 | .post-event-invitees-modal { 2 | .modal-inner-container { 3 | min-width: 90vw; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/stylesheets/desktop/discourse-post-event-invitees.scss: -------------------------------------------------------------------------------- 1 | .post-event-invitees-modal { 2 | .modal-inner-container { 3 | min-width: 350px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << '../lib' 2 | 3 | require 'coverage_report' 4 | 5 | RSpec.configure do |c| 6 | c.order = :random 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | group :development do 6 | gem "rubocop-discourse" 7 | gem "syntax_tree" 8 | end 9 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_custom_govt_holiday_defs.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 3: 3 | - name: Yaaay Government Day 4 | regions: [custom_multiple_files, custom_multiple_files_govt] 5 | mday: 1 -------------------------------------------------------------------------------- /assets/javascripts/discourse/admin-calendar-route-map.js: -------------------------------------------------------------------------------- 1 | export default { 2 | resource: "admin.adminPlugins", 3 | path: "/plugins", 4 | map() { 5 | this.route("calendar"); 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import DiscourseRecommended from "@discourse/lint-configs/eslint"; 2 | 3 | export default [ 4 | ...DiscourseRecommended, 5 | { 6 | ignores: ["public/**/*"], 7 | }, 8 | ]; 9 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/Makefile: -------------------------------------------------------------------------------- 1 | default: validate 2 | 3 | validate: 4 | bundle exec ruby lib/validation/run.rb 5 | 6 | test: 7 | bundle exec rspec 8 | 9 | .PHONY: validate,test 10 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/discourse-post-event-upcoming-events.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | 3 | export default RouteTemplate(); 4 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/spec/data/invalid/months/missing/no_months.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | methods: 3 | test: 4 | arguments: year 5 | source: | 6 | true 7 | 8 | tests: | 9 | test 10 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec', '~> 3' 4 | gem 'pry', :require => false 5 | gem 'spoon', :require => false 6 | gem 'simplecov-rcov', :require => false 7 | -------------------------------------------------------------------------------- /lib/discourse_calendar/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ::DiscourseCalendar 4 | class Engine < ::Rails::Engine 5 | engine_name PLUGIN_NAME 6 | isolate_namespace DiscourseCalendar 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/system/core_features_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Core features", type: :system do 4 | before { enable_current_plugin } 5 | 6 | it_behaves_like "having working core features" 7 | end 8 | -------------------------------------------------------------------------------- /lib/discourse_post_event/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class Engine < ::Rails::Engine 5 | engine_name PLUGIN_NAME 6 | isolate_namespace DiscoursePostEvent 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/discourse_post_event/upcoming_events_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class UpcomingEventsController < DiscoursePostEventController 5 | def index 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /assets/stylesheets/common/discourse-calendar-holidays.scss: -------------------------------------------------------------------------------- 1 | .region-input { 2 | width: 50%; 3 | } 4 | 5 | .disabled td { 6 | background-color: var(--primary-very-low); 7 | color: var(--primary-medium); 8 | font-style: italic; 9 | } 10 | -------------------------------------------------------------------------------- /translator.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for discourse-translator-bot 2 | 3 | files: 4 | - source_path: config/locales/client.en.yml 5 | destination_path: client.yml 6 | - source_path: config/locales/server.en.yml 7 | destination_path: server.yml 8 | -------------------------------------------------------------------------------- /.github/workflows/discourse-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Discourse Plugin 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | ci: 11 | uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 12 | -------------------------------------------------------------------------------- /config/locales/server.en_GB.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | en_GB: 8 | -------------------------------------------------------------------------------- /db/migrate/20211216124303_add_timezone_to_calendar_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTimezoneToCalendarEvents < ActiveRecord::Migration[6.1] 4 | def change 5 | add_column :calendar_events, :timezone, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20250526154632_add_recurrence_until.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | class AddRecurrenceUntil < ActiveRecord::Migration[7.2] 4 | def change 5 | add_column :discourse_post_event_events, :recurrence_until, :datetime 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-mine.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | 3 | export default class DiscoursePostEventUpcomingEventsMineController extends Controller { 4 | queryParams = ["view"]; 5 | } 6 | -------------------------------------------------------------------------------- /db/migrate/20250616101944_add_location_to_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddLocationToEvent < ActiveRecord::Migration[7.2] 4 | def change 5 | add_column :discourse_post_event_events, :location, :string, limit: 1000 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/serializers/user_timezone_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserTimezoneSerializer < BasicUserSerializer 4 | attributes :timezone, :on_holiday 5 | 6 | def on_holiday 7 | @options[:on_holiday] || false 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-index.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | 3 | export default class DiscoursePostEventUpcomingEventsIndexController extends Controller { 4 | queryParams = ["view"]; 5 | } 6 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/discourse-post-event-adapter.js: -------------------------------------------------------------------------------- 1 | import RestAdapter from "discourse/adapters/rest"; 2 | 3 | export default class DiscoursePostEventAdapter extends RestAdapter { 4 | basePath() { 5 | return "/discourse-post-event/"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /db/migrate/20250616101945_add_description_to_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddDescriptionToEvent < ActiveRecord::Migration[7.2] 4 | def change 5 | add_column :discourse_post_event_events, :description, :string, limit: 1000 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20221223210225_add_minimal_option_to_calendar_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddMinimalOptionToCalendarEvent < ActiveRecord::Migration[7.0] 4 | def change 5 | add_column :discourse_post_event_events, :minimal, :boolean 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/spec/data/valid/simple.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | months: 3 | 1: 4 | - name: Test Holiday 5 | regions: [test] 6 | mday: 1 7 | 8 | methods: 9 | test: 10 | arguments: year 11 | source: | 12 | true 13 | 14 | tests: | 15 | test 16 | -------------------------------------------------------------------------------- /db/migrate/20250602114410_add_show_local_time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddShowLocalTime < ActiveRecord::Migration[7.2] 4 | def change 5 | add_column :discourse_post_event_events, :show_local_time, :boolean, default: false, null: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20220228163400_adds_timezone_to_discourse_post_event_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddsTimezoneToDiscoursePostEventEvent < ActiveRecord::Migration[6.1] 4 | def change 5 | add_column :discourse_post_event_events, :timezone, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/spec/data/invalid/months/malformed/bad.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | months: 3 | 1: 4 | - name: Test Holiday 5 | regions: [test] 6 | mday: 1 7 | 8 | methods: 9 | test: 10 | arguments: year 11 | source: | 12 | true 13 | 14 | tests: | 15 | test 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/connectors/header-topic-title-suffix/event-date-container.gjs: -------------------------------------------------------------------------------- 1 | import EventDate from "../../components/event-date"; 2 | 3 | const EventDateContainer = ; 6 | 7 | export default EventDateContainer; 8 | -------------------------------------------------------------------------------- /db/migrate/20240513140542_add_closed_to_discourse_post_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddClosedToDiscoursePostEvent < ActiveRecord::Migration[7.0] 4 | def change 5 | add_column :discourse_post_event_events, :closed, :boolean, default: false, null: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/discourse-post-event-invitee.js: -------------------------------------------------------------------------------- 1 | import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter"; 2 | 3 | export default class DiscoursePostEventInvitee extends DiscoursePostEventNestedAdapter { 4 | apiNameFor() { 5 | return "invitee"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/discourse-post-event-reminder.js: -------------------------------------------------------------------------------- 1 | import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter"; 2 | 3 | export default class DiscoursePostEventReminder extends DiscoursePostEventNestedAdapter { 4 | apiNameFor() { 5 | return "reminder"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/stylesheets/desktop/discourse-calendar.scss: -------------------------------------------------------------------------------- 1 | .calendar.fc { 2 | table { 3 | width: 100%; 4 | } 5 | 6 | .fc-list-item-add-to-calendar { 7 | float: right; 8 | margin-right: 5px; 9 | } 10 | 11 | .fc-list-item:hover td { 12 | background: var(--highlight-medium); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /db/migrate/20200810190429_drop_reminders_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DropRemindersTable < ActiveRecord::Migration[6.0] 4 | def up 5 | drop_table :discourse_post_event_reminders 6 | end 7 | 8 | def down 9 | raise ActiveRecord::IrreversibleMigration 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_invalid_region.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module BAD_REGION_NAME 3 | def self.defined_regions 4 | [:test_region] 5 | end 6 | 7 | def self.holidays_by_month 8 | {} 9 | end 10 | 11 | def self.custom_methods 12 | {} 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/discourse-post-event-reminder.js: -------------------------------------------------------------------------------- 1 | import RestModel from "discourse/models/rest"; 2 | 3 | export default class DiscoursePostEventReminder extends RestModel { 4 | init() { 5 | super.init(...arguments); 6 | 7 | this.__type = "discourse-post-event-reminder"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_region.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module TEST_REGION 3 | def self.defined_regions 4 | [:test_region, :test_region2] 5 | end 6 | 7 | def self.holidays_by_month 8 | {} 9 | end 10 | 11 | def self.custom_methods 12 | {} 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_single_custom_holiday_defs.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 6: 3 | - name: Company Founding 4 | regions: [custom_single_file] 5 | mday: 20 6 | 7 | tests: 8 | - given: 9 | date: '2013-06-20' 10 | regions: [custom_single_file] 11 | expect: 12 | name: "Company Founding" 13 | -------------------------------------------------------------------------------- /assets/stylesheets/mobile/discourse-post-event-core-ext.scss: -------------------------------------------------------------------------------- 1 | .link-top-line { 2 | .event-date-container { 3 | display: block; 4 | margin-top: 0.25em; 5 | 6 | .event-date { 7 | margin: 0; 8 | } 9 | } 10 | } 11 | 12 | .header-title { 13 | .event-date { 14 | display: none; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /db/migrate/20200810185432_refactor_reminders.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RefactorReminders < ActiveRecord::Migration[6.0] 4 | def up 5 | add_column :discourse_post_event_events, :reminders, :string 6 | end 7 | 8 | def down 9 | raise ActiveRecord::IrreversibleMigration 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/spec/coverage_report.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'simplecov-rcov' 3 | SimpleCov.minimum_coverage 100 4 | SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter 5 | SimpleCov.coverage_dir 'reports/coverage' 6 | SimpleCov.start do 7 | add_filter "/spec/" 8 | add_filter "/.bundle/" 9 | end 10 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_custom_informal_holidays_defs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | months: 3 | 1: 4 | - name: informal day with type as string 5 | regions: [custom_informal] 6 | mday: 1 7 | type: informal 8 | - name: informal day with type as symbol 9 | regions: [custom_informal] 10 | mday: 5 11 | type: :informal -------------------------------------------------------------------------------- /assets/javascripts/discourse/discourse-event-upcoming-events-route-map.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | this.route( 3 | "discourse-post-event-upcoming-events", 4 | { path: "/upcoming-events" }, 5 | function () { 6 | this.route("index", { path: "/" }); 7 | this.route("mine"); 8 | } 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /db/migrate/20200409181607_remove_display_invitees.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemoveDisplayInvitees < ActiveRecord::Migration[6.0] 4 | def up 5 | remove_column :discourse_post_event_events, :display_invitees 6 | end 7 | 8 | def down 9 | raise ActiveRecord::IrreversibleMigration 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_multiple_custom_holiday_defs.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 10: 3 | - name: Company Founding! 4 | regions: [custom_multiple_files] 5 | mday: 5 6 | 7 | tests: 8 | - given: 9 | date: "2013-10-5" 10 | regions: ["custom_multiple_files"] 11 | expect: 12 | name: "Company Founding!" 13 | -------------------------------------------------------------------------------- /config/locales/server.te.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | te: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: సభ్యనామం 12 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/errors.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | class Error < StandardError; end 3 | 4 | class FunctionNotFound < Error; end 5 | class InvalidFunctionResponse < Error; end 6 | 7 | class UnknownRegionError < Error ; end 8 | class InvalidRegion < Error; end 9 | 10 | class DefinitionTestError < Error; end 11 | end 12 | -------------------------------------------------------------------------------- /config/locales/server.bs_BA.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bs_BA: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nadimak 12 | -------------------------------------------------------------------------------- /config/locales/server.lv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lv: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Lietotājvārds 12 | -------------------------------------------------------------------------------- /config/locales/server.th.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | th: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: ชื่อผู้ใช้ 12 | -------------------------------------------------------------------------------- /db/migrate/20200729094848_add_url_column_to_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddUrlColumnToEvent < ActiveRecord::Migration[6.0] 4 | def up 5 | add_column :discourse_post_event_events, :url, :string, limit: 1000 6 | end 7 | 8 | def down 9 | remove_column :discourse_post_event_events, :url 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/entity/custom_method.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Entity 4 | CustomMethod = Struct.new(:name, :arguments, :source) do 5 | def initialize(fields = {}) 6 | super(*fields.values_at(*members)) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/entity/test.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Entity 4 | Test = Struct.new(:dates, :regions, :options, :name, :holiday?) do 5 | def initialize(fields = {}) 6 | super(*fields.values_at(*members)) 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/format-future-date.js: -------------------------------------------------------------------------------- 1 | import { htmlSafe } from "@ember/template"; 2 | import guessDateFormat from "../lib/guess-best-date-format"; 3 | 4 | export default function (date) { 5 | date = moment.utc(date).tz(moment.tz.guess()); 6 | const format = guessDateFormat(date); 7 | return htmlSafe(date.format(format)); 8 | } 9 | -------------------------------------------------------------------------------- /config/locales/server.bg.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bg: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Потребителско име 12 | -------------------------------------------------------------------------------- /config/locales/server.sq.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sq: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Emri i përdoruesit 12 | -------------------------------------------------------------------------------- /config/locales/server.sr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sr: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Korisničko Ime 12 | -------------------------------------------------------------------------------- /db/migrate/20200310200000_remove_timezone_custom_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemoveTimezoneCustomField < ActiveRecord::Migration[5.2] 4 | def up 5 | execute "DELETE FROM user_custom_fields WHERE name = 'timezone'" 6 | end 7 | 8 | def down 9 | raise ActiveRecord::IrreversibleMigration 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200812193122_add_recurrence_to_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddRecurrenceToEvents < ActiveRecord::Migration[6.0] 4 | def up 5 | add_column :discourse_post_event_events, :recurrence, :string 6 | end 7 | 8 | def down 9 | remove_column :discourse_post_event_events, :recurrence 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20250520042223_add_chat_fields_to_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class AddChatFieldsToEvents < ActiveRecord::Migration[7.2] 3 | def change 4 | add_column :discourse_post_event_events, :chat_enabled, :boolean, default: false, null: false 5 | add_column :discourse_post_event_events, :chat_channel_id, :bigint 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_in.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/in.yaml 7 | class InDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_in 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_sa.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/sa.yaml 7 | class SaDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_sa 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_sg.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/sg.yaml 7 | class SgDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_sg 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fabricators/calendar_event_fabricator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Fabricator(:calendar_event) do 4 | user 5 | username { |attrs| attrs[:user].username } 6 | 7 | topic { |attrs| Fabricate(:topic, user: attrs[:user]) } 8 | post { |attrs| Fabricate(:post, user: attrs[:user], topic: attrs[:topic]) } 9 | 10 | start_date "2000-01-01" 11 | end 12 | -------------------------------------------------------------------------------- /config/locales/client.en_GB.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | en_GB: 8 | js: 9 | discourse_post_event: 10 | builder_modal: 11 | description: 12 | label: "Description" 13 | -------------------------------------------------------------------------------- /db/migrate/20200805133257_add_custom_fields_to_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddCustomFieldsToEvent < ActiveRecord::Migration[6.0] 4 | def up 5 | add_column :discourse_post_event_events, :custom_fields, :jsonb, null: false, default: {} 6 | end 7 | 8 | def down 9 | remove_column :discourse_post_event_events, :custom_fields 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/stylesheets/common/discourse-post-event-upcoming-events.scss: -------------------------------------------------------------------------------- 1 | .discourse-post-event-upcoming-events { 2 | height: 100%; 3 | 4 | .upcoming-events-table { 5 | width: 100%; 6 | 7 | thead { 8 | tr th { 9 | text-align: left; 10 | } 11 | } 12 | 13 | tbody { 14 | tr td { 15 | padding: 0.5em; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_unitednations.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/unitednations.yaml 7 | class UnitednationsDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_unitednations 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/lib/validation/error.rb: -------------------------------------------------------------------------------- 1 | module Definitions 2 | module Errors 3 | class Error < StandardError; end 4 | class NoMonths < Error ; end 5 | class InvalidMonth < Error; end 6 | class InvalidMethod < Error; end 7 | class InvalidRegions < Error; end 8 | class InvalidCustomMethod < Error; end 9 | class InvalidTest < Error; end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/pre-initializers/transformers.js: -------------------------------------------------------------------------------- 1 | import { withPluginApi } from "discourse/lib/plugin-api"; 2 | 3 | export default { 4 | before: "freeze-valid-transformers", 5 | initialize() { 6 | withPluginApi("1.33.0", (api) => { 7 | api.addValueTransformerName( 8 | "discourse-calendar-event-more-menu-should-show-participants" 9 | ); 10 | }); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /test/javascripts/helpers/get-event-by-text.js: -------------------------------------------------------------------------------- 1 | export default function getEventByText(text) { 2 | const events = [...document.querySelectorAll(".fc-day-grid-event")].filter( 3 | (event) => event.textContent.includes(text) 4 | ); 5 | 6 | switch (events.length) { 7 | case 0: 8 | return; 9 | case 1: 10 | return events[0]; 11 | default: 12 | return events; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/description.gjs: -------------------------------------------------------------------------------- 1 | import CookText from "discourse/components/cook-text"; 2 | 3 | const DiscoursePostEventDescription = ; 10 | 11 | export default DiscoursePostEventDescription; 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import UpcomingEventsCalendar from "../components/upcoming-events-calendar"; 3 | 4 | export default RouteTemplate( 5 | 10 | ); 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-mine.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import UpcomingEventsCalendar from "../components/upcoming-events-calendar"; 3 | 4 | export default RouteTemplate( 5 | 10 | ); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "@discourse/lint-configs": "2.25.0", 5 | "ember-template-lint": "7.8.1", 6 | "eslint": "9.28.0", 7 | "prettier": "3.5.3", 8 | "stylelint": "16.20.0" 9 | }, 10 | "engines": { 11 | "node": ">= 22", 12 | "npm": "please-use-pnpm", 13 | "yarn": "please-use-pnpm", 14 | "pnpm": "9.x" 15 | }, 16 | "packageManager": "pnpm@9.15.5" 17 | } 18 | -------------------------------------------------------------------------------- /config/locales/server.zh_TW.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_TW: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: 使用者名稱 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "上傳檔案有錯誤,請再試一次。" 16 | -------------------------------------------------------------------------------- /db/migrate/20200809154642_create_reminders_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateRemindersTable < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :discourse_post_event_reminders do |t| 6 | t.integer :post_id, null: false 7 | t.integer :value, null: false, default: 0 8 | t.integer :mean, null: false, default: 0 9 | t.string :unit, null: false, default: "minutes" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/system/page_objects/discourse_calendar/upcoming_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PageObjects 4 | module Pages 5 | module DiscourseCalendar 6 | class UpcomingEvents < PageObjects::Pages::Base 7 | def visit 8 | super("/upcoming-events") 9 | end 10 | 11 | def open_year_list 12 | find(".fc-listNextYear-button").click 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/group-timezones/new-day.gjs: -------------------------------------------------------------------------------- 1 | import icon from "discourse/helpers/d-icon"; 2 | 3 | const NewDay = ; 15 | 16 | export default NewDay; 17 | -------------------------------------------------------------------------------- /db/migrate/20200409102642_rename_setting_to_discourse_post_event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameSettingToDiscoursePostEvent < ActiveRecord::Migration[6.0] 4 | def up 5 | execute "UPDATE site_settings SET name = 'discourse_post_event_enabled' WHERE name = 'post_event_enabled'" 6 | end 7 | 8 | def down 9 | execute "UPDATE site_settings SET name = 'post_event_enabled' WHERE name = 'discourse_post_event_enabled'" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/connectors/before-topic-list-body/category-calendar.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | 3 | export default class CategoryCalendar extends Component { 4 | static shouldRender(_, ctx) { 5 | return ( 6 | ctx.siteSettings.calendar_categories_outlet === "before-topic-list-body" 7 | ); 8 | } 9 | 10 | 13 | } 14 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/connectors/discovery-list-container-top/category-events-calendar.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | 3 | export default class CategoryEventsCalendar extends Component { 4 | static shouldRender(_, ctx) { 5 | return ( 6 | ctx.siteSettings.calendar_categories_outlet === 7 | "discovery-list-container-top" 8 | ); 9 | } 10 | 11 | 14 | } 15 | -------------------------------------------------------------------------------- /config/locales/server.gl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | gl: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nome de usuario 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Houbo un erro ao cargar o ficheiro. Ténteo máis tarde." 16 | -------------------------------------------------------------------------------- /config/locales/server.vi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | vi: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Tên tài khoản 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Đã xảy ra lỗi khi tải lên tệp đó. Vui lòng thử lại sau." 16 | -------------------------------------------------------------------------------- /db/migrate/20200327195549_add_topic_custom_field_post_event_date_index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTopicCustomFieldPostEventDateIndex < ActiveRecord::Migration[6.0] 4 | def change 5 | add_index :topic_custom_fields, 6 | %i[name topic_id], 7 | name: :idx_topic_custom_fields_post_event_starts_at, 8 | unique: true, 9 | where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT}'" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/location.gjs: -------------------------------------------------------------------------------- 1 | import CookText from "discourse/components/cook-text"; 2 | import icon from "discourse/helpers/d-icon"; 3 | 4 | const DiscoursePostEventLocation = ; 13 | 14 | export default DiscoursePostEventLocation; 15 | -------------------------------------------------------------------------------- /config/locales/server.et.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | et: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Kasutajanimi 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Selle faili üleslaadimisel tekkis viga. Palun proovi hiljem uuesti." 16 | -------------------------------------------------------------------------------- /config/locales/server.sw.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sw: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Jina la mtumiaji 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Tatizo limetokea wakati wa kupakia faili. Tafadhali jaribu tena." 16 | -------------------------------------------------------------------------------- /config/locales/server.ug.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ug: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: ئىشلەتكۈچى ئاتى 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "ھۆججەتنى يۈكلەۋاتقاندا خاتالىق كۆرۈلدى. سەل تۇرۇپ قايتا سىناڭ." 16 | -------------------------------------------------------------------------------- /app/controllers/discourse_post_event/discourse_post_event_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class DiscoursePostEventController < ::ApplicationController 5 | requires_plugin DiscourseCalendar::PLUGIN_NAME 6 | before_action :ensure_discourse_post_event_enabled 7 | 8 | private 9 | 10 | def ensure_discourse_post_event_enabled 11 | raise Discourse::NotFound if !SiteSetting.discourse_post_event_enabled 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config/locales/server.nb_NO.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nb_NO: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Brukernavn 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Det skjedde en feil når filen ble lastet opp. Prøv igjen senere. " 16 | -------------------------------------------------------------------------------- /config/locales/server.be.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | be: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Імя карыстальніка 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Была памылка загрузкі гэтага файла. Калі ласка паспрабуйце зноў пазней." 16 | -------------------------------------------------------------------------------- /config/locales/server.ca.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ca: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nom d'usuari 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Hi ha hagut un error en carregar aquest fitxer. Proveu-ho de nou més tard." 16 | -------------------------------------------------------------------------------- /config/locales/server.hy.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hy: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Օգտանուն 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Այդ ֆայլը վերբեռնելիս տեղի է ունեցել սխալ: Խնդրում ենք փորձել կրկին ավելի ուշ: " 16 | -------------------------------------------------------------------------------- /config/locales/server.hr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hr: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Korisničko ime 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Dogodila se greška pri učitavanju te datoteke. Molimo kasnije pokušajte ponovno." 16 | -------------------------------------------------------------------------------- /config/locales/server.el.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | el: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Όνομα Χρήστη 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Παρουσίαστηκε ένα σφάλμα κατά το ανέβασμα του αρχείου σας, Παρακαλώ δοκιμάστε αργότερα." 16 | -------------------------------------------------------------------------------- /config/locales/server.sk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sk: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Používateľské meno 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "Počas nahrávanie súboru sa vyskytla chyba. Prosím, vyskúšajte ho nahraď znovu neskôr." 16 | -------------------------------------------------------------------------------- /config/locales/server.ur.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ur: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: صارف نام 12 | discourse_post_event: 13 | errors: 14 | bulk_invite: 15 | error: "یہ فائل اَپ لوڈ کرنے میں ایک خرابی کا سامنا کرنا پڑا۔ براہ مہربانی کچھ دیر بعد دوبارہ کوشش کریں۔" 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/guess-best-date-format.js: -------------------------------------------------------------------------------- 1 | export function isNotFullDayEvent(startsAt, endsAt) { 2 | return ( 3 | startsAt.hours() > 0 || 4 | startsAt.minutes() > 0 || 5 | (endsAt && (moment(endsAt).hours() > 0 || moment(endsAt).minutes() > 0)) 6 | ); 7 | } 8 | 9 | export default function guessDateFormat(startsAt, endsAt) { 10 | let format; 11 | if (!isNotFullDayEvent(startsAt, endsAt)) { 12 | format = "LL"; 13 | } else { 14 | format = "LLL"; 15 | } 16 | 17 | return format; 18 | } 19 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/services/discourse-post-event-service.js: -------------------------------------------------------------------------------- 1 | import Service, { service } from "@ember/service"; 2 | 3 | export default class DiscoursePostEventService extends Service { 4 | @service siteSettings; 5 | @service discoursePostEventApi; 6 | 7 | async fetchEvents(params = {}) { 8 | if (this.siteSettings.include_expired_events_on_calendar) { 9 | params.include_expired = true; 10 | } 11 | const events = await this.discoursePostEventApi.events(params); 12 | return await events; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/stylesheets/colors.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable scss/no-global-function-names */ 2 | :root { 3 | --calendar-normal: #{dark-light-choose( 4 | lighten($tertiary, 55%), 5 | darken($tertiary, 25%) 6 | )}; 7 | --calendar-close-to-working-hours: #{dark-light-choose( 8 | desaturate(lighten($tertiary, 45%), 15%), 9 | darken($tertiary, 15%) 10 | )}; 11 | --calendar-in-working-hours: #{dark-light-choose( 12 | desaturate(lighten($tertiary, 40%), 20%), 13 | darken($tertiary, 10%) 14 | )}; 15 | } 16 | -------------------------------------------------------------------------------- /lib/discourse_post_event/post_extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | module PostExtension 5 | extend ActiveSupport::Concern 6 | 7 | prepended do 8 | has_one :event, dependent: :destroy, class_name: "DiscoursePostEvent::Event", foreign_key: :id 9 | 10 | validate :valid_event 11 | end 12 | 13 | def valid_event 14 | return unless self.raw_changed? 15 | 16 | validator = DiscoursePostEvent::EventValidator.new(self) 17 | validator.validate_event 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/connectors/topic-list-after-title/event-badge.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | import { service } from "@ember/service"; 3 | import EventDate from "../../components/event-date"; 4 | 5 | export default class EventBadge extends Component { 6 | @service siteSettings; 7 | 8 | 15 | } 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/discourse-post-event-event.js: -------------------------------------------------------------------------------- 1 | import { underscore } from "@ember/string"; 2 | import DiscoursePostEventAdapter from "./discourse-post-event-adapter"; 3 | 4 | export default class DiscoursePostEventEvent extends DiscoursePostEventAdapter { 5 | pathFor(store, type, findArgs) { 6 | const path = 7 | this.basePath(store, type, findArgs) + 8 | underscore(store.pluralize(this.apiNameFor(type))); 9 | return this.appendQueryParams(path, findArgs); 10 | } 11 | 12 | apiNameFor() { 13 | return "event"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /db/migrate/20200926144256_add_unique_index_to_topic_event_ends_at_custom_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddUniqueIndexToTopicEventEndsAtCustomField < ActiveRecord::Migration[6.0] 4 | def up 5 | add_index :topic_custom_fields, 6 | %i[name topic_id], 7 | name: :idx_topic_custom_fields_topic_post_event_ends_at, 8 | unique: true, 9 | where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT}'" 10 | end 11 | 12 | def down 13 | raise ActiveRecord::IrreversibleMigration 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/modal/post-event-invitees/user.gjs: -------------------------------------------------------------------------------- 1 | import avatar from "discourse/helpers/avatar"; 2 | import { userPath } from "discourse/lib/url"; 3 | import { formatUsername } from "discourse/lib/utilities"; 4 | 5 | const User = ; 15 | 16 | export default User; 17 | -------------------------------------------------------------------------------- /jobs/regular/discourse_post_event/bump_topic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jobs 4 | class DiscoursePostEventBumpTopic < ::Jobs::Base 5 | sidekiq_options retry: false 6 | 7 | def execute(args) 8 | return unless topic = Topic.find_by(id: args[:topic_id].to_i) 9 | return if args[:date].blank? 10 | 11 | event_user = User.find_by(id: topic.user_id) 12 | timer = TopicTimer.find_by(topic_id: args[:topic_id].to_i) 13 | 14 | topic.set_or_create_timer(TopicTimer.types[:bump], args[:date], by_user: event_user) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /jobs/regular/discourse_post_event/event_started.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jobs 4 | class DiscoursePostEventEventStarted < ::Jobs::Base 5 | sidekiq_options retry: false 6 | 7 | def execute(args) 8 | raise Discourse::InvalidParameters.new(:event_id) if args[:event_id].blank? 9 | event = DiscoursePostEvent::Event.find(args[:event_id]) 10 | MessageBus.publish("/topic/#{event.post.topic_id}", reload_topic: true, refresh_stream: true) 11 | DiscourseEvent.trigger(:discourse_post_event_event_started, event) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/event-field.gjs: -------------------------------------------------------------------------------- 1 | import { notEq } from "truth-helpers"; 2 | import { i18n } from "discourse-i18n"; 3 | 4 | const EventField = ; 19 | 20 | export default EventField; 21 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/discourse-post-event-event-stats.js: -------------------------------------------------------------------------------- 1 | import { tracked } from "@glimmer/tracking"; 2 | 3 | export default class DiscoursePostEventEventStats { 4 | static create(args = {}) { 5 | return new DiscoursePostEventEventStats(args); 6 | } 7 | 8 | @tracked going = 0; 9 | @tracked interested = 0; 10 | @tracked invited = 0; 11 | @tracked notGoing = 0; 12 | 13 | constructor(args = {}) { 14 | this.going = args.going; 15 | this.invited = args.invited; 16 | this.interested = args.interested; 17 | this.notGoing = args.not_going; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/locales/server.id.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | id: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nama Pengguna 12 | discourse_calendar: 13 | invite_user_notification: "%{username} mengundang Anda ke: %{description}" 14 | discourse_post_event: 15 | errors: 16 | bulk_invite: 17 | error: "Terjadi kesalahan saat mengunggah file itu. Coba lagi nanti." 18 | -------------------------------------------------------------------------------- /db/migrate/20231123233308_delete_duplicated_holidays.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeleteDuplicatedHolidays < ActiveRecord::Migration[7.0] 4 | def up 5 | execute <<~SQL 6 | DELETE 7 | FROM calendar_events ce 8 | WHERE 9 | ce.id IN (SELECT ce2.id FROM calendar_events ce2 10 | INNER JOIN users ON users.id = ce2.user_id 11 | WHERE ce2.post_id IS NULL 12 | AND ce2.username != users.username) 13 | SQL 14 | end 15 | 16 | def down 17 | raise ActiveRecord::IrreversibleMigration 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/calendar-locale.js: -------------------------------------------------------------------------------- 1 | import I18n, { i18n } from "discourse-i18n"; 2 | 3 | export function getCurrentBcp47Locale() { 4 | return I18n.currentLocale().replace("_", "-").toLowerCase(); 5 | } 6 | 7 | export function getCalendarButtonsText() { 8 | return { 9 | today: i18n("discourse_calendar.toolbar_button.today"), 10 | month: i18n("discourse_calendar.toolbar_button.month"), 11 | week: i18n("discourse_calendar.toolbar_button.week"), 12 | day: i18n("discourse_calendar.toolbar_button.day"), 13 | list: i18n("discourse_calendar.toolbar_button.list"), 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /vendor/holidays/test/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | These tests are dependent on the files in /definitions (and, by proxy, /lib/generated_definitions). 4 | It is possible that these tests will break because of 'unrelated' definition changes. The code 5 | behind these changes could still be good but since the definitions changed we could see failures. 6 | 7 | These are not unit tests. This is not testing whether specific internal code is working. These are 8 | tests from the consumer perspective. You must recognize that this could fail because of code 9 | changes unrelated to definition changes. 10 | -------------------------------------------------------------------------------- /config/locales/server.pt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nome de Utilizador 12 | discourse_automation: 13 | triggerables: 14 | event_started: 15 | title: Evento iniciado 16 | discourse_post_event: 17 | errors: 18 | bulk_invite: 19 | error: "Ocorreu um erro ao carregar esse ficheiro. Por favor tente mais tarde." 20 | -------------------------------------------------------------------------------- /db/migrate/20200805073343_drop_old_discourse_calendar_tables.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "migration/table_dropper" 4 | 5 | class DropOldDiscourseCalendarTables < ActiveRecord::Migration[6.0] 6 | def up 7 | if table_exists?(:discourse_calendar_post_events) 8 | Migration::TableDropper.execute_drop(:discourse_calendar_post_events) 9 | end 10 | 11 | if table_exists?(:discourse_calendar_invitees) 12 | Migration::TableDropper.execute_drop(:discourse_calendar_invitees) 13 | end 14 | end 15 | 16 | def down 17 | raise ActiveRecord::IrrelversibleMigration 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /assets/stylesheets/common/discourse-post-event-preview.scss: -------------------------------------------------------------------------------- 1 | .discourse-post-event-preview { 2 | background: var(--secondary); 3 | align-items: center; 4 | flex-direction: column; 5 | padding: 0.5em; 6 | border: 1px solid var(--primary-low); 7 | display: flex; 8 | flex: 1 0 auto; 9 | 10 | .event-preview-status { 11 | margin: 0 0 0.5em 0; 12 | } 13 | 14 | .event-preview-dates { 15 | font-weight: 700; 16 | } 17 | 18 | &.alert-error { 19 | border-color: var(--danger-low-mid); 20 | } 21 | } 22 | 23 | .discourse-post-event-preview + .discourse-post-event-preview { 24 | margin-top: 1em; 25 | } 26 | -------------------------------------------------------------------------------- /db/migrate/20200409102639_drop_incorrect_future_schema_migrations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DropIncorrectFutureSchemaMigrations < ActiveRecord::Migration[5.2] 4 | def up 5 | execute <<-SQL 6 | DELETE FROM schema_migrations WHERE version = '20201303000001'; 7 | DELETE FROM schema_migration_details WHERE version = '20201303000001'; 8 | DELETE FROM schema_migrations WHERE version = '20201303000002'; 9 | DELETE FROM schema_migration_details WHERE version = '20201303000002'; 10 | SQL 11 | end 12 | 13 | def down 14 | raise ActiveRecord::IrreversibleMigration 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20220604200919_create_disabled_holidays.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateDisabledHolidays < ActiveRecord::Migration[7.0] 4 | def change 5 | create_table :discourse_calendar_disabled_holidays do |t| 6 | t.string :holiday_name, null: false 7 | t.string :region_code, null: false 8 | t.boolean :disabled, null: false, default: true 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :discourse_calendar_disabled_holidays, 14 | %i[holiday_name region_code], 15 | name: "index_disabled_holidays_on_holiday_name_and_region_code" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/locales/server.ko.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ko: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: 사용자명 12 | discourse_automation: 13 | triggerables: 14 | event_started: 15 | title: 이벤트 시작됨 16 | discourse_calendar: 17 | invite_user_notification: "%{username}님이 사용자님을 초대했습니다: %{description}" 18 | discourse_post_event: 19 | errors: 20 | bulk_invite: 21 | error: "해당 파일을 업로드하는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오." 22 | -------------------------------------------------------------------------------- /assets/stylesheets/mobile/discourse-post-event.scss: -------------------------------------------------------------------------------- 1 | .discourse-post-event { 2 | .discourse-post-event-widget { 3 | border-width: 1px; 4 | } 5 | 6 | .event-dates { 7 | .date { 8 | max-width: 75vw; 9 | } 10 | } 11 | 12 | .event-actions { 13 | .event-status { 14 | width: 100%; 15 | display: flex; 16 | justify-content: space-between; 17 | 18 | button { 19 | padding: 0 0.6em; 20 | height: 2.5em; 21 | } 22 | } 23 | } 24 | 25 | .event-invitees-status { 26 | font-size: var(--font-down-1); 27 | } 28 | 29 | .creators { 30 | .created-by { 31 | display: none; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_id.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/id.yaml 7 | class IdDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_id 10 | assert_equal "Chinese New Year", (Holidays.on(Date.civil(2025, 1, 29), [:id])[0] || {})[:name] 11 | 12 | assert_equal "Good Friday", (Holidays.on(Date.civil(2025, 4, 18), [:id])[0] || {})[:name] 13 | 14 | assert_equal "Ascension Day of Jesus Christ", (Holidays.on(Date.civil(2025, 5, 29), [:id])[0] || {})[:name] 15 | 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-index.js: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import { service } from "@ember/service"; 3 | import DiscourseURL from "discourse/lib/url"; 4 | import DiscourseRoute from "discourse/routes/discourse"; 5 | 6 | export default class PostEventUpcomingEventsIndexRoute extends DiscourseRoute { 7 | @service discoursePostEventService; 8 | 9 | @action 10 | activate() { 11 | if (!this.siteSettings.discourse_post_event_enabled) { 12 | DiscourseURL.redirectTo("/404"); 13 | } 14 | } 15 | 16 | async model(params) { 17 | return await this.discoursePostEventService.fetchEvents(params); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /db/migrate/20200409120815_rename_topic_custom_field_topic_post_event_starts_at_index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameTopicCustomFieldTopicPostEventStartsAtIndex < ActiveRecord::Migration[6.0] 4 | def up 5 | remove_index :topic_custom_fields, name: "idx_topic_custom_fields_post_event_starts_at" 6 | 7 | add_index :topic_custom_fields, 8 | %i[name topic_id], 9 | name: :idx_topic_custom_fields_topic_post_event_starts_at, 10 | unique: true, 11 | where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT}'" 12 | end 13 | 14 | def down 15 | raise ActiveRecord::IrreversibleMigration 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/discourse-post-event-invitee.js: -------------------------------------------------------------------------------- 1 | import { tracked } from "@glimmer/tracking"; 2 | import User from "discourse/models/user"; 3 | 4 | export default class DiscoursePostEventInvitee { 5 | static create(args = {}) { 6 | return new DiscoursePostEventInvitee(args); 7 | } 8 | 9 | @tracked status; 10 | 11 | constructor(args = {}) { 12 | this.id = args.id; 13 | this.post_id = args.post_id; 14 | this.status = args.status; 15 | this.user = this.#initUserModel(args.user); 16 | } 17 | 18 | #initUserModel(user) { 19 | if (!user || user instanceof User) { 20 | return user; 21 | } 22 | 23 | return User.create(user); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vendor/holidays/test/integration/test_custom_informal_holidays.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 2 | 3 | class CustomHolidaysTest < Test::Unit::TestCase 4 | 5 | def test_custom_region_informal_day_parsing 6 | Holidays.load_custom('test/data/test_custom_informal_holidays_defs.yaml') 7 | 8 | assert_not_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :informal) 9 | assert_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :observed) 10 | 11 | assert_not_equal [], Holidays.on(Date.new(2018,1,5), :custom_informal, :informal) 12 | assert_equal [], Holidays.on(Date.new(2018,1,5), :custom_informal, :observed) 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/calendar_settings_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CalendarSettingsValidator 4 | def initialize(opts = {}) 5 | @opts = opts 6 | end 7 | 8 | def valid_value?(val) 9 | return true if val == "" 10 | 11 | split = val.split(":") 12 | return false if split.count != 2 13 | 14 | hour = split.first 15 | return false if hour.length != 2 16 | return false if hour.to_i < 0 || hour.to_i >= 24 17 | 18 | minutes = split.second 19 | return false if minutes.length != 2 20 | return false if minutes.to_i < 0 || minutes.to_i >= 60 21 | true 22 | end 23 | 24 | def error_message 25 | I18n.t("site_settings.all_day_event_time_error") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20200409102641_create_invitees_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateInviteesTable < ActiveRecord::Migration[5.2] 4 | def up 5 | unless ActiveRecord::Base.connection.table_exists?("discourse_calendar_invitees") 6 | create_table :discourse_calendar_invitees do |t| 7 | t.integer :post_id, null: false 8 | t.integer :user_id, null: false 9 | t.integer :status 10 | t.timestamps null: false 11 | t.boolean :notified, null: false, default: false 12 | end 13 | 14 | add_index :discourse_calendar_invitees, %i[post_id user_id], unique: true 15 | end 16 | end 17 | 18 | def down 19 | drop_table :discourse_calendar_invitees 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/doc/architecture/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records 2 | 3 | Here we document decisions we made regarding high level architecture/design of this repository. 4 | 5 | For details on what ADR is and why it's important, please reference the following blog posts: 6 | 7 | - https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0 8 | - http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions 9 | 10 | Please note that we only began keeping ADRs for this project in October of 2018. Decisions made before that are not covered. 11 | 12 | ## Table of contents 13 | 14 | 1. [Language specific custom methods](adr-001.md) 15 | 1. [Year ranges syntax update](adr-002.md) 16 | -------------------------------------------------------------------------------- /lib/group_timezones.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | class GroupTimezones 5 | def self.update(post) 6 | groups = [] 7 | 8 | Nokogiri 9 | .HTML(post.cooked) 10 | .css("div.group-timezones") 11 | .map do |group_timezones| 12 | group_timezones.attributes.values.each do |attribute| 13 | if attribute.name == "data-group" 14 | group_name = CGI.escapeHTML(attribute.value || "") 15 | groups << group_name if group_name.present? 16 | end 17 | end 18 | end 19 | 20 | post.group_timezones = groups.present? ? { groups: groups } : nil 21 | post.save_custom_fields 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/core_extensions/time.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module CoreExtensions 3 | module Time 4 | def self.included(base) 5 | base.extend ClassMethods 6 | end 7 | 8 | module ClassMethods 9 | COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 10 | 11 | # Returns the number of days in the given month. 12 | # If no year is specified, it will use the current year. 13 | def days_in_month(month, year = current.year) 14 | if month == 2 && ::Date.gregorian_leap?(year) 15 | 29 16 | else 17 | COMMON_YEAR_DAYS_IN_MONTH[month] 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_single_custom_holiday_with_custom_procs.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 0: 3 | - name: Custom Holiday 4 | regions: [custom_single_file] 5 | function: custom_method(year, month) 6 | function_modifier: 5 7 | 6: 8 | - name: Company Founding 9 | regions: [custom_single_file] 10 | mday: 20 11 | methods: 12 | custom_method: 13 | arguments: year, month 14 | source: | 15 | d = Date.civil(year, month, 1) 16 | d + 2 17 | 18 | tests: 19 | - given: 20 | date: '2013-06-20' 21 | regions: [custom_single_file] 22 | expect: 23 | name: "Company Founding" 24 | - given: 25 | date: '2015-01-01' 26 | regions: [custom_single_file] 27 | expect: 28 | name: "Custom Holiday" 29 | -------------------------------------------------------------------------------- /lib/calendar_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | class CalendarValidator 5 | def initialize(post) 6 | @post = post 7 | end 8 | 9 | def validate_calendar 10 | extracted_calendars = DiscourseCalendar::Calendar.extract(@post) 11 | 12 | return false if extracted_calendars.count == 0 13 | 14 | if extracted_calendars.count > 1 15 | @post.errors.add(:base, I18n.t("discourse_calendar.more_than_one_calendar")) 16 | return false 17 | end 18 | 19 | if !@post.is_first_post? 20 | @post.errors.add(:base, I18n.t("discourse_calendar.calendar_must_be_in_first_post")) 21 | return false 22 | end 23 | 24 | true 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/disable-sort.js: -------------------------------------------------------------------------------- 1 | import { withPluginApi } from "discourse/lib/plugin-api"; 2 | 3 | export default { 4 | name: "disable-sort", 5 | 6 | initialize(container) { 7 | withPluginApi("0.8", (api) => { 8 | api.registerValueTransformer( 9 | "topic-list-header-sortable-column", 10 | ({ value, context }) => { 11 | if (!value) { 12 | return value; 13 | } 14 | 15 | const siteSettings = container.lookup("service:site-settings"); 16 | return !( 17 | siteSettings.disable_resorting_on_categories_enabled && 18 | context.category?.custom_fields?.disable_topic_resorting 19 | ); 20 | } 21 | ); 22 | }); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /db/migrate/20201110225115_create_post_event_dates_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreatePostEventDatesTable < ActiveRecord::Migration[6.0] 4 | def up 5 | create_table :discourse_calendar_post_event_dates do |t| 6 | t.integer :event_id 7 | t.datetime :starts_at 8 | t.datetime :ends_at 9 | t.integer :reminder_counter, default: 0 10 | t.datetime :event_will_start_sent_at 11 | t.datetime :event_started_sent_at 12 | t.datetime :finished_at 13 | t.timestamps 14 | end 15 | add_index :discourse_calendar_post_event_dates, :event_id 16 | add_index :discourse_calendar_post_event_dates, :finished_at 17 | end 18 | 19 | def down 20 | raise ActiveRecord::IrreversibleMigration 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /vendor/holidays/doc/REFERENCES: -------------------------------------------------------------------------------- 1 | === References 2 | 3 | We are eternally grateful to the following sources. 4 | 5 | ==== Date calculations 6 | * http://michaelthompson.org/technikos/holidays.php 7 | 8 | ==== Easter calculations 9 | * http://www.assa.org.au/edm.html 10 | * http://www.smart.net/~mmontes/ec-cal.html 11 | * https://github.com/Loyolny/when_easter - graciously allowed by Michał Nierebiński 12 | 13 | ==== World dates 14 | * http://www.un.org/geninfo/faq/factsheets/FS18.HTM 15 | * http://en.wikipedia.org/wiki/List_of_holidays_by_country 16 | 17 | Other sources for specific regions are noted in the definitions files themselves. 18 | 19 | If a commit sneaks past the maintainers without attribution then please, let us know immediately! It was entirely unintentional. 20 | -------------------------------------------------------------------------------- /db/migrate/20190724181542_add_on_holiday_index.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddOnHolidayIndex < ActiveRecord::Migration[5.2] 4 | def up 5 | execute <<~SQL 6 | DELETE 7 | FROM user_custom_fields a 8 | USING user_custom_fields b 9 | WHERE a.name = 'on_holiday' 10 | AND a.name = b.name 11 | AND a.user_id = b.user_id 12 | AND a.id > b.id 13 | SQL 14 | 15 | add_index :user_custom_fields, 16 | %i[name user_id], 17 | unique: true, 18 | name: :idx_user_custom_fields_on_holiday, 19 | where: "name = 'on_holiday'" 20 | end 21 | 22 | def down 23 | remove_index :user_custom_fields, name: :idx_user_custom_fields_on_holiday 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/lib/calendar_settings_validator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "rails_helper" 3 | 4 | describe CalendarSettingsValidator do 5 | def expect_invalid(val) 6 | expect(subject.valid_value?(val)).to eq(false) 7 | end 8 | 9 | def expect_valid(val) 10 | expect(subject.valid_value?(val)).to eq(true) 11 | end 12 | 13 | it "only allows valid HH:mm formats" do 14 | expect_invalid "markvanlan" 15 | expect_invalid "000:00" 16 | expect_invalid "00:000" 17 | expect_invalid "24:00" 18 | expect_invalid "00:60" 19 | expect_invalid "002:30" 20 | 21 | expect_valid "" 22 | expect_valid "00:00" 23 | expect_valid "23:00" 24 | expect_valid "00:59" 25 | expect_valid "23:59" 26 | expect_valid "06:40" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/chat-channel.gjs: -------------------------------------------------------------------------------- 1 | import { LinkTo } from "@ember/routing"; 2 | import { and } from "truth-helpers"; 3 | import { optionalRequire } from "discourse/lib/utilities"; 4 | 5 | const ChannelTitle = optionalRequire( 6 | "discourse/plugins/chat/discourse/components/channel-title" 7 | ); 8 | 9 | const DiscoursePostEventChatChannel = ; 19 | 20 | export default DiscoursePostEventChatChannel; 21 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/url.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | import icon from "discourse/helpers/d-icon"; 3 | 4 | export default class DiscoursePostEventUrl extends Component { 5 | get url() { 6 | return this.args.url.includes("://") || this.args.url.includes("mailto:") 7 | ? this.args.url 8 | : `https://${this.args.url}`; 9 | } 10 | 11 | 26 | } 27 | -------------------------------------------------------------------------------- /vendor/holidays/test/integration/test_available_regions.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 2 | 3 | class AvailableRegionsTests < Test::Unit::TestCase 4 | def setup 5 | @date = Date.civil(2008,1,1) 6 | end 7 | 8 | def test_available_regions_returns_array 9 | assert Holidays.available_regions.is_a?(Array) 10 | end 11 | 12 | def test_available_regions_returns_array_of_symbols 13 | Holidays.available_regions.each do |r| 14 | assert r.is_a?(Symbol) 15 | end 16 | end 17 | 18 | # This test might fail if we add new regions. Since this is an integration test 19 | # I am fine with that! 20 | def test_available_regions_returns_correct_number_of_regions 21 | assert_equal 258, Holidays.available_regions.count 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/admin-holidays-list.gjs: -------------------------------------------------------------------------------- 1 | import { i18n } from "discourse-i18n"; 2 | import AdminHolidaysListItem from "./admin-holidays-list-item"; 3 | 4 | const AdminHolidaysList = ; 24 | 25 | export default AdminHolidaysList; 26 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_custom_year_range_holiday_defs.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 6: 3 | - name: after_year 4 | regions: [custom_year_range_file] 5 | mday: 1 6 | year_ranges: 7 | from: 2016 8 | - name: before_year 9 | regions: [custom_year_range_file] 10 | mday: 2 11 | year_ranges: 12 | until: 2017 13 | - name: between_year 14 | regions: [custom_year_range_file] 15 | mday: 3 16 | year_ranges: 17 | between: 18 | start: 2016 19 | end: 2018 20 | - name: limited_year 21 | regions: [custom_year_range_file] 22 | mday: 4 23 | year_ranges: 24 | limited: [2016,2018,2019] 25 | 26 | tests: 27 | - given: 28 | date: '2017-01-01' 29 | regions: [custom_year_range_file] 30 | expect: 31 | name: "after_year" 32 | -------------------------------------------------------------------------------- /app/models/discourse_calendar/disabled_holiday.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | class DisabledHoliday < ActiveRecord::Base 5 | validates :holiday_name, presence: true 6 | validates :region_code, presence: true 7 | end 8 | end 9 | 10 | # == Schema Information 11 | # 12 | # Table name: discourse_calendar_disabled_holidays 13 | # 14 | # id :bigint not null, primary key 15 | # holiday_name :string not null 16 | # region_code :string not null 17 | # disabled :boolean default(TRUE), not null 18 | # created_at :datetime not null 19 | # updated_at :datetime not null 20 | # 21 | # Indexes 22 | # 23 | # index_disabled_holidays_on_holiday_name_and_region_code (holiday_name,region_code) 24 | # 25 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/full-calendar-default-options.js: -------------------------------------------------------------------------------- 1 | import { escape } from "pretty-text/sanitizer"; 2 | import { 3 | getCalendarButtonsText, 4 | getCurrentBcp47Locale, 5 | } from "./calendar-locale"; 6 | import { buildPopover, destroyPopover } from "./popover"; 7 | 8 | export default function fullCalendarDefaultOptions() { 9 | return { 10 | eventClick: function () { 11 | destroyPopover(); 12 | }, 13 | locale: getCurrentBcp47Locale(), 14 | buttonText: getCalendarButtonsText(), 15 | eventMouseEnter: function ({ event, jsEvent }) { 16 | destroyPopover(); 17 | 18 | const htmlContent = escape(event.title); 19 | buildPopover(jsEvent, htmlContent); 20 | }, 21 | eventMouseLeave: function () { 22 | destroyPopover(); 23 | }, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-mine.gjs: -------------------------------------------------------------------------------- 1 | import { action } from "@ember/object"; 2 | import { service } from "@ember/service"; 3 | import DiscourseURL from "discourse/lib/url"; 4 | import DiscourseRoute from "discourse/routes/discourse"; 5 | 6 | export default class PostEventUpcomingEventsIndexRoute extends DiscourseRoute { 7 | @service discoursePostEventApi; 8 | @service discoursePostEventService; 9 | @service currentUser; 10 | 11 | @action 12 | activate() { 13 | if (!this.siteSettings.discourse_post_event_enabled) { 14 | DiscourseURL.redirectTo("/404"); 15 | } 16 | } 17 | 18 | async model(params) { 19 | params.attending_user = this.currentUser?.username; 20 | return await this.discoursePostEventService.fetchEvents(params); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/serializers/discourse_post_event/invitee_list_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class InviteeListSerializer < ApplicationSerializer 5 | root false 6 | attributes :meta 7 | has_many :invitees, serializer: InviteeSerializer, embed: :objects 8 | 9 | def invitees 10 | object[:invitees] 11 | end 12 | 13 | def meta 14 | { 15 | suggested_users: 16 | ActiveModel::ArraySerializer.new( 17 | suggested_users, 18 | each_serializer: BasicUserSerializer, 19 | scope: scope, 20 | ), 21 | } 22 | end 23 | 24 | def include_meta? 25 | suggested_users.present? 26 | end 27 | 28 | def suggested_users 29 | object[:suggested_users] 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ae.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ae.yaml 7 | class AeDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ae 10 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2024, 1, 1), [:ae], [:informal])[0] || {})[:name] 11 | 12 | assert_equal "Commemoration Day", (Holidays.on(Date.civil(2024, 12, 1), [:ae], [:informal])[0] || {})[:name] 13 | 14 | assert_equal "National Day", (Holidays.on(Date.civil(2024, 12, 2), [:ae], [:informal])[0] || {})[:name] 15 | 16 | assert_equal "National Day (Day 2)", (Holidays.on(Date.civil(2024, 12, 3), [:ae], [:informal])[0] || {})[:name] 17 | 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/decorator/custom_method_proc.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Decorator 4 | class CustomMethodProc 5 | def call(proc) 6 | validate!(proc) 7 | 8 | eval("Proc.new { |#{parse_arguments(proc.arguments)}| 9 | #{proc.source} 10 | }") 11 | end 12 | 13 | private 14 | 15 | def validate!(proc) 16 | raise ArgumentError if proc.name.nil? || proc.name.empty? 17 | raise ArgumentError if proc.arguments.nil? || proc.arguments.empty? 18 | raise ArgumentError if proc.source.nil? || proc.source.empty? 19 | end 20 | 21 | def parse_arguments(args) 22 | a = args.join(", ") 23 | a[0..-1] 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/group_timezones_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe DiscourseCalendar::GroupTimezones do 6 | before do 7 | Jobs.run_immediately! 8 | SiteSetting.calendar_enabled = true 9 | end 10 | 11 | let(:calendar_post) { create_post(raw: '[timezones group="admins"]\n[/timezones]') } 12 | 13 | it "converts the Markdown to HTML" do 14 | expect(calendar_post.cooked.rstrip).to match_html(<<~HTML.rstrip) 15 |
16 |

\\n

17 |
18 | HTML 19 | end 20 | 21 | it "creates correct custom fields" do 22 | calendar_post.reload 23 | expect(calendar_post.has_group_timezones?).to eq(true) 24 | expect(calendar_post.group_timezones).to eq("groups" => ["admins"]) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/helpers/format-event-name.js: -------------------------------------------------------------------------------- 1 | import { i18n } from "discourse-i18n"; 2 | 3 | function sameTimezoneOffset(timezone1, timezone2) { 4 | if (!timezone1 || !timezone2) { 5 | return false; 6 | } 7 | 8 | const offset1 = moment.tz(timezone1).utcOffset(); 9 | const offset2 = moment.tz(timezone2).utcOffset(); 10 | return offset1 === offset2; 11 | } 12 | 13 | export function formatEventName(event, userTimezone) { 14 | let output = event.name || event.post.topic.title; 15 | 16 | if ( 17 | event.showLocalTime && 18 | event.timezone && 19 | !sameTimezoneOffset(event.timezone, userTimezone) 20 | ) { 21 | output += 22 | ` (${i18n("discourse_calendar.local_time")}: ` + 23 | moment(event.startsAt).tz(event.timezone).format("H:mma") + 24 | ")"; 25 | } 26 | 27 | return output; 28 | } 29 | -------------------------------------------------------------------------------- /db/migrate/20200409102640_create_post_events_table.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreatePostEventsTable < ActiveRecord::Migration[5.2] 4 | def up 5 | unless ActiveRecord::Base.connection.table_exists?("discourse_calendar_post_events") 6 | create_table :discourse_calendar_post_events, id: false do |t| 7 | t.bigint :id, null: false, primary_key: true 8 | t.integer :status, default: 0, null: false 9 | t.integer :display_invitees, default: 0, null: false 10 | t.datetime :starts_at, null: false, default: -> { "CURRENT_TIMESTAMP" } 11 | t.datetime :ends_at 12 | t.datetime :deleted_at 13 | t.string :raw_invitees, array: true 14 | t.string :name 15 | end 16 | end 17 | end 18 | 19 | def down 20 | drop_table :discourse_calendar_post_events 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/serializers/discourse_post_event/invitee_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class InviteeSerializer < ApplicationSerializer 5 | attributes :id, :status, :user, :post_id, :meta 6 | 7 | def status 8 | object.status ? Invitee.statuses[object.status] : nil 9 | end 10 | 11 | def include_id? 12 | object.id 13 | end 14 | 15 | def user 16 | BasicUserSerializer.new(object.user, embed: :objects, root: false) 17 | end 18 | 19 | def meta 20 | { 21 | event_should_display_invitees: 22 | (object.event.public? && object.event.invitees.count > 0) || 23 | (object.event.private? && object.event.raw_invitees.count > 0), 24 | event_stats: EventStatsSerializer.new(object.event, root: false), 25 | } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/services/discourse_calendar/holiday.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "holidays" 4 | 5 | module DiscourseCalendar 6 | class Holiday 7 | def self.find_holidays_for( 8 | region_code:, 9 | start_date: Date.current.beginning_of_year, 10 | end_date: Date.current.end_of_year, 11 | show_holiday_observed_on_dates: false 12 | ) 13 | holidays = 14 | Holidays.between( 15 | start_date, 16 | end_date, 17 | [region_code], 18 | show_holiday_observed_on_dates ? :observed : [], 19 | ) 20 | 21 | holidays.map do |holiday| 22 | holiday[:disabled] = DiscourseCalendar::DisabledHoliday.where( 23 | region_code: region_code, 24 | ).exists?(holiday_name: holiday[:name]) 25 | end 26 | 27 | holidays 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/integration/topic_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Topic do 6 | before do 7 | freeze_time 8 | Jobs.run_immediately! 9 | SiteSetting.calendar_enabled = true 10 | SiteSetting.discourse_post_event_enabled = true 11 | end 12 | 13 | let(:user) { Fabricate(:user, refresh_auto_groups: true) } 14 | 15 | context "when a topic is created" do 16 | context "with a date in title" do 17 | it "doesn’t create a post event" do 18 | post_with_date = 19 | PostCreator.create!( 20 | user, 21 | title: "Let’s buy a boat with me tomorrow", 22 | raw: "The boat market is quite active lately.", 23 | ) 24 | 25 | expect(DiscoursePostEvent::Event).to_not exist(id: post_with_date.id) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/requests/site_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe "calendar site additions" do 4 | let(:user) { Fabricate(:user) } 5 | let(:admin) { Fabricate(:admin) } 6 | 7 | before do 8 | SiteSetting.calendar_enabled = true 9 | DiscourseCalendar.users_on_holiday = [user.username] 10 | end 11 | 12 | it "includes users_on_holiday for staff only" do 13 | get "/site.json" 14 | expect(response.status).to eq(200) 15 | expect(response.parsed_body["users_on_holiday"]).to eq(nil) 16 | 17 | sign_in(user) 18 | get "/site.json" 19 | expect(response.status).to eq(200) 20 | expect(response.parsed_body["users_on_holiday"]).to eq(nil) 21 | 22 | sign_in(admin) 23 | get "/site.json" 24 | expect(response.status).to eq(200) 25 | expect(response.parsed_body["users_on_holiday"]).to eq([user.username]) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/system/disable_sort_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe "Disabling topic list sorting", type: :system do 4 | fab!(:category) 5 | let(:category_page) { PageObjects::Pages::Category.new } 6 | 7 | before do 8 | SiteSetting.calendar_enabled = true 9 | Fabricate.times(2, :topic, category:) 10 | end 11 | 12 | it "disables the ability to sort topic list columns" do 13 | category_page.visit(category) 14 | expect(find("th.activity")).to match_selector(".sortable") 15 | 16 | category.custom_fields["disable_topic_resorting"] = true 17 | category.save! 18 | page.refresh 19 | expect(find("th.activity")).to match_selector(".sortable") 20 | 21 | SiteSetting.disable_resorting_on_categories_enabled = true 22 | page.refresh 23 | expect(find("th.activity")).to_not match_selector(".sortable") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/group_timezones_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe "Group timezones feature", type: :system do 4 | fab!(:group) { Fabricate(:group, name: "test-group") } 5 | 6 | fab!(:users) do 7 | Fabricate 8 | .times(5, :user) 9 | .each do |user| 10 | user.user_option.timezone = "America/New_York" 11 | user.user_option.save! 12 | group.add(user) 13 | end 14 | end 15 | 16 | before do 17 | Jobs.run_immediately! 18 | SiteSetting.calendar_enabled = true 19 | end 20 | 21 | let(:post) { create_post(raw: <<~RAW) } 22 | [timezones group="test-group"] 23 | [/timezones] 24 | RAW 25 | 26 | it "renders successfully" do 27 | visit(post.url) 28 | expect(page).to have_selector(".group-timezones") 29 | expect(page).to have_selector(".group-timezones-member", count: 5) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/validator/custom_method.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Validator 4 | class CustomMethod 5 | VALID_ARGUMENTS = ["date", "year", "month", "day", "region"] 6 | 7 | def valid?(m) 8 | valid_name?(m[:name]) && 9 | valid_arguments?(m[:arguments]) && 10 | valid_source?(m[:source]) 11 | end 12 | 13 | private 14 | 15 | def valid_name?(name) 16 | !name.nil? && !name.empty? 17 | end 18 | 19 | def valid_arguments?(arguments) 20 | arguments.split(",").all? { |arg| 21 | arg == arg.chomp && VALID_ARGUMENTS.include?(arg.strip) 22 | } 23 | end 24 | 25 | def valid_source?(source) 26 | !source.nil? && !source.empty? 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/creator.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | import avatar from "discourse/helpers/avatar"; 3 | import { formatUsername } from "discourse/lib/utilities"; 4 | import { i18n } from "discourse-i18n"; 5 | 6 | export default class DiscoursePostEventCreator extends Component { 7 | get username() { 8 | return this.args.user.name || formatUsername(this.args.user.username); 9 | } 10 | 11 | 23 | } 24 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/colors.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/16348977 2 | export function stringToColor(str) { 3 | let hash = 0; 4 | for (let i = 0; i < str.length; i++) { 5 | // eslint-disable-next-line no-bitwise 6 | hash = str.charCodeAt(i) + ((hash << 5) - hash); 7 | } 8 | let color = []; 9 | for (let i = 0; i < 3; i++) { 10 | // eslint-disable-next-line no-bitwise 11 | let value = (hash >> (i * 8)) & 0xff; 12 | color.push(value); 13 | } 14 | return color; 15 | } 16 | 17 | export function colorToHex(color) { 18 | let hex = "#"; 19 | for (let i = 0; i < 3; i++) { 20 | hex += ("00" + Math.round(color[i]).toString(16)).slice(-2); 21 | } 22 | return hex; 23 | } 24 | 25 | export function contrastColor(color) { 26 | const luminance = 0.2126 * color[0] + 0.7152 * color[1] + 0.0722 * color[2]; 27 | return luminance / 255 >= 0.5 ? "#000d" : "#fffd"; 28 | } 29 | -------------------------------------------------------------------------------- /config/locales/server.ro.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ro: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: Nume de utilizator 12 | site_settings: 13 | map_events_title: "Suprascrie „Evenimente” în titlul barei laterale „Evenimente viitoare” pe categorie." 14 | sidebar_show_upcoming_events: "Afișează legătură evenimente viitoare în bara laterală sub „Mai multe”." 15 | include_expired_events_on_calendar: "Include evenimentele trecute sau expirate în vizualizările de calendar pe categorii și evenimente viitoare." 16 | discourse_post_event: 17 | errors: 18 | bulk_invite: 19 | error: "A apărut o eroare la încărcarea acestui fișier. Te rugăm să încerci din nou, mai târziu." 20 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/controllers/admin-plugins-calendar.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | import { action } from "@ember/object"; 3 | import { ajax } from "discourse/lib/ajax"; 4 | import { popupAjaxError } from "discourse/lib/ajax-error"; 5 | 6 | export default class AdminPluginsCalendarController extends Controller { 7 | selectedRegion = null; 8 | loading = false; 9 | 10 | @action 11 | async getHolidays(region_code) { 12 | if (this.loading) { 13 | return; 14 | } 15 | 16 | this.set("selectedRegion", region_code); 17 | this.set("loading", true); 18 | 19 | return ajax( 20 | `/admin/discourse-calendar/holiday-regions/${region_code}/holidays` 21 | ) 22 | .then((response) => { 23 | this.model.set("holidays", response.holidays); 24 | }) 25 | .catch(popupAjaxError) 26 | .finally(() => this.set("loading", false)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/context/merger.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Context 4 | # Merge a new set of definitions into the Holidays module. 5 | class Merger 6 | def initialize(holidays_by_month_repo, regions_repo, custom_methods_repo) 7 | @holidays_repo = holidays_by_month_repo 8 | @regions_repo = regions_repo 9 | @custom_methods_repo = custom_methods_repo 10 | end 11 | 12 | def call(target_regions, target_holidays, target_custom_methods) 13 | #FIXME Does this need to come in this exact order? God I hope not. 14 | # If not then we should swap the order so it matches the init. 15 | @regions_repo.add(target_regions) 16 | @holidays_repo.add(target_holidays) 17 | @custom_methods_repo.add(target_custom_methods) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/validator/region.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Validator 4 | class Region 5 | def initialize(regions_repo) 6 | @regions_repo = regions_repo 7 | end 8 | 9 | def valid?(r) 10 | return false unless r.is_a?(Symbol) 11 | 12 | region = find_wildcard_base(r) 13 | 14 | (region == :any || 15 | @regions_repo.loaded?(region) || 16 | @regions_repo.all_generated.include?(region)) 17 | end 18 | 19 | private 20 | 21 | # Ex: :gb_ transformed to :gb 22 | def find_wildcard_base(region) 23 | r = region.to_s 24 | 25 | if r =~ /_$/ 26 | base = r.split('_').first 27 | else 28 | base = r 29 | end 30 | 31 | base.to_sym 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/add-recurrent-events.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import DiscoursePostEventEvent from "../models/discourse-post-event-event"; 3 | 4 | export default function addRecurrentEvents(events) { 5 | try { 6 | return events.flatMap((event) => { 7 | if (!event.upcomingDates?.length) { 8 | return [event]; 9 | } 10 | 11 | const upcomingEvents = 12 | event.upcomingDates?.map((upcomingDate) => 13 | DiscoursePostEventEvent.create({ 14 | name: event.name, 15 | post: event.post, 16 | category_id: event.categoryId, 17 | starts_at: upcomingDate.starts_at, 18 | ends_at: upcomingDate.ends_at, 19 | }) 20 | ) || []; 21 | 22 | return upcomingEvents; 23 | }); 24 | } catch (error) { 25 | console.error("Failed to retrieve events:", error); 26 | return []; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/holidays/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | holidays (8.4.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | coderay (1.1.3) 10 | docile (1.4.0) 11 | method_source (1.0.0) 12 | mocha (1.12.0) 13 | power_assert (2.0.0) 14 | pry (0.14.1) 15 | coderay (~> 1.1) 16 | method_source (~> 1.0) 17 | rake (12.3.3) 18 | simplecov (0.21.2) 19 | docile (~> 1.1) 20 | simplecov-html (~> 0.11) 21 | simplecov_json_formatter (~> 0.1) 22 | simplecov-html (0.12.3) 23 | simplecov_json_formatter (0.1.3) 24 | test-unit (3.4.2) 25 | power_assert 26 | 27 | PLATFORMS 28 | arm64-darwin-21 29 | arm64-darwin-22 30 | x86_64-linux 31 | 32 | DEPENDENCIES 33 | bundler (~> 2) 34 | holidays! 35 | mocha (~> 1) 36 | pry (~> 0.12) 37 | rake (~> 12) 38 | simplecov (~> 0.16) 39 | test-unit (~> 3) 40 | 41 | BUNDLED WITH 42 | 2.3.4 43 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_fed_ex.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/fedex.yaml 7 | class Fed_exDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_fed_ex 10 | {Date.civil(2015,1,1) => 'New Year\'s Day', 11 | Date.civil(2015,5,25) => 'Memorial Day', 12 | Date.civil(2015,7,4) => 'Independence Day', 13 | Date.civil(2015,9,7) => 'Labor Day', 14 | Date.civil(2015,11,26) => 'Thanksgiving', 15 | Date.civil(2015,11,27) => 'Day After Thanksgiving', 16 | Date.civil(2013,11,28) => 'Thanksgiving', 17 | Date.civil(2013,11,29) => 'Day After Thanksgiving', 18 | Date.civil(2015,12,25) => 'Christmas Day', 19 | Date.civil(2015,12,31) => 'New Year\'s Eve',}.each do |date, name| 20 | assert_equal name, (Holidays.on(date, :ups)[0] || {})[:name] 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/event_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | class EventValidator 5 | def initialize(post) 6 | @post = post 7 | @first_post = post.topic.first_post 8 | end 9 | 10 | def validate_event 11 | dates_count = count_dates(@post) 12 | calendar_type = @first_post.custom_fields[DiscourseCalendar::CALENDAR_CUSTOM_FIELD] 13 | 14 | if calendar_type == "dynamic" && dates_count > 2 15 | @post.errors.add(:base, I18n.t("discourse_calendar.more_than_two_dates")) 16 | return false 17 | end 18 | 19 | return false if calendar_type == "static" && dates_count > 0 20 | 21 | dates_count > 0 22 | end 23 | 24 | private 25 | 26 | def count_dates(post) 27 | cooked = PrettyText.cook(post.raw, topic_id: post.topic_id, user_id: post.user_id) 28 | Nokogiri.HTML(cooked).css("span.discourse-local-date").count 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /vendor/holidays/test/integration/test_nonstandard_regions.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | class NonstandardRegionsHolidaysTests < Test::Unit::TestCase 5 | def test_ecbtarget_christmas_day 6 | h = Holidays.on(Date.new(2018,12,25), :ecbtarget) 7 | assert_equal 'Christmas Day', h[0][:name] 8 | end 9 | 10 | def test_federalreserve_memorial_day 11 | h = Holidays.on(Date.new(2018,5,28), :federalreserve) 12 | assert_equal 'Memorial Day', h[0][:name] 13 | 14 | end 15 | 16 | def test_federalreservebanks_independence_day 17 | h = Holidays.on(Date.new(2019,7,4), :federalreservebanks, :observed) 18 | assert_equal 'Independence Day', h[0][:name] 19 | end 20 | 21 | def test_unitednations_international_day_of_families 22 | h = Holidays.on(Date.new(2021,5,15), :unitednations) 23 | assert_equal 'International Day of Families', h[0][:name] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/decorator/custom_method_source.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Decorator 4 | class CustomMethodSource 5 | def call(proc) 6 | validate!(proc) 7 | 8 | method_name = proc.name 9 | args = args_string(proc.arguments) 10 | source = proc.source 11 | 12 | "\"#{method_name.to_s}(#{args})\" => Proc.new { |#{args}|\n#{source}}" 13 | end 14 | 15 | private 16 | 17 | def validate!(proc) 18 | raise ArgumentError if proc.name.nil? || proc.name == "" 19 | raise ArgumentError if proc.arguments.nil? || !proc.arguments.is_a?(Array) || proc.arguments.empty? 20 | raise ArgumentError if proc.source.nil? || proc.source == "" 21 | end 22 | 23 | def args_string(args) 24 | a = args.join(", ") 25 | a[0..-1] 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/system/page_objects/discourse_calendar/post_event_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PageObjects 4 | module Pages 5 | module DiscourseCalendar 6 | class PostEventForm < PageObjects::Pages::Base 7 | MODAL_SELECTOR = ".post-event-builder-modal" 8 | 9 | def fill_location(with) 10 | form.find(".event-field.location input").fill_in(with:) 11 | self 12 | end 13 | 14 | def fill_description(with) 15 | form.find(".event-field.description textarea").fill_in(with:) 16 | self 17 | end 18 | 19 | def form 20 | modal.find("form") 21 | end 22 | 23 | def modal 24 | find(MODAL_SELECTOR) 25 | end 26 | 27 | def submit 28 | modal.find(".d-modal__footer .btn-primary").click 29 | has_no_selector?(MODAL_SELECTOR) 30 | self 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/finder/rules/in_region.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Finder 3 | module Rules 4 | class InRegion 5 | class << self 6 | def call(requested, available) 7 | return true if requested.include?(:any) 8 | 9 | # When an underscore is encountered, derive the parent regions 10 | # symbol and check for both. 11 | requested = requested.collect do |r| 12 | if r.to_s =~ /_/ 13 | chunks = r.to_s.split('_') 14 | 15 | chunks.length.downto(1).map do |num| 16 | chunks[0..-num].join('_').to_sym 17 | end 18 | else 19 | r 20 | end 21 | end 22 | 23 | requested = requested.flatten.uniq 24 | 25 | available.any? { |avail| requested.include?(avail) } 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /db/migrate/20231124021939_delete_similar_holidays.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeleteSimilarHolidays < ActiveRecord::Migration[7.0] 4 | def up 5 | execute <<~SQL 6 | DELETE 7 | FROM calendar_events ce 8 | WHERE 9 | ce.id IN (SELECT DISTINCT(ce3.id) FROM calendar_events ce2 10 | LEFT JOIN calendar_events ce3 ON ce3.user_id = ce2.user_id AND ce3.description = ce2.description 11 | WHERE ce2.start_date >= (ce3.start_date - INTERVAL '1 days') 12 | AND ce2.start_date <= (ce3.start_date + INTERVAL '1 days') 13 | AND ce2.timezone IS NOT NULL 14 | AND ce3.timezone IS NULL 15 | AND ce3.id != ce2.id 16 | AND ce2.post_id IS NULL 17 | AND ce3.post_id IS NULL 18 | ) 19 | SQL 20 | end 21 | 22 | def down 23 | raise ActiveRecord::IrreversibleMigration 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/repository/custom_methods.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Repository 4 | class CustomMethods 5 | def initialize 6 | @custom_methods = {} 7 | end 8 | 9 | # This performs a merge that overwrites any conflicts. 10 | # While this is not ideal I'm leaving it as-is since I have no 11 | # evidence of any current definitions that will cause an issue. 12 | # 13 | # FIXME: this should probably return an error if a method with the 14 | # same ID already exists. 15 | def add(new_custom_methods) 16 | raise ArgumentError if new_custom_methods.nil? 17 | @custom_methods.merge!(new_custom_methods) 18 | end 19 | 20 | def find(method_id) 21 | raise ArgumentError if method_id.nil? || method_id.empty? 22 | @custom_methods[method_id] 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20221121165352_add_type_field_to_events_reminders.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddTypeFieldToEventsReminders < ActiveRecord::Migration[7.0] 4 | def up 5 | reminders_query = <<~SQL 6 | SELECT id, reminders 7 | FROM discourse_post_event_events 8 | WHERE reminders IS NOT NULL 9 | SQL 10 | 11 | DB 12 | .query(reminders_query) 13 | .each do |event| 14 | refactored_reminders = [] 15 | event 16 | .reminders 17 | .split(",") { |reminder| refactored_reminders.push(reminder.prepend("notification.")) } 18 | 19 | event_reminders = refactored_reminders.join(",") 20 | 21 | DB.exec(<<~SQL, id: event.id, reminders: event_reminders) 22 | UPDATE discourse_post_event_events 23 | SET reminders = :reminders 24 | WHERE id = :id 25 | SQL 26 | end 27 | end 28 | 29 | def down 30 | raise ActiveRecord::IrreversibleMigration 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /vendor/holidays/test/holidays/date_calculator/test_day_of_month.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../../test_helper' 2 | 3 | require 'holidays/date_calculator/day_of_month' 4 | 5 | class DayOfMonthDateCalculatorTests < Test::Unit::TestCase 6 | def setup 7 | @subject = Holidays::DateCalculator::DayOfMonth.new 8 | end 9 | 10 | def test_call_returns_expected_results 11 | assert_equal 7, @subject.call(2008, 1, :first, :monday) 12 | assert_equal 18, @subject.call(2008, 12, :third, :thursday) 13 | assert_equal 28, @subject.call(2008, 1, :last, 1) 14 | end 15 | 16 | def test_returns_argument_error_with_invalid_week_parameter 17 | assert_raises ArgumentError do 18 | @subject.call(2008, 1, :wrong_week_argument, :monday) 19 | end 20 | end 21 | 22 | def test_returns_argument_error_with_invalid_day_parameter 23 | assert_raises ArgumentError do 24 | @subject.call(2008, 1, :first, :bad_wday) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /.discourse-compatibility: -------------------------------------------------------------------------------- 1 | < 3.5.0.beta5-dev: bd6e30721474e16d09d1a55adb11cfaf70947837 2 | < 3.5.0.beta3-dev: 8d09cf8503b78f4c72b47a7319c0f4b9ad0247e7 3 | < 3.5.0.beta2-dev: c1031224a552ea01958d153350c2d1254b323579 4 | < 3.5.0.beta1-dev: fdf3ad927744a9dbb826cc46e489cca8ad469044 5 | < 3.4.0.beta2-dev: 5f6942c1f3616d43eec18a71d76baa0e55e1340b 6 | < 3.4.0.beta1-dev: 908ad614bc412f831f929ca726a4bda0b9ccaab6 7 | < 3.3.0.beta2-dev: 455eeed541a9b5cacf627349e543028427178a44 8 | < 3.3.0.beta1-dev: 84ef46a38cf02748ecacad16c5d9c6fec12dc8da 9 | < 3.2.0.beta3-dev: 513a92b59dd80e79cc39fe6d8eb084ce7f5db5a7 10 | < 3.2.0.beta2: 6222eec8fc0e61971f4fa4939b39cf9247201c33 11 | 3.1.0.beta3: 10077ca904956005f9fa83c3d9fb124b59e8c47b 12 | 2.9.0.beta13: b4c366b35d6f9778e54a878aa083348e8a45e86e 13 | 2.9.0.beta9: 6f16ae10dc0306d6e1369e0d1414416d7e72141d 14 | 2.9.0.beta7: ec34a96f215dce9c7321dfe04f6dd0172a197711 15 | 2.9.0.beta3: 9ab1b76a1425304f3434b49db49cb4eec8919cf6 16 | 2.7.0.beta3: 52e22b9972dc79c67f6a2f7c4c9f918307222030 17 | -------------------------------------------------------------------------------- /spec/integration/curently_away_report_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe "currently_away report" do 4 | fab!(:user_1) { Fabricate(:user) } 5 | fab!(:user_2) { Fabricate(:user) } 6 | fab!(:group_1) { Fabricate(:group) } 7 | 8 | before { group_1.add(user_1) } 9 | 10 | context "when users_on_holiday is not set" do 11 | it "does not generate report with data" do 12 | report = Report.find("currently_away", filters: { group: group_1.id }) 13 | 14 | expect(report.data).to eq([]) 15 | expect(report.total).to eq(0) 16 | end 17 | end 18 | 19 | context "when users_on_holiday is set" do 20 | before { DiscourseCalendar.users_on_holiday = [user_1.username] } 21 | 22 | it "generates a correct report" do 23 | report = Report.find("currently_away", filters: { group: group_1.id }) 24 | 25 | expect(report.data).to contain_exactly({ username: user_1.username }) 26 | expect(report.total).to eq(1) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/holiday_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | class HolidayStatus 5 | def self.set!(user, ends_at) 6 | status = user.user_status 7 | if status.blank? || status.expired? || 8 | (is_holiday_status?(status) && status.ends_at != ends_at) 9 | user.set_status!( 10 | I18n.t("discourse_calendar.holiday_status.description"), 11 | emoji_name, 12 | ends_at, 13 | ) 14 | end 15 | end 16 | 17 | def self.clear!(user) 18 | user.clear_status! if user&.user_status && is_holiday_status?(user.user_status) 19 | end 20 | 21 | private 22 | 23 | def self.is_holiday_status?(status) 24 | status.emoji == emoji_name && 25 | status.description == I18n.t("discourse_calendar.holiday_status.description") 26 | end 27 | 28 | def self.emoji_name 29 | emoji = SiteSetting.holiday_status_emoji 30 | emoji.blank? ? "date" : emoji 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_vi.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/vi.yaml 7 | class ViDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_vi 10 | assert_equal "Tết dương lịch", (Holidays.on(Date.civil(2014, 1, 1), [:vi])[0] || {})[:name] 11 | 12 | assert_equal "Ngày Giải phóng miền Nam, thống nhất đất nước", (Holidays.on(Date.civil(2014, 4, 30), [:vi])[0] || {})[:name] 13 | 14 | assert_equal "Ngày Quốc tế Lao động", (Holidays.on(Date.civil(2014, 5, 1), [:vi])[0] || {})[:name] 15 | 16 | assert_equal "Quốc khánh", (Holidays.on(Date.civil(2014, 9, 2), [:vi])[0] || {})[:name] 17 | 18 | assert_equal "Giỗ tổ Hùng Vương", (Holidays.on(Date.civil(2017, 4, 6), [:vi])[0] || {})[:name] 19 | assert_equal "Giỗ tổ Hùng Vương", (Holidays.on(Date.civil(2018, 3, 27), [:vi])[0] || {})[:name] 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/decorator/test.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Decorator 4 | class Test 5 | def call(t) 6 | src = "" 7 | 8 | t.dates.each do |d| 9 | date = "Date.civil(#{d.year}, #{d.month}, #{d.day})" 10 | 11 | holiday_call = "Holidays.on(#{date}, #{t.regions}" 12 | 13 | if t.options 14 | holiday_call += ", #{decorate_options(t.options)}" 15 | end 16 | 17 | if t.holiday? 18 | src += "assert_equal \"#{t.name}\", (#{holiday_call})[0] || {})[:name]\n" 19 | else 20 | src += "assert_nil (#{holiday_call})[0] || {})[:name]\n" 21 | end 22 | end 23 | 24 | src 25 | end 26 | 27 | private 28 | 29 | def decorate_options(options) 30 | options.map do |o| 31 | o.to_sym 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/discourse_calendar/site_settings/map_events_title_json_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | module SiteSettings 5 | class MapEventsTitleJsonSchema 6 | def self.schema 7 | @schema ||= { 8 | type: "array", 9 | uniqueItems: true, 10 | items: { 11 | type: "object", 12 | title: "Title Mapping", 13 | properties: { 14 | category_slug: { 15 | type: "string", 16 | description: "Slug of the category", 17 | }, 18 | custom_title: { 19 | type: "string", 20 | default: "Upcoming events", 21 | description: 22 | "The words you want to replace 'Upcoming Events' with for the sidebar calendar", 23 | }, 24 | }, 25 | required: %w[category_slug custom_title], 26 | }, 27 | } 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /assets/stylesheets/common/discourse-post-event-bulk-invite-modal.scss: -------------------------------------------------------------------------------- 1 | .post-event-bulk-invite { 2 | .bulk-event-help { 3 | margin: 0 0 1em 0; 4 | } 5 | 6 | .bulk-invite-rows { 7 | margin-bottom: 1em; 8 | 9 | .group-selector { 10 | margin: 0; 11 | } 12 | } 13 | 14 | .bulk-invite-row { 15 | display: flex; 16 | padding: 0.25em 0; 17 | 18 | .bulk-invite-attendance { 19 | margin: 0 0.5em; 20 | 21 | .select-kit-header { 22 | height: 100%; 23 | } 24 | } 25 | 26 | .remove-bulk-invite { 27 | margin-left: auto; 28 | } 29 | } 30 | 31 | .bulk-invites { 32 | margin-bottom: 2em; 33 | 34 | .bulk-invite-actions { 35 | display: flex; 36 | 37 | .add-bulk-invite { 38 | margin-left: auto; 39 | } 40 | } 41 | } 42 | 43 | .csv-bulk-invites { 44 | .bulk-invite-actions { 45 | display: flex; 46 | 47 | > :last-child { 48 | margin-left: 0.5em; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /assets/stylesheets/mobile/discourse-calendar.scss: -------------------------------------------------------------------------------- 1 | .discourse-calendar-wrap { 2 | border: 0; 3 | 4 | .discourse-calendar-header { 5 | padding: 0; 6 | background: none; 7 | 8 | h2.discourse-calendar-title { 9 | font-size: var(--font-0); 10 | flex-wrap: nowrap; 11 | max-width: 75%; 12 | white-space: nowrap; 13 | text-overflow: ellipsis; 14 | overflow: hidden; 15 | } 16 | 17 | .discourse-calendar-timezone-picker { 18 | max-width: 40vw; 19 | } 20 | } 21 | 22 | .fc-view-container { 23 | .fc-day-header.fc-widget-header { 24 | text-align: center; 25 | padding: 0.25em; 26 | 27 | span { 28 | font-size: var(--font-down-1); 29 | text-align: center; 30 | padding: 0; 31 | } 32 | } 33 | } 34 | 35 | .calendar { 36 | padding: 0; 37 | 38 | .fc-list-item-add-to-calendar { 39 | display: block; 40 | } 41 | 42 | &.fc-unthemed .fc-header-toolbar { 43 | padding: 0.5em 0; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/context/load.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Context 4 | class Load 5 | def initialize(definition_merger, full_definitions_path) 6 | @definition_merger = definition_merger 7 | @full_definitions_path = full_definitions_path 8 | end 9 | 10 | def call(region) 11 | region_definition_file = "#{@full_definitions_path}/#{region}" 12 | require region_definition_file 13 | 14 | target_region_module = Module.const_get("Holidays").const_get(region.upcase) 15 | 16 | @definition_merger.call( 17 | region, 18 | target_region_module.holidays_by_month, 19 | target_region_module.custom_methods, 20 | ) 21 | 22 | target_region_module.defined_regions 23 | rescue NameError, LoadError => e 24 | raise UnknownRegionError.new(e), "Could not load region prefix: #{region.to_s}" 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /config/locales/client.sr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sr: 8 | js: 9 | discourse_automation: 10 | triggerables: 11 | event_started: 12 | fields: 13 | topic_id: 14 | label: ID Teme 15 | discourse_calendar: 16 | disable_holiday: "Onemogući" 17 | enable_holiday: "Omogući" 18 | region: 19 | none: "Ništa" 20 | toolbar_button: 21 | today: "Danas" 22 | month: "Mesec" 23 | week: "Nedelja" 24 | discourse_post_event: 25 | builder_modal: 26 | update: "Sačuvaj" 27 | reminders: 28 | units: 29 | days: "dana" 30 | periods: 31 | before: "pre" 32 | url: 33 | label: "URL" 34 | location: 35 | label: "Lokacija" 36 | description: 37 | label: "Opis" 38 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/add-upcoming-events-to-sidebar.js: -------------------------------------------------------------------------------- 1 | import { withPluginApi } from "discourse/lib/plugin-api"; 2 | import { i18n } from "discourse-i18n"; 3 | 4 | export default { 5 | name: "add-upcoming-events-to-sidebar", 6 | 7 | initialize(container) { 8 | const siteSettings = container.lookup("service:site-settings"); 9 | if ( 10 | siteSettings.discourse_post_event_enabled && 11 | siteSettings.sidebar_show_upcoming_events 12 | ) { 13 | withPluginApi("0.8.7", (api) => { 14 | api.addCommunitySectionLink((baseSectionLink) => { 15 | return class UpcomingEventsSectionLink extends baseSectionLink { 16 | name = "upcoming-events"; 17 | route = "discourse-post-event-upcoming-events"; 18 | text = i18n("discourse_post_event.upcoming_events.title"); 19 | title = i18n("discourse_post_event.upcoming_events.title"); 20 | defaultPrefixValue = "calendar-day"; 21 | }; 22 | }); 23 | }); 24 | } 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /spec/integration/invitee_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe DiscoursePostEvent::Invitee do 6 | before do 7 | freeze_time 8 | Jobs.run_immediately! 9 | SiteSetting.calendar_enabled = true 10 | SiteSetting.discourse_post_event_enabled = true 11 | end 12 | 13 | let(:user) { Fabricate(:user, admin: true) } 14 | let(:user_1) { Fabricate(:user) } 15 | let(:topic) { Fabricate(:topic, user: user) } 16 | let(:post1) { Fabricate(:post, topic: topic) } 17 | let(:post_event) { Fabricate(:event, post: post1) } 18 | 19 | context "when a user is destroyed" do 20 | context "when the user is an invitee to an event" do 21 | before { post_event.create_invitees([{ user_id: user_1.id, status: nil }]) } 22 | 23 | it "destroys the invitee" do 24 | expect(post_event.invitees.first.user.id).to eq(user_1.id) 25 | 26 | UserDestroyer.new(user_1).destroy(user_1) 27 | 28 | expect(post_event.invitees).to be_empty 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/javascripts/acceptance/sort-event-topics-test.js: -------------------------------------------------------------------------------- 1 | import { visit } from "@ember/test-helpers"; 2 | import { test } from "qunit"; 3 | import Site from "discourse/models/site"; 4 | import { acceptance } from "discourse/tests/helpers/qunit-helpers"; 5 | 6 | acceptance("Calendar - Disable sorting headers", function (needs) { 7 | needs.user(); 8 | needs.settings({ 9 | calendar_enabled: true, 10 | discourse_post_event_enabled: true, 11 | disable_resorting_on_categories_enabled: true, 12 | }); 13 | 14 | test("visiting a category page", async function (assert) { 15 | const site = Site.current(); 16 | site.categories[15].custom_fields = { disable_topic_resorting: true }; 17 | 18 | await visit("/c/bug"); 19 | assert.dom(".topic-list").exists("The list of topics was rendered"); 20 | assert 21 | .dom(".topic-list .topic-list-data") 22 | .exists("The headers were rendered"); 23 | assert 24 | .dom(".topic-list") 25 | .doesNotHaveClass("sortable", "The headers are not sortable"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/lib/validation/definition_validator.rb: -------------------------------------------------------------------------------- 1 | require_relative 'error' 2 | 3 | module Definitions 4 | module Validation 5 | class Definition 6 | def initialize(custom_method_validator, months_validator, test_validator) 7 | @custom_method_validator = custom_method_validator 8 | @months_validator = months_validator 9 | @test_validator = test_validator 10 | end 11 | 12 | def call(definition) 13 | validate_months!(definition['months']) 14 | validate_methods!(definition['methods']) 15 | validate_tests!(definition['tests']) 16 | 17 | true 18 | end 19 | 20 | private 21 | 22 | def validate_months!(months) 23 | @months_validator.call(months) 24 | end 25 | 26 | def validate_methods!(methods) 27 | @custom_method_validator.call(methods) unless methods.nil? 28 | end 29 | 30 | def validate_tests!(tests) 31 | @test_validator.call(tests) unless tests.nil? 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/initializers/event-relative-date.js: -------------------------------------------------------------------------------- 1 | import { cancel } from "@ember/runloop"; 2 | import { isTesting } from "discourse/lib/environment"; 3 | import discourseLater from "discourse/lib/later"; 4 | import eventRelativeDate from "../lib/event-relative-date"; 5 | 6 | function computeRelativeEventDates() { 7 | document 8 | .querySelectorAll(".event-relative-date.topic-list") 9 | .forEach((dateContainer) => eventRelativeDate(dateContainer)); 10 | } 11 | 12 | export default { 13 | name: "event-future-date", 14 | 15 | initialize() { 16 | computeRelativeEventDates(); 17 | 18 | if (!isTesting()) { 19 | this._tick(); 20 | } 21 | }, 22 | 23 | teardown() { 24 | if (this._interval) { 25 | cancel(this._interval); 26 | this._interval = null; 27 | } 28 | }, 29 | 30 | _tick() { 31 | this._interval && cancel(this._interval); 32 | 33 | this._interval = discourseLater(() => { 34 | computeRelativeEventDates(); 35 | this._tick(); 36 | }, 60 * 1000); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /spec/system/category_calendar_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe "Category calendar", type: :system do 4 | fab!(:admin) 5 | fab!(:category) 6 | 7 | let(:category_page) { PageObjects::Pages::Category.new } 8 | 9 | before do 10 | SiteSetting.calendar_enabled = true 11 | SiteSetting.discourse_post_event_enabled = true 12 | SiteSetting.events_calendar_categories = category.id.to_s 13 | sign_in(admin) 14 | end 15 | 16 | it "shows the calendar on the category page" do 17 | category_page.visit(category) 18 | 19 | expect(category_page).to have_selector("#category-events-calendar.fc") 20 | 21 | find(".nav-item_hot").click 22 | 23 | expect(page).to have_current_path("#{category.relative_url}/l/hot") 24 | expect(category_page).to have_selector("#category-events-calendar.fc") 25 | 26 | find(".nav-item_latest").click 27 | 28 | expect(page).to have_current_path("#{category.relative_url}/l/latest") 29 | expect(category_page).to have_selector("#category-events-calendar.fc") 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/serializers/discourse_post_event/event_stats_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | class EventStatsSerializer < ApplicationSerializer 5 | attributes :going 6 | attributes :interested 7 | attributes :not_going 8 | attributes :invited 9 | 10 | def invited 11 | unanswered = counts[nil] || 0 12 | 13 | # when a group is private we know the list of possible users 14 | # even if an invitee has not been created yet 15 | unanswered += object.missing_users.count if object.private? 16 | 17 | going + interested + not_going + unanswered 18 | end 19 | 20 | def going 21 | @going ||= counts[Invitee.statuses[:going]] || 0 22 | end 23 | 24 | def interested 25 | @interested ||= counts[Invitee.statuses[:interested]] || 0 26 | end 27 | 28 | def not_going 29 | @not_going ||= counts[Invitee.statuses[:not_going]] || 0 30 | end 31 | 32 | def counts 33 | @counts ||= object.invitees.group(:status).count 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/lib/validation/custom_method_validator.rb: -------------------------------------------------------------------------------- 1 | require_relative 'error' 2 | 3 | module Definitions 4 | module Validation 5 | class CustomMethod 6 | VALID_ARGUMENTS = ["date", "year", "month", "day"] 7 | 8 | def call(methods) 9 | methods.each do |name, method| 10 | raise Errors::InvalidCustomMethod unless 11 | valid_name?(name) && 12 | valid_arguments?(method['arguments']) && 13 | valid_source?(method['ruby']) 14 | end 15 | 16 | true 17 | end 18 | 19 | private 20 | 21 | def valid_name?(name) 22 | !name.nil? && !name.empty? 23 | end 24 | 25 | def valid_arguments?(arguments) 26 | !arguments.nil? && 27 | !arguments.empty? && 28 | arguments.split(",").all? { |arg| 29 | arg == arg.chomp && VALID_ARGUMENTS.include?(arg.strip) 30 | } 31 | end 32 | 33 | def valid_source?(source) 34 | !source.nil? && !source.empty? 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /vendor/holidays/test/holidays/definition/context/test_merger.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../../../test_helper' 2 | 3 | require 'holidays/definition/context/merger' 4 | 5 | class MergerTests < Test::Unit::TestCase 6 | def setup 7 | @target_regions = [:new_region] 8 | @target_holidays = {0 => [:mday => 1, :name => "Test", :regions => [:test2, :test]]} 9 | @target_custom_methods = {"test_method" => Proc.new { |year| Date.civil(year, 1, 1) } } 10 | 11 | @holidays_repo = mock() 12 | @regions_repo = mock() 13 | @custom_methods_repo = mock() 14 | 15 | @subject = Holidays::Definition::Context::Merger.new(@holidays_repo, @regions_repo, @custom_methods_repo) 16 | end 17 | 18 | def test_repos_are_called_to_add_regions_and_holidays 19 | @holidays_repo.expects(:add).with(@target_holidays) 20 | @regions_repo.expects(:add).with(@target_regions) 21 | @custom_methods_repo.expects(:add).with(@target_custom_methods) 22 | 23 | @subject.call(@target_regions, @target_holidays, @target_custom_methods) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/popover.js: -------------------------------------------------------------------------------- 1 | import { createPopper } from "@popperjs/core"; 2 | 3 | let eventPopper; 4 | const EVENT_POPOVER_ID = "event-popover"; 5 | 6 | export function buildPopover(jsEvent, htmlContent) { 7 | const node = document.createElement("div"); 8 | node.setAttribute("id", EVENT_POPOVER_ID); 9 | node.innerHTML = htmlContent; 10 | 11 | const arrow = document.createElement("span"); 12 | arrow.dataset.popperArrow = true; 13 | node.appendChild(arrow); 14 | document.body.appendChild(node); 15 | 16 | eventPopper = createPopper( 17 | jsEvent.target, 18 | document.getElementById(EVENT_POPOVER_ID), 19 | { 20 | placement: "bottom", 21 | modifiers: [ 22 | { 23 | name: "arrow", 24 | }, 25 | { 26 | name: "offset", 27 | options: { 28 | offset: [20, 10], 29 | }, 30 | }, 31 | ], 32 | } 33 | ); 34 | } 35 | 36 | export function destroyPopover() { 37 | eventPopper?.destroy(); 38 | document.getElementById(EVENT_POPOVER_ID)?.remove(); 39 | } 40 | -------------------------------------------------------------------------------- /vendor/holidays/test/coverage_report.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | 3 | # For reasons I don't understand jruby implementations report lower coverage 4 | # than other ruby versions. Ruby 2.5.3, for instance, is at 92%. 5 | # 6 | # We set the floor based on jruby so that all automated tests pass on Travis CI. 7 | SimpleCov.minimum_coverage 89 8 | 9 | SimpleCov.add_filter [ 10 | # Apparently simplecov doesn't automatically filter 'spec' or 'test' so we 11 | # have to do it manually. 12 | 'test', 13 | 14 | # Only filtered because I tend to not see value in testing factories. 15 | 'lib/holidays/factory/', 16 | 17 | # jruby coverage flips out here and doesn't count much of the large date 18 | # arrays used by this class. This results in an extremely low reported 19 | # coverage for this specific file but only in jruby, not other ruby versions. 20 | # Since it obliterates coverage percentages I'll filter it until I can come 21 | # up with a solution. 22 | 'lib/holidays/date_calculator/lunar_date.rb', 23 | ] 24 | 25 | SimpleCov.coverage_dir 'reports/coverage' 26 | SimpleCov.start 27 | -------------------------------------------------------------------------------- /spec/serializers/user_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe UserSerializer do 4 | fab!(:user) 5 | 6 | subject(:json) { described_class.new(user, scope: guardian).as_json } 7 | 8 | before do 9 | SiteSetting.calendar_enabled = true 10 | user.upsert_custom_fields(DiscourseCalendar::REGION_CUSTOM_FIELD => "uk") 11 | end 12 | 13 | context "as another user" do 14 | fab!(:guardian) { Fabricate(:user).guardian } 15 | 16 | it "does not return user region" do 17 | expect(json[:user][:custom_fields]).to be_blank 18 | end 19 | end 20 | 21 | context "as current user" do 22 | fab!(:guardian) { user.guardian } 23 | 24 | it "returns user region" do 25 | expect(json[:user][:custom_fields]).to eq(DiscourseCalendar::REGION_CUSTOM_FIELD => "uk") 26 | end 27 | end 28 | 29 | context "as staff" do 30 | fab!(:guardian) { Fabricate(:admin).guardian } 31 | 32 | it "returns user region" do 33 | expect(json[:user][:custom_fields]).to eq(DiscourseCalendar::REGION_CUSTOM_FIELD => "uk") 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/api-initializers/discourse-group-timezones.gjs: -------------------------------------------------------------------------------- 1 | import { apiInitializer } from "discourse/lib/api"; 2 | import GroupTimezones from "../components/group-timezones"; 3 | 4 | const GroupTimezonesShim = ; 11 | 12 | export default apiInitializer((api) => { 13 | api.decorateCookedElement((element, helper) => { 14 | element.querySelectorAll(".group-timezones").forEach((el) => { 15 | const post = helper.getModel(); 16 | 17 | if (!post) { 18 | return; 19 | } 20 | 21 | const group = el.dataset.group; 22 | if (!group) { 23 | throw new Error( 24 | "Group timezone element is missing 'data-group' attribute" 25 | ); 26 | } 27 | 28 | helper.renderGlimmer(el, GroupTimezonesShim, { 29 | group, 30 | members: (post.group_timezones || {})[group] || [], 31 | size: el.dataset.size || "medium", 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /vendor/holidays/lib/generated_definitions/vi.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Holidays 3 | # This file is generated by the Ruby Holidays gem. 4 | # 5 | # Definitions loaded: definitions/vi.yaml 6 | # 7 | # All the definitions are available at https://github.com/holidays/holidays 8 | module VI # :nodoc: 9 | def self.defined_regions 10 | [:vi] 11 | end 12 | 13 | def self.holidays_by_month 14 | { 15 | 1 => [{:mday => 1, :name => "Tết dương lịch", :regions => [:vi]}], 16 | 3 => [{:mday => 10, :function => "lunar_to_solar(year, month, day, region)", :function_arguments => [:year, :month, :day, :region], :name => "Giỗ tổ Hùng Vương", :regions => [:vi]}], 17 | 4 => [{:mday => 30, :name => "Ngày Giải phóng miền Nam, thống nhất đất nước", :regions => [:vi]}], 18 | 5 => [{:mday => 1, :name => "Ngày Quốc tế Lao động", :regions => [:vi]}], 19 | 9 => [{:mday => 2, :name => "Quốc khánh", :regions => [:vi]}] 20 | } 21 | end 22 | 23 | def self.custom_methods 24 | { 25 | 26 | } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/bulk-invite-sample-csv-file.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@ember/component"; 2 | import { action } from "@ember/object"; 3 | import DButton from "discourse/components/d-button"; 4 | 5 | export default class BulkInviteSampleCsvFile extends Component { 6 | @action 7 | downloadSampleCsv() { 8 | const sampleData = [ 9 | ["my_awesome_group", "going"], 10 | ["lucy", "interested"], 11 | ["mark", "not_going"], 12 | ["sam", "unknown"], 13 | ]; 14 | 15 | let csv = ""; 16 | sampleData.forEach((row) => { 17 | csv += row.join(","); 18 | csv += "\n"; 19 | }); 20 | 21 | const btn = document.createElement("a"); 22 | btn.href = `data:text/csv;charset=utf-8,${encodeURI(csv)}`; 23 | btn.target = "_blank"; 24 | btn.rel = "noopener noreferrer"; 25 | btn.download = "bulk-invite-sample.csv"; 26 | btn.click(); 27 | } 28 | 29 | 35 | } 36 | -------------------------------------------------------------------------------- /spec/fabricators/event_fabricator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Fabricator(:event, from: "DiscoursePostEvent::Event") do 4 | post { |attrs| attrs[:post] || Fabricate(:post) } 5 | 6 | id { |attrs| attrs[:post].id } 7 | 8 | status do |attrs| 9 | if attrs[:status] 10 | DiscoursePostEvent::Event.statuses[attrs[:status]] 11 | else 12 | DiscoursePostEvent::Event.statuses[:public] 13 | end 14 | end 15 | original_starts_at { |attrs| attrs[:original_starts_at] || 1.day.from_now.iso8601 } 16 | original_ends_at { |attrs| attrs[:original_ends_at] } 17 | end 18 | 19 | Fabricator(:event_date, from: "DiscoursePostEvent::EventDate") do 20 | event 21 | 22 | starts_at { |attrs| attrs[:starts_at] || 1.day.from_now.iso8601 } 23 | ends_at { |attrs| attrs[:ends_at] } 24 | end 25 | 26 | def create_post_with_event(user, extra_raw = "") 27 | start = (Time.now - 10.seconds).utc.iso8601(3) 28 | 29 | PostCreator.create!( 30 | user, 31 | title: "Sell a boat party ##{SecureRandom.alphanumeric}", 32 | raw: "[event start=\"#{start}\" #{extra_raw}]\n[/event]", 33 | ).reload 34 | end 35 | -------------------------------------------------------------------------------- /spec/serializers/discourse_post_event/post_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe PostSerializer do 4 | before do 5 | Jobs.run_immediately! 6 | SiteSetting.calendar_enabled = true 7 | SiteSetting.discourse_post_event_enabled = true 8 | end 9 | 10 | context "when post has an event" do 11 | let(:user) { Fabricate(:user, admin: true) } 12 | let(:topic_1) { Fabricate(:topic, user: user) } 13 | let(:post_1) { Fabricate(:post, topic: topic_1) } 14 | let!(:post_event_1) { Fabricate(:event, post: post_1) } 15 | 16 | it "serializes the associated event" do 17 | json = PostSerializer.new(post_1, scope: Guardian.new).as_json 18 | expect(json[:post][:event]).to be_present 19 | end 20 | 21 | context "when the post has been destroyed" do 22 | before { PostDestroyer.new(Discourse.system_user, post_1).destroy } 23 | 24 | it "doesn’t serialize the associated event" do 25 | json = PostSerializer.new(post_1, scope: Guardian.new).as_json 26 | expect(json[:post][:event]).to_not be_present 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /assets/stylesheets/common/discourse-post-event-core-ext.scss: -------------------------------------------------------------------------------- 1 | .header-title { 2 | @include ellipsis; 3 | 4 | .topic-link { 5 | display: inline; 6 | } 7 | 8 | .event-date { 9 | font-size: var(--font-down-4); 10 | color: var(--primary-medium); 11 | font-weight: normal; 12 | padding: 0.25em; 13 | } 14 | } 15 | 16 | .main-link { 17 | .event-date-container-wrapper { 18 | // prevents new dot from breaking separately onto next line 19 | white-space: nowrap; 20 | } 21 | } 22 | 23 | .link-top-line, 24 | .header-title { 25 | .event-date { 26 | display: inline-flex; 27 | align-items: center; 28 | font-size: var(--font-down-2); 29 | border: 1px solid var(--primary-medium); 30 | background: none; 31 | padding: 0 0.25em; 32 | border-radius: 3px; 33 | pointer-events: auto; // needed to show title attribute on hover 34 | vertical-align: text-bottom; 35 | 36 | .indicator { 37 | display: flex; 38 | width: 6px; 39 | height: 6px; 40 | border-radius: 3px; 41 | background: var(--success); 42 | margin-right: 0.25em; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/discourse_post_event/export_csv_file_extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | module ExportPostEventCsvReportExtension 5 | def post_event_export(&block) 6 | return enum_for(:post_event_export) unless block_given? 7 | 8 | guardian = Guardian.new(current_user) 9 | 10 | event = DiscoursePostEvent::Event.includes(invitees: :user).find(@extra[:id]) 11 | 12 | guardian.ensure_can_act_on_discourse_post_event!(event) 13 | 14 | event 15 | .invitees 16 | .order(:id) 17 | .each do |invitee| 18 | yield( 19 | [ 20 | invitee.user.username, 21 | DiscoursePostEvent::Invitee.statuses[invitee.status], 22 | invitee.created_at, 23 | invitee.updated_at, 24 | ] 25 | ) 26 | end 27 | end 28 | 29 | def get_header(entity) 30 | if SiteSetting.discourse_post_event_enabled && entity === "post_event" 31 | %w[username status first_answered_at last_updated_at] 32 | else 33 | super 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/javascripts/acceptance/topic-calendar-events-test.js: -------------------------------------------------------------------------------- 1 | import { visit } from "@ember/test-helpers"; 2 | import { skip } from "qunit"; 3 | import { acceptance, fakeTime } from "discourse/tests/helpers/qunit-helpers"; 4 | import eventTopicFixture from "../helpers/event-topic-fixture"; 5 | import getEventByText from "../helpers/get-event-by-text"; 6 | 7 | acceptance("Discourse Calendar - Topic Calendar Events", function (needs) { 8 | needs.hooks.beforeEach(function () { 9 | this.clock = fakeTime("2023-09-10T00:00:00", "Europe/Lisbon", true); 10 | }); 11 | 12 | needs.hooks.afterEach(function () { 13 | this.clock.restore(); 14 | }); 15 | 16 | needs.settings({ 17 | calendar_enabled: true, 18 | }); 19 | 20 | needs.pretender((server, helper) => { 21 | server.get("/t/252.json", () => { 22 | return helper.response(eventTopicFixture); 23 | }); 24 | }); 25 | 26 | skip("renders calendar events with fullDay='false'", async (assert) => { 27 | await visit("/t/-/252"); 28 | 29 | assert.dom(getEventByText("Event 1")).exists(); 30 | assert.dom(getEventByText("Event 2")).exists(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/README.md: -------------------------------------------------------------------------------- 1 | # Holidays Definitions [![Build Status](https://travis-ci.org/holidays/definitions.svg?branch=master)](https://travis-ci.org/holidays/definitions) 2 | 3 | This repository contains the 'raw' definitions for the various holidays projects. It should be added a submodule to 4 | any project that wants to use them. 5 | 6 | Currently it is only used by the [existing Holidays gem](https://github.com/holidays/holidays), which takes these 7 | definitions and generates ruby classes for use in that gem. In the future it will be used by other languages in 8 | a similar manner. 9 | 10 | **Please note** that this is _not_ a gem. The validation process is written in ruby simply for convenience. The real 11 | stars of this show are the YAML files. 12 | 13 | ### Documentation 14 | 15 | 1. [Syntax Guide](doc/SYNTAX.md) 16 | 2. [Contribution Guidelines](doc/CONTRIBUTING.md) 17 | 3. [Maintainer Guidelines](doc/MAINTAINERS.md) 18 | 4. [Architecture Decision Records](doc/architecture/README.md) 19 | 20 | ### Credits 21 | 22 | Thank you to all of these [wonderful contributors!](https://github.com/holidays/definitions/contributors) 23 | -------------------------------------------------------------------------------- /vendor/holidays/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/coverage_report' 2 | 3 | $:.unshift(File.expand_path(File.dirname(__FILE__) + '../../lib/')) 4 | 5 | $KCODE = 'u' if RUBY_VERSION =~ /^1\.8/ 6 | 7 | require 'rubygems' 8 | require 'test/unit' 9 | require 'mocha/test_unit' 10 | require 'date' 11 | require 'holidays' 12 | require 'holidays/core_extensions/date' 13 | require 'holidays/core_extensions/time' 14 | 15 | # Loads core extension for use in various definition tests as necessary 16 | class Date 17 | include Holidays::CoreExtensions::Date 18 | end 19 | 20 | class Time 21 | include Holidays::CoreExtensions::Time 22 | end 23 | 24 | module Holidays 25 | # Test region used for generating a holiday on Date.today 26 | module Test # :nodoc: 27 | DEFINED_REGIONS = [:test] 28 | 29 | HOLIDAYS_BY_MONTH = { 30 | Date.today.mon => [{:mday => Date.today.mday, :name => "Test Holiday", :regions => [:test]}] 31 | } 32 | 33 | CUSTOM_METHODS = {} 34 | end 35 | end 36 | 37 | Holidays::Factory::Definition.merger.call(Holidays::Test::DEFINED_REGIONS, Holidays::Test::HOLIDAYS_BY_MONTH, Holidays::Test::CUSTOM_METHODS) 38 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/lib/discourse-markdown/discourse-post-event-block.js: -------------------------------------------------------------------------------- 1 | const rule = { 2 | tag: "event", 3 | 4 | wrap(token, info) { 5 | if (!info.attrs.start) { 6 | return false; 7 | } 8 | 9 | token.attrs = [["class", "discourse-post-event"]]; 10 | 11 | Object.keys(info.attrs).forEach((key) => { 12 | const value = info.attrs[key]; 13 | 14 | if (typeof value !== "undefined") { 15 | token.attrs.push([`data-${dasherize(key)}`, value]); 16 | } 17 | }); 18 | 19 | return true; 20 | }, 21 | }; 22 | 23 | function dasherize(input) { 24 | return input.replace(/[A-Z]/g, function (char, index) { 25 | return (index !== 0 ? "-" : "") + char.toLowerCase(); 26 | }); 27 | } 28 | 29 | export function setup(helper) { 30 | helper.allowList(["div.discourse-post-event"]); 31 | 32 | helper.registerOptions((opts, siteSettings) => { 33 | opts.features.discourse_post_event = 34 | siteSettings.calendar_enabled && 35 | siteSettings.discourse_post_event_enabled; 36 | }); 37 | 38 | helper.registerPlugin((md) => 39 | md.block.bbcode.ruler.push("discourse-post-event", rule) 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /lib/discourse_calendar/site_settings/map_event_tag_colors_json_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseCalendar 4 | module SiteSettings 5 | class MapEventTagColorsJsonSchema 6 | def self.schema 7 | @schema ||= { 8 | type: "array", 9 | uniqueItems: true, 10 | items: { 11 | type: "object", 12 | title: "Color Mapping", 13 | properties: { 14 | type: { 15 | type: "string", 16 | description: "Type of mapping (tag or category)", 17 | enum: %w[tag category], 18 | }, 19 | slug: { 20 | type: "string", 21 | description: "Slug of the tag or category", 22 | }, 23 | color: { 24 | type: "string", 25 | format: "color", 26 | default: "#FFFFFF", 27 | description: "Color associated with the tag or category", 28 | }, 29 | }, 30 | required: %w[slug type color], 31 | }, 32 | } 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Civilized Discourse Construction Kit, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/discourse-post-event/event-status.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@glimmer/component"; 2 | import { i18n } from "discourse-i18n"; 3 | 4 | export default class EventStatus extends Component { 5 | get eventStatusLabel() { 6 | return i18n( 7 | `discourse_post_event.models.event.status.${this.args.event.status}.title` 8 | ); 9 | } 10 | 11 | get eventStatusDescription() { 12 | return i18n( 13 | `discourse_post_event.models.event.status.${this.args.event.status}.description` 14 | ); 15 | } 16 | 17 | get statusClass() { 18 | return `status ${this.args.event.status}`; 19 | } 20 | 21 | 36 | } 37 | -------------------------------------------------------------------------------- /vendor/holidays/Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | setup: update-defs 4 | bundle install 5 | 6 | generate: 7 | bundle exec rake generate 8 | 9 | test: 10 | bundle exec rake test 11 | 12 | console: 13 | bundle exec rake console 14 | 15 | test-region: 16 | bundle exec rake test_region $(REGION) 17 | 18 | build: clean 19 | bundle exec gem build holidays.gemspec 20 | 21 | push: 22 | bundle exec gem push $(GEM) 23 | 24 | update-defs: definitions/ 25 | git submodule update --init --remote --recursive 26 | 27 | definitions: point-to-defs-master 28 | 29 | point-to-defs-branch: 30 | git submodule add -b $(BRANCH) git@github.com:$(USER)/definitions.git definitions/ 31 | 32 | point-to-defs-master: 33 | git submodule add https://github.com/holidays/definitions definitions/ 34 | 35 | clean-defs: 36 | git rm -f definitions 37 | rm -rf .git/modules/definitions 38 | git config -f .git/config --remove-section submodule.definitions 2> /dev/null 39 | 40 | clean: 41 | rm -rf holidays-*.gem 42 | rm -rf reports 43 | rm -rf coverage 44 | 45 | .PHONY: setup test generate console build push update-defs test-region clean-defs point-to-defs-master point-to-defs-branch clean definitions 46 | -------------------------------------------------------------------------------- /vendor/holidays/LICENSE: -------------------------------------------------------------------------------- 1 | ==== Ruby Holidays Gem License 2 | 3 | Copyright (c) 2007, 2014 Alex Dunae 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/LICENSE: -------------------------------------------------------------------------------- 1 | ==== Ruby Holidays Gem License 2 | 3 | Copyright (c) 2016 Phil Peble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/admin-plugins-calendar.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; 3 | import { i18n } from "discourse-i18n"; 4 | import AdminHolidaysList from "../components/admin-holidays-list"; 5 | import RegionInput from "../components/region-input"; 6 | 7 | export default RouteTemplate( 8 | 33 | ); 34 | -------------------------------------------------------------------------------- /spec/services/discourse_post_event/chat_channel_sync_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | return if !defined?(Chat) 4 | 5 | describe DiscoursePostEvent::ChatChannelSync do 6 | fab!(:user) 7 | fab!(:admin) 8 | fab!(:admin_post) { Fabricate(:post, user: admin) } 9 | 10 | it "is able to create a chat channel and sync members" do 11 | event = Fabricate(:event, chat_enabled: true, post: admin_post) 12 | 13 | expect(event.chat_channel_id).to be_present 14 | expect(event.chat_channel.name).to eq(event.name) 15 | expect(event.chat_channel.user_chat_channel_memberships.count).to eq(1) 16 | expect(event.chat_channel.user_chat_channel_memberships.first.user_id).to eq(admin.id) 17 | 18 | event.create_invitees([user_id: user.id, status: DiscoursePostEvent::Invitee.statuses[:going]]) 19 | event.save! 20 | 21 | expect(event.chat_channel.user_chat_channel_memberships.count).to eq(2) 22 | end 23 | 24 | it "will simply do nothing if user has no permission to create channel" do 25 | post = Fabricate(:post, user: user) 26 | event = Fabricate(:event, chat_enabled: true, post: post) 27 | 28 | expect(event.chat_channel_id).to be_nil 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/factory/date_calculator.rb: -------------------------------------------------------------------------------- 1 | require 'holidays/date_calculator/easter' 2 | require 'holidays/date_calculator/weekend_modifier' 3 | require 'holidays/date_calculator/day_of_month' 4 | require 'holidays/date_calculator/lunar_date' 5 | 6 | module Holidays 7 | module Factory 8 | module DateCalculator 9 | module Easter 10 | module Gregorian 11 | class << self 12 | def easter_calculator 13 | Holidays::DateCalculator::Easter::Gregorian.new 14 | end 15 | end 16 | end 17 | 18 | module Julian 19 | class << self 20 | def easter_calculator 21 | Holidays::DateCalculator::Easter::Julian.new 22 | end 23 | end 24 | end 25 | end 26 | 27 | class << self 28 | def lunar_date 29 | Holidays::DateCalculator::LunarDate.new 30 | end 31 | 32 | def weekend_modifier 33 | Holidays::DateCalculator::WeekendModifier.new 34 | end 35 | 36 | def day_of_month_calculator 37 | Holidays::DateCalculator::DayOfMonth.new 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /config/locales/client.sq.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sq: 8 | js: 9 | discourse_automation: 10 | triggerables: 11 | event_started: 12 | fields: 13 | topic_id: 14 | label: ID e temës 15 | discourse_calendar: 16 | disable_holiday: "Çaktivizo" 17 | enable_holiday: "Aktivizo" 18 | region: 19 | none: "Asnjë" 20 | toolbar_button: 21 | today: "Sot" 22 | month: "Këtë muaj" 23 | week: "Këtë javë" 24 | day: "Dit" 25 | discourse_post_event: 26 | bulk_invite_modal: 27 | success: "Skedari u ngarkua, do njoftoheni me mesazh kur procesi të mbarojë. " 28 | error: "Na vjen keq, skedari duhet të jete i formatit CSV." 29 | builder_modal: 30 | update: "Ruaj" 31 | reminders: 32 | units: 33 | days: "ditë" 34 | url: 35 | label: "URL" 36 | location: 37 | label: "Vendndodhja" 38 | description: 39 | label: "Përshkrimi" 40 | -------------------------------------------------------------------------------- /vendor/holidays/lib/generated_definitions/ecbtarget.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Holidays 3 | # This file is generated by the Ruby Holidays gem. 4 | # 5 | # Definitions loaded: definitions/ecbtarget.yaml 6 | # 7 | # All the definitions are available at https://github.com/holidays/holidays 8 | module ECBTARGET # :nodoc: 9 | def self.defined_regions 10 | [:ecbtarget] 11 | end 12 | 13 | def self.holidays_by_month 14 | { 15 | 0 => [{:function => "easter(year)", :function_arguments => [:year], :function_modifier => -2, :name => "Good Friday", :regions => [:ecbtarget]}, 16 | {:function => "easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Easter Monday", :regions => [:ecbtarget]}], 17 | 1 => [{:mday => 1, :name => "New Year's Day", :regions => [:ecbtarget]}], 18 | 5 => [{:mday => 1, :name => "Labour Day", :regions => [:ecbtarget]}], 19 | 12 => [{:mday => 25, :name => "Christmas Day", :regions => [:ecbtarget]}, 20 | {:mday => 26, :name => "Christmas Holiday", :regions => [:ecbtarget]}] 21 | } 22 | end 23 | 24 | def self.custom_methods 25 | { 26 | 27 | } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /config/locales/server.fa_IR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fa_IR: 8 | reports: 9 | currently_away: 10 | labels: 11 | username: نام‌کاربری 12 | discourse_automation: 13 | triggerables: 14 | event_started: 15 | title: رویداد شروع شد 16 | site_settings: 17 | events_calendar_categories: "نمایش رویدادهای تقویم در بالای دسته‌بندی" 18 | sidebar_show_upcoming_events: "پیوند رویدادهای آینده را در نوار کناری زیر «بیشتر» نمایش دهید." 19 | discourse_calendar: 20 | invite_user_notification: "%{username} شما را دعوت کرده به: %{description}" 21 | holiday_status: 22 | description: "در تعطیلات" 23 | discourse_post_event: 24 | errors: 25 | bulk_invite: 26 | error: "خطایی هنگام آپلود فایل مربوطه رخ داده است. لطفا بعدا امتحان کنید." 27 | models: 28 | event: 29 | invalid_timezone: "منطقه‌زمانی شناسایی نشد." 30 | invalid_allowed_groups: "گروه‌های مجاز نامعتبر." 31 | acting_user_not_allowed_to_invite_these_groups: "کاربر فعلی اجازه دعوت از این گروه‌ها را ندارد." 32 | -------------------------------------------------------------------------------- /vendor/holidays/lib/generated_definitions/tn.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Holidays 3 | # This file is generated by the Ruby Holidays gem. 4 | # 5 | # Definitions loaded: definitions/tn.yaml 6 | # 7 | # All the definitions are available at https://github.com/holidays/holidays 8 | module TN # :nodoc: 9 | def self.defined_regions 10 | [:tn] 11 | end 12 | 13 | def self.holidays_by_month 14 | { 15 | 1 => [{:mday => 1, :name => "Jour de l'an", :regions => [:tn]}, 16 | {:mday => 14, :name => "Fête de la Révolution et de la Jeunesse", :regions => [:tn]}], 17 | 3 => [{:mday => 20, :name => "Fête de l'Indépendance", :regions => [:tn]}], 18 | 4 => [{:mday => 9, :name => "Journée des Martyrs", :regions => [:tn]}], 19 | 5 => [{:mday => 1, :name => "Fête du travail", :regions => [:tn]}], 20 | 7 => [{:mday => 25, :name => "Fête de la République", :regions => [:tn]}], 21 | 8 => [{:mday => 13, :name => "Fête de la Femme et de la Famille", :regions => [:tn]}], 22 | 10 => [{:mday => 15, :name => "Fête de l'Évacuation", :regions => [:tn]}] 23 | } 24 | end 25 | 26 | def self.custom_methods 27 | { 28 | 29 | } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_tn.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/tn.yaml 7 | class TnDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_tn 10 | assert_equal "Jour de l'an", (Holidays.on(Date.civil(2016, 1, 1), [:tn])[0] || {})[:name] 11 | 12 | assert_equal "Fête de la Révolution et de la Jeunesse", (Holidays.on(Date.civil(2016, 1, 14), [:tn])[0] || {})[:name] 13 | 14 | assert_equal "Fête de l'Indépendance", (Holidays.on(Date.civil(2016, 3, 20), [:tn])[0] || {})[:name] 15 | 16 | assert_equal "Journée des Martyrs", (Holidays.on(Date.civil(2016, 4, 9), [:tn])[0] || {})[:name] 17 | 18 | assert_equal "Fête du travail", (Holidays.on(Date.civil(2016, 5, 1), [:tn])[0] || {})[:name] 19 | 20 | assert_equal "Fête de la République", (Holidays.on(Date.civil(2016, 7, 25), [:tn])[0] || {})[:name] 21 | 22 | assert_equal "Fête de la Femme et de la Famille", (Holidays.on(Date.civil(2016, 8, 13), [:tn])[0] || {})[:name] 23 | 24 | assert_equal "Fête de l'Évacuation", (Holidays.on(Date.civil(2016, 10, 15), [:tn])[0] || {})[:name] 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ecbtarget.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ecbtarget.yaml 7 | class EcbtargetDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ecbtarget 10 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2013, 1, 1), [:ecbtarget])[0] || {})[:name] 11 | 12 | assert_equal "Labour Day", (Holidays.on(Date.civil(2013, 5, 1), [:ecbtarget])[0] || {})[:name] 13 | 14 | assert_equal "Good Friday", (Holidays.on(Date.civil(2013, 3, 29), [:ecbtarget])[0] || {})[:name] 15 | 16 | assert_equal "Easter Monday", (Holidays.on(Date.civil(2013, 4, 1), [:ecbtarget])[0] || {})[:name] 17 | 18 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2013, 12, 25), [:ecbtarget])[0] || {})[:name] 19 | 20 | assert_equal "Christmas Holiday", (Holidays.on(Date.civil(2013, 12, 26), [:ecbtarget])[0] || {})[:name] 21 | 22 | assert_equal "Good Friday", (Holidays.on(Date.civil(2013, 3, 29), [:ecbtarget])[0] || {})[:name] 23 | 24 | assert_equal "Easter Monday", (Holidays.on(Date.civil(2013, 4, 1), [:ecbtarget])[0] || {})[:name] 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/javascripts/lib/raw-event-helper-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from "qunit"; 2 | import { replaceRaw } from "discourse/plugins/discourse-calendar/discourse/lib/raw-event-helper"; 3 | 4 | module("Unit | Lib | raw-event-helper", function () { 5 | test("replaceRaw", function (assert) { 6 | const raw = 'Some text \n[event param1="va]lue1"]\n[/event]\n more text'; 7 | const params = { 8 | param1: "newValue1", 9 | param2: "value2", 10 | }; 11 | 12 | assert.strictEqual( 13 | replaceRaw(params, raw), 14 | 'Some text \n[event param1="newValue1" param2="value2"]\n[/event]\n more text', 15 | "updates existing parameters and adds new ones" 16 | ); 17 | 18 | assert.false( 19 | replaceRaw(params, "No event tag here"), 20 | "returns false when no event tag is found" 21 | ); 22 | 23 | assert.strictEqual( 24 | replaceRaw({ foo: 'bar"quoted' }, '[event original="value"]\n[/event]'), 25 | '[event foo="barquoted"]\n[/event]', 26 | "escapes double quotes in parameter values" 27 | ); 28 | 29 | assert.strictEqual( 30 | replaceRaw({}, '[event param1="value1"]\n[/event]'), 31 | "[event ]\n[/event]", 32 | "handles empty params object" 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/toggle-invitees.gjs: -------------------------------------------------------------------------------- 1 | import { fn } from "@ember/helper"; 2 | import { eq } from "truth-helpers"; 3 | import DButton from "discourse/components/d-button"; 4 | import concatClass from "discourse/helpers/concat-class"; 5 | 6 | const ToggleInvitees = ; 36 | 37 | export default ToggleInvitees; 38 | -------------------------------------------------------------------------------- /vendor/holidays/lib/generated_definitions/ma.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Holidays 3 | # This file is generated by the Ruby Holidays gem. 4 | # 5 | # Definitions loaded: definitions/ma.yaml 6 | # 7 | # All the definitions are available at https://github.com/holidays/holidays 8 | module MA # :nodoc: 9 | def self.defined_regions 10 | [:ma] 11 | end 12 | 13 | def self.holidays_by_month 14 | { 15 | 1 => [{:mday => 1, :name => "Ras l' âm", :regions => [:ma]}, 16 | {:mday => 11, :name => "Takdim watikat al-istiqlal", :regions => [:ma]}], 17 | 5 => [{:mday => 1, :name => "Eid Ash-Shughl", :regions => [:ma]}], 18 | 7 => [{:mday => 30, :name => "Eid Al-Ârch", :regions => [:ma]}], 19 | 8 => [{:mday => 14, :name => "Zikra Oued Ed-Dahab", :regions => [:ma]}, 20 | {:mday => 20, :name => "Thawrat al malik wa shâab", :regions => [:ma]}, 21 | {:mday => 21, :name => "Eid Al Milad", :regions => [:ma]}], 22 | 11 => [{:mday => 6, :name => "Eid Al Massira Al Khadra", :regions => [:ma]}, 23 | {:mday => 18, :name => "Eid Al Istiqulal", :regions => [:ma]}] 24 | } 25 | end 26 | 27 | def self.custom_methods 28 | { 29 | 30 | } 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ph.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ph.yaml 7 | class PhDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ph 10 | assert_equal "Good Friday", (Holidays.on(Date.civil(2015, 4, 3), [:ph])[0] || {})[:name] 11 | 12 | assert_equal "The Day of Valor", (Holidays.on(Date.civil(2015, 4, 9), [:ph])[0] || {})[:name] 13 | 14 | assert_equal "Labor Day", (Holidays.on(Date.civil(2015, 5, 1), [:ph])[0] || {})[:name] 15 | 16 | assert_equal "Independence Day", (Holidays.on(Date.civil(2015, 6, 12), [:ph])[0] || {})[:name] 17 | 18 | assert_equal "Ninoy Aquino Day", (Holidays.on(Date.civil(2015, 8, 21), [:ph])[0] || {})[:name] 19 | 20 | assert_equal "National Heroes Day", (Holidays.on(Date.civil(2015, 8, 31), [:ph])[0] || {})[:name] 21 | 22 | assert_equal "Bonifacio Day", (Holidays.on(Date.civil(2015, 11, 30), [:ph])[0] || {})[:name] 23 | 24 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2015, 12, 25), [:ph])[0] || {})[:name] 25 | 26 | assert_equal "Rizal Day", (Holidays.on(Date.civil(2015, 12, 30), [:ph])[0] || {})[:name] 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /vendor/holidays/test/holidays/definition/repository/test_custom_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../../../test_helper' 2 | 3 | require 'holidays/definition/repository/custom_methods' 4 | 5 | class CustomMethodsRepoTests < Test::Unit::TestCase 6 | def setup 7 | @subject = Holidays::Definition::Repository::CustomMethods.new 8 | end 9 | 10 | def test_add_raises_error_if_input_is_nil 11 | assert_raise ArgumentError do 12 | @subject.add(nil) 13 | end 14 | end 15 | 16 | def test_find_returns_nil_if_method_id_does_not_exist 17 | assert_nil @subject.find("some-method-id") 18 | end 19 | 20 | def test_add_successfully_adds_new_custom_methods 21 | new_custom_methods = { 22 | "some-method-id" => Proc.new { |year| 23 | Date.civil(year, 1, 1) 24 | } 25 | } 26 | 27 | @subject.add(new_custom_methods) 28 | 29 | target_method = @subject.find("some-method-id") 30 | 31 | assert_equal new_custom_methods["some-method-id"], target_method 32 | end 33 | 34 | def test_find_raises_error_if_target_method_id_is_nil_or_empty 35 | assert_raise ArgumentError do 36 | @subject.find(nil) 37 | end 38 | 39 | assert_raise ArgumentError do 40 | @subject.find("") 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /vendor/holidays/lib/generated_definitions/nerc.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module Holidays 3 | # This file is generated by the Ruby Holidays gem. 4 | # 5 | # Definitions loaded: definitions/nerc.yaml 6 | # 7 | # All the definitions are available at https://github.com/holidays/holidays 8 | module NERC # :nodoc: 9 | def self.defined_regions 10 | [:nerc] 11 | end 12 | 13 | def self.holidays_by_month 14 | { 15 | 1 => [{:mday => 1, :observed => "to_monday_if_sunday(date)", :observed_arguments => [:date], :name => "New Year's Day", :regions => [:nerc]}], 16 | 5 => [{:wday => 1, :week => -1, :name => "Memorial Day", :regions => [:nerc]}], 17 | 7 => [{:mday => 4, :observed => "to_monday_if_sunday(date)", :observed_arguments => [:date], :name => "Independence Day", :regions => [:nerc]}], 18 | 9 => [{:wday => 1, :week => 1, :name => "Labor Day", :regions => [:nerc]}], 19 | 11 => [{:wday => 4, :week => 4, :name => "Thanksgiving", :regions => [:nerc]}], 20 | 12 => [{:mday => 25, :observed => "to_monday_if_sunday(date)", :observed_arguments => [:date], :name => "Christmas Day", :regions => [:nerc]}] 21 | } 22 | end 23 | 24 | def self.custom_methods 25 | { 26 | 27 | } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ng.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ng.yaml 7 | class NgDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ng 10 | assert_equal "Good Friday", (Holidays.on(Date.civil(2008, 3, 21), [:ng])[0] || {})[:name] 11 | 12 | assert_equal "Easter Monday", (Holidays.on(Date.civil(2008, 3, 24), [:ng])[0] || {})[:name] 13 | 14 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2008, 1, 1), [:ng])[0] || {})[:name] 15 | 16 | assert_equal "Workers' Day", (Holidays.on(Date.civil(2008, 5, 1), [:ng])[0] || {})[:name] 17 | 18 | assert_equal "Children's Day", (Holidays.on(Date.civil(2008, 5, 27), [:ng], [:informal])[0] || {})[:name] 19 | 20 | assert_equal "Democracy Day", (Holidays.on(Date.civil(2019, 6, 12), [:ng])[0] || {})[:name] 21 | 22 | assert_equal "Independence Day", (Holidays.on(Date.civil(2008, 10, 1), [:ng])[0] || {})[:name] 23 | 24 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2008, 12, 25), [:ng])[0] || {})[:name] 25 | 26 | assert_equal "Boxing Day", (Holidays.on(Date.civil(2008, 12, 26), [:ng])[0] || {})[:name] 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /vendor/holidays/holidays.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | require 'holidays/version' 6 | 7 | Gem::Specification.new do |gem| 8 | gem.name = 'holidays' 9 | gem.version = Holidays::VERSION 10 | gem.authors = ['Alex Dunae', 'Phil Peble'] 11 | gem.email = ['holidaysgem@gmail.com'] 12 | gem.homepage = 'https://github.com/holidays/holidays' 13 | gem.description = %q(A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!) 14 | gem.summary = %q(A collection of Ruby methods to deal with statutory and other holidays.) 15 | gem.files = `git ls-files`.split("\n") - ['.gitignore', '.travis.yml'] 16 | gem.test_files = gem.files.grep(/^test/) 17 | gem.require_paths = ['lib'] 18 | gem.licenses = ['MIT'] 19 | gem.required_ruby_version = '>= 2.4' 20 | gem.add_development_dependency 'bundler', '~> 2' 21 | gem.add_development_dependency 'rake', '~> 12' 22 | gem.add_development_dependency 'simplecov', '~> 0.16' 23 | gem.add_development_dependency 'test-unit', '~> 3' 24 | gem.add_development_dependency 'mocha', '~> 1' 25 | gem.add_development_dependency 'pry', '~> 0.12' 26 | end 27 | -------------------------------------------------------------------------------- /vendor/holidays/test/holidays/definition/context/test_load.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../../../test_helper' 2 | 3 | require 'holidays/definition/context/load' 4 | 5 | class LoadTests < Test::Unit::TestCase 6 | def setup 7 | @definition_merger = mock() 8 | full_definitions_path = File.expand_path(File.dirname(__FILE__)) + '/../../../data' 9 | 10 | @subject = Holidays::Definition::Context::Load.new( 11 | @definition_merger, 12 | full_definitions_path, 13 | ) 14 | end 15 | 16 | def test_region_is_found_and_loaded_and_merged 17 | @definition_merger.expects(:call).with(:test_region, {}, {}) 18 | @subject.call(:test_region) 19 | end 20 | 21 | def test_region_file_not_found 22 | assert_raises Holidays::UnknownRegionError do 23 | @subject.call(:unknown) 24 | end 25 | end 26 | 27 | def test_region_can_be_loaded_but_file_is_invalid 28 | assert_raises Holidays::UnknownRegionError do 29 | @subject.call(:test_invalid_region) 30 | end 31 | end 32 | 33 | def test_returns_list_of_loaded_regions 34 | @definition_merger.expects(:call).with(:test_region, {}, {}) 35 | assert_equal([:test_region, :test_region2], @subject.call(:test_region), "Should cache subregions under the parent region's name") 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/discourse_post_event/rrule_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rrule" 4 | 5 | class RRuleGenerator 6 | def self.generate( 7 | starts_at:, 8 | timezone: "UTC", 9 | max_years: nil, 10 | recurrence: "every_week", 11 | recurrence_until: nil 12 | ) 13 | rrule = generate_hash(RRuleConfigurator.rule(recurrence_until:, recurrence:, starts_at:)) 14 | rrule = set_mandatory_options(rrule, starts_at) 15 | 16 | ::RRule::Rule 17 | .new(stringify(rrule), dtstart: starts_at, tzid: timezone) 18 | .between(Time.current, Time.current + 14.months) 19 | .first(RRuleConfigurator.how_many_recurring_events(recurrence:, max_years:)) 20 | end 21 | 22 | private 23 | 24 | def self.stringify(rrule) 25 | rrule.map { |k, v| "#{k}=#{v}" }.join(";") 26 | end 27 | 28 | def self.generate_hash(rrule) 29 | rrule 30 | .split(";") 31 | .each_with_object({}) do |rr, h| 32 | key, value = rr.split("=") 33 | h[key] = value 34 | end 35 | end 36 | 37 | def self.set_mandatory_options(rrule, time) 38 | rrule["BYHOUR"] = time.strftime("%H") 39 | rrule["BYMINUTE"] = time.strftime("%M") 40 | rrule["INTERVAL"] ||= 1 41 | rrule["WKST"] = "MO" # considers Monday as the first day of the week 42 | rrule 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_cr.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/cr.yaml 7 | class CrDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_cr 10 | assert_equal "Año Nuevo", (Holidays.on(Date.civil(2015, 1, 1), [:cr])[0] || {})[:name] 11 | 12 | assert_equal "Día de Juan Santamaría", (Holidays.on(Date.civil(2015, 4, 11), [:cr])[0] || {})[:name] 13 | 14 | assert_equal "Día del Trabajador", (Holidays.on(Date.civil(2015, 5, 1), [:cr])[0] || {})[:name] 15 | 16 | assert_equal "Día de la Anexión de Guanacaste", (Holidays.on(Date.civil(2015, 7, 25), [:cr])[0] || {})[:name] 17 | 18 | assert_equal "Día de la Virgen de los Angeles", (Holidays.on(Date.civil(2015, 8, 2), [:cr])[0] || {})[:name] 19 | 20 | assert_equal "Día de la Madre", (Holidays.on(Date.civil(2015, 8, 15), [:cr])[0] || {})[:name] 21 | 22 | assert_equal "Día de la Independencia", (Holidays.on(Date.civil(2015, 9, 15), [:cr])[0] || {})[:name] 23 | 24 | assert_equal "Día de las Culturas", (Holidays.on(Date.civil(2015, 10, 12), [:cr])[0] || {})[:name] 25 | 26 | assert_equal "Navidad", (Holidays.on(Date.civil(2016, 12, 25), [:cr])[0] || {})[:name] 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/discourse_post_event/export_csv_controller_extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscoursePostEvent 4 | module ExportCsvControllerExtension 5 | def export_entity 6 | if post_event_export? && ensure_can_export_post_event 7 | Jobs.enqueue( 8 | :export_csv_file, 9 | entity: export_params[:entity], 10 | user_id: current_user.id, 11 | args: export_params[:args], 12 | ) 13 | StaffActionLogger.new(current_user).log_entity_export(export_params[:entity]) 14 | render json: success_json 15 | else 16 | super 17 | end 18 | end 19 | 20 | private 21 | 22 | def export_params 23 | if post_event_export? 24 | @_export_params ||= 25 | begin 26 | params.require(:entity) 27 | params.permit(:entity, args: %i[id]).to_h 28 | end 29 | else 30 | super 31 | end 32 | end 33 | 34 | def post_event_export? 35 | params[:entity] === "post_event" 36 | end 37 | 38 | def ensure_can_export_post_event 39 | return if !SiteSetting.discourse_post_event_enabled 40 | 41 | post_event = DiscoursePostEvent::Event.find(export_params[:args][:id]) 42 | post_event && guardian.can_act_on_discourse_post_event?(post_event) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /vendor/holidays/definitions/vi.yaml: -------------------------------------------------------------------------------- 1 | # Vietnam holiday definitions for the Ruby Holiday gem. 2 | # 3 | # Updated: 2014-07-17. 4 | # Source: http://en.wikipedia.org/wiki/Public_holidays_in_Vietnam 5 | --- 6 | months: 7 | 1: 8 | - name: Tết dương lịch 9 | regions: [vi] 10 | mday: 1 11 | 3: 12 | - name: Giỗ tổ Hùng Vương 13 | regions: [vi] 14 | function: lunar_to_solar(year, month, day, region) 15 | mday: 10 16 | 4: 17 | - name: Ngày Giải phóng miền Nam, thống nhất đất nước 18 | regions: [vi] 19 | mday: 30 20 | 5: 21 | - name: Ngày Quốc tế Lao động 22 | regions: [vi] 23 | mday: 1 24 | 9: 25 | - name: Quốc khánh 26 | regions: [vi] 27 | mday: 2 28 | 29 | tests: 30 | - given: 31 | date: '2014-01-01' 32 | regions: ["vi"] 33 | expect: 34 | name: "Tết dương lịch" 35 | - given: 36 | date: '2014-04-30' 37 | regions: ["vi"] 38 | expect: 39 | name: "Ngày Giải phóng miền Nam, thống nhất đất nước" 40 | - given: 41 | date: '2014-05-01' 42 | regions: ["vi"] 43 | expect: 44 | name: "Ngày Quốc tế Lao động" 45 | - given: 46 | date: '2014-09-02' 47 | regions: ["vi"] 48 | expect: 49 | name: "Quốc khánh" 50 | - given: 51 | date: ['2017-04-06', '2018-03-27'] 52 | regions: ["vi"] 53 | expect: 54 | name: "Giỗ tổ Hùng Vương" 55 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ke.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ke.yaml 7 | class KeDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ke 10 | assert_equal "Good Friday", (Holidays.on(Date.civil(2008, 3, 21), [:ke])[0] || {})[:name] 11 | 12 | assert_equal "Easter Monday", (Holidays.on(Date.civil(2008, 3, 24), [:ke])[0] || {})[:name] 13 | 14 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2008, 1, 1), [:ke])[0] || {})[:name] 15 | 16 | assert_equal "Labour Day", (Holidays.on(Date.civil(2008, 5, 1), [:ke])[0] || {})[:name] 17 | 18 | assert_equal "Madaraka Day", (Holidays.on(Date.civil(2019, 6, 1), [:ke])[0] || {})[:name] 19 | 20 | assert_equal "Huduma Day", (Holidays.on(Date.civil(2018, 10, 10), [:ke])[0] || {})[:name] 21 | 22 | assert_equal "Mashujaa Day", (Holidays.on(Date.civil(2018, 10, 20), [:ke])[0] || {})[:name] 23 | 24 | assert_equal "Jamhuri Day", (Holidays.on(Date.civil(2019, 12, 12), [:ke])[0] || {})[:name] 25 | 26 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2008, 12, 25), [:ke])[0] || {})[:name] 27 | 28 | assert_equal "Utamaduni Day", (Holidays.on(Date.civil(2018, 12, 26), [:ke])[0] || {})[:name] 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/repository/regions.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Repository 4 | class Regions 5 | def initialize(all_generated_regions, parent_region_lookup) 6 | @loaded_regions = [] 7 | @all_generated_regions = all_generated_regions 8 | @parent_region_lookup = parent_region_lookup 9 | end 10 | 11 | def all_generated 12 | @all_generated_regions 13 | end 14 | 15 | def parent_region_lookup(r) 16 | @parent_region_lookup[r] 17 | end 18 | 19 | def all_loaded 20 | @loaded_regions 21 | end 22 | 23 | def loaded?(region) 24 | raise ArgumentError unless region.is_a?(Symbol) 25 | @loaded_regions.include?(region) 26 | end 27 | 28 | def add(regions) 29 | regions = [regions] unless regions.is_a?(Array) 30 | 31 | regions.each do |region| 32 | raise ArgumentError unless region.is_a?(Symbol) 33 | end 34 | 35 | @loaded_regions = @loaded_regions | regions 36 | @loaded_regions.uniq! 37 | end 38 | 39 | def search(prefix) 40 | raise ArgumentError unless prefix.is_a?(Symbol) 41 | @loaded_regions.select { |region| region.to_s =~ Regexp.new("^#{prefix}") } 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_nerc.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/nerc.yaml 7 | class NercDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_nerc 10 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2013, 1, 1), [:nerc], [:observed])[0] || {})[:name] 11 | 12 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2017, 1, 2), [:nerc], [:observed])[0] || {})[:name] 13 | 14 | assert_equal "Memorial Day", (Holidays.on(Date.civil(2013, 5, 27), [:nerc], [:observed])[0] || {})[:name] 15 | 16 | assert_equal "Independence Day", (Holidays.on(Date.civil(2013, 7, 4), [:nerc], [:observed])[0] || {})[:name] 17 | 18 | assert_equal "Independence Day", (Holidays.on(Date.civil(2010, 7, 5), [:nerc], [:observed])[0] || {})[:name] 19 | 20 | assert_equal "Labor Day", (Holidays.on(Date.civil(2013, 9, 2), [:nerc], [:observed])[0] || {})[:name] 21 | 22 | assert_equal "Thanksgiving", (Holidays.on(Date.civil(2013, 11, 28), [:nerc], [:observed])[0] || {})[:name] 23 | 24 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2013, 12, 25), [:nerc], [:observed])[0] || {})[:name] 25 | 26 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2011, 12, 26), [:nerc], [:observed])[0] || {})[:name] 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ups.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ups.yaml 7 | class UpsDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ups 10 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2008, 1, 1), [:ups])[0] || {})[:name] 11 | 12 | assert_equal "Memorial Day", (Holidays.on(Date.civil(2008, 5, 26), [:ups])[0] || {})[:name] 13 | 14 | assert_equal "Independence Day", (Holidays.on(Date.civil(2008, 7, 4), [:ups])[0] || {})[:name] 15 | 16 | assert_equal "Labor Day", (Holidays.on(Date.civil(2008, 9, 1), [:ups])[0] || {})[:name] 17 | 18 | assert_equal "Thanksgiving", (Holidays.on(Date.civil(2008, 11, 27), [:ups])[0] || {})[:name] 19 | 20 | assert_equal "Day After Thanksgiving", (Holidays.on(Date.civil(2008, 11, 28), [:ups])[0] || {})[:name] 21 | 22 | assert_equal "Thanksgiving", (Holidays.on(Date.civil(2013, 11, 28), [:ups])[0] || {})[:name] 23 | 24 | assert_equal "Day After Thanksgiving", (Holidays.on(Date.civil(2013, 11, 29), [:ups])[0] || {})[:name] 25 | 26 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2008, 12, 25), [:ups])[0] || {})[:name] 27 | 28 | assert_equal "New Year's Eve", (Holidays.on(Date.civil(2008, 12, 31), [:ups])[0] || {})[:name] 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/region-input.js: -------------------------------------------------------------------------------- 1 | import { computed } from "@ember/object"; 2 | import { classNames } from "@ember-decorators/component"; 3 | import { i18n } from "discourse-i18n"; 4 | import ComboBoxComponent from "select-kit/components/combo-box"; 5 | import { 6 | pluginApiIdentifiers, 7 | selectKitOptions, 8 | } from "select-kit/components/select-kit"; 9 | import { HOLIDAY_REGIONS } from "../lib/regions"; 10 | 11 | @selectKitOptions({ 12 | filterable: true, 13 | allowAny: false, 14 | }) 15 | @pluginApiIdentifiers("timezone-input") 16 | @classNames("timezone-input", "region-input") 17 | export default class RegionInput extends ComboBoxComponent { 18 | allowNoneRegion = false; 19 | 20 | @computed 21 | get content() { 22 | const localeNames = {}; 23 | let regions = []; 24 | 25 | JSON.parse(this.siteSettings.available_locales).forEach((locale) => { 26 | localeNames[locale.value] = locale.name; 27 | }); 28 | 29 | if (this.allowNoneRegion === true) { 30 | regions.push({ 31 | name: i18n("discourse_calendar.region.none"), 32 | id: null, 33 | }); 34 | } 35 | 36 | regions = regions.concat( 37 | HOLIDAY_REGIONS.map((region) => ({ 38 | name: i18n(`discourse_calendar.region.names.${region}`), 39 | id: region, 40 | })).sort((a, b) => a.name.localeCompare(b.name)) 41 | ); 42 | return regions; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_fedex.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/fedex.yaml 7 | class FedexDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_fedex 10 | assert_equal "New Year's Day", (Holidays.on(Date.civil(2015, 1, 1), [:ups])[0] || {})[:name] 11 | 12 | assert_equal "Memorial Day", (Holidays.on(Date.civil(2015, 5, 25), [:ups])[0] || {})[:name] 13 | 14 | assert_equal "Independence Day", (Holidays.on(Date.civil(2015, 7, 4), [:ups])[0] || {})[:name] 15 | 16 | assert_equal "Labor Day", (Holidays.on(Date.civil(2015, 9, 7), [:ups])[0] || {})[:name] 17 | 18 | assert_equal "Thanksgiving", (Holidays.on(Date.civil(2015, 11, 26), [:ups])[0] || {})[:name] 19 | 20 | assert_equal "Day After Thanksgiving", (Holidays.on(Date.civil(2015, 11, 27), [:ups])[0] || {})[:name] 21 | 22 | assert_equal "Thanksgiving", (Holidays.on(Date.civil(2013, 11, 28), [:ups])[0] || {})[:name] 23 | 24 | assert_equal "Day After Thanksgiving", (Holidays.on(Date.civil(2013, 11, 29), [:ups])[0] || {})[:name] 25 | 26 | assert_equal "Christmas Day", (Holidays.on(Date.civil(2015, 12, 25), [:ups])[0] || {})[:name] 27 | 28 | assert_equal "New Year's Eve", (Holidays.on(Date.civil(2015, 12, 31), [:ups])[0] || {})[:name] 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /vendor/holidays/test/integration/test_multiple_regions_with_conflict.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 2 | 3 | # See https://github.com/holidays/holidays/issues/344 for more info on why 4 | # these tests exist. 5 | class MultipleRegionsWithConflictsTests < Test::Unit::TestCase 6 | 7 | def test_corpus_christi_returns_correctly_for_co_even_if_br_is_loaded_first 8 | result = Holidays.on(Date.new(2014, 6, 19), :br) 9 | assert_equal 1, result.count 10 | assert_equal 'Corpus Christi', result.first[:name] 11 | 12 | result = Holidays.on(Date.new(2014, 6, 23), :co) 13 | assert_equal 1, result.count 14 | assert_equal 'Corpus Christi', result.first[:name] 15 | end 16 | 17 | def test_custom_loaded_region_returns_correct_value_with_function_modifier_conflict_even_if_conflict_definition_is_loaded_first 18 | Holidays.load_custom('test/data/test_multiple_regions_with_conflicts_region_1.yaml') 19 | result = Holidays.on(Date.new(2019, 6, 20), :multiple_with_conflict_1) 20 | assert_equal 1, result.count 21 | assert_equal 'With Function Modifier', result.first[:name] 22 | 23 | Holidays.load_custom('test/data/test_multiple_regions_with_conflicts_region_2.yaml') 24 | result = Holidays.on(Date.new(2019, 6, 24), :multiple_with_conflict_2) 25 | assert_equal 1, result.count 26 | assert_equal 'With Function Modifier', result.first[:name] 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_ma.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/ma.yaml 7 | class MaDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_ma 10 | assert_equal "Ras l' âm", (Holidays.on(Date.civil(2007, 1, 1), [:ma], [:informal])[0] || {})[:name] 11 | 12 | assert_equal "Takdim watikat al-istiqlal", (Holidays.on(Date.civil(2007, 1, 11), [:ma], [:informal])[0] || {})[:name] 13 | 14 | assert_equal "Eid Ash-Shughl", (Holidays.on(Date.civil(2007, 5, 1), [:ma], [:informal])[0] || {})[:name] 15 | 16 | assert_equal "Eid Al-Ârch", (Holidays.on(Date.civil(2007, 7, 30), [:ma], [:informal])[0] || {})[:name] 17 | 18 | assert_equal "Zikra Oued Ed-Dahab", (Holidays.on(Date.civil(2007, 8, 14), [:ma], [:informal])[0] || {})[:name] 19 | 20 | assert_equal "Thawrat al malik wa shâab", (Holidays.on(Date.civil(2007, 8, 20), [:ma], [:informal])[0] || {})[:name] 21 | 22 | assert_equal "Eid Al Milad", (Holidays.on(Date.civil(2007, 8, 21), [:ma], [:informal])[0] || {})[:name] 23 | 24 | assert_equal "Eid Al Massira Al Khadra", (Holidays.on(Date.civil(2007, 11, 6), [:ma], [:informal])[0] || {})[:name] 25 | 26 | assert_equal "Eid Al Istiqulal", (Holidays.on(Date.civil(2007, 11, 18), [:ma], [:informal])[0] || {})[:name] 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_multiple_regions_with_conflicts_region_1.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 0: 3 | - name: With Function Modifier 4 | regions: [multiple_with_conflict_1] 5 | function: easter(year) 6 | function_modifier: 60 7 | - name: With Function Only Different Function Name 8 | regions: [multiple_with_conflict_1] 9 | function: conflict_custom_method_1(year) 10 | - name: With Function Only Same Function Name 11 | regions: [multiple_with_conflict_1] 12 | function: conflict_custom_method_identical_name_between_regions(year) 13 | - name: With Function Only Same Function Name - Region 1 14 | regions: [multiple_with_conflict_1] 15 | function: conflict_custom_method_identical_name_between_regions_but_different_holiday_names(year) 16 | 1: 17 | - name: New Year's Day 18 | regions: [multiple_with_conflict_1] 19 | mday: 1 20 | observed: to_monday_if_weekend(date) 21 | 10: 22 | - name: Testing Conflict Month 10 23 | regions: [multiple_with_conflict_1] 24 | mday: 5 25 | 26 | methods: 27 | conflict_custom_method_1: 28 | arguments: year 29 | ruby: | 30 | Date.civil(year, 8, 1) 31 | conflict_custom_method_identical_name_between_regions: 32 | arguments: year 33 | ruby: | 34 | Date.civil(year, 9, 1) 35 | conflict_custom_method_identical_name_between_regions_but_different_holiday_names: 36 | arguments: year 37 | ruby: | 38 | Date.civil(year, 9, 15) 39 | -------------------------------------------------------------------------------- /vendor/holidays/test/defs/test_defs_at.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__)) + '/../test_helper' 3 | 4 | # This file is generated by the Ruby Holiday gem. 5 | # 6 | # Definitions loaded: definitions/at.yaml 7 | class AtDefinitionTests < Test::Unit::TestCase # :nodoc: 8 | 9 | def test_at 10 | assert_equal "Neujahrstag", (Holidays.on(Date.civil(2009, 1, 1), [:at], [:informal])[0] || {})[:name] 11 | 12 | assert_equal "Ostermontag", (Holidays.on(Date.civil(2009, 4, 13), [:at])[0] || {})[:name] 13 | 14 | assert_equal "Christi Himmelfahrt", (Holidays.on(Date.civil(2009, 5, 21), [:at])[0] || {})[:name] 15 | 16 | assert_equal "Pfingstmontag", (Holidays.on(Date.civil(2009, 6, 1), [:at])[0] || {})[:name] 17 | 18 | assert_equal "Nationalfeiertag", (Holidays.on(Date.civil(2009, 10, 26), [:at], [:informal])[0] || {})[:name] 19 | 20 | assert_equal "Mariä Empfängnis", (Holidays.on(Date.civil(2009, 12, 8), [:at], [:informal])[0] || {})[:name] 21 | 22 | assert_equal "1. Weihnachtstag", (Holidays.on(Date.civil(2009, 12, 25), [:at], [:informal])[0] || {})[:name] 23 | 24 | assert_equal "2. Weihnachtstag", (Holidays.on(Date.civil(2009, 12, 26), [:at], [:informal])[0] || {})[:name] 25 | 26 | assert_nil (Holidays.on(Date.civil(2010, 5, 8), [:at])[0] || {})[:name] 27 | 28 | assert_equal "Fronleichnam", (Holidays.on(Date.civil(2017, 6, 15), [:at])[0] || {})[:name] 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /vendor/holidays/test/data/test_multiple_regions_with_conflicts_region_2.yaml: -------------------------------------------------------------------------------- 1 | months: 2 | 0: 3 | - name: With Function Modifier 4 | regions: [multiple_with_conflict_2] 5 | function: easter(year) 6 | function_modifier: 64 7 | - name: With Function Only Different Function Name 8 | regions: [multiple_with_conflict_2] 9 | function: conflict_custom_method_2(year) 10 | - name: With Function Only Same Function Name 11 | regions: [multiple_with_conflict_2] 12 | function: conflict_custom_method_identical_name_between_regions(year) 13 | - name: With Function Only Same Function Name - Region 2 14 | regions: [multiple_with_conflict_2] 15 | function: conflict_custom_method_identical_name_between_regions_but_different_holiday_names(year) 16 | 1: 17 | - name: New Year's Day 18 | regions: [multiple_with_conflict_2] 19 | mday: 1 20 | observed: to_tuesday_if_sunday_or_monday_if_saturday(date) 21 | 10: 22 | - name: Testing Conflict Month 10 23 | regions: [multiple_with_conflict_2] 24 | mday: 7 25 | 26 | methods: 27 | conflict_custom_method_2: 28 | arguments: year 29 | ruby: | 30 | Date.civil(year, 12, 1) 31 | conflict_custom_method_identical_name_between_regions: 32 | arguments: year 33 | ruby: | 34 | Date.civil(year, 11, 1) 35 | conflict_custom_method_identical_name_between_regions_but_different_holiday_names: 36 | arguments: year 37 | ruby: | 38 | Date.civil(year, 11, 15) 39 | -------------------------------------------------------------------------------- /spec/system/page_objects/discourse_calendar/bulk_invite_modal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PageObjects 4 | module Pages 5 | module DiscourseCalendar 6 | class BulkInviteModal < PageObjects::Pages::Base 7 | def set_invitee_at_row(name, status, row_number) 8 | dropdown = 9 | PageObjects::Components::SelectKit.new( 10 | ".bulk-invite-row:nth-of-type(#{row_number}) .email-group-user-chooser", 11 | ) 12 | dropdown.expand 13 | dropdown.search(name) 14 | dropdown.select_row_by_value(name) 15 | 16 | dropdown = 17 | PageObjects::Components::SelectKit.new( 18 | ".bulk-invite-row:nth-of-type(#{row_number}) .bulk-invite-attendance", 19 | ) 20 | dropdown.expand 21 | dropdown.select_row_by_value(status) 22 | 23 | self 24 | end 25 | 26 | def add_invitee 27 | find(".add-bulk-invite").click 28 | self 29 | end 30 | 31 | def remove_invitee_row(row_number) 32 | find(".bulk-invite-row:nth-of-type(#{row_number}) .remove-bulk-invite").click 33 | self 34 | end 35 | 36 | def send_invites 37 | find(".send-bulk-invites").click 38 | self 39 | end 40 | 41 | def closed? 42 | has_no_css?(".post-event-bulk-invite") 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/tasks/javascript.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "open-uri" 4 | 5 | task "javascript:update_constants" => :environment do 6 | timezone_definitions = 7 | "https://raw.githubusercontent.com/moment/moment-timezone/develop/data/meta/latest.json" 8 | 9 | unused_regions = %w[ 10 | ecbtarget 11 | federalreserve 12 | federalreservebanks 13 | fedex 14 | nerc 15 | unitednations 16 | ups 17 | nyse 18 | ] 19 | 20 | holidays_country_overrides = { "gr" => "el" } 21 | 22 | require "holidays" if !defined?(Holidays) 23 | 24 | holiday_regions = Holidays.available_regions.map(&:to_s) - unused_regions 25 | 26 | time_zone_to_region = {} 27 | data = JSON.parse(URI.parse(timezone_definitions).open.read) 28 | data["zones"].sort.each do |timezone, timezone_data| 29 | country_code = timezone_data["countries"].first.downcase 30 | 31 | if holidays_country_overrides.include?(country_code) 32 | country_code = holidays_country_overrides[country_code] 33 | end 34 | 35 | next if !holiday_regions.include?(country_code) 36 | time_zone_to_region[timezone] = country_code 37 | end 38 | 39 | write_template( 40 | "../../../plugins/discourse-calendar/assets/javascripts/discourse/lib/regions.js", 41 | "update_constants", 42 | <<~JS, 43 | export const HOLIDAY_REGIONS = #{holiday_regions.to_json}; 44 | 45 | export const TIME_ZONE_TO_REGION = #{time_zone_to_region.to_json}; 46 | JS 47 | ) 48 | end 49 | -------------------------------------------------------------------------------- /vendor/holidays/lib/holidays/definition/repository/cache.rb: -------------------------------------------------------------------------------- 1 | module Holidays 2 | module Definition 3 | module Repository 4 | class Cache 5 | def initialize 6 | reset! 7 | end 8 | 9 | def cache_between(start_date, end_date, cache_data, options) 10 | raise ArgumentError unless cache_data 11 | raise ArgumentError unless start_date && end_date 12 | 13 | @cache_range[options] = start_date..end_date 14 | @cache[options] = cache_data.group_by { |holiday| holiday[:date] } 15 | end 16 | 17 | def find(start_date, end_date, options) 18 | return nil unless in_cache_range?(start_date, end_date, options) 19 | 20 | if start_date == end_date 21 | @cache[options].fetch(start_date, []) 22 | else 23 | @cache[options].select do |date, holidays| 24 | date >= start_date && date <= end_date 25 | end.flat_map { |date, holidays| holidays } 26 | end 27 | end 28 | 29 | def reset! 30 | @cache = {} 31 | @cache_range = {} 32 | end 33 | 34 | private 35 | 36 | def in_cache_range?(start_date, end_date, options) 37 | range = @cache_range[options] 38 | if range 39 | range.begin <= start_date && range.end >= end_date 40 | else 41 | false 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/discourse-post-event-invitees.js: -------------------------------------------------------------------------------- 1 | import { tracked } from "@glimmer/tracking"; 2 | import { TrackedArray } from "@ember-compat/tracked-built-ins"; 3 | import User from "discourse/models/user"; 4 | import DiscoursePostEventInvitee from "./discourse-post-event-invitee"; 5 | 6 | export default class DiscoursePostEventInvitees { 7 | static create(args = {}) { 8 | return new DiscoursePostEventInvitees(args); 9 | } 10 | 11 | @tracked _invitees; 12 | @tracked _suggestedUsers; 13 | 14 | constructor(args = {}) { 15 | this.invitees = args.invitees || []; 16 | this.suggestedUsers = args.meta?.suggested_users || []; 17 | } 18 | 19 | get invitees() { 20 | return this._invitees; 21 | } 22 | 23 | set invitees(invitees = []) { 24 | this._invitees = new TrackedArray( 25 | invitees.map((i) => DiscoursePostEventInvitee.create(i)) 26 | ); 27 | } 28 | 29 | get suggestedUsers() { 30 | return this._suggestedUsers; 31 | } 32 | 33 | set suggestedUsers(suggestedUsers = []) { 34 | this._suggestedUsers = new TrackedArray( 35 | suggestedUsers.map((su) => User.create(su)) 36 | ); 37 | } 38 | 39 | add(invitee) { 40 | this.invitees.push(invitee); 41 | 42 | this.suggestedUsers = this.suggestedUsers.filter( 43 | (su) => su.id !== invitee.user.id 44 | ); 45 | } 46 | 47 | remove(invitee) { 48 | this.invitees = this.invitees.filter((i) => i.user.id !== invitee.user.id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/holidays/test/holidays/finder/rules/test_in_region.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__)) + '/../../../test_helper' 2 | 3 | require 'holidays/finder/rules/in_region' 4 | 5 | class FinderRulesInRegionTests < Test::Unit::TestCase 6 | def setup 7 | @available = [:test] 8 | @subject = Holidays::Finder::Rules::InRegion 9 | end 10 | 11 | def test_returns_true_if_any_specified 12 | assert_equal(true, @subject.call([:any], @available)) 13 | end 14 | 15 | def test_returns_true_if_exact_match_found 16 | assert_equal(true, @subject.call([:test], @available)) 17 | end 18 | 19 | def test_returns_true_if_subregion_matches_parent 20 | assert_equal(true, @subject.call([:test_sub], @available)) 21 | end 22 | 23 | def test_returns_true_if_subregion_matches_grandparent 24 | assert_equal(true, @subject.call([:test_sub_sub], @available)) 25 | end 26 | 27 | def test_returns_true_if_subregion_is_in_available 28 | assert_equal(true, @subject.call([:test_sub], [:test, :test_sub])) 29 | end 30 | 31 | def test_returns_false_if_match_not_found 32 | assert_equal(false, @subject.call([:other], @available)) 33 | end 34 | 35 | def test_returns_false_if_match_not_found_for_subregion 36 | assert_equal(false, @subject.call([:other_sub], @available)) 37 | end 38 | 39 | def test_returns_true_if_request_includes_nonmatching_but_also_any 40 | assert_equal(true, @subject.call([:other_sub, :other, :any], @available)) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /config/locales/client.be.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | be: 8 | js: 9 | discourse_automation: 10 | triggerables: 11 | event_started: 12 | fields: 13 | topic_id: 14 | label: Номер тэмы 15 | discourse_calendar: 16 | disable_holiday: "Адключыць" 17 | enable_holiday: "Уключыць" 18 | toolbar_button: 19 | today: "сёння" 20 | month: "месяц" 21 | week: "тыдзень" 22 | day: "дзень" 23 | discourse_post_event: 24 | upcoming_events: 25 | status: "Статус" 26 | models: 27 | event: 28 | closed: "Закрыта" 29 | status: 30 | public: 31 | title: "грамадскага" 32 | private: 33 | title: "прыватны" 34 | builder_modal: 35 | create: "стварыць" 36 | update: "захаваць" 37 | reminders: 38 | units: 39 | days: "дзён" 40 | periods: 41 | before: "перад" 42 | after: "пасля" 43 | url: 44 | label: "URL спасылка" 45 | location: 46 | label: "размяшчэнне" 47 | description: 48 | label: "Апісанне" 49 | status: 50 | label: "Статус" 51 | invite_user_or_group: 52 | invite: "адправіць" 53 | -------------------------------------------------------------------------------- /spec/jobs/regular/discourse_post_event/bump_topic_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe Jobs::DiscoursePostEventBumpTopic do 6 | let(:admin_1) { Fabricate(:user, admin: true) } 7 | let(:topic_1) { Fabricate(:topic, user: admin_1) } 8 | 9 | before do 10 | freeze_time DateTime.parse("2019-11-10 12:00") 11 | 12 | Jobs.run_immediately! 13 | 14 | SiteSetting.calendar_enabled = true 15 | SiteSetting.discourse_post_event_enabled = true 16 | end 17 | 18 | describe "#execute" do 19 | context "when params are present" do 20 | it "creates an auto-bump topic timer" do 21 | freeze_time 22 | Jobs::DiscoursePostEventBumpTopic.new.execute(topic_id: topic_1.id, date: "2019-12-10 5:00") 23 | 24 | timer = TopicTimer.find_by(topic: topic_1) 25 | expect(timer.status_type).to eq(TopicTimer.types[:bump]) 26 | expect(timer.execute_at).to eq_time(Time.zone.local(2019, 12, 10, 5, 0)) 27 | end 28 | end 29 | 30 | context "when the topic_id param is missing" do 31 | it "does not throw an error if the date param is present" do 32 | expect { 33 | Jobs::DiscoursePostEventBumpTopic.new.execute(date: "2019-12-10 5:00") 34 | }.not_to raise_error 35 | end 36 | it "does not throw an error if the date param is missing" do 37 | expect { Jobs::DiscoursePostEventBumpTopic.new.execute({}) }.not_to raise_error 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /db/migrate/20220724130519_fix_post_event_timezones.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class FixPostEventTimezones < ActiveRecord::Migration[7.0] 4 | def up 5 | execute <<~SQL 6 | UPDATE discourse_post_event_events 7 | SET 8 | original_starts_at = (original_starts_at::timestamp AT TIME ZONE timezone), 9 | original_ends_at = (original_ends_at::timestamp AT TIME ZONE timezone) 10 | WHERE timezone IS NOT NULL; 11 | SQL 12 | 13 | execute <<~SQL 14 | UPDATE discourse_calendar_post_event_dates 15 | SET 16 | starts_at = (starts_at::timestamp AT TIME ZONE timezone), 17 | ends_at = (ends_at::timestamp AT TIME ZONE timezone) 18 | FROM discourse_post_event_events 19 | WHERE discourse_post_event_events.id = discourse_calendar_post_event_dates.event_id 20 | AND discourse_post_event_events.timezone IS NOT NULL 21 | SQL 22 | 23 | execute <<~SQL 24 | UPDATE topic_custom_fields 25 | SET value = (value::timestamp AT TIME ZONE discourse_post_event_events.timezone) AT TIME ZONE 'UTC' 26 | FROM discourse_post_event_events 27 | JOIN posts ON discourse_post_event_events.id = posts.id 28 | WHERE discourse_post_event_events.timezone IS NOT NULL 29 | AND topic_custom_fields.topic_id = posts.topic_id 30 | AND topic_custom_fields.name IN ('TopicEventStartsAt', 'TopicEventEndsAt') 31 | SQL 32 | end 33 | 34 | def down 35 | raise ActiveRecord::IrreversibleMigration 36 | end 37 | end 38 | --------------------------------------------------------------------------------