From 0ae7b43018238a883326da950f1e3bbf7e1f87a6 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Tue, 14 Jun 2022 11:27:48 +0800 Subject: [PATCH 001/184] PERF: Remove total for time to first response report. (#17082) The query is very inefficient without any constraints on large sites and the average of all time to first response since the beginning of time is not useful as well. --- app/models/concerns/reports/time_to_first_response.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/concerns/reports/time_to_first_response.rb b/app/models/concerns/reports/time_to_first_response.rb index b40f1712c7..6217591ab5 100644 --- a/app/models/concerns/reports/time_to_first_response.rb +++ b/app/models/concerns/reports/time_to_first_response.rb @@ -17,8 +17,6 @@ module Reports::TimeToFirstResponse report.data << { x: r['date'], y: r['hours'].to_f.round(2) } end - report.total = Topic.time_to_first_response_total(category_id: category_id, include_subcategories: include_subcategories) - report.prev30Days = Topic.time_to_first_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_id, include_subcategories: include_subcategories) end end From 4a1937adff47cc123be4bd035ca4fdffac487191 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 14 Jun 2022 15:48:19 +1000 Subject: [PATCH 002/184] FEATURE: Change auto tracking to require 5 minutes vs 4 (#17081) Per @codinghorror, we prefer to change this setting to 5 minutes to avoid tracking topic automatically except for extremely exceptional situations. --- config/site_settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 32285973e9..e2be4ea867 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -2456,7 +2456,7 @@ user_preferences: default: 2880 default_other_auto_track_topics_after_msecs: enum: "AutoTrackDurationSiteSetting" - default: 240000 + default: 300000 default_other_notification_level_when_replying: enum: "NotificationLevelWhenReplyingSiteSetting" default: 2 From 47034d9ca0e5540e07a720d9f8c340da2cfca0b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 13:49:03 +0800 Subject: [PATCH 003/184] Build(deps): Bump zeitwerk from 2.5.4 to 2.6.0 (#17080) Bumps [zeitwerk](https://github.com/fxn/zeitwerk) from 2.5.4 to 2.6.0. - [Release notes](https://github.com/fxn/zeitwerk/releases) - [Changelog](https://github.com/fxn/zeitwerk/blob/main/CHANGELOG.md) - [Commits](https://github.com/fxn/zeitwerk/compare/v2.5.4...v2.6.0) --- updated-dependencies: - dependency-name: zeitwerk dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6914db5048..d0340e2dc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -497,7 +497,7 @@ GEM jwt (~> 2.0) xorcist (1.1.2) yaml-lint (0.0.10) - zeitwerk (2.5.4) + zeitwerk (2.6.0) PLATFORMS aarch64-linux From e7e23e8d9ce73ac61b970d5192f9f4af90b3a87c Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Tue, 14 Jun 2022 15:39:56 +0800 Subject: [PATCH 004/184] FIX: Remove tags from experimental sidebar on notification level changed (#17083) As part of this commit, a bug where updating a tag's notification level on the server side does not update the state of the user's tag notification levels on the client side is fixed too. --- .../discourse/app/controllers/tag-show.js | 17 +++--- .../app/models/topic-tracking-state.js | 15 +++-- .../acceptance/sidebar-tags-section-test.js | 33 +++++++++++ .../unit/models/topic-tracking-state-test.js | 4 +- app/controllers/tags_controller.rb | 2 +- app/serializers/basic_user_serializer.rb | 10 ---- .../concerns/user_tag_notifications_mixin.rb | 33 +++++++++++ app/serializers/current_user_serializer.rb | 28 +--------- app/serializers/user_serializer.rb | 17 +----- .../user_tag_notifications_serializer.rb | 15 +++++ spec/requests/tags_controller_spec.rb | 55 +++++++++++++++++++ .../current_user_serializer_spec.rb | 16 +++--- 12 files changed, 167 insertions(+), 78 deletions(-) create mode 100644 app/serializers/concerns/user_tag_notifications_mixin.rb create mode 100644 app/serializers/user_tag_notifications_serializer.rb diff --git a/app/assets/javascripts/discourse/app/controllers/tag-show.js b/app/assets/javascripts/discourse/app/controllers/tag-show.js index c3339debfc..e0faae1e25 100644 --- a/app/assets/javascripts/discourse/app/controllers/tag-show.js +++ b/app/assets/javascripts/discourse/app/controllers/tag-show.js @@ -181,14 +181,15 @@ export default DiscoverySortableController.extend( this.tagNotification .update({ notification_level: notificationLevel }) .then((response) => { - this.currentUser.set( - "muted_tag_ids", - this.currentUser.calculateMutedIds( - notificationLevel, - response.responseJson.tag_id, - "muted_tag_ids" - ) - ); + const payload = response.responseJson; + + this.currentUser.setProperties({ + watched_tags: payload.watched_tags, + watching_first_post_tags: payload.watching_first_post_tags, + tracked_tags: payload.tracked_tags, + muted_tags: payload.muted_tags, + regular_tags: payload.regular_tags, + }); }); }, } diff --git a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js index 109bd65707..0a2a9dac3c 100644 --- a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js +++ b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js @@ -32,15 +32,15 @@ function isUnseen(topic) { return !topic.is_seen; } -function hasMutedTags(topicTagIds, mutedTagIds, siteSettings) { - if (!mutedTagIds || !topicTagIds) { +function hasMutedTags(topicTags, mutedTags, siteSettings) { + if (!mutedTags || !topicTags) { return false; } return ( (siteSettings.remove_muted_tags_from_latest === "always" && - topicTagIds.any((tagId) => mutedTagIds.includes(tagId))) || + topicTags.any((topicTag) => mutedTags.includes(topicTag))) || (siteSettings.remove_muted_tags_from_latest === "only_muted" && - topicTagIds.every((tagId) => mutedTagIds.includes(tagId))) + topicTags.every((topicTag) => mutedTags.includes(topicTag))) ); } @@ -876,10 +876,9 @@ const TopicTrackingState = EmberObject.extend({ } if (["new_topic", "latest"].includes(data.message_type)) { - const mutedTagIds = User.currentProp("muted_tag_ids"); - if ( - hasMutedTags(data.payload.topic_tag_ids, mutedTagIds, this.siteSettings) - ) { + const mutedTags = User.currentProp("muted_tags"); + + if (hasMutedTags(data.payload.tags, mutedTags, this.siteSettings)) { return; } } diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js index 03c26f0992..cf220996fb 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js @@ -14,6 +14,8 @@ import { import { isLegacyEmber } from "discourse-common/config/environment"; import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures"; import { cloneJSON } from "discourse-common/lib/object"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; +import { NotificationLevels } from "discourse/lib/notification-levels"; acceptance("Sidebar - Tags section - tagging disabled", function (needs) { needs.settings({ @@ -62,6 +64,16 @@ acceptance("Sidebar - Tags section", function (needs) { ); }); }); + + server.put("/tag/:tagId/notifications", (request) => { + return helper.response({ + watched_tags: [], + watching_first_post_tags: [], + regular_tags: [request.params.tagId], + tracked_tags: [], + muted_tags: [], + }); + }); }); conditionalTest( @@ -370,4 +382,25 @@ acceptance("Sidebar - Tags section", function (needs) { ); } ); + + conditionalTest( + "updating tags notification levels", + !isLegacyEmber(), + async function (assert) { + await visit(`/tag/tag1/l/unread`); + + const notificationLevelsDropdown = selectKit(".notifications-button"); + + await notificationLevelsDropdown.expand(); + + await notificationLevelsDropdown.selectRowByValue( + NotificationLevels.REGULAR + ); + + assert.ok( + !exists(".sidebar-section-tags .sidebar-section-link-tag1"), + "tag1 section link is removed from sidebar" + ); + } + ); }); diff --git a/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js b/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js index 5978f449e3..173035392b 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/topic-tracking-state-test.js @@ -760,8 +760,10 @@ discourseModule("Unit | Model | topic-tracking-state", function (hooks) { }); test("topics in muted tags do not get added to the state", function (assert) { - trackingState.currentUser.set("muted_tag_ids", [44]); + trackingState.currentUser.set("muted_tags", ["pending"]); + publishToMessageBus("/new", newTopicPayload); + assert.strictEqual( trackingState.findState(222), undefined, diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 7d3012bb65..096c4d8c38 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -301,7 +301,7 @@ class TagsController < ::ApplicationController raise Discourse::NotFound unless tag level = params[:tag_notification][:notification_level].to_i TagUser.change(current_user.id, tag.id, level) - render json: { notification_level: level, tag_id: tag.id } + render_serialized(current_user, UserTagNotificationsSerializer, root: false) end def personal_messages diff --git a/app/serializers/basic_user_serializer.rb b/app/serializers/basic_user_serializer.rb index 26249bb483..fc580931f6 100644 --- a/app/serializers/basic_user_serializer.rb +++ b/app/serializers/basic_user_serializer.rb @@ -36,14 +36,4 @@ class BasicUserSerializer < ApplicationSerializer def category_user_notification_levels @category_user_notification_levels ||= CategoryUser.notification_levels_for(user) end - - def tags_with_notification_level(lookup_level) - tag_user_notification_levels.select do |id, level| - level == TagUser.notification_levels[lookup_level] - end.keys - end - - def tag_user_notification_levels - @tag_user_notification_levels ||= TagUser.notification_levels_for(user) - end end diff --git a/app/serializers/concerns/user_tag_notifications_mixin.rb b/app/serializers/concerns/user_tag_notifications_mixin.rb new file mode 100644 index 0000000000..dfa367d616 --- /dev/null +++ b/app/serializers/concerns/user_tag_notifications_mixin.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module UserTagNotificationsMixin + def muted_tags + tags_with_notification_level(:muted) + end + + def tracked_tags + tags_with_notification_level(:tracking) + end + + def watching_first_post_tags + tags_with_notification_level(:watching_first_post) + end + + def watched_tags + tags_with_notification_level(:watching) + end + + def regular_tags + tags_with_notification_level(:regular) + end + + def tags_with_notification_level(lookup_level) + tag_user_notification_levels.select do |id, level| + level == TagUser.notification_levels[lookup_level] + end.keys + end + + def tag_user_notification_levels + @tag_user_notification_levels ||= TagUser.notification_levels_for(user) + end +end diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index 2a48b85a3e..afbc3b7585 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class CurrentUserSerializer < BasicUserSerializer + include UserTagNotificationsMixin attributes :name, :unread_notifications, @@ -33,7 +34,6 @@ class CurrentUserSerializer < BasicUserSerializer :tracked_category_ids, :watched_first_post_category_ids, :watched_category_ids, - :muted_tag_ids, :watched_tags, :watching_first_post_tags, :tracked_tags, @@ -230,32 +230,6 @@ class CurrentUserSerializer < BasicUserSerializer categories_with_notification_level(:watching_first_post) end - # this is a weird outlier that is used for topic tracking state which - # needs the actual ids, which is why it is duplicated with muted_tags - def muted_tag_ids - TagUser.lookup(object, :muted).pluck(:tag_id) - end - - def muted_tags - tags_with_notification_level(:muted) - end - - def tracked_tags - tags_with_notification_level(:tracking) - end - - def watching_first_post_tags - tags_with_notification_level(:watching_first_post) - end - - def watched_tags - tags_with_notification_level(:watching) - end - - def regular_tags - tags_with_notification_level(:regular) - end - def ignored_users IgnoredUser.where(user: object.id).joins(:ignored_user).pluck(:username) end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 01df2bddfe..7c0a97378c 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class UserSerializer < UserCardSerializer + include UserTagNotificationsMixin attributes :bio_raw, :bio_cooked, @@ -211,22 +212,6 @@ class UserSerializer < UserCardSerializer ### ### PRIVATE ATTRIBUTES ### - def muted_tags - tags_with_notification_level(:muted) - end - - def tracked_tags - tags_with_notification_level(:tracking) - end - - def watching_first_post_tags - tags_with_notification_level(:watching_first_post) - end - - def watched_tags - tags_with_notification_level(:watching) - end - def muted_category_ids categories_with_notification_level(:muted) end diff --git a/app/serializers/user_tag_notifications_serializer.rb b/app/serializers/user_tag_notifications_serializer.rb new file mode 100644 index 0000000000..d2d0766a3e --- /dev/null +++ b/app/serializers/user_tag_notifications_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class UserTagNotificationsSerializer < ApplicationSerializer + include UserTagNotificationsMixin + + attributes :watched_tags, + :watching_first_post_tags, + :tracked_tags, + :muted_tags, + :regular_tags + + def user + object + end +end diff --git a/spec/requests/tags_controller_spec.rb b/spec/requests/tags_controller_spec.rb index 260f2d2002..5fe847eb7b 100644 --- a/spec/requests/tags_controller_spec.rb +++ b/spec/requests/tags_controller_spec.rb @@ -1095,4 +1095,59 @@ describe TagsController do end end end + + describe '#update_notifications' do + fab!(:tag) { Fabricate(:tag) } + + before do + sign_in(user) + end + + it 'returns 404 when tag is not found' do + put "/tag/someinvalidtagname/notifications.json" + + expect(response.status).to eq(404) + end + + it 'updates the notification level of a tag for a user' do + tag_user = TagUser.change(user.id, tag.id, NotificationLevels.all[:muted]) + + put "/tag/#{tag.name}/notifications.json", params: { + tag_notification: { + notification_level: NotificationLevels.all[:tracking] + } + } + + expect(response.status).to eq(200) + expect(response.parsed_body["watched_tags"]).to eq([]) + expect(response.parsed_body["watching_first_post_tags"]).to eq([]) + expect(response.parsed_body["tracked_tags"]).to eq([tag.name]) + expect(response.parsed_body["muted_tags"]).to eq([]) + expect(response.parsed_body["regular_tags"]).to eq([]) + + expect(tag_user.reload.notification_level).to eq(NotificationLevels.all[:tracking]) + end + + it 'sets the notification level of a tag for a user' do + expect do + put "/tag/#{tag.name}/notifications.json", params: { + tag_notification: { + notification_level: NotificationLevels.all[:muted] + } + } + + expect(response.status).to eq(200) + + expect(response.parsed_body["watched_tags"]).to eq([]) + expect(response.parsed_body["watching_first_post_tags"]).to eq([]) + expect(response.parsed_body["tracked_tags"]).to eq([]) + expect(response.parsed_body["muted_tags"]).to eq([tag.name]) + expect(response.parsed_body["regular_tags"]).to eq([]) + end.to change { user.tag_users.count }.by(1) + + tag_user = user.tag_users.last + + expect(tag_user.notification_level).to eq(NotificationLevels.all[:muted]) + end + end end diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 4047b40d65..bf2ec182ba 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -60,19 +60,21 @@ RSpec.describe CurrentUserSerializer do end end - context "#muted_tag_ids" do + context "#muted_tag" do fab!(:user) { Fabricate(:user) } fab!(:tag) { Fabricate(:tag) } + let!(:tag_user) do - TagUser.create!(user_id: user.id, - notification_level: TagUser.notification_levels[:muted], - tag_id: tag.id - ) + TagUser.create!( + user_id: user.id, + notification_level: TagUser.notification_levels[:muted], + tag_id: tag.id + ) end - it 'include muted tag ids' do + it 'includes muted tag names' do payload = serializer.as_json - expect(payload[:muted_tag_ids]).to eq([tag.id]) + expect(payload[:muted_tags]).to eq([tag.name]) end end From 4a240f6c9079b5c9e397cfec96d4bb33af9cfc4e Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Tue, 14 Jun 2022 15:56:20 +0800 Subject: [PATCH 005/184] DEV: First pass at messages section experimental sidebar (#17084) --- .../components/sidebar/messages-section.js | 17 +++++++ .../app/templates/components/sidebar.hbs | 2 + .../components/sidebar/messages-section.hbs | 10 +++++ .../templates/components/sidebar/section.hbs | 8 +++- .../sidebar-messages-section-test.js | 44 +++++++++++++++++++ config/locales/client.en.yml | 4 ++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/discourse/app/components/sidebar/messages-section.js create mode 100644 app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs create mode 100644 app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js new file mode 100644 index 0000000000..a71dc9dd4b --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js @@ -0,0 +1,17 @@ +import { action } from "@ember/object"; + +import GlimmerComponent from "discourse/components/glimmer"; +import Composer from "discourse/models/composer"; +import { getOwner } from "discourse-common/lib/get-owner"; + +export default class SidebarMessagesSection extends GlimmerComponent { + @action + composePersonalMessage() { + const composerArgs = { + action: Composer.PRIVATE_MESSAGE, + draftKey: Composer.NEW_TOPIC_KEY, + }; + + getOwner(this).lookup("controller:composer").open(composerArgs); + } +} diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs index 9c69d3ae7c..7469358ed5 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs @@ -7,6 +7,8 @@ {{#if this.siteSettings.tagging_enabled}} {{/if}} + + {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs new file mode 100644 index 0000000000..735f04344c --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs @@ -0,0 +1,10 @@ + + + diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/section.hbs index 3667ba75a0..0a6a8afed0 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/section.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/section.hbs @@ -4,7 +4,13 @@ {{d-icon this.headerCaretIcon}} - + + {{@headerLinkText}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js new file mode 100644 index 0000000000..c24f44e1ca --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js @@ -0,0 +1,44 @@ +import { click, currentURL, visit } from "@ember/test-helpers"; + +import { + acceptance, + conditionalTest, + exists, +} from "discourse/tests/helpers/qunit-helpers"; +import { isLegacyEmber } from "discourse-common/config/environment"; + +acceptance("Sidebar - Messages Section", function (needs) { + needs.user({ + experimental_sidebar_enabled: true, + }); + + conditionalTest( + "clicking on section header button", + !isLegacyEmber(), + async function (assert) { + await visit("/"); + + await click(".sidebar-section-messages .sidebar-section-header-button"); + + assert.ok( + exists("#reply-control.private-message"), + "it opens the composer" + ); + } + ); + + conditionalTest( + "clicking on section header link", + !isLegacyEmber(), + async function (assert) { + await visit("/"); + await click(".sidebar-section-messages .sidebar-section-header-link"); + + assert.strictEqual( + currentURL(), + `/u/eviltrout/messages`, + "it should transistion to the user's messages" + ); + } + ); +}); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3a58d4bb83..4be6914c16 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4041,6 +4041,10 @@ en: unread_count: "%{count} unread" new_count: "%{count} new" sections: + messages: + header_link_title: "personal messages" + header_link_text: "Messages" + header_action_title: "create a personal message" tags: no_tracked_tags: "You are not tracking any tags." header_link_title: "all tags" From 96e87af6053c39a4bf2f5e91d80a7025ff040b57 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Tue, 14 Jun 2022 11:05:03 +0200 Subject: [PATCH 006/184] FIX: Rake tasks related to uploads were broken (#17085) --- lib/tasks/uploads.rake | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 898f21cd97..8a12c168ce 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -857,11 +857,11 @@ end def analyze_missing_s3 puts "List of posts with missing images:" sql = <<~SQL - SELECT post_id, url, sha1, extension, uploads.id + SELECT ur.target_id, u.url, u.sha1, u.extension, u.id FROM upload_references ur - RIGHT JOIN uploads on uploads.id = ur.upload_id - WHERE ur.target_type = 'Post' AND verification_status = :invalid_etag - ORDER BY created_at + RIGHT JOIN uploads u ON u.id = ur.upload_id + WHERE ur.target_type = 'Post' AND u.verification_status = :invalid_etag + ORDER BY ur.created_at SQL lookup = {} @@ -1047,12 +1047,11 @@ def fix_missing_s3 puts "Rebaking posts with missing uploads, this can take a while as all rebaking runs inline" sql = <<~SQL - SELECT target_id + SELECT ur.target_id FROM upload_references ur - JOIN uploads on uploads.id = ur.upload_id - WHERE ur.target_type = 'Post' - WHERE verification_status = :invalid_etag - ORDER BY target_id DESC + JOIN uploads u ON u.id = ur.upload_id + WHERE ur.target_type = 'Post' AND u.verification_status = :invalid_etag + ORDER BY ur.target_id DESC SQL DB.query_single(sql, invalid_etag: Upload.verification_statuses[:invalid_etag]).each do |post_id| From e0364cf228e91e64f214c7afd8e466992f86e48f Mon Sep 17 00:00:00 2001 From: Discourse Translator Bot Date: Tue, 14 Jun 2022 10:57:33 -0400 Subject: [PATCH 007/184] Update translations (#17087) --- config/locales/client.ar.yml | 5 ++ config/locales/client.be.yml | 4 + config/locales/client.bg.yml | 5 ++ config/locales/client.bs_BA.yml | 5 ++ config/locales/client.ca.yml | 5 ++ config/locales/client.cs.yml | 5 ++ config/locales/client.da.yml | 5 ++ config/locales/client.de.yml | 7 ++ config/locales/client.el.yml | 5 ++ config/locales/client.es.yml | 11 +++ config/locales/client.et.yml | 5 ++ config/locales/client.fa_IR.yml | 8 ++ config/locales/client.fi.yml | 5 ++ config/locales/client.fr.yml | 14 ++++ config/locales/client.gl.yml | 5 ++ config/locales/client.he.yml | 76 ++++++++++--------- config/locales/client.hu.yml | 5 ++ config/locales/client.hy.yml | 5 ++ config/locales/client.id.yml | 4 + config/locales/client.it.yml | 9 +++ config/locales/client.ja.yml | 5 ++ config/locales/client.ko.yml | 5 ++ config/locales/client.lt.yml | 5 ++ config/locales/client.lv.yml | 5 ++ config/locales/client.nb_NO.yml | 5 ++ config/locales/client.nl.yml | 5 ++ config/locales/client.pl_PL.yml | 5 ++ config/locales/client.pt.yml | 5 ++ config/locales/client.pt_BR.yml | 5 ++ config/locales/client.ro.yml | 5 ++ config/locales/client.ru.yml | 9 +++ config/locales/client.sk.yml | 5 ++ config/locales/client.sl.yml | 6 ++ config/locales/client.sq.yml | 5 ++ config/locales/client.sr.yml | 2 + config/locales/client.sv.yml | 11 +++ config/locales/client.sw.yml | 5 ++ config/locales/client.te.yml | 4 + config/locales/client.th.yml | 5 ++ config/locales/client.tr_TR.yml | 8 ++ config/locales/client.uk.yml | 5 ++ config/locales/client.ur.yml | 5 ++ config/locales/client.vi.yml | 5 ++ config/locales/client.zh_CN.yml | 9 +++ config/locales/client.zh_TW.yml | 5 ++ config/locales/server.ar.yml | 1 - config/locales/server.be.yml | 1 - config/locales/server.bg.yml | 1 - config/locales/server.bs_BA.yml | 1 - config/locales/server.ca.yml | 1 - config/locales/server.da.yml | 1 - config/locales/server.de.yml | 9 ++- config/locales/server.el.yml | 1 - config/locales/server.es.yml | 12 ++- config/locales/server.fa_IR.yml | 6 +- config/locales/server.fi.yml | 1 - config/locales/server.fr.yml | 1 - config/locales/server.gl.yml | 1 - config/locales/server.he.yml | 45 ++++++----- config/locales/server.hy.yml | 1 - config/locales/server.it.yml | 8 +- config/locales/server.ja.yml | 1 - config/locales/server.ko.yml | 1 - config/locales/server.lt.yml | 1 - config/locales/server.nb_NO.yml | 1 - config/locales/server.nl.yml | 1 - config/locales/server.pl_PL.yml | 7 +- config/locales/server.pt.yml | 1 - config/locales/server.pt_BR.yml | 1 - config/locales/server.ro.yml | 1 - config/locales/server.ru.yml | 9 ++- config/locales/server.sk.yml | 1 - config/locales/server.sl.yml | 11 +++ config/locales/server.sq.yml | 1 - config/locales/server.sv.yml | 9 ++- config/locales/server.tr_TR.yml | 6 +- config/locales/server.uk.yml | 1 - config/locales/server.ur.yml | 1 - config/locales/server.vi.yml | 1 - config/locales/server.zh_CN.yml | 9 ++- config/locales/server.zh_TW.yml | 1 - plugins/poll/config/locales/server.fa_IR.yml | 2 + .../config/locales/client.fa_IR.yml | 2 + 83 files changed, 400 insertions(+), 92 deletions(-) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index 7eab6c3152..a9ec604b90 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -4275,6 +4275,11 @@ ar: unread_count: "%{count} غير مقروء" new_count: "%{count} جديد" sections: + messages: + header_link_text: "الرسائل" + tags: + header_link_title: "كل الوسوم" + header_link_text: "الوسوم" categories: header_link_title: "كل الفئات" header_link_text: "الفئات" diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml index 3dd8340076..84f951235c 100644 --- a/config/locales/client.be.yml +++ b/config/locales/client.be.yml @@ -1341,6 +1341,10 @@ be: no_likes_others: "Не спадабалася паведамленні." sidebar: sections: + messages: + header_link_text: "Паведамленні" + tags: + header_link_text: "тэгі" categories: header_link_title: "усе катэгорыі" header_link_text: "катэгорыі" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 491c965bba..ed4fba9f87 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -2988,6 +2988,11 @@ bg: unread_count: "%{count} непрочетен" new_count: "%{count} нов" sections: + messages: + header_link_text: "Съобщения" + tags: + header_link_title: "всички тагове" + header_link_text: "Тагове" categories: header_link_title: "всички категории" header_link_text: "Категории" diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml index d83929a16f..ce21b89841 100644 --- a/config/locales/client.bs_BA.yml +++ b/config/locales/client.bs_BA.yml @@ -3066,6 +3066,11 @@ bs_BA: unread_count: "%{count} nepročitana" new_count: "%{count} novi" sections: + messages: + header_link_text: "Poruke" + tags: + header_link_title: "sve oznake" + header_link_text: "Oznake" categories: header_link_title: "Sve kategorije" header_link_text: "Kategorije" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index b60568d6d4..1140082730 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -2883,6 +2883,11 @@ ca: unread_count: "%{count} no llegit" new_count: "%{count} nou" sections: + messages: + header_link_text: "Missatges" + tags: + header_link_title: "totes les etiquetes" + header_link_text: "Etiquetes" categories: header_link_title: "totes les categories" header_link_text: "Categories" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 593de41627..6abcd6b991 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -2816,6 +2816,11 @@ cs: unread_count: "%{count} nepřečtené" new_count: "%{count} nové" sections: + messages: + header_link_text: "Zprávy" + tags: + header_link_title: "všechny štítky" + header_link_text: "Tagy" categories: header_link_title: "všechny kategorie" header_link_text: "Kategorie" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 67aa98ee18..d34d36fce8 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -3556,6 +3556,11 @@ da: unread_count: "%{count} ulæst" new_count: "%{count} ny" sections: + messages: + header_link_text: "Beskeder" + tags: + header_link_title: "alle mærker" + header_link_text: "Tags" categories: header_link_title: "alle kategorier" header_link_text: "Kategorier" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index 0bb80e4953..4ca19f35fe 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -3719,7 +3719,14 @@ de: unread_count: "%{count} ungelesenes" new_count: "%{count} neues" sections: + messages: + header_link_text: "Nachrichten" + tags: + no_tracked_tags: "Du verfolgst keine Schlagwörter (tags)." + header_link_title: "alle Schlagwörter" + header_link_text: "Schlagwörter" categories: + no_tracked_categories: "Du verfolgst keine Kategorien." header_link_title: "alle Kategorien" header_link_text: "Kategorien" topics: diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index ff4195452e..3095134b6d 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -3064,6 +3064,11 @@ el: unread_count: "%{count} μη αναγνωσμένο" new_count: "%{count} νέο" sections: + messages: + header_link_text: "Μηνύματα" + tags: + header_link_title: "όλες οι ετικέτες" + header_link_text: "Ετικέτες" categories: header_link_title: "όλες οι κατηγορίες" header_link_text: "Κατηγορίες" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index abdef48f9d..c584070752 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -1636,6 +1636,8 @@ es: the_topic: "el tema" user_status: save: "Guardar" + set_custom_status: "Escribir estado personalizado" + what_are_you_doing: "¿Qué estás haciendo?" loading: "Cargando..." errors: prev_page: "mientras se intentaba cargar" @@ -3717,6 +3719,11 @@ es: unread_count: "%{count} sin leer" new_count: "%{count} nuevo" sections: + messages: + header_link_text: "Mensajes" + tags: + header_link_title: "todas las etiquetas" + header_link_text: "Etiquetas" categories: header_link_title: "todas las categorías" header_link_text: "Categorías" @@ -3736,6 +3743,10 @@ es: title: "Todos los temas enn marcadores" my_posts: content: "Mis publicaciones" + title: "Mis publicaciones" + draft_count: + one: "%{count} borrador" + other: "%{count} borradores" admin_js: type_to_filter: "filtrar opciones..." admin: diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 3697d29534..4fcda0902d 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -2533,6 +2533,11 @@ et: unread_count: "%{count} lugemata" new_count: "%{count} uus" sections: + messages: + header_link_text: "Sõnumid" + tags: + header_link_title: "kõik sildid" + header_link_text: "Sildid" categories: header_link_title: "kõik foorumid" header_link_text: "Liigid" diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml index 2f7a02d9f9..0c542f806f 100644 --- a/config/locales/client.fa_IR.yml +++ b/config/locales/client.fa_IR.yml @@ -3187,6 +3187,14 @@ fa_IR: unread_count: "%{count} خوانده نشده" new_count: "%{count} تازه" sections: + messages: + header_link_title: "پیام‌های شخصی" + header_link_text: "پیام‌ها" + header_action_title: "ایجاد پیام شخصی" + tags: + no_tracked_tags: "شما هیچ برچسبی را ردیابی نمی‌کنید." + header_link_title: "تمام برچسب‌ها" + header_link_text: "برچسب" categories: header_link_title: "همه‌ی دسته‌بندی‌ها" header_link_text: "دسته‌بندی‌ها" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 4bdb5aaa96..18a522ba40 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -3462,6 +3462,11 @@ fi: unread_count: "%{count} lukematta" new_count: "%{count} uusi" sections: + messages: + header_link_text: "Viestit" + tags: + header_link_title: "kaikki tunnisteet" + header_link_text: "Tunnisteet" categories: header_link_title: "kaikki alueet" header_link_text: "Alueet" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index d794a01371..8a2e4166da 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -1535,6 +1535,7 @@ fr: expires_at: "Ce lien expirera dans" custom_message: "Message personnel (facultatif)" send_invite_email: "Enregistrer et envoyer le courriel" + send_invite_email_instructions: "Restreindre l'invitation à l'email pour envoyer un email d'invitation" save_invite: "Enregistrer l'invitation" invite_saved: "Invitation enregistrée." bulk_invite: @@ -1619,6 +1620,7 @@ fr: none: "(aucun)" instructions: "Apparaît après votre nom d'utilisateur" flair: + title: "Flair" none: "(aucun)" instructions: "icône affichée à côté de votre image de profil" primary_group: @@ -2602,7 +2604,10 @@ fr: unpin: "Désépingler le sujet…" unarchive: "Désarchiver le sujet" archive: "Archiver le sujet" + invisible: "Rendre le sujet invisible" + visible: "Lister le sujet" reset_read: "Réinitialiser les données de lecture" + make_public: "Rendre le sujet public..." make_private: "Transformer en message direct" reset_bump_date: "Réinitialiser la date d'actualisation" feature: @@ -3331,6 +3336,8 @@ fr: one: "%{count} non lu" other: "%{count} non lus" unseen: + title: "Invisible" + lower_title: "invisible" help: "nouveaux sujets et sujets que vous suivez ou surveillez avec des messages non lus" new: lower_title_with_count: @@ -3710,6 +3717,11 @@ fr: unread_count: "%{count} non lu" new_count: "%{count} nouveau" sections: + messages: + header_link_text: "Messages" + tags: + header_link_title: "toutes les étiquettes" + header_link_text: "Étiquettes" categories: header_link_title: "toutes les catégories" header_link_text: "Catégories" @@ -3729,6 +3741,7 @@ fr: title: "Tous les sujets marqués d'un signet" my_posts: content: "Mes messages" + title: "Mes publications" admin_js: type_to_filter: "commencez votre saisie pour filtrer…" admin: @@ -3921,6 +3934,7 @@ fr: no_description: (aucune description) all_api_keys: Toutes les clés pour l’API user_mode: Niveau de l’utilisateur + scope_mode: Périmètre d'application impersonate_all_users: Incarner un utilisateur single_user: "Utilisateur unique" user_placeholder: Saisissez un nom d'utilisateur diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index ea7be531ed..5c6e2a8d77 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -3353,6 +3353,11 @@ gl: unread_count: "%{count} pendente de lectura" new_count: "%{count} novo" sections: + messages: + header_link_text: "Mensaxes" + tags: + header_link_title: "todas as etiquetas" + header_link_text: "Etiquetas" categories: header_link_title: "todas as categorías" header_link_text: "Categorías" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 8c478a87d7..f5a18485c1 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -183,7 +183,7 @@ he: many: "כעבור %{count} חודשים" other: "כעבור %{count} חודשים" x_years: - one: "שנה לאחר מכן" + one: "כעבור שנה %{count}" two: "כעבור שנתיים" many: "כעבור %{count} שנים" other: "כעבור %{count} שנים" @@ -208,8 +208,8 @@ he: invited_user: "נשלחה הזמנה אל %{who} ב־%{when}" invited_group: "נשלחה הזמנה אל %{who} ב־%{when}" user_left: "%{who} הסירו עצמם מהודעה זו %{when}" - removed_user: "התבצעה הסרה של %{who} ב־%{when}" - removed_group: "%{who} הוסר ב־%{when}" + removed_user: "הוסר ע״י %{who} ב־%{when}" + removed_group: "הוסר ע״י %{who} ב־%{when}" autobumped: "הוקפץ אוטומטית ב־%{when}" autoclosed: enabled: "נסגר ב־%{when}" @@ -248,7 +248,7 @@ he: other: "כדי להקל על הקמת האתר החדש שלך, כרגע המערכת במצב אתחול ראשוני. לכל המשתמשים החדשים תוענק דרגת האמון 1 ויישלח אליהם תמצות יומי בדוא״ל. אפשרות זו תכבה אוטומטית לאחר שהצטרפו %{count} משתמשים." bootstrap_mode_disabled: "מצב Bootstrap יבוטל תוך 24 שעות." themes: - default_description: "בררת מחדל" + default_description: "ברירת מחדל" broken_theme_alert: "יתכן שהאתר שלך לא יתפקד כיוון שבערכת העיצוב / הרכיב יש שגיאות." error_caused_by: "נגרם על ידי ‚%{name}’. יש ללחוץ כאן כדי לעדכן, להגדיר מחדש או להשבית." only_admins: "(הודעה זו מופיע רק למנהלי האתר)" @@ -264,7 +264,7 @@ he: ca_central_1: "קנדה (מרכז)" cn_north_1: "סין (בייג׳ינג)" cn_northwest_1: "סין (נינגשיה)" - eu_central_1: "האיחוד האירופי (פרנקפורט)" + eu_central_1: "אירופה (פרנקפורט)" eu_north_1: "אירופה (שטוקהולם)" eu_south_1: "אירופה (מילאנו)" eu_west_1: "אירופה (אירלנד)" @@ -273,17 +273,17 @@ he: sa_east_1: "אמריקה הדרומית (סאו פאולו)" us_east_1: "מזרח ארה״ב (צפון וירג׳יניה)" us_east_2: "מזרח ארה״ב (אוהיו)" - us_gov_east_1: "הענן הממשלתי של AWS (ארה״ב-מערב)" + us_gov_east_1: "הענן הממשלתי של AWS (מזרח ארה״ב)" us_gov_west_1: "הענן הממשלתי של AWS (מערב ארה״ב)" us_west_1: "מערב ארה״ב (צפון קליפורניה)" us_west_2: "מערב ארה״ב (אורגון)" clear_input: "פינוי הקלט" edit: "עריכת הכותרת והקטגוריה של נושא זה" - expand: "הרחב" - not_implemented: "תכונה זו עדיין לא מומשה, עמך הסליחה!" + expand: "הרחבה" + not_implemented: "יכולת זו עדיין לא מומשה, עמך הסליחה!" no_value: "לא" yes_value: "כן" - submit: "שליחה" + submit: "הגשה" generic_error: "ארעה שגיאה, עמך הסליחה." generic_error_with_reason: "ארעה שגיאה: %{error}" sign_up: "הרשמה" @@ -304,14 +304,14 @@ he: privacy_policy: "מדיניות פרטיות" privacy: "פרטיות" tos: "תנאי השירות" - rules: "חוקים" + rules: "כללים" conduct: "נהלי התנהגות" mobile_view: "תצוגת נייד" desktop_view: "תצוגת מחשב" or: "או" now: "ממש עכשיו" - read_more: "המשך קריאה" - more: "להרחבה" + read_more: "מידע נוסף" + more: "עוד" x_more: one: "עוד %{count}" two: "עוד %{count}" @@ -340,13 +340,13 @@ he: pm_title: "הודעות מוצעות" about: simple_title: "על אודות" - title: "על אודות %{title}" - stats: "סטטיסטיקות אתר" + title: "על %{title}" + stats: "סטטיסטיקת אתר" our_admins: "המנהלים שלנו" our_moderators: "המפקחים שלנו" moderators: "מפקחים" stat: - all_time: "כל הזמנים" + all_time: "כל הזמן" last_day: "24 השעות האחרונות" last_7_days: "7 הימים האחרונים" last_30_days: "30 הימים האחרונים" @@ -444,23 +444,23 @@ he: cancel: "ביטול" deleting: "מתבצעת מחיקה…" save: "שמירת השינויים" - saving: "בהליכי שמירה..." + saving: "בהליכי שמירה…" saved: "נשמר!" upload: "העלאה" - uploading: "בהליכי העלאה..." + uploading: "בהליכי העלאה…" processing: "מתבצע עיבוד…" - uploading_filename: "מעלה: %{filename}..." + uploading_filename: "בהעלאה: %{filename}…" processing_filename: "מתבצע עיבוד: %{filename}…" - clipboard: "לוח" + clipboard: "לוח גזירים" uploaded: "הועלה!" - pasting: "מדביק..." - enable: "לאפשר" + pasting: "מתבצעת הדבקה…" + enable: "הפעלה" disable: "השבתה" continue: "המשך" undo: "לבטל פעולה" revert: "להחזיר" failed: "נכשל" - switch_to_anon: "כניסה למצב אלמוני" + switch_to_anon: "מעבר למצב אלמוני" switch_from_anon: "יציאה ממצב אלמוני" banner: close: "התעלמות מהכרזה" @@ -494,7 +494,7 @@ he: title: "כאשר חבר סגל בוחר לנקוט בפעולה הדגל מקבל בונוס." user_accuracy_bonus: name: "דיוק משתמש" - title: "משתמשים שסימון הדגל שלהם קיבל הסכמה בעבר מקבלים בונוס." + title: "משתמשים שסימון הדגל שלהם היה מוצדק בעבר מקבלים בונוס." trust_level_bonus: name: "דרגת אמון" title: "לפריטים לסקירה שנוצרו על ידי משתמשים בדרגות אמון גבוהות יותר יש ניקוד גבוה יותר." @@ -508,17 +508,17 @@ he: claimed_by_you: "דרשת את הפריט הזה ועכשיו יתאפשר לך לסקור אותו." claimed_by_other: "הפריט הזה זמין לסריקה רק על ידי %{username}." claim: - title: "דרישת פריט זה" + title: "דרישת נושא זה" unclaim: help: "הסרת דרישה זו" awaiting_approval: "בהמתנה לאישור" - delete: "הסרה" + delete: "מחיקה" settings: saved: "נשמר" save_changes: "שמירת השינויים" title: "הגדרות" priorities: - title: "עדיפויות ניתנות לסקירה" + title: "עדיפויות פריטים לסקירה" moderation_history: "היסטוריית פעילות פיקוח" view_all: "להציג הכול" grouped_by_topic: "קיבוץ לפי נושא" @@ -592,7 +592,7 @@ he: type: title: "סוג" all: "(כל הסוגים)" - minimum_score: "ניקוד מזערי" + minimum_score: "ניקוד מזערי:" refresh: "רענון" status: "מצב" category: "קטגוריה" @@ -1889,13 +1889,13 @@ he: forgot_password: title: "אתחול סיסמה" action: "שכחתי את הסיסמה שלי" - invite: "נא להקליד את שם המשתמש וכתובת הדוא״ל שלך ואנו נשלח לך הודעה בדוא״ל לאיפוס ססמה." + invite: "נא להקליד את שם המשתמש וכתובת הדוא״ל שלך ואנו נשלח לך הודעה בדוא״ל לאיפוס סיסמה." invite_no_username: "נא להקליד את כתובת הדוא״ל שלך ואנו נשלח לך הודעה בדוא״ל לאיפוס ססמה." - reset: "איפוס ססמה" - complete_username: "אם קיים חשבון שמתאים לשם המשתמש %{username}, תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הססמה שלך." - complete_email: "אם החשבון מתאים לכתובת %{email}, תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הססמה שלך." - complete_username_found: "מצאנו חשבון שתואם לשם המשתמש %{username}. תוך זמן קצר אמורה להגיע לדוא״ל שלך הודעה עם הנחיות כיצד לאפס את הססמה שלך." - complete_email_found: "מצאנו חשבון שתואם לכתובת %{email}. תוך זמן קצר אמורה להגיע לדוא״ל שלך הודעה עם הנחיות כיצד לאפס את הססמה שלך." + reset: "איפוס סיסמה" + complete_username: "אם יש חשבון שעונה לשם המשתמש %{username}, תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הסיסמה שלך." + complete_email: "אם החשבון עונה לכתובת %{email}, תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הסיסמה שלך." + complete_username_found: "מצאנו חשבון שעונה לשם המשתמש %{username}. תוך זמן קצר אמורה להגיע לדוא״ל שלך הודעה עם הנחיות כיצד לאפס את הסיסמה שלך." + complete_email_found: "מצאנו חשבון שתואם לכתובת %{email}. תוך זמן קצר אמורה להגיע לדוא״ל שלך הודעה עם הנחיות כיצד לאפס את הסיסמה שלך." complete_username_not_found: "שום חשבון אינו תואם לשם המשתמש %{username}" complete_email_not_found: "שום חשבון אינו תואם ל %{email}" help: "ההודעה אינה מגיעה אליך לתיבת הדוא״ל? נא לבדוק את תיקיית הזבל/ספאם קודם.

לא ברור לך באיזו כתובת דוא״ל השתמשת? נא להקליד כתובת דוא״ל ואנו ניידע אותך אם היא קיימת כאן.

אם כבר אין לך גישה לכתובת הדוא״ל של החשבון שלך, נא ליצור קשר עם הסגל המועיל שלנו.

" @@ -1939,12 +1939,12 @@ he: cookies_error: "כנראה שהעוגיות בדפדפן שלך מושבתות. אין אפשרות להיכנס מבלי להפעיל אותן." rate_limit: "נא להמתין בטרם ביצוע ניסיון כניסה חוזר." blank_username: "נא למלא כתובת דוא״ל או שם משתמש." - blank_username_or_password: "נא למלא את כתובת הדוא״ל או את שם המשתמש שלך וססמה." + blank_username_or_password: "נא למלא את כתובת הדוא״ל או את שם המשתמש שלך וסיסמה." reset_password: "אפס סיסמה" logging_in: "מתחבר...." or: "או" authenticating: "מאשר..." - awaiting_activation: "החשבון שלך ממתין להפעלה, נא להשתמש בקישור „שכחתי ססמה” כדי לשלוח הודעת הפעלה נוספת." + awaiting_activation: "החשבון שלך ממתין להפעלה, נא להשתמש בקישור „שכחתי סיסמה” כדי לשלוח הודעת הפעלה נוספת." awaiting_approval: "החשבון שלך טרם אושר על ידי חבר סגל. תישלח אליך הודעה בדוא״ל כשהוא יאושר." requires_invite: "סליחה, גישה לפורום הזה היא בהזמנה בלבד." not_activated: "עוד אין לך אפשרות להיכנס. שלחנו אליך הודעת הפעלה בדוא״ל אל %{sentTo}. נא לעקוב אחר ההוראות שבהודעה כדי להפעיל את החשבון שלך." @@ -4054,6 +4054,12 @@ he: unread_count: "לא נקרא (%{count})" new_count: "חדש (%{count})" sections: + messages: + header_link_text: "הודעות" + tags: + no_tracked_tags: "אין לך אף תגית במעקב." + header_link_title: "כל התגיות" + header_link_text: "תגיות" categories: header_link_title: "כל הקטגוריות" header_link_text: "קטגוריות" diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index c045d0eb19..bfbad79b4d 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -3299,6 +3299,11 @@ hu: unread_count: "%{count} olvasatlan" new_count: "%{count} új" sections: + messages: + header_link_text: "Üzenetek" + tags: + header_link_title: "összes címke" + header_link_text: "Címkék" categories: header_link_title: "összes kategória" header_link_text: "Kategóriák" diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml index acfc06b111..7ff6fd715c 100644 --- a/config/locales/client.hy.yml +++ b/config/locales/client.hy.yml @@ -2872,6 +2872,11 @@ hy: unread_count: "%{count} չկարդացած" new_count: "%{count} նոր" sections: + messages: + header_link_text: "Հաղորդագրություններ" + tags: + header_link_title: "բոլոր թեգերը" + header_link_text: "Թեգեր" categories: header_link_title: "բոլոր կատեգորիաները" header_link_text: "Կատեգորիաներ" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 2f83ee62ff..58bee8402c 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -1785,6 +1785,10 @@ id: sidebar: new_count: "%{count} baru" sections: + messages: + header_link_text: "Pesan-pesan" + tags: + header_link_text: "Label" categories: header_link_title: "Semua kategori" header_link_text: "Kategori" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index c0954cf765..a5355a82af 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -3680,7 +3680,16 @@ it: unread_count: "%{count} non letti" new_count: "%{count} nuovi" sections: + messages: + header_link_title: "messaggi personali" + header_link_text: "Messaggi" + header_action_title: "crea un messaggio personale" + tags: + no_tracked_tags: "Non stai seguendo alcuna etichetteta." + header_link_title: "tutte le etichette" + header_link_text: "Etichette" categories: + no_tracked_categories: "Non stai monitorando nessuna categoria." header_link_title: "tutte le categorie" header_link_text: "Categorie" topics: diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index c51d9423b9..bb75ec41ed 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -3397,6 +3397,11 @@ ja: unread_count: "未読 %{count}" new_count: "新規 %{count}" sections: + messages: + header_link_text: "メッセージ" + tags: + header_link_title: "すべてのタグ" + header_link_text: "タグ" categories: header_link_title: "すべてのカテゴリ" header_link_text: "カテゴリ" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index ed74802cd2..b5c1ab01b6 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -3440,6 +3440,11 @@ ko: unread_count: "%{count} unread" new_count: "%{count} new" sections: + messages: + header_link_text: "메시지" + tags: + header_link_title: "모든 태그" + header_link_text: "태그" categories: header_link_title: "모든 카테고리" header_link_text: "카테고리" diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index e817e135c4..32533c12bf 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -3484,6 +3484,11 @@ lt: unread_count: "%{count} neperskaitytas" new_count: "%{count} nauja" sections: + messages: + header_link_text: "Žinutės" + tags: + header_link_title: "visos žymos" + header_link_text: "Žymos" categories: header_link_title: "visos kategorijos" header_link_text: "Kategorijos" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index 9afe7e2514..f8b16bdb2f 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -2805,6 +2805,11 @@ lv: unread_count: "%{count} nelasīta" new_count: "%{count} jauna" sections: + messages: + header_link_text: "Ziņas" + tags: + header_link_title: "visi tagi" + header_link_text: "Birkas" categories: header_link_title: "visas sadaļas" header_link_text: "Sadaļas" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index 435b86cf0d..62794ad227 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -3420,6 +3420,11 @@ nb_NO: unread_count: "%{count} ulest" new_count: "%{count} ny" sections: + messages: + header_link_text: "Meldinger" + tags: + header_link_title: "alle stikkord" + header_link_text: "Stikkord" categories: header_link_title: "alle kategorier" header_link_text: "Kategorier" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index da1c9382c0..cee377ffb4 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -3490,6 +3490,11 @@ nl: unread_count: "%{count} ongelezen" new_count: "%{count} nieuw" sections: + messages: + header_link_text: "Berichten" + tags: + header_link_title: "alle tags" + header_link_text: "Tags" categories: header_link_title: "alle categorieën" header_link_text: "Categorieën" diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml index c4113a2ccb..c2c5134216 100644 --- a/config/locales/client.pl_PL.yml +++ b/config/locales/client.pl_PL.yml @@ -4030,6 +4030,11 @@ pl_PL: unread_count: "%{count} nieprzeczytany" new_count: "%{count} nowy" sections: + messages: + header_link_text: "Wiadomości" + tags: + header_link_title: "wszystkie tagi" + header_link_text: "Etykiety" categories: header_link_title: "wszystkie kategorie" header_link_text: "Kategorie" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index 3163ed8375..4cc31555ee 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -3422,6 +3422,11 @@ pt: unread_count: "%{count} não lido" new_count: "%{count} novo" sections: + messages: + header_link_text: "Mensagens" + tags: + header_link_title: "todas as etiquetas" + header_link_text: "Etiquetas" categories: header_link_title: "todas as categorias" header_link_text: "Categorias" diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml index b2a814cc1c..a4425e1f3a 100644 --- a/config/locales/client.pt_BR.yml +++ b/config/locales/client.pt_BR.yml @@ -3622,6 +3622,11 @@ pt_BR: unread_count: "%{count} não lido" new_count: "%{count} nova" sections: + messages: + header_link_text: "Mensagens" + tags: + header_link_title: "todas as etiquetas" + header_link_text: "Etiquetas" categories: header_link_title: "todas as categorias" header_link_text: "Categorias" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index a480b376b2..d3789c53c2 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -2783,6 +2783,11 @@ ro: unread_count: "%{count} necitit" new_count: "%{count} nou" sections: + messages: + header_link_text: "Mesaje" + tags: + header_link_title: "toate etichetele" + header_link_text: "Etichete" categories: header_link_title: "Toate categoriile" header_link_text: "Categorii" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 3e71e08d59..7f86c24049 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -4057,7 +4057,16 @@ ru: unread_count: "%{count} непрочитанных" new_count: "%{count} новых" sections: + messages: + header_link_title: "Личные сообщения" + header_link_text: "Личные сообщения" + header_action_title: "Создать личное сообщение" + tags: + no_tracked_tags: "Вы не отслеживаете ни один тег." + header_link_title: "Все теги" + header_link_text: "Теги" categories: + no_tracked_categories: "Вы не отслеживаете ни один раздел." header_link_title: "Все разделы" header_link_text: "Разделы" topics: diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index 99bd45589d..560a4fe038 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -2271,6 +2271,11 @@ sk: unread_count: "%{count} neprečítaná" new_count: "%{count} nová" sections: + messages: + header_link_text: "Správy" + tags: + header_link_title: "všetky štítky" + header_link_text: "Štítky" categories: header_link_title: "všetky kategórie" header_link_text: "Kategórie" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 5f63805d52..e7629c6d6d 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -3374,6 +3374,11 @@ sl: unread_count: "%{count} neprebrana" new_count: "%{count} nova" sections: + messages: + header_link_text: "Sporočila" + tags: + header_link_title: "vse oznake" + header_link_text: "Oznake" categories: header_link_title: "vse kategorije" header_link_text: "Kategorije" @@ -3385,6 +3390,7 @@ sl: content: "Sledim" my_posts: content: "Moji prispevki" + title: "Moji prispevki" admin_js: type_to_filter: "vnesite za filter..." admin: diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 8963aff861..a264206715 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -1931,6 +1931,11 @@ sq: unread_count: "%{count} e palexuar" new_count: "%{count} e re" sections: + messages: + header_link_text: "Mesazhet" + tags: + header_link_title: "të gjitha etiketat" + header_link_text: "Etiketat" categories: header_link_title: "kategoritë" header_link_text: "Categories" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 18632698f9..d54ed87bf9 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -1801,6 +1801,8 @@ sr: unread_count: "%{count} nepročitana" new_count: "%{count} nova" sections: + messages: + header_link_text: "Poruke" categories: header_link_title: "sve kategorije" header_link_text: "Kategorije" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index d5323e9f83..972087656c 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -3719,7 +3719,14 @@ sv: unread_count: "%{count} oläst" new_count: "%{count} ny" sections: + messages: + header_link_text: "Meddelanden" + tags: + no_tracked_tags: "Du följer inga taggar." + header_link_title: "alla taggar" + header_link_text: "Taggar" categories: + no_tracked_categories: "Du följer inga kategorier." header_link_title: "alla kategorier" header_link_text: "Kategorier" topics: @@ -3738,6 +3745,10 @@ sv: title: "Alla bokmärkta ämnen" my_posts: content: "Mina inlägg" + title: "Mina inlägg" + draft_count: + one: "%{count} utkast" + other: "%{count} utkast" admin_js: type_to_filter: "skriv för att filtrera..." admin: diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index 8c9c443ed1..83aa27c665 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -2254,6 +2254,11 @@ sw: unread_count: "%{count} haijasomwa" new_count: "Mada mpya %{count}" sections: + messages: + header_link_text: "Ujumbe" + tags: + header_link_title: "lebo zote" + header_link_text: "Lebo" categories: header_link_title: "kategoria zote" header_link_text: "Kategoria" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index 6bdeb110ed..63252917e3 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -1352,6 +1352,10 @@ te: unread_count: "%{count} చదవనవి" new_count: "%{count} కొత్త" sections: + messages: + header_link_text: "సందేశాలు" + tags: + header_link_text: "ట్యాగులు" categories: header_link_title: "అన్ని వర్గాలు" header_link_text: "వర్గాలు" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 93756725ed..9172c21d7e 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -2428,6 +2428,11 @@ th: unread_count: "%{count} ยังไม่อ่าน" new_count: "%{count} ใหม่" sections: + messages: + header_link_text: "ข้อความ" + tags: + header_link_title: "แท็กทั้งหมด" + header_link_text: "ป้าย" categories: header_link_title: "ทุกหมวดหมู่" header_link_text: "หมวดหมู่" diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml index 9ab910ae24..45e97f2055 100644 --- a/config/locales/client.tr_TR.yml +++ b/config/locales/client.tr_TR.yml @@ -3662,7 +3662,14 @@ tr_TR: unread_count: "%{count} okunmamış" new_count: "%{count} yeni" sections: + messages: + header_link_text: "İletiler" + tags: + no_tracked_tags: "Herhangi bir etiketi takip etmiyorsunuz." + header_link_title: "tüm etiketler" + header_link_text: "Etiketler" categories: + no_tracked_categories: "Herhangi bir kategoriyi takip etmiyorsunuz." header_link_title: "tüm kategoriler" header_link_text: "Kategoriler" topics: @@ -3673,6 +3680,7 @@ tr_TR: content: "Takipte" my_posts: content: "Gönderilerim" + title: "Gönderilerim" admin_js: type_to_filter: "filtrelemek için yaz..." admin: diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index e968041497..1594041c6b 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -4011,6 +4011,11 @@ uk: unread_count: "%{count} непрочитана" new_count: "%{count} нова" sections: + messages: + header_link_text: "Повідомлення" + tags: + header_link_title: "усі мітки" + header_link_text: "Теги" categories: header_link_title: "усі розділи" header_link_text: "Категорії" diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index d366b067b5..6fac9c7a5e 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -3636,6 +3636,11 @@ ur: unread_count: "%{count} بغیر پڑھا ہوا" new_count: "%{count} نیا" sections: + messages: + header_link_text: "پیغامات" + tags: + header_link_title: "تمام ٹیگز" + header_link_text: "ٹیگز" categories: header_link_title: "تمام زُمرَہ جات" header_link_text: "زُمرَہ جات" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index 14e5112fef..da96059d81 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -3457,6 +3457,11 @@ vi: unread_count: "%{count} chưa đọc" new_count: "%{count} mới" sections: + messages: + header_link_text: "Tin nhắn" + tags: + header_link_title: "tất cả thẻ" + header_link_text: "Thẻ" categories: header_link_title: "tất cả chuyên mục" header_link_text: "Thư mục" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index d188a7ea44..f07d5f307f 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -3549,7 +3549,16 @@ zh_CN: unread_count: "%{count} 未读" new_count: "%{count} 新" sections: + messages: + header_link_title: "个人消息" + header_link_text: "消息" + header_action_title: "创建个人消息" + tags: + no_tracked_tags: "你没有跟踪任何标签。" + header_link_title: "所有标签" + header_link_text: "标签" categories: + no_tracked_categories: "你没有跟踪任何类别。" header_link_title: "所有类别" header_link_text: "类别" topics: diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 062c27174c..1fbc1d5b58 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -2794,6 +2794,11 @@ zh_TW: unread_count: "%{count} 個未讀" new_count: "%{count} 近期" sections: + messages: + header_link_text: "訊息" + tags: + header_link_title: "所有標籤" + header_link_text: "標籤" categories: header_link_title: "所有分類" header_link_text: "分類" diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml index badee6dc42..73cac14561 100644 --- a/config/locales/server.ar.yml +++ b/config/locales/server.ar.yml @@ -1637,7 +1637,6 @@ ar: contact_email: "عنوان البريد الإلكتروني لجهة الاتصال الرئيسية المسؤولة عن هذا الموقع. يتم استخدامه للإشعارات المهمة ويتم عرضه أيضًا على صفحة /about للمسائل العاجلة." contact_url: "عنوان URL للتواصل لهذا الموقع. يتم عرضه على صفحة /about للمسائل العاجلة." crawl_images: "استعادة الصور من عناوين URL البعيدة لإدراج الأبعاد الصحيحة للطول والعرض" - download_remote_images_to_local: "يمكن تحويل الصور البعيدة إلى صور محلية عن طريق تنزيلها، وبذلك تضمن عدم تلف الصور." download_remote_images_threshold: "الحد الأدنى من مساحة القرص اللازمة لتنزيل الصور البعيدة محليًا (بالنسبة المئوية)" disabled_image_download_domains: "لن يتم تنزيل الصور البعيدة من هذه النطاقات أبدًا. قائمة مفصولة بشرائط عمودية." editing_grace_period: "لن يؤدي التعديل إلى إنشاء نسخة جديدة في سجل المنشورات لمدة (n) ثانية بعد النشر." diff --git a/config/locales/server.be.yml b/config/locales/server.be.yml index 107b4f5a70..446726b508 100644 --- a/config/locales/server.be.yml +++ b/config/locales/server.be.yml @@ -902,7 +902,6 @@ be: site_description: "Апісаць гэты сайт у адным сказе, як ён выкарыстоўваецца ў мета-тэг апісання." short_site_description: "Кароткае апісанне, якое выкарыстоўваецца ў назве пазнакі на галоўнай старонцы." crawl_images: "Атрыманне малюнкаў з аддаленых URL-адрасоў, каб ўставіць правільныя памеры шырыні і вышыні." - download_remote_images_to_local: "Пераўтварэнне аддаленых малюнкаў на лакальныя малюнка, загрузіўшы іх; гэта прадухіляе зламаныя выявы." download_remote_images_threshold: "Мінімальныя дыскавая прастора, неабходнае для загрузкі аддаленых малюнкаў лакальна (у працэнтах)" disabled_image_download_domains: "Выдаленыя малюнка не будуць загружацца з гэтых даменаў. Pipe коскамі спіс." editing_grace_period_max_diff_high_trust: "Максімальную колькасць змяненняў сімвалаў, дазволеных для рэдагавання ільготнага перыяду, калі больш змененым крамы іншага паста перагляду (давер ўзроўню 2 і вышэй)" diff --git a/config/locales/server.bg.yml b/config/locales/server.bg.yml index d37fc3a59d..f966d24edf 100644 --- a/config/locales/server.bg.yml +++ b/config/locales/server.bg.yml @@ -675,7 +675,6 @@ bg: title: "Наименованието на този сайт , както е използвано в заглавния маркер." site_description: "Опишете този сайт с едно изречение като използвате мета описателен таг." crawl_images: "Възстановете изображенията от отдалечения URLs, за да въведете размери с правилната дължина и ширина." - download_remote_images_to_local: "Конвертирайте отдалечените изображения в местни изображения като ги свалите; това ще предотврати повредата им." download_remote_images_threshold: "Минимум необходимо дисково пространство за изтеглянето на отдалечените изображения ( в проценти)" disabled_image_download_domains: "Отдалечените изображения никога няма да бъдат изтеглени от тези домейни. Разделен със знака \"|\" списък." editing_grace_period: "За(n) секунди след публикуването , системата няма да запише нова версия в историята на публикациите." diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml index 1e1081c2e7..44978083d0 100644 --- a/config/locales/server.bs_BA.yml +++ b/config/locales/server.bs_BA.yml @@ -619,7 +619,6 @@ bs_BA: title: "Brief title of this site, used in the title tag." site_description: "Describe this site in one sentence, used in the meta description tag." crawl_images: "Retrieve images from remote URLs to insert the correct width and height dimensions." - download_remote_images_to_local: "Convert remote images to local images by downloading them; this prevents broken images." download_remote_images_threshold: "Minimum disk space necessary to download remote images locally (in percent)" disabled_image_download_domains: "Remote images will never be downloaded from these domains. Pipe-delimited list." editing_grace_period: "For (n) seconds after posting, editing will not create a new version in the post history." diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml index 694a4f9eda..bb8d9fb754 100644 --- a/config/locales/server.ca.yml +++ b/config/locales/server.ca.yml @@ -1209,7 +1209,6 @@ ca: site_description: "Descriviu el lloc web amb una frase per a la metaetiqueta de descripció." short_site_description: "Descripció breu per a l'etiqueta de títol de la pàgina d'inici." crawl_images: "Recupera imatges d'adreces URL remotes per a inserir-hi les dimensions correctes d'amplada i alçada." - download_remote_images_to_local: "Converteix les imatges remotes en imatges locals en descarregar-les. Això evita les imatges trencades." download_remote_images_threshold: "L'espai mínim necessari en el disc per a descarregar imatges remotes en local (percentatge)" disabled_image_download_domains: "Les imatges remotes no es descarregaran mai des d'aquests dominis. Llista delimitada amb barres verticals." editing_grace_period: "Durant (n) segons després de publicar, l'edició no crearà una nova versió en l'historial de publicació." diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml index a2114a558a..41393cfed3 100644 --- a/config/locales/server.da.yml +++ b/config/locales/server.da.yml @@ -1349,7 +1349,6 @@ da: site_description: "Beskriv denne side i én sætning, som anvendt i meta beskrivelsen." short_site_description: "Kort beskrivelse, som bruges i titelmærket på websiden." crawl_images: "Hent billeder fra eksterne URLs for at indsætte de korrekte bredde og højde dimensioner." - download_remote_images_to_local: "Konverter eksterne billeder til lokale billeder ved at downloade dem; dette forebygger at billeder ikke forsvinder." download_remote_images_threshold: "Minimum hukommelse påkrævet på drev for at downloade billeder lokalt (i procent)" disabled_image_download_domains: "Eksterne billeder vil ikke blive hentet fra disse domæner. Afgrænset liste." editing_grace_period: "For (n) sekunder efter postering, vil redigering ikke skabe et nyt indlæg." diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 5b1c39e197..c261c44f6a 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -669,6 +669,11 @@ de: post: image_placeholder: broken: "Dieses Bild ist beschädigt" + blocked_hotlinked_title: "Das Bild befindet sich auf einer anderen Website. Anklicken, um es in einem neuen Tab zu öffnen." + blocked_hotlinked: "Externes Bild" + media_placeholder: + blocked_hotlinked_title: "Die Medien werden auf einer anderen Website gehostet. Anklicken, um in einem neuen Tab zu öffnen." + blocked_hotlinked: "Externe Medien" hidden_bidi_character: "Bidirektionale Zeichen können die Reihenfolge ändern, in der der Text gerendert wird. Dies könnte dazu genutzt werden, bösartigen Code zu verschleiern." has_likes: one: "%{count} „Gefällt mir“" @@ -1430,9 +1435,11 @@ de: contact_email: "E-Mail-Adresse einer verantwortlichen Person für diese Website. Wird verwendet für kritische Benachrichtigungen und unter /about für dringende Anfragen angezeigt." contact_url: "Kontakt-URL für diese Website. Wird auf der Seite /about für dringende Anliegen angezeigt." crawl_images: "Lade Bilder von fremden URLs herunter, um ihre Höhe und Breite zu bestimmen." - download_remote_images_to_local: "Lade eine Kopie von extern gehosteten Bildern herunter und ersetze Links in Beiträgen entsprechend; dies verhindert defekte Bilder." + download_remote_images_to_local: "Konvertiere entfernte (mit Hotlinks versehene) Bilder in lokale Bilder, indem du sie herunterlädst; so bleibt der Inhalt erhalten, auch wenn die Bilder in Zukunft von der entfernten Seite entfernt werden." download_remote_images_threshold: "Minimal benötigter freier Festplattenspeicher, um externe Bilder lokal herunterzuladen (in Prozent)" disabled_image_download_domains: "Durch senkrechte Striche („|“) getrennte Liste von Domains, von denen verlinkte Bilder niemals heruntergeladen werden sollen." + block_hotlinked_media: "Verhindert, dass Benutzer entfernte (Hotlinked) Medien in ihren Beiträgen einführen. Entfernte Medien, die nicht über 'download_remote_images_to_local' heruntergeladen werden, werden durch einen Platzhalter-Link ersetzt." + block_hotlinked_media_exceptions: "Eine Liste von Basis-URLs, die von der Einstellung block_hotlinked_media ausgenommen sind. Füge das Protokoll hinzu (z.B. https://example.com)." editing_grace_period: "Für (n) Sekunden nach dem Schreiben wird durch die Bearbeitung keine neue Version im Beitragsverlauf erstellt." editing_grace_period_max_diff: "Maximale Anzahl von Zeichenänderungen, die während der Bearbeitungsfrist erlaubt sind. Bei größeren Änderungen wird eine neue Beitragsversion gespeichert (Vertrauensstufe 0 und 1)" editing_grace_period_max_diff_high_trust: "Maximale Anzahl von Zeichenänderungen, die während der Bearbeitungsfrist erlaubt sind. Bei größeren Änderungen wird eine neue Beitragsversion gespeichert (Vertrauensstufe 2 und höher)" diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml index d445abb9f4..4fa24c293f 100644 --- a/config/locales/server.el.yml +++ b/config/locales/server.el.yml @@ -932,7 +932,6 @@ el: title: "Το όνομα αυτής της ιστοσελίδας, όπως χρησιμοποιείται στην ετικέτα τίτλου." site_description: "Περιέγραψε αυτή την ιστοσελίδα με μία πρόταση, όπως χρησιμοποιείται στην ετικέτα περιγραφής." crawl_images: "Ανάκτηση εικόνων από απομακρυσμένα URLs για να προστεθούν οι σωστές διαστάσεις πλάτους και ύψους." - download_remote_images_to_local: "Μετέτρεψε τις απομονωμένες εικόνες σε τοπικές εικόνες κατεβάζωντάς τες, αυτό αποτρέπει τις 'σπασμένες' εικόνες." download_remote_images_threshold: "Ελάχιστη απαιτούμενη χωρητικότητα δίσκου για το κατέβασμα απομακρυσμένων εικόνων τοπικά (σε ποσοστό)" disabled_image_download_domains: "Οι απομακρυσμένες εικόνες δεν θα κατέβουν ποτέ από αυτούς τους τομείς. Λίστα pipe-delimited" editing_grace_period: "Για (n) δευτερόλεπτα μετά από την ανάρτηση, η επεξεργασία δεν θα δημιουργήσει νεα έκδoση στο ιστορικό της ανάρτησης. " diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml index a71cf752d2..6069f18fcc 100644 --- a/config/locales/server.es.yml +++ b/config/locales/server.es.yml @@ -668,6 +668,11 @@ es: post: image_placeholder: broken: "Esta imagen está rota" + blocked_hotlinked_title: "Imagen alojada en otro sitio. Haz clic para abrirla en una nueva pestaña." + blocked_hotlinked: "Imagen externa" + media_placeholder: + blocked_hotlinked_title: "Medio alojado en otro sitio. Haz clic para abrir en una nueva pestaña." + blocked_hotlinked: "Medio externo" hidden_bidi_character: "Los caracteres bidireccionales pueden cambiar el orden en el que se muestra el texto. Esto podría usarse para ocultar código malintencionado." has_likes: one: "%{count} me gusta" @@ -1429,9 +1434,11 @@ es: contact_email: "Dirección de correo electrónico del contacto clave responsable de este sitio. Se utiliza para notificaciones críticas, y también se muestra en /about para asuntos urgentes." contact_url: "Dirección URL de contacto para el sitio. Se mostrará en la página de /about (acerca de) para temas urgentes." crawl_images: "Recuperar imágenes desde URLs remotas para insertarlas con las dimensiones correctas de ancho y de largo." - download_remote_images_to_local: "Convertir imágenes remotas a imágenes locales descargándolas; esto previene imágenes rotas." + download_remote_images_to_local: "Convertir imágenes remotas (hotlinked) a imágenes locales descargándolas. Esto preserva el contenido incluso si las imágenes son eliminadas de su sitio remoto original en el futuro." download_remote_images_threshold: "Espacio mínimo en el disco necesario para descargar imágenes remotas de forma local (porcentaje)" disabled_image_download_domains: "Las imágenes remotas que provengan de estos dominios no serán descargadas. Lista delimitada por barras." + block_hotlinked_media: "Evitar que los usuarios incluyan archivos multimedia (hotlinking) en sus publicaciones. Los archivos multimedia no descargados según el ajuste «download_remote_images_to_local» serán reemplazados por un enlace." + block_hotlinked_media_exceptions: "Lista de comienzos de URL que están exentas del ajuste block_hotlinked_media. Incluye el protocolo (ej.: https://ejemplo.com)" editing_grace_period: "Durante (n) segundos después de que se publique algún mensaje, se puede editar la publicación sin crear una nueva versión en el historial." editing_grace_period_max_diff: "Número máximo de cambios de caracteres permitidos en el período de gracia de edición, si hay más cambios, almacenar otra revisión de publicación (nivel de confianza 0 y 1)" editing_grace_period_max_diff_high_trust: "Número máximo de cambios de caracteres permitidos en el período de gracia de edición, si hay más cambios, almacenar otra revisión de publicación (nivel de confianza 2 y superiores)" @@ -4724,6 +4731,9 @@ es: post_raw: "Siguiendo con el tema %{parent_url}.\n\nPartes anteriores:\n\n%{previous_topics}" small_action_post_raw: "Este tema continúa: %{new_title}." fallback_username: "usuario" + user_status: + errors: + ends_at_should_be_greater_than_set_at: "ends_at debe ser mayor que set_at" activemodel: errors: <<: *errors diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml index 3703fbc81e..312862be33 100644 --- a/config/locales/server.fa_IR.yml +++ b/config/locales/server.fa_IR.yml @@ -401,6 +401,11 @@ fa_IR: post: image_placeholder: broken: "عکس خراب شده است." + blocked_hotlinked_title: "تصویر در سایت دیگری میزبانی شده است، کلیک کنید تا در یک زبانه جدید باز شود." + blocked_hotlinked: "تصویر خارجی" + media_placeholder: + blocked_hotlinked_title: "این رسانه در سایت دیگری میزبانی شده است، کلیک کنید تا در یک زبانه جدید باز شود." + blocked_hotlinked: "رسانه خارجی" hidden_bidi_character: "کارکترهای دو جهته می‌توانند، ترتیب رندر شدن متن را تغییر دهند. این می‌تواند برای پنهان کردن کدهای مخرب استفاده شود." has_likes: one: "%{count} پسندیده" @@ -902,7 +907,6 @@ fa_IR: title: "نام این وب سایت، استفاده شده در تگ title" site_description: "سایت را در یک جمله توصیف کنید،‌ همانطور که در تگ meta توضیح داده شده بود" crawl_images: "بازیابی تصاویر از URL‌های خارجی برای وارد کردن طول و عرض صحیح. " - download_remote_images_to_local: "تبدیل عکس‌های خارجی به عکس های محلی با دانلود کردن آنها، این مانع از شکستن عکس‌ها می شود." download_remote_images_threshold: "حداقل فضای دیسک مورد نیاز برای دانلود عکس‌های خارجی به هاست (به درصد)" disabled_image_download_domains: "عکس‌های خارجی هرگز از این دامنه‌ها دانلود نخواهند شد. لیست Pipe-delimited" editing_grace_period: "ویرایش کردن تا (n) ثانیه بعد از ارسال فرسته، نسخه جدیدی در تاریخچه ایجاد نمی‌کند." diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml index 7fc0e7d53e..97a22a31a1 100644 --- a/config/locales/server.fi.yml +++ b/config/locales/server.fi.yml @@ -1388,7 +1388,6 @@ fi: contact_email: "Sivustosta vastaavan henkilön sähköpostiosoite. Siihen lähetetään kriittiset ilmoitukset, ja se näkyy myös /about-sivulla kiireellisiä yhteydenottoja varten." contact_url: "URL-osoite sivuston yhteydenottoja varten. Näytetään Tietoja-sivulla kiireellisiä yhteydenottoja varten." crawl_images: "Lataa linkatut kuvat kuvan mittojen määrittamiseksi." - download_remote_images_to_local: "Muunna linkatut kuvat liitetiedostoiksi lataamalla ne; tämä estää kuvien rikkoontumisen vanhentuneiden linkkien vuoksi." download_remote_images_threshold: "Vähin vapaa tila, jotta linkatut kuvat ladataan paikallisesti (prosenteissa)" disabled_image_download_domains: "Linkattuja kuvia ei koskaan ladata näistä verkkotunnuksista. Pystyviivalla eroteltu luettelo." editing_grace_period: "Viestin muokkaaminen (n) sekunnin sisällä sen lähettämisestä ei luo viestistä uutta versiota viestin historiaan." diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml index b73b0c4923..a49bc0468b 100644 --- a/config/locales/server.fr.yml +++ b/config/locales/server.fr.yml @@ -1430,7 +1430,6 @@ fr: contact_email: "Adresse courriel de la personne responsable de ce site. Elle est utilisée pour les notifications critiques et également affichée sur la page /about pour les questions urgentes." contact_url: "URL de contact pour ce site. Elle est affichée sur la page À propos et les utilisateurs sont invités à l'utiliser en cas de problème urgent." crawl_images: "Récupérer les images provenant de sources tierces pour y insérer les dimensions correctes (hauteur et largeur)." - download_remote_images_to_local: "Transformer les images distantes en images locales en les téléchargeant ; cela permet d'éviter les liens morts." download_remote_images_threshold: "Quantité minimale d'espace disque requise pour télécharger localement des images distantes (en pourcentage)" disabled_image_download_domains: "Les images distantes de ces domaines ne seront jamais téléchargées. Liste délimitée par des lignes verticales." editing_grace_period: "Pendant (n) secondes après sa publication, la modification du message ne créera pas de nouvelle version dans l'historique des messages." diff --git a/config/locales/server.gl.yml b/config/locales/server.gl.yml index c43445cc10..df04713630 100644 --- a/config/locales/server.gl.yml +++ b/config/locales/server.gl.yml @@ -1344,7 +1344,6 @@ gl: site_description: "Describa o sitio nunha única frase, utilizada na etiqueta metadescrición." short_site_description: "Descrición curta, utilizada como o título da etiqueta na páxina principal." crawl_images: "Recuperar imaxes desde URL remotos para inserilas coas dimensións correctas de largura e altura." - download_remote_images_to_local: "Converter imaxes remotas en locais ao descargalas; isto prevén que haxa imaxes rotas." download_remote_images_threshold: "Espazo mínimo que cómpre no disco para descargar imaxes remotas de forma local (en porcentaxe)" disabled_image_download_domains: "As imaxes remotas procedentes destes dominios non serán descargadas. Listaxe delimitada por barras verticais." editing_grace_period: "Durante (n) segundos despois de publicar, pódese editar sen chegar a crear unha nova versión no historial." diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml index e20a6ab3b6..1f1b984d58 100644 --- a/config/locales/server.he.yml +++ b/config/locales/server.he.yml @@ -586,16 +586,16 @@ he: user: attributes: password: - common: "אחת מתוך 10000 הססמאות הנפוצות ביותר. נא להשתמש בססמה בטוחה יותר." - same_as_username: "זהה לשם המשתמש שלך. נא להשתמש בססמה בטוחה יותר." - same_as_email: "זהה לכתובת הדוא״ל שלך. נא להשתמש בססמה בטוחה יותר." - same_as_current: "זהה לססמה הנוכחית שלך." + common: "אחת מתוך 10000 הסיסמאות הנפוצות ביותר. נא להשתמש בסיסמה בטוחה יותר." + same_as_username: "זהה לשם המשתמש שלך. נא להשתמש בסיסמה בטוחה יותר." + same_as_email: "זהה לכתובת הדוא״ל שלך. נא להשתמש בסיסמה בטוחה יותר." + same_as_current: "זהה לסיסמה הנוכחית שלך." same_as_name: "זהה לשם שלך." - unique_characters: "מכיל יותר מידי תווים שחוזרים על עצמם. נא להשתמש בססמה בטוחה יותר." + unique_characters: "מכיל יותר מידי תווים שחוזרים על עצמם. נא להשתמש בסיסמה בטוחה יותר." username: - same_as_password: "זהה לססמה שלך." + same_as_password: "זהה לסיסמה שלך." name: - same_as_password: "זהה לססמה שלך." + same_as_password: "זהה לסיסמה שלך." ip_address: signup_not_allowed: "אסור להירשם מהחשבון הזה." user_profile: @@ -700,6 +700,11 @@ he: post: image_placeholder: broken: "תמונה זו פגומה" + blocked_hotlinked_title: "התמונה מתארחת באתר אחר. לחיצה תפתח אותה בלשונית חדשה." + blocked_hotlinked: "תמונה חיצונית" + media_placeholder: + blocked_hotlinked_title: "המדיה מתארחת באתר אחר. לחיצה תפתח אותה בלשונית חדשה." + blocked_hotlinked: "מדיה חיצונית" hidden_bidi_character: "תווי דו־כיווניות יכולים לשנות את סדר עיבוד הטקסט. אפשר להשתמש בהם להסוואת קוד זדוני." has_likes: one: "לייק %{count}" @@ -855,16 +860,16 @@ he: many: "לפני %{count} שנים כמעט" other: "לפני %{count} שנים כמעט" password_reset: - no_token: "הקישור להחלפת הססמה ישן מדי, עמך הסליחה. נא לבחור בכפתור הכניסה ולהשתמש באפשרות ‚שכחתי את הססמה שלי’ כדי לקבל קישור חדש." - choose_new: "נא לבחור בססמה חדשה" - choose: "בחירת ססמה" - update: "עדכון ססמה" - save: "הגדרת ססמה" - title: "איפוס ססמה" - success: "החלפת את ססמתך בהצלחה וכעת נכנסת לאתר." - success_unapproved: "החלפת את ססמתך בהצלחה." + no_token: "הקישור להחלפת הסיסמה ישן מדי, עמך הסליחה. נא לבחור בכפתור הכניסה ולהשתמש באפשרות ‚שכחתי את הסיסמה שלי’ כדי לקבל קישור חדש.י" + choose_new: "נא לבחור בסיסמה חדשה" + choose: "בחירת סיסמה" + update: "עדכון סיסמה" + save: "הגדרת סיסמה" + title: "איפוס סיסמה" + success: "החלפת את הסיסמה בהצלחה וכעת נכנסת לאתר." + success_unapproved: "החלפת את הסיסמה בהצלחה." email_login: - invalid_token: "קישור הכניסה בדוא״ל ישן מדי, עמך הסליחה. יש לבחור בכפתור הכניסה ולהשתמש ב‚שכחתי את ססמתי’ כדי לקבל קישור חדש." + invalid_token: "קישור הכניסה בדוא״ל ישן מדי, עמך הסליחה. יש לבחור בכפתור הכניסה ולהשתמש ב‚שכחתי סיסמה’ כדי לקבל קישור חדש." title: "כניסה עם דוא״ל" user_auth_tokens: browser: @@ -1513,7 +1518,7 @@ he: contact_email: "כתובת דוא״ל של איש קשר מוביל שאחראי על האתר הזה. משמש להתראות קריטיות, וגם יוצג ב־‎/about לנושאים דחופים." contact_url: "כתובת יצירת הקשר של האתר הזה. מופיעה בעמוד ‎/about לנושאים דחופים." crawl_images: "לקבל תמונות מכתובות מרוחקות כדי לציין את ממדי הרוחב והגובה הנכונים." - download_remote_images_to_local: "המרת תמונות מרוחקות למקומיות על ידי הורדתן, פעולה זו מסייעת במניעת תמונות שבורות." + download_remote_images_to_local: "המרת תמונות מרוחקות (בקישור חם) למקומיות על ידי הורדתן. פעולה זו משמרת את התוכן אפילו אם התמונות נמחקות מהאתר המרוחק בעתיד." download_remote_images_threshold: "שטח האחסון המזערי (באחוזים) הנדרש להורדת תמונות כדי לאחסן אותן מקומית" disabled_image_download_domains: "תמונות מרחוק לעולם לא יורדו ממתחמים (domains) אלו. " editing_grace_period: "למשך (n) שניות לאחר הפרסום, עריכה לא תיצור גרסה חדשה בהיסטוריית הפוסט." @@ -2404,14 +2409,14 @@ he: security_key_invalid: "אירעה שגיאה באימות מפתח האבטחה." not_approved: "חשבונך טרם אושר. תישלח אליך הודעה בדוא״ל כשיהיה מוכן לכניסה." incorrect_username_email_or_password: "שם משתמש, דואר אלקטרוני או סיסמה לא נכונים" - incorrect_password: "ססמה שגויה" + incorrect_password: "סיסמה שגויה" wait_approval: "תודה על שנרשמת. אנחנו ניידע אותך כשהחשבון שלך יאושר." active: "החשבון שלך הופעל ומוכן לשימוש." activate_email: "

כמעט סיימת! שלחנו הודעת הפעלה בדוא״ל אל %{email}. נא לעקוב אחר ההנחיות המופיעות בהודעה כדי להפעיל את החשבון שלך.

אם ההודעה לא הגיעה אליך כדאי לבדוק בתיקיית הספאם.

" not_activated: "אין לך אפשרות להיכנס עדיין. שלחנו אליך הודעת הפעלה בדוא״ל. נא לעקוב אחר ההוראות שבהודעה כדי להפעיל את החשבון שלך." not_allowed_from_ip_address: "אין לך אפשרות להיכנס בתור %{username} מכתובת IP זו." admin_not_allowed_from_ip_address: "אין לך אפשרות להיכנס כהנהלת המערכת מכתובת IP זו." - reset_not_allowed_from_ip_address: "אי אפשר לבקש איפוס ססמה מכתובת ה־IP הזאת." + reset_not_allowed_from_ip_address: "אי אפשר לבקש איפוס סיסמה מכתובת ה־IP הזאת." suspended: "אין לך אפשרות להיכנס עד %{date}." suspended_with_reason: "חשבון הושעה עד %{date}: %{reason}" suspended_with_reason_forever: "חשבון מושעה: %{reason}" @@ -3750,7 +3755,7 @@ he: אם זאת הייתה פעילות שלך, נהדר! לא צריך לעשות שום דבר. - אם לא מדובר בפעולות מטעמך, אנא [לסקור את ההפעלות הנוכחיות שלך](%{base_url}/my/preferences/account) לצד שיקול בכובד ראש להחלפת הססמה שלך. + אם לא מדובר בפעולות מטעמך, אנא [לסקור את ההפעלות הנוכחיות שלך](%{base_url}/my/preferences/account) לצד שיקול בכובד ראש להחלפת הסיסמה שלך. post_approved: title: "הפוסט שלך אושר" subject_template: "[%{site_name}] הפוסט שלך אושר" diff --git a/config/locales/server.hy.yml b/config/locales/server.hy.yml index 437bb51200..c8a735cfd4 100644 --- a/config/locales/server.hy.yml +++ b/config/locales/server.hy.yml @@ -1084,7 +1084,6 @@ hy: site_description: "Նկարագրեք այս կայքը մեկ նախադասությամբ, ինչպես որ օգտագործված է մետա-նկարագրության թեգում:" short_site_description: "Կարճ նկարագրություն, ինչպես որ օգտագործված է գլխավոր էջի վերնագրի թեգում:" crawl_images: "Ստանալ նկարներ հեռակա URL-ներից՝ ճշգրիտ լայնության և երկարության չափումները մուտքագրելու համար:" - download_remote_images_to_local: "Դարձնել հեռակա նկարները տեղական՝ ներբեռնելով դրանք; սա կանխում է նկարների կորչելը: " download_remote_images_threshold: "Հեռակա նկարները տեղայնորեն ներբեռնելու համար դիսկի նվազագույն անհրաժեշտ տարածքը (տոկոսներով)" disabled_image_download_domains: "Հեռակա նկարները երբեք չեն ներբեռնվի այս դոմեններից: Խողովակի սիմվոլով( | ) բաժանված ցանկ:" editing_grace_period: "Գրառումից հետո (n) վայրկյանի ընթացքում խմբագրումը գրառման պատմության մեջ նոր տարբերակ չի ստեղծի:" diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml index 6aebb7b1c4..b92fd42767 100644 --- a/config/locales/server.it.yml +++ b/config/locales/server.it.yml @@ -667,6 +667,11 @@ it: post: image_placeholder: broken: "Questa immagine è assente" + blocked_hotlinked_title: "Immagine ospitata su un altro sito. Clicca per aprire in una nuova scheda." + blocked_hotlinked: "Immagine Esterna" + media_placeholder: + blocked_hotlinked_title: "Contenuti ospitati su un altro sito. Clicca per aprire in una nuova scheda." + blocked_hotlinked: "Contenuti Esterni" hidden_bidi_character: "I caratteri bidirezionali possono cambiare l'ordine di visualizzazione del testo. Ciò potrebbe essere utilizzato per nascondere del codice maligno." has_likes: one: "%{count} Mi Piace" @@ -1428,9 +1433,10 @@ it: contact_email: "Indirizzo email di contatto del principale responsabile di questo sito. Utilizzato per notifiche cruciali, nonché visualizzato sul modulo di contatto /about per questioni urgenti." contact_url: "URL di contatto per questo sito. Visualizzato nella pagina /Informazioni per contatti urgenti." crawl_images: "Recupera le immagini dagli URL remoti per inserire le dimensioni corrette di ampiezza e altezza nel tag." - download_remote_images_to_local: "Scarica localmente le immagini remote; ciò permettei di evitare immagini assenti." + download_remote_images_to_local: "Converti immagini remote (linkate) in immagini locali scaricandole; ciò preserva il contenuto anche se le immagini vengono rimosse dal sito remoto in futuro." download_remote_images_threshold: "Minimo spazio su disco necessario per scaricare le immagini remote in locale (in percentuale)" disabled_image_download_domains: "Le immagini non verranno mai scaricate dai seguenti domini. Elenco delimitato dal carattere pipe |" + block_hotlinked_media_exceptions: "Un elenco di URL di base esenti dall'impostazione block_hotlinked_media. Includere il protocollo (ad es. https://example.com)." editing_grace_period: "Per (n) secondi dopo la creazione del messaggio, le modifiche non saranno tracciate nella cronologia." editing_grace_period_max_diff: "Numero massimo di modifiche ai caratteri consentite durante la modifica nel periodo di tolleranza, se più modifiche memorizzano un'altra revisione del post (Livello attendibilità 0 e 1)" editing_grace_period_max_diff_high_trust: "Numero massimo di modifiche ai caratteri consentite durante la modifica nel periodo di tolleranza, se più modifiche memorizzano un'altra revisione del post (Livello attendibilità 2 e successivi)" diff --git a/config/locales/server.ja.yml b/config/locales/server.ja.yml index 3ec090dabb..d0ec994bc8 100644 --- a/config/locales/server.ja.yml +++ b/config/locales/server.ja.yml @@ -1338,7 +1338,6 @@ ja: contact_email: "このサイトの責任者の主要連絡先のメールアドレス。重要な通知に使用され、緊急事項の /about にも表示されます。" contact_url: "このサイトの連絡先 URL。緊急事項の /about ページに表示されます。" crawl_images: "リモート URL から画像を取得して、正しい幅と高さの寸法を挿入する。" - download_remote_images_to_local: "リモート画像をダウンロードしてローカル画像に変換する。画像の破損を防止できます。" download_remote_images_threshold: "リモート画像をダウンロードするために必要なディスクの最低空き容量 (パーセント表示)" disabled_image_download_domains: "これらのドメインのリモート画像をダウンロードしない (パイプ区切りのリスト)。" editing_grace_period: "投稿から (n) 秒間は、編集しても投稿履歴に新しいバージョンを作成しない。" diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml index b57818cf22..0f04c8cc80 100644 --- a/config/locales/server.ko.yml +++ b/config/locales/server.ko.yml @@ -1358,7 +1358,6 @@ ko: contact_email: "사이트를 담당자 이메일 주소입니다. 긴급한 문제에 대한 문의 뿐만 아니라 중요한 알림에 사용됩니다." contact_url: "이 사이트의 연락처 URL입니다. 긴급한 사항의 문의는 /about 페이지에서 확인하세요." crawl_images: "원격 URL에서 이미지를 가져와 올바른 너비 및 높이 치수를 삽입합니다." - download_remote_images_to_local: "원격 이미지를 다운로드하여 로컬 이미지로 변환합니다. 이렇게 하면 이미지가 손상되지 않습니다." download_remote_images_threshold: "원격의 이미지를 로컬에 다운받기 위한 최소 디스크 공간(%)" disabled_image_download_domains: "이 도메인의 원격 이미지는 다운로드 하지 않습니다. 목록은 ` | `를 사용해 구분하세요." editing_grace_period: "글 작성 후, 편집해도 히스토리에 남기지 않는 시간 (단위: 초)" diff --git a/config/locales/server.lt.yml b/config/locales/server.lt.yml index 3b5907357b..19473748e0 100644 --- a/config/locales/server.lt.yml +++ b/config/locales/server.lt.yml @@ -1259,7 +1259,6 @@ lt: site_description: "Apibūdinkite šią svetainę vienu sakiniu, kaip naudojama meta aprašymo žymoje." short_site_description: "Trumpas aprašymas, naudojamas pagrindinio puslapio žymoje." crawl_images: "Gaukite vaizdus iš nuotolinių URL, kad įterptumėte teisingus pločio ir aukščio matmenis." - download_remote_images_to_local: "Konvertuoti nuotolinius vaizdus į vietinius vaizdus juos atsisiunčiant; tai apsaugo nuo sugadintų vaizdų." download_remote_images_threshold: "Minimali vieta diske, reikalinga nuotoliniams vaizdams atsisiųsti vietoje (procentais)" editing_grace_period_max_diff_high_trust: "Didžiausias leidžiamas simbolių pakeitimų skaičius per redagavimo atidėjimo laikotarpį, jei pakeitimų daugiau, išsaugokite kitą įrašo versiją (2 pasitikėjimo lygis ir aukštesnis)" staff_edit_locks_post: "Pranešimai bus užrakinti nuo redagavimo, jei juos redaguos darbuotojai" diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml index 4954260f8b..2a849ff876 100644 --- a/config/locales/server.nb_NO.yml +++ b/config/locales/server.nb_NO.yml @@ -957,7 +957,6 @@ nb_NO: title: "Navnet på denne siden, slik du vil ha det i title-taggen." site_description: "Beskriv siden med en setning, vil brukes i beskrivelses-merkelappen." crawl_images: "Hent bilder fra ekstern URL for å sette inn korrekt høyde og bredde." - download_remote_images_to_local: "Konverter eksterne bilder ved å laste de ned lokalt, dette forhindrer ødelagte bilder." download_remote_images_threshold: "Minimum ledig diskplass for å kunne laste ned eksterne bilder (i prosent)" disabled_image_download_domains: "Eksterne bilder vil aldri bli lastet ned fra disse domenene (pipe-separert liste)." editing_grace_period: "(n) sekunder etter publisering vil redigering ikke opprette en ny versjon i redigeringsloggen." diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml index d534e086ff..dd15b1430b 100644 --- a/config/locales/server.nl.yml +++ b/config/locales/server.nl.yml @@ -1299,7 +1299,6 @@ nl: site_description: "Beschrijf deze website in één zin, zoals gebruikt in de meta-omschrijvingstag." short_site_description: "Korte beschrijving, zoals gebruikt in de titeltag op de startpagina." crawl_images: "Afbeeldingen van externe URL's ophalen om de juiste breedte- en hoogteafmetingen in te voegen" - download_remote_images_to_local: "Externe afbeeldingen naar lokale afbeeldingen converteren door ze te downloaden; dit voorkomt beschadigde afbeeldingen." download_remote_images_threshold: "Minimaal vereiste schijfruimte om externe afbeeldingen lokaal te downloaden (percentage)" disabled_image_download_domains: "Externe afbeeldingen zullen nooit vanaf deze domeinen worden gedownload. Pipe-gescheiden lijst." editing_grace_period: "De eerste (n) seconden na plaatsing van een bericht leidt bewerking ervan niet tot een nieuwe versie in de berichtgeschiedenis." diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml index ed21c7964c..34c9f35dde 100644 --- a/config/locales/server.pl_PL.yml +++ b/config/locales/server.pl_PL.yml @@ -1526,7 +1526,6 @@ pl_PL: contact_email: "Adres e-mail do kluczowego kontaktu do osoby odpowiedzialnej za tę witrynę. Używany do krytycznych powiadomień, a także wyświetlany na /about w pilnych sprawach." contact_url: "Adres URL kontaktu dla tej witryny. Wyświetlany na stronie /about dla pilnych spraw." crawl_images: "Pobieraj grafiki ze zdalnych URLi aby ustawić poprawną wysokość i szerokość w tagu img." - download_remote_images_to_local: "Pobieraj zdalne grafiki i twórz ich lokalne kopie aby zapobiegać uszkodzonym/brakującym obrazkom na stronach." download_remote_images_threshold: "Minimalna ilość wolnego miejsca na dysku wymagana przez funkcję pobierania zdalnych grafik (w procentach)" disabled_image_download_domains: "Zdalne grafiki z tych domen nie będą pobierane." editing_grace_period: "Przez (n) sekund po utworzeniu nowego wpisu jego kolejne edycje nie będą tworzyć nowych wersji w historii wpisu." @@ -1563,11 +1562,11 @@ pl_PL: logo_small_dark: "Ciemna alternatywa dla ustawienia witryny 'logo small'." mobile_logo_dark: "Ciemna alternatywa dla ustawienia witryny 'mobile logo'." large_icon: "Obraz używany jako podstawa dla innych ikon metadanych. Idealnie powinna być większa niż 512 x 512. Jeśli pozostanie puste, zostanie użyte logo_small." - manifest_icon: "Obraz używany jako logo / obraz powitalny na Androidzie. Zostanie automatycznie zmieniony na 512 × 512. Jeśli pozostanie puste, zostanie użyty duży_plik." + manifest_icon: "Obraz używany jako logo / obraz powitalny na Androidzie. Zostanie automatycznie przeskalowany do rozmiaru 512 × 512. Jeśli pozostanie puste, zostanie użyty large_icon." manifest_screenshots: "Zrzuty ekranu, które pokazują możliwości i funkcjonalność Twojej instancji na stronie instalacyjnej. Wszystkie obrazy powinny być przesłane lokalnie i o tych samych wymiarach." favicon: "Favicona dla Twojej strony, zobacz https://en.wikipedia.org/wiki/Favicon. Aby działała poprawnie poprzez CDN, musi to być w formacie png. Zostanie przeskalowana do 32x32. Jeśli pozostanie pusta, zostanie użyte large_icon." - apple_touch_icon: "Ikona używana dla urządzeń Apple Touch. Zostanie automatycznie zmieniony na 180x180. Jeśli pozostanie puste, zostanie użyty duży_plik." - opengraph_image: "Domyślny obraz opengraph, używany, gdy strona nie ma innego odpowiedniego obrazu. Jeśli pozostanie puste, zostanie użyty duży_plik" + apple_touch_icon: "Ikona używana dla urządzeń Apple Touch. Zostanie automatycznie przeskalowany do rozmiaru 180x180. Jeśli pozostanie puste, zostanie użyty large_icon." + opengraph_image: "Domyślny obraz opengraph, używany, gdy strona nie ma innego odpowiedniego obrazu. Jeśli pozostanie puste, zostanie użyty large_icon" notification_email: "Adres z którego wysyłane będą wszystkie istotne emaile systemowe.\nKonieczna jest poprawna konfiguracja rekordów SPF, DKIM oraz zwrotnego PTR użytej domeny." email_custom_headers: "A pipe-delimited list of custom email headers" email_subject: "Konfigurowalny format tematyczny dla standardowych e-maili. Zobacz https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801" diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index a0121e681f..4501956181 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -986,7 +986,6 @@ pt: title: "O nome deste sítio, tal como usado na etiqueta do título." site_description: "Descrever este sítio em uma frase, tal como usado na etiqueta de descrição meta." crawl_images: "Recuperar imagens de URLs remotos para inserir o comprimento e largura corretos." - download_remote_images_to_local: "Converta as imagens remotas em imagens locais transferindo-as; isto evita imagens corrompidas." download_remote_images_threshold: "Espaço mínimo necessário em disco para transferir as imagens remotas localmente (em percentagem)" disabled_image_download_domains: "As imagens remotas nunca serão transferidas destes domínios. Lista delimitada por canal." editing_grace_period: "Por (n) segundos após publicar, a edição não irá criar uma nova versão no histórico da mensagem." diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml index 6b0477f9cd..ba413b951a 100644 --- a/config/locales/server.pt_BR.yml +++ b/config/locales/server.pt_BR.yml @@ -1413,7 +1413,6 @@ pt_BR: contact_email: "Endereço de e-mail do contato principal responsável por este site. É usado para notificações críticas e também em /about para assuntos urgentes." contact_url: "URL de contato deste site. Exibida na página /about para assuntos urgentes." crawl_images: "Recupere imagens de URLs remotas para inserir as dimensões corretas de largura e altura." - download_remote_images_to_local: "Transferira para converter imagens remotas em imagens locais. Isso evita imagens quebradas." download_remote_images_threshold: "Espaço mínimo necessário em disco para baixar em modo local imagens remotas (em %)" disabled_image_download_domains: "Imagens remotas hospedadas nestes domínios nunca serão baixadas. Lista delimitada por barras verticais." editing_grace_period: "Durante (n) segundos após a publicação, as edições não criarão uma nova versão no histórico da postagem." diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml index c0df4c02ee..c739737f5a 100644 --- a/config/locales/server.ro.yml +++ b/config/locales/server.ro.yml @@ -819,7 +819,6 @@ ro: title: "Titlul scurt al site-ului, folosit în tag-ul title." site_description: "Descrie acest site într-o singură frază, așa cum se folosește în tagul meta-descriere." crawl_images: "Recuperează imaginile din URL-uri la distanță pentru a insera dimensiunile corecte de înălțime și lățime." - download_remote_images_to_local: "Transformă imaginile la distanță în imagini locale descărcându-le; asta previne imaginile stricate." download_remote_images_threshold: "Spațiul minim necesar pentru descărcarea imaginilor la distanță pe local (în procente)" disabled_image_download_domains: "Imaginile la distanță nu vor mai fi descărcate de pe aceste domenii. Listă delimitată de simbolul | (pipe)." editing_grace_period: "Pentru (n) secunde după postare, editarea nu va crea o versiune nouă în istoricul postării." diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index 3bc309fe8a..1c2b52724f 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -723,6 +723,11 @@ ru: post: image_placeholder: broken: "Некорректный файл изображения" + blocked_hotlinked_title: "Изображение размещено на другом сайте. Нажмите, чтобы открыть его в новой вкладке." + blocked_hotlinked: "Внешнее изображение" + media_placeholder: + blocked_hotlinked_title: "Мультимедийный контент размещён на другом сайте. Нажмите, чтобы открыть его в новой вкладке." + blocked_hotlinked: "Внешниий мультимедийный контент" hidden_bidi_character: "Двунаправленные символы могут изменить порядок отображения текста. Это может быть использовано для сокрытия вредоносного кода." has_likes: one: "%{count} симпатия" @@ -1536,9 +1541,11 @@ ru: contact_email: "Email ключевого контактного лица, ответственного за этот сайт. Используется для критических уведомлений или срочных обращений и отображается на странице /about." contact_url: "Контактный URL этого сайта. Указан на странице /about для срочных вопросов." crawl_images: "Скачивать картинки с других ресурсов для автоматического определения их размеров." - download_remote_images_to_local: "Скачивать картинки, расположенные на других сайтах, и хранить их локально, что снижает риск их повреждения." + download_remote_images_to_local: "Конвертировать удалённые (hotlinked) изображения в локальные изображения; это позволит сохранить содержимое, даже если в будущем изображения будут удалены со стороннего сайта." download_remote_images_threshold: "Минимально доступное место на диске (в процентах), необходимое для хранения скачанных картинок." disabled_image_download_domains: "Список доменов, разделённых знаком \"|\", с которых не нужно скачивать картинки." + block_hotlinked_media: "Запретить пользователям размещать в своих сообщениях удалённый (hotlinked) мультимедийный контент. Подобный контент, если он не был загружен при включённом параметре 'download_remote_images_to_local', будет заменён ссылкой." + block_hotlinked_media_exceptions: "Список базовых URL-адресов, на которые не распространяется настройка параметра'block_hotlinked_media'. Вначале адреса укажите протокол (например, https://example.com)." editing_grace_period: "Количество секунд после отправки сообщения, в течении которых правка сообщения не будет записываться в историю правок (т.н. льготный период редактирования)." editing_grace_period_max_diff: "Максимальное количество изменённых символов, допустимое в льготном периоде редактирования. При превышении этого значения все изменения будут записаны в историю правок (уровень доверия 0 и 1)." editing_grace_period_max_diff_high_trust: "Максимальное количество изменённых символов, допустимое в льготном периоде редактирования. При превышении этого значения все изменения будут записаны в историю правок (уровень доверия 2 и выше)." diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml index 5340753914..c27e1428c9 100644 --- a/config/locales/server.sk.yml +++ b/config/locales/server.sk.yml @@ -795,7 +795,6 @@ sk: title: "Meno stránok, tak ako je použité v značke title." site_description: "Popíšte stránky v jednej vete, tak ako sa popisujú v tagu meta." crawl_images: "Načítať obrázky z URL pre vloženie správnej výšky a šírky." - download_remote_images_to_local: "Stiahnuť obrázky zo vzdialených zdrojov na lokálne. Zabráni poškodeniu obrázkov. " download_remote_images_threshold: "Minimálne miesto na lokálnom disku potrebné na stiahnutie obrázkov zo vzdialených zdrojov (v percentách)" disabled_image_download_domains: "Nikdy nesťahovať obrázky z týchto domén. Zoznam oddeleny pipou \"|\"." editing_grace_period: "Nevytvárať nové verzie editovaných príspevkov do (n) sekúnd po odoslaní príspevku." diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml index a767aec8f9..2ed830cc80 100644 --- a/config/locales/server.sl.yml +++ b/config/locales/server.sl.yml @@ -100,6 +100,7 @@ sl: other: ! "%{count} napak preprečuje tej %{model} da se shrani" site_settings: second_factor_cannot_be_enforced_with_discourse_connect_enabled: "2FA ne morete nastaviti kot zahtevano, če je omogočena možnost DiscourseConnect." + twitter_summary_large_image_no_svg: "Slike povzetka Twitterja, ki se uporabljajo za metapodatke twitter:image, ne morejo biti slike tipa .svg." invite: confirm_email: "

Ste skoraj na koncu! Poslali smo vam aktivacijsko e-sporočilo na vaš e-naslov. Sledite navodilom v e-sporočilu za aktivacijo računa.

Če e-sporočila ne prejmete, preverite predal z vsiljeno pošto.

" disabled_errors: @@ -339,6 +340,12 @@ sl: admin: "Admin" staff: "Osebje" post: + image_placeholder: + blocked_hotlinked_title: "Slika gostuje na drugem spletnem mestu. Kliknite, da se odpre v novem zavihku." + blocked_hotlinked: "Zunanja slika" + media_placeholder: + blocked_hotlinked_title: "Medijska vsebina gostuje na drugem spletnem mestu. Kliknite, da se odpre v novem zavihku." + blocked_hotlinked: "Zunanja medijska vsebina" has_likes: one: "%{count} všeček" two: "%{count} všečka" @@ -724,6 +731,10 @@ sl: site_settings: disabled: "onemogočeno" allow_uncategorized_topics: "Dovoli ustvarjanje tem brez kategorije. OPOZORILO: Če obstajajo nerazvrščene teme, jih morate ponovno razvrstiti, preden izklopite to nastavitev." + download_remote_images_to_local: "Pretvorite oddaljene (povezane) slike v lokalne slike tako, da jih prenesete; To ohranja vsebino, tudi če bodo slike v prihodnosti odstranjene z oddaljenega spletnega mesta." + block_hotlinked_media: "Uporabnikom preprečite, da bi v svoje objave vključevali oddaljene (povezane) medijske vsebine. Oddaljene vsebine, ki niso prenesene z uporabo nastavitve 'download_remote_images_to_local', bodo zamenjane z nadomestno povezavo." + block_hotlinked_media_exceptions: "Seznam naslovov URL domen, za katere ne velja nastavitev block_hotlinked_media. Naslove vpišite vključno s protokolom (npr. https://example.com)." + twitter_summary_large_image: "Velika slika povzetka za kartico Twitter (imeti mora vsaj 280 pik v širino in vsaj 150 pik v višino, ne sme biti .svg). Če ostane prazno se metapodatki kartice ustvarijo z uporabo opengraph_image, razen če tudi ta ni .svg" send_welcome_message: "Pošlji sporočilo z dobrodošlico in kratkimi uvodnimi navodili vsem novim uporabnikom." auth_skip_create_confirm: Pri prijavi s pomočjo zunanjega overjanja preskočite pojavno okno za ustvarjanje računa. Priporočena uporaba skupaj z auth_overrides_email, auth_overrides_username in auth_overrides_name. auth_immediately: "Samodejno preusmeri na zunanji sistem za prijavo brez interakcije uporabnika. To začne veljati šele, ko je login_required vklopljen in ko obstaja samo en način zunanje overitve" diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml index 9afe76a370..1aa08d3ebf 100644 --- a/config/locales/server.sq.yml +++ b/config/locales/server.sq.yml @@ -620,7 +620,6 @@ sq: title: "The name of this site, as used in the title tag." site_description: "Describe this site in one sentence, as used in the meta description tag." crawl_images: "Retrieve images from remote URLs to insert the correct width and height dimensions." - download_remote_images_to_local: "Convert remote images to local images by downloading them; this prevents broken images." download_remote_images_threshold: "Minimum disk space necessary to download remote images locally (in percent)" disabled_image_download_domains: "Remote images will never be downloaded from these domains. Pipe-delimited list." editing_grace_period: "For (n) seconds after posting, editing will not create a new version in the post history." diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml index 147395faee..9ee5a04038 100644 --- a/config/locales/server.sv.yml +++ b/config/locales/server.sv.yml @@ -649,6 +649,11 @@ sv: post: image_placeholder: broken: "Den här bilden är trasig" + blocked_hotlinked_title: "Bilden finns på en annan webbplats. Klicka för att öppna i en ny flik." + blocked_hotlinked: "Extern bild" + media_placeholder: + blocked_hotlinked_title: "Media finns på en annan webbplats. Klicka för att öppna i en ny flik." + blocked_hotlinked: "Externa media" hidden_bidi_character: "Dubbelriktade tecken kan ändra ordningen som text renderas. Detta kan användas för att dölja skadlig kod." has_likes: one: "%{count} gillning" @@ -1410,9 +1415,11 @@ sv: contact_email: "E-postadress till nyckelkontakt som är ansvarig för denna webbplats. Används för kritiska aviseringar, och visas även på /about för brådskande ärenden." contact_url: "Kontakt-URL för denna webbplats. Visas på /about-sidan för brådskande ärenden." crawl_images: "Hämta bilder från tredjepartskällor för att infoga korrekta bredd- och höjddimensioner." - download_remote_images_to_local: "Konvertera externa bilder till lokala bilder genom att hämta dem; det motverkar felaktiga bildlänkar." + download_remote_images_to_local: "Konvertera fjärrbilder (med aktiva länkar) till lokala bilder genom att ladda ner dem. Detta bevarar innehåll även om bilderna tas bort från den fjärranslutna webbplatsen i framtiden." download_remote_images_threshold: "Minimalt diskutrymme nödvändigt för att hämta hem externa bilder lokalt (i procent)" disabled_image_download_domains: "Externa bilder kommer aldrig att hämtas hem från dessa domän. Pipe-avgränsad lista." + block_hotlinked_media: "Förhindra att användare infogar fjärranslutna media (med aktiva länkar) i sina inlägg. Fjärranslutna media som inte hämtas via 'download_remote_images_to_local' kommer att ersättas med en platshållarlänk." + block_hotlinked_media_exceptions: "En lista över baswebbadresser som undantas från inställningen block_hotlinked_media (Blockera media med aktiva länkar). Inkludera protokollet (t.ex. https://example.com)." editing_grace_period: "I (n) sekunder efter inlägg kommer redigeringar inte att skapa nya versioner i inläggshistoriken." editing_grace_period_max_diff: "Uppnått maximalt antal teckenförändringar som tillåts för redigering under prövotid, om fler ändras spara dessa i en annan inläggsversion (förtroendenivå 0 och 1)" editing_grace_period_max_diff_high_trust: "Uppnått maximalt antal teckenförändringar som tillåts för redigering under prövotid, om fler ändras spara dessa i en annan inläggsversion (förtroendenivå 2 och upp)" diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml index c8e4e1ff12..7685ce60da 100644 --- a/config/locales/server.tr_TR.yml +++ b/config/locales/server.tr_TR.yml @@ -658,6 +658,11 @@ tr_TR: post: image_placeholder: broken: "Bu görüntü bozuldu" + blocked_hotlinked_title: "Resim başka bir sitede. Yeni bir sekmede açmak için tıklayın." + blocked_hotlinked: "Harici Görsel" + media_placeholder: + blocked_hotlinked_title: "Medya başka bir sitede. Yeni bir sekmede açmak için tıklayın." + blocked_hotlinked: "Harici Medya" hidden_bidi_character: "Çift yönlü karakterler, metnin oluşturulma sırasını değiştirebilir. Bu, kötü amaçlı kodu gizlemek için kullanılabilir." has_likes: one: "%{count} Beğeni" @@ -1417,7 +1422,6 @@ tr_TR: contact_email: "Bu site için yöneticini e-posta adresi. /about iletişim formu içerisinde, sadece acil durumlar ve kritik bildirimler için kullanılacak." contact_url: "Bu site için iletişim URL'sidir. Acil konular için / about iletişim formunda kullanılır." crawl_images: "Doğru genişlik ve yükseklik boyutlarını girmek için uzak URL'lerdeki resimlerin birer kopyasını alınsın." - download_remote_images_to_local: "Uzaktaki resimler yerel resimlere çevirmek için indirilsin; bu ayar resim bağlantılarının kırılmasını önleyecektir" download_remote_images_threshold: "Uzaktaki resimlerin yerele indirilmesi için gereken en az disk alanı (yüzdesel)" disabled_image_download_domains: "Bu alan adlarından hiç bir zaman uzaktan resim indirme. Sınırlandırılmış liste." editing_grace_period: "Gönderi oluşturulduktan (n) saniye içerisinde bir düzenleme yapıldığında, gönderi geçmişinde yeni bir sürüm oluşturma." diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml index 45060c8d89..d03bb7856f 100644 --- a/config/locales/server.uk.yml +++ b/config/locales/server.uk.yml @@ -1524,7 +1524,6 @@ uk: contact_email: "Адреса електронної пошти ключового контакту, відповідального за цей сайт. Використовується для критичних повідомлень, а також відображається на /about для невідкладних питань." contact_url: "Контактний URL цього сайту. Вказано на сторінці /about для термінових питань." crawl_images: "Отримувати зображення з віддалених адрес, щоб встановити правильні розміри ширини та висоти." - download_remote_images_to_local: "Завантажувати картинки, вставлені в повідомлення посиланнями на інші сайти, і зберігати їх локально, щоб запобігти їх зміни або втрату." download_remote_images_threshold: "Мінімальне доступне місце на диску (у відсотках), при якому дозволено автоматичне скачування картинок для локального зберігання." disabled_image_download_domains: "Список доменів, розділений знаком \"|\", в яких не потрібно завантажувати картинки." editing_grace_period: "Кількість секунд після відправки соощенія, в перебігу яких правка повідомлення не буде записуватися в історії редагувань." diff --git a/config/locales/server.ur.yml b/config/locales/server.ur.yml index 113c2f6fbf..154483ab16 100644 --- a/config/locales/server.ur.yml +++ b/config/locales/server.ur.yml @@ -1422,7 +1422,6 @@ ur: contact_email: "اس سائٹ کے ذمہ دار کلیدی رابطہ کا ای میل پتہ۔ اہم اطلاعات کے لیے استعمال کیا جاتا ہے، اور فوری معاملات کے لیے /متعلق پر بھی دکھایا جاتا ہے۔" contact_url: "اس سائٹ کے لیے یو آر ایل سے رابطہ کریں۔ فوری معاملات کے لیے /متعلقہ صفحہ پر دکھایا گیا ہے۔" crawl_images: "صحیح چوڑائی اور اونچائی والے طول و عرض داخل کرنے کیلئے ریمَوٹ URL سے تصاویر حاصل کریں۔" - download_remote_images_to_local: "ڈاؤن لوڈ کرکے ریمَوٹ تصاویر کو مقامی تصاویر میں تبدیل کریں؛ اِس طرح سے ٹوٹی ہوئی تصاویر کو روکا جا سکتا ہے۔" download_remote_images_threshold: "مقامی طور پر ریمَوٹ تصاویر کو ڈاؤن لَوڈ کرنے کیلئے ڈِسک میں کم از کم جگہ (فیصد میں)" disabled_image_download_domains: "ریمَوٹ تصاویر اِن ڈومینز سے کبھی ڈاؤن لوڈ نہیں ہوں گی۔ پائپ کے ساتھ الگ کی گئی فہرست۔" editing_grace_period: "پوسٹنگ کے بعد (ن) سیکنڈ تک، نئی ترمیم پوسٹ کی ہسٹری میں ایک نیا ورژن نہیں بنائے گا۔" diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml index f8b5af9004..c47e2048a7 100644 --- a/config/locales/server.vi.yml +++ b/config/locales/server.vi.yml @@ -914,7 +914,6 @@ vi: title: "Tên của trang này, sử dụng trong thẻ tiêu đề" site_description: "Mô tả trang này trong một câu, nó sẽ được sử dụng trong thẻ meta description" crawl_images: "Lấy hình ảnh tử URL bên ngoài để thêm vào đúng chiều dài và chiều cao." - download_remote_images_to_local: "Tải ảnh về lưu trữ để tránh ảnh bị hư." download_remote_images_threshold: "Dung lượng tối thiểu cần để tải ảnh từ xa về lưu trữ (tính bằng phần trăm)" disabled_image_download_domains: "Tải ảnh từ xa sẽ không áp dụng với các tên miền sau. Phân cách bằng dấu |" editing_grace_period: "Trong khoảng (n) giây sau khi gửi bài, chỉnh sửa sẽ không tạo ra một phiên bản mới trong bài lịch sử bài viết." diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index bd4d91030e..91e5dfe4b8 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -643,6 +643,11 @@ zh_CN: post: image_placeholder: broken: "此图片已损坏" + blocked_hotlinked_title: "图片托管主机在另一个站点。点击在新标签页中打开。" + blocked_hotlinked: "外部图片" + media_placeholder: + blocked_hotlinked_title: "媒体托管主机在另一个站点上。点击以在新标签页中打开。" + blocked_hotlinked: "外部媒体" hidden_bidi_character: "双向字符可以更改文本渲染的顺序。这可以用来掩盖恶意代码。" has_likes: other: "%{count} 个赞" @@ -1378,9 +1383,11 @@ zh_CN: contact_email: "此站点主要负责人的电子邮件地址。用于重要通知,也显示在 /about 页面上用于紧急事项。" contact_url: "此站点的联系 URL。显示在 /about 页面上以应对紧急事项。" crawl_images: "从远程 URL 检索图片以插入正确的宽度和高度尺寸。" - download_remote_images_to_local: "通过下载将远程图片转换为本地图片;这样可以防止图片损坏。" + download_remote_images_to_local: "通过下载将远程(hotlinked)图像并转换为本地图像;即使将来从远程站点删除图像,这也能保留内容。" download_remote_images_threshold: "将远程图片下载到本地所需的最小磁盘空间(百分比)" disabled_image_download_domains: "永远不会从这些网域下载远程图片。以 | 分隔的列表。" + block_hotlinked_media: "防止用户在他们的帖子中引入远程(hotlinked)媒体。未通过 'download_remote_images_to_local' 下载的远程媒体将被替换成一个占位符链接。" + block_hotlinked_media_exceptions: "不受 block_hotlinked_media 设置影响的基本 URL 列表。包括协议(例如:https://example.com)。" editing_grace_period: "发帖后 (n) 秒内,编辑不会在帖子历史记录中创建新版本。" editing_grace_period_max_diff: "编辑宽限期内允许的最大字符更改数,超过此值将保存另一个帖子修订(信任级别 0 和 1)" editing_grace_period_max_diff_high_trust: "编辑宽限期内允许的最大字符更改数,超过此值将保存另一个帖子修订(信任级别 2 及更高)" diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml index 817f91231d..92da5f4e2d 100644 --- a/config/locales/server.zh_TW.yml +++ b/config/locales/server.zh_TW.yml @@ -1105,7 +1105,6 @@ zh_TW: site_description: "用一句話描述這個網站,用於 meta description 標籤。" short_site_description: "簡短描述,用於網頁標題。" crawl_images: "允許從第三方 URL 取得圖片,來加入寬和高的數值" - download_remote_images_to_local: "下載外部鏈接的圖片到本機;以防圖片損壞。" download_remote_images_threshold: "可用來下載外部圖片到本機最少的空間(百分比)" disabled_image_download_domains: "在此列表的網域名稱的遠端圖片將不會進行下載,用 | 分割多個域名" editing_grace_period: "在 (n) 秒之內,對貼文的編輯不生成貼文歷史。" diff --git a/plugins/poll/config/locales/server.fa_IR.yml b/plugins/poll/config/locales/server.fa_IR.yml index 8fc7d6c5c0..5b5c744140 100644 --- a/plugins/poll/config/locales/server.fa_IR.yml +++ b/plugins/poll/config/locales/server.fa_IR.yml @@ -30,8 +30,10 @@ fa_IR: post_is_deleted: "این کار را نمی‌توان روی یک نوشته حذف شده انجام داد." topic_must_be_open_to_vote: "موضوع باید برای رای گزاری باز باشد. " poll_must_be_open_to_vote: "نظرسنجی باید باز باشد برای رای گیری." + one_vote_per_user: "تنها ۱ رای برای این نظرسنجی مجاز است." topic_must_be_open_to_toggle_status: "موضوع باید برای تغییر وضیعت باز باشد. " only_staff_or_op_can_toggle_status: "فقط همکاران یا ارسال کنندگان پست می توانند وضعیت نظرسنجی را تغییر دهند. " + insufficient_rights_to_create: "شما مجاز به ایجاد نظرسنجی نیستید." email: link_to_poll: "برای دیدن نظرسنجی کلیک کنید." user_field: diff --git a/plugins/styleguide/config/locales/client.fa_IR.yml b/plugins/styleguide/config/locales/client.fa_IR.yml index 3e2522da1d..b1cb809dfc 100644 --- a/plugins/styleguide/config/locales/client.fa_IR.yml +++ b/plugins/styleguide/config/locales/client.fa_IR.yml @@ -85,3 +85,5 @@ fa_IR: title: "نمادهای سربرگ" spinners: title: "اسپینرها" + empty_state: + title: "حالت خالی" From 348b6e848f91881e7b122095e6174f3b55f3d8f8 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Tue, 14 Jun 2022 21:16:33 +0200 Subject: [PATCH 008/184] FIX: NavItem and Composer prop overriding was broken (#17092) --- .../discourse/app/controllers/composer.js | 2 +- .../discourse/app/models/nav-item.js | 4 +-- .../tests/unit/models/nav-item-test.js | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js index 37706589b9..04f4fcf47b 100644 --- a/app/assets/javascripts/discourse/app/controllers/composer.js +++ b/app/assets/javascripts/discourse/app/controllers/composer.js @@ -136,7 +136,7 @@ export default Controller.extend({ }, set disableSubmit(value) { - this.set("_disableSubmit", value); + return this.set("_disableSubmit", value); }, @discourseComputed("showPreview") diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js index 9b4cec0f76..56d1f9566c 100644 --- a/app/assets/javascripts/discourse/app/models/nav-item.js +++ b/app/assets/javascripts/discourse/app/models/nav-item.js @@ -27,7 +27,7 @@ const NavItem = EmberObject.extend({ }, set(value) { - this.set("_title", value); + return this.set("_title", value); }, }, @@ -56,7 +56,7 @@ const NavItem = EmberObject.extend({ }, set(value) { - this.set("_displayName", value); + return this.set("_displayName", value); }, }, diff --git a/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js b/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js index d1b5bab44e..a7a1386fc7 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/nav-item-test.js @@ -57,4 +57,29 @@ module("Unit | Model | nav-item", function (hooks) { "it updates when a new message arrives" ); }); + + test("displayName", function (assert) { + const navItem = createStore().createRecord("nav-item", { + name: "something", + }); + + assert.strictEqual( + navItem.displayName, + "[en.filters.something.title count=0]" + ); + + navItem.set("displayName", "Extra Item"); + assert.strictEqual(navItem.displayName, "Extra Item"); + }); + + test("title", function (assert) { + const navItem = createStore().createRecord("nav-item", { + name: "something", + }); + + assert.strictEqual(navItem.title, "[en.filters.something.help]"); + + navItem.set("title", "Extra Item"); + assert.strictEqual(navItem.title, "Extra Item"); + }); }); From d7886e4d1801c0e4e981cb722f47bc15c4579534 Mon Sep 17 00:00:00 2001 From: Daniel Waterworth Date: Tue, 14 Jun 2022 14:54:49 -0500 Subject: [PATCH 009/184] FIX: Allow all subdomains of localhost in development (#17089) domains can contain numbers. Follow-up-to: 00e756e35858c6fbc058b2e686eca47ae90bda90 --- config/environments/development.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index bbac74b1a9..6f087acd59 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -111,5 +111,5 @@ Discourse::Application.configure do end end - config.hosts << /\A(([a-z-]+)\.)*localhost(\:\d+)?\Z/ + config.hosts << /\A(([a-z0-9-]+)\.)*localhost(\:\d+)?\Z/ end From 39f45c701c84023c695665dba456925f6481cd8f Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Wed, 15 Jun 2022 01:31:21 +0200 Subject: [PATCH 010/184] DEV: Mark inline styles as safe (#17093) Fixes many Ember deprecation warnings like: ``` WARNING: Binding style attributes may introduce cross-site scripting vulnerabilities; please ensure that values being bound are properly escaped. For more information, including how to disable this warning, see https://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes. Style affected: "border-color: #33B0B0; --category-color: #33B0B0;" ``` --- .../components/categories-boxes-with-topics.hbs | 4 +++- .../app/templates/components/categories-boxes.hbs | 9 ++++++++- .../app/templates/components/parent-category-row.hbs | 7 ++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs b/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs index 0a041d8cc0..e25f0d922c 100644 --- a/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs @@ -1,5 +1,5 @@ {{#each categories as |c|}} -
+ diff --git a/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs b/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs index d806ed1aaf..95758c17e5 100644 --- a/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs @@ -1,6 +1,7 @@ {{#each categories as |c|}} {{plugin-outlet name="category-box-before-each-box" args=(hash category=c)}} -
+ +
{{#unless c.isMuted}} {{/unless}} + + {{plugin-outlet name="category-box-after-each-box" args=(hash category=c)}} {{/each}} + {{plugin-outlet name="category-boxes-after-boxes" args=(hash category=c)}} diff --git a/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs b/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs index a00118be6e..19fd656fd0 100644 --- a/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs @@ -1,14 +1,17 @@ {{#unless isHidden}} {{plugin-outlet name="category-list-above-each-category" args=(hash category=category)}} + - + {{category-title-link category=category}} {{plugin-outlet name="below-category-title-link" connectorTagName="div" args=(hash category=category)}} + {{#if category.description_excerpt}}
{{dir-span category.description_excerpt htmlSafe="true"}}
{{/if}} + {{#if category.isGrandParent}} @@ -25,10 +28,12 @@ {{/if}} + + {{#unless isMuted}} {{#if showTopics}} '; - } - } - if (opts.isDisabled) { - arr.push('is-disabled'); - } - if (opts.isToday) { - arr.push('is-today'); - } - if (opts.isSelected) { - arr.push('is-selected'); - ariaSelected = 'true'; - } - if (opts.hasEvent) { - arr.push('has-event'); - } - if (opts.isInRange) { - arr.push('is-inrange'); - } - if (opts.isStartRange) { - arr.push('is-startrange'); - } - if (opts.isEndRange) { - arr.push('is-endrange'); - } - return ''; - }, - - renderWeek = function (d, m, y) { - // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified. - var onejan = new Date(y, 0, 1), - weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7); - return ''; - }, - - renderRow = function(days, isRTL, pickWholeWeek, isRowSelected) - { - return '' + (isRTL ? days.reverse() : days).join('') + ''; - }, - - renderBody = function(rows) - { - return '' + rows.join('') + ''; - }, - - renderHead = function(opts) - { - var i, arr = []; - if (opts.showWeekNumber) { - arr.push(''); - } - for (i = 0; i < 7; i++) { - arr.push(''); - } - return '' + (opts.isRTL ? arr.reverse() : arr).join('') + ''; - }, - - renderTitle = function(instance, c, year, month, refYear, randId) - { - var i, j, arr, - opts = instance._o, - isMinYear = year === opts.minYear, - isMaxYear = year === opts.maxYear, - html = '
', - monthHtml, - yearHtml, - prev = true, - next = true; - - for (arr = [], i = 0; i < 12; i++) { - arr.push(''); - } - - monthHtml = '
' + opts.i18n.months[month] + '
'; - - if (isArray(opts.yearRange)) { - i = opts.yearRange[0]; - j = opts.yearRange[1] + 1; - } else { - i = year - opts.yearRange; - j = 1 + year + opts.yearRange; - } - - for (arr = []; i < j && i <= opts.maxYear; i++) { - if (i >= opts.minYear) { - arr.push(''); - } - } - yearHtml = '
' + year + opts.yearSuffix + '
'; - - if (opts.showMonthAfterYear) { - html += yearHtml + monthHtml; - } else { - html += monthHtml + yearHtml; - } - - if (isMinYear && (month === 0 || opts.minMonth >= month)) { - prev = false; - } - - if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { - next = false; - } - - if (c === 0) { - html += ''; - } - if (c === (instance._o.numberOfMonths - 1) ) { - html += ''; - } - - return html += '
'; - }, - - renderTable = function(opts, data, randId) - { - return '
{{html-safe category.stat}}
{{category-unread category=category tagName="div" class="unread-new"}}
From 63df2b45506b47adb8913a291d9b88fde4eb434b Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Wed, 15 Jun 2022 09:59:57 +1000 Subject: [PATCH 011/184] FIX: whisper available when reply to topic (#17054) When a user is answering a whisper comment, they cannot change from whisper to regular answer. However, user can click reply to topic. We keep `postSnapshot` so user can change mind and switch back to reply to post. In that case, a toggle whisper button should appear. To make it happen, I am ensuring to display a toggle whisper button when user is replying to topic - `postLink` attribute is missing. --- .../tests/acceptance/composer-test.js | 37 +++++++++++++++++++ .../addon/components/composer-actions.js | 3 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js index eb2c686813..018bdf7b16 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js @@ -1,5 +1,7 @@ import { click, currentURL, fillIn, settled, visit } from "@ember/test-helpers"; import { toggleCheckDraftPopup } from "discourse/controllers/composer"; +import { cloneJSON } from "discourse-common/lib/object"; +import TopicFixtures from "discourse/tests/fixtures/topic"; import LinkLookup from "discourse/lib/link-lookup"; import { withPluginApi } from "discourse/lib/plugin-api"; import Composer, { @@ -43,6 +45,11 @@ acceptance("Composer", function (needs) { max_users_notified_per_group_mention: 100, }); }); + server.get("/t/960.json", () => { + const topicList = cloneJSON(TopicFixtures["/t/9/1.json"]); + topicList.post_stream.posts[2].post_type = 4; + return helper.response(topicList); + }); }); test("composer controls", async function (assert) { @@ -573,6 +580,36 @@ acceptance("Composer", function (needs) { ); }); + test("Composer can toggle whisper when switching from reply to whisper to reply to topic", async function (assert) { + await visit("/t/topic-with-whisper/960"); + + await click(".topic-post:nth-of-type(3) button.reply"); + await click(".reply-details summary div"); + assert.ok( + !exists('.reply-details li[data-value="toggle_whisper"]'), + "toggle whisper is not available when reply to whisper" + ); + await click('.reply-details li[data-value="reply_to_topic"]'); + await click(".reply-details summary div"); + assert.ok( + exists('.reply-details li[data-value="toggle_whisper"]'), + "toggle whisper is available when reply to topic" + ); + }); + + test("Composer can toggle whisper when clicking reply to topic after reply to whisper", async function (assert) { + await visit("/t/topic-with-whisper/960"); + + await click(".topic-post:nth-of-type(3) button.reply"); + await click("#reply-control .save-or-cancel a.cancel"); + await click(".topic-footer-main-buttons button.create"); + await click(".reply-details summary div"); + assert.ok( + exists('.reply-details li[data-value="toggle_whisper"]'), + "toggle whisper is available when reply to topic" + ); + }); + test("Composer with dirty reply can toggle to edit", async function (assert) { await visit("/t/this-is-a-test-topic/9"); diff --git a/app/assets/javascripts/select-kit/addon/components/composer-actions.js b/app/assets/javascripts/select-kit/addon/components/composer-actions.js index ac8cbb294e..da00b80e31 100644 --- a/app/assets/javascripts/select-kit/addon/components/composer-actions.js +++ b/app/assets/javascripts/select-kit/addon/components/composer-actions.js @@ -182,7 +182,8 @@ export default DropdownSelectBoxComponent.extend({ // if answered post is a whisper, we can only answer with a whisper so no need for toggle if ( this.canWhisper && - (!_postSnapshot || + (!this.replyOptions.postLink || + !_postSnapshot || _postSnapshot.post_type !== this.site.post_types.whisper) ) { items.push({ From 4d3c1ceb44b87d3bcb9c5eb2472d86b0cd2b2b0b Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Wed, 15 Jun 2022 10:28:30 +1000 Subject: [PATCH 012/184] FEATURE: Log the SMTP response in EmailLog (#17056) When sending emails with delivery_method_options -> return_response set to true, the SMTP sending code inside Mail will return the SMTP response when calling deliver! for mail within the app. This commit ensures that Email::Sender captures this response if it is returned and stores it against the EmailLog created for the sent email. A follow up PR will make this visible within the admin email UI. --- app/mailers/group_smtp_mailer.rb | 3 +- app/models/email_log.rb | 33 +++++++-------- ...smtp_transaction_response_to_email_logs.rb | 7 ++++ lib/email/message_builder.rb | 1 + lib/email/sender.rb | 8 +++- spec/lib/email/sender_spec.rb | 41 ++++++++++++------- 6 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 db/migrate/db/migrate/20220606061813_add_smtp_transaction_response_to_email_logs.rb diff --git a/app/mailers/group_smtp_mailer.rb b/app/mailers/group_smtp_mailer.rb index 42a2c9a082..5a72c1c92c 100644 --- a/app/mailers/group_smtp_mailer.rb +++ b/app/mailers/group_smtp_mailer.rb @@ -16,7 +16,8 @@ class GroupSmtpMailer < ActionMailer::Base user_name: from_group.email_username, password: from_group.email_password, authentication: GlobalSetting.smtp_authentication, - enable_starttls_auto: from_group.smtp_ssl + enable_starttls_auto: from_group.smtp_ssl, + return_response: true } group_name = from_group.name_full_preferred diff --git a/app/models/email_log.rb b/app/models/email_log.rb index a75fc0764b..8e3c7b163d 100644 --- a/app/models/email_log.rb +++ b/app/models/email_log.rb @@ -127,22 +127,23 @@ end # # Table name: email_logs # -# id :integer not null, primary key -# to_address :string not null -# email_type :string not null -# user_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# post_id :integer -# bounce_key :uuid -# bounced :boolean default(FALSE), not null -# message_id :string -# smtp_group_id :integer -# cc_addresses :text -# cc_user_ids :integer is an Array -# raw :text -# topic_id :integer -# bounce_error_code :string +# id :integer not null, primary key +# to_address :string not null +# email_type :string not null +# user_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# post_id :integer +# bounce_key :uuid +# bounced :boolean default(FALSE), not null +# message_id :string +# smtp_group_id :integer +# cc_addresses :text +# cc_user_ids :integer is an Array +# raw :text +# topic_id :integer +# bounce_error_code :string +# smtp_transaction_response :string(500) # # Indexes # diff --git a/db/migrate/db/migrate/20220606061813_add_smtp_transaction_response_to_email_logs.rb b/db/migrate/db/migrate/20220606061813_add_smtp_transaction_response_to_email_logs.rb new file mode 100644 index 0000000000..60a15a74d8 --- /dev/null +++ b/db/migrate/db/migrate/20220606061813_add_smtp_transaction_response_to_email_logs.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddSmtpTransactionResponseToEmailLogs < ActiveRecord::Migration[7.0] + def change + add_column :email_logs, :smtp_transaction_response, :string, null: true, limit: 500 + end +end diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index 187bfc364d..62236252e0 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -145,6 +145,7 @@ module Email } args[:delivery_method_options] = @opts[:delivery_method_options] if @opts[:delivery_method_options] + args[:delivery_method_options] = (args[:delivery_method_options] || {}).merge(return_response: true) args end diff --git a/lib/email/sender.rb b/lib/email/sender.rb index 7583c200d7..ce5ab66fc3 100644 --- a/lib/email/sender.rb +++ b/lib/email/sender.rb @@ -315,7 +315,13 @@ module Email DiscourseEvent.trigger(:before_email_send, @message, @email_type) begin - @message.deliver_now + message_response = @message.deliver! + + # TestMailer from the Mail gem does not return a real response, it + # returns an array containing @message, so we have to have this workaround. + if message_response.kind_of?(Net::SMTP::Response) + email_log.smtp_transaction_response = message_response.message&.chomp + end rescue *SMTP_CLIENT_ERRORS => e return skip(SkippedEmailLog.reason_types[:custom], custom_reason: e.message) end diff --git a/spec/lib/email/sender_spec.rb b/spec/lib/email/sender_spec.rb index 8045995edb..f2615b96da 100644 --- a/spec/lib/email/sender_spec.rb +++ b/spec/lib/email/sender_spec.rb @@ -7,6 +7,13 @@ describe Email::Sender do SiteSetting.secure_media_allow_embed_images_in_emails = false end fab!(:post) { Fabricate(:post) } + let(:mock_smtp_transaction_response) { "250 Ok: queued as 2l3Md07BObzB8kRyHZeoN0baSUAhzc7A-NviRioOr80=@mailhog.example" } + + def stub_deliver_response(message) + message.stubs(:deliver!).returns( + Net::SMTP::Response.new("250", mock_smtp_transaction_response) + ) + end context "disable_emails is enabled" do fab!(:user) { Fabricate(:user) } @@ -41,19 +48,20 @@ describe Email::Sender do before { SiteSetting.disable_emails = "non-staff" } it "doesn't deliver mail to normal user" do - Mail::Message.any_instance.expects(:deliver_now).never + Mail::Message.any_instance.expects(:deliver!).never message = Mail::Message.new(to: user.email, body: "hello") + stub_deliver_response(message) expect(Email::Sender.new(message, :hello).send).to eq(nil) end it "delivers mail to staff user" do - Mail::Message.any_instance.expects(:deliver_now).once + Mail::Message.any_instance.expects(:deliver!).once message = Mail::Message.new(to: moderator.email, body: "hello") Email::Sender.new(message, :hello).send end it "delivers mail to staff user when confirming new email if user is provided" do - Mail::Message.any_instance.expects(:deliver_now).once + Mail::Message.any_instance.expects(:deliver!).once Fabricate(:email_change_request, { user: moderator, new_email: "newemail@testmoderator.com", @@ -69,32 +77,32 @@ describe Email::Sender do end it "doesn't deliver mail when the message is of type NullMail" do - Mail::Message.any_instance.expects(:deliver_now).never + Mail::Message.any_instance.expects(:deliver!).never message = ActionMailer::Base::NullMail.new expect(Email::Sender.new(message, :hello).send).to eq(nil) end it "doesn't deliver mail when the message is nil" do - Mail::Message.any_instance.expects(:deliver_now).never + Mail::Message.any_instance.expects(:deliver!).never Email::Sender.new(nil, :hello).send end it "doesn't deliver when the to address is nil" do message = Mail::Message.new(body: 'hello') - message.expects(:deliver_now).never + message.expects(:deliver!).never Email::Sender.new(message, :hello).send end it "doesn't deliver when the to address uses the .invalid tld" do message = Mail::Message.new(body: 'hello', to: 'myemail@example.invalid') - message.expects(:deliver_now).never + message.expects(:deliver!).never expect { Email::Sender.new(message, :hello).send }. to change { SkippedEmailLog.where(reason_type: SkippedEmailLog.reason_types[:sender_message_to_invalid]).count }.by(1) end it "doesn't deliver when the body is nil" do message = Mail::Message.new(to: 'eviltrout@test.domain') - message.expects(:deliver_now).never + message.expects(:deliver!).never Email::Sender.new(message, :hello).send end @@ -126,14 +134,14 @@ describe Email::Sender do to: 'eviltrout@test.domain', body: '**hello**' ) - message.stubs(:deliver_now) + stub_deliver_response(message) message end let(:email_sender) { Email::Sender.new(message, :valid_type) } it 'calls deliver' do - message.expects(:deliver_now).once + message.expects(:deliver!).once email_sender.send end @@ -414,6 +422,7 @@ describe Email::Sender do before do SiteSetting.enable_smtp = true + stub_deliver_response(message) end it 'adds the group id and raw content to the email log' do @@ -684,7 +693,7 @@ describe Email::Sender do message = Mail::Message.new to: 'disc@ourse.org', body: 'some content' message.header['X-Discourse-Post-Id'] = post.id message.header['X-Discourse-Topic-Id'] = post.topic_id - message.expects(:deliver_now).never + message.expects(:deliver!).never email_sender = Email::Sender.new(message, :valid_type) expect { email_sender.send }.to change { SkippedEmailLog.count } @@ -703,7 +712,7 @@ describe Email::Sender do message = Mail::Message.new to: 'disc@ourse.org', body: 'some content' message.header['X-Discourse-Post-Id'] = post.id message.header['X-Discourse-Topic-Id'] = post.topic_id - message.expects(:deliver_now).never + message.expects(:deliver!).never email_sender = Email::Sender.new(message, :valid_type) expect { email_sender.send }.to change { SkippedEmailLog.count } @@ -717,7 +726,7 @@ describe Email::Sender do context 'with a user' do let(:message) do message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body' - message.stubs(:deliver_now) + stub_deliver_response(message) message end @@ -733,6 +742,10 @@ describe Email::Sender do expect(@email_log.user_id).to eq(user.id) end + it 'should have the smtp_transaction_response message' do + expect(@email_log.smtp_transaction_response).to eq(mock_smtp_transaction_response) + end + describe "post reply keys" do fab!(:post) { Fabricate(:post) } @@ -782,7 +795,7 @@ describe Email::Sender do context "with cc addresses" do let(:message) do message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body', cc: 'someguy@test.com;otherguy@xyz.com' - message.stubs(:deliver_now) + stub_deliver_response(message) message end From f723b4c322e2d4f31d3be159d45fa08377c6d327 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Wed, 15 Jun 2022 02:55:55 +0200 Subject: [PATCH 013/184] FIX: Handle sites with more than 1 JSON-LD element (#17095) A followup to #17007 --- lib/onebox/json_ld.rb | 23 ++++++++--------------- spec/lib/onebox/json_ld_spec.rb | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/onebox/json_ld.rb b/lib/onebox/json_ld.rb index c4f426ac3d..77e2534f16 100644 --- a/lib/onebox/json_ld.rb +++ b/lib/onebox/json_ld.rb @@ -12,32 +12,25 @@ module Onebox private def extract(doc) - extracted_json = extract_json_from(doc) - parsed_json = parse_json(extracted_json) + return {} if Onebox::Helpers::blank?(doc) + + doc.css('script[type="application/ld+json"]').each do |element| + parsed_json = parse_json(element.text) - extracted = case parsed_json["@type"] when MOVIE_JSON_LD_TYPE - Onebox::Movie.new(parsed_json) - else - {} + return Onebox::Movie.new(parsed_json).to_h end + end - extracted.to_h - end - - def extract_json_from(doc) - return {} if Onebox::Helpers::blank?(doc) - json_ld = doc.search('script[type="application/ld+json"]').text - return {} if Onebox::Helpers::blank?(json_ld) - json_ld + {} end def parse_json(json) begin JSON[json] rescue JSON::ParserError => e - Discourse.warn_exception(e, message: "Error parsing JSON-LD json: #{json}") + Discourse.warn_exception(e, message: "Error parsing JSON-LD: #{json}") {} end end diff --git a/spec/lib/onebox/json_ld_spec.rb b/spec/lib/onebox/json_ld_spec.rb index 027f1097ea..00d84d4dee 100644 --- a/spec/lib/onebox/json_ld_spec.rb +++ b/spec/lib/onebox/json_ld_spec.rb @@ -7,27 +7,26 @@ describe Onebox::JsonLd do invalid_json = "{\"@type\":invalid-json}" doc = Nokogiri::HTML("") Discourse.expects(:warn_exception).with( - instance_of(JSON::ParserError), { message: "Error parsing JSON-LD json: #{invalid_json}" } + instance_of(JSON::ParserError), { message: "Error parsing JSON-LD: #{invalid_json}" } ) json_ld = described_class.new(doc) - expect(json_ld.data).to eq({}) end - it 'returns an empty hash if there is no json_ld script tag' do + it 'returns an empty hash if there is no JSON-LD script tag' do doc = Nokogiri::HTML("") json_ld = described_class.new(doc) expect(json_ld.data).to eq({}) end - it 'returns an empty hash if there is no json_ld data' do + it 'returns an empty hash if there is no JSON-LD data' do doc = Nokogiri::HTML("") json_ld = described_class.new(doc) expect(json_ld.data).to eq({}) end - it 'returns an empty hash if the type of JSONLD data is not Movie' do + it 'returns an empty hash if the type of JSON-LD data is not Movie' do doc = Nokogiri::HTML("") json_ld = described_class.new(doc) expect(json_ld.data).to eq({}) @@ -39,6 +38,15 @@ describe Onebox::JsonLd do expect(json_ld.data).to eq(expected_movie_hash) end + it 'does not fail when there is more than one JSON-LD element' do + doc = Nokogiri::HTML(onebox_response('imdb')) + doc.css("body")[0] << "" + Discourse.expects(:warn_exception).never + + json_ld = described_class.new(doc) + expect(json_ld.data).to eq(expected_movie_hash) + end + private def expected_movie_hash From 275849771f32ede21072aea81cc2256247595e38 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 15 Jun 2022 16:13:44 +0100 Subject: [PATCH 014/184] DEV: Emit a 'change' event when PresenceChannel info changes (#17088) e.g. ``` presenceChannel = this.presence.getChannel('/blah'); presenceChannel.subscribe(); presenceChannel.on('change', (channel) => console.log(channel.users)); ``` This commit also does some refactoring to remove the use of an unnecessary EmberObject and dynamic `defineProperty` call --- .../discourse/app/services/presence.js | 42 ++++++++++++------- .../tests/unit/services/presence-test.js | 14 +++++-- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/app/services/presence.js b/app/assets/javascripts/discourse/app/services/presence.js index d0394aef89..98fa6445f2 100644 --- a/app/assets/javascripts/discourse/app/services/presence.js +++ b/app/assets/javascripts/discourse/app/services/presence.js @@ -1,6 +1,5 @@ import Service from "@ember/service"; -import EmberObject, { computed, defineProperty } from "@ember/object"; -import { readOnly } from "@ember/object/computed"; +import EmberObject, { computed } from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import { cancel, @@ -20,6 +19,7 @@ import userPresent, { removeOnPresenceChange, } from "discourse/lib/user-presence"; import { bind } from "discourse-common/utils/decorators"; +import Evented from "@ember/object/evented"; const PRESENCE_INTERVAL_S = 30; const PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500; @@ -45,17 +45,12 @@ export class PresenceChannelNotFound extends Error {} // Instances of this class are handed out to consumers. They act as // convenient proxies to the PresenceService and PresenceServiceState -class PresenceChannel extends EmberObject { +// The 'change' event is fired whenever the users list or the count change +class PresenceChannel extends EmberObject.extend(Evented) { init({ name, presenceService }) { super.init(...arguments); this.name = name; this.presenceService = presenceService; - defineProperty( - this, - "_presenceState", - readOnly(`presenceService._presenceChannelStates.${name}`) - ); - this.set("present", false); this.set("subscribed", false); } @@ -91,8 +86,12 @@ class PresenceChannel extends EmberObject { if (this.subscribed) { return; } - await this.presenceService._subscribe(this, initialData); + const state = await this.presenceService._subscribe(this, initialData); this.set("subscribed", true); + this.set("_presenceState", state); + + this._publishChange(); + state.on("change", this._publishChange); } async unsubscribe() { @@ -101,6 +100,14 @@ class PresenceChannel extends EmberObject { } await this.presenceService._unsubscribe(this); this.set("subscribed", false); + this._presenceState.off("change", this._publishChange); + this.set("_presenceState", null); + this._publishChange(); + } + + @bind + _publishChange() { + this.trigger("change", this); } @computed("_presenceState.users", "subscribed") @@ -128,7 +135,7 @@ class PresenceChannel extends EmberObject { } } -class PresenceChannelState extends EmberObject { +class PresenceChannelState extends EmberObject.extend(Evented) { init({ name, presenceService }) { super.init(...arguments); this.name = name; @@ -179,6 +186,7 @@ class PresenceChannelState extends EmberObject { ); this.set("_subscribedCallback", callback); + this.trigger("change"); } // Stop subscribing to updates from the server about this channel @@ -191,6 +199,7 @@ class PresenceChannelState extends EmberObject { this.set("_subscribedCallback", null); this.set("users", null); this.set("count", null); + this.trigger("change"); } } @@ -221,6 +230,7 @@ class PresenceChannelState extends EmberObject { if (this.countOnly && data.count_delta !== undefined) { this.set("count", this.count + data.count_delta); + this.trigger("change"); } else if ( !this.countOnly && (data.entering_users || data.leaving_user_ids) @@ -235,6 +245,7 @@ class PresenceChannelState extends EmberObject { this.users.removeObjects(toRemove); } this.set("count", this.users.length); + this.trigger("change"); } else { // Unexpected message await this._resubscribe(); @@ -247,7 +258,7 @@ export default class PresenceService extends Service { init() { super.init(...arguments); this._queuedEvents = []; - this._presenceChannelStates = EmberObject.create(); + this._presenceChannelStates = new Map(); this._presentProxies = new Map(); this._subscribedProxies = new Map(); this._initialDataRequests = new Map(); @@ -429,7 +440,7 @@ export default class PresenceService extends Service { this._addSubscribed(channelProxy); const channelName = channelProxy.name; - let state = this._presenceChannelStates[channelName]; + let state = this._presenceChannelStates.get(channelName); if (!state) { state = PresenceChannelState.create({ name: channelName, @@ -438,14 +449,15 @@ export default class PresenceService extends Service { this._presenceChannelStates.set(channelName, state); await state.subscribe(initialData); } + return state; } _unsubscribe(channelProxy) { const subscribedCount = this._removeSubscribed(channelProxy); if (subscribedCount === 0) { const channelName = channelProxy.name; - this._presenceChannelStates[channelName].unsubscribe(); - this._presenceChannelStates.set(channelName, undefined); + this._presenceChannelStates.get(channelName).unsubscribe(); + this._presenceChannelStates.delete(channelName); } } diff --git a/app/assets/javascripts/discourse/tests/unit/services/presence-test.js b/app/assets/javascripts/discourse/tests/unit/services/presence-test.js index f8f63a61c5..40e07a66c3 100644 --- a/app/assets/javascripts/discourse/tests/unit/services/presence-test.js +++ b/app/assets/javascripts/discourse/tests/unit/services/presence-test.js @@ -59,12 +59,17 @@ acceptance("Presence - Subscribing", function (needs) { test("subscribing and receiving updates", async function (assert) { let presenceService = this.container.lookup("service:presence"); let channel = presenceService.getChannel("/test/ch1"); + let changes = 0; + const countChanges = () => changes++; + channel.on("change", countChanges); + assert.strictEqual(channel.name, "/test/ch1"); await channel.subscribe({ users: usersFixture(), last_message_id: 1, }); + assert.strictEqual(changes, 1); assert.strictEqual(channel.users.length, 3, "it starts with three users"); @@ -78,6 +83,7 @@ acceptance("Presence - Subscribing", function (needs) { ); assert.strictEqual(channel.users.length, 2, "one user is removed"); + assert.strictEqual(changes, 2); publishToMessageBus( "/presence/test/ch1", @@ -89,6 +95,8 @@ acceptance("Presence - Subscribing", function (needs) { ); assert.strictEqual(channel.users.length, 3, "one user is added"); + assert.strictEqual(changes, 3); + channel.off("change", countChanges); }); test("fetches data when no initial state", async function (assert) { @@ -216,14 +224,14 @@ acceptance("Presence - Subscribing", function (needs) { assert.strictEqual(channel.subscribed, false, "channel can unsubscribe"); assert.strictEqual( channelDup._presenceState, - channel._presenceState, - "state is maintained" + presenceService._presenceChannelStates.get(channel.name), + "state is maintained in the subscribed channel" ); await channelDup.unsubscribe(); assert.strictEqual(channel.subscribed, false, "channelDup can unsubscribe"); assert.strictEqual( - channelDup._presenceState, + presenceService._presenceChannelStates.get(channel.name), undefined, "state is cleared" ); From bc0a37b642c38de605b72509e8950540a3583d0e Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:36:04 -0500 Subject: [PATCH 015/184] DEV: @bind create-account actions (#17100) DEV: @bind create-account actions Context: https://github.com/discourse/discourse/pull/16983#discussion_r894721403 --- .../app/components/create-account.js | 3 +++ .../create-account-user-fields-test.js | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/components/create-account.js b/app/assets/javascripts/discourse/app/components/create-account.js index a56b945bc0..695cbc1c33 100644 --- a/app/assets/javascripts/discourse/app/components/create-account.js +++ b/app/assets/javascripts/discourse/app/components/create-account.js @@ -1,5 +1,6 @@ import Component from "@ember/component"; import cookie from "discourse/lib/cookie"; +import { bind } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["create-account-body"], @@ -21,6 +22,7 @@ export default Component.extend({ } }, + @bind actionOnEnter(event) { if (!this.disabled && event.key === "Enter") { event.preventDefault(); @@ -30,6 +32,7 @@ export default Component.extend({ } }, + @bind selectKitFocus(event) { const target = document.getElementById(event.target.getAttribute("for")); if (target?.classList.contains("select-kit")) { diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js index bfeb8222e1..9572d3cbde 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js @@ -1,9 +1,10 @@ import { acceptance, + count, exists, query, } from "discourse/tests/helpers/qunit-helpers"; -import { click, fillIn, visit } from "@ember/test-helpers"; +import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; import { test } from "qunit"; acceptance("Create Account - User Fields", function (needs) { @@ -65,4 +66,22 @@ acceptance("Create Account - User Fields", function (needs) { await click(".user-field input[type=checkbox]"); await click(".modal-footer .btn-primary"); }); + + test("can submit with enter", async function (assert) { + await visit("/"); + await click("header .sign-up-button"); + + await triggerKeyEvent(".modal-footer .btn-primary", "keydown", 13); + + assert.strictEqual( + count("#modal-alert:visible"), + 1, + "hitting Enter triggers modal action" + ); + assert.strictEqual( + count(".d-modal:visible"), + 1, + "hitting Enter does not dismiss modal due to alert error" + ); + }); }); From a7034c5a14b24af2984bf7bf0f19f8f379011934 Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:47:47 -0500 Subject: [PATCH 016/184] DEV: Update create-account test to be more clear (#17102) --- .../tests/acceptance/create-account-user-fields-test.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js index 9572d3cbde..04b753f0fd 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js @@ -70,18 +70,12 @@ acceptance("Create Account - User Fields", function (needs) { test("can submit with enter", async function (assert) { await visit("/"); await click("header .sign-up-button"); - await triggerKeyEvent(".modal-footer .btn-primary", "keydown", 13); assert.strictEqual( count("#modal-alert:visible"), 1, - "hitting Enter triggers modal action" - ); - assert.strictEqual( - count(".d-modal:visible"), - 1, - "hitting Enter does not dismiss modal due to alert error" + "hitting Enter triggers action" ); }); }); From 888f50543d10dc92d506a02f00a3a3324e7889c2 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Wed, 15 Jun 2022 19:49:04 +0200 Subject: [PATCH 017/184] DEV: Fix `javascript:update` rake task (#17098) * The `javascript:update` rake task failed because recent versions of chart.js use a lowercase filename (`chart.min.js` instead of `Chart.min.js`) * Changed `loadScript()` to use lowercase keys to lookup scripts * `svg-arrow.css` seems to have changed slightly (linebreak at the end of file) --- .../javascripts/discourse/app/lib/load-script.js | 2 +- .../discourse/app/lib/public-js-versions.js | 2 +- .../discourse/tests/unit/lib/load-script-test.js | 11 +++++++++++ app/assets/stylesheets/vendor/svg-arrow.css | 2 +- lib/tasks/javascript.rake | 4 ++-- .../chart.js/3.5.1/{Chart.min.js => chart.min.js} | 0 6 files changed, 16 insertions(+), 5 deletions(-) rename public/javascripts/chart.js/3.5.1/{Chart.min.js => chart.min.js} (100%) diff --git a/app/assets/javascripts/discourse/app/lib/load-script.js b/app/assets/javascripts/discourse/app/lib/load-script.js index 2eb5a52c09..5acbbceac4 100644 --- a/app/assets/javascripts/discourse/app/lib/load-script.js +++ b/app/assets/javascripts/discourse/app/lib/load-script.js @@ -119,7 +119,7 @@ export function cacheBuster(url) { if (PUBLIC_JS_VERSIONS) { let [folder, ...lib] = url.split("/").filter(Boolean); if (folder === "javascripts") { - lib = lib.join("/"); + lib = lib.join("/").toLowerCase(); const versionedPath = PUBLIC_JS_VERSIONS[lib]; if (versionedPath) { return `/javascripts/${versionedPath}`; diff --git a/app/assets/javascripts/discourse/app/lib/public-js-versions.js b/app/assets/javascripts/discourse/app/lib/public-js-versions.js index 393052736c..a8ced62efa 100644 --- a/app/assets/javascripts/discourse/app/lib/public-js-versions.js +++ b/app/assets/javascripts/discourse/app/lib/public-js-versions.js @@ -4,7 +4,7 @@ export const PUBLIC_JS_VERSIONS = { "ace/ace.js": "ace.js/1.4.13/ace.js", "jsoneditor.js": "@json-editor/json-editor/2.6.1/jsoneditor.js", - "Chart.min.js": "chart.js/3.5.1/Chart.min.js", + "chart.min.js": "chart.js/3.5.1/chart.min.js", "chartjs-plugin-datalabels.min.js": "chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.min.js", "diffhtml.min.js": "diffhtml/1.0.0-beta.20/diffhtml.min.js", diff --git a/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js index fe48fbc858..609b6a931f 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/load-script-test.js @@ -24,4 +24,15 @@ module("Unit | Utility | load-script", function () { `/javascripts/${jsVersions["ace/ace.js"]}` ); }); + + test("lookups are case-insensitive", (assert) => { + assert.strictEqual( + cacheBuster("/javascripts/Chart.min.js"), + `/javascripts/${jsVersions["chart.min.js"]}` + ); + assert.strictEqual( + cacheBuster("/javascripts/chart.min.js"), + `/javascripts/${jsVersions["chart.min.js"]}` + ); + }); }); diff --git a/app/assets/stylesheets/vendor/svg-arrow.css b/app/assets/stylesheets/vendor/svg-arrow.css index 5e5b389c63..c2a61ad7ae 100644 --- a/app/assets/stylesheets/vendor/svg-arrow.css +++ b/app/assets/stylesheets/vendor/svg-arrow.css @@ -1 +1 @@ -.tippy-box[data-placement^=top]>.tippy-svg-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-svg-arrow:after,.tippy-box[data-placement^=top]>.tippy-svg-arrow>svg{top:16px;transform:rotate(180deg)}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-placement^=left]>.tippy-svg-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-svg-arrow:after,.tippy-box[data-placement^=left]>.tippy-svg-arrow>svg{transform:rotate(90deg);top:calc(50% - 3px);left:11px}.tippy-box[data-placement^=right]>.tippy-svg-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-svg-arrow:after,.tippy-box[data-placement^=right]>.tippy-svg-arrow>svg{transform:rotate(-90deg);top:calc(50% - 3px);right:11px}.tippy-svg-arrow{width:16px;height:16px;fill:#333;text-align:initial}.tippy-svg-arrow,.tippy-svg-arrow>svg{position:absolute} +.tippy-box[data-placement^=top]>.tippy-svg-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-svg-arrow:after,.tippy-box[data-placement^=top]>.tippy-svg-arrow>svg{top:16px;transform:rotate(180deg)}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-placement^=left]>.tippy-svg-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-svg-arrow:after,.tippy-box[data-placement^=left]>.tippy-svg-arrow>svg{transform:rotate(90deg);top:calc(50% - 3px);left:11px}.tippy-box[data-placement^=right]>.tippy-svg-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-svg-arrow:after,.tippy-box[data-placement^=right]>.tippy-svg-arrow>svg{transform:rotate(-90deg);top:calc(50% - 3px);right:11px}.tippy-svg-arrow{width:16px;height:16px;fill:#333;text-align:initial}.tippy-svg-arrow,.tippy-svg-arrow>svg{position:absolute} \ No newline at end of file diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index 7f2ceb5905..b632a401b9 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -75,7 +75,7 @@ def dependencies package_name: '@json-editor/json-editor', public: true }, { - source: 'chart.js/dist/Chart.min.js', + source: 'chart.js/dist/chart.min.js', public: true }, { source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js', @@ -310,7 +310,7 @@ task 'javascript:update' => 'clean_up' do else package_dir_name = public_path_name(f) package_version = JSON.parse(File.read("#{library_src}/#{node_package_name(f)}/package.json"))["version"] - versions[filename] = "#{package_dir_name}/#{package_version}/#{filename}" + versions[filename.downcase] = "#{package_dir_name}/#{package_version}/#{filename}" path = "#{public_js}/#{package_dir_name}/#{package_version}" dest = "#{path}/#{filename}" diff --git a/public/javascripts/chart.js/3.5.1/Chart.min.js b/public/javascripts/chart.js/3.5.1/chart.min.js similarity index 100% rename from public/javascripts/chart.js/3.5.1/Chart.min.js rename to public/javascripts/chart.js/3.5.1/chart.min.js From c625dc0adcff0a6c2a2c64b63f2f76185e1a555c Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Thu, 16 Jun 2022 13:33:40 +0800 Subject: [PATCH 018/184] FEATURE: Add messages section links to experimental sidebar. (#17096) --- .../components/sidebar/messages-section.js | 86 +++++ .../discourse/app/lib/page-tracker.js | 2 + .../group-message-section-link.js | 80 +++++ .../personal-message-section-link.js | 68 ++++ .../app/templates/components/sidebar.hbs | 4 +- .../components/sidebar/messages-section.hbs | 27 ++ .../components/sidebar/section-link.hbs | 2 +- .../sidebar-messages-section-test.js | 331 ++++++++++++++++-- .../stylesheets/common/base/sidebar.scss | 16 + config/locales/client.en.yml | 6 + 10 files changed, 592 insertions(+), 30 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js create mode 100644 app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js index a71dc9dd4b..9c964bf4ab 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js @@ -1,10 +1,96 @@ import { action } from "@ember/object"; +import { cached } from "@glimmer/tracking"; import GlimmerComponent from "discourse/components/glimmer"; import Composer from "discourse/models/composer"; import { getOwner } from "discourse-common/lib/get-owner"; +import GroupMessageSectionLink from "discourse/lib/sidebar/messages-section/group-message-section-link"; +import PersonalMessageSectionLink from "discourse/lib/sidebar/messages-section/personal-message-section-link"; + +export const INBOX = "inbox"; +const UNREAD = "unread"; +const SENT = "sent"; +const NEW = "new"; +const ARCHIVE = "archive"; + +export const PERSONAL_MESSAGES_INBOXES = [INBOX, UNREAD, NEW, SENT, ARCHIVE]; +export const GROUP_MESSAGES_INBOXES = [INBOX, UNREAD, NEW, ARCHIVE]; export default class SidebarMessagesSection extends GlimmerComponent { + constructor() { + super(...arguments); + + this.appEvents.on( + "page:changed", + this, + this._refreshSectionLinksDisplayState + ); + } + + willDestroy() { + this.appEvents.off( + "page:changed", + this, + this._refreshSectionLinksDisplayState + ); + } + + _refreshSectionLinksDisplayState({ + currentRouteName, + currentRouteParentName, + currentRouteParams, + }) { + const sectionLinks = [ + ...this.personalMessagesSectionLinks, + ...this.groupMessagesSectionLinks, + ]; + + if (currentRouteParentName !== "userPrivateMessages") { + sectionLinks.forEach((sectionLink) => { + sectionLink.collapse(); + }); + } else { + sectionLinks.forEach((sectionLink) => { + sectionLink.pageChanged(currentRouteName, currentRouteParams); + }); + } + } + + @cached + get personalMessagesSectionLinks() { + const links = []; + + PERSONAL_MESSAGES_INBOXES.forEach((type) => { + links.push( + new PersonalMessageSectionLink({ + currentUser: this.currentUser, + type, + }) + ); + }); + + return links; + } + + @cached + get groupMessagesSectionLinks() { + const links = []; + + this.currentUser.groupsWithMessages.forEach((group) => { + GROUP_MESSAGES_INBOXES.forEach((groupMessageLink) => { + links.push( + new GroupMessageSectionLink({ + group, + type: groupMessageLink, + currentUser: this.currentUser, + }) + ); + }); + }); + + return links; + } + @action composePersonalMessage() { const composerArgs = { diff --git a/app/assets/javascripts/discourse/app/lib/page-tracker.js b/app/assets/javascripts/discourse/app/lib/page-tracker.js index 3cb60f36ff..66316654cf 100644 --- a/app/assets/javascripts/discourse/app/lib/page-tracker.js +++ b/app/assets/javascripts/discourse/app/lib/page-tracker.js @@ -42,6 +42,8 @@ export function startPageTracking(router, appEvents, documentTitle) { url, title: documentTitle.getTitle(), currentRouteName: router.currentRouteName, + currentRouteParams: router.currentRoute.params, + currentRouteParentName: router.currentRoute.parent?.name, replacedOnlyQueryParams, }); }); diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js new file mode 100644 index 0000000000..9de4993327 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js @@ -0,0 +1,80 @@ +import I18n from "I18n"; + +import { tracked } from "@glimmer/tracking"; +import { capitalize } from "@ember/string"; + +import { INBOX } from "discourse/components/sidebar/messages-section"; + +export default class GroupMessageSectionLink { + @tracked shouldDisplay = this._isInbox; + + routeNames = new Set([ + "userPrivateMessages.group", + "userPrivateMessages.groupUnread", + "userPrivateMessages.groupNew", + "userPrivateMessages.groupArchive", + ]); + + constructor({ group, type, currentUser, router }) { + this.group = group; + this.type = type; + this.currentUser = currentUser; + this.router = router; + } + + get name() { + return `group-messages-${this.type}`; + } + + get class() { + return this.group.name; + } + + get route() { + if (this._isInbox) { + return "userPrivateMessages.group"; + } else { + return `userPrivateMessages.group${capitalize(this.type)}`; + } + } + + get currentWhen() { + if (this._isInbox) { + return [...this.routeNames].join(" "); + } + } + + get models() { + return [this.currentUser, this.group.name]; + } + + get text() { + if (this._isInbox) { + return this.group.name; + } else { + return I18n.t(`sidebar.sections.messages.links.${this.type}`); + } + } + + collapse() { + if (this._isInbox) { + return; + } + + this.shouldDisplay = false; + } + + pageChanged(currentRouteName, currentRouteParams) { + if (this._isInbox) { + return; + } + + this.shouldDisplay = + this.routeNames.has(currentRouteName) && + currentRouteParams.name.toLowerCase() === this.group.name.toLowerCase(); + } + + get _isInbox() { + return this.type === INBOX; + } +} diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js new file mode 100644 index 0000000000..66bc38e856 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js @@ -0,0 +1,68 @@ +import I18n from "I18n"; + +import { tracked } from "@glimmer/tracking"; +import { INBOX } from "discourse/components/sidebar/messages-section"; + +export default class PersonalMessageSectionLink { + @tracked shouldDisplay = this._isInbox; + + routeNames = new Set([ + "userPrivateMessages.index", + "userPrivateMessages.unread", + "userPrivateMessages.sent", + "userPrivateMessages.new", + "userPrivateMessages.archive", + ]); + + constructor({ currentUser, type, router }) { + this.currentUser = currentUser; + this.type = type; + this.router = router; + } + + get name() { + return `personal-messages-${this.type}`; + } + + get route() { + if (this._isInbox) { + return "userPrivateMessages.index"; + } else { + return `userPrivateMessages.${this.type}`; + } + } + + get currentWhen() { + if (this._isInbox) { + return [...this.routeNames].join(" "); + } + } + + get model() { + return this.currentUser; + } + + get text() { + return I18n.t(`sidebar.sections.messages.links.${this.type}`); + } + + collapse() { + if (this._isInbox) { + return; + } + + this.shouldDisplay = false; + } + + pageChanged(currentRouteName) { + if (this._isInbox) { + return; + } + + this.shouldDisplay = this.routeNames.has(currentRouteName); + } + + get _isInbox() { + return this.type === INBOX; + } +} diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs index 7469358ed5..ee690e6808 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs @@ -8,7 +8,9 @@ {{/if}} - + {{#if this.siteSettings.enable_personal_messages}} + + {{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs index 735f04344c..bed39761ef 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs @@ -7,4 +7,31 @@ @headerLinkText={{i18n "sidebar.sections.messages.header_link_text"}} @headerLinkTitle={{i18n "sidebar.sections.messages.header_link_title"}} > + {{#each this.personalMessagesSectionLinks as |personalMessageSectionLink|}} + {{#if personalMessageSectionLink.shouldDisplay}} + + {{/if}} + {{/each}} + + {{#if (gt this.groupMessagesSectionLinks.length 0)}} +
+ + {{#each this.groupMessagesSectionLinks as |groupMessageSectionLink|}} + {{#if groupMessageSectionLink.shouldDisplay}} + + {{/if}} + {{/each}} + {{/if}} + diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/section-link.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/section-link.hbs index 049cf540b1..3faa0becac 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/section-link.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/section-link.hbs @@ -1,6 +1,6 @@ <% end %> - - <%= server_plugin_outlet("after_post_body", locals: {post: post}) %> <% end %> <% end %> From a4fc88ce68341bcb8928383a681299d1a8da0e50 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 16 Jun 2022 12:16:53 +0100 Subject: [PATCH 020/184] FIX: Ensure emoji is inserted in the correct location (#17107) In the specific case where you start typing an emoji, then open the full emoji picker, the chosen emoji would be inserted in the wrong place. This was an unintentional side effect of the changes in 75d9c16156f79f5a9f30a04433c3cb0ae82d914c This commit updates the `emojiSelected` logic to avoid mutating the 'selected' object, and also adds a test for this specific behaviour. --- .../app/mixins/textarea-text-manipulation.js | 12 ++++------ .../integration/components/d-editor-test.js | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js b/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js index 462324baf2..9e46e0c454 100644 --- a/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js +++ b/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js @@ -570,14 +570,12 @@ export default Mixin.create({ this.addText(selected, `:${code}:`); } } else { - let numOfRemovedChars = selected.pre.length - captures[1].length; - selected.pre = selected.pre.slice( - 0, - selected.pre.length - captures[1].length + let numOfRemovedChars = captures[1].length; + this._insertAt( + selected.start - numOfRemovedChars, + selected.end, + `${code}:` ); - selected.start -= numOfRemovedChars; - selected.end -= numOfRemovedChars; - this.addText(selected, `${code}:`); } }, }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js index d0cf310f8d..0b1a4f98c5 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-editor-test.js @@ -19,6 +19,7 @@ import formatTextWithSelection from "discourse/tests/helpers/d-editor-helper"; import hbs from "htmlbars-inline-precompile"; import { next } from "@ember/runloop"; import { withPluginApi } from "discourse/lib/plugin-api"; +import { isLegacyEmber } from "discourse-common/config/environment"; discourseModule("Integration | Component | d-editor", function (hooks) { setupRenderingTest(hooks); @@ -727,7 +728,27 @@ third line` await click( '.emoji-picker .section[data-section="smileys_&_emotion"] img.emoji[title="grinning"]' ); - assert.strictEqual(this.value, "hello world. :grinning:"); + assert.strictEqual( + this.value, + "hello world. :grinning:", + "it works when there is no partial emoji" + ); + + if (!isLegacyEmber()) { + await click("textarea.d-editor-input"); + await fillIn(".d-editor-input", "starting to type an emoji like :gri"); + jumpEnd(query("textarea.d-editor-input")); + await click("button.emoji"); + + await click( + '.emoji-picker .section[data-section="smileys_&_emotion"] img.emoji[title="grinning"]' + ); + assert.strictEqual( + this.value, + "starting to type an emoji like :grinning:", + "it works when there is a partial emoji" + ); + } }, }); From c00205730e7833f0ea3dcc3f3490f24dd65e979a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 16 Jun 2022 14:38:43 +0100 Subject: [PATCH 021/184] FIX: Ensure presence endpoints don't break the session (#17108) Presence endpoints are often called asynchronously at the same time as other request, and never need to modify the session. Skipping ensures that an unneeded cookie rotation doesn't race against another request and cause issues. This change brings presence in line with message-bus's behaviour. --- app/controllers/presence_controller.rb | 9 +++++++++ spec/requests/presence_controller_spec.rb | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/controllers/presence_controller.rb b/app/controllers/presence_controller.rb index 653250a8af..fe26e68fab 100644 --- a/app/controllers/presence_controller.rb +++ b/app/controllers/presence_controller.rb @@ -3,6 +3,7 @@ class PresenceController < ApplicationController skip_before_action :check_xhr before_action :ensure_logged_in, only: [:update] + before_action :skip_persist_session MAX_CHANNELS_PER_REQUEST ||= 50 @@ -75,4 +76,12 @@ class PresenceController < ApplicationController render json: response end + private + + def skip_persist_session + # Presence endpoints are often called asynchronously at the same time as other request, + # and never need to modify the session. Skipping ensures that an unneeded cookie rotation + # doesn't race against another request and cause issues. + session.options[:skip] = true + end end diff --git a/spec/requests/presence_controller_spec.rb b/spec/requests/presence_controller_spec.rb index 6fd04bf22e..279df8a8a0 100644 --- a/spec/requests/presence_controller_spec.rb +++ b/spec/requests/presence_controller_spec.rb @@ -113,6 +113,25 @@ describe PresenceController do expect(allowed_user_channel.user_ids).to eq([user.id]) expect(allowed_group_channel.user_ids).to eq([user.id]) end + + it "doesn't overwrite the session" do + sign_in(user) + + session_cookie_name = "_forum_session" + get "/session/csrf.json" + expect(response.status).to eq(200) + expect(response.cookies.keys).to include(session_cookie_name) + + client_id = SecureRandom.hex + post "/presence/update.json", params: { + client_id: client_id, + present_channels: [ + ch1.name, + ] + } + expect(response.status).to eq(200) + expect(response.cookies.keys).not_to include(session_cookie_name) + end end describe "#get" do From 691afa2593ce4104e64c1fdc18d99f6b45d87eed Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 16 Jun 2022 17:16:15 +0100 Subject: [PATCH 022/184] DEV: Handle emoji-picker and d-editor being destroyed simultaneously (#17109) --- .../javascripts/discourse/app/components/d-editor.js | 7 +++++++ .../discourse/app/templates/components/d-editor.hbs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index 37d570b268..e669a5685c 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -672,6 +672,13 @@ export default Component.extend(TextareaTextManipulation, { return true; }, + @action + onEmojiPickerClose() { + if (!(this.isDestroyed || this.isDestroying)) { + this.set("emojiPickerIsActive", false); + } + }, + actions: { emoji() { if (this.disabled) { diff --git a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs index a8bad744e3..f6ca96a8ea 100644 --- a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs @@ -74,5 +74,5 @@ isEditorFocused=isEditorFocused initialFilter=this.emojiFilter emojiSelected=(action "emojiSelected") - onEmojiPickerClose=(action (mut emojiPickerIsActive) false) + onEmojiPickerClose=onEmojiPickerClose }} From e5691362dfae219602b903fd5931f449d7994f04 Mon Sep 17 00:00:00 2001 From: Roman Rizzi Date: Thu, 16 Jun 2022 13:46:56 -0300 Subject: [PATCH 023/184] DEV: add smtp_should_reject to the receive_emails api key scope (#17110) --- app/models/api_key_scope.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/api_key_scope.rb b/app/models/api_key_scope.rb index 6c157566a2..1cc804fecf 100644 --- a/app/models/api_key_scope.rb +++ b/app/models/api_key_scope.rb @@ -64,7 +64,7 @@ class ApiKeyScope < ActiveRecord::Base list: { actions: %w[admin/users#index] }, }, email: { - receive_emails: { actions: %w[admin/email#handle_mail] } + receive_emails: { actions: %w[admin/email#handle_mail admin/email#smtp_should_reject] } }, badges: { create: { actions: %w[admin/badges#create] }, From dfba188bd6713bdcad57900e0d42b3d67c35c33e Mon Sep 17 00:00:00 2001 From: Kris Date: Thu, 16 Jun 2022 21:09:45 -0400 Subject: [PATCH 024/184] UX: remove extra whitespace in github onebox (#17111) --- lib/onebox/templates/githubblob.mustache | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/onebox/templates/githubblob.mustache b/lib/onebox/templates/githubblob.mustache index bfffc2555b..b76dfd2a33 100644 --- a/lib/onebox/templates/githubblob.mustache +++ b/lib/onebox/templates/githubblob.mustache @@ -39,15 +39,14 @@ }} -
-      
-        
    - {{#lines}} - {{data}} - {{/lines}} -
-
-
+ {{!-- pre and code are intentionally on the same line to control whitespace --}} +

+      
    + {{#lines}} + {{data}} + {{/lines}} +
+
{{/has_lines}} {{/binary}} From 4c810ca121da30ffdb2efde5ddbe6b114f113c59 Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Fri, 17 Jun 2022 02:33:42 +0000 Subject: [PATCH 025/184] FIX: JSON Schema editor layout issue with subarrays (#17112) --- .../common/admin/json_schema_editor.scss | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/common/admin/json_schema_editor.scss b/app/assets/stylesheets/common/admin/json_schema_editor.scss index 7f24897f1c..2fb20a20bb 100644 --- a/app/assets/stylesheets/common/admin/json_schema_editor.scss +++ b/app/assets/stylesheets/common/admin/json_schema_editor.scss @@ -7,7 +7,7 @@ } } - .card .je-object__container { + .je-object__container { border-bottom: 1px dashed var(--primary-low); padding-bottom: 1em; margin-bottom: 1em; @@ -30,7 +30,7 @@ } } - .btn-group:last-child { + > .btn-group:last-child { position: absolute; right: 0px; top: 0px; @@ -39,19 +39,16 @@ font-size: var(--font-down-1); } } + > .btn-group { + margin-top: 0; + display: flex; + align-items: stretch; + gap: 0.25em; + } } - .btn-group { - margin-top: 0; - display: flex; - align-items: stretch; - gap: 0.25em; - - .btn { - .d-icon { - margin-right: 0; - } - } + .btn-group .btn .d-icon { + margin-right: 0; } .card-body > .btn-group { @@ -61,9 +58,21 @@ } .table { - td { - vertical-align: top; - padding: 1em 0; + width: 100%; + margin-bottom: 0.5em; + + td, + th { + vertical-align: middle; + padding: 0.5em 0; + &:last-child { + text-align: right; + } + } + + .btn-group .btn { + margin-left: 0.25em; + font-size: var(--font-down-1); } td.compact { @@ -74,7 +83,8 @@ } } - input[type="text"] { + input[type="text"], + .form-group .form-control { margin-bottom: 0; width: 95%; @@ -85,7 +95,13 @@ } } - .desktop-view & #json-editor-holder { - min-width: 600px; + .row div[data-schematype="array"] { + padding: 0.5em; + background-color: var(--primary-very-low); + } + + .desktop-view & .modal-inner-container { + --modal-max-width: 75vw; + min-width: 55vw; } } From f618fdf17fbf4cce719cd16f1aa8f52d0f5fb84d Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Fri, 17 Jun 2022 12:24:15 +0800 Subject: [PATCH 026/184] Revert "DEV: Centralize user updates to a single MessageBus channel. (#17058)" (#17115) This reverts commit 94c3bbc2d1b7bf76d823950fba16fd00b2eced08. At this current point in time, we do not have enough data on whether this centralisation is the trade-offs of coupling features into a single channel. --- .../subscribe-user-notifications.js | 24 ++++++------- .../acceptance/sidebar-topics-section-test.js | 7 ++-- .../tests/acceptance/user-status-test.js | 10 ++---- app/models/user.rb | 34 ++++--------------- app/models/user_stat.rb | 10 +++--- spec/lib/post_creator_spec.rb | 8 ++--- spec/models/draft_spec.rb | 14 ++++---- spec/requests/user_status_controller_spec.rb | 20 ++++------- ...er_notification_schedule_processor_spec.rb | 5 ++- 9 files changed, 47 insertions(+), 85 deletions(-) diff --git a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js index 373327f532..bf4235ba85 100644 --- a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js +++ b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js @@ -108,19 +108,17 @@ export default { user.notification_channel_position ); - bus.subscribe(`/user-updates/${user.id}`, (data) => { - switch (data.type) { - case "drafts": - user.updateDraftProperties(data.payload); - break; - case "do_not_disturb": - user.updateDoNotDisturbStatus(data.payload.ends_at); - break; - case "user_status": - user.set("status", data.payload); - appEvents.trigger("user-status:changed"); - break; - } + bus.subscribe(`/user-drafts/${user.id}`, (data) => { + user.updateDraftProperties(data); + }); + + bus.subscribe(`/do-not-disturb/${user.get("id")}`, (data) => { + user.updateDoNotDisturbStatus(data.ends_at); + }); + + bus.subscribe(`/user-status/${user.id}`, (data) => { + user.set("status", data); + appEvents.trigger("user-status:changed"); }); const site = container.lookup("site:main"); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js index 6572c5a316..898faa49bc 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js @@ -242,11 +242,8 @@ acceptance("Sidebar - Topics Section", function (needs) { async function (assert) { await visit("/t/280"); - publishToMessageBus(`/user-updates/${loggedInUser().id}`, { - type: "drafts", - payload: { - draft_count: 1, - }, + publishToMessageBus(`/user-drafts/${loggedInUser().id}`, { + draft_count: 1, }); await settled(); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js index d78472eadf..67e6815ccb 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js @@ -17,17 +17,13 @@ acceptance("User Status", function (needs) { needs.pretender((server, helper) => { server.put("/user-status.json", () => { - publishToMessageBus(`/user-updates/${userId}`, { - type: "user_status", - payload: { description: userStatus }, + publishToMessageBus(`/user-status/${userId}`, { + description: userStatus, }); return helper.response({ success: true }); }); server.delete("/user-status.json", () => { - publishToMessageBus(`/user-updates/${userId}`, { - type: "user_status", - payload: null, - }); + publishToMessageBus(`/user-status/${userId}`, null); return helper.response({ success: true }); }); }); diff --git a/app/models/user.rb b/app/models/user.rb index 50679a363a..2ca32db206 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -657,38 +657,16 @@ class User < ActiveRecord::Base MessageBus.publish("/notification/#{id}", payload, user_ids: [id]) end - PUBLISH_USER_STATUS_TYPE = "user_status" - PUBLISH_DO_NOT_STATUS_TYPE = "do_not_disturb" - PUBLISH_DRAFTS_TYPE = "drafts" - - def self.publish_updates_channel(user_id) - "/user-updates/#{user_id}" - end - - def self.publish_updates(user_id:, type:, payload:) - MessageBus.publish( - publish_updates_channel(user_id), - { - type: type, - payload: payload - }, - user_ids: [user_id] - ) - end - - def publish_updates(type:, payload:) - self.class.publish_updates(user_id: id, type: type, payload: payload) - end - def publish_do_not_disturb(ends_at: nil) - publish_updates(type: PUBLISH_DO_NOT_STATUS_TYPE, payload: { ends_at: ends_at&.httpdate }) + MessageBus.publish("/do-not-disturb/#{id}", { ends_at: ends_at&.httpdate }, user_ids: [id]) end def publish_user_status(status) - publish_updates( - type: PUBLISH_USER_STATUS_TYPE, - payload: status ? { description: status.description, emoji: status.emoji } : nil - ) + payload = status ? + { description: status.description, emoji: status.emoji } : + nil + + MessageBus.publish("/user-status/#{id}", payload, user_ids: [id]) end def password=(password) diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index d005f2681f..e7d41994d1 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -214,13 +214,13 @@ class UserStat < ActiveRecord::Base RETURNING draft_count, (SELECT 1 FROM drafts WHERE user_id = :user_id AND draft_key = :new_topic) SQL - User.publish_updates( - user_id: user_id, - type: User::PUBLISH_DRAFTS_TYPE, - payload: { + MessageBus.publish( + "/user-drafts/#{user_id}", + { draft_count: draft_count, has_topic_draft: !!has_topic_draft - } + }, + user_ids: [user_id] ) else DB.exec <<~SQL diff --git a/spec/lib/post_creator_spec.rb b/spec/lib/post_creator_spec.rb index 51e7a2efd5..1c929c2609 100644 --- a/spec/lib/post_creator_spec.rb +++ b/spec/lib/post_creator_spec.rb @@ -174,9 +174,9 @@ describe PostCreator do "/latest", "/topic/#{created_post.topic_id}", "/topic/#{created_post.topic_id}", - User.publish_updates_channel(admin.id), - User.publish_updates_channel(admin.id), - User.publish_updates_channel(admin.id), + "/user-drafts/#{admin.id}", + "/user-drafts/#{admin.id}", + "/user-drafts/#{admin.id}", ].sort ) @@ -205,7 +205,7 @@ describe PostCreator do user_action = messages.find { |m| m.channel == "/u/#{p.user.username}" } expect(user_action).not_to eq(nil) - draft_count = messages.find { |m| m.channel == User.publish_updates_channel(p.user_id) } + draft_count = messages.find { |m| m.channel == "/user-drafts/#{p.user_id}" } expect(draft_count).not_to eq(nil) expect(messages.filter { |m| m.channel != "/distributed_hash" }.length).to eq(7) diff --git a/spec/models/draft_spec.rb b/spec/models/draft_spec.rb index 5b03458a18..12b4cf69ce 100644 --- a/spec/models/draft_spec.rb +++ b/spec/models/draft_spec.rb @@ -179,19 +179,21 @@ describe Draft do it 'updates draft count when a draft is created or destroyed' do Draft.set(Fabricate(:user), Draft::NEW_TOPIC, 0, "data") - messages = MessageBus.track_publish("/user-updates/#{user.id}") do + messages = MessageBus.track_publish("/user-drafts/#{user.id}") do Draft.set(user, Draft::NEW_TOPIC, 0, "data") end - expect(messages.first.data[:payload][:draft_count]).to eq(1) - expect(messages.first.data[:payload][:has_topic_draft]).to eq(true) + expect(messages.first.data[:draft_count]).to eq(1) + expect(messages.first.data[:has_topic_draft]).to eq(true) + expect(messages.first.user_ids).to contain_exactly(user.id) - messages = MessageBus.track_publish("/user-updates/#{user.id}") do + messages = MessageBus.track_publish("/user-drafts/#{user.id}") do Draft.where(user: user).destroy_all end - expect(messages.first.data[:payload][:draft_count]).to eq(0) - expect(messages.first.data[:payload][:has_topic_draft]).to eq(false) + expect(messages.first.data[:draft_count]).to eq(0) + expect(messages.first.data[:has_topic_draft]).to eq(false) + expect(messages.first.user_ids).to contain_exactly(user.id) end describe '#stream' do diff --git a/spec/requests/user_status_controller_spec.rb b/spec/requests/user_status_controller_spec.rb index b5364ecc77..80c258fbbc 100644 --- a/spec/requests/user_status_controller_spec.rb +++ b/spec/requests/user_status_controller_spec.rb @@ -50,15 +50,11 @@ describe UserStatusController do it "publishes to message bus" do status = "off to dentist" - - messages = MessageBus.track_publish(User.publish_updates_channel(user.id)) do - put "/user-status.json", params: { description: status } - expect(response.status).to eq(200) - end + messages = MessageBus.track_publish { put "/user-status.json", params: { description: status } } expect(messages.size).to eq(1) - expect(messages[0].data[:type]).to eq(User::PUBLISH_USER_STATUS_TYPE) - expect(messages[0].data[:payload][:description]).to eq(status) + expect(messages[0].channel).to eq("/user-status/#{user.id}") + expect(messages[0].data[:description]).to eq(status) expect(messages[0].user_ids).to eq([user.id]) end end @@ -97,15 +93,11 @@ describe UserStatusController do end it "publishes to message bus" do - messages = MessageBus.track_publish(User.publish_updates_channel(user.id)) do - delete "/user-status.json" - - expect(response.status).to eq(200) - end + messages = MessageBus.track_publish { delete "/user-status.json" } expect(messages.size).to eq(1) - expect(messages[0].data[:type]).to eq(User::PUBLISH_USER_STATUS_TYPE) - expect(messages[0].data[:payload]).to eq(nil) + expect(messages[0].channel).to eq("/user-status/#{user.id}") + expect(messages[0].data).to eq(nil) expect(messages[0].user_ids).to eq([user.id]) end end diff --git a/spec/services/user_notification_schedule_processor_spec.rb b/spec/services/user_notification_schedule_processor_spec.rb index 1fbab38723..02a10c644d 100644 --- a/spec/services/user_notification_schedule_processor_spec.rb +++ b/spec/services/user_notification_schedule_processor_spec.rb @@ -143,13 +143,12 @@ describe UserNotificationScheduleProcessor do user.user_option.update(timezone: "UTC") schedule = standard_schedule travel_to Time.new(2020, 12, 31, 1, 0, 0, "+00:00") do - messages = MessageBus.track_publish(User.publish_updates_channel(user.id)) do + messages = MessageBus.track_publish("/do-not-disturb/#{user.id}") do UserNotificationScheduleProcessor.create_do_not_disturb_timings_for(schedule) end expect(messages.size).to eq(1) - expect(messages[0].data[:type]).to eq(User::PUBLISH_DO_NOT_STATUS_TYPE) - expect(messages[0].data[:payload][:ends_at]).to eq(Time.new(2020, 12, 31, 7, 59, 0, "+00:00").httpdate) + expect(messages[0].data[:ends_at]).to eq(Time.new(2020, 12, 31, 7, 59, 0, "+00:00").httpdate) expect(messages[0].user_ids).to contain_exactly(user.id) end end From 97e21b80a0f6c1a6db2738e063ab9556e76ff2a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jun 2022 10:47:06 +0200 Subject: [PATCH 027/184] Build(deps-dev): Bump minitest from 5.15.0 to 5.16.0 (#17103) Bumps [minitest](https://github.com/seattlerb/minitest) from 5.15.0 to 5.16.0. - [Release notes](https://github.com/seattlerb/minitest/releases) - [Changelog](https://github.com/minitest/minitest/blob/master/History.rdoc) - [Commits](https://github.com/seattlerb/minitest/compare/v5.15.0...v5.16.0) --- updated-dependencies: - dependency-name: minitest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d0340e2dc3..23da814164 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,7 +241,7 @@ GEM mini_sql (1.4.0) mini_suffix (0.3.3) ffi (~> 1.9) - minitest (5.15.0) + minitest (5.16.0) mocha (1.14.0) msgpack (1.5.2) multi_json (1.15.0) From 1166db12b4540e8d8b9e576dde3d672b993b4edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guitaut?= Date: Fri, 17 Jun 2022 11:07:58 +0200 Subject: [PATCH 028/184] FIX: Make watched words uploads work as intended (#17097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FIX: Make watched words uploads work as intended Currently when we upload a file containing watched words, it will always add the words to the action that was initially selected: this is the `block` action by default but if changing manually the action in the URL to `flag` for example, then this action will be selected and uploaded watched words will be categorised as `flag` no matter what. The problem lies with how the component works: it’s an Uppy object where extra data is defined to provide an action key to the server but when navigating to another listed action, while this action key is properly updated on the component itself, the underlying Uppy object has already been created and doesn’t care about the new value. This patch solves this by using the `_perFileData` method instead of `data`: the former is merged just before uploading a file whereas the latter is used when the Uppy object is created. --- .../addon/components/watched-word-uploader.js | 6 +- .../components/watched-word-uploader-test.js | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/discourse/tests/integration/components/watched-word-uploader-test.js diff --git a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js index 8c2864b69b..efbacb9c0b 100644 --- a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js +++ b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js @@ -3,7 +3,6 @@ import I18n from "I18n"; import UppyUploadMixin from "discourse/mixins/uppy-upload"; import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; -import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend(UppyUploadMixin, { type: "txt", @@ -16,9 +15,8 @@ export default Component.extend(UppyUploadMixin, { return { skipValidation: true }; }, - @discourseComputed("actionKey") - data(actionKey) { - return { action_key: actionKey }; + _perFileData() { + return { action_key: this.actionKey }; }, uploadDone() { diff --git a/app/assets/javascripts/discourse/tests/integration/components/watched-word-uploader-test.js b/app/assets/javascripts/discourse/tests/integration/components/watched-word-uploader-test.js new file mode 100644 index 0000000000..04f65e36cc --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/watched-word-uploader-test.js @@ -0,0 +1,57 @@ +import componentTest, { + setupRenderingTest, +} from "discourse/tests/helpers/component-test"; +import { + createFile, + discourseModule, +} from "discourse/tests/helpers/qunit-helpers"; +import hbs from "htmlbars-inline-precompile"; +import pretender, { response } from "discourse/tests/helpers/create-pretender"; +import { click, waitFor } from "@ember/test-helpers"; +import { isLegacyEmber } from "discourse-common/config/environment"; + +if (!isLegacyEmber()) { + discourseModule( + "Integration | Component | watched-word-uploader", + function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () { + pretender.post( + "/admin/customize/watched_words/upload.json", + function () { + return response(200, {}); + } + ); + }); + + componentTest("sets the proper action key on uploads", { + template: hbs`{{watched-word-uploader + id="watched-word-uploader" + actionKey=actionNameKey + done=doneUpload + }}`, + + async test(assert) { + const done = assert.async(); + this.set("actionNameKey", "flag"); + this.set("doneUpload", function () { + assert.equal( + Object.entries(this._uppyInstance.getState().files)[0][1].meta + .action_key, + "flag" + ); + done(); + }); + + const words = createFile("watched-words.txt"); + await this.container + .lookup("service:app-events") + .trigger("upload-mixin:watched-word-uploader:add-files", words); + await waitFor(".bootbox span.d-button-label"); + await click(".bootbox span.d-button-label"); + }, + }); + } + ); +} From 6695d148d689e83b8e20381aedf6bd3326a6c55b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 17 Jun 2022 11:07:32 +0100 Subject: [PATCH 029/184] DEV: Cleanup topic thumbnail enqueuing (#17119) - Ensure only one job is enqueued when there are no extra sizes requested by themes - Update and un-skip relevant tests --- app/models/topic.rb | 3 +-- spec/integration/topic_thumbnail_spec.rb | 13 ++++++++----- spec/serializers/topic_view_serializer_spec.rb | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/models/topic.rb b/app/models/topic.rb index db73eee2ac..5eaa32f8d6 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -85,8 +85,7 @@ class Topic < ActiveRecord::Base if SiteSetting.create_thumbnails && enqueue_if_missing && records.length < thumbnail_sizes.length && - Discourse.redis.set(thumbnail_job_redis_key(thumbnail_sizes), 1, nx: true, ex: 1.minute) - + Discourse.redis.set(thumbnail_job_redis_key(extra_sizes), 1, nx: true, ex: 1.minute) Jobs.enqueue(:generate_topic_thumbnails, { topic_id: id, extra_sizes: extra_sizes }) end diff --git a/spec/integration/topic_thumbnail_spec.rb b/spec/integration/topic_thumbnail_spec.rb index afbe27eaf3..5a2bcdcba4 100644 --- a/spec/integration/topic_thumbnail_spec.rb +++ b/spec/integration/topic_thumbnail_spec.rb @@ -37,13 +37,18 @@ describe "Topic Thumbnails" do end it "includes the theme specified resolutions" do - pending "We're creating two generate topic thumbnails jobs instead of one" - topic_json = nil expect do topic_json = get_topic - end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1) + end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(2) + + expect( + Jobs::GenerateTopicThumbnails.jobs.map { |j| j["args"][0]["extra_sizes"] } + ).to eq([ + nil, # Job for core/plugin sizes + [[10, 10], [20, 20], [30, 30]]] # Job for theme sizes + ) thumbnails = topic_json["thumbnails"] @@ -92,8 +97,6 @@ describe "Topic Thumbnails" do end it "includes the theme specified resolutions" do - pending "We're creating two generate topic thumbnails jobs instead of one" - topic_json = nil expect do diff --git a/spec/serializers/topic_view_serializer_spec.rb b/spec/serializers/topic_view_serializer_spec.rb index 753a054e50..27a789cbca 100644 --- a/spec/serializers/topic_view_serializer_spec.rb +++ b/spec/serializers/topic_view_serializer_spec.rb @@ -70,7 +70,7 @@ describe TopicViewSerializer do it 'should have thumbnail jobs enqueued' do SiteSetting.create_thumbnails = true - Discourse.redis.del(topic.thumbnail_job_redis_key(Topic.thumbnail_sizes)) + Discourse.redis.del(topic.thumbnail_job_redis_key([])) json = nil expect do From e0ff367b49b09d0e35d4a2be70c168e41a494c3f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 17 Jun 2022 11:50:30 +0100 Subject: [PATCH 030/184] DEV: Stop logging error response body in FileHelper (#17120) This doesn't cope well with gzipped, binary, or large responses. Ideally we would teach FinalDestination to safely retrieve and decode some of the response body. But for now, let's remove the broken implementation. --- lib/file_helper.rb | 2 +- spec/lib/file_helper_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/file_helper.rb b/lib/file_helper.rb index 43427c3228..31530a12e9 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -73,7 +73,7 @@ class FileHelper # attempt error API compatibility io = FakeIO.new io.status = [response.code, ""] - raise OpenURI::HTTPError.new("#{response.code} Error: #{response.body}", io) + raise OpenURI::HTTPError.new("#{response.code} Error", io) else log(:error, "FinalDestination did not work for: #{url}") if verbose throw :done diff --git a/spec/lib/file_helper_spec.rb b/spec/lib/file_helper_spec.rb index ae1d721f52..54c2233a3a 100644 --- a/spec/lib/file_helper_spec.rb +++ b/spec/lib/file_helper_spec.rb @@ -29,7 +29,7 @@ describe FileHelper do expect(e.io.status[0]).to eq("404") raise end - end.to raise_error(OpenURI::HTTPError, "404 Error: 404") + end.to raise_error(OpenURI::HTTPError, "404 Error") end it "does not follow redirects if instructed not to" do From f27f95b2e93405ea7bc865e2929004fafa57cede Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 13:44:52 +0200 Subject: [PATCH 031/184] DEV: Don't print uppy warnings in test env (#17121) --- app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js b/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js index 51e5907ebd..421229e999 100644 --- a/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js +++ b/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js @@ -1,6 +1,7 @@ import { BasePlugin } from "@uppy/core"; import { Promise } from "rsvp"; import { warn } from "@ember/debug"; +import { isTesting } from "discourse-common/config/environment"; export class UppyPluginBase extends BasePlugin { constructor(uppy, opts) { @@ -9,7 +10,9 @@ export class UppyPluginBase extends BasePlugin { } _consoleWarn(msg) { - warn(`[${this.id}] ${msg}`, { id: `discourse.${this.id}` }); + if (!isTesting()) { + warn(`[${this.id}] ${msg}`, { id: `discourse.${this.id}` }); + } } _consoleDebug(msg) { From 79d3b25d97db6c3fe7cbd9a526b4697fab0636a9 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 13:45:05 +0200 Subject: [PATCH 032/184] DEV: Fix various frontend warnings (#17122) 3 computed property overrides and a tippy warning --- .../discourse/app/components/d-popover.js | 16 +++++++++++----- .../app/templates/components/emoji-uploader.hbs | 2 +- .../discourse/tests/helpers/create-pretender.js | 2 +- .../integration/components/d-navigation-test.js | 4 +--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/d-popover.js b/app/assets/javascripts/discourse/app/components/d-popover.js index 1a3b0ea9b0..930f16348f 100644 --- a/app/assets/javascripts/discourse/app/components/d-popover.js +++ b/app/assets/javascripts/discourse/app/components/d-popover.js @@ -80,12 +80,18 @@ export default class DiscoursePopover extends Component { }, }; + const target = document + .getElementById(this.componentId) + .querySelector( + ':scope > .d-popover-trigger, :scope > .btn, :scope > [role="button"]' + ); + + if (!target) { + return null; + } + const instance = tippy( - document - .getElementById(this.componentId) - .querySelector( - ':scope > .d-popover-trigger, :scope > .btn, :scope > [role="button"]' - ), + target, Object.assign({}, baseOptions, this.options || {}) ); diff --git a/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs b/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs index 32c118bc3f..519f95e4e2 100644 --- a/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs @@ -40,7 +40,7 @@ type="file" multiple="true" accept=".png,.gif"> - {{d-button class="btn-primary" computedLabel=buttonLabel icon="plus" action=(action "chooseFiles") disabled=uploading}} + {{d-button class="btn-primary" translatedLabel=buttonLabel icon="plus" action=(action "chooseFiles") disabled=uploading}} diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js index 7c57057ebd..dbabe18be0 100644 --- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js +++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js @@ -269,7 +269,7 @@ export function applyDefaultHandlers(pretender) { return response(fixturesByUrl["/search.json"]); } else if (request.queryParams.q === "discourse visited") { const obj = JSON.parse(JSON.stringify(fixturesByUrl["/search.json"])); - obj.topics.firstObject.visited = true; + obj.topics.firstObject.last_read_post_number = 1; return response(obj); } else if ( request.queryParams.q === "discourse in:personal" || diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-navigation-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-navigation-test.js index 4366428196..54eedc875b 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/d-navigation-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/d-navigation-test.js @@ -19,9 +19,7 @@ discourseModule("Integration | Component | d-navigation", function (hooks) { const categories = this.site.categoriesList .filter((category) => !category.parent_category_id) .slice(0, 4); - this.site.setProperties({ - categoriesList: categories, - }); + this.site.setProperties({ categories }); this.currentUser.set( "indirectly_muted_category_ids", categories.slice(0, 3).map((category) => category.id) From fddd6fd5e009cbb2873a8d9d7a5ade4899a41dcd Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 13:45:16 +0200 Subject: [PATCH 033/184] DEV: Fix an rspec warning (#17123) ``` WARNING: Using `expect { }.not_to raise_error(SpecificErrorClass)` risks false positives, since literally any other error would cause the expectation to pass, including those raised by Ruby (e.g. `NoMethodError`, `NameError` and `ArgumentError`), meaning the code you are intending to test may not even get reached. Instead consider using `expect { }.not_to raise_error` or `expect { }.to raise_error(DifferentSpecificErrorClass)`. This message can be suppressed by setting: `RSpec::Expectations.configuration.on_potential_false_positives = :nothing`. Called from /var/www/discourse/spec/lib/retrieve_title_spec.rb:155:in `block (3 levels) in
'. ``` --- spec/lib/retrieve_title_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/retrieve_title_spec.rb b/spec/lib/retrieve_title_spec.rb index 6711f233f1..74d56ce08e 100644 --- a/spec/lib/retrieve_title_spec.rb +++ b/spec/lib/retrieve_title_spec.rb @@ -152,7 +152,7 @@ describe RetrieveTitle do it "it ignores Net::ReadTimeout errors" do stub_request(:get, "https://example.com").to_raise(Net::ReadTimeout) - expect { RetrieveTitle.crawl("https://example.com") }.not_to raise_error(Net::ReadTimeout) + expect { RetrieveTitle.crawl("https://example.com") }.not_to raise_error end end From fcb4e5a1a15631cef9c20489866a68600ecf768d Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 14:50:21 +0200 Subject: [PATCH 034/184] DEV: Make wizard an ember addon (#17027) Co-authored-by: David Taylor --- .github/workflows/tests.yml | 5 - .../discourse-common/addon/resolver.js | 40 ++++++- .../discourse/app/components/global-notice.js | 6 + .../javascripts/discourse/app/lib/url.js | 1 - .../templates/components/global-notice.hbs | 44 +++++--- .../javascripts/discourse/ember-cli-build.js | 3 + .../discourse/lib/bootstrap-json/index.js | 8 +- .../discourse/lib/translation-plugin.js | 3 +- app/assets/javascripts/discourse/package.json | 5 +- .../public/assets/scripts/discourse-boot.js | 5 +- .../discourse/tests/acceptance/wizard-test.js | 66 +++++++++++ .../javascripts/discourse/tests/index.html | 2 +- .../components/wizard-invite-list-test.js | 100 +++++++++++++++++ .../tests/unit/models/wizard-field-test.js | 35 ++++++ app/assets/javascripts/package.json | 3 +- .../addon/components/icon-picker.js | 2 +- .../select-kit/addon/lib/ajax-helper.js | 8 -- .../select-kit/addon/mixins/tags.js | 2 +- app/assets/javascripts/wizard-application.js | 13 --- app/assets/javascripts/wizard-shims.js | 47 -------- app/assets/javascripts/wizard-start.js | 5 - app/assets/javascripts/wizard-vendor.js | 10 -- app/assets/javascripts/wizard.js | 2 + app/assets/javascripts/wizard/.npmrc | 1 + .../components/homepage-preview.js | 0 .../components/image-preview-favicon.js | 0 .../components/image-preview-large-icon.js | 0 .../components/image-preview-logo-small.js | 0 .../components/image-preview-logo.js | 0 .../components/invite-list-user.js | 0 .../{ => addon}/components/invite-list.js | 0 .../{ => addon}/components/staff-count.js | 0 .../{ => addon}/components/styling-preview.js | 18 ++- .../{ => addon}/components/wizard-canvas.js | 0 .../components/wizard-field-checkboxes.js | 0 .../components/wizard-field-dropdown.js | 9 +- .../components/wizard-field-image.js | 3 +- .../components/wizard-field-radio.js | 0 .../components/wizard-field-textarea.js | 0 .../{ => addon}/components/wizard-field.js | 0 .../components/wizard-image-preview.js | 0 .../components/wizard-step-form.js | 0 .../{ => addon}/components/wizard-step.js | 2 + .../wizard/addon/controllers/wizard-step.js | 24 ++++ .../wizard/{ => addon}/lib/preview.js | 0 .../wizard/{ => addon}/mixins/valid-state.js | 0 .../wizard/{ => addon}/models/step.js | 10 +- .../wizard/{ => addon}/models/wizard-field.js | 0 .../wizard/{ => addon}/models/wizard.js | 10 +- .../wizard/addon/routes/wizard-index.js | 8 ++ .../wizard/addon/routes/wizard-route-map.js | 5 + .../wizard/addon/routes/wizard-step.js | 17 +++ .../javascripts/wizard/addon/routes/wizard.js | 21 ++++ .../templates/components/invite-list-user.hbs | 0 .../templates/components/invite-list.hbs | 0 .../templates/components/staff-count.hbs | 0 .../templates/components/styling-preview.hbs | 0 .../templates/components/theme-preview.hbs | 0 .../components/wizard-field-checkbox.hbs | 0 .../components/wizard-field-checkboxes.hbs | 0 .../components/wizard-field-dropdown.hbs | 0 .../components/wizard-field-image.hbs | 0 .../components/wizard-field-radio.hbs | 30 +++++ .../components/wizard-field-text.hbs | 0 .../components/wizard-field-textarea.hbs | 0 .../templates/components/wizard-field.hbs | 0 .../components/wizard-image-preview.hbs | 0 .../templates/components/wizard-step.hbs | 0 .../wizard/addon/templates/step.hbs | 6 + .../wizard/addon/templates/wizard.hbs | 46 ++++++++ .../addon/test-helpers/wizard-pretender.js | 59 ++++++++++ app/assets/javascripts/wizard/app/.gitkeep | 0 .../wizard/components/radio-button.js | 21 ---- .../wizard/controllers/application.js | 25 ----- .../javascripts/wizard/controllers/step.js | 29 ----- .../javascripts/wizard/ember-cli-build.js | 9 ++ app/assets/javascripts/wizard/index.js | 20 ++++ .../wizard/initializers/load-helpers.js | 14 --- app/assets/javascripts/wizard/lib/ajax.js | 33 ------ app/assets/javascripts/wizard/package.json | 57 ++++++++++ app/assets/javascripts/wizard/router.js | 14 --- .../javascripts/wizard/routes/application.js | 14 --- app/assets/javascripts/wizard/routes/index.js | 7 -- app/assets/javascripts/wizard/routes/step.js | 17 --- .../wizard/templates/application.hbs | 43 ------- .../templates/components/radio-button.hbs | 17 --- .../components/wizard-field-radio.hbs | 11 -- .../javascripts/wizard/templates/step.hbs | 1 - .../wizard/test/acceptance/wizard-test.js | 78 ------------- .../test/components/invite-list-test.js | 82 -------------- .../wizard/test/helpers/component-test.js | 18 --- .../wizard/test/helpers/start-app.js | 19 ---- .../wizard/test/models/wizard-field-test.js | 36 ------ .../javascripts/wizard/test/test_helper.js | 76 ------------- .../wizard/test/wizard-pretender.js | 106 ------------------ app/assets/javascripts/wizard/wizard.js | 29 ----- app/assets/stylesheets/wizard.scss | 35 ++---- app/controllers/bootstrap_controller.rb | 6 + app/controllers/wizard_controller.rb | 19 ++-- app/jobs/regular/critical_user_email.rb | 2 - .../common/_discourse_stylesheet.html.erb | 4 + app/views/layouts/application.html.erb | 8 +- .../layouts/finish_installation.html.erb | 1 + app/views/qunit/index.html.erb | 1 + app/views/wizard/index.html.erb | 29 ----- app/views/wizard/qunit.html.erb | 15 --- config/application.rb | 2 +- config/initializers/assets.rb | 18 ++- lib/discourse_js_processor.rb | 1 - lib/js_locale_helper.rb | 14 ++- lib/tasks/assets.rake | 1 + 111 files changed, 688 insertions(+), 928 deletions(-) create mode 100644 app/assets/javascripts/discourse/tests/acceptance/wizard-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js create mode 100644 app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js delete mode 100644 app/assets/javascripts/select-kit/addon/lib/ajax-helper.js delete mode 100644 app/assets/javascripts/wizard-application.js delete mode 100644 app/assets/javascripts/wizard-shims.js delete mode 100644 app/assets/javascripts/wizard-start.js delete mode 100644 app/assets/javascripts/wizard-vendor.js create mode 100644 app/assets/javascripts/wizard.js create mode 100644 app/assets/javascripts/wizard/.npmrc rename app/assets/javascripts/wizard/{ => addon}/components/homepage-preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-favicon.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-large-icon.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-logo-small.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-logo.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/invite-list-user.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/invite-list.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/staff-count.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/styling-preview.js (91%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-canvas.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-checkboxes.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-dropdown.js (83%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-image.js (96%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-radio.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-textarea.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-image-preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-step-form.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-step.js (99%) create mode 100644 app/assets/javascripts/wizard/addon/controllers/wizard-step.js rename app/assets/javascripts/wizard/{ => addon}/lib/preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/mixins/valid-state.js (100%) rename app/assets/javascripts/wizard/{ => addon}/models/step.js (87%) rename app/assets/javascripts/wizard/{ => addon}/models/wizard-field.js (100%) rename app/assets/javascripts/wizard/{ => addon}/models/wizard.js (84%) create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-index.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-route-map.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-step.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard.js rename app/assets/javascripts/wizard/{ => addon}/templates/components/invite-list-user.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/invite-list.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/staff-count.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/styling-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/theme-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-checkbox.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-checkboxes.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-dropdown.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-image.hbs (100%) create mode 100644 app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-text.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-textarea.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-image-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-step.hbs (100%) create mode 100644 app/assets/javascripts/wizard/addon/templates/step.hbs create mode 100644 app/assets/javascripts/wizard/addon/templates/wizard.hbs create mode 100644 app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js create mode 100644 app/assets/javascripts/wizard/app/.gitkeep delete mode 100644 app/assets/javascripts/wizard/components/radio-button.js delete mode 100644 app/assets/javascripts/wizard/controllers/application.js delete mode 100644 app/assets/javascripts/wizard/controllers/step.js create mode 100644 app/assets/javascripts/wizard/ember-cli-build.js create mode 100644 app/assets/javascripts/wizard/index.js delete mode 100644 app/assets/javascripts/wizard/initializers/load-helpers.js delete mode 100644 app/assets/javascripts/wizard/lib/ajax.js create mode 100644 app/assets/javascripts/wizard/package.json delete mode 100644 app/assets/javascripts/wizard/router.js delete mode 100644 app/assets/javascripts/wizard/routes/application.js delete mode 100644 app/assets/javascripts/wizard/routes/index.js delete mode 100644 app/assets/javascripts/wizard/routes/step.js delete mode 100644 app/assets/javascripts/wizard/templates/application.hbs delete mode 100644 app/assets/javascripts/wizard/templates/components/radio-button.hbs delete mode 100644 app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs delete mode 100644 app/assets/javascripts/wizard/templates/step.hbs delete mode 100644 app/assets/javascripts/wizard/test/acceptance/wizard-test.js delete mode 100644 app/assets/javascripts/wizard/test/components/invite-list-test.js delete mode 100644 app/assets/javascripts/wizard/test/helpers/component-test.js delete mode 100644 app/assets/javascripts/wizard/test/helpers/start-app.js delete mode 100644 app/assets/javascripts/wizard/test/models/wizard-field-test.js delete mode 100644 app/assets/javascripts/wizard/test/test_helper.js delete mode 100644 app/assets/javascripts/wizard/test/wizard-pretender.js delete mode 100644 app/assets/javascripts/wizard/wizard.js delete mode 100644 app/views/wizard/index.html.erb delete mode 100644 app/views/wizard/qunit.html.erb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ab6fa2dfc..b2d486670e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -158,11 +158,6 @@ jobs: run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000'] timeout-minutes: 30 - - name: Wizard QUnit (Legacy) - if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core' - run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/wizard/qunit'] - timeout-minutes: 10 - - name: Plugin QUnit (Legacy) if: matrix.build_type == 'frontend-legacy' && matrix.target == 'plugins' run: QUNIT_EMBER_CLI=0 bin/rake plugin:qunit['*','1200000'] diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js index c7914aa912..c0b8162866 100644 --- a/app/assets/javascripts/discourse-common/addon/resolver.js +++ b/app/assets/javascripts/discourse-common/addon/resolver.js @@ -93,6 +93,7 @@ export function buildResolver(baseName) { if (split.length > 1) { const appBase = `${baseName}/${split[0]}s/`; const adminBase = "admin/" + split[0] + "s/"; + const wizardBase = "wizard/" + split[0] + "s/"; // Allow render 'admin/templates/xyz' too split[1] = split[1].replace(".templates", "").replace("/templates", ""); @@ -101,7 +102,8 @@ export function buildResolver(baseName) { let dashed = dasherize(split[1].replace(/\./g, "/")); if ( requirejs.entries[appBase + dashed] || - requirejs.entries[adminBase + dashed] + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] ) { return split[0] + ":" + dashed; } @@ -110,7 +112,8 @@ export function buildResolver(baseName) { dashed = dasherize(split[1].replace(/\./g, "-")); if ( requirejs.entries[appBase + dashed] || - requirejs.entries[adminBase + dashed] + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] ) { return split[0] + ":" + dashed; } @@ -253,6 +256,7 @@ export function buildResolver(baseName) { templates[decamelized.replace(/\_/, "/")] || templates[`${baseName}/templates/${withoutType}`] || this.findAdminTemplate(parsedName) || + this.findWizardTemplate(parsedName) || this.findUnderscoredTemplate(parsedName) ); }, @@ -296,5 +300,37 @@ export function buildResolver(baseName) { ); } }, + + findWizardTemplate(parsedName) { + let decamelized = decamelize(parsedName.fullNameWithoutType); + if (decamelized.startsWith("components")) { + let comPath = `wizard/templates/${decamelized}`; + const compTemplate = + Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath]; + if (compTemplate) { + return compTemplate; + } + } + + if (decamelized === "javascripts/wizard") { + return Ember.TEMPLATES["wizard/templates/wizard"]; + } + + if ( + decamelized.startsWith("wizard") || + decamelized.startsWith("javascripts/wizard") + ) { + decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/"); + decamelized = decamelized.replace(/^wizard\./, "wizard/templates/"); + decamelized = decamelized.replace(/\./g, "_"); + + const dashed = decamelized.replace(/_/g, "-"); + return ( + Ember.TEMPLATES[decamelized] || + Ember.TEMPLATES[dashed] || + Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")] + ); + } + }, }); } diff --git a/app/assets/javascripts/discourse/app/components/global-notice.js b/app/assets/javascripts/discourse/app/components/global-notice.js index 7432143273..b2097becb6 100644 --- a/app/assets/javascripts/discourse/app/components/global-notice.js +++ b/app/assets/javascripts/discourse/app/components/global-notice.js @@ -50,6 +50,8 @@ const Notice = EmberObject.extend({ }); export default Component.extend({ + tagName: "", + router: service(), logsNoticeService: service("logsNotice"), logNotice: null, @@ -70,6 +72,10 @@ export default Component.extend({ ); }, + get visible() { + return !this.router.currentRouteName.startsWith("wizard."); + }, + @discourseComputed( "site.isReadOnly", "site.wizard_required", diff --git a/app/assets/javascripts/discourse/app/lib/url.js b/app/assets/javascripts/discourse/app/lib/url.js index f9bc2565ca..6c092a4f81 100644 --- a/app/assets/javascripts/discourse/app/lib/url.js +++ b/app/assets/javascripts/discourse/app/lib/url.js @@ -24,7 +24,6 @@ const SERVER_SIDE_ONLY = [ /^\/raw\//, /^\/posts\/\d+\/raw/, /^\/raw\/\d+/, - /^\/wizard/, /\.rss$/, /\.json$/, /^\/admin\/upgrade$/, diff --git a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs index 08ff36603f..efb640d1be 100644 --- a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs @@ -1,19 +1,27 @@ -{{#each notices as |notice|}} -
-
- {{#if notice.options.html}} - {{html-safe notice.options.html}} - {{/if}} - {{html-safe notice.text}} +
+ {{#if this.visible}} + {{#each notices as |notice|}} +
+
+ {{#if notice.options.html}} + {{html-safe notice.options.html}} + {{/if}} - {{#if notice.options.dismissable}} - {{d-button - class="btn-flat close" - icon="times" - action=(action "dismissNotice") - actionParam=notice - }} - {{/if}} -
-
-{{/each}} + {{html-safe notice.text}} + + {{#if notice.options.dismissable}} + {{d-button + class="btn-flat close" + icon="times" + action=(action "dismissNotice") + actionParam=notice + }} + {{/if}} +
+
+ {{/each}} + {{/if}} +
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index 60f707624d..d9232652ab 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -128,6 +128,9 @@ module.exports = function (defaults) { concat(mergeTrees([app.options.adminTree]), { outputFile: `assets/admin.js`, }), + concat(mergeTrees([app.options.wizardTree]), { + outputFile: `assets/wizard.js`, + }), prettyTextEngine(vendorJs, "discourse-markdown"), concat("public/assets/scripts", { outputFile: `assets/start-discourse.js`, diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index 73f3a6e90d..a1fcc95436 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -85,10 +85,16 @@ function head(buffer, bootstrap, headers, baseURL) { }); if (bootstrap.preloaded.currentUser) { - let staff = JSON.parse(bootstrap.preloaded.currentUser).staff; + const user = JSON.parse(bootstrap.preloaded.currentUser); + let { admin, staff } = user; + if (staff) { buffer.push(``); } + + if (admin) { + buffer.push(``); + } } bootstrap.plugin_js.forEach((src) => diff --git a/app/assets/javascripts/discourse/lib/translation-plugin.js b/app/assets/javascripts/discourse/lib/translation-plugin.js index d1e6543b33..3f23e8116a 100644 --- a/app/assets/javascripts/discourse/lib/translation-plugin.js +++ b/app/assets/javascripts/discourse/lib/translation-plugin.js @@ -59,6 +59,7 @@ class TranslationPlugin extends Plugin { let extras = { en: { admin: parsed.en.admin_js.admin, + wizard: parsed.en.wizard_js.wizard, }, }; @@ -70,7 +71,7 @@ class TranslationPlugin extends Plugin { this.replaceMF(formats, parsed); this.replaceMF(formats, extras); - formats = Object.keys(formats).map((k) => `"${k}": ${formats[k]}`); + formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`); let contents = ` I18n.locale = 'en'; diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 7c06473209..ac38f94aae 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -23,7 +23,6 @@ "@ember/test-helpers": "^2.2.0", "@glimmer/component": "^1.0.4", "@glimmer/tracking": "^1.0.4", - "tippy.js": "^6.3.7", "@popperjs/core": "2.10.2", "@uppy/aws-s3": "^2.0.8", "@uppy/aws-s3-multipart": "^2.2.1", @@ -72,8 +71,10 @@ "sass": "^1.32.8", "select-kit": "^1.0.0", "sinon": "^13.0.1", + "tippy.js": "^6.3.7", "virtual-dom": "^2.1.1", - "webpack": "^5.67.0" + "webpack": "^5.67.0", + "wizard": "^1.0.0" }, "engines": { "node": "12.* || 14.* || >= 16", diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js index a456dff981..7dab11a480 100644 --- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js +++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js @@ -6,11 +6,12 @@ // TODO: Remove this and have resolver find the templates const prefix = "discourse/templates/"; const adminPrefix = "admin/templates/"; + const wizardPrefix = "wizard/templates/"; let len = prefix.length; Object.keys(requirejs.entries).forEach(function (key) { - if (key.indexOf(prefix) === 0) { + if (key.startsWith(prefix)) { Ember.TEMPLATES[key.slice(len)] = require(key).default; - } else if (key.indexOf(adminPrefix) === 0) { + } else if (key.startsWith(adminPrefix) || key.startsWith(wizardPrefix)) { Ember.TEMPLATES[key] = require(key).default; } }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js new file mode 100644 index 0000000000..8bc467145e --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js @@ -0,0 +1,66 @@ +import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; +import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; +import { test } from "qunit"; + +acceptance("Wizard", function (needs) { + needs.user(); + + test("Wizard starts", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-column-contents")); + assert.strictEqual(currentRouteName(), "wizard.step"); + }); + + test("Going back and forth in steps", async function (assert) { + await visit("/wizard/steps/hello-world"); + assert.ok(exists(".wizard-step")); + assert.ok( + exists(".wizard-step-hello-world"), + "it adds a class for the step id" + ); + assert.ok(!exists(".wizard-btn.finish"), "cannot finish on first step"); + assert.ok(exists(".wizard-progress")); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-step-description")); + assert.ok( + !exists(".invalid .field-full-name"), + "don't show it as invalid until the user does something" + ); + assert.ok(exists(".wizard-field .field-description")); + assert.ok(!exists(".wizard-btn.back")); + assert.ok(!exists(".wizard-field .field-error-description")); + + // invalid data + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + + // server validation fail + await fillIn("input.field-full-name", "Server Fail"); + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + assert.ok(exists(".wizard-field .field-error-description")); + + // server validation ok + await fillIn("input.field-full-name", "Evil Trout"); + await click(".wizard-btn.next"); + assert.ok(!exists(".wizard-field .field-error-description")); + assert.ok(!exists(".wizard-step-description")); + assert.ok( + exists(".wizard-btn.finish"), + "shows finish on an intermediate step" + ); + + await click(".wizard-btn.next"); + assert.ok(exists(".select-kit.field-snack"), "went to the next step"); + assert.ok(exists(".preview-area"), "renders the component field"); + assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); + assert.ok(exists(".action-link.back"), "shows the back button"); + assert.ok(!exists(".wizard-step-title")); + assert.ok(!exists(".wizard-btn.finish"), "cannot finish on last step"); + + await click(".action-link.back"); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-btn.next")); + assert.ok(!exists(".wizard-prev")); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html index 98c7318aef..cc64b77714 100644 --- a/app/assets/javascripts/discourse/tests/index.html +++ b/app/assets/javascripts/discourse/tests/index.html @@ -36,7 +36,6 @@ width: 1000px; height: 1000px; } - @@ -51,6 +50,7 @@ + {{content-for "test-plugin-js"}} diff --git a/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js new file mode 100644 index 0000000000..39f54e07a6 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js @@ -0,0 +1,100 @@ +import { + count, + discourseModule, + exists, +} from "discourse/tests/helpers/qunit-helpers"; +import componentTest, { + setupRenderingTest, +} from "discourse/tests/helpers/component-test"; +import { click, fillIn } from "@ember/test-helpers"; +import hbs from "htmlbars-inline-precompile"; + +discourseModule( + "Integration | Component | Wizard | invite-list", + function (hooks) { + setupRenderingTest(hooks); + + componentTest("can add users", { + template: hbs`{{invite-list field=field}}`, + + beforeEach() { + this.set("field", {}); + }, + + async test(assert) { + assert.ok( + !exists(".users-list .invite-list-user"), + "no users at first" + ); + assert.ok(!exists(".new-user .invalid"), "not invalid at first"); + + const firstVal = JSON.parse(this.field.value); + assert.strictEqual(firstVal.length, 0, "empty JSON at first"); + + assert.ok( + this.field.warning, + "it has a warning since no users were added" + ); + + await click(".add-user"); + assert.ok( + !exists(".users-list .invite-list-user"), + "doesn't add a blank user" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await fillIn(".invite-email", "eviltrout@example.com"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "adds the user" + ); + assert.ok(!exists(".new-user .invalid")); + + const val = JSON.parse(this.field.value); + assert.strictEqual(val.length, 1); + assert.strictEqual( + val[0].email, + "eviltrout@example.com", + "adds the email to the JSON" + ); + assert.ok(val[0].role.length, "adds the role to the JSON"); + assert.ok( + !this.get("field.warning"), + "no warning once the user is added" + ); + + await fillIn(".invite-email", "eviltrout@example.com"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "can't add the same user twice" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await fillIn(".invite-email", "not-an-email"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "won't add an invalid email" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await click( + ".invite-list .invite-list-user:nth-of-type(1) .remove-user" + ); + assert.ok( + !exists(".users-list .invite-list-user"), + 0, + "removed the user" + ); + }, + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js new file mode 100644 index 0000000000..41b59bc72e --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js @@ -0,0 +1,35 @@ +import { module, test } from "qunit"; +import WizardField from "wizard/models/wizard-field"; + +module("Unit | Model | Wizard | wizard-field", function () { + test("basic state", function (assert) { + const w = WizardField.create({ type: "text" }); + assert.ok(w.unchecked); + assert.ok(!w.valid); + assert.ok(!w.invalid); + }); + + test("text - required - validation", function (assert) { + const w = WizardField.create({ type: "text", required: true }); + assert.ok(w.unchecked); + + w.check(); + assert.ok(!w.unchecked); + assert.ok(!w.valid); + assert.ok(w.invalid); + + w.set("value", "a value"); + w.check(); + assert.ok(!w.unchecked); + assert.ok(w.valid); + assert.ok(!w.invalid); + }); + + test("text - optional - validation", function (assert) { + const f = WizardField.create({ type: "text" }); + assert.ok(f.unchecked); + + f.check(); + assert.ok(f.valid); + }); +}); diff --git a/app/assets/javascripts/package.json b/app/assets/javascripts/package.json index d2b43f0a78..4c2ad27ab8 100644 --- a/app/assets/javascripts/package.json +++ b/app/assets/javascripts/package.json @@ -8,6 +8,7 @@ "discourse-widget-hbs", "pretty-text", "select-kit", - "truth-helpers" + "truth-helpers", + "wizard" ] } diff --git a/app/assets/javascripts/select-kit/addon/components/icon-picker.js b/app/assets/javascripts/select-kit/addon/components/icon-picker.js index 6d506ab64d..ab6553acbd 100644 --- a/app/assets/javascripts/select-kit/addon/components/icon-picker.js +++ b/app/assets/javascripts/select-kit/addon/components/icon-picker.js @@ -7,7 +7,7 @@ import MultiSelectComponent from "select-kit/components/multi-select"; import { computed } from "@ember/object"; import { isDevelopment } from "discourse-common/config/environment"; import { makeArray } from "discourse-common/lib/helpers"; -import { ajax } from "select-kit/lib/ajax-helper"; +import { ajax } from "discourse/lib/ajax"; export default MultiSelectComponent.extend({ pluginApiIdentifiers: ["icon-picker"], diff --git a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js b/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js deleted file mode 100644 index 4cd5d97ece..0000000000 --- a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -let ajax; -if (window.Discourse) { - ajax = requirejs("discourse/lib/ajax").ajax; -} else { - ajax = requirejs("wizard/lib/ajax").ajax; -} - -export { ajax }; diff --git a/app/assets/javascripts/select-kit/addon/mixins/tags.js b/app/assets/javascripts/select-kit/addon/mixins/tags.js index fe4c34f50b..caa1eaf394 100644 --- a/app/assets/javascripts/select-kit/addon/mixins/tags.js +++ b/app/assets/javascripts/select-kit/addon/mixins/tags.js @@ -1,6 +1,6 @@ import I18n from "I18n"; import Mixin from "@ember/object/mixin"; -import { ajax } from "select-kit/lib/ajax-helper"; +import { ajax } from "discourse/lib/ajax"; import getURL from "discourse-common/lib/get-url"; import { isEmpty } from "@ember/utils"; import { makeArray } from "discourse-common/lib/helpers"; diff --git a/app/assets/javascripts/wizard-application.js b/app/assets/javascripts/wizard-application.js deleted file mode 100644 index 3c91a3ddef..0000000000 --- a/app/assets/javascripts/wizard-application.js +++ /dev/null @@ -1,13 +0,0 @@ -//= require_tree ./truth-helpers/addon -//= require_tree ./discourse-common/addon -//= require_tree ./select-kit/addon -//= require wizard/router -//= require wizard/wizard -//= require_tree ./wizard/templates -//= require_tree ./wizard/components -//= require_tree ./wizard/models -//= require_tree ./wizard/routes -//= require_tree ./wizard/controllers -//= require_tree ./wizard/lib -//= require_tree ./wizard/mixins -//= require_tree ./wizard/initializers diff --git a/app/assets/javascripts/wizard-shims.js b/app/assets/javascripts/wizard-shims.js deleted file mode 100644 index b506393302..0000000000 --- a/app/assets/javascripts/wizard-shims.js +++ /dev/null @@ -1,47 +0,0 @@ -define("@popperjs/core", ["exports"], function (__exports__) { - __exports__.default = window.Popper; - __exports__.createPopper = window.Popper.createPopper; - __exports__.defaultModifiers = window.Popper.defaultModifiers; - __exports__.popperGenerator = window.Popper.popperGenerator; -}); - -define("tippy.js", ["exports"], function (__exports__) { - __exports__.default = window.tippy; -}); - -define("@uppy/core", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Core; - __exports__.BasePlugin = window.Uppy.Core.BasePlugin; -}); - -define("@uppy/aws-s3", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3; -}); - -define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3Multipart; -}); - -define("@uppy/xhr-upload", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.XHRUpload; -}); - -define("@uppy/drop-target", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.DropTarget; -}); - -define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.delay; -}); - -define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.EventTracker; -}); - -define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { - __exports__.AbortController = - window.Uppy.Utils.AbortControllerLib.AbortController; - __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; - __exports__.createAbortError = - window.Uppy.Utils.AbortControllerLib.createAbortError; -}); diff --git a/app/assets/javascripts/wizard-start.js b/app/assets/javascripts/wizard-start.js deleted file mode 100644 index e12c46d8f1..0000000000 --- a/app/assets/javascripts/wizard-start.js +++ /dev/null @@ -1,5 +0,0 @@ -// discourse-skip-module -(function () { - const wizard = require("wizard/wizard").default.create(); - wizard.start(); -})(); diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js deleted file mode 100644 index 5d175a6bb5..0000000000 --- a/app/assets/javascripts/wizard-vendor.js +++ /dev/null @@ -1,10 +0,0 @@ -//= require ember_jquery -//= require template_include.js -//= require uppy.js -//= require bootstrap-modal.js -//= require bootbox.js -//= require virtual-dom -//= require virtual-dom-amd -//= require popper.js -//= require tippy.umd.js -//= require wizard-shims diff --git a/app/assets/javascripts/wizard.js b/app/assets/javascripts/wizard.js new file mode 100644 index 0000000000..7f52738184 --- /dev/null +++ b/app/assets/javascripts/wizard.js @@ -0,0 +1,2 @@ +//= require discourse/app/lib/export-result +//= require_tree ./wizard/addon diff --git a/app/assets/javascripts/wizard/.npmrc b/app/assets/javascripts/wizard/.npmrc new file mode 100644 index 0000000000..c42da845b4 --- /dev/null +++ b/app/assets/javascripts/wizard/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js b/app/assets/javascripts/wizard/addon/components/homepage-preview.js similarity index 100% rename from app/assets/javascripts/wizard/components/homepage-preview.js rename to app/assets/javascripts/wizard/addon/components/homepage-preview.js diff --git a/app/assets/javascripts/wizard/components/image-preview-favicon.js b/app/assets/javascripts/wizard/addon/components/image-preview-favicon.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-favicon.js rename to app/assets/javascripts/wizard/addon/components/image-preview-favicon.js diff --git a/app/assets/javascripts/wizard/components/image-preview-large-icon.js b/app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-large-icon.js rename to app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js diff --git a/app/assets/javascripts/wizard/components/image-preview-logo-small.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-logo-small.js rename to app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js diff --git a/app/assets/javascripts/wizard/components/image-preview-logo.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-logo.js rename to app/assets/javascripts/wizard/addon/components/image-preview-logo.js diff --git a/app/assets/javascripts/wizard/components/invite-list-user.js b/app/assets/javascripts/wizard/addon/components/invite-list-user.js similarity index 100% rename from app/assets/javascripts/wizard/components/invite-list-user.js rename to app/assets/javascripts/wizard/addon/components/invite-list-user.js diff --git a/app/assets/javascripts/wizard/components/invite-list.js b/app/assets/javascripts/wizard/addon/components/invite-list.js similarity index 100% rename from app/assets/javascripts/wizard/components/invite-list.js rename to app/assets/javascripts/wizard/addon/components/invite-list.js diff --git a/app/assets/javascripts/wizard/components/staff-count.js b/app/assets/javascripts/wizard/addon/components/staff-count.js similarity index 100% rename from app/assets/javascripts/wizard/components/staff-count.js rename to app/assets/javascripts/wizard/addon/components/staff-count.js diff --git a/app/assets/javascripts/wizard/components/styling-preview.js b/app/assets/javascripts/wizard/addon/components/styling-preview.js similarity index 91% rename from app/assets/javascripts/wizard/components/styling-preview.js rename to app/assets/javascripts/wizard/addon/components/styling-preview.js index 1a4deaa075..f3bc3e941a 100644 --- a/app/assets/javascripts/wizard/components/styling-preview.js +++ b/app/assets/javascripts/wizard/addon/components/styling-preview.js @@ -31,6 +31,18 @@ export default createPreviewComponent(659, 320, { this.wizard.off("homepageStyleChanged", this.onHomepageStyleChange); }, + didInsertElement() { + this._super(...arguments); + this.element.addEventListener("mouseleave", this.handleMouseLeave); + this.element.addEventListener("mousemove", this.handleMouseMove); + }, + + willDestroyElement() { + this._super(...arguments); + this.element.removeEventListener("mouseleave", this.handleMouseLeave); + this.element.removeEventListener("mousemove", this.handleMouseMove); + }, + mouseDown(e) { const slider = this.element.querySelector(".previews"); this.setProperties({ @@ -40,7 +52,8 @@ export default createPreviewComponent(659, 320, { }); }, - mouseLeave() { + @bind + handleMouseLeave() { this.set("draggingActive", false); }, @@ -48,7 +61,8 @@ export default createPreviewComponent(659, 320, { this.set("draggingActive", false); }, - mouseMove(e) { + @bind + handleMouseMove(e) { if (!this.draggingActive) { return; } diff --git a/app/assets/javascripts/wizard/components/wizard-canvas.js b/app/assets/javascripts/wizard/addon/components/wizard-canvas.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-canvas.js rename to app/assets/javascripts/wizard/addon/components/wizard-canvas.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-checkboxes.js b/app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-checkboxes.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js similarity index 83% rename from app/assets/javascripts/wizard/components/wizard-field-dropdown.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js index 99548fa464..34b136cdf3 100644 --- a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js @@ -3,8 +3,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import { action, set } from "@ember/object"; export default Component.extend({ - init(...args) { - this._super(...args); + init() { + this._super(...arguments); if (this.field.id === "color_scheme") { for (let choice of this.field.choices) { @@ -17,10 +17,7 @@ export default Component.extend({ @discourseComputed("field.id") componentName(id) { - if (id === "color_scheme") { - return "color-palettes"; - } - return "combo-box"; + return id === "color_scheme" ? "color-palettes" : "combo-box"; }, keyPress(e) { diff --git a/app/assets/javascripts/wizard/components/wizard-field-image.js b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js similarity index 96% rename from app/assets/javascripts/wizard/components/wizard-field-image.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-image.js index c4ff8328b5..365db6e836 100644 --- a/app/assets/javascripts/wizard/components/wizard-field-image.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js @@ -4,7 +4,6 @@ import I18n from "I18n"; import { dasherize } from "@ember/string"; import discourseComputed from "discourse-common/utils/decorators"; import { getOwner } from "discourse-common/lib/get-owner"; -import { getToken } from "wizard/lib/ajax"; import getUrl from "discourse-common/lib/get-url"; import Uppy from "@uppy/core"; import DropTarget from "@uppy/drop-target"; @@ -37,7 +36,7 @@ export default Component.extend({ this._uppyInstance.use(XHRUpload, { endpoint: getUrl("/uploads.json"), headers: { - "X-CSRF-Token": getToken(), + "X-CSRF-Token": this.session.csrfToken, }, }); diff --git a/app/assets/javascripts/wizard/components/wizard-field-radio.js b/app/assets/javascripts/wizard/addon/components/wizard-field-radio.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-radio.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-radio.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-textarea.js b/app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-textarea.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js diff --git a/app/assets/javascripts/wizard/components/wizard-field.js b/app/assets/javascripts/wizard/addon/components/wizard-field.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field.js rename to app/assets/javascripts/wizard/addon/components/wizard-field.js diff --git a/app/assets/javascripts/wizard/components/wizard-image-preview.js b/app/assets/javascripts/wizard/addon/components/wizard-image-preview.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-image-preview.js rename to app/assets/javascripts/wizard/addon/components/wizard-image-preview.js diff --git a/app/assets/javascripts/wizard/components/wizard-step-form.js b/app/assets/javascripts/wizard/addon/components/wizard-step-form.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-step-form.js rename to app/assets/javascripts/wizard/addon/components/wizard-step-form.js diff --git a/app/assets/javascripts/wizard/components/wizard-step.js b/app/assets/javascripts/wizard/addon/components/wizard-step.js similarity index 99% rename from app/assets/javascripts/wizard/components/wizard-step.js rename to app/assets/javascripts/wizard/addon/components/wizard-step.js index b0957bbf9b..2e6b95e2c4 100644 --- a/app/assets/javascripts/wizard/components/wizard-step.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-step.js @@ -150,8 +150,10 @@ export default Component.extend({ if (result.warnings.length) { const unwarned = result.warnings.filter((w) => !alreadyWarned[w]); + if (unwarned.length) { unwarned.forEach((w) => (alreadyWarned[w] = true)); + return window.bootbox.confirm( unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"), I18n.t("no_value"), diff --git a/app/assets/javascripts/wizard/addon/controllers/wizard-step.js b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js new file mode 100644 index 0000000000..7bae0e0dcc --- /dev/null +++ b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js @@ -0,0 +1,24 @@ +import getUrl from "discourse-common/lib/get-url"; +import Controller from "@ember/controller"; +import { action } from "@ember/object"; + +export default Controller.extend({ + wizard: null, + step: null, + + @action + goNext(response) { + const next = this.get("step.next"); + + if (response?.refresh_required) { + document.location = getUrl(`/wizard/steps/${next}`); + } else if (response?.success) { + this.transitionToRoute("wizard.step", next); + } + }, + + @action + goBack() { + this.transitionToRoute("wizard.step", this.step.previous); + }, +}); diff --git a/app/assets/javascripts/wizard/lib/preview.js b/app/assets/javascripts/wizard/addon/lib/preview.js similarity index 100% rename from app/assets/javascripts/wizard/lib/preview.js rename to app/assets/javascripts/wizard/addon/lib/preview.js diff --git a/app/assets/javascripts/wizard/mixins/valid-state.js b/app/assets/javascripts/wizard/addon/mixins/valid-state.js similarity index 100% rename from app/assets/javascripts/wizard/mixins/valid-state.js rename to app/assets/javascripts/wizard/addon/mixins/valid-state.js diff --git a/app/assets/javascripts/wizard/models/step.js b/app/assets/javascripts/wizard/addon/models/step.js similarity index 87% rename from app/assets/javascripts/wizard/models/step.js rename to app/assets/javascripts/wizard/addon/models/step.js index ed67491b64..3decd1dadb 100644 --- a/app/assets/javascripts/wizard/models/step.js +++ b/app/assets/javascripts/wizard/addon/models/step.js @@ -1,13 +1,15 @@ import EmberObject from "@ember/object"; import ValidState from "wizard/mixins/valid-state"; -import { ajax } from "wizard/lib/ajax"; +import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; export default EmberObject.extend(ValidState, { id: null, @discourseComputed("index") - displayIndex: (index) => index + 1, + displayIndex(index) { + return index + 1; + }, @discourseComputed("fields.[]") fieldsById(fields) { @@ -48,8 +50,8 @@ export default EmberObject.extend(ValidState, { url: `/wizard/steps/${this.id}`, type: "PUT", data: { fields }, - }).catch((response) => { - response.responseJSON.errors.forEach((err) => + }).catch((error) => { + error.jqXHR.responseJSON.errors.forEach((err) => this.fieldError(err.field, err.description) ); }); diff --git a/app/assets/javascripts/wizard/models/wizard-field.js b/app/assets/javascripts/wizard/addon/models/wizard-field.js similarity index 100% rename from app/assets/javascripts/wizard/models/wizard-field.js rename to app/assets/javascripts/wizard/addon/models/wizard-field.js diff --git a/app/assets/javascripts/wizard/models/wizard.js b/app/assets/javascripts/wizard/addon/models/wizard.js similarity index 84% rename from app/assets/javascripts/wizard/models/wizard.js rename to app/assets/javascripts/wizard/addon/models/wizard.js index 9f880c4e16..fbdf8f86c0 100644 --- a/app/assets/javascripts/wizard/models/wizard.js +++ b/app/assets/javascripts/wizard/addon/models/wizard.js @@ -2,12 +2,11 @@ import EmberObject from "@ember/object"; import Evented from "@ember/object/evented"; import Step from "wizard/models/step"; import WizardField from "wizard/models/wizard-field"; -import { ajax } from "wizard/lib/ajax"; -import discourseComputed from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; +import { readOnly } from "@ember/object/computed"; const Wizard = EmberObject.extend(Evented, { - @discourseComputed("steps.length") - totalSteps: (length) => length, + totalSteps: readOnly("steps.length"), getTitle() { const titleStep = this.steps.findBy("id", "forum-title"); @@ -53,8 +52,7 @@ const Wizard = EmberObject.extend(Evented, { }); export function findWizard() { - return ajax({ url: "/wizard.json" }).then((response) => { - const wizard = response.wizard; + return ajax({ url: "/wizard.json" }).then(({ wizard }) => { wizard.steps = wizard.steps.map((step) => { const stepObj = Step.create(step); stepObj.fields = stepObj.fields.map((f) => WizardField.create(f)); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-index.js b/app/assets/javascripts/wizard/addon/routes/wizard-index.js new file mode 100644 index 0000000000..d6305f8619 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-index.js @@ -0,0 +1,8 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + beforeModel() { + const appModel = this.modelFor("wizard"); + this.replaceWith("wizard.step", appModel.start); + }, +}); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js new file mode 100644 index 0000000000..62ed332378 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js @@ -0,0 +1,5 @@ +export default function () { + this.route("wizard", function () { + this.route("step", { path: "/steps/:step_id" }); + }); +} diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-step.js b/app/assets/javascripts/wizard/addon/routes/wizard-step.js new file mode 100644 index 0000000000..76d78bc44d --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-step.js @@ -0,0 +1,17 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model(params) { + const allSteps = this.modelFor("wizard").steps; + const step = allSteps.findBy("id", params.step_id); + + return step || allSteps[0]; + }, + + setupController(controller, step) { + const wizard = this.modelFor("wizard"); + this.controllerFor("wizard").set("currentStepId", step.id); + + controller.setProperties({ step, wizard }); + }, +}); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard.js b/app/assets/javascripts/wizard/addon/routes/wizard.js new file mode 100644 index 0000000000..73aa06c142 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard.js @@ -0,0 +1,21 @@ +import Route from "@ember/routing/route"; +import { findWizard } from "wizard/models/wizard"; + +export default Route.extend({ + model() { + return findWizard(); + }, + + activate() { + document.body.classList.add("wizard"); + this.controllerFor("application").setProperties({ + showTop: false, + showFooter: false, + }); + }, + + deactivate() { + document.body.classList.remove("wizard"); + this.controllerFor("application").set("showTop", true); + }, +}); diff --git a/app/assets/javascripts/wizard/templates/components/invite-list-user.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/invite-list-user.hbs rename to app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs diff --git a/app/assets/javascripts/wizard/templates/components/invite-list.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/invite-list.hbs rename to app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs diff --git a/app/assets/javascripts/wizard/templates/components/staff-count.hbs b/app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/staff-count.hbs rename to app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs diff --git a/app/assets/javascripts/wizard/templates/components/styling-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/styling-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/theme-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/theme-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs diff --git a/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs new file mode 100644 index 0000000000..1e2bc2a0ba --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs @@ -0,0 +1,30 @@ +{{#each field.choices as |choice|}} +
+
+ {{radio-button + selection=field.value + value=choice.id + name=choice.label + onChange=(action "changed") + }} + + + {{#if choice.icon}} + {{d-icon choice.icon}} + {{/if}} + + {{choice.label}} + + + {{#if choice.extraLabel}} + + {{html-safe choice.extraLabel}} + + {{/if}} +
+ +
+ {{choice.description}} +
+
+{{/each}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-step.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs diff --git a/app/assets/javascripts/wizard/addon/templates/step.hbs b/app/assets/javascripts/wizard/addon/templates/step.hbs new file mode 100644 index 0000000000..69bacff68d --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/step.hbs @@ -0,0 +1,6 @@ +{{wizard-step + step=step + wizard=wizard + goNext=(action "goNext") + goBack=(action "goBack") +}} diff --git a/app/assets/javascripts/wizard/addon/templates/wizard.hbs b/app/assets/javascripts/wizard/addon/templates/wizard.hbs new file mode 100644 index 0000000000..3449dd3575 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/wizard.hbs @@ -0,0 +1,46 @@ +
+ {{#if showCanvas}} + {{wizard-canvas}} + {{/if}} + +
+
+ {{outlet}} + + {{!-- Load all font styles --}} +
+ {{#each fontClasses as |fontClass|}} +   +   + {{/each}} +
+
+ + +
+
diff --git a/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js new file mode 100644 index 0000000000..54d8a9fd8e --- /dev/null +++ b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js @@ -0,0 +1,59 @@ +export default function (helpers) { + const { parsePostData, response } = helpers; + + this.get("/wizard.json", () => { + return response({ + wizard: { + start: "hello-world", + completed: true, + steps: [ + { + id: "hello-world", + title: "hello there", + index: 0, + description: "hello!", + fields: [ + { + id: "full_name", + type: "text", + required: true, + description: "Your name", + }, + ], + next: "second-step", + }, + { + id: "second-step", + title: "Second step", + index: 1, + fields: [{ id: "some-title", type: "text" }], + previous: "hello-world", + next: "last-step", + }, + { + id: "last-step", + index: 2, + fields: [ + { id: "snack", type: "dropdown", required: true }, + { id: "theme-preview", type: "component" }, + { id: "an-image", type: "image" }, + ], + previous: "second-step", + }, + ], + }, + }); + }); + + this.put("/wizard/steps/:id", (request) => { + const body = parsePostData(request.requestBody); + + if (body.fields.full_name === "Server Fail") { + return response(422, { + errors: [{ field: "full_name", description: "Invalid name" }], + }); + } else { + return response(200, { success: true }); + } + }); +} diff --git a/app/assets/javascripts/wizard/app/.gitkeep b/app/assets/javascripts/wizard/app/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/javascripts/wizard/components/radio-button.js b/app/assets/javascripts/wizard/components/radio-button.js deleted file mode 100644 index af16d49236..0000000000 --- a/app/assets/javascripts/wizard/components/radio-button.js +++ /dev/null @@ -1,21 +0,0 @@ -import { observes, on } from "discourse-common/utils/decorators"; -import Component from "@ember/component"; -import { next } from "@ember/runloop"; - -export default Component.extend({ - tagName: "label", - - click(e) { - e.preventDefault(); - this.onChange(this.radioValue); - }, - - @observes("value") - @on("init") - updateVal() { - const checked = this.value === this.radioValue; - next( - () => (this.element.querySelector("input[type=radio]").checked = checked) - ); - }, -}); diff --git a/app/assets/javascripts/wizard/controllers/application.js b/app/assets/javascripts/wizard/controllers/application.js deleted file mode 100644 index d0c86664b3..0000000000 --- a/app/assets/javascripts/wizard/controllers/application.js +++ /dev/null @@ -1,25 +0,0 @@ -import Controller from "@ember/controller"; -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - -export default Controller.extend({ - currentStepId: null, - - @discourseComputed("currentStepId") - showCanvas(currentStepId) { - return currentStepId === "finished"; - }, - - @discourseComputed("model") - fontClasses(model) { - const fontsStep = model.steps.findBy("id", "styling"); - if (!fontsStep) { - return []; - } - - const fontField = fontsStep.get("fieldsById.body_font"); - return fontField.choices.map( - (choice) => `body-font-${dasherize(choice.id)}` - ); - }, -}); diff --git a/app/assets/javascripts/wizard/controllers/step.js b/app/assets/javascripts/wizard/controllers/step.js deleted file mode 100644 index 17c08fcec9..0000000000 --- a/app/assets/javascripts/wizard/controllers/step.js +++ /dev/null @@ -1,29 +0,0 @@ -import getUrl from "discourse-common/lib/get-url"; -import Controller from "@ember/controller"; -import { action } from "@ember/object"; - -export default Controller.extend({ - wizard: null, - step: null, - - @action - goNext(response) { - const next = this.get("step.next"); - if (response && response.refresh_required) { - if (this.get("step.id") === "locale") { - document.location = getUrl(`/wizard/steps/${next}`); - return; - } else { - this.send("refreshRoute"); - } - } - if (response && response.success) { - this.transitionToRoute("step", next); - } - }, - - @action - goBack() { - this.transitionToRoute("step", this.get("step.previous")); - }, -}); diff --git a/app/assets/javascripts/wizard/ember-cli-build.js b/app/assets/javascripts/wizard/ember-cli-build.js new file mode 100644 index 0000000000..81a8a5e9aa --- /dev/null +++ b/app/assets/javascripts/wizard/ember-cli-build.js @@ -0,0 +1,9 @@ +"use strict"; + +const EmberAddon = require("ember-cli/lib/broccoli/ember-addon"); + +module.exports = function (defaults) { + let app = new EmberAddon(defaults, {}); + + return app.toTree(); +}; diff --git a/app/assets/javascripts/wizard/index.js b/app/assets/javascripts/wizard/index.js new file mode 100644 index 0000000000..f29529cc51 --- /dev/null +++ b/app/assets/javascripts/wizard/index.js @@ -0,0 +1,20 @@ +"use strict"; + +const calculateCacheKeyForTree = require("calculate-cache-key-for-tree"); + +module.exports = { + name: require("./package").name, + treeForAddon(tree) { + let app = this._findHost(); + app.options.wizardTree = this._super.treeForAddon.call(this, tree); + return; + }, + + cacheKeyForTree(tree) { + return calculateCacheKeyForTree(tree, this); + }, + + isDevelopingAddon() { + return true; + }, +}; diff --git a/app/assets/javascripts/wizard/initializers/load-helpers.js b/app/assets/javascripts/wizard/initializers/load-helpers.js deleted file mode 100644 index cf14d2bbfc..0000000000 --- a/app/assets/javascripts/wizard/initializers/load-helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -import { registerHelpers } from "discourse-common/lib/helpers"; - -export default { - name: "load-helpers", - - initialize(application) { - Object.keys(requirejs.entries).forEach((entry) => { - if (/\/helpers\//.test(entry)) { - requirejs(entry, null, null, true); - } - }); - registerHelpers(application); - }, -}; diff --git a/app/assets/javascripts/wizard/lib/ajax.js b/app/assets/javascripts/wizard/lib/ajax.js deleted file mode 100644 index 3889069f71..0000000000 --- a/app/assets/javascripts/wizard/lib/ajax.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Promise } from "rsvp"; -import getUrl from "discourse-common/lib/get-url"; -import jQuery from "jquery"; -import { run } from "@ember/runloop"; - -let token; - -export function getToken() { - if (!token) { - token = document.querySelector('meta[name="csrf-token"]')?.content; - } - - return token; -} - -export function ajax(args) { - let url; - - if (arguments.length === 2) { - url = arguments[0]; - args = arguments[1]; - } else { - url = args.url; - } - - return new Promise((resolve, reject) => { - args.headers = { "X-CSRF-Token": getToken() }; - args.success = (data) => run(null, resolve, data); - args.error = (xhr) => run(null, reject, xhr); - args.url = getUrl(url); - jQuery.ajax(args); - }); -} diff --git a/app/assets/javascripts/wizard/package.json b/app/assets/javascripts/wizard/package.json new file mode 100644 index 0000000000..623421f134 --- /dev/null +++ b/app/assets/javascripts/wizard/package.json @@ -0,0 +1,57 @@ +{ + "name": "wizard", + "version": "1.0.0", + "description": "Discourse's setup wizard", + "author": "Discourse", + "license": "MIT", + "keywords": [ + "ember-addon" + ], + "repository": "", + "scripts": { + "build": "ember build", + "lint:hbs": "ember-template-lint .", + "lint:js": "eslint .", + "start": "ember serve" + }, + "dependencies": { + "ember-auto-import": "^2.2.4", + "ember-cli-babel": "^7.13.0", + "ember-cli-htmlbars": "^4.2.0", + "xss": "^1.0.8", + "webpack": "^5.67.0" + }, + "devDependencies": { + "@ember/optional-features": "^1.1.0", + "@glimmer/component": "^1.0.0", + "babel-eslint": "^10.0.3", + "broccoli-asset-rev": "^3.0.0", + "ember-cli": "~3.25.3", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-eslint": "^5.1.0", + "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-sri": "^2.1.1", + "ember-cli-template-lint": "^1.0.0-beta.3", + "ember-cli-uglify": "^3.0.0", + "ember-disable-prototype-extensions": "^1.1.3", + "ember-export-application-global": "^2.0.1", + "ember-load-initializers": "^2.1.1", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-resolver": "^7.0.0", + "ember-source": "~3.15.0", + "ember-source-channel-url": "^2.0.1", + "ember-try": "^2.0.0", + "eslint": "^7.27.0", + "eslint-plugin-ember": "^7.7.1", + "eslint-plugin-node": "^10.0.0", + "loader.js": "^4.7.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16", + "npm": "please-use-yarn", + "yarn": ">= 1.21.1" + }, + "ember": { + "edition": "default" + } +} diff --git a/app/assets/javascripts/wizard/router.js b/app/assets/javascripts/wizard/router.js deleted file mode 100644 index f992207737..0000000000 --- a/app/assets/javascripts/wizard/router.js +++ /dev/null @@ -1,14 +0,0 @@ -import EmberRouter from "@ember/routing/router"; -import getUrl from "discourse-common/lib/get-url"; -import { isTesting } from "discourse-common/config/environment"; - -const Router = EmberRouter.extend({ - rootURL: getUrl("/wizard/"), - location: isTesting() ? "none" : "history", -}); - -Router.map(function () { - this.route("step", { path: "/steps/:step_id" }); -}); - -export default Router; diff --git a/app/assets/javascripts/wizard/routes/application.js b/app/assets/javascripts/wizard/routes/application.js deleted file mode 100644 index 26bf3ff08f..0000000000 --- a/app/assets/javascripts/wizard/routes/application.js +++ /dev/null @@ -1,14 +0,0 @@ -import Route from "@ember/routing/route"; -import { findWizard } from "wizard/models/wizard"; -import { action } from "@ember/object"; - -export default Route.extend({ - model() { - return findWizard(); - }, - - @action - refreshRoute() { - this.refresh(); - }, -}); diff --git a/app/assets/javascripts/wizard/routes/index.js b/app/assets/javascripts/wizard/routes/index.js deleted file mode 100644 index ccebc21a26..0000000000 --- a/app/assets/javascripts/wizard/routes/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import Route from "@ember/routing/route"; -export default Route.extend({ - beforeModel() { - const appModel = this.modelFor("application"); - this.replaceWith("step", appModel.start); - }, -}); diff --git a/app/assets/javascripts/wizard/routes/step.js b/app/assets/javascripts/wizard/routes/step.js deleted file mode 100644 index ed1bef9d90..0000000000 --- a/app/assets/javascripts/wizard/routes/step.js +++ /dev/null @@ -1,17 +0,0 @@ -import Route from "@ember/routing/route"; -export default Route.extend({ - model(params) { - const allSteps = this.modelFor("application").steps; - const step = allSteps.findBy("id", params.step_id); - return step ? step : allSteps[0]; - }, - - setupController(controller, step) { - this.controllerFor("application").set("currentStepId", step.get("id")); - - controller.setProperties({ - step, - wizard: this.modelFor("application"), - }); - }, -}); diff --git a/app/assets/javascripts/wizard/templates/application.hbs b/app/assets/javascripts/wizard/templates/application.hbs deleted file mode 100644 index 54fb74e4cd..0000000000 --- a/app/assets/javascripts/wizard/templates/application.hbs +++ /dev/null @@ -1,43 +0,0 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} - -
-
- {{outlet}} - - {{!-- Load all font styles --}} -
- {{#each fontClasses as |fontClass|}} -   -   - {{/each}} -
-
- -
diff --git a/app/assets/javascripts/wizard/templates/components/radio-button.hbs b/app/assets/javascripts/wizard/templates/components/radio-button.hbs deleted file mode 100644 index 2974b5ebec..0000000000 --- a/app/assets/javascripts/wizard/templates/components/radio-button.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
- - - {{#if icon}} - {{d-icon icon}} - {{/if}} - {{label}} - - {{#if extraLabel}} - - {{html-safe extraLabel}} - - {{/if}} -
-
- {{description}} -
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs deleted file mode 100644 index 8994de0de4..0000000000 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#each field.choices as |c|}} -
- {{radio-button value=field.value - radioValue=c.id - label=c.label - extraLabel=c.extra_label - icon=c.icon - description=c.description - onChange=(action "changed")}} -
-{{/each}} diff --git a/app/assets/javascripts/wizard/templates/step.hbs b/app/assets/javascripts/wizard/templates/step.hbs deleted file mode 100644 index 9d42be41e5..0000000000 --- a/app/assets/javascripts/wizard/templates/step.hbs +++ /dev/null @@ -1 +0,0 @@ -{{wizard-step step=step wizard=wizard goNext=(action "goNext") goBack=(action "goBack")}} diff --git a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js deleted file mode 100644 index ea550cf270..0000000000 --- a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js +++ /dev/null @@ -1,78 +0,0 @@ -import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; -import { module, test } from "qunit"; -import { run } from "@ember/runloop"; -import startApp from "wizard/test/helpers/start-app"; - -let wizard; -module("Acceptance: wizard", { - beforeEach() { - wizard = startApp(); - }, - - afterEach() { - run(wizard, "destroy"); - }, -}); - -function exists(selector) { - return document.querySelector(selector) !== null; -} - -test("Wizard starts", async function (assert) { - await visit("/"); - assert.ok(exists(".wizard-column-contents")); - assert.strictEqual(currentRouteName(), "step"); -}); - -test("Going back and forth in steps", async function (assert) { - await visit("/steps/hello-world"); - assert.ok(exists(".wizard-step")); - assert.ok( - exists(".wizard-step-hello-world"), - "it adds a class for the step id" - ); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on first step"); - assert.ok(exists(".wizard-progress")); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-step-description")); - assert.ok( - !exists(".invalid .field-full-name"), - "don't show it as invalid until the user does something" - ); - assert.ok(exists(".wizard-field .field-description")); - assert.ok(!exists(".wizard-btn.back")); - assert.ok(!exists(".wizard-field .field-error-description")); - - // invalid data - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - - // server validation fail - await fillIn("input.field-full-name", "Server Fail"); - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - assert.ok(exists(".wizard-field .field-error-description")); - - // server validation ok - await fillIn("input.field-full-name", "Evil Trout"); - await click(".wizard-btn.next"); - assert.ok(!exists(".wizard-field .field-error-description")); - assert.ok(!exists(".wizard-step-description")); - assert.ok( - exists(".wizard-btn.finish"), - "shows finish on an intermediate step" - ); - - await click(".wizard-btn.next"); - assert.ok(exists(".select-kit.field-snack"), "went to the next step"); - assert.ok(exists(".preview-area"), "renders the component field"); - assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); - assert.ok(exists(".action-link.back"), "shows the back button"); - assert.ok(!exists(".wizard-step-title")); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on last step"); - - await click(".action-link.back"); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-btn.next")); - assert.ok(!exists(".wizard-prev")); -}); diff --git a/app/assets/javascripts/wizard/test/components/invite-list-test.js b/app/assets/javascripts/wizard/test/components/invite-list-test.js deleted file mode 100644 index 2acfe5071c..0000000000 --- a/app/assets/javascripts/wizard/test/components/invite-list-test.js +++ /dev/null @@ -1,82 +0,0 @@ -import { componentTest } from "wizard/test/helpers/component-test"; -import { moduleForComponent } from "ember-qunit"; -import { click, fillIn } from "@ember/test-helpers"; - -moduleForComponent("invite-list", { integration: true }); - -componentTest("can add users", { - template: `{{invite-list field=field}}`, - - beforeEach() { - this.set("field", {}); - }, - - async test(assert) { - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "no users at first" - ); - assert.ok( - document.querySelectorAll(".new-user .invalid").length === 0, - "not invalid at first" - ); - - const firstVal = JSON.parse(this.get("field.value")); - assert.strictEqual(firstVal.length, 0, "empty JSON at first"); - - assert.ok( - this.get("field.warning"), - "it has a warning since no users were added" - ); - - await click(".add-user"); - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "doesn't add a blank user" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await fillIn(".invite-email", "eviltrout@example.com"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "adds the user" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 0); - - const val = JSON.parse(this.get("field.value")); - assert.strictEqual(val.length, 1); - assert.strictEqual( - val[0].email, - "eviltrout@example.com", - "adds the email to the JSON" - ); - assert.ok(val[0].role.length, "adds the role to the JSON"); - assert.ok(!this.get("field.warning"), "no warning once the user is added"); - - await fillIn(".invite-email", "eviltrout@example.com"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "can't add the same user twice" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await fillIn(".invite-email", "not-an-email"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "won't add an invalid email" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await click(".invite-list .invite-list-user:nth-of-type(1) .remove-user"); - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "removed the user" - ); - }, -}); diff --git a/app/assets/javascripts/wizard/test/helpers/component-test.js b/app/assets/javascripts/wizard/test/helpers/component-test.js deleted file mode 100644 index e7d5f7c228..0000000000 --- a/app/assets/javascripts/wizard/test/helpers/component-test.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable no-undef */ -import initializer from "wizard/initializers/load-helpers"; -import { test } from "qunit"; - -export function componentTest(name, opts) { - opts = opts || {}; - - test(name, function (assert) { - initializer.initialize(this.registry); - - if (opts.beforeEach) { - opts.beforeEach.call(this); - } - - andThen(() => this.render(opts.template)); - andThen(() => opts.test.call(this, assert)); - }); -} diff --git a/app/assets/javascripts/wizard/test/helpers/start-app.js b/app/assets/javascripts/wizard/test/helpers/start-app.js deleted file mode 100644 index e49d315bb2..0000000000 --- a/app/assets/javascripts/wizard/test/helpers/start-app.js +++ /dev/null @@ -1,19 +0,0 @@ -import Wizard from "wizard/wizard"; -import initializer from "wizard/initializers/load-helpers"; -import { run } from "@ember/runloop"; - -let app; -let started = false; - -export default function () { - run(() => (app = Wizard.create({ rootElement: "#ember-testing" }))); - - if (!started) { - initializer.initialize(app); - app.start(); - started = true; - } - app.setupForTesting(); - app.injectTestHelpers(); - return app; -} diff --git a/app/assets/javascripts/wizard/test/models/wizard-field-test.js b/app/assets/javascripts/wizard/test/models/wizard-field-test.js deleted file mode 100644 index 90eda2bb14..0000000000 --- a/app/assets/javascripts/wizard/test/models/wizard-field-test.js +++ /dev/null @@ -1,36 +0,0 @@ -import WizardField from "wizard/models/wizard-field"; -import { moduleFor } from "ember-qunit"; -import { test } from "qunit"; - -moduleFor("model:wizard-field"); - -test("basic state", function (assert) { - const w = WizardField.create({ type: "text" }); - assert.ok(w.get("unchecked")); - assert.ok(!w.get("valid")); - assert.ok(!w.get("invalid")); -}); - -test("text - required - validation", function (assert) { - const w = WizardField.create({ type: "text", required: true }); - assert.ok(w.get("unchecked")); - - w.check(); - assert.ok(!w.get("unchecked")); - assert.ok(!w.get("valid")); - assert.ok(w.get("invalid")); - - w.set("value", "a value"); - w.check(); - assert.ok(!w.get("unchecked")); - assert.ok(w.get("valid")); - assert.ok(!w.get("invalid")); -}); - -test("text - optional - validation", function (assert) { - const f = WizardField.create({ type: "text" }); - assert.ok(f.get("unchecked")); - - f.check(); - assert.ok(f.get("valid")); -}); diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js deleted file mode 100644 index 39ed80b0dd..0000000000 --- a/app/assets/javascripts/wizard/test/test_helper.js +++ /dev/null @@ -1,76 +0,0 @@ -// discourse-skip-module -/*global document, Logster, QUnit */ - -//= require env -//= require jquery.debug -//= require ember.debug -//= require locales/i18n -//= require locales/en -//= require route-recognizer -//= require fake_xml_http_request -//= require pretender -//= require qunit -//= require ember-qunit -//= require discourse-loader -//= require jquery.debug -//= require handlebars -//= require ember-template-compiler -//= require wizard-application -//= require wizard-vendor -//= require_tree ./helpers -//= require_tree ./acceptance -//= require_tree ./models -//= require_tree ./components -//= require ./wizard-pretender -//= require test-shims - -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); - -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; -} -// eslint-disable-next-line no-undef -Ember.Test.adapter = window.QUnitAdapter.create(); - -let createPretendServer = requirejs( - "wizard/test/wizard-pretender", - null, - null, - false -).default; - -let server; - -const queryParams = new URLSearchParams(window.location.search); - -if (queryParams.get("qunit_disable_auto_start") === "1") { - QUnit.config.autostart = false; -} - -QUnit.testStart(function () { - server = createPretendServer(); -}); - -QUnit.testDone(function () { - server.shutdown(); -}); - -let _testApp = requirejs("wizard/test/helpers/start-app").default(); -let _buildResolver = requirejs("discourse-common/resolver").buildResolver; -window.setResolver(_buildResolver("wizard").create({ namespace: _testApp })); - -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry, null, null, true); - } -}); diff --git a/app/assets/javascripts/wizard/test/wizard-pretender.js b/app/assets/javascripts/wizard/test/wizard-pretender.js deleted file mode 100644 index e9dccfb3fb..0000000000 --- a/app/assets/javascripts/wizard/test/wizard-pretender.js +++ /dev/null @@ -1,106 +0,0 @@ -import Pretender from "pretender"; - -// TODO: This file has some copied and pasted functions from `create-pretender` - would be good -// to centralize that code at some point. - -function parsePostData(query) { - const result = {}; - query.split("&").forEach(function (part) { - const item = part.split("="); - const firstSeg = decodeURIComponent(item[0]); - const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); - - const val = decodeURIComponent(item[1]).replace(/\+/g, " "); - if (m) { - result[m[1]] = result[m[1]] || {}; - result[m[1]][m[2]] = val; - } else { - result[firstSeg] = val; - } - }); - return result; -} - -function response(code, obj) { - if (typeof code === "object") { - obj = code; - code = 200; - } - return [code, { "Content-Type": "application/json" }, obj]; -} - -export default function () { - const server = new Pretender(function () { - this.get("/wizard.json", () => { - return response(200, { - wizard: { - start: "hello-world", - completed: true, - steps: [ - { - id: "hello-world", - title: "hello there", - index: 0, - description: "hello!", - fields: [ - { - id: "full_name", - type: "text", - required: true, - description: "Your name", - }, - ], - next: "second-step", - }, - { - id: "second-step", - title: "Second step", - index: 1, - fields: [{ id: "some-title", type: "text" }], - previous: "hello-world", - next: "last-step", - }, - { - id: "last-step", - index: 2, - fields: [ - { id: "snack", type: "dropdown", required: true }, - { id: "theme-preview", type: "component" }, - { id: "an-image", type: "image" }, - ], - previous: "second-step", - }, - ], - }, - }); - }); - - this.put("/wizard/steps/:id", (request) => { - const body = parsePostData(request.requestBody); - - if (body.fields.full_name === "Server Fail") { - return response(422, { - errors: [{ field: "full_name", description: "Invalid name" }], - }); - } else { - return response(200, { success: true }); - } - }); - }); - - server.prepareBody = function (body) { - if (body && typeof body === "object") { - return JSON.stringify(body); - } - return body; - }; - - server.unhandledRequest = function (verb, path) { - const error = - "Unhandled request in test environment: " + path + " (" + verb + ")"; - window.console.error(error); - throw error; - }; - - return server; -} diff --git a/app/assets/javascripts/wizard/wizard.js b/app/assets/javascripts/wizard/wizard.js deleted file mode 100644 index 8e84fe18f5..0000000000 --- a/app/assets/javascripts/wizard/wizard.js +++ /dev/null @@ -1,29 +0,0 @@ -import Application from "@ember/application"; -import { buildResolver } from "discourse-common/resolver"; - -export default Application.extend({ - rootElement: "#wizard-main", - Resolver: buildResolver("wizard"), - - start() { - // required for select kit to work without Ember CLI - // eslint-disable-next-line no-undef - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - // eslint-disable-next-line no-undef - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - }, -}); diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index bf154a0b70..22aa6e9525 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -10,7 +10,7 @@ $bubbles-mask: svg-uri( ' <%= discourse_stylesheet_link_tag 'wizard', theme_id: nil %> <%= discourse_color_scheme_stylesheets %> + <%= render partial: "layouts/head" %> <%= t 'wizard.title' %> diff --git a/app/views/qunit/index.html.erb b/app/views/qunit/index.html.erb index 9912387a2d..3dbac746a1 100644 --- a/app/views/qunit/index.html.erb +++ b/app/views/qunit/index.html.erb @@ -9,6 +9,7 @@ <%= preload_script "vendor" %> <%= preload_script "application" %> <%= preload_script "admin" %> + <%= preload_script "wizard" %> <%= preload_script "discourse/tests/test-support-rails" %> <%= preload_script "discourse/tests/test-helpers-rails" %> <%= preload_script "discourse/tests/active-plugins" %> diff --git a/app/views/wizard/index.html.erb b/app/views/wizard/index.html.erb deleted file mode 100644 index 7fb1c7332c..0000000000 --- a/app/views/wizard/index.html.erb +++ /dev/null @@ -1,29 +0,0 @@ - - - <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> - <%= discourse_color_scheme_stylesheets %> - <%= preload_script "locales/#{I18n.locale}" %> - <%- if ExtraLocalesController.client_overrides_exist? %> - <%= preload_script_url ExtraLocalesController.url('overrides') %> - <%- end %> - <%= preload_script 'wizard-vendor' %> - <%= preload_script 'wizard-application' %> - <%= preload_script_url ExtraLocalesController.url('wizard') %> - <%= build_plugin_html 'wizard:head' %> - <%= csrf_meta_tags %> - - <%= render partial: "layouts/head" %> - <%= t 'wizard.title' %> - - - -
- <%= preload_script 'wizard-start' %> - - - - diff --git a/app/views/wizard/qunit.html.erb b/app/views/wizard/qunit.html.erb deleted file mode 100644 index 52e1ffaf90..0000000000 --- a/app/views/wizard/qunit.html.erb +++ /dev/null @@ -1,15 +0,0 @@ - - - - QUnit Test Runner - <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> - <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> - <%= preload_script "wizard/test/test_helper" %> - <%= csrf_meta_tags %> - - - -
-
- - diff --git a/config/application.rb b/config/application.rb index d459fad4cb..2b69416af9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -174,6 +174,7 @@ module Discourse config.handlebars.templates_root = { 'discourse/app/templates' => '', 'admin/addon/templates' => 'admin/templates/', + 'wizard/addon/templates' => 'wizard/templates/', 'select-kit/addon/templates' => 'select-kit/templates/' } @@ -256,7 +257,6 @@ module Discourse qunit.css test_helper.css discourse/tests/test-boot-rails.js - wizard/test/test_helper.js }.include?(logical_path) || logical_path =~ /\/node_modules/ || logical_path =~ /\/dist/ diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 69f6c4aa7b..2f2aeff0ce 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -24,13 +24,12 @@ end] Rails.application.config.assets.precompile += %w{ vendor.js admin.js + wizard.js browser-detect.js browser-update.js break_string.js ember_jquery.js pretty-text-bundle.js - wizard-application.js - wizard-vendor.js markdown-it-bundle.js service-worker.js google-tag-manager.js @@ -41,7 +40,6 @@ Rails.application.config.assets.precompile += %w{ omniauth-complete.js activate-account.js auto-redirect.js - wizard-start.js locales/i18n.js discourse/app/lib/webauthn.js confirm-new-email/confirm-new-email.js @@ -51,16 +49,16 @@ Rails.application.config.assets.precompile += %w{ discourse/tests/active-plugins.js admin-plugins.js discourse/tests/test_starter.js - } +} if EmberCli.enabled? Rails.application.config.assets.precompile += %w{ - discourse.js - test-support.js - test-helpers.js - scripts/discourse-test-listen-boot - scripts/discourse-boot - } + discourse.js + test-support.js + test-helpers.js + scripts/discourse-test-listen-boot + scripts/discourse-boot + } Rails.application.config.assets.precompile += EmberCli::ASSETS.map { |name| name.sub('.js', '.map') } else Rails.application.config.assets.precompile += %w{ diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index fcc385d9d1..4eaa4ec2ce 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -63,7 +63,6 @@ class DiscourseJsProcessor return true if %w( start-discourse - wizard-start onpopstate-handler google-tag-manager google-universal-analytics-v3 diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb index d5a319b41e..0b5164665a 100644 --- a/lib/js_locale_helper.rb +++ b/lib/js_locale_helper.rb @@ -210,7 +210,19 @@ module JsLocaleHelper end end - translations.present? ? "I18n.extras = #{translations.to_json};" : "" + return "" if translations.blank? + + <<~JS + if (!I18n.extras) { + I18n.extras = {} + } + + if (!I18n.extras["#{locale}"]) { + I18n.extras["#{locale}"] = {}; + } + + Object.assign(I18n.extras["#{locale}"], #{translations[locale].to_json}); + JS end MOMENT_LOCALE_MAPPING ||= { diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index 55b153f28d..b17f777d04 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -102,6 +102,7 @@ def is_ember_cli_asset?(name) %w( discourse.js admin.js + wizard.js ember_jquery.js pretty-text-bundle.js start-discourse.js From 91bc4442d14eca1041e99a4e75c14ed8702a17c1 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 15:35:18 +0200 Subject: [PATCH 035/184] DEV: Remove wizard step from docker tests (#17125) Wizard tests are now in core. --- lib/tasks/docker.rake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/tasks/docker.rake b/lib/tasks/docker.rake index eb0fb0f4b5..80c08d3106 100644 --- a/lib/tasks/docker.rake +++ b/lib/tasks/docker.rake @@ -210,10 +210,6 @@ task 'docker:test' do unless ENV["SKIP_CORE"] @good &&= run_or_fail("cd app/assets/javascripts/discourse && CI=1 yarn ember exam --random") - - if !ENV["SKIP_WIZARD_TESTS"] - @good &&= run_or_fail("QUNIT_EMBER_CLI=0 bundle exec rake qunit:test['#{js_timeout}','/wizard/qunit']") - end end unless ENV["SKIP_PLUGINS"] From b0a896a10f2f1ca1526c688efab87397366d8471 Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 17 Jun 2022 16:53:50 +0300 Subject: [PATCH 036/184] UX: Skip special paste handling in email-group-user-chooser when maximum is 1 (#17124) `email-group-user-chooser` currently handles paste events to allow users to paste multiple entries at once instead of entering them one by one. This behavior makes sense when the component is used in scenarios where it makes sense to provide multiple entries such as the recipients field when creating a PM. However, for instances where the component accepts only 1 entry, it doesn't make a lot of sense to do custom handling of paste events. This commit makes our paste event handler a NOOP when the component is configured to accept only 1 entry in which case pasting will simply input the value into the component's search field. --- .../addon/components/email-group-user-chooser-filter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-filter.js b/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-filter.js index 0d9002751a..9c03ea8add 100644 --- a/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-filter.js +++ b/app/assets/javascripts/select-kit/addon/components/email-group-user-chooser-filter.js @@ -6,6 +6,10 @@ export default MultiSelectFilterComponent.extend({ @action onPaste(event) { + if (this.selectKit.options.maximum === 1) { + return; + } + const data = event.originalEvent.clipboardData; if (!data) { From 1ada3a9404711bf4b80d0873ead3698045e1f41b Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Fri, 17 Jun 2022 09:43:13 -0500 Subject: [PATCH 037/184] DEV: Remove use of run-loop-and-computed-dot-access (#17113) Context: https://deprecations.emberjs.com/v3.x/#toc_deprecated-run-loop-and-computed-dot-access --- .../addon/utils/decorators.js | 88 +++++++++++++------ .../select-kit/addon/components/combo-box.js | 4 +- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/discourse-common/addon/utils/decorators.js b/app/assets/javascripts/discourse-common/addon/utils/decorators.js index 5417a0dabc..b847ad830e 100644 --- a/app/assets/javascripts/discourse-common/addon/utils/decorators.js +++ b/app/assets/javascripts/discourse-common/addon/utils/decorators.js @@ -1,5 +1,35 @@ import { on as emberOn } from "@ember/object/evented"; -import { computed, observer } from "@ember/object"; +import { observer } from "@ember/object"; +import { + alias as EmberAlias, + and as EmberAnd, + bool as EmberBool, + collect as EmberCollect, + empty as EmberEmpty, + equal as EmberEqual, + filter as EmberFilter, + filterBy as EmberFilterBy, + gt as EmberGt, + gte as EmberGte, + lt as EmberLt, + lte as EmberLte, + map as EmberMap, + mapBy as EmberMapBy, + match as EmberMatch, + max as EmberMax, + min as EmberMin, + none as EmberNone, + not as EmberNot, + notEmpty as EmberNotEmpty, + oneWay as EmberOneWay, + or as EmberOr, + reads as EmberReads, + setDiff as EmberSetDiff, + sort as EmberSort, + sum as EmberSum, + union as EmberUnion, + uniq as EmberUniq, +} from "@ember/object/computed"; import { bind as emberBind, schedule } from "@ember/runloop"; import decoratorAlias from "discourse-common/utils/decorator-alias"; import extractValue from "discourse-common/utils/extract-value"; @@ -63,31 +93,31 @@ export const observes = decoratorAlias( "Can not `observe` without property names" ); -export const alias = macroAlias(computed.alias); -export const and = macroAlias(computed.and); -export const bool = macroAlias(computed.bool); -export const collect = macroAlias(computed.collect); -export const empty = macroAlias(computed.empty); -export const equal = macroAlias(computed.equal); -export const filter = macroAlias(computed.filter); -export const filterBy = macroAlias(computed.filterBy); -export const gt = macroAlias(computed.gt); -export const gte = macroAlias(computed.gte); -export const lt = macroAlias(computed.lt); -export const lte = macroAlias(computed.lte); -export const map = macroAlias(computed.map); -export const mapBy = macroAlias(computed.mapBy); -export const match = macroAlias(computed.match); -export const max = macroAlias(computed.max); -export const min = macroAlias(computed.min); -export const none = macroAlias(computed.none); -export const not = macroAlias(computed.not); -export const notEmpty = macroAlias(computed.notEmpty); -export const oneWay = macroAlias(computed.oneWay); -export const or = macroAlias(computed.or); -export const reads = macroAlias(computed.reads); -export const setDiff = macroAlias(computed.setDiff); -export const sort = macroAlias(computed.sort); -export const sum = macroAlias(computed.sum); -export const union = macroAlias(computed.union); -export const uniq = macroAlias(computed.uniq); +export const alias = macroAlias(EmberAlias); +export const and = macroAlias(EmberAnd); +export const bool = macroAlias(EmberBool); +export const collect = macroAlias(EmberCollect); +export const empty = macroAlias(EmberEmpty); +export const equal = macroAlias(EmberEqual); +export const filter = macroAlias(EmberFilter); +export const filterBy = macroAlias(EmberFilterBy); +export const gt = macroAlias(EmberGt); +export const gte = macroAlias(EmberGte); +export const lt = macroAlias(EmberLt); +export const lte = macroAlias(EmberLte); +export const map = macroAlias(EmberMap); +export const mapBy = macroAlias(EmberMapBy); +export const match = macroAlias(EmberMatch); +export const max = macroAlias(EmberMax); +export const min = macroAlias(EmberMin); +export const none = macroAlias(EmberNone); +export const not = macroAlias(EmberNot); +export const notEmpty = macroAlias(EmberNotEmpty); +export const oneWay = macroAlias(EmberOneWay); +export const or = macroAlias(EmberOr); +export const reads = macroAlias(EmberReads); +export const setDiff = macroAlias(EmberSetDiff); +export const sort = macroAlias(EmberSort); +export const sum = macroAlias(EmberSum); +export const union = macroAlias(EmberUnion); +export const uniq = macroAlias(EmberUniq); diff --git a/app/assets/javascripts/select-kit/addon/components/combo-box.js b/app/assets/javascripts/select-kit/addon/components/combo-box.js index a9b2f95482..03735a8381 100644 --- a/app/assets/javascripts/select-kit/addon/components/combo-box.js +++ b/app/assets/javascripts/select-kit/addon/components/combo-box.js @@ -1,5 +1,5 @@ import SingleSelectComponent from "select-kit/components/single-select"; -import { computed } from "@ember/object"; +import { gte } from "@ember/object/computed"; export default SingleSelectComponent.extend({ pluginApiIdentifiers: ["combo-box"], @@ -13,5 +13,5 @@ export default SingleSelectComponent.extend({ headerComponent: "combo-box/combo-box-header", }, - autoFilterable: computed.gte("content.length", 10), + autoFilterable: gte("content.length", 10), }); From a69b9147107aa7858044f0db666da5b727980638 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 17:28:54 +0200 Subject: [PATCH 038/184] FIX: Wizard css was overriding some of core css (#17126) * Use `var()` * Remove imports * Scope all wizard css to `body.wizard` --- app/assets/stylesheets/wizard.scss | 1132 ++++++++++++++-------------- 1 file changed, 559 insertions(+), 573 deletions(-) diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 22aa6e9525..56c86f2431 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -1,11 +1,3 @@ -@import "color_definitions"; -@import "vendor/normalize"; -@import "vendor/normalize-ext"; -@import "common/foundation/colors"; -@import "common/select-kit/_index"; -@import "common/components/svg"; -@import "common/base/modal"; - $bubbles-mask: svg-uri( ' @@ -722,7 +714,7 @@ $bubbles-mask: svg-uri( body.wizard { background: var(--secondary) $bubbles-mask; color: var(--primary-very-high); - line-height: $line-height-large; + line-height: var(--line-height-large); font-size: 15px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, Arial, sans-serif; @@ -736,633 +728,627 @@ body.wizard { flex-direction: column; justify-content: center; } -} -.finish-installation { - .tada { - width: 300px; + .finish-installation { + .tada { + width: 300px; + } + + .row { + text-align: center; + margin-bottom: 1em; + } + + .help-text { + color: var(--primary-medium); + } } - .row { - text-align: center; - margin-bottom: 1em; + .discourse-logo { + &, + svg { + height: 30px; + width: 110px; + } + + .logo-contour { + fill: var(--primary); + } } - .help-text { - color: var(--primary-medium); - } -} + .wizard-warning { + font-family: sans-serif; -.discourse-logo { - &, - svg { - height: 30px; - width: 110px; + p { + margin-top: 0; + } + + fieldset { + display: none; + } + + h2 { + display: none; + } + .sa-icon { + display: none !important; + } } - .logo-contour { - fill: var(--primary); - } -} - -.wizard-warning { - font-family: sans-serif; - - p { - margin-top: 0; + .select { + width: 400px; } - fieldset { + .hidden { display: none; } - h2 { - display: none; + .wizard-canvas { + position: absolute; + top: 0; + left: 0; + z-index: 10; } - .sa-icon { - display: none !important; - } -} -.select { - width: 400px; -} - -.hidden { - display: none; -} - -.wizard-canvas { - position: absolute; - top: 0; - left: 0; - z-index: 10; -} - -.staff-count { - font-weight: bold; -} - -.wizard-step-privacy { - label[for="privacy_options"] .field-description { - margin-bottom: 1em; + .staff-count { font-weight: bold; } - .field-privacy-options { - margin-bottom: 0.8em; - - .radio-label { - font-weight: normal; - } - } -} - -.wizard-step-form { - max-height: 500px; - display: flex; - - .wizard-fields-main { - width: 100%; - } - - .wizard-fields-sidebar { - width: 170px; - box-sizing: border-box; - padding: 30px 0px 15px 15px; - background: var(--primary-very-low); - + .wizard-fields-main { - padding: 15px; - padding-top: 30px; - background: var(--primary-very-low); - width: calc(100% - 170px); - } - } -} - -.wizard-step-styling { - .preview-nav { - display: flex; - justify-content: flex-end; - position: relative; - margin-top: -1px; - padding-right: 10px; - .preview-nav-button { - text-align: center; - padding: 10px 15px; - cursor: pointer; - margin-left: 10px; - font-size: 14px; + .wizard-step-privacy { + label[for="privacy_options"] .field-description { + margin-bottom: 1em; font-weight: bold; - color: var(--primary-high); - &.active { - background: var(--secondary); - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - color: var(--tertiary); - border: 1px dashed var(--tertiary-low); - border-top: none; + } + + .field-privacy-options { + margin-bottom: 0.8em; + + .radio-label { + font-weight: normal; } } } - .previews { - position: relative; - height: 320px; - width: 100%; - overflow: hidden; - background: var(--secondary); - border: 1px dashed var(--tertiary-low); - border-radius: 10px; - cursor: grab; - user-select: none; - &.dragging { - cursor: grabbing; - } - .topic-preview { - position: absolute; - left: 0px; - top: 0px; - transform: scale(0.85) translateX(-45px); - } - .homepage-preview { - position: absolute; - left: calc(100% + 25px); - top: 0px; - transform: scale(0.85); - padding-right: 20px; - } - } -} - -.wizard-column { - position: relative; - z-index: 11; - background-color: var(--secondary); - box-shadow: 0 5px 10px rgba(var(--primary-rgb), 0.15); - box-sizing: border-box; - margin: 1.5em auto; - padding: 0; - max-width: 820px; - min-width: 280px; - width: 100%; - border: 1px solid var(--primary-low-mid); - border-radius: 5px; - - a { - text-decoration: none; - color: var(--tertiary); - } - - .preloaded-font-styles { - font-size: 1px; - } - - .wizard-step-contents { - height: 550px; - margin-bottom: 2em; - } - - .wizard-column-contents { - padding: 1.2em; - - h1 { - margin: 0 0 1em 0; - } - } - - .wizard-step-description { - margin-bottom: 2em; - } - .wizard-step-banner { - margin-bottom: 2em; - width: 100%; - display: block; - } - - .wizard-footer { - border-top: 1px solid var(--primary-low-mid); - background-color: var(--primary-low); - padding: 0.5em; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - } - - .wizard-progress { - border: 1px solid var(--tertiary-high); - width: 200px; - height: 1.4em; - - div { - position: absolute; - height: 1.4em; - } - - .white { - background: var(--secondary); - width: 200px; - z-index: 11; - } - - .black { - background: var(--primary); - transition: width 0.3s; - z-index: 12; - } - - span { - position: absolute; - font-size: $font-0; - mix-blend-mode: difference; - color: var(--secondary-or-primary); - z-index: 13; - left: 1.5em; - line-height: 1.4em; - } - - .screen { - background-color: var(--tertiary-high); - mix-blend-mode: screen; - width: 200px; - z-index: 14; - } - } - - .action-link { - margin-right: 1em; - text-decoration: none; - color: var(--tertiary); - &:hover { - color: var(--tertiary-hover); - } - } - - .wizard-btn { - border-radius: 2px; - font-size: $font-0; - border: 0; - padding: 0.5em; - transition: background-color 0.3s; - margin-right: 0.5em; - text-decoration: none; - background-color: var(--secondary); - color: var(--primary-very-high); - box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.4); - cursor: pointer; - - &.small { - padding: 0.25em 0.5em; - font-size: $font-down-1; - } - - &:hover, - &:focus { - background-color: var(--primary-low); - } - - &:active { - background-color: var(--primary-low-mid); - } - - &:disabled, - &.disabled { - background-color: var(--primary-medium); - } - - .d-icon-chevron-right { - margin-left: 0.25em; - font-size: 0.8em; - } - .d-icon-chevron-left { - margin-right: 0.25em; - font-size: 0.8em; - } - } - - .wizard-btn.primary { - background-color: var(--tertiary); - color: var(--secondary); - box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.6); - - &:hover, - &:focus { - background-color: var(--tertiary-hover); - } - - &:active { - background-color: var(--tertiary-high); - } - - &:disabled { - background-color: var(--tertiary-low); - } - } - - .wizard-btn.danger { - background-color: var(--danger); - color: var(--secondary); - - &:hover, - &:focus { - background-color: var(--danger-hover); - } - - &:active { - background-color: var(--danger-medium); - } - - &:disabled { - background-color: var(--danger-low); - } - } - - .wizard-btn-upload { - clear: both; - display: inline-block; - .fa { - margin-left: 0.5em; - } - } - - .wizard-hidden-upload-field { - visibility: hidden; - position: absolute; - } - - .wizard-step-footer { + .wizard-step-form { + max-height: 500px; display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - .wizard-btn.next { - min-width: 70px; + .wizard-fields-main { + width: 100%; + } + + .wizard-fields-sidebar { + width: 170px; + box-sizing: border-box; + padding: 30px 0px 15px 15px; + background: var(--primary-very-low); + + .wizard-fields-main { + padding: 15px; + padding-top: 30px; + background: var(--primary-very-low); + width: calc(100% - 170px); + } + } + } + + .wizard-step-styling { + .preview-nav { + display: flex; + justify-content: flex-end; + position: relative; + margin-top: -1px; + padding-right: 10px; + .preview-nav-button { + text-align: center; + padding: 10px 15px; + cursor: pointer; + margin-left: 10px; + font-size: 14px; + font-weight: bold; + color: var(--primary-high); + &.active { + background: var(--secondary); + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + color: var(--tertiary); + border: 1px dashed var(--tertiary-low); + border-top: none; + } + } + } + + .previews { + position: relative; + height: 320px; + width: 100%; + overflow: hidden; + background: var(--secondary); + border: 1px dashed var(--tertiary-low); + border-radius: 10px; + cursor: grab; + user-select: none; + &.dragging { + cursor: grabbing; + } + .topic-preview { + position: absolute; + left: 0px; + top: 0px; + transform: scale(0.85) translateX(-45px); + } + .homepage-preview { + position: absolute; + left: calc(100% + 25px); + top: 0px; + transform: scale(0.85); + padding-right: 20px; + } + } + } + + .wizard-column { + position: relative; + z-index: 11; + background-color: var(--secondary); + box-shadow: 0 5px 10px rgba(var(--primary-rgb), 0.15); + box-sizing: border-box; + margin: 1.5em auto; + padding: 0; + max-width: 820px; + min-width: 280px; + width: 100%; + border: 1px solid var(--primary-low-mid); + border-radius: 5px; + + a { + text-decoration: none; + color: var(--tertiary); + } + + .preloaded-font-styles { + font-size: 1px; + } + + .wizard-step-contents { + height: 550px; + margin-bottom: 2em; + } + + .wizard-column-contents { + padding: 1.2em; + + h1 { + margin: 0 0 1em 0; + } + } + + .wizard-step-description { + margin-bottom: 2em; + } + .wizard-step-banner { + margin-bottom: 2em; + width: 100%; + display: block; + } + + .wizard-footer { + border-top: 1px solid var(--primary-low-mid); + background-color: var(--primary-low); + padding: 0.5em; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } + + .wizard-progress { + border: 1px solid var(--tertiary-high); + width: 200px; + height: 1.4em; + + div { + position: absolute; + height: 1.4em; + } + + .white { + background: var(--secondary); + width: 200px; + z-index: 11; + } + + .black { + background: var(--primary); + transition: width 0.3s; + z-index: 12; + } + + span { + position: absolute; + font-size: var(--font-0); + mix-blend-mode: difference; + color: var(--secondary-or-primary); + z-index: 13; + left: 1.5em; + line-height: 1.4em; + } + + .screen { + background-color: var(--tertiary-high); + mix-blend-mode: screen; + width: 200px; + z-index: 14; + } + } + + .action-link { + margin-right: 1em; + text-decoration: none; + color: var(--tertiary); + &:hover { + color: var(--tertiary-hover); + } + } + + .wizard-btn { + border-radius: 2px; + font-size: var(--font-0); + border: 0; + padding: 0.5em; + transition: background-color 0.3s; + margin-right: 0.5em; + text-decoration: none; + background-color: var(--secondary); + color: var(--primary-very-high); + box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.4); + cursor: pointer; + + &.small { + padding: 0.25em 0.5em; + font-size: var(--font-down-1); + } + + &:hover, + &:focus { + background-color: var(--primary-low); + } + + &:active { + background-color: var(--primary-low-mid); + } + + &:disabled, + &.disabled { + background-color: var(--primary-medium); + } .d-icon-chevron-right { margin-left: 0.25em; font-size: 0.8em; } + .d-icon-chevron-left { + margin-right: 0.25em; + font-size: 0.8em; + } } - button.wizard-btn:last-child { - margin-right: 0; - } - - button.wizard-btn.done, - button.wizard-btn.finish { + .wizard-btn.primary { + background-color: var(--tertiary); color: var(--secondary); - background-color: var(--success); + box-shadow: 0 1px 4px rgba(var(--always-black-rgb), 0.6); &:hover, &:focus { - background-color: var(--success-hover); + background-color: var(--tertiary-hover); } &:active { - background-color: var(--success-medium); + background-color: var(--tertiary-high); } &:disabled { - background-color: var(--success-low); - } - } - } - - .wizard-image-row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - } - - .wizard-image-preview { - img.field-logo-url { - max-height: 40px; - } - img.field-logo-small-url { - max-height: 40px; - max-width: 80px; - } - img.field-favicon-url { - max-height: 16px; - max-width: 16px; - } - img.field-apple-touch-icon-url { - max-height: 40px; - max-width: 40px; - } - - padding: 0.1em; - } - - .wizard-field { - label .label-value { - font-weight: bold; - } - - .input-area { - margin-top: 0.5em; - } - - .field-error-description { - color: var(--danger); - font-weight: bold; - } - - .field-description { - margin-top: 0.5em; - } - - .field-extra-description { - margin-top: 0.5em; - color: var(--primary-high); - } - - .field-required { - display: none; - } - - &.text-field { - .field-required { - display: inline; - color: var(--danger); + background-color: var(--tertiary-low); } } - margin-bottom: 2em; - } + .wizard-btn.danger { + background-color: var(--danger); + color: var(--secondary); - .wizard-image-row canvas { - border: 1px solid rgba(0, 0, 0, 0.2); - } -} + &:hover, + &:focus { + background-color: var(--danger-hover); + } -.textarea-field textarea { - width: 100%; - height: 10em; - padding: 6px; - background: var(--secondary); -} + &:active { + background-color: var(--danger-medium); + } -.text-field input { - width: 100%; - font-size: $font-up-1; - margin: 0; - padding: 6px; - background-color: var(--secondary); - border: 1px solid var(--primary-low-mid); - transition: border-color 0.5s; -} + &:disabled { + background-color: var(--danger-low); + } + } -.textarea-field.invalid textarea, -.text-field.invalid input { - outline: 0; - border: 3px solid var(--danger); - animation: bump 0.25s ease-in-out; - animation-iteration-count: 2; -} + .wizard-btn-upload { + clear: both; + display: inline-block; + .fa { + margin-left: 0.5em; + } + } -.radio-field-choice { - margin-bottom: 1.25em; + .wizard-hidden-upload-field { + visibility: hidden; + position: absolute; + } - .radio-label { - font-weight: bold; - margin-left: 0.5em; - } - .radio-description { - margin-top: 0.25em; - margin-left: 1.75em; - color: var(--primary-high); - } -} - -.invite-list { - .users-list { - margin-bottom: 1em; - - .invite-list-user { + .wizard-step-footer { display: flex; flex-direction: row; justify-content: space-between; + align-items: center; + + .wizard-btn.next { + min-width: 70px; + + .d-icon-chevron-right { + margin-left: 0.25em; + font-size: 0.8em; + } + } + + button.wizard-btn:last-child { + margin-right: 0; + } + + button.wizard-btn.done, + button.wizard-btn.finish { + color: var(--secondary); + background-color: var(--success); + + &:hover, + &:focus { + background-color: var(--success-hover); + } + + &:active { + background-color: var(--success-medium); + } + + &:disabled { + background-color: var(--success-low); + } + } + } + + .wizard-image-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .wizard-image-preview { + img.field-logo-url { + max-height: 40px; + } + img.field-logo-small-url { + max-height: 40px; + max-width: 80px; + } + img.field-favicon-url { + max-height: 16px; + max-width: 16px; + } + img.field-apple-touch-icon-url { + max-height: 40px; + max-width: 40px; + } + + padding: 0.1em; + } + + .wizard-field { + label .label-value { + font-weight: bold; + } + + .input-area { + margin-top: 0.5em; + } + + .field-error-description { + color: var(--danger); + font-weight: bold; + } + + .field-description { + margin-top: 0.5em; + } + + .field-extra-description { + margin-top: 0.5em; + color: var(--primary-high); + } + + .field-required { + display: none; + } + + &.text-field { + .field-required { + display: inline; + color: var(--danger); + } + } + + margin-bottom: 2em; + } + + .wizard-image-row canvas { + border: 1px solid rgba(0, 0, 0, 0.2); + } + } + + .textarea-field textarea { + width: 100%; + height: 10em; + padding: 6px; + background: var(--secondary); + } + + .text-field input { + width: 100%; + font-size: var(--font-up-1); + margin: 0; + padding: 6px; + background-color: var(--secondary); + border: 1px solid var(--primary-low-mid); + transition: border-color 0.5s; + } + + .textarea-field.invalid textarea, + .text-field.invalid input { + outline: 0; + border: 3px solid var(--danger); + animation: bump 0.25s ease-in-out; + animation-iteration-count: 2; + } + + .radio-field-choice { + margin-bottom: 1.25em; + + .radio-label { + font-weight: bold; + margin-left: 0.5em; + } + .radio-description { + margin-top: 0.25em; + margin-left: 1.75em; + color: var(--primary-high); + } + } + + .invite-list { + .users-list { margin-bottom: 1em; - .email { - width: 330px; - } + .invite-list-user { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 1em; - .role { - width: 200px; + .email { + width: 330px; + } + + .role { + width: 200px; + } + } + } + + .new-user { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-bottom: 1em; + + .invite-email { + width: 350px; + } + } + + button.add-user { + .fa { + margin-right: 0.5em; } } } - .new-user { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - margin-bottom: 1em; - - .invite-email { - width: 350px; - } - } - - button.add-user { - .fa { - margin-right: 0.5em; - } - } -} - -.modal-footer { - a.btn { - text-decoration: none; - display: inline-block; - padding: 6px 12px; - background: var(--primary-low); - color: var(--primary); - &:hover { - background: var(--primary-medium); - color: var(--secondary); - } - - &.btn-primary { - background: var(--tertiary); - color: var(--secondary); + .modal-footer { + a.btn { + text-decoration: none; + display: inline-block; + padding: 6px 12px; + background: var(--primary-low); + color: var(--primary); &:hover { - background-color: var(--tertiary-hover); + background: var(--primary-medium); + color: var(--secondary); + } + + &.btn-primary { + background: var(--tertiary); + color: var(--secondary); + &:hover { + background-color: var(--tertiary-hover); + } + } + } + } + + /* fix wizard for mobile -- iPhone 5 default width */ + @media only screen and (max-device-width: 568px) { + h1 { + font-size: var(--font-up-2) !important; + } + .wizard-column { + margin: auto !important; + } + .wizard-step-form { + max-height: none; + } + .wizard-step-contents { + height: auto !important; + } + .wizard-step-banner { + width: 100% !important; + margin-bottom: 1em !important; + } + .wizard-step-footer { + display: block !important; + } + .wizard-progress { + margin-bottom: 10px !important; + } + .wizard-buttons { + text-align: right !important; + } + .wizard-footer { + display: none !important; + } + .wizard-step-description { + margin-bottom: 1em !important; + } + .wizard-column-contents { + padding: 1em !important; + } + .emoji-preview img { + width: 16px !important; + height: 16px !important; + } + .invite-list .new-user { + flex-direction: column !important; + align-items: inherit !important; + } + .invite-list .new-user .invite-email { + width: 100% !important; + margin-bottom: 5px !important; + } + .invite-list .add-user { + margin-top: 5px !important; + } + + canvas { + max-width: 365px; + } + + .wizard-step-logos, + .wizard-step-icons { + canvas { + max-width: 225px; } } } } - -@media only screen and (min-device-width: 569px) { - @import "desktop/modal"; -} - -/* fix wizard for mobile -- iPhone 5 default width */ -@media only screen and (max-device-width: 568px) { - @import "mobile/modal"; - - h1 { - font-size: $font-up-2 !important; - } - .wizard-column { - margin: auto !important; - } - .wizard-step-form { - max-height: none; - } - .wizard-step-contents { - height: auto !important; - } - .wizard-step-banner { - width: 100% !important; - margin-bottom: 1em !important; - } - .wizard-step-footer { - display: block !important; - } - .wizard-progress { - margin-bottom: 10px !important; - } - .wizard-buttons { - text-align: right !important; - } - .wizard-footer { - display: none !important; - } - .wizard-step-description { - margin-bottom: 1em !important; - } - .wizard-column-contents { - padding: 1em !important; - } - .emoji-preview img { - width: 16px !important; - height: 16px !important; - } - .invite-list .new-user { - flex-direction: column !important; - align-items: inherit !important; - } - .invite-list .new-user .invite-email { - width: 100% !important; - margin-bottom: 5px !important; - } - .invite-list .add-user { - margin-top: 5px !important; - } - - canvas { - max-width: 365px; - } - - .wizard-step-logos, - .wizard-step-icons { - canvas { - max-width: 225px; - } - } -} From 12980418ae5b947fb67e0427d3afe3fd224927c4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 17 Jun 2022 16:51:28 +0100 Subject: [PATCH 039/184] DEV: Disable the use of 'legacy' Ember assets (#17127) Anyone still using `EMBER_CLI_PROD_ASSETS=0` in development or production will be gracefully switched to Ember CLI. In development, a repeated message will be logged to STDERR. Similarly, passing `QUNIT_EMBER_CLI=0` to the qunit rake task will now do nothing. A warning will be printed, and ember-cli mode will be used. Note that we've chosen not to fail the task, so that existing plugin/theme CI jobs don't immediately start failing. We may switch to a hard fail in the coming days/weeks. --- .github/workflows/tests.yml | 12 +----------- lib/ember_cli.rb | 5 ++++- lib/tasks/assets.rake | 4 ++++ lib/tasks/qunit.rake | 6 +++++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b2d486670e..9e04565559 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: fail-fast: false matrix: - build_type: [backend, frontend, frontend-legacy, annotations] + build_type: [backend, frontend, annotations] target: [core, plugins] exclude: - build_type: annotations @@ -153,16 +153,6 @@ jobs: if: matrix.build_type == 'backend' && matrix.target == 'plugins' run: bin/rake plugin:spec - - name: Core QUnit (Legacy) - if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core' - run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000'] - timeout-minutes: 30 - - - name: Plugin QUnit (Legacy) - if: matrix.build_type == 'frontend-legacy' && matrix.target == 'plugins' - run: QUNIT_EMBER_CLI=0 bin/rake plugin:qunit['*','1200000'] - timeout-minutes: 30 - - name: Plugin QUnit (Ember CLI) if: matrix.build_type == 'frontend' && (matrix.target == 'plugins' || matrix.target == 'core-plugins') run: QUNIT_EMBER_CLI=1 bin/rake plugin:qunit['*','1200000'] diff --git a/lib/ember_cli.rb b/lib/ember_cli.rb index de6d5ef310..04387c67e7 100644 --- a/lib/ember_cli.rb +++ b/lib/ember_cli.rb @@ -17,7 +17,10 @@ module EmberCli } def self.enabled? - ENV["EMBER_CLI_PROD_ASSETS"] != "0" + if !Rails.env.production? && ENV["EMBER_CLI_PROD_ASSETS"] == "0" + STDERR.puts "The 'legacy' ember environment is discontinued. Running with ember-cli assets. Remove the EMBER_CLI_PROD_ASSETS=0 flag." + end + true end def self.script_chunks diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index b17f777d04..d5c84ec344 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -4,6 +4,10 @@ if !defined?(EMBER_CLI) EMBER_CLI = EmberCli.enabled? end +if ENV["EMBER_CLI_PROD_ASSETS"] == "0" + STDERR.puts "The 'legacy' ember environment is discontinued. Compiling with ember-cli assets..." +end + task 'assets:precompile:before' do require 'uglifier' diff --git a/lib/tasks/qunit.rake b/lib/tasks/qunit.rake index 35da5e3590..a7294a38ba 100644 --- a/lib/tasks/qunit.rake +++ b/lib/tasks/qunit.rake @@ -28,7 +28,11 @@ task "qunit:test", [:timeout, :qunit_path] do |_, args| false end - ember_cli = ENV['QUNIT_EMBER_CLI'] == "1" + if ENV['QUNIT_EMBER_CLI'] == "0" + puts "The 'legacy' ember environment is discontinued - running tests with ember-cli assets..." + end + + ember_cli = true port = ENV['TEST_SERVER_PORT'] || 60099 while !port_available? port From 1f85eea64cb0797917d2a2b051a6a9ab3d7b1fe8 Mon Sep 17 00:00:00 2001 From: 1resu Date: Fri, 17 Jun 2022 17:58:04 +0200 Subject: [PATCH 040/184] DEV: Allow changing APP_ROOT for puma via ENV variable (#15744) --- config/puma.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/puma.rb b/config/puma.rb index 12c9886df0..0fea9a2487 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -3,7 +3,7 @@ if ENV['RAILS_ENV'] == 'production' # First, you need to change these below to your situation. - APP_ROOT = '/home/discourse/discourse' + APP_ROOT = ENV["APP_ROOT"] || '/home/discourse/discourse' num_workers = ENV["NUM_WEBS"].to_i > 0 ? ENV["NUM_WEBS"].to_i : 4 # Second, you can choose how many threads that you are going to run at same time. From 6ad5db80deea0690871d625269117e3c81859144 Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Fri, 17 Jun 2022 11:19:14 -0500 Subject: [PATCH 041/184] DEV: Remove use of run-loop-dot-access (#17114) Context: https://deprecations.emberjs.com/v3.x/#toc_deprecated-run-loop-and-computed-dot-access --- .../admin/addon/controllers/admin-badges-show.js | 4 ++-- .../javascripts/discourse/app/helpers/route-action.js | 4 ++-- app/assets/javascripts/discourse/app/lib/d-popover.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js index 33c8ad620b..91a2687346 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js @@ -6,7 +6,7 @@ import { bufferedProperty } from "discourse/mixins/buffered-content"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { propertyNotEqual } from "discourse/lib/computed"; import { equal, reads } from "@ember/object/computed"; -import { run } from "@ember/runloop"; +import { next } from "@ember/runloop"; import { action } from "@ember/object"; import getURL from "discourse-common/lib/get-url"; @@ -33,7 +33,7 @@ export default Controller.extend(bufferedProperty("model"), { // this is needed because the model doesnt have default values // and as we are using a bufferedProperty it's not accessible // in any other way - run.next(() => { + next(() => { if (this.model) { if (!this.model.badge_type_id) { this.model.set( diff --git a/app/assets/javascripts/discourse/app/helpers/route-action.js b/app/assets/javascripts/discourse/app/helpers/route-action.js index c55052a6dd..d9dcad74e3 100644 --- a/app/assets/javascripts/discourse/app/helpers/route-action.js +++ b/app/assets/javascripts/discourse/app/helpers/route-action.js @@ -2,7 +2,7 @@ import { A } from "@ember/array"; import Helper from "@ember/component/helper"; import { computed, get } from "@ember/object"; import { getOwner } from "@ember/application"; -import { run } from "@ember/runloop"; +import { join } from "@ember/runloop"; import { assert, runInDebug } from "@ember/debug"; function getCurrentRouteInfos(router) { @@ -40,7 +40,7 @@ export function routeAction(actionName, router, ...params) { return function (...invocationArgs) { let { action, handler } = getRouteWithAction(router, actionName); let args = params.concat(invocationArgs); - return run.join(handler, action, ...args); + return join(handler, action, ...args); }; } diff --git a/app/assets/javascripts/discourse/app/lib/d-popover.js b/app/assets/javascripts/discourse/app/lib/d-popover.js index 052ae5c201..9c943d979a 100644 --- a/app/assets/javascripts/discourse/app/lib/d-popover.js +++ b/app/assets/javascripts/discourse/app/lib/d-popover.js @@ -1,5 +1,5 @@ import { isLegacyEmber } from "discourse-common/config/environment"; -import { run } from "@ember/runloop"; +import { begin, end } from "@ember/runloop"; import tippy from "tippy.js"; import { iconHTML } from "discourse-common/lib/icon-library"; @@ -41,8 +41,8 @@ export function showPopover(event, options = {}) { // hangs on legacy ember if (!isLegacyEmber) { - run.begin(); - instance.popper.addEventListener("transitionend", run.end, { + begin(); + instance.popper.addEventListener("transitionend", end, { once: true, }); } From 84b0a6414d15151113443c1d15199f6b98d86267 Mon Sep 17 00:00:00 2001 From: Ghassan Maslamani Date: Fri, 17 Jun 2022 19:32:57 +0300 Subject: [PATCH 042/184] FIX: double selecting replies (#17086) When selecting a post and its replies using the "select +replies" button, the action would push all ids, without checking if some were already selected. This change add a filter to remove ids that are already selected. This fixes https://meta.discourse.org/t/selecting-posts-replies-miscounts-the-number-of-posts/229242 Co-authored-by: @ZogStriP --- .../discourse/app/controllers/topic.js | 3 +- .../tests/unit/controllers/topic-test.js | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index 3832df9372..2e28cca50f 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -890,7 +890,8 @@ export default Controller.extend(bufferedProperty("model"), { selectReplies(post) { ajax(`/posts/${post.id}/reply-ids.json`).then((replies) => { const replyIds = replies.map((r) => r.id); - this.selectedPostIds.pushObjects([post.id, ...replyIds]); + const postIds = [...this.selectedPostIds, post.id, ...replyIds]; + this.set("selectedPostIds", [...new Set(postIds)]); this._forceRefreshPostStream(); }); }, diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js index e7b66504a9..de0517afb0 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js +++ b/app/assets/javascripts/discourse/tests/unit/controllers/topic-test.js @@ -599,6 +599,52 @@ discourseModule("Unit | Controller | topic", function (hooks) { ); }); + test("selectReplies", async function (assert) { + pretender.get("/posts/1/reply-ids.json", () => { + return [ + 200, + { "Content-Type": "application/json" }, + [{ id: 2, level: 1 }], + ]; + }); + + let model = topicWithStream({ + posts: [{ id: 1 }, { id: 2 }], + }); + + const controller = this.getController("topic", { model }); + + controller.send("selectReplies", { id: 1 }); + await settled(); + + assert.strictEqual( + controller.get("selectedPostsCount"), + 2, + "It should select two, the post and its replies" + ); + + controller.send("togglePostSelection", { id: 1 }); + assert.strictEqual( + controller.get("selectedPostsCount"), + 1, + "It should be selecting one only " + ); + assert.strictEqual( + controller.get("selectedPostIds")[0], + 2, + "It should be selecting the reply id " + ); + + controller.send("selectReplies", { id: 1 }); + await settled(); + + assert.strictEqual( + controller.get("selectedPostsCount"), + 2, + "It should be selecting two, even if reply was already selected" + ); + }); + test("topVisibleChanged", function (assert) { let model = topicWithStream({ posts: [{ id: 1 }], From 2eeb50dfc1f1a0fdc84d45400eb98fabd3be3ba4 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 19:34:08 +0200 Subject: [PATCH 043/184] FIX: Extra-locale merging didn't account for fallbacks (#17128) Regressed in #17027 --- lib/js_locale_helper.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb index 0b5164665a..009b75698f 100644 --- a/lib/js_locale_helper.rb +++ b/lib/js_locale_helper.rb @@ -202,8 +202,9 @@ module JsLocaleHelper def self.output_extra_locales(bundle, locale) translations = translations_for(locale) + locales = translations.keys - translations.keys.each do |l| + locales.each do |l| translations[l].keys.each do |k| bundle_translations = translations[l].delete(k) translations[l].deep_merge!(bundle_translations) if k == bundle @@ -212,17 +213,15 @@ module JsLocaleHelper return "" if translations.blank? - <<~JS - if (!I18n.extras) { - I18n.extras = {} - } + output = +"if (!I18n.extras) { I18n.extras = {}; }" + locales.each do |l| + output << <<~JS + if (!I18n.extras["#{l}"]) { I18n.extras["#{l}"] = {}; } + Object.assign(I18n.extras["#{l}"], #{translations[l].to_json}); + JS + end - if (!I18n.extras["#{locale}"]) { - I18n.extras["#{locale}"] = {}; - } - - Object.assign(I18n.extras["#{locale}"], #{translations[locale].to_json}); - JS + output end MOMENT_LOCALE_MAPPING ||= { From 1022d8872c491ca4a8db94b09bd160f6575e66ad Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Sat, 18 Jun 2022 00:17:15 +0200 Subject: [PATCH 044/184] FIX: Add missing pluralization rules for Bosnian (#17131) --- lib/javascripts/locale/bs.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/javascripts/locale/bs.js diff --git a/lib/javascripts/locale/bs.js b/lib/javascripts/locale/bs.js new file mode 100644 index 0000000000..e2c139ddf9 --- /dev/null +++ b/lib/javascripts/locale/bs.js @@ -0,0 +1,10 @@ +MessageFormat.locale.bs = function (n) { + if ((n % 10) == 1 && (n % 100) != 11) { + return 'one'; + } + if ((n % 10) >= 2 && (n % 10) <= 4 && + ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { + return 'few'; + } + return 'other'; +}; From 7ac485fb9f8adf466d38a74e457bea4415f04ad1 Mon Sep 17 00:00:00 2001 From: Discourse Translator Bot Date: Fri, 17 Jun 2022 18:18:22 -0400 Subject: [PATCH 045/184] FEATURE: Add Croatian language (#17130) Co-authored-by: Gerhard Schlager --- app/assets/javascripts/locales/hr.js.erb | 3 + config/locales/client.hr.yml | 5501 +++++++++++++++++ config/locales/server.hr.yml | 1085 ++++ lib/javascripts/locale/hr.js | 4 - .../config/locales/client.hr.yml | 13 + .../config/locales/server.hr.yml | 9 + .../config/locales/client.hr.yml | 42 + .../config/locales/server.hr.yml | 12 + .../config/locales/client.hr.yml | 12 + .../config/locales/server.hr.yml | 98 + .../config/locales/client.hr.yml | 21 + .../config/locales/server.hr.yml | 10 + plugins/poll/config/locales/client.hr.yml | 125 + plugins/poll/config/locales/server.hr.yml | 60 + .../styleguide/config/locales/client.hr.yml | 89 + .../styleguide/config/locales/server.hr.yml | 10 + public/403.hr.html | 28 + public/422.hr.html | 27 + public/500.hr.html | 13 + public/503.hr.html | 12 + 20 files changed, 7170 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/locales/hr.js.erb create mode 100644 config/locales/client.hr.yml create mode 100644 config/locales/server.hr.yml create mode 100644 plugins/discourse-details/config/locales/client.hr.yml create mode 100644 plugins/discourse-details/config/locales/server.hr.yml create mode 100644 plugins/discourse-local-dates/config/locales/client.hr.yml create mode 100644 plugins/discourse-local-dates/config/locales/server.hr.yml create mode 100644 plugins/discourse-narrative-bot/config/locales/client.hr.yml create mode 100644 plugins/discourse-narrative-bot/config/locales/server.hr.yml create mode 100644 plugins/discourse-presence/config/locales/client.hr.yml create mode 100644 plugins/discourse-presence/config/locales/server.hr.yml create mode 100644 plugins/poll/config/locales/client.hr.yml create mode 100644 plugins/poll/config/locales/server.hr.yml create mode 100644 plugins/styleguide/config/locales/client.hr.yml create mode 100644 plugins/styleguide/config/locales/server.hr.yml create mode 100644 public/403.hr.html create mode 100644 public/422.hr.html create mode 100644 public/500.hr.html create mode 100644 public/503.hr.html diff --git a/app/assets/javascripts/locales/hr.js.erb b/app/assets/javascripts/locales/hr.js.erb new file mode 100644 index 0000000000..df6aa33c6e --- /dev/null +++ b/app/assets/javascripts/locales/hr.js.erb @@ -0,0 +1,3 @@ +//= depend_on 'client.hr.yml' +//= require locales/i18n +<%= JsLocaleHelper.output_locale(:hr) %> diff --git a/config/locales/client.hr.yml b/config/locales/client.hr.yml new file mode 100644 index 0000000000..eda8690d46 --- /dev/null +++ b/config/locales/client.hr.yml @@ -0,0 +1,5501 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + number: + format: + separator: "." + delimiter: "," + human: + storage_units: + format: "%n %u" + units: + byte: + one: Bajt + few: Bajta + other: Bajta + gb: GB + kb: KB + mb: MB + tb: TB + short: + thousands: "%{number}k" + millions: "%{number}M" + dates: + time: "h:mm a" + time_with_zone: "Hh:mm (z)" + time_short_day: "dd, Hh:mm" + timeline_date: "MMM YYYY" + long_no_year: "D MMM, HH:Mm" + long_no_year_no_time: "D MMM" + full_no_year_no_time: "Do MMMM " + long_with_year: "D MMM, YYY h:mm a" + long_with_year_no_time: "D MMM, YYYY" + full_with_year_no_time: "Do MMMM, YYYY" + long_date_with_year: "D MMM, 'YY LT" + long_date_without_year: "D MMM, LT" + long_date_with_year_without_time: "D MMM, 'YY" + long_date_without_year_with_linebreak: "D MMM
LT" + long_date_with_year_with_linebreak: "D MMM, 'YY
LT" + wrap_ago: "prije %{date} " + wrap_on: "na %{date}" + tiny: + half_a_minute: "< 1 min." + less_than_x_seconds: + one: "< %{count} sek." + few: "< %{count} sek." + other: "< %{count} sek." + x_seconds: + one: "%{count} sek." + few: "%{count} sek." + other: "%{count} sek." + less_than_x_minutes: + one: "< %{count}m" + few: "< %{count}m" + other: "< %{count}m" + x_minutes: + one: "%{count} minuta" + few: "%{count} minuta" + other: "%{count} minuta" + about_x_hours: + one: "%{count} sat" + few: "%{count} sata" + other: "%{count}sati" + x_days: + one: "%{count} dan" + few: "%{count} dana" + other: "%{count}dana" + x_months: + one: "%{count} mjesec" + few: "%{count} mjeseci" + other: "%{count}mjeseci" + about_x_years: + one: "%{count} godina" + few: "%{count} godina" + other: "%{count}godina" + over_x_years: + one: "> %{count} godina" + few: "> %{count} godina" + other: "> %{count} godina" + almost_x_years: + one: "%{count} godina" + few: "%{count} godine" + other: "%{count}godina" + date_month: "D MMM" + date_year: "Mjesec Godina" + medium: + less_than_x_minutes: + one: "manje od %{count} min" + few: "manje od %{count} minuta" + other: "manje od %{count} minuta" + x_minutes: + one: "%{count} min" + few: "%{count} mins" + other: "%{count} minuta" + x_hours: + one: "%{count} sat" + few: "%{count} sati" + other: "%{count} sati" + about_x_hours: + one: "oko %{count} sat" + few: "oko %{count} sati" + other: "oko %{count} sati" + x_days: + one: "%{count} dan" + few: "%{count} dana" + other: "%{count} dana" + x_months: + one: "%{count} mjesec" + few: "%{count} mjeseci" + other: "%{count} mjeseci" + about_x_years: + one: "oko %{count} godinu" + few: "oko %{count} godina" + other: "oko %{count} godina" + over_x_years: + one: "preko %{count} godinu" + few: "preko %{count} godina" + other: "preko %{count} godina" + almost_x_years: + one: "skoro %{count} godina" + few: "skoro %{count} godina" + other: "skoro %{count} godina" + medium_with_ago: + x_minutes: + one: "prije %{count} minutu" + few: "prije %{count} minuta" + other: "prije %{count} minuta" + x_hours: + one: "prije %{count} sat" + few: "prije %{count} sata" + other: "prije %{count} sata" + x_days: + one: "prije %{count} dan" + few: "prije %{count} dana" + other: "prije %{count} dana" + x_months: + one: "prije %{count} mjesec" + few: "prije %{count} mjeseci" + other: "prije %{count} mjeseci" + x_years: + one: "prije %{count} godinu" + few: "prije %{count} godina" + other: "prije %{count} godina" + later: + x_days: + one: "%{count} dan kasnije" + few: "%{count} dana kasnije" + other: "%{count} dana kasnije" + x_months: + one: "%{count} mjesec poslije" + few: "nakon %{count} mjeseci" + other: "nakon %{count} mjeseci" + x_years: + one: "%{count} godinu kasnije" + few: "nakon %{count} godina" + other: "nakon %{count} godina" + previous_month: "Prethodni mjesec" + next_month: "Sljedeći mjesec" + placeholder: datum + from_placeholder: "od datuma" + to_placeholder: "do danas" + share: + topic_html: 'Tema: %{topicTitle}' + post: "objava #%{postNumber} od @%{username}" + close: "zatvori" + twitter: "Podijeli na Twitteru" + facebook: "Podijeli na Facebooku" + email: "Pošalji putem e-maila" + url: "Kopiraj i podijeli URL" + action_codes: + public_topic: "je učinio ovu temu javnom %{when}" + open_topic: "pretvorio ovo u temu %{when}" + private_topic: "je učinio ovu temu privatnom porukom %{when}" + split_topic: "je podijelio ovu temu %{when}" + invited_user: "je pozvao %{who} %{when}" + invited_group: "je pozvao %{who} %{when}" + user_left: "%{who} se uklonio iz ove poruke %{when}" + removed_user: "je uklonio %{who} %{when}" + removed_group: "je uklonio %{who} %{when}" + autobumped: "automatski bumpirano %{when}" + autoclosed: + enabled: "zatvoren %{when}" + disabled: "otvoren %{when}" + closed: + enabled: "zatvoren %{when}" + disabled: "otvoren %{when}" + archived: + enabled: "pohranjen %{when}" + disabled: "%{when} ponovo otvoren" + pinned: + enabled: "zakvačeno %{when}" + disabled: "otkvačeno %{when}" + pinned_globally: + enabled: "Globalno zakvačeno %{when}" + disabled: "otkvačeno %{when}" + visible: + enabled: "navedeno %{when}" + disabled: "nenavedeno %{when}" + banner: + enabled: "pretvoreno u banner %{when}. Banner će biti prikazan na vrhu svake stranice dok ga korisnik ne isključi." + disabled: "maknuo banner %{when}. Banner se više neće prikazivati na vrhu svake stranice." + forwarded: "proslijedio je gore navedenu e-poštu" + topic_admin_menu: "mogućnosti teme" + skip_to_main_content: "Preskoči na glavni sadržaj" + wizard_required: "Dobrodošao u svoj novi Discourse! Započnimo s postavljanjem ✨" + emails_are_disabled: "Svi emailovi prema van su blokirani od strane administratora. Ni jedna vrsta obavijesti putem emaila neće biti poslana." + emails_are_disabled_non_staff: "Odlazna e-pošta onemogućena je za korisnike koji nisu zaposleni." + software_update_prompt: + message: "Ažurirali smo ovu stranicu, osvježiteili ćete možda doživjeti neočekivano ponašanje." + dismiss: "Skloni" + bootstrap_mode_enabled: + one: "Kako bi vam olakšali pokretanje novog foruma, nalazite se u bootstrap načinu rada. Svim novim korisnicima biti će dodijeljena razina povjerenja 1 i omogućeni dnevni e-mailovi sa sažecima. Bootstrap će se automatski isključiti čim se učlani minimalno %{count} korisnika." + few: "Kako bi vam olakšali pokretanje vašeg novog foruma, u bootstrap modu ste. Svim novim korisnicima biti će dodijeljena razina povjerenja 1 i omogućeni dnevni e-mailovi sa sažecima. Ovo će automatski biti isključeno kada se učlani minimalno %{count} korisnika." + other: "Kako bi vam olakšali pokretanje novog foruma, nalazite se u bootstrap načinu rada. Svim novim korisnicima biti će dodijeljena razina povjerenja 1 i omogućeni dnevni e-mailovi sa sažecima. Bootstrap će se automatski isključiti čim se učlani minimalno %{count} korisnika." + bootstrap_mode_disabled: "Bootstrap mod biti će isključen u sljedećih 24 sata." + themes: + default_description: "Zadano" + broken_theme_alert: "Vaša stranica možda neće raditi jer tema/komponenta ima pogreške." + error_caused_by: "Uzrokuje '%{name}'. Kliknite ovdje da biste ažurirali, ponovno konfigurirali ili onemogućili." + only_admins: "(ova se poruka prikazuje samo administratorima stranice)" + broken_decorator_alert: "Postovi se možda neće ispravno prikazati jer je jedan od dekoratora sadržaja postova na vašoj web-lokaciji pokrenuo pogrešku." + s3: + regions: + ap_northeast_1: "Azija Pacifik (Tokyo)" + ap_northeast_2: "Azija Pacifik (Seoul)" + ap_east_1: "Azija Pacifik (Hong Kong)" + ap_south_1: "Azija Pacifik (Mumbai)" + ap_southeast_1: "Azija Pacifik (Singapur)" + ap_southeast_2: "Azija Pacifik (Sydney)" + ca_central_1: "Kanada (centralna)" + cn_north_1: "Kina (Peking)" + cn_northwest_1: "Kina (Ningxia)" + eu_central_1: "EU (Frankfurt)" + eu_north_1: "EU (Stockholm)" + eu_south_1: "EU (Milano)" + eu_west_1: "EU (Irska)" + eu_west_2: "EU (London)" + eu_west_3: "EU (Pariz)" + sa_east_1: "Južna Amerika (São Paulo)" + us_east_1: "USA istok (Sjeverna Virginia)" + us_east_2: "SAD Istok (Ohio)" + us_gov_east_1: "AWS GovCloud (SAD-Istok)" + us_gov_west_1: "AWS GovCloud (SAD-zapad)" + us_west_1: "USA Zapad (Sjeverna Kalifornija)" + us_west_2: "USA Zapad (Oregon)" + clear_input: "Obriši unos" + edit: "izmjeni naslov i kategoriju ove teme" + expand: "Proširi" + not_implemented: "Nažalost, ova mogućnost nije još implementirana." + no_value: "Ne" + yes_value: "Da" + submit: "Pošalji" + generic_error: "Dogodila se greška, ispričavamo se." + generic_error_with_reason: "Dogodila se greška: %{error}" + sign_up: "Učlani se" + log_in: "Prijavi se" + age: "Dob" + joined: "Prijavljen" + admin_title: "Administrator" + show_more: "prikaži više" + show_help: "postavke" + links: "Linkovi" + links_lowercase: + one: "poveznica" + few: "poveznica" + other: "poveznice" + faq: "ČPP" + guidelines: "Vodić za uporabu" + privacy_policy: "Izjava o privatnosti" + privacy: "Privatnost" + tos: "Uvjeti korištenja" + rules: "Pravila" + conduct: "Pravila ponašanja" + mobile_view: "Mobilni prikaz" + desktop_view: "Desktop prikaz" + or: "ili" + now: "upravo" + read_more: "pročitaj više" + more: "Više" + x_more: + one: "%{count} još" + few: "Još %{count} " + other: "%{count} još" + never: "nikad" + every_30_minutes: "svakih 30 minuta" + every_hour: "svaki sat" + daily: "dnevno" + weekly: "tjedno" + every_month: "svaki mjesec" + every_six_months: "svakih šest mjeseci" + max_of_count: "ukupno %{count}" + character_count: + one: "%{count} znakova" + few: "%{count} znakova" + other: "%{count} znakova" + period_chooser: + aria_label: "Filtrirajte po razdoblju" + related_messages: + title: "Vezane poruke" + see_all: 'Pregledajte sve poruke od @%{username}...' + suggested_topics: + title: "Preporučene teme" + pm_title: "Predložene poruke" + about: + simple_title: "O nama" + title: "O %{title}" + stats: "Statistika web stranice" + our_admins: "Naši Administratori" + our_moderators: "Naši Moderatori" + moderators: "Moderatori" + stat: + all_time: "Ukupno" + last_day: "Posljednja 24 sata" + last_7_days: "Zadnjih 7 dana" + last_30_days: "Zadnjih 30 dana" + like_count: "Likeovi" + topic_count: "Teme" + post_count: "Objave" + user_count: "Korisnici" + active_user_count: "Aktivni korisnici" + contact: "Kontaktirajte nas" + contact_info: "U slučaju kritičnog problema ili hitnosti koje utječu na rad stranice, kontaktirajte nas na %{contact_info}." + bookmarked: + title: "Zabilješka" + edit_bookmark: "Uredi oznaku" + clear_bookmarks: "Očisti oznake" + help: + bookmark: "Kliknite za označavanje prvog posta u ovoj temi" + edit_bookmark: "Kliknite za uređivanje oznake na ovu temu" + edit_bookmark_for_topic: "Kliknite za uređivanje oznake na ovu temu" + unbookmark: "Klikni za uklananje svih oznaka u ovoj temi" + unbookmark_with_reminder: "Kliknite da biste uklonili sve oznake i podsjetnike u ovoj temi." + bookmarks: + created: "Označili ste ovu objavu %{name}" + created_generic: "Označili ste ovo. %{name}" + create: "Stvori oznaku" + edit: "Uredi oznaku" + not_bookmarked: "zabilježi objavu" + remove_reminder_keep_bookmark: "Ukloni podsjetnik i zadrži oznaku" + created_with_reminder: "Označili ste ovu objavu s podsjetnikom %{date}. %{name}" + created_with_reminder_generic: "Označili ste ovo s podsjetnikom %{date}. %{name}" + remove: "Ukloni zabilješku" + delete: "Izbriši oznaku" + confirm_delete: "Jeste li sigurni da želite izbrisati ovu oznaku? Podsjetnik će također biti izbrisan." + confirm_clear: "Jeste li sigurni da želite ukloniti sve vaše zabilježbe iz ove teme?" + save: "Spremi" + no_timezone: 'Još niste postavili vremensku zonu. Nećete moći postaviti podsjetnike. Postavi jedan u svom profilu.' + invalid_custom_datetime: "Datum i vrijeme koje ste naveli nisu valjani. Pokušajte ponovo." + list_permission_denied: "Nemate dozvolu za pregled oznaka ovog korisnika." + no_user_bookmarks: "Nemate označenih objava; oznake (bookmarks) omogućuju da se brzo referirate na specifične objave." + auto_delete_preference: + label: "Nakon što ste obaviješteni" + never: "Zadrži oznaku" + when_reminder_sent: "Izbriši oznaku" + on_owner_reply: "Obriši oznaku čim odgovorim" + clear_reminder: "Zadržite oznaku i očisti podsjetnik" + search_placeholder: "Pretraživanje oznaka po nazivu, naslovu teme ili sadržaju posta" + search: "Pretraži" + reminders: + today_with_time: "danas u %{time}" + tomorrow_with_time: "sutra u %{time}" + at_time: "na %{date_time}" + existing_reminder: "Imate podsjetnik postavljen za ovu oznaku (bookmark) koji će biti poslan %{at_date_time}" + copy_codeblock: + copied: "kopirano!" + copy: "kopirati kod u međuspremnik" + fullscreen: "prikaži kod na cijelom ekranu" + drafts: + label: "Skice" + label_with_count: "Skice (%{count})" + resume: "Nastavite" + remove: "Ukloni" + remove_confirmation: "Jeste li sigurni da želite izbrisati ovaj nacrt?" + new_topic: "Nova skica teme" + new_private_message: "Nova skica osobne poruke" + topic_reply: "Skica odgovora" + abandon: + confirm: "Imate nacrt u tijeku za ovu temu. Što biste željeli učiniti s njom?" + yes_value: "Odbaci" + no_value: "Nastavi uređivati" + topic_count_categories: + one: "Pregledajte %{count} novu ili ažuriranu temu." + few: "Pregledajte %{count} nove ili ažurirane teme." + other: "Pregledajte %{count} nove ili ažurirane teme." + topic_count_latest: + one: "Pregledajte %{count} novu ili ažuriranu temu." + few: "Pregledajte %{count} nove ili ažurirane teme." + other: "Pregledajte %{count} nove ili ažurirane teme." + topic_count_unseen: + one: "Pregledajte %{count} novu ili ažuriranu temu." + few: "Pregledajte %{count} nove ili ažurirane teme." + other: "Pregledajte %{count} nove ili ažurirane teme." + topic_count_unread: + one: "Pregledajte %{count} nepročitanu temu." + few: "Pregledajte %{count} nepročitane teme." + other: "Pregledajte %{count} nepročitane teme." + topic_count_new: + one: "Pregledajte %{count} nepročitanu temu." + few: "Pregledajte %{count} nove teme." + other: "Pregledajte %{count} nove teme." + preview: "Predprikaz" + cancel: "otkaži" + deleting: "Brisanje..." + save: "Zabilježi promjene" + saving: "Spremam..." + saved: "Spremljeno!" + upload: "Učitaj" + uploading: "Učitava se..." + processing: "Obrada..." + uploading_filename: "Učitavam: %{filename}..." + processing_filename: "Obrada: %{filename}..." + clipboard: "međuspremnik" + uploaded: "Učitano!" + pasting: "Zalijepljujem..." + enable: "Omogući" + disable: "Onemogući" + continue: "Nastavi" + undo: "Vrati na prethodno" + revert: "Vrati nazad" + failed: "Neuspješno" + switch_to_anon: "Uđi u anonimni mod" + switch_from_anon: "Napusti neimenovani način" + banner: + close: "Sakrij ovaj element" + edit: "Izmijeni" + pwa: + install_banner: "Želite li instalirati %{title} na ovaj uređaj?" + choose_topic: + none_found: "Nema pronađenih članaka." + title: + search: "Potražite temu" + placeholder: "ovdje upišite naslov teme, URL ili id" + choose_message: + none_found: "Niti jedna poruka nije pronađena." + title: + search: "Traži poruku" + placeholder: "ovdje upišite naslov poruke, URL ili id" + review: + order_by: "Poredaj prema" + date_filter: "Objavljeno između" + in_reply_to: "odgovor na" + explain: + why: "objasnite zašto je ta stavka završila u redu čekanja" + title: "Moguće ocjenjivanje" + formula: "Formula" + subtotal: "Suma stavke" + total: "Ukupno" + min_score_visibility: "Minimalna ocjena za vidljivost" + score_to_hide: "Ocjena za skrivanje objave" + take_action_bonus: + name: "poduzeo akciju" + title: "Kada član osoblja odabere nešto, zastava mu se daje kao bonus." + user_accuracy_bonus: + name: "točnost korisnika" + title: "Korisnici s čijim se zastavama povijesno dogovara dobivaju bonus." + trust_level_bonus: + name: "razina povjerenja" + title: "Stavke koje su stvorili korisnici s visokom razinom povjerenja imaju višu ocjenu." + type_bonus: + name: "upišite bonus" + title: "Određenim vrstama koje mogu pregledati osoblje može im dodijeliti bonus kako bi im postao veći prioritet." + stale_help: "Ova revizija je riješena %{username}." + claim_help: + optional: "Možete prisvojiti ovu stavku kako drugi ne bi mogli dodavati osvrte." + required: "Morate prisvojiti ovu stavku prije nego dodate osvrt na nju." + claimed_by_you: "Prisvojili ste ovu stavku i možete dodati osvrt." + claimed_by_other: "Osvrt za ovu stavku može dodati samo %{username}." + claim: + title: "prisvoji ovu temu" + unclaim: + help: "izbriši prisvajanje" + awaiting_approval: "Čeka na odobrenje" + delete: "Pobriši" + settings: + saved: "Spremljeno" + save_changes: "Zabilježi promjene" + title: "Postavke" + priorities: + title: "Prioriteti koji se mogu pregledavati" + moderation_history: "Povijest moderiranja" + view_all: "Pregledaj sve" + grouped_by_topic: "Grupirano po temi" + none: "Nema stavki za osvrt." + view_pending: "pregledaj neriješene" + topic_has_pending: + one: "Ova tema ima %{count} post koji čeka na odobrenje" + few: "Ova tema ima %{count} postova koji čekaju na odobrenje" + other: "Ova tema ima %{count} postova koji čekaju na odobrenje" + title: "Osvrt" + topic: "Tema:" + filtered_topic: "Filtrirali ste sadržaj na koji se mogu dodati osvrti u jednoj temi." + filtered_user: "Korisnik" + filtered_reviewed_by: "Pregledano" + show_all_topics: "prikaži sve teme" + deleted_post: "(post izbrisan)" + deleted_user: "(korisnik izbrisan)" + user: + bio: "Biografija" + website: "Web stranica" + username: "Korisničko ime" + email: "Email" + name: "Ime" + fields: "Polja" + reject_reason: "Razlog" + user_percentage: + summary: + one: "%{agreed}, %{disagreed}, %{ignored} (zadnjih zastava-flagova)" + few: "%{agreed}, %{disagreed}, %{ignored} (od zadnjih %{count} zastava)" + other: "%{agreed}, %{disagreed}, %{ignored} (od zadnjih %{count} zastava)" + agreed: + one: "%{count}% se slaže" + few: "%{count}% se slaže" + other: "%{count}% se slaže" + disagreed: + one: "%{count}% se ne slaže" + few: "%{count}% se ne slaže" + other: "%{count}% se ne slaže" + ignored: + one: "%{count}% ignorira" + few: "%{count}% ignorira" + other: "%{count}% ignorira" + topics: + topic: "Teme" + reviewable_count: "Količina" + reported_by: "Prijavio" + deleted: "[Tema izbrisana]" + original: "(originalna tema)" + details: "detalji" + unique_users: + one: "%{count} korisnik" + few: "%{count} korisnika" + other: "%{count} korisnika" + replies: + one: "%{count} odgovor" + few: "%{count} odgovora" + other: "%{count} odgovora" + edit: "Izmijeni" + save: "Spremi" + cancel: "Odustani" + new_topic: "Odobrenjem ove stavke kreirat će se nova tema" + filters: + all_categories: "(sve kategorije)" + type: + title: "Tip" + all: "(svi tipovi)" + minimum_score: "Minimalna ocjena" + refresh: "Osviježi." + status: "Status" + category: "Kategorija" + orders: + score: "Ocjena" + score_asc: "Rezultat (obrnuto)" + created_at: "Stvoreno" + created_at_asc: "Stvoreno (obrnutim slijedom)" + priority: + title: "Minimalni prioritet" + any: "(bilo koji)" + low: "Nizak" + medium: "Srednji" + high: "Visok" + conversation: + view_full: "pregledaj čitavu raspravu" + scores: + about: "Ocjena se računa na bazi razine povjerenja izvjestitelja, točnosti njihovih prethodnih zastavica i prioriteta stavke o kojoj se izvješćuje." + score: "Ocjena" + date: "Datum" + type: "Tip" + status: "Statust" + submitted_by: "Podnio zahtjev" + reviewed_by: "Pregledao" + statuses: + pending: + title: "Na čekanju" + approved: + title: "Odobrio" + rejected: + title: "Odbio" + ignored: + title: "Ignorirao" + deleted: + title: "Izbrisao" + reviewed: + title: "(svi pregledani)" + all: + title: "(sve)" + types: + reviewable_flagged_post: + title: "Post označen zastavicom" + flagged_by: "Označio" + reviewable_queued_topic: + title: "Tema u redu čekanja" + reviewable_queued_post: + title: "Objava u redu čekanja" + reviewable_user: + title: "Korisnik" + reviewable_post: + title: "Objava" + approval: + title: "natpis čeka potvrdu" + description: "Zaprimili smo vašu objavu, ali potrebno je odobrenje moderatora prije nego se pojavi. Molim vas budite strpljivi." + pending_posts: + one: "Imate %{count} objavu na čekanju." + few: "Imate %{count} objava na čekanju." + other: "Imate %{count} objava na čekanju." + ok: "U redu" + example_username: "Korisničko ime" + reject_reason: + title: "Zašto odbijate ovog korisnika?" + send_email: "Pošalji poruku o odbijanju" + relative_time_picker: + minutes: + one: "minuta" + few: "minute" + other: "minute" + hours: + one: "sat" + few: "sata" + other: "sati" + days: + one: "dan" + few: "dana" + other: "dana" + months: + one: "mjesec" + few: "mjeseca" + other: "mjeseci" + years: + one: "godina" + few: "godine" + other: "godina" + relative: "Relativno" + time_shortcut: + now: "Sada" + later_today: "Kasnije danas" + two_days: "Dva dana" + next_business_day: "Sljedeći radni dan" + tomorrow: "Sutra" + post_local_date: "Datum u objavi" + later_this_week: "Kasnije ovoga tjedna" + this_weekend: "Ovaj vikend" + start_of_next_business_week: "Ponedjeljak" + start_of_next_business_week_alt: "Sljedećeg ponedjeljka" + next_week: "Idući tjedan" + two_weeks: "Dva tjedna" + next_month: "Sljedeći mjesec" + two_months: "Dva mjeseca" + three_months: "Tri mjeseca" + four_months: "Četiri mjeseca" + six_months: "Šest mjeseci" + one_year: "Godinu dana" + forever: "Zauvijek" + relative: "Relativno vrijeme" + none: "Nijedna nije potrebna" + last_custom: "Posljednji prilagođeni datum" + custom: "Prilagođeni datum i vrijeme" + select_timeframe: "Odabir vremenskog okvira" + user_action: + user_posted_topic: "%{user} je objavio temu" + you_posted_topic: "Objavio si temu" + user_replied_to_post: "%{user} je odgovorio na %{post_number}" + you_replied_to_post: "Ti si odgovorio na %{post_number}" + user_replied_to_topic: "%{user} je odgovorio na temu" + you_replied_to_topic: "Tisi odgovorio na temu" + user_mentioned_user: "%{user} je spomenuo %{another_user}" + user_mentioned_you: "%{user} je spomenuo tebe" + you_mentioned_user: "Ti si spomenuo %{another_user}" + posted_by_user: "Objavio %{user}" + posted_by_you: "Objavio ti" + sent_by_user: "Poslao %{user}" + sent_by_you: "Poslao ti" + directory: + username: "Korisničko ime" + filter_name: "filtar po imenu" + title: "Korisnici" + likes_given: "Dano" + likes_received: "Primljeno" + topics_entered: "Pogledano" + topics_entered_long: "Tema pogledano" + time_read: "Vrijeme čitanja" + topic_count: "Teme" + topic_count_long: "Napravljene teme" + post_count: "Odgovori" + post_count_long: "Poslan odgovor" + no_results: "Nema pronađenih rezultata" + days_visited: "Posjete" + days_visited_long: "Dana posjećeno" + posts_read: "Pročitano" + posts_read_long: "Pročitani natpisi" + last_updated: "Zadnji put ažurirano:" + total_rows: + one: "%{count} korisnik" + few: "%{count} korisnika" + other: "%{count} korisnika" + edit_columns: + title: "Uredi stupce direktorija" + save: "Spremi" + reset_to_default: "Vrati na zadano" + group: + all: "sve grupe" + group_histories: + actions: + change_group_setting: "Promijenite postavke grupe" + add_user_to_group: "Dodaj korisnika" + remove_user_from_group: "Ukloni korisnika" + make_user_group_owner: "Postavi vlasnika" + remove_user_as_group_owner: "Poništi vlasnika" + groups: + member_added: "Dodano" + member_requested: "Zatraženo na" + add_members: + title: "Dodaj članove u %{group_name}" + description: "Unesite popis korisnika koje želite pozvati u grupu ili zalijepite u popis odvojen zarezima:" + usernames_placeholder: "korisnička imena" + usernames_or_emails_placeholder: "korisnička imena ili e-adrese" + notify_users: "Obavijesti korisnike" + set_owner: "Postavite korisnike kao vlasnike ove grupe" + requests: + title: "Zahtjevi" + reason: "Razlog" + accept: "Prihvati" + accepted: "prihvaćen" + deny: "Odbiti" + denied: "odbijen" + undone: "zahtjev poništen" + handle: "obradi zahtjev za članstvo" + manage: + title: "Upravljanje" + name: "Ime" + full_name: "Ime i prezime" + add_members: "Dodaj korisnike" + invite_members: "Pozovite" + delete_member_confirm: "Uklonite '%{username}' iz '%{group}' grupe?" + profile: + title: Profil + interaction: + title: Interakcija + posting: Objavljivanje + notification: Obavijest + email: + title: "Email" + status: "Sinkronizirano %{old_emails} / %{total_emails} e-poštu putem IMAP-a." + enable_smtp: "Omogući SMTP" + enable_imap: "Omogući IMAP" + test_settings: "Postavke testiranja" + save_settings: "Spremi postavke" + last_updated: "Posljednje ažuriranje:" + last_updated_by: "od" + settings_required: "Sve su postavke potrebne, ispunite sva polja prije provjere valjanosti." + smtp_settings_valid: "SMTP postavke važeće." + smtp_title: "SMTP" + smtp_instructions: "Kada omogućite SMTP za grupu, sve odlazne e-pošte poslane iz ulazne pošte grupe poslat će se putem SMTP postavki navedenih ovdje umjesto poslužitelja e-pošte konfiguriranog za druge poruke e-pošte koje šalje vaš forum." + imap_title: "IMAP" + imap_additional_settings: "Dodatne postavke" + imap_instructions: 'Kada omogućite IMAP za grupu, e-adrese se sinkroniziraju između pristigle pošte grupe i isporučenog IMAP poslužitelja i poštanskog sandučića. SMTP mora biti omogućen s važećim i provjerenim vjerodajnicama da bi se IMAP mogao omogućiti. Korisničko ime i lozinka e-pošte korišteni za SMTP koristit će se za IMAP. Za više informacija pogledajte najava značajke na Discourse Meta.' + imap_alpha_warning: "Upozorenje: Ovo značajka je u alfa fazi. Službeno je podržan samo Gmail. Koristite na vlastiti rizik!" + imap_settings_valid: "IMAP postavke važeće." + smtp_disable_confirm: "Ako onemogućite SMTP, sve SMTP i IMAP postavke bit će resetirane i povezana funkcionalnost će biti onemogućena. Jeste li sigurni da želite nastaviti?" + imap_disable_confirm: "Ako onemogućite IMAP sve će IMAP postavke biti resetirane i povezane funkcije će biti onemogućene. Jeste li sigurni da želite nastaviti?" + imap_mailbox_not_selected: "Morate odabrati poštanski sandučić za ovu IMAP konfiguraciju jer se neće sinkronizirati nijedan poštanski sandučić!" + prefill: + title: "Unaprijed ispunite postavke za:" + gmail: "GMail" + credentials: + title: "Vjerodajnice" + smtp_server: "SMTP poslužitelj" + smtp_port: "SMTP priključak" + smtp_ssl: "Koristi SSL za SMTP" + imap_server: "IMAP poslužitelj" + imap_port: "IMAP priključak" + imap_ssl: "Koristi SSL za IMAP" + username: "Korisničko ime" + password: "Zaporka" + settings: + title: "Postavke" + allow_unknown_sender_topic_replies: "Dopusti odgovore nepoznatih pošiljatelja na teme." + allow_unknown_sender_topic_replies_hint: "Omogućuje nepoznatim pošiljateljima da odgovore na teme grupe. Ako to nije omogućeno, odgovori s e-adresa koje još nisu pozvane u temu stvorit će novu temu." + from_alias: "Od Aliasa" + from_alias_hint: "Alias koji se koristi kao adresa pošiljatelja pri slanju grupnih SMTP e-poruka. Imajte na umu da to možda ne podržavaju svi davatelji e-pošte, molimo pogledajte dokumentaciju svog davatelja e-pošte." + mailboxes: + synchronized: "Sinkronizirani poštanski sandučić" + none_found: "Na ovom računu e-pošte nisu pronađeni poštanski sandučići." + disabled: "Onemogućeno" + membership: + title: Članstvo + access: Pristup + categories: + title: Kategorije + long_title: "Zadane obavijesti kategorije" + description: "Kada se korisnici dodaju u ovu grupu, njihove postavke notifikacija za kategorije bit će postavljene na zadane postavke. Nakon toga, mogu ih promijeniti." + watched_categories_instructions: "Automatski prati sve teme u ovim kategorijama. Članovi grupe bit će obaviješteni o svim novim postovima i temama, a pored teme pojavit će se i broj novih postova." + tracked_categories_instructions: "Automatski pratite sve teme u ovim kategorijama. Broj novih postova pojavit će se pored teme." + watching_first_post_categories_instructions: "Korisnici će biti obaviješteni o prvoj objavi u ovim kategorijama o svakoj novoj temi." + regular_categories_instructions: "Ako su te kategorije isključene, one će biti neprigušene (unmuted) za članove grupe. Korisnici će biti obaviješteni ako ih se spomene ili netko odgovori na njihovu objavu." + muted_categories_instructions: "Korisnici neće biti obaviješteni o ničemu o novim temama u ovim kategorijama, i neće se pojaviti na kategorijama ili najnovijim temama stranicama." + tags: + title: Oznake + long_title: "Zadane obavijesti oznaka (notifikacija)" + description: "Kada se korisnici dodaju u ovu grupu, njihove postavke obavijesti oznaka bit će postavljene na zadane postavke. Nakon toga, mogu ih promijeniti." + watched_tags_instructions: "Automatski prati sve teme s ovim oznakama. Članovi grupe bit će obaviješteni o svim novim postovima i temama, a pored teme pojavit će se i broj novih postova." + tracked_tags_instructions: "Automatski prati sve teme s ovim oznakama. Broj novih postova pojavit će se pored teme." + watching_first_post_tags_instructions: "Korisnici će biti obaviješteni o prvoj objavi u svakoj novoj temi s ovim oznakama." + regular_tags_instructions: "Ako su ove oznake isključene, neće biti isključene za članove grupe. Korisnici će biti obaviješteni ako ih se spomene ili netko odgovori na njihove objave." + muted_tags_instructions: "Nećete biti obaviješteni o ničemu s ovim oznakama i neće se pojavljivati u najnovijim objavama." + logs: + title: "Zapisi" + when: "Kada" + action: "Akcija" + acting_user: "Djelujući korisnik" + target_user: "Ciljani korisnik" + subject: "predmet" + details: "Detalji" + from: "Od" + to: "Za" + permissions: + title: "Dopuštenja" + none: "Nema kategorija povezanih s ovom grupom." + description: "Članovi ove grupe mogu pristupiti ovim kategorijama" + public_admission: "Dozvoli korisnicima da se slobodno pridruže grupi (Zahtijeva javno vidljivu grupu)" + public_exit: "Dozvoli korisnicima da slobodno napuste grupu" + empty: + posts: "Nema objava članova ove grupe." + members: "Grupa bez članova." + requests: "Nema zahtjeva za članstvo za ovu grupu." + mentions: "Nema spominjanja ove grupe." + messages: "Nema poruka u ovoj grupi" + topics: "Članovi ove grupe nemaju teme." + logs: "Nema dnevnika za ovu grupu." + add: "Dodaj" + join: "Pridružite se" + leave: "Napustiti" + request: "Zahtjev" + message: "Poruka" + confirm_leave: "Jeste li sigurni da želite napustiti ovu grupu?" + allow_membership_requests: "Dopusti korisnicima slanje zahtjeva za članstvo vlasnicima grupa (zahtijeva javno vidljivu grupu)" + membership_request_template: "Prilagođeni predložak za prikaz korisnicima prilikom slanja zahtjeva za članstvo" + membership_request: + submit: "Pošalji zahtjev" + title: "Podnesi zahtjev za pridruživanje @%{group_name}" + reason: "Neka vlasnici grupe znaju zašto pripadaš ovoj grupi" + membership: "Članstvo" + name: "Ime" + group_name: "Ime grupe" + user_count: "Korisnici" + bio: "O grupi" + selector_placeholder: "unesite korisničko ime" + owner: "vlasnik" + index: + title: "Grupe" + all: "Sve grupe" + empty: "Nema vidljivih grupa." + filter: "Filtriranje prema vrsti grupe" + owner_groups: "Grupe koje posjedujem" + close_groups: "Zatvorene grupe" + automatic_groups: "Automatske grupe" + automatic: "Automatska" + closed: "Zatvoreno" + public: "Javno" + private: "Privatno" + public_groups: "Javne grupe" + my_groups: "Moje grupe" + group_type: "Vrsta grupe" + is_group_user: "Član" + is_group_owner: "Vlasnik" + title: + one: "Grupa" + few: "Grupe" + other: "Grupe" + activity: "Aktivnost" + members: + title: "Članovi" + filter_placeholder_admin: "korisničko ime ili e-mail" + filter_placeholder: "Korisničko ime" + remove_member: "Ukloni člana" + remove_member_description: "Ukloni %{username} iz ove grupe" + make_owner: "Postavi kao vlasnika" + make_owner_description: "Postavi %{username} kao vlasnika ove grupe" + remove_owner: "Ukloni kao vlasnika" + remove_owner_description: "Ukloni %{username} kao vlasnika ove grupe" + make_primary: "Napravi primarnu" + make_primary_description: "Učini primarnom grupom za %{username}" + remove_primary: "Ukloni kao primarnu" + remove_primary_description: "Ukloni kao primarnu grupu za %{username}" + remove_members: "Ukloni članove" + remove_members_description: "Ukloni odabrane korisnike iz ove grupe" + make_owners: "Postavi kao vlasnike" + make_owners_description: "Odabrane korisnike postavite za vlasnike ove grupe" + remove_owners: "Ukloni vlasnike" + remove_owners_description: "Ukloni odabrane korisnike kao vlasnike ove grupe" + make_all_primary: "Napravi sve primarnim" + make_all_primary_description: "Učinite primarnom grupom za sve odabrane korisnike" + remove_all_primary: "Ukloni kao primarnu" + remove_all_primary_description: "Ukloni ovu grupu kao primarnu" + owner: "Vlasnik" + primary: "Primarna" + forbidden: "Nemate dozvolu vidjeti članove." + no_filter_matches: "Nijedan član ne odgovara toj potrazi." + topics: "Teme" + posts: "Objave" + mentions: "Spomeni" + messages: "Poruke" + notification_level: "Zadani nivo obavijesti za grupne poruke" + alias_levels: + mentionable: "Tko može @spomenuti ovu grupu?" + messageable: "Tko može poslati poruku ovoj grupi?" + nobody: "Nitko" + only_admins: "Samo administratori" + mods_and_admins: "Samo moderatori i Administratori" + members_mods_and_admins: "Samo članovi grupe, moderatori i administratori" + owners_mods_and_admins: "Samo vlasnici grupa, moderatori i administratori" + everyone: "Svi" + notifications: + watching: + title: "Promatrano" + description: "Biti ćete obaviješteni o svakoj novoj objavi u svakoj poruci, te će biti prikazan broj novih odgovora." + watching_first_post: + title: "Pratim prvi natpis" + description: "Biti ćete obaviješteni o novim porukama u ovoj grupi, ali ne i odgovorima na poruke." + tracking: + title: "Praćenje" + description: "Bit ćete obaviješteni ako netko spomene vaše @ime ili vam pošalje odgovor, a prikazat će se i broj novih odgovora." + regular: + title: "Normalno" + description: "Bit ćete obaviješteni ako netko spomene vaše @ime ili vam pošalje odgovor." + muted: + title: "Utišano" + description: "Nećete biti obaviješteni o promjenama u porukama u ovoj grupi." + flair_url: "Avatar Flair slika" + flair_upload_description: "Koristite kvadratne slike ne manje od 20px sa 20px." + flair_bg_color: "Avatar Flair pozadinska boja" + flair_bg_color_placeholder: "(Neobavezno) Hex vrijednost boje" + flair_color: "Avatar Flair boja" + flair_color_placeholder: "(Neobavezno) Hex vrijednost boje" + flair_preview_icon: "Ikona pretpregleda" + flair_preview_image: "Slika pretpregleda" + flair_type: + icon: "Odaberi ikonu" + image: "Učitaj sliku" + default_notifications: + modal_title: "Korisničke zadane obavijesti" + modal_description: "Želite li povijesno primijeniti ovu promjenu? Time će se promijeniti postavke za %{count} postojećih korisnika." + modal_yes: "Da" + modal_no: "Ne, primijeni promjenu samo ubuduće" + user_action_groups: + "1": "Dodani likeovi" + "2": "Primljeni likeovi" + "3": "Zabilješke" + "4": "Teme" + "5": "Odgovori" + "6": "Odgovori" + "7": "Spomeni" + "9": "Citati" + "11": "Izmjene" + "12": "Poslano" + "13": "Primljeno" + "14": "Na čekanju" + "15": "Skice" + categories: + all: "sve kategorije" + all_subcategories: "sve" + no_subcategory: "ništa" + category: "Kategorija" + category_list: "Prikaži popis kategorija" + reorder: + title: "Promijeni redoslijed kategorija" + title_long: "Reorganiziranje popisa kategorija" + save: "Spremi redoslijed" + apply_all: "Primijeni" + position: "Položaj" + posts: "Objave" + topics: "Teme" + latest: "Zadnje" + subcategories: "Podkategorije" + muted: "Prigušene (muted) kategorije" + topic_sentence: + one: "%{count} tema" + few: "%{count} teme" + other: "%{count} teme" + topic_stat: + one: "%{number} / %{unit}" + few: "%{number} / %{unit}" + other: "%{number} / %{unit}" + topic_stat_unit: + week: "tjedan" + month: "mjesec" + topic_stat_all_time: + one: "%{number} ukupno" + few: "%{number} ukupno" + other: "%{number} ukupno" + topic_stat_sentence_week: + one: "%{count} nova tema u proteklom tjednu." + few: "%{count} novih tema u proteklom tjednu." + other: "%{count} novih tema u proteklom tjednu." + topic_stat_sentence_month: + one: "%{count} nova tema u proteklom mjesecu." + few: "%{count} nove teme u proteklom mjesecu." + other: "%{count} novih tema u proteklom mjesecu." + n_more: "Kategorije (%{count} više)..." + ip_lookup: + title: Pretraga po IP adresi + hostname: Ime računala + location: Lokacija + location_not_found: (nepoznato) + organisation: Organizacija / Poduzeće + phone: Telefon + other_accounts: "Ostali računi s istom IP adresom" + delete_other_accounts: "Obriši %{count}" + username: "Korisničko ime" + trust_level: "Razina povjerenja" + read_time: "Vrijeme čitanja" + topics_entered: "tema posjećeno" + post_count: "# odgovora" + confirm_delete_other_accounts: "Jeste li sigurni da želite obrisati ove račune?" + powered_by: "koristeći MaxminddB" + copied: "kopirano" + user_fields: + none: "(odaberi opciju)" + required: 'Unesi vrijednost za "%{name}"' + user: + said: "%{username}:" + profile: "Profil" + mute: "Utišaj" + edit: "Uredi postavke" + download_archive: + button_text: "Preuzmi sve" + confirm: "Želite li preuzeti objave?" + success: "Preuzimanje započeto, biti ćete obaviješteni porukom kada proces bude završen." + rate_limit_error: "Objave mogu biti preuzimane jednom na dan, molimo pokušajte sutra." + new_private_message: "Nova poruka" + private_message: "Poruka" + private_messages: "Poruke" + user_notifications: + filters: + filter_by: "Filtriraj po" + all: "Sve" + read: "Pročitano" + unread: "Nepročitano" + unseen: "Neviđeno" + ignore_duration_title: "Ignoriraj korisnika" + ignore_duration_username: "Korisničko ime" + ignore_duration_when: "Trajanje:" + ignore_duration_save: "Ignoriraj" + ignore_duration_note: "Imajte na umu da se sva ignoriranja automatski uklanjaju nakon isteka trajanja ignoriranja." + ignore_duration_time_frame_required: "Odaberi vremenski okvir" + ignore_no_users: "Nemate ignoriranih korisnika." + ignore_option: "Ignorirao" + ignore_option_title: "Nećete primati obavijesti vezane uz ovog korisnika i sve njihove teme i odgovori bit će skriveni." + add_ignored_user: "Dodaj..." + mute_option: "Utišano" + mute_option_title: "Nećete primati obavijesti vezane uz ovog korisnika." + normal_option: "Normalno" + normal_option_title: "Bit ćete obaviješteni ako vam ovaj korisnik odgovara, citira vas ili spominje." + notification_schedule: + title: "Raspored obavijesti (notifikacije)" + label: "Omogući prilagođeni raspored obavijesti" + tip: "Van ovih sati bit ćete automatski postavljeni na 'ne smetaj'." + midnight: "Ponoć" + none: "Ništa" + monday: "ponedjeljak" + tuesday: "utorak" + wednesday: "srijeda" + thursday: "četvrtak" + friday: "petak" + saturday: "subota" + sunday: "nedjelja" + to: "za" + activity_stream: "Aktivnosti" + read: "Pročitajte" + read_help: "Nedavno pročitane teme" + preferences: "Postavke" + feature_topic_on_profile: + open_search: "Odaberite novu temu" + title: "Odaberite temu" + search_label: "Traži temu po naslovu" + save: "Spremi" + clear: + title: "Izbriši" + warning: "Jeste li sigurni da želite očistiti svoju istaknutu temu?" + use_current_timezone: "Koristi trenutnu vremensku zonu" + profile_hidden: "Javni profil ovog korisnika je skriven." + expand_profile: "Proširi" + sr_expand_profile: "Proširite pojedinosti profila" + collapse_profile: "Sakrij" + sr_collapse_profile: "Sažmi pojedinosti profila" + bookmarks: "Zabilješke" + bio: "O meni" + timezone: "Vremenska zona" + invited_by: "Pozvao" + trust_level: "Razina Povjerenja" + notifications: "Obavijest" + statistics: "Statistika" + desktop_notifications: + label: "Obavijesti (uživo)" + not_supported: "Nažalost, obavijesti nisu podržane u ovom pregledniku." + perm_default: "Uključi obavijesti" + perm_denied_btn: "Dozvola odbijena" + perm_denied_expl: "Odbili ste dopuštenje za obavijesti. Dopusti obavijesti putem postavki preglednika." + disable: "Isključi obavijesti" + enable: "Uključi obavijesti" + each_browser_note: 'Napomena: Ovu postavku morate promijeniti na svakom pregledniku kojeg koristite. Sve obavijesti bit će onemogućene kada su u "ne ometaj", bez obzira na ovu postavku.' + consent_prompt: "Želite li obavijesti (uživo) kada ljudi odgovaraju na vaše postove?" + dismiss: "Skloni" + dismiss_notifications: "Skloni sve" + dismiss_notifications_tooltip: "Označi sve nepročitane obavijesti kao pročitane" + no_messages_title: "Nemate nijednu poruku" + no_messages_body: > + Trebate voditi izravan osobni razgovor s nekim, izvan uobičajenog toka razgovora? Pošaljite im poruku odabirom avatara i %{icon} poruke.

Ako trebate pomoć, možete poslati poruku osoblju. + no_bookmarks_title: "Još ništa niste označili" + no_bookmarks_body: > + Započnite s oznakama postova s oznakom %{icon} i oni će ovdje biti navedeni radi lakšeg snalaženja. Možete zakazati i podsjetnik! + no_bookmarks_search: "Nisu pronađene oznake s ponuđenim upitom za pretraživanje." + no_notifications_title: "Još nemate nijednu obavijest" + no_notifications_body: > + Bit ćete obaviješteni u ovom panelu o aktivnosti izravno relevantne za vas, uključujući i odgovorima na vaše teme i postove, kada netko @označi Vas ili citira vas, i odgovori na temu koju gledate. Obavijesti će se slati i na vašu e-poštu kada se neko vrijeme niste prijavili.

Potražite %{icon} da biste odlučili o kojim određenim temama, kategorijama i oznakama želite biti obaviješteni. Više informacija potražite u postavki obavijesti. + no_notifications_page_title: "Još nemate nijednu obavijest" + no_notifications_page_body: > + Bit ćete obaviješteni o aktivnostima izravno relevantne za vas, uključujući i odgovorima na vaše teme i postove, kada netko @mentions Vi ili citati vas, i odgovori na temu koju gledate. Obavijesti će se također slati na vašu e -poštu ako se neko vrijeme niste prijavili.

Potražite %{icon} da biste odlučili o kojim temama, kategorijama i oznakama želite biti obaviješteni. Za više informacija pogledajte postavki obavijesti. + first_notification: "Vaša prva obavijest! Odaberite ju za početak." + dynamic_favicon: "Prikaži brojeve na ikoni preglednika" + skip_new_user_tips: + description: "Preskoči savjete i značke novog korisnika" + not_first_time: "Nije ti prvi put?" + skip_link: "Preskoči ove savjete" + read_later: "Pročitat ću ga kasnije." + theme_default_on_all_devices: "Postavi ovu temu kao zadanu na svim mojim uređajima" + color_scheme_default_on_all_devices: "Postavljanje zadane sheme boja na svim mojim uređajima" + color_scheme: "Shema boja" + color_schemes: + default_description: "Zadana tema" + disable_dark_scheme: "Isto kao obična tema" + dark_instructions: "Shemu boja tamnog načina možete pregledati prebacivanjem tamnog načina rada uređaja." + undo: "Resetirati" + regular: "Stalni član" + dark: "Tamni način rada" + default_dark_scheme: "(zadana web lokacija)" + dark_mode: "Tamni način rada" + dark_mode_enable: "Omogućite automatsku shemu boja u tamnom načinu" + text_size_default_on_all_devices: "Postavi ovu veličinu teksta kao zadanu na svim mojim uređajima" + allow_private_messages: "Dopustite drugim korisnicima da mi šalju osobne poruke" + external_links_in_new_tab: "Otvori sve vanjske poveznice u novoj kartici" + enable_quoting: "Omogući citirani odgovor označenoj teksta" + enable_defer: "Uključi odgodu označavanja nepročitanih tema" + enable_experimental_sidebar: "Omogući eksperimentalnu bočnu traku" + change: "promijeni" + featured_topic: "Istaknuta tema" + moderator: "%{user} je moderator" + admin: "%{user} je administrator" + moderator_tooltip: "Ovaj korisnik je moderator." + admin_tooltip: "Ovaj korisnik je administrator" + silenced_tooltip: "Ovaj korisnik je ušutkan" + suspended_notice: "Ovaj korisnik je suspendiran do %{date}." + suspended_permanently: "Ovaj korisnik je suspendiran." + suspended_reason: "Razlog:" + github_profile: "GitHub" + email_activity_summary: "Sažetak aktivnosti" + mailing_list_mode: + label: "Način mailing liste" + enabled: "Omogući način mailing liste" + instructions: | + Ova postavka nadjačava sažetak aktivnosti.
+ Zanemarene teme i kategorije nisu uključene u ove poruke e-pošte. + individual: "Pošalji e-mail za svaki natpis" + individual_no_echo: "Pošalji e-mail za sve natpise osim moga" + many_per_day: "Pošalji e-mail za svaki novi natpis (oko %{dailyEmailEstimate} dnevno)" + few_per_day: "Pošalji e-mail za svaki novi natpis (oko 2 dnevno)" + warning: "Način mailing liste omogućen. Postavke obavijesti e-pošte nadjačane su." + tag_settings: "Oznake" + watched_tags: "Promatrano" + watched_tags_instructions: "Automatski ćete gledati sve teme s tim oznakama. Bit ćete obaviješteni o svim novim postovima i temama, a pored teme će se pojaviti i broj novih postova." + tracked_tags: "Praćeno" + tracked_tags_instructions: "Automatski ćete pratiti sve teme s tim oznakama. Uz temu će se pojaviti broj novih postova." + muted_tags: "Utišano" + muted_tags_instructions: "Nećete biti obaviješteni o ničemu o novim temama s tim oznakama i neće se pojaviti u zadnjim postovima." + watched_categories: "Promatrano" + watched_categories_instructions: "Automatski ćete pratiti sve teme u tim kategorijama. Bit ćete obaviješteni o svim novim postovima i temama, a pored teme će se pojaviti i broj novih postova." + tracked_categories: "Praćeno" + tracked_categories_instructions: "Automatski ćete pratiti sve teme u tim kategorijama. Uz temu će se pojaviti broj novih postova." + watched_first_post_categories: "Pratim prvi natpis" + watched_first_post_categories_instructions: "Bit ćete obaviješteni o prvom postu u svakoj novoj temi u ovim kategorijama." + watched_first_post_tags: "Pratim prvi natpis" + watched_first_post_tags_instructions: "Bit ćete obaviješteni o prvom postu u svakoj novoj temi s tim oznakama." + muted_categories: "Utišano" + muted_categories_instructions: "Nećete biti obaviješteni o bilo čemu o novim temama u tim kategorijama, a oni se neće pojaviti na kategorijama ili najnovijim postovima." + muted_categories_instructions_dont_hide: "Nećete biti obaviješteni o ničemu o novim temama u tim kategorijama." + regular_categories: "Stalni član" + regular_categories_instructions: "Te kategorije vidjet ćete u popisima tema \"Najnovije\" i \"Top\"." + no_category_access: "Kao moderator imate ograničen pristup kategoriji, spremanje je onemogućeno." + delete_account: "Obriši moj račun" + delete_account_confirm: "Jeste li sigurni da želite trajno obrisati svoj račun? Ova se akcija ne može poništiti!" + deleted_yourself: "Vaš račun je uspješno obrisan." + delete_yourself_not_allowed: "Molimo kontaktirajte člana osoblja ako želite da vaš račun bude izbrisan." + unread_message_count: "Poruke" + admin_delete: "Pobriši" + users: "Korisnici" + muted_users: "Utišano" + muted_users_instructions: "Suzbijte sve obavijesti i PM-ove od tih korisnika." + allowed_pm_users: "Dopušteno" + allowed_pm_users_instructions: "Dozvolite samo PM-ove od ovih korisnika." + allow_private_messages_from_specific_users: "Dozvolite samo određenim korisnicima da mi šalju osobne poruke" + ignored_users: "Ignorirao" + ignored_users_instructions: "Suzbijte sve objave, obavijesti i PM-ove od tih korisnika." + tracked_topics_link: "Pokaži" + automatically_unpin_topics: "Automatski otkvačite teme kada dođem do dna." + apps: "Aplikacije" + revoke_access: "Opoziv pristupa" + undo_revoke_access: "Poništi opozivanje pristupa" + api_approved: "Odobrio:" + api_last_used_at: "Zadnji put uporabljeno u:" + theme: "Tema" + save_to_change_theme: 'Tema će biti ažurirana nakon što kliknete "%{save_text}"' + home: "Zadana početna stranica" + staged: "Priređen" + staff_counters: + flags_given: "pomočne oznake" + flagged_posts: "označene objave" + deleted_posts: "obrisane objave" + suspensions: "suspenzije" + warnings_received: "upozorenja" + rejected_posts: "odbijenih postova" + messages: + all: "sve pristigle pošte" + inbox: "Primljeno" + personal: "Osobni" + latest: "Zadnje" + sent: "Poslano" + unread: "Nepročitano" + unread_with_count: + one: "Nepročitano (%{count})" + few: "Nepročitano (%{count})" + other: "Nepročitano (%{count})" + new: "Novo" + new_with_count: + one: "Novi (%{count})" + few: "Novi (%{count})" + other: "Novi (%{count})" + archive: "Arhiva" + groups: "Moje grupe" + move_to_inbox: "Premjesti u ulaznu poštu" + move_to_archive: "Arhiva" + failed_to_move: "Neuspješno premještanje odabranih poruka (možda vam je mreža nedostupna)" + tags: "Oznake" + warnings: "Službena upozorenja" + read_more_in_group: "Želite li pročitati više? Pregledajte ostale poruke u %{groupLink}." + read_more: "Želite li pročitati više? Pregledajte ostale poruke u osobnim porukama." + read_more_group_pm_MF: "tamo { UNREAD, plural, =0 {} one { je # nepročitano } few { su # nepročitano } other { su # nepročitano } } { NEW, plural, =0 {} one { {BOTH, select, true{i } false {je } other{}} # nova poruka} few {} other { {BOTH, select, true{i } false {su } other{}} # novih poruka} } preostale ili pregledajte druge poruke u {groupLink}" + read_more_personal_pm_MF: "tamo { UNREAD, plural, =0 {} one { je # nepročitano } few { su # nepročitano } other { su # nepročitano } } { NEW, plural, =0 {} one { {BOTH, select, true{i } false {je } other{}} # nova poruka} few {} other { {BOTH, select, true{i } false {su } other{}} # nova poruka} } preostale, ili pregledajte druge osobne poruke" + preferences_nav: + account: "Račun" + security: "Sigurnost" + profile: "Profil" + emails: "E-adrese" + notifications: "Obavijest" + categories: "Kategorije" + users: "Korisnici" + tags: "Oznake" + interface: "Sučelje" + apps: "Aplikacije" + change_password: + success: "(email je poslan)" + in_progress: "(email se šalje)" + error: "(greška)" + emoji: "zaključavanje emotikona" + action: "Pošalj e-mail za povratak zaporke." + set_password: "Postavi zaporku" + choose_new: "Izaberite novu lozinku" + choose: "Izaberite lozinku" + second_factor_backup: + title: "Dva faktorski rezervni kodovi" + regenerate: "Regeneriraj" + disable: "Onemogući" + enable: "Omogući" + enable_long: "Omogući rezervu sigurnosni kodova" + manage: + one: "Upravljanje rezervnim kopijama kodova. Preostale su vam %{count} sigurnosne kopija." + few: "Upravljanje rezervnim kopijama kodova. Preostale su vam %{count} sigurnosne kopija." + other: "Upravljanje rezervnim kopijama kodova. Preostale su vam %{count} sigurnosne kopija." + copy_to_clipboard: "Kopirati u međuspremnik" + copy_to_clipboard_error: "Greška pri kopiranju podataka u međuspremnik" + copied_to_clipboard: "Kopirano u međuspremnik" + download_backup_codes: "Preuzmite kodove baze podataka" + remaining_codes: + one: "Preostalo vam je %{count} rezervni kodova." + few: "Preostalo vam je %{count} rezervni kodova." + other: "Preostalo vam je %{count} rezervni kodova." + use: "Upotrijebite kod baze podataka" + enable_prerequisites: "Morate omogućiti primarnu metodu dva faktora prije generiranja sigurnosnih kodova." + codes: + title: "Kodovi baze podataka generirani" + description: "Svaki od ovih rezervnih kodova može se koristiti samo jednom. Držite ih negdje na sigurnom, ali pristupačnom." + second_factor: + title: "Dvofaktorska autentifikacija" + enable: "Upravljanje dvofaktorskom autentifikacijom" + disable_all: "Onemogući sve" + forgot_password: "Zaboravili ste lozinku?" + confirm_password_description: "Molimo potvrdite svoju lozinku za nastavak" + name: "Ime i prezime" + label: "Šifra" + rate_limit: "Pričekajte prije pokušaja drugog koda za provjeru autentičnosti." + enable_description: | + Skenirajte ovaj QR kod u podržanoj aplikaciji (AndroidiOS) i unesite svoj kod za provjeru autentičnosti. + disable_description: "Unesite kod za provjeru autentičnosti iz svoje aplikacije" + show_key_description: "Unesite ručno" + short_description: | + Zaštitite svoj račun pomoću jednokratnih sigurnosnih kodova. + extended_description: | + Dvofaktorna provjera autentičnosti dodaje dodatnu sigurnost vašem računu tako da uz lozinku zahtijeva jednokratni token. Tokeni se mogu generirati na Android i iOS uređajima. + oauth_enabled_warning: "Imajte na umu da će prijave na društvene mreže biti onemogućene nakon što je omogućena dva faktora autentifikacije na vašem računu." + use: "Korištenje aplikacije Authenticator" + enforced_notice: "Prije pristupanja ovoj stranici morate omogućiti autentifikaciju dva faktora." + disable: "Onemogući" + disable_confirm: "Jeste li sigurni da želite onemogućiti sve metode s dva faktora?" + save: "Spremi" + edit: "Izmijeni" + edit_title: "Uredi autentifikator" + edit_description: "Ime autentifikatora" + enable_security_key_description: | + Kada pripremite hardverski sigurnosni ključ , pritisnite tipku Registracija u nastavku. + totp: + title: "Autentifikatori temeljeni na Tokenu" + add: "Dodaj provjeru autentičnosti" + default_name: "Moj autentifikator" + name_and_code_required_error: "Morate navesti ime i kôd iz aplikacije za provjeru autentičnosti." + security_key: + register: "Registracija" + title: "Sigurnosni ključevi" + add: "Dodaj sigurnosni ključ" + default_name: "Glavni sigurnosni ključ" + not_allowed_error: "Postupak registracije sigurnosnog ključa ili je vremenski ograničen ili je otkazan." + already_added_error: "Već ste registrirali ovaj sigurnosni ključ. Ne morate ga ponovno registrirati." + edit: "Uredi sigurnosni ključ" + save: "Spremi" + edit_description: "Naziv sigurnosnog ključa" + name_required_error: "Morate navesti ime za svoj sigurnosni ključ." + change_about: + title: "Promijeni O meni" + error: "Došlo je do pogreške u promjeni ove vrijednosti." + change_username: + title: "Promijeni Korisničko ime" + confirm: "Jeste li apsolutno sigurni da želite promijeniti svoje korisničko ime?" + taken: "Žao nam je, to je ime zauzeto." + invalid: "Korisničko ime je nevažeće. Može sadržavati samo slova i brojeve." + add_email: + title: "Dodaj e-poštu" + add: "dodaj" + change_email: + title: "Promijeni E-mail" + taken: "Žao nam je, taj e-mail nije dostupan." + error: "Dogodila se greška u promijeni tvog e-maila. Možda ta adresa već postoji?" + success: "Poslali smo e-mail na tu adresu. Molimo vas slijedite upute za potvrdu." + success_via_admin: "Na tu smo adresu poslali e-poštu. Korisnik će morati slijediti upute za potvrdu u e-poruci." + success_staff: "Poslali smo e-mail na vašu trenutnu adresu. Slijedite upute za potvrdu." + change_avatar: + title: "Promjeni sliku profila." + gravatar: "%{gravatarName}, na temelju" + gravatar_title: "Promijenite avatar na %{gravatarName} web stranici" + gravatar_failed: "Nismo mogli pronaći %{gravatarName} s tom e-mail adresom." + refresh_gravatar_title: "Osvježite %{gravatarName}" + letter_based: "Sistemski dodjeljena slika profila" + uploaded_avatar: "Vlastita slika" + uploaded_avatar_empty: "Dodajte vlastitu sliku" + upload_title: "Učitajte sliku" + image_is_not_a_square: "Upozorenje: obrezali smo vašu sliku; širina i visina nisu bile jednake." + logo_small: "Mali logotip web stranice. Koristi se prema zadanim postavkama." + use_custom: "Ili prenesite prilagođeni avatar:" + change_profile_background: + title: "Zaglavlje profila" + instructions: "Zaglavlja profila bit će centrirana i imaju zadanu širinu od 1110px." + change_card_background: + title: "Pozadina Korisničke Kartice" + instructions: "Pozadinske slike će biti centrirane i imati standardu širinu od 590 piksela." + change_featured_topic: + title: "Istaknuta tema" + instructions: "Veza na ovu temu bit će na vašoj korisničkoj kartici i profilu." + email: + title: "E-mail" + primary: "Primarna e-pošta" + secondary: "Sekundarna e-pošta" + primary_label: "primarna" + unconfirmed_label: "nepotvrđeno" + resend_label: "ponovno pošaljite poruku e-pošte s potvrdom" + resending_label: "slanje ..." + resent_label: "email je poslan" + update_email: "Promijeni E-mail" + set_primary: "Postavi primarnu e-poštu" + destroy: "Ukloni e-poštu" + add_email: "Dodaj zamjensku e-poštu" + auth_override_instructions: "E-pošta se može ažurirati od davatelja autentičnosti." + no_secondary: "Nema sekundarnih e-pošta" + instructions: "NIkada neće biti javno prikazano." + admin_note: "Napomena: Administratorski korisnik koji mijenja e-adresu drugog ne-administrativnog korisnika znači da je korisnik izgubio pristup svom izvornom računu e-pošte, pa će se e-pošta za resetiranje lozinke poslati na njegovu novu adresu. E-adresa korisnika neće se mijenjati dok ne dovrše postupak resetiranja lozinke." + ok: "Poslati ćemo vam e-mail za potvrdu" + required: "Unesite e-mail adresu" + invalid: "Molimo unesite valjanu e-mail adresu" + authenticated: "E-mail vam je potvrđen od %{provider}" + invite_auth_email_invalid: "Adresa e-pošte s pozivnicom ne podudara se s adresom e-pošte koju je autentificirao %{provider}" + authenticated_by_invite: "Vaša e-pošta potvrđena je pozivnicom" + frequency_immediately: "Odmah ćemo vam poslati e-mail ako niste pročitali ono o ćemu vam šaljemo e-mailove." + frequency: + one: "Poslat ćemo vam e -poruku samo ako vas nismo vidjeli u zadnjoj minuti." + few: "Poslat ćemo vam e -poruku samo ako vas nismo vidjeli u zadnjih %{count} minuta." + other: "Poslat ćemo ti e-mail samo ako te nismo vidjeli u zadnjih %{count} minuta." + associated_accounts: + title: "Povezani računi" + connect: "Povežite se" + revoke: "Povuci" + cancel: "Odustani" + not_connected: "(nije povezano)" + confirm_modal_title: "Poveži %{provider} račun" + confirm_description: + disconnect: "Vaš postojeći %{provider} račun '%{account_description}' bit će prekinut." + account_specific: "Vaš %{provider} račun '%{account_description}' koristit će se za provjeru autentičnosti." + generic: "Vaš %{provider} račun koristit će se za provjeru autentičnosti." + name: + title: "Ime" + instructions: "Vaše ime i prezime (neobavezno)" + instructions_required: "Vaše ime i prezime" + required: "Unesite ime" + too_short: "Ime vam je prekratko" + ok: "Vaše ime dobro izgleda" + username: + title: "Korisničko ime" + instructions: "jedinstveno, bez razmaka, kratko" + short_instructions: "Ljudi vas mogu spominjati kao @%{username}" + available: "Vaše je korisničko ime slobodno" + not_available: "Nije dostupno. Pokušajte %{suggestion}?" + not_available_no_suggestion: "Nije dostupno" + too_short: "Vaše je korisničko ima prekratko" + too_long: "Vaše je korisničko ima pre dugačko" + checking: "Provjeravamo dostupnost korisničkog imena..." + prefilled: "E-mail odgovara ovom registriranom korisničkom imenom" + required: "Unesite korisničko ime" + edit: "Uredite korisničko ime" + locale: + title: "Jezik sučelja" + instructions: "Jezik korisničkog sučelja. Promijenit će se kad ponovo učitate stranicu." + default: "(zadano)" + any: "bilo koji" + password_confirmation: + title: "Ponoviti zaporku" + invite_code: + title: "Pozivni kod" + instructions: "Registracija računa zahtijeva pozivni kod" + auth_tokens: + title: "Nedavno korišteni uređaji" + details: "Detalji" + log_out_all: "Odjavite se sviju" + not_you: "Niste vi?" + show_all: "Prikaži sve (%{count})" + show_few: "Prikaži manje" + was_this_you: "Jeste li to bili vi?" + was_this_you_description: "Ako to niste bili vi, preporučujemo da promijenite lozinku i odjavite se posvuda." + browser_and_device: "%{browser} na %{device}" + secure_account: "Zaštitite moj račun" + latest_post: "Zadnji ste odgovorili…" + device_location: '%{device}%{location}' + browser_active: '%{browser} | aktivan sada' + browser_last_seen: "%{browser} | %{date}" + last_posted: "Zadnji Članak" + last_seen: "Viđeno" + created: "Pridužio" + log_out: "Odjavi se" + location: "Lokacija" + website: "Web stranica" + email_settings: "E-mail" + hide_profile_and_presence: "Sakrij moj javni profil i značajke prisutnosti" + enable_physical_keyboard: "Omogućivanje podrške za fizičku tipkovnicu na iPad-u" + text_size: + title: "Veličina teksta" + smallest: "Najmanji" + smaller: "Manji" + normal: "Normalno" + larger: "Veći" + largest: "Najveći" + title_count_mode: + title: "Naslov pozadinske stranice prikazuje broj:" + notifications: "Nove obavijesti" + contextual: "Sadržaj nove stranice" + like_notification_frequency: + title: "Obavijesti ako se sviđa" + always: "Uvijek" + first_time_and_daily: "Objava se svidjela prvi put i dnevno" + first_time: "Objava se svidjela prvi put" + never: "Nikada" + email_previous_replies: + title: "Uključite prethodne odgovore na dnu e-pošte" + unless_emailed: "osim ako prethodno nije poslano" + always: "uvijek" + never: "nikada" + email_digests: + title: "Kada ne posjetim ovdje, pošaljite mi e-mail sažetak popularnih tema i odgovora" + every_30_minutes: "svakih 30 minuta" + every_hour: "svaki sat" + daily: "dnevno" + weekly: "tjedno" + every_month: "svaki mjesec" + every_six_months: "svakih šest mjeseci" + email_level: + title: "Pošalji mi e-poštu kada me citiraju, odgovore na njih, spomene moje @korisničko ime ili kada postoji nova aktivnost u mojim gledanim kategorijama, oznakama ili temama" + always: "uvijek" + only_when_away: "samo kada je odstupan" + never: "nikad" + email_messages_level: "Pošaljite mi e-mail kada dobijem osobnu poruku" + include_tl0_in_digests: "Uključite sadržaj novih korisnika u sažetak e-pošte" + email_in_reply_to: "Uključite isječak odgovora na postove u e-poruke" + other_settings: "Drugo" + categories_settings: "Kategorije" + new_topic_duration: + label: "Teme smatrati novim kad" + not_viewed: "Još ih nisam pogledao" + last_here: "otvoreno odkad sam zadnji put bio tu" + after_1_day: "Jučer otvoreno" + after_2_days: "otvoreno prije 2 dana" + after_1_week: "otvoreno protekli tjedan" + after_2_weeks: "otvoreno protekla 2 tjedna" + auto_track_topics: "Automatski prati teme u koje uđem" + auto_track_options: + never: "nikad" + immediately: "odmah" + after_30_seconds: "nakon 30 sekundi" + after_1_minute: "nakon 1 minute" + after_2_minutes: "nakon 2 minute" + after_3_minutes: "nakon 3 minute" + after_4_minutes: "nakon 4 minute" + after_5_minutes: "nakon 5 minuta" + after_10_minutes: "nakon 10 minuta" + notification_level_when_replying: "Kad pišem u temi postavi temu na" + invited: + title: "Pozivnice" + pending_tab: "Na čekanju" + pending_tab_with_count: "Na čekanju (%{count})" + expired_tab: "Isteklo" + expired_tab_with_count: "Isteklo (%{count})" + redeemed_tab: "Nadoknadi" + redeemed_tab_with_count: "odabrano (%{count})" + invited_via: "Pozivnica" + invited_via_link: "Veza %{key} (%{count} / %{max} iskorišten)" + groups: "Grupe" + topic: "Tema" + sent: "Stvoreno/Zadnja poslana" + expires_at: "Istječe" + edit: "Uredi" + remove: "Ukloni" + copy_link: "Uzmi poveznicu" + reinvite: "Ponovo pošalji e-poštu" + reinvited: "Pozivnica ponovno poslana" + removed: "Uklonjeno" + search: "upišite da pregledate pozivnice" + user: "Pozvani Korisnici" + none: "Nema poziva za prikaz." + truncated: + one: "Prikazuje se prva pozivnica." + few: "Prikazuje se prvih %{count} pozivnica." + other: "Prikazuje se prvih %{count} pozivnica." + redeemed: "Nadoknadi Pozivnice" + redeemed_at: "Nadoknadi" + pending: "Tekuće pozivnice" + topics_entered: "Pogledane Teme" + posts_read_count: "Pročitani članci" + expired: "Ova je pozivnica istekla." + remove_all: "Ukloni istekle pozivnice" + removed_all: "Sve istekle pozivnice uklonjene!" + remove_all_confirm: "Jeste li sigurni da želite ukloniti sve istekle pozivnice?" + reinvite_all: "Ponovno pošalji sve pozivnice" + reinvite_all_confirm: "Jeste li sigurni da želite ponovo poslati sve pozivnice?" + reinvited_all: "Sve pozivnice poslane!" + time_read: "Vrijeme čitanja" + days_visited: "Dana posjećeno" + account_age_days: "Računaj starost u danima" + create: "Pozovite" + generate_link: "Stvori link pozivnicu" + link_generated: "Izvoli pozivnicu!" + valid_for: "Pozivnica je važeća isključivo sa elektroničke pošte: %{email}" + single_user: "Pozovi putem e-maila" + multiple_user: "Pozovi putem linka" + invite_link: + title: "Veza pozivnice" + success: "Pozivnica uspješno napravljena" + error: "Došlo je do pogreške generiranja linka za pozivnicu" + invite: + new_title: "Stvori pozivnicu" + edit_title: "Uredi pozivnicu" + instructions: "Podijelite ovu vezu da biste odmah dodijelili pristup stranici:" + copy_link: "kopiraj link" + expires_in_time: "Istječe za %{time}" + expired_at_time: "Isteklo u %{time}" + show_advanced: "Prikaži napredne mogućnosti" + hide_advanced: "Sakrij napredne opcije" + restrict: "Ograničiti na" + restrict_email: "Ograničite na e-poštu" + restrict_domain: "Ograniči na domenu" + email_or_domain_placeholder: "ime@primjer.com ili primjer.com" + max_redemptions_allowed: "Max korisnika" + add_to_groups: "Dodaj u grupe" + invite_to_topic: "Dođite do teme" + expires_at: "Istječe nakon" + custom_message: "Izborna osobna poruka" + send_invite_email: "Spremi i pošalji e-mail" + send_invite_email_instructions: "Ograničite pozivnicu na e-poštu za slanje e-pošte s pozivnicom" + save_invite: "Spremi pozivnicu" + invite_saved: "Pozivnica je spremljena." + bulk_invite: + none: "Nema pozivnica za prikaz na ovoj stranici." + text: "Skupna pozivnica" + instructions: | +

Pozovite popis korisnika da biste ubrzali svoju zajednicu. Pripremite CSV datoteka koji sadrži najmanje jedan redak po adresi e-pošte korisnika koje želite pozvati. Sljedeće informacije odvojene zarezom možete navesti ako želite dodati osobe u grupe ili ih poslati na određenu temu prilikom prve prijave.

+
john@smith.com, prvo_group_name; second_group_name, tema_id
+

Svaka adresa e-pošte u vašoj učitanoj CSV datoteci poslat će se pozivnica i moći ćete je kasnije upravljati.

+ progress: "Upload %{progress}%..." + success: "Datoteka je uspješno prenesena. Bit ćete obaviješteni putem poruke kada je postupak dovršen." + error: "Nažalost, datoteka bi trebala biti u CSV formatu." + password: + title: "Zaporka" + too_short: "Vaša je zaporka prekratka" + common: "Ta je zaporka pre česta." + same_as_username: "Vaša lozinka je ista kao korisničko ime." + same_as_email: "Vaša lozinka jednaka je vašoj e-pošti." + ok: "Vaša zaporka dobro izgleda." + instructions: "minimalno %{count} znakova" + required: "Molimo unesite lozinku" + summary: + title: "Sažetak" + stats: "Statistike" + time_read: "Vrijeme čitanja" + time_read_title: "%{duration} (sve vrijeme)" + recent_time_read: "vrijeme nedavnog čitanja" + recent_time_read_title: "%{duration} (u zadnjih 60 dana)" + topic_count: + one: "tema stvorena" + few: "napravljene teme" + other: "stvorene teme" + post_count: + one: "post kreiran" + few: "objava stvoreno" + other: "stvoreni postova" + likes_given: + one: "dato" + few: "dano" + other: "dato" + likes_received: + one: "primljeno" + few: "primljeno" + other: "primljeno" + days_visited: + one: "posjećeno dana" + few: "posjećenih dana" + other: "posjećenih dana" + topics_entered: + one: "tema pregledana" + few: "pregledane teme" + other: "pregledane teme" + posts_read: + one: "pročitani post" + few: "pročitanih postova" + other: "pročitanih postova" + bookmark_count: + one: "Zabilješka" + few: "Zabilješke" + other: "Zabilješke" + top_replies: "Top odgovori" + no_replies: "Još nema odgovora." + more_replies: "Više odgovora" + top_topics: "Istaknute teme" + no_topics: "Još nema tema." + more_topics: "Više tema" + top_badges: "Istaknute značke" + no_badges: "Još nema značke." + more_badges: "Više značaka." + top_links: "Istaknute poveznice" + no_links: "Još nema poveznica." + most_liked_by: "Najviše se sviđa" + most_liked_users: "Najviše se sviđa" + most_replied_to_users: "Najčešće odgovora" + no_likes: "Još se nikome ne sviđa." + top_categories: "Top kategorije" + topics: "Teme" + replies: "Odgovori" + ip_address: + title: "Posljednja IP adresa" + registration_ip_address: + title: "Registracijska IP adresa" + avatar: + title: "Profilna slika" + header_title: "profil, poruke, oznake i postavke" + name_and_description: "%{name} - %{description}" + edit: "Uredi sliku profila" + title: + title: "Naslov" + none: "(ništa)" + instructions: "se pojavljuje nakon vašeg korisničkog imena" + flair: + title: "Njuh" + none: "(ništa)" + instructions: "ikona prikazana uz sliku vašeg profila" + primary_group: + title: "Primarna grupa" + none: "(ništa)" + filters: + all: "Sve" + stream: + posted_by: "Objavio" + sent_by: "Poslao" + private_message: "poruka" + the_topic: "tema" + user_status: + save: "Spremi" + set_custom_status: "Postavi prilagođeni status" + what_are_you_doing: "Što radiš?" + loading: "Očitavanje..." + errors: + prev_page: "pri pokušaju očitanja" + reasons: + network: "Greška mreže" + server: "Greška servera" + forbidden: "Pristup odbijen" + unknown: "Greška" + not_found: "Stranica nije pronađena" + desc: + network: "Molimo provjerite svoju vezu." + network_fixed: "Čini se da se vratila." + server: "Vrsta greške: %{status} " + forbidden: "Nemate dozvolu to gledati." + not_found: "Opa... Izgleda da taj URL ne postoji." + unknown: "Nešto je pošlo krivo." + buttons: + back: "Idi natrag" + again: "Pokušaj ponovno" + fixed: "Učitaj stranicu" + modal: + close: "zatvori" + dismiss_error: "Odbaci pogrešku" + close: "Zatvori" + assets_changed_confirm: "Ova je web lokacija upravo dobila nadogradnju softvera. Nabavite najnoviju verziju sada?" + logout: "Odjavljeni ste." + refresh: "Osviježi." + home: "Naslovnica" + read_only_mode: + enabled: "Ova stranica je u načinu rada samo za čitanje. Nastavite pregledavati, ali odgovaranje, sviđanja i druge radnje su za sada onemogućene." + login_disabled: "Prijava je onemogućena dok je stranica u modelu \"samo čitanje\"," + logout_disabled: "Odjava je onemogućena dok je forum u načinu rada samo za čitanje." + too_few_topics_and_posts_notice_MF: >- + Počnimo raspravu! Tamo {currentTopics, plural, one {je # tema} few {su # tema} other {su # tema}} i {currentPosts, plural, one {# post} few {# postova} other {# postova}}, Posjetitelji trebaju više za čitanje i odgovaranje na njih - preporučujemo ih barem {requiredTopics, plural, one {# tema} few {# tema} other {# tema}} i {requiredPosts, plural, one {# post} few {# postova} other {# postova}}, Samo osoblje može vidjeti ovu poruku. + too_few_topics_notice_MF: >- + Hajde da započnemo diskusiju! Tamo {currentTopics, plural, one {je # tema} few {su # teme} other {su # teme}}. Posjetiteljima je potrebno više čitati i odgovoriti na — preporučujemo barem {requiredTopics, plural, one {# tema} few {# teme} other {# teme}}. Samo osoblje može vidjeti ovu poruku. + too_few_posts_notice_MF: >- + Hajde da započnemo diskusiju! Tamo {currentPosts, plural, one {je # tema} few {su # teme} other {su # teme}}. Posjetiteljima je potrebno više čitati i odgovoriti na — preporučujemo barem {requiredPosts, plural, one {# tema} few {# teme} other {# teme}}. Samo osoblje može vidjeti ovu poruku. + logs_error_rate_notice: + reached_hour_MF: "{relativeAge}{rate, plural, one {# Greška/sat} few {# greške/sat} other {# greške/sat}} dosegao ograničenje za postavljanje stranice {limit, plural, one {# Greška/sat} few {# greške/sat} other {# greške/sat}}." + reached_minute_MF: "{relativeAge}{rate, plural, one {# Greška/minuta} few {# greške/minuta} other {# greške/minuta}} dosegao ograničenje za postavljanje stranice {limit, plural, one {# Greška/minuta} few {# greške/minuta} other {# greške/minuta}}." + exceeded_hour_MF: "{relativeAge}{rate, plural, one {# Greška/sat} few {# greške/sat} other {# greške/sat}} dosegao ograničenje za postavljanje stranice {limit, plural, one {# Greška/sat} few {# greške/sat} other {# greške/sat}}." + exceeded_minute_MF: "{relativeAge}{rate, plural, one {# Greška/minuta} few {# greške/minuta} other {# greške/minuta}} premašio ograničenje za postavljanje stranice {limit, plural, one {# Greška/minuta} few {# greške/minuta} other {# greške/minuta}}." + learn_more: "saznaj više..." + first_post: Prvi članak + mute: Utišaj + unmute: Poništi utišanje + last_post: Objavljeno + local_time: "Lokalno vrijeme" + time_read: Pročitano + time_read_recently: "%{time_read} nedavno" + time_read_tooltip: "%{time_read} ukupno vrijeme čitanja" + time_read_recently_tooltip: "%{time_read} ukupno vrijeme čitanja (%{recent_time_read} u posljednjih 60 dana)" + last_reply_lowercase: zadnji odgovor + replies_lowercase: + one: odgovor + few: odgovori + other: odgovora + signup_cta: + sign_up: "Učlani se" + hide_session: "Podsjeti me sutra." + hide_forever: "ne hvala" + hidden_for_session: "OK, pitat ćemo te sutra. Uvijek možete upotrijebiti 'Prijava' i za stvaranje računa." + intro: "Zdravo! Čini se da uživate u raspravi, ali još se niste prijavili za račun." + value_prop: "Kada stvorite račun, sjećamo se točno onoga što ste pročitali, pa se uvijek vraćate na mjesto gdje ste stali. Također dobivate obavijesti, ovdje i putem e-pošte, kad god vam netko odgovara. I možete lajkati :heartpulse: tuđe postove i reagirati na iste reakcijama." + summary: + enabled_description: "Gledate pregled ove teme: najzanimljivije članke odabire zajednica." + description: + one: "Postoji %{count} odgovor." + few: "Postoji %{count} odgovora." + other: "Ima %{count} odgovora." + description_time_MF: "Tamo {replyCount, plural, one {je # odgovor} few {su # odgovora} other {su # odgovora}} s predviđenim vremenom čitanja {readingTime, plural, one {# minuta} few {# minuta} other {# minuta}}." + enable: "Sažmi ovu temu" + disable: "Prikaži sve članke" + short_label: "Rezimirati" + short_title: "Prikaži sažetak ove teme: najzanimljiviji postovi prema odluci zajednice" + deleted_filter: + enabled_description: "Ova tema sadrži prisane članke, koji su skriveni." + disabled_description: "Obrisani članci u temi su prikazani." + enable: "Sakrij obrisane članke" + disable: "Pokaži obrisane članke" + private_message_info: + title: "Poruka" + invite: "Pozovite druge..." + edit: "Dodaj ili ukloni..." + remove: "Ukloni..." + add: "Dodaj..." + leave_message: "Želite li zaista ostaviti ovu poruku?" + remove_allowed_user: "Želite li zaista ukloniti %{name} iz ove poruke?" + remove_allowed_group: "Želite li zaista ukloniti %{name} iz ove poruke?" + email: "E-mail" + username: "Korisničko ime" + last_seen: "Viđeno" + created: "Stvoreno" + created_lowercase: "stvoreno" + trust_level: "Razina Povjerenja" + search_hint: "korisničko ime, e-mail ili IP adresa" + create_account: + header_title: "Dobrodošli!" + subheader_title: "Kreirajmo vaš račun" + disclaimer: "Registracijom pristajete na politiku privatnosti i uvjete korištenja." + title: "Kreiraj svoj račun" + failed: "Nešto je pošlo krivo, možda je ovaj e-mail već registriran, pokušajte s poveznicom za zaboravljenu zaporku." + associate: "Već imate račun? Prijavite se da biste povezali svoj %{provider} račun." + forgot_password: + title: "Poništavanje lozinke" + action: "Zaboravio/la sam zaporku" + invite: "Upišite svoje korisničko ime ili e-mail adresu, pa ćemo vam poslati e-mail za ponovno postavljanje zaporke." + invite_no_username: "Unesite svoju adresu e-pošte, a mi ćemo vam poslati e-poštu za poništavanje lozinke." + reset: "Ponovno postavi zaporku" + complete_username: "Ako račun odgovara korisničkom imenu %{username}, trebali bi ste uskoro dobiti e-mail s uputama za ponovno postavljanje zaporke." + complete_email: "Ako račun odgovara %{email}, trebali bi ste uskoro dobiti e-mail s uputama za ponovno postavljanje zaporke." + complete_username_found: "Pronašli smo račun koji odgovara korisničkom imenu %{username}. Trebali biste primiti uskoro e-poruku s uputama o tome kako vratiti lozinku." + complete_email_found: "Pronašli smo račun koji odgovara korisničkom imenu %{email}. Trebali biste primiti uskoro e-poruku s uputama o tome kako vratiti lozinku." + complete_username_not_found: "Nema računa koji odgovara korisničkom imenu %{username}" + complete_email_not_found: "Nema računa koji odgovara %{email}" + help: "E-mail ne stiže? Svakako najprije provjerite mapu neželjene pošte.

Niste sigurni koju ste adresu e-pošte koristili? Unesite e-mail adresu i obavijestit ćemo vas da li postoji ovdje.

Ako više nemate pristup e-mail adresi na svom računu, kontaktirajte naše korisno osoblje.

" + button_ok: "U redu" + button_help: "Pomoć" + email_login: + link_label: "Pošaljite mi link za prijavu" + button_label: "s e-mailom" + login_link: "Preskoči lozinku; pošalji mi link za prijavu" + emoji: "zaključajte emoji" + complete_username: "Ako se račun podudara s korisničkim imenom %{username}, uskoro biste trebali dobiti e-poštu s vezom za prijavu." + complete_email: "Ako se račun podudara s %{email}, uskoro biste trebali dobiti e-poštu s vezom za prijavu." + complete_username_found: "Pronašli smo račun koji odgovara korisničkom imenu %{username}, trebali biste uskoro primiti e-mail s linkom za prijavu." + complete_email_found: "Pronašli smo račun koji odgovara %{email}, trebali biste uskoro primiti e-poštu s linkom za prijavu." + complete_username_not_found: "Nema računa koji odgovara korisničkom imenu %{username}" + complete_email_not_found: "Nema računa koji odgovara %{email}" + confirm_title: Nastavite na %{site_name} + logging_in_as: Prijava kao %{email} + confirm_button: Završi prijavu + login: + header_title: "Dobro došli natrag" + subheader_title: "Prijavite se na svoj račun" + title: "Prijavite se" + username: "Korisnik" + password: "Zaporka" + second_factor_title: "Dvofaktorska autentifikacija" + second_factor_description: "Unesite kod za provjeru autentičnosti iz svoje aplikacije:" + second_factor_backup: "Prijavite se pomoću rezervnog koda" + second_factor_backup_title: "Sigurnosna kopija od dva faktora" + second_factor_backup_description: "Unesite jedan od vaših rezervnih kodova:" + second_factor: "Prijavite se pomoću aplikacije Authenticator" + security_key_description: "Kada pripremite svoj fizički sigurnosni ključ, pritisnite gumb Autentifikacija sa sigurnosnim ključem u nastavku." + security_key_alternative: "Pokušaj na drugi način" + security_key_authenticate: "Autentifikacija sa sigurnosnim ključem" + security_key_not_allowed_error: "Proces provjere autentičnosti sigurnosnog ključa je istekao ili je otkazan." + security_key_no_matching_credential_error: "U priloženom sigurnosnom ključu nije moguće pronaći odgovarajuće vjerodajnice." + security_key_support_missing_error: "Vaš trenutni uređaj ili preglednik ne podržava korištenje sigurnosnih tipki. Molimo koristite drugu metodu." + email_placeholder: "E-adresa / korisničko ime" + caps_lock_warning: "Caps Lock vam je uključen" + error: "Nepoznata greška" + cookies_error: "Čini se da su u vašem pregledniku onemogućeni kolačići. Možda se nećete moći prijaviti bez da ih prvo omogućite." + rate_limit: "Molimo pričekajte prije nego što pokušate ponovno prijaviti." + blank_username: "Unesite svoj e-mail ili korisničko ime." + blank_username_or_password: "Molimo unesite e-mail ili korisničko ime i zaporku." + reset_password: "Ponovno postavi zaporku" + logging_in: "Prijavljivanje..." + or: "Ili" + authenticating: "Autentifikaciranje..." + awaiting_activation: "Vaš račun čeka aktivaciju, poslužite se poveznicom za zaboravljenu zaporku da pošaljemo drugi aktivacijski e-mail." + awaiting_approval: "Vaš račun nije odobren od osoblja. Poslat ćemo vam e-mail kad bude odobren." + requires_invite: "Žao nam je, pristup ovom forumu moguć je samo uz pozivnicu." + not_activated: "Ne možete se još prijaviti. Ranije smo vam poslali e-mail na %{sentTo}. Molimo slijedite upute da aktivirate račun." + not_allowed_from_ip_address: "Ne možete se prijaviti s te IP adrese." + admin_not_allowed_from_ip_address: "Ne možete se prijaviti kao administrator s te IP adrese." + resend_activation_email: "Pritisnite ovjde da ponovno pošaljemo aktivacijski e-mail." + omniauth_disallow_totp: "Na vašem je računu omogućena dvofaktorska provjera autentičnosti. Molimo prijavite se zaporkom." + resend_title: "Ponovno pošaljite e-poštu za aktivaciju" + change_email: "Promijeni e mail adresu" + provide_new_email: "Nanesite novu adresu i ponovno ćemo vam poslati e-poštu s potvrdom." + submit_new_email: "Ažurirajte adresu e-pošte" + sent_activation_email_again: "Poslali smo vam drugi aktivacijski e-mail na: %{currentEmail}. Možda zatreba par minuta da stigne; svakako provjerite mapu nepoželjne pošte." + sent_activation_email_again_generic: "Poslali smo još jedan email za aktivaciju. Možda će trebati nekoliko minuta da stigne; provjerite mapu neželjene pošte." + to_continue: "Molimo prijavite se" + preferences: "Morate biti prijavljeni da biste promijenili svoje korisničke postavke." + not_approved: "Vaš račun još nije odobren. Bit ćete obaviješteni putem e-maila kada budete spremni za prijavu." + google_oauth2: + name: "Google" + title: "Prijavite se s Googleom" + sr_title: "Prijavite se s Googleom" + twitter: + name: "Twitter" + title: "Prijavite se s Twitterom" + sr_title: "Prijavite se s Twitterom" + instagram: + name: "Instagram" + title: "Prijavite se s Instagramom" + sr_title: "Prijavite se s Instagramom" + facebook: + name: "Facebook" + title: "Prijavite se s Facebookom" + sr_title: "Prijavite se s Facebookom" + github: + name: "GitHub" + title: "Prijavite se putem GitHuba" + sr_title: "Prijavite se putem GitHuba" + discord: + name: "Discord" + title: "Prijavite se s Discordom" + sr_title: "Prijavite se s Discordom" + second_factor_toggle: + totp: "Umjesto toga upotrijebite aplikaciju za provjeru autentičnosti" + backup_code: "Umjesto toga upotrijebite sigurnosni kod" + security_key: "Umjesto toga upotrijebite sigurnosni ključ" + invites: + accept_title: "Pozivnica" + emoji: "emoji omotnice" + welcome_to: "Dobrodošli na %{site_name}" + invited_by: "Pozvao/la vas je:" + social_login_available: "Također ćete se moći prijaviti s bilo kojom društvenim profilom pomoću te e-pošte." + your_email: "Adresa e-pošte vašeg računa je %{email}." + accept_invite: "Prihvati pozivnicu" + success: "Vaš račun je kreiran i sada ste prijavljeni." + name_label: "Ime" + password_label: "Lozinka" + password_reset: + continue: "Nastavite na %{site_name}" + emoji_set: + apple_international: "Apple/International" + google: "Google" + twitter: "Twitter" + win10: "Win10" + google_classic: "Google Classic" + facebook_messenger: "Facebook Messenger" + category_page_style: + categories_only: "Samo kategorije" + categories_with_featured_topics: "Kategorije s istaknutim temama" + categories_and_latest_topics: "Kategorije i najnovije teme" + categories_and_top_topics: "Kategorije i glavne teme" + categories_boxes: "Kutije s potkategorijama" + categories_boxes_with_topics: "Kutije s istaknutim temama" + subcategories_with_featured_topics: "Potkategorije s istaknutim temama" + shortcut_modifier_key: + shift: "Shift" + ctrl: "Ctrl" + alt: "Alt" + enter: "Unesi" + conditional_loading_section: + loading: Očitavanje... + category_row: + topic_count: + one: "%{count} tema u ovoj kategoriji" + few: "%{count} tema u ovoj kategoriji" + other: "%{count} tema u ovoj kategoriji" + plus_subcategories_title: + one: "%{name} i jedna potkategorija" + few: "%{name} i %{count} potkategorija" + other: "%{name} i %{count} podkategorije" + plus_subcategories: + one: "+ %{count} potkategorija" + few: "+ %{count} potkategorija" + other: "+ %{count} potkategorija" + select_kit: + delete_item: "Obriši %{name}" + filter_by: "Filtriraj po: %{name}" + select_to_filter: "Odaberite vrijednost za filtriranje" + default_header_text: Odaberi... + no_content: Nema pronađenih podudaranja + results_count: + one: "%{count} rezultat" + few: "%{count} rezultata" + other: "%{count} rezultata" + filter_placeholder: Pretraži... + filter_placeholder_with_any: Pretraži ili stvori... + create: "Stvori: '%{content}'" + max_content_reached: + one: "Možete odabrati samo %{count} stavku." + few: "Možete odabrati samo %{count} stavki." + other: "Možete odabrati samo %{count} stavaka." + min_content_not_reached: + one: "Odaberite barem %{count} stavku." + few: "Odaberite barem %{count} stavki." + other: "Odaberite barem %{count} stavki." + invalid_selection_length: + one: "Odabir mora sadržavati najmanje %{count} znakova." + few: "Odabir mora sadržavati najmanje %{count} znakova." + other: "Odabir mora sadržavati najmanje %{count} znakova." + components: + tag_drop: + filter_for_more: Filtrirajte za više ... + categories_admin_dropdown: + title: "Upravljanje kategorijama" + date_time_picker: + from: Od + to: Za + emoji_picker: + filter_placeholder: Traži emotikone + smileys_&_emotion: Smajlići i emotikoni + people_&_body: Ljudi i tijelo + animals_&_nature: Životinje i priroda + food_&_drink: Hrana i piće + travel_&_places: Putovanja i mjesta + activities: Aktivnosti + objects: Objekti + symbols: Simboli + flags: Oznake zastavicom + recent: Nedavno korišteno + default_tone: Nema ton kože + light_tone: Svijetli ton kože + medium_light_tone: Srednje svijetli ton kože + medium_tone: Srednje svijetli ton kože + medium_dark_tone: Srednje taman ton kože + dark_tone: Tamni ton kože + default: Prilagođeni emojiji + shared_drafts: + title: "Dijeljeni nacrti" + notice: "Ova je tema vidljiva samo onima koji mogu objaviti zajedničke skice." + destination_category: "Kategorija odredišta" + publish: "Objavi zajedničku skicu" + confirm_publish: "Jeste li sigurni da želite objaviti ovaj nacrt?" + publishing: "Objavljivanje teme ..." + composer: + emoji: "Emoji :)" + more_emoji: "više..." + options: "Mogućnosti" + whisper: "šapat" + unlist: "nenavedeno" + add_warning: "Ovo je službeno upozorenje." + toggle_whisper: "Uključi šapat" + toggle_unlisted: "Uključi / isključi Nenavedeno" + posting_not_on_topic: "U kojoj temi želite odgovoriti?" + saved_local_draft_tip: "spašeno lokalno" + similar_topics: "Vaša tema je slična..." + drafts_offline: "nacrti offline" + edit_conflict: "uredi sukob" + group_mentioned_limit: + one: "Upozorenje! Spomenuli ste %{group}, međutim ova grupa ima više članova od administratora konfiguriranog ograničenja spominjanja od %{count} korisnika. Nitko neće biti obaviješten." + few: "Upozorenje! Spomenuli ste %{group}, međutim ova grupa ima više članova od administratora konfiguriranog ograničenja spominjanja od %{count} korisnika. Nitko neće biti obaviješten." + other: "Upozorenje! Spomenuli ste %{group}, međutim ova grupa ima više članova od administratora koji je konfigurirao ograničenje spominjanja od %{count} korisnika. Nitko neće biti obaviješten." + group_mentioned: + one: "Spominjući %{group}, te o tome obavijestiti %{count} osobe - jeste li sigurni?" + few: "Spominjući %{group}, te o tome obavijestiti %{count} osobe - jeste li sigurni?" + other: "Spominjući %{group}, te o tome obavijestiti %{count} ljudi - jeste li sigurni?" + cannot_see_mention: + category: "Spomenuli ste @%{username} , ali oni neće biti obaviješteni jer nemaju pristup ovoj kategoriji. Morat ćete ih dodati u grupu koja ima pristup ovoj kategoriji." + private: "Spomenuli ste @%{username} , ali oni neće biti obaviješteni jer ne mogu vidjeti ovu osobnu poruku. Morat ćete ih pozvati u ovu osobnu poruku." + muted_topic: "Spomenuli ste @%{username} , ali oni neće biti obaviješteni jer su isključili ovu temu." + not_allowed: "Spomenuli ste @%{username} , ali oni neće biti obaviješteni jer nisu pozvani na ovu temu." + here_mention: + one: "Spominjanjem @%{here}obavijestit ćete %{count} korisnika – jeste li sigurni?" + few: "Spominjanjem @%{here}obavijestit ćete %{count} korisnika – jeste li sigurni?" + other: "Spominjanjem @%{here}obavijestit ćete %{count} korisnika – jeste li sigurni?" + duplicate_link: "Čini se da je vašu vezu na %{domain} već objavio u temi @%{username} u odgovor na %{ago} - jeste li sigurni da ga želite ponovo objaviti?" + reference_topic_title: "RE: %{title}" + error: + title_missing: "Naslov je obavezan" + title_too_short: + one: "Naslov mora biti barem %{count} znakova" + few: "Naslov mora biti barem %{count} znakova" + other: "Naslov mora biti barem %{count} znakova" + title_too_long: + one: "Naslov ne smije imati više od %{count} znakova" + few: "Naslov ne smije imati više od %{count} znakova" + other: "Naslov ne smije imati više od %{count} znakova" + post_missing: "Post ne može biti prazan" + post_length: + one: "Objava mora sadržavati najmanje %{count} znakova" + few: "Objava mora sadržavati najmanje %{count} znakova" + other: "Post mora sadržavati najmanje %{count} znakova" + try_like: "Jeste li isprobali gumb %{heart}" + category_missing: "Morate odabrati kategoriju" + tags_missing: + one: "Morate odabrati najmanje %{count} oznaku" + few: "Morate odabrati najmanje %{count} oznaka" + other: "Morate odabrati najmanje %{count} oznaka" + topic_template_not_modified: "Uređivanjem predloška teme dodajte detalje i pojedinosti svojoj temi." + save_edit: "Spremi izmjenu" + overwrite_edit: "Prebriši Uređivanje" + reply_original: "Odgovori na originalnu temu" + reply_here: "Odgovori ovdje" + reply: "Odgovori" + cancel: "Odustani" + create_topic: "Pokreni temu" + create_pm: "Poruka" + create_whisper: "Šapat" + create_shared_draft: "Stvori zajedničku skicu" + edit_shared_draft: "Uredi zajedničku skicu" + title: "Ili pritisnite %{modifier}Enter" + users_placeholder: "Dodaj korisnika" + title_placeholder: "O čemu je ova rasprava u jednoj kratkoj rečenici?" + title_or_link_placeholder: "Upišite naslov ili ovdje zalijepite vezu" + edit_reason_placeholder: "zašto izmjenjujete?" + topic_featured_link_placeholder: "Unesite vezu prikazanu s naslovom." + remove_featured_link: "Ukloni vezu iz teme." + reply_placeholder: "Tipkajte ovdje. Za formatiranje koristite Markdown, BBCode ili HTML. Povucite ili zalijepite slike." + reply_placeholder_no_images: "Tipkajte ovdje. Za formatiranje koristite Markdown, BBCode ili HTML." + reply_placeholder_choose_category: "Odaberite kategoriju prije upisivanja ovdje." + view_new_post: "Pogledajte svoj novi članak." + saving: "Spremanje" + saved: "Spremljeno!" + saved_draft: "Nacrt posta u tijeku. Dodirnite za nastavak." + uploading: "Učitavanje..." + show_preview: "prikaz pregleda" + hide_preview: "sakrij pregled" + quote_post_title: "Citiraj cijeli članak" + bold_label: "B" + bold_title: "Jak" + bold_text: "jaki text" + italic_label: "I" + italic_title: "Naglasak" + italic_text: "naglašen tekst" + link_title: "Poveznica" + link_description: "ovdje unesite opis poveznice" + link_dialog_title: "Umetni poveznicu" + link_optional_text: "neobavezni naslov" + link_url_placeholder: "Zalijepite URL ili tip za pretraživanje tema" + blockquote_title: "\"Blockquote\" izdvojeni citat" + blockquote_text: "\"Blockquote\" izdvojeni citat" + code_title: "Pred-formatirani text" + code_text: "uvuci pred-formatirani text za 4 mjesta" + paste_code_text: "upišite ili zalijepite kod ovdje" + upload_title: "Učitaj" + upload_description: "ovdje unesite opise stvari koju učitavate" + olist_title: "Brojčani popis" + ulist_title: "Točkani popis" + list_item: "Predmet popisa" + toggle_direction: "Uključi smjer" + help: "Markdown pomoć pri izmjenama" + collapse: "minimizirajte skladateljsku ploču" + open: "otvorite ploču skladatelja" + abandon: "zatvorite skladatelja i odbacite skicu" + enter_fullscreen: "unesite kompozitor preko cijelog zaslona" + exit_fullscreen: "izađi iz cijelog zaslona u kompozitoru" + show_toolbar: "prikaži alatnu traku skladatelja" + hide_toolbar: "sakrij alatnu traku skladatelja" + modal_ok: "U redu" + modal_cancel: "Odustani" + cant_send_pm: "Žao nam je, ne možete poslati poruku %{username}." + yourself_confirm: + title: "Jeste li zaboravili dodati primatelje?" + body: "Trenutno će ova poruka biti poslana samo vama!" + slow_mode: + error: "Ova je tema u usporenom načinu rada. Već ste nedavno objavili; možete ponovo objaviti u %{timeLeft}." + admin_options_title: "Neobavezna pravila osoblja za ovu temu" + composer_actions: + reply: Odgovori + draft: Nacrt + edit: Izmijeni + reply_to_post: + label: Odgovorite na post korisnika %{postUsername} + desc: Odgovor na određeni post + reply_as_new_topic: + label: Odgovori u povezanoj temi + desc: Stvaranje nove teme povezane s ovom temom + confirm: Imate novi nacrt teme spremljen, koji će biti prebrisan ako stvorite povezanu temu. + reply_as_new_group_message: + label: Odgovori kao nova grupna poruka + desc: Izradite novu poruku počevši od istih primatelja + reply_to_topic: + label: Odgovori na temu + desc: Odgovor na temu, a ne određeni post + toggle_whisper: + label: Uključi šapat + desc: Šaputanja su vidljiva samo članovima osoblja + create_topic: + label: "Nova tema" + shared_draft: + label: "Zajednički nacrt" + desc: "Nacrtajte temu koja će biti vidljiva samo dopuštenim korisnicima" + toggle_topic_bump: + label: "Uključi kvrga teme" + desc: "Odgovorite bez promjene zadnjeg datuma odgovora" + reload: "Ponovno učitaj" + ignore: "Zanemari" + image_alt_text: + aria_label: Zamjenski tekst za sliku + notifications: + tooltip: + regular: + one: "%{count} neviđena obavijest" + few: "%{count} neviđeni obavijesti" + other: "%{count} neviđenih obavijesti" + message: + one: "%{count} nepročitana poruka" + few: "%{count} nepročitanih poruka" + other: "%{count} nepročitanih poruka" + high_priority: + one: "%{count} nepročitana obavijest visokog prioriteta" + few: "%{count} nepročitanih obavijesti visokog prioriteta" + other: "%{count} nepročitanih obavijesti visokih prioriteta" + title: "obavijesti o spominjanju @ime, odgovori na vaše objave i teme, poruke itd." + none: "Nemoguće očitati obavijesti u ovom trenutku." + empty: "Obavijesti nisu pronađene." + post_approved: "Vaš post je odobren" + reviewable_items: "stavke koje zahtijevaju pregled" + mentioned: "%{username} %{description}" + group_mentioned: "%{username} %{description}" + quoted: "%{username} %{description}" + bookmark_reminder: "%{username} %{description}" + replied: "%{username} %{description}" + posted: "%{username} %{description}" + edited: "%{username} %{description}" + liked: "%{username} %{description}" + liked_2: "%{username}, %{username2} %{description}" + liked_many: + one: "%{username}, %{username2} i %{count} ostalo %{description}" + few: "%{username}, %{username2} i %{count} ostalih %{description}" + other: "%{username}, %{username2} i %{count} ostali %{description}" + liked_consolidated_description: + one: "se sviđa vaš %{count} post" + few: "se sviđa vaših %{count} postova" + other: "se svidjelo %{count} vaših postova" + liked_consolidated: "%{username} %{description}" + private_message: "%{username} %{description}" + invited_to_private_message: "

%{username} %{description}" + invited_to_topic: "%{username} %{description}" + invitee_accepted: "%{username} prihvatio/la je vašu pozivnicu" + moved_post: "%{username} premješteno %{description}" + linked: "%{username} %{description}" + granted_badge: "Dobio '%{description}'" + topic_reminder: "%{username} %{description}" + watching_first_post: "Nova tema %{description}" + membership_request_accepted: "Članstvo prihvaćeno u \"%{group_name}\"" + membership_request_consolidated: + one: "%{count} otvorenih zahtjeva za članstvo za '%{group_name}'" + few: "%{count} otvorenih zahtjeva za članstvo za '%{group_name}'" + other: "%{count} otvorenih zahtjeva za članstvo za '%{group_name}'" + reaction: "%{username} %{description}" + reaction_2: "%{username}, %{username2} %{description}" + votes_released: "%{description} - dovršeno" + dismiss_confirmation: + body: + one: "Jesi li siguran? Imate %{count} važnih obavijesti." + few: "Jesi li siguran? Imate %{count} važnih obavijesti." + other: "Jesi li siguran? Imate %{count} važnih obavijesti." + dismiss: "Skloni" + cancel: "Odustani" + group_message_summary: + one: "%{count} poruka u vašem %{group_name} dolaznom pretincu" + few: "%{count} poruka u vašem %{group_name} dolaznom pretincu" + other: "%{count} poruka u vašem %{group_name} dolaznom pretincu" + popup: + mentioned: '%{username} vas je spomenuo u "%{topic}" - %{site_title}' + group_mentioned: '%{username} vas je spomenuo u "%{topic}" - %{site_title}' + quoted: '%{username} citirao vas je u "%{topic}" - %{site_title}' + replied: '%{username} odgovorio u "%{topic}" - %{site_title}' + posted: '%{username} objavio u "%{topic}" - %{site_title}' + private_message: '%{username} vam je poslao privatnu poruku u temi "%{topic}" - %{site_title}' + linked: '%{username} se povezao s tvojom objavom "%{topic}" - %{site_title}' + watching_first_post: '%{username} stvorio je novu temu "%{topic}" - %{site_title}' + confirm_title: "Obavijesti omogućene - %{site_title}" + confirm_body: "Uspjeh! Obavijesti su omogućene." + custom: "Obavijest od %{username} %{site_title}" + titles: + mentioned: "spominje" + replied: "novi odgovor" + quoted: "citirano" + edited: "uredio" + liked: "novo sviđanje" + private_message: "nova privatna poruka" + invited_to_private_message: "pozvan na privatnu poruku" + invitee_accepted: "poziv prihvaćen" + posted: "novi post" + moved_post: "post premješten" + linked: "povezan" + bookmark_reminder: "podsjetnik na oznaku" + bookmark_reminder_with_name: "podsjetnik za oznaku - %{name}" + granted_badge: "dodijeljena značka" + invited_to_topic: "pozvani na temu" + group_mentioned: "spomenuta grupa" + group_message_summary: "nove grupne poruke" + watching_first_post: "nova tema" + topic_reminder: "podsjetnik teme" + liked_consolidated: "novi lajkovi" + post_approved: "post odobren" + membership_request_consolidated: "novi zahtjevi za članstvo" + reaction: "nova reakcija" + votes_released: "Glasanje je objavljeno" + upload_selector: + uploading: "Učitavanje" + processing: "Obrada prijenosa" + select_file: "Odaberite datoteku" + default_image_alt_text: slika + search: + sort_by: "Sortiraj po" + relevance: "Relevantnost" + latest_post: "Posljednje objave" + latest_topic: "Najnovija tema" + most_viewed: "Nagledanije" + most_liked: "Najviše se sviđa" + select_all: "Odaberi sve" + clear_all: "Obriši sve" + too_short: "Vaš je pojam za pretragu prekratak." + open_advanced: "Otvorite napredno pretraživanje" + clear_search: "Očisti pretragu" + sort_or_bulk_actions: "Sortiranje ili skupni odabir rezultata" + result_count: + one: "%{count} rezultat za%{term}" + few: "%{count}%{plus} rezultata za%{term}" + other: "%{count}%{plus} rezultata za%{term}" + title: "Pretraži" + full_page_title: "Pretraži" + no_results: "Nema rezultata" + no_more_results: "Nema više rezultata." + post_format: "#%{post_number} od %{username}" + results_page: "Rezultati pretraživanja za '%{term}'" + more_results: "Postoji još rezultata pretrage. Molimo suzite kriterij pretrage." + cant_find: "Ne možete pronaći ono što tražite?" + start_new_topic: "Možda započeti novu temu?" + or_search_google: "Ili umjesto toga pokušajte pretraživati s Googleom:" + search_google: "Pokušajte umjesto toga pretraživati s Googleom:" + search_google_button: "Google" + search_button: "Pretraživanje" + search_term_label: "unesite ključnu riječ za pretraživanje" + categories: "Kategorije" + tags: "Oznake" + in: "u" + in_this_topic: "u ovoj temi" + in_this_topic_tooltip: "prijeđite na pretraživanje svih tema" + in_messages: "u porukama" + in_messages_tooltip: "prijeđite na pretraživanje regularnih tema" + in_topics_posts: "u svim temama i postovima" + enter_hint: "ili pritisnite Enter" + in_posts_by: "u objavama od %{username}" + browser_tip: "%{modifier} + f" + browser_tip_description: "ponovno za korištenje izvornog pretraživanja preglednika" + recent: "Nedavna pretraživanja" + clear_recent: "Obriši nedavna pretraživanja" + type: + default: "Teme/objave" + users: "Korisnici" + categories: "Kategorije" + categories_and_tags: "Kategorije/oznake" + context: + user: "Pretražite članke od @%{username}" + category: "Pretražite kategoriju #%{category}" + tag: "Pretraživanje oznake #%{tag}" + topic: "Pretražite ovu temu" + private_messages: "Pretražite poruke" + tips: + category_tag: "filteri po kategoriji ili oznaci" + author: "filteri po autoru objave" + in: "filteri prema metapodacima (npr. u:naslov, u:osobno, u:prikvačeno)" + status: "filteri prema statusu teme" + full_search: "pokreće pretraživanje cijele stranice" + full_search_key: "%{modifier} + Enter" + advanced: + title: Napredni filtri + posted_by: + label: Objavio + aria_label: Filtrirajte prema autoru objave + in_category: + label: Kategorizirano + in_group: + label: U grupi + with_badge: + label: Sa značkom + with_tags: + label: Označeno + aria_label: Filtrirajte pomoću oznaka + filters: + label: Vrati samo teme/postove ... + title: Usklađivanje samo u naslovu + likes: Sviđalo mi se + posted: Objavio sam u + created: Stvorio/la sam + watching: Gledam + tracking: Pratim + private: U mojim porukama + bookmarks: Označio/la sam + first: su prva objava + pinned: su prikvačeni + seen: Čitao/la sam + unseen: Nisam čitao/la. + wiki: su wiki + images: uključi slike + all_tags: Sve gore navedene oznake + statuses: + label: Gdje teme + open: su otvoreni + closed: su zatvoreni + public: su javni + archived: su arhivirani + noreplies: imaju nula odgovora + single_user: sadrži jednog korisnika + post: + count: + label: Postovi + min: + placeholder: minimum + aria_label: filtrirati prema minimalnom broju postova + max: + placeholder: maksimum + aria_label: filtrirajte prema maksimalnom broju postova + time: + label: Objavljeno + aria_label: Filtrirajte prema datumu objave + before: prije + after: poslije + views: + label: Pregledi + min_views: + placeholder: minimum + aria_label: filtrirati prema minimalnom broju pregleda + max_views: + placeholder: maksimum + aria_label: filtrirajte prema maksimalnom broju pregleda + additional_options: + label: "Filtrirajte prema broju postova i pregledima teme" + hamburger_menu: "izbornik" + new_item: "novo" + go_back: "idi natrag" + not_logged_in_user: "stranica korisnika sa sažetkom trenutnih aktivnosti i postavki" + current_user: "idi na korisničku stranicu" + view_all: "pogledaj sve %{tab}" + topics: + new_messages_marker: "posljednji posjet" + bulk: + select_all: "Odaberi sve" + clear_all: "Očistiti sve" + unlist_topics: "Poništite popis tema" + relist_topics: "Poništite popis tema" + reset_bump_dates: "Resetiraj datum obnove" + defer: "Odloži" + delete: "Obriši teme" + dismiss: "Skloni" + dismiss_read: "Skloni sve nepročitano" + dismiss_read_with_selected: + one: "Odbaci %{count} nepročitano" + few: "Odbaci %{count} nepročitanih" + other: "Odbaci %{count} nepročitanih" + dismiss_button: "Skloni..." + dismiss_button_with_selected: + one: "Odbaci (%{count})…" + few: "Odbaci (%{count})…" + other: "Odbaci (%{count})…" + dismiss_tooltip: "Skloni sve nove objave ili prestani pratiti teme" + also_dismiss_topics: "Prestanite pratiti ove teme kako se više nikada ne bi pokazale kao nepročitane za mene" + dismiss_new: "Odbaci novo" + dismiss_new_with_selected: + one: "Odbaci novo (%{count})" + few: "Odbaci novo (%{count})" + other: "Odbaci novo (%{count})" + toggle: "uključi/isključi grupni odabir tema" + actions: "Grupne aktivnosti" + change_category: "Postavi kategoriju..." + close_topics: "Zatvori teme" + archive_topics: "Arhiviraj teme" + move_messages_to_inbox: "Premjesti u Inbox" + notification_level: "Obavijesti..." + change_notification_level: "Promijenite razinu obavijesti" + choose_new_category: "Odaberite novu kategoriju za teme:" + selected: + one: "Odabrali ste %{count} temu." + few: "Odabrali ste %{count} tema." + other: "Odabrali ste %{count} tema." + change_tags: "Zamijeni oznake" + append_tags: "Dodajte oznake" + choose_new_tags: "Odaberite nove oznake za ove teme:" + choose_append_tags: "Odaberite nove oznake za dodavanje ovim temama:" + changed_tags: "Oznake tih tema su promijenjene." + remove_tags: "Ukloni sve oznake" + confirm_remove_tags: + one: "Sve oznake bit će uklonjene iz ove teme. Jesi li siguran?" + few: "Sve oznake %{count} bit će uklonjene iz ove teme. Jeste li sigurni?" + other: "Sve oznake bit će uklonjene iz %{count} temi. Jeste li sigurni?" + progress: + one: "Napredak: %{count} tema" + few: "Napredak: %{count} tema" + other: "Napredak: %{count} teme" + none: + unread: "Nemate nepročitanih tema." + unseen: "Nemate nepročitanih tema." + new: "Nemate novih članaka." + read: "Niste još pročitali ni jednu temu." + posted: "Niste još objavljivali ni u jendoj temi." + latest: "Svi ste uhvaćeni!" + bookmarks: "Još nemate zabiljženih tema." + category: "Nema tema u %{category}." + top: "Nema glavnih tema." + educate: + new: '

Vaše nove teme će se pojaviti ovdje. Prema zadanim postavkama teme se smatraju novim i pokazat će indikator ako su stvorene u posljednja 2 dana.

Posjetite preferenci da biste to promijenili.

' + unread: "

Vaše nepročitane teme pojavljuju se ovdje.

Prema zadanim postavkama, teme se smatraju nepročitanima i prikazat će se nepročitane brojke 1 ako:

  • Izrađeno je temu
  • Odgovoreno na temu
  • Pročitajte temu više od 4 minute

Ili ako ste izričito postavili temu na Praćeni ili promatrani putem \U0001F514 u svakoj temi.

Posjetite preferenci da biste to promijenili.

" + bottom: + latest: "Nema više najnovijih tema." + posted: "Nema više objavljenih članaka." + read: "Nema više pročitanih tema." + new: "Nema više novih tema." + unread: "Nema više nepročitanih tema." + unseen: "Nema više nepročitanih tema." + category: "Nema više %{category} tema." + tag: "Nema više %{tag} tema." + top: "Nema više glavnih tema." + bookmarks: "U oznakama nema više spremljenih tema." + topic: + filter_to: + one: "%{count} objava u temi" + few: "%{count} objava u temi" + other: "%{count} objave u temi" + create: "Nova tema" + create_long: "Otvori novu temu" + open_draft: "Otvori skicu" + private_message: "Pokrenite poruku" + archive_message: + help: "Premjesite poruku u vašu arhivu" + title: "Arhiva" + move_to_inbox: + title: "Premjesti u ulaznu poštu" + help: "Premjestite poruku nazad u dolazni pretinac" + edit_message: + help: "Uredite prvu objavu u poruci" + title: "Izmijeni" + defer: + help: "Označi kao nepročitano" + title: "Odloži" + list: "Teme" + new: "nova tema" + unread: "nepročitano" + new_topics: + one: "%{count} nova tema" + few: "%{count} nove teme" + other: "%{count} nove teme" + unread_topics: + one: "%{count} nepročitana tema" + few: "%{count} nepročitanih tema" + other: "%{count} neprocitanih tema" + title: "Tema" + invalid_access: + title: "Tema je privatna" + description: "Žao nam je, nemate pristup temi!" + login_required: "Morate se prijaviti da vidite temu." + server_error: + title: "Očitavanje teme nije uspjelo" + description: "Žao nam je, nismo mogli očitat temu, moguće zbog problema s vezom. Molimo pokušajte ponovno. Ako problem usraje, javite nam." + not_found: + title: "Tema nije pronađena" + description: "Žao nam je, nismo mogli pronaći temu. Možda ju je moderator uklonio?" + unread_posts: + one: "Imate %{count} nepročitanih članaka u ovoj temi" + few: "Imate %{count} nepročitanih članaka u ovoj temi" + other: "Imate %{count} nepročitanih članaka u ovoj temi" + likes: + one: "Ima %{count} like u temi" + few: "Ima %{count} like-ova u temi" + other: "Ima %{count} like-ova u temi" + back_to_list: "Natrag na listu tema" + options: "Mogućnosti teme" + show_links: "pokaži poveznice u temi" + collapse_details: "Sažmi detalje teme" + expand_details: "proširite pojedinosti o temi" + read_more_in_category: "Želite čitati više? Pregledajte druge teme u %{catLink} ili %{latestLink}." + read_more: "Želite li čitati više? %{catLink} Ili %{latestLink}." + unread_indicator: "Još nijedan član nije pročitao zadnji post ove teme." + read_more_MF: "Tamo { UNREAD, plural, =0 {} one { je # nepročitano } few { su # nepročitanih } other { su # nepročitanih } } { NEW, plural, =0 {} one { {BOTH, select, true{i } false {je } other{}} # nova tema} few {} other { {BOTH, select, true{i } false {jesu } other{}} # nove teme} } preostalo, ili {CATEGORY, select, true {pregledajte ostale teme u {catLink}} false {{latestLink}} other {}}" + bumped_at_title_MF: "{FIRST_POST}: {CREATED_AT}\n{LAST_POST}: {BUMPED_AT}" + browse_all_categories: Pregledaj sve kategorije + browse_all_tags: Pregledajte sve oznake + view_latest_topics: pogledaj najnovije teme + suggest_create_topic: Spremni za započeti novi razgovor? + jump_reply_up: skoči na raniji odgovor + jump_reply_down: skoči na kasniji odgovor + deleted: "Tema je obrisana." + slow_mode_update: + title: "Usporeni način rada" + select: "Korisnici mogu objavljivati u ovoj temi samo jednom svaki put:" + description: "Kako bi promovirali promišljenu raspravu u brzim pokretnim ili spornim raspravama, korisnici moraju pričekati prije ponovnog objavljivanja u ovoj temi." + enable: "Omogući" + update: "Ažuriraj" + enabled_until: "Omogućeno do:" + remove: "Onemogući" + hours: "Sati:" + minutes: "Minute:" + seconds: "Sekunde:" + durations: + 10_minutes: "10 minuta" + 15_minutes: "15 minuta" + 30_minutes: "30 minuta" + 45_minutes: "45 minuta" + 1_hour: "1 sat" + 2_hours: "2 sata" + 4_hours: "4 sata" + 8_hours: "8 sati" + 12_hours: "12 sati" + 24_hours: "24 sata" + custom: "Prilagođeno trajanje" + slow_mode_notice: + duration: "Molimo pričekajte %{duration} između postova u ovoj temi" + topic_status_update: + title: "Timer teme" + save: "Postavi tajmer" + num_of_hours: "Broj sati:" + num_of_days: "Broj dana:" + remove: "Uklonite timer" + publish_to: "Objavi na:" + when: "Kada:" + time_frame_required: "Odaberite vremenski okvir" + min_duration: "Trajanje mora biti veće od 0" + max_duration: "Trajanje mora biti manje od 20 godina" + duration: "Trajanje" + publish_to_category: + title: "Raspored objavljivanja" + temp_open: + title: "Privremeno otvori" + auto_reopen: + title: "Automatsko otvaranje teme" + temp_close: + title: "Zatvori privremeno" + auto_close: + title: "Automatsko zatvaranje teme" + label: "Automatsko zatvaranje teme nakon:" + error: "Molimo unesite valjanu vrijednost" + based_on_last_post: "Nemoj zatvoriti dok posljednji članak nije ovoliko star." + auto_close_after_last_post: + title: "Automatsko zatvaranje teme nakon zadnjeg posta" + auto_delete: + title: "Automatsko brisanje teme" + auto_bump: + title: "Auto-Bump Tema" + reminder: + title: "Podsjeti me" + auto_delete_replies: + title: "Automatsko brisanje odgovora" + status_update_notice: + auto_open: "Ova će se tema automatski otvoriti %{timeLeft}." + auto_close: "Ova tema će se autmatski zatvoriti %{timeLeft}." + auto_publish_to_category: "Ova će tema biti objavljena u #%{categoryName} %{timeLeft}." + auto_close_after_last_post: "Ova tema će se zatvoriti %{duration} nakon zadnjeg odgovora." + auto_delete: "Ova će se tema automatski izbrisati %{timeLeft}." + auto_bump: "Ova će tema automatski biti postavljena na %{timeLeft}." + auto_reminder: "Podsjetit ćete se na ovu temu %{timeLeft}." + auto_delete_replies: "Odgovori na ovu temu automatski se brišu nakon %{duration}." + auto_close_title: "Postavke automatskog zatvaranja" + auto_close_immediate: + one: "Posljednji post u ovoj temi je star već %{count} sat, stoga će tema odmah biti zatvorena." + few: "Posljednji post u ovoj temi je star već %{count} sati, stoga će tema odmah biti zatvorena." + other: "Posljednji post u ovoj temi je star već %{count} sati, stoga će tema odmah biti zatvorena." + auto_close_momentarily: + one: "Posljednji post u ovoj temi je star već %{count} sat, stoga će tema odmah biti zatvorena." + few: "Zadnji post u temi star je već %{count} sati, tako da će se tema trenutno zatvoriti." + other: "Zadnji post u temi star je već %{count} sati, tako da će se tema trenutno zatvoriti." + timeline: + back: "Natrag" + back_description: "Vrati se na posljednju nepročitanu poruku" + replies_short: "%{current} / %{total}" + progress: + title: napredak teme + jump_prompt: "Skoči na..." + jump_prompt_of: + one: "od %{count} objava" + few: "od %{count} objava" + other: "od %{count} objava" + jump_prompt_long: "Skoči na..." + jump_prompt_to_date: "do danas" + jump_prompt_or: "ili" + notifications: + title: promijenite koliko često ćete biti obaviješteni o ovoj temi + reasons: + mailing_list_mode: "Imate način mailing liste omogućen, tako da ćete biti obaviješteni o odgovorima na ovu temu putem e-maila." + "3_10": "Primat ćete obavijesti jer gledate oznaku na ovu temu." + "3_10_stale": "Primit ćete obavijesti jer ste u prošlosti gledali oznaku na ovu temu." + "3_6": "Primati će te obavijesti jer promatrate ovu kategoriju." + "3_6_stale": "Dobit ćete obavijesti jer ste ovu kategoriju gledali u prošlosti." + "3_5": "Primat će te obavijesti jer ste automatski počeli promatrati temu." + "3_2": "Primat će te obavijest jer promatrate temu." + "3_1": "Primat će te obavijesti jer ste vi otvorili temu." + "3": "Primat će te obavijesti jer promatrate temu." + "2_8": "Vidjet ćete broj novih odgovora jer pratite ovu kategoriju." + "2_8_stale": "Vidjet ćete broj novih odgovora jer ste u prošlosti pratili ovu kategoriju." + "2_4": "Vidjet ćete broj novih odgovora jer ste objavili odgovor na ovu temu." + "2_2": "Vidjet ćete broj novih odgovora jer pratite ovu temu." + "2": 'Vidjet ćete broj novih odgovora jer ste pročitali ovu temu.' + "1_2": "Obavijestit ćete se ako netko spomene vaše @ime ili vam odgovori." + "1": "Obavijestit ćete se ako netko spomene vaše @ime ili vam odgovori." + "0_7": "Ignorirate sve obavijesti iz ove kategorije." + "0_2": "Ignorirate sve obavijesti iz ove teme." + "0": "Ignorirate sve obavijesti iz ove teme." + watching_pm: + title: "Promatrano" + description: "Biti ćete obaviještene o svim novim odgovorima u ovoj poruci i biti će prikazan broj novih odgovora." + watching: + title: "Promatrano" + description: "Bit ćete obaviješteni o svakom novom odgovoru u ovoj temi, a bit će prikazan i broj novih odgovora." + tracking_pm: + title: "Praćeno" + description: "Broj novih odgovora biti će prikazan za ovu poruku. Biti ćete objaviješteni ako vas netko spomene koristeći @ime ili vam odgovori." + tracking: + title: "Praćeno" + description: "Na ovu će temu biti prikazan broj novih odgovora. Obavijestit ćete se ako netko spomene vaše @ime ili vam odgovori." + regular: + title: "Normalno" + description: "Obavijestit ćete se ako netko spomene vaše @ime ili vam odgovori." + regular_pm: + title: "Normalno" + description: "Obavijestit ćete se ako netko spomene vaše @ime ili vam odgovori." + muted_pm: + title: "Utišano" + description: "Nikada nećete dobiti obavijesti o ovoj poruci." + muted: + title: "Utišano" + description: "Nikad nećete biti obaviješteni o bilo kojoj temi o ovoj temi, a ona se neće pojaviti zadnjim postovima." + actions: + title: "Akcije" + recover: "Vrati obrisanu temu" + delete: "Obriši temu" + open: "Otvori temu" + close: "Zatvori temu" + multi_select: "Odaberi objave..." + slow_mode: "Postavi spori način ..." + timed_update: "Podesi tajmer teme..." + pin: "Prikvači temu…" + unpin: "Otkvači temu" + unarchive: "Vrati temu iz arhiva" + archive: "Arhiviraj temu" + invisible: "Poništite popis tema" + visible: "Izlistaj temu" + reset_read: "Poništi podatke o pročitanom" + make_public: "Napravi javnu temu..." + make_private: "Izradite osobnu poruku" + reset_bump_date: "Poništi datum bump" + feature: + pin: "Zakvači temu" + unpin: "Otkvači temu" + pin_globally: "Zakvači temu globalno" + make_banner: "Napravite temu bannera" + remove_banner: "Ukloni istaknutu objavu" + reply: + title: "Odgovori" + help: "počni sastavljati odgovor za ovu temu" + share: + title: "Podijelite temu" + extended_title: "Podijelite vezu" + help: "dijelite poveznicu na ovu temu" + instructions: "Podijelite vezu na ovu temu:" + copied: "Kopirana veza do teme." + restricted_groups: + one: "Vidljivo samo članovima grupe: %{groupNames}" + few: "Vidljivo samo članovima grupa: %{groupNames}" + other: "Vidljivo samo članovima grupa: %{groupNames}" + invite_users: "Pozovi" + print: + title: "Ispis" + help: "Otvorite verziju ove teme za pisač" + flag_topic: + title: "Označi zastavicom" + help: "privatno označi ovu temu za pozornost, ili pošalji privatnu obavijest o njoj" + success_message: "Uspješno ste označili ovu temu zastavicom" + make_public: + title: "Pretvori u javnu temu" + choose_category: "Molimo odaberite kategoriju za javnu temu:" + feature_topic: + title: "Istakni ovu temu" + pin: "Neka se ova tema pojavi na vrhu kategorije %{categoryLink} do" + unpin: "Uklonite ovu temu s vrha kategorije %{categoryLink}." + unpin_until: "Uklonite ovu temu s vrha %{categoryLink} kategorije ili pričekajte %{until}." + pin_note: "Korisnici mogu otkvačiti temu pojedinačno za sebe." + pin_validation: "Za prikvačivanje ove teme potreban je datum." + not_pinned: "Nijedna tema nije prikvačena u %{categoryLink}." + already_pinned: + one: "Trenutno zakačene teme u %{categoryLink}: %{count}" + few: "Trenutno zakačenih tema u %{categoryLink}: %{count}" + other: "Tema trenutno prikvačenih u %{categoryLink}: %{count}" + pin_globally: "Neka se ova tema prikaže na vrhu svih popisa tema do" + confirm_pin_globally: + one: "Već imate %{count} globalno prikvačenih tema. Previše prikvačenih tema može biti teret za nove i anonimne korisnike. Jeste li sigurni da želite globalno zakačiti drugu temu?" + few: "Već imate %{count} globalno prikvačenih tema. Previše prikvačenih tema može biti teret za nove i anonimne korisnike. Jeste li sigurni da želite globalno zakačiti drugu temu?" + other: "Već imate %{count} globalno prikvačenih tema. Previše prikvačenih tema može biti teret za nove i anonimne korisnike. Jeste li sigurni da želite globalno zakačiti drugu temu?" + unpin_globally: "Uklonite ovu temu s vrha svih popisa tema." + unpin_globally_until: "Uklonite ovu temu s vrha svih popisa tema ili pričekajte do %{until}." + global_pin_note: "Korisnici mogu otkvačiti temu pojedinačno za sebe." + not_pinned_globally: "Ne postoje teme prikvačene na globalnoj razini." + already_pinned_globally: + one: "Trenutno zakačene teme na globalnoj razini: %{count}" + few: "Trenutno zakačenih tema na globalnoj razini: %{count}" + other: "Teme su trenutno prikvačene na globalnoj razini: %{count}" + make_banner: "Umetni temu kao istaknutu objavu na vrh svake stranice" + remove_banner: "Ukloni istaknutu objavu sa vrha svake stranice" + banner_note: "Korisnik može ukloniti istaknutu objavu. U određenom trenutku može biti istaknuta samo jedna objava." + no_banner_exists: "Ne postoji istaknuta objava" + banner_exists: "Trenutno je istaknuta objava.." + inviting: "Pozivanje..." + automatically_add_to_groups: "Ova pozivnica obuhvaća i pristup tim grupama:" + invite_private: + title: "Pozovite u Poruke" + email_or_username: "E-mail ili korisničko ime pozivanog" + email_or_username_placeholder: "e-mail adresa ili korisničko ime" + action: "Pozovi" + success: "Pozvali smo tog korisnika da sudjeluju u ovoj poruci" + success_group: "Pozvali smo tu grupu da sudjeluje u ovoj poruci." + error: "Žao nam je, dogodila se greške u pozivanju." + not_allowed: "Nažalost, tog korisnika nije moguće pozvati." + group_name: "ime grupe" + controls: "Kontrole teme" + invite_reply: + title: "Pozovite" + username_placeholder: "Korisničko ime" + action: "Pošalji pozivnicu" + help: "pozovite druge na ovu temu putem e-pošte ili obavijesti" + to_forum: "Poslat ćemo kratku e-poštu dopuštajući vašem prijatelju da se odmah pridruži klikom na vezu." + discourse_connect_enabled: "Unesite korisničko ime osobe koju želite pozvati u ovu temu." + to_topic_blank: "Unesite korisničko ime ili adresu e-pošte osobe koju želite pozvati na ovu temu." + to_topic_email: "Na adresu elektroničke pošte koju ste unijeli poslati ćemo pozivnicu kako bi vaš prijatelj mogao poslati odgovor." + to_topic_username: "Unijeli ste korisničko ime. Poslat ćemo obavijest s vezom koja će ih pozvati na ovu temu." + to_username: "Unesite korisničko ime osobe koju želite pozvati. Poslat ćemo obavijest s vezom koja ih poziva na ovu temu." + email_placeholder: "ime@primjer.com" + success_email: "Poslali smo pozivnicu na broj %{invitee}. Obavijestit ćemo vas kad pozivnica bude iskorištena. Provjerite karticu pozivnica na svojoj korisničkoj stranici da biste pratili vaše pozivnice." + success_username: "Pozvali smo tog korisnika da sudjeluje u ovoj temi." + error: "Žao nam je, nismo mogli pozvati tu osobu. Možda je već tu? (Pozivi su ograničeni)" + success_existing_email: "Korisnik s e-poštom %{emailOrUsername} već postoji. Pozvali smo tog korisnika da sudjeluje u ovoj temi." + login_reply: "Prijavite se za odgovoriti" + filters: + n_posts: + one: "%{count} objava" + few: "%{count} objava" + other: "%{count} objava" + cancel: "Uklonite filter" + move_to: + title: "Premjesti u" + action: "premjestiti u" + error: "Došlo je do pogreške prilikom premještanja postova." + split_topic: + title: "Prebaci u novu temu" + action: "prebaci u novu temu" + topic_name: "Naslov nove teme" + radio_label: "Nova tema" + error: "Dogodili se greška pri prebacivanju objava u novu temu." + instructions: + one: "Otvoriti će te novu temu u popunjenu s %{count} objava koje ste izabrali." + few: "Otvoriti će te novu temu u popunjenu s %{count} objava koje ste izabrali." + other: "Otvoriti će te novu temu u popunjenu s %{count} objava koje ste izabrali." + merge_topic: + title: "Prebaci u postojeću temu" + action: "prebaci u postoječu temu" + error: "Dogodili se greška pri prebacivanju objava u temu." + radio_label: "Postojeća tema" + instructions: + one: "Molimo odaberite temu u koju želite prebaciti %{count} objava." + few: "Molimo odaberite temu u koju želite prebaciti %{count} objava." + other: "Molimo odaberite temu u koju želite prebaciti %{count} objava." + move_to_new_message: + title: "Premjestite u novu poruku" + action: "premjestite u novu poruku" + message_title: "Naslov nove poruke" + radio_label: "Nova poruka" + participants: "Sudionici" + instructions: + one: "Kreirat ćete novu poruku i ispuniti je odabranom objavom." + few: "Kreirat ćete novu poruku i ispuniti je %{count} odabranim objavama." + other: "Kreirat ćete novu poruku i ispuniti je %{count} odbabranim objavama." + move_to_existing_message: + title: "Premjesti u postojeću poruku" + action: "premjesti u postojeću poruku" + radio_label: "Postojeća poruka" + participants: "Sudionici" + instructions: + one: "Molimo odaberite poruku u koju želite premjestiti ovu objavu." + few: "Molimo odaberite poruku u koju želite premjestiti ovih %{count} objava." + other: "Molimo odaberite poruku u koju želite premjestiti ovih %{count} objava." + merge_posts: + title: "Uredi odabrane objave" + action: "spoji odabrane objave" + error: "Dogodila se greška pri spajanju objava." + publish_page: + title: "Objavljivanje stranica" + publish: "Objavi" + description: "Kada je tema objavljena kao stranica, njezin se URL može dijeliti i prikazivat će se s prilagođenim stilom." + slug: "Puž" + public: "Javnost" + public_description: "Ljudi mogu vidjeti stranicu čak i ako je povezana tema privatna." + publish_url: "Vaša je stranica objavljena na:" + topic_published: "Vaša tema je objavljena na adresi:" + preview_url: "Vaša stranica će biti objavljena na adresi:" + invalid_slug: "Žao mi je, ne možete objaviti ovu stranicu." + unpublish: "Poništi objavljivanje" + unpublished: "Vaša stranica nije objavljena i više nije dostupna." + publishing_settings: "Postavke objavljivanja" + change_owner: + title: "Promijeni vlasnika" + action: "promijeni vlasništvo" + error: "Dogodila se greška u promjeni vlasništva nad ovom objavom." + placeholder: "korisničko ime novog vlasnika" + instructions: + one: "Molimo odaberite novog vlasnika za post @%{old_user}" + few: "Molimo odaberite novog vlasnika za %{count} postove od člana @%{old_user}" + other: "Molimo odaberite novog vlasnika za %{count} postove od člana @%{old_user}" + instructions_without_old_user: + one: "Molimo odaberite novog vlasnika za post" + few: "Molimo odaberite novog vlasnika za post @%{count}" + other: "Molimo odaberite novog vlasnika za post @%{count}" + change_timestamp: + title: "Promijeni vremensku oznaku ..." + action: "promijeni vremensku oznaku" + invalid_timestamp: "Vremenska oznaka ne može biti u budućnosti." + error: "Došlo je do pogreške prilikom promjene vremenske oznake teme." + instructions: "Odaberite novu vremensku oznaku teme. Objave u temi bit će ažurirane tako da imaju istu vremensku razliku." + multi_select: + select: "odaberi" + selected: "odabrano (%{count})" + select_post: + label: "odaberi" + title: "Dodajte post u odabir" + selected_post: + label: "odabran" + title: "Kliknite da biste uklonili post iz izbora" + select_replies: + label: "odaberi +odgovori" + title: "Dodajte post i sve njegove odgovore odabiru" + select_below: + label: "odaberite +ispod" + title: "Dodaj post i sve nakon toga na odabir" + delete: obriši odabrano + cancel: odustani od odabranog + select_all: odaberi sve + deselect_all: odustani od odabira + description: + one: Odabrali ste %{count} objava. + few: "Odabrali ste %{count} objava." + other: "Odabrali ste %{count} objava." + deleted_by_author_simple: "(temu izbrisao autor)" + post: + confirm_delete: "Jeste li sigurni da želite izbrisati ovaj post?" + quote_reply: "Citat" + quote_reply_shortcut: "Ili pritisnite q" + quote_edit: "Izmijeni" + quote_edit_shortcut: "Ili pritisnite e" + quote_share: "Podijelite" + edit_reason: "Razlog:" + post_number: "objava %{number}" + ignored: "Zanemareni sadržaj" + wiki_last_edited_on: "wiki zadnji put uređeno %{dateTime}" + last_edited_on: "post zadnji put uređen %{dateTime}" + reply_as_new_topic: "Odgovori u povezanoj temi" + reply_as_new_private_message: "Odgovorite novom porukom istim primateljima." + continue_discussion: "Nastavak rasrave iz %{postLink}:" + follow_quote: "idi na citiranu objavu" + show_full: "Prikaži cijelu objavu" + show_hidden: "Pogledajte zanemareni sadržaj." + deleted_by_author_simple: "(post izbrisao autor)" + collapse: "sakrij" + sr_collapse_replies: "Sažmi ugrađene odgovore" + sr_date: "Datum objave" + sr_expand_replies: + one: "Ovaj post ima %{count} odgovora. Kliknite za proširenje" + few: "Ovaj post ima %{count} odgovora. Kliknite za proširenje" + other: "Ovaj post ima %{count} odgovora. Kliknite za proširenje" + expand_collapse: "proširi/sruši" + sr_below_embedded_posts_description: "objavi #%{post_number} odgovora" + sr_embedded_reply_description: "odgovorite sa @%{username} na objavu broj%{post_number}" + locked: "član osoblja zaključao je ovaj post od uređivanja" + gap: + one: "pogledaj %{count} skriveni odgovor" + few: "pogledaj %{count} skrivenih odgovora" + other: "pogledaj %{count} skrivenih odgovora" + notice: + new_user: "Ovo je prva objava %{user} - poželimo mu dobrodošlicu u zajednicu." + returning_user: "Prošlo je dosta vremena otkad smo vidjeli %{user} — njihov posljednji post bio je %{time}." + unread: "Objava je nepročitana" + has_replies: + one: "%{count} Odgovor" + few: "%{count} Odgovora" + other: "%{count} Odgovora" + has_replies_count: "%{count}" + unknown_user: "(nepoznati/izbrisani korisnik)" + has_likes_title: + one: "Objava se sviđa %{count} osobi" + few: "%{count} se sviđa objava" + other: "%{count} se sviđa objava" + has_likes_title_only_you: "sviđa vam se ova objava" + has_likes_title_you: + one: "tebi i još %{count} se sviđa objava" + few: "tebi i još %{count} se sviđa objava" + other: "tebi i još %{count} se sviđa objava" + sr_post_like_count_button: + one: "Ovaj post se svidio %{count} osobi. Kliknite za pregled" + few: "Ovaj post se svidio %{count} osobi. Kliknite za pregled" + other: "Ovaj post se svidio %{count} ljudi. Kliknite za pregled" + sr_post_read_count_button: + one: "%{count} osoba je pročitalo ovaj post. Kliknite za pregled" + few: "%{count} osoba je pročitalo ovaj post. Kliknite za pregled" + other: "%{count} ljudi je pročitalo ovaj post. Kliknite za pregled" + filtered_replies_hint: + one: "Pogledajte ovaj post i njegov odgovor" + few: "Pogledajte ovaj post i %{count} odgovora" + other: "Pogledajte ovaj post i %{count} odgovora" + filtered_replies_viewing: + one: "Pregled %{count} odgovora na" + few: "Pregled %{count} odgovora na" + other: "Pregled %{count} odgovora na" + in_reply_to: "Učitaj nadređeni post" + view_all_posts: "Pogledajte sve postove" + errors: + create: "Žao nam je, dogodila se greška u stvaranju tvoje objave. Molimo pokušajte ponovo." + edit: "Žao nam je, dogodila se greška u izmjenjivanju tvoje objave. Molimo pokušajte ponovo." + upload: "Žao nam je, dogodila se greška pri učitavanju te datoteke. Molimo pokušajte ponovo." + file_too_large: "Nažalost, ta je datoteka prevelika (maksimalna veličina je %{max_size_kb}kb). Zašto svoju veliku datoteku ne biste prenijeli na vanjski servis poput imgur.com, a zatim zalijepili vezu?" + file_size_zero: "Žao nam je, izgleda da je nešto pošlo po zlu, datoteka koju pokušavate prenijeti je 0 bajtova. Molim te pokušaj ponovno." + file_too_large_humanized: "Nažalost, ta je datoteka prevelika (maksimalna veličina je %{max_size}). Zašto ne biste prenijeli svoju veliku datoteku na uslugu dijeljenja u oblaku, a zatim zalijepili vezu?" + too_many_uploads: "Žao nam je, možete učitati samo jednu datoteku od jednom." + too_many_dragged_and_dropped_files: + one: "Nažalost, istodobno možete prenijeti samo %{count} datoteka." + few: "Nažalost, istodobno možete prenijeti samo %{count} datoteka." + other: "Nažalost, istodobno možete prenijeti samo %{count} datoteka." + upload_not_authorized: "Nažalost, datoteka koju pokušavate prenijeti nije autorizirana (autorizirana proširenja: %{authorized_extensions})." + image_upload_not_allowed_for_new_user: "Žao nam je, novi korisnici ne mogu učitavati slike." + attachment_upload_not_allowed_for_new_user: "Žao nam je, novi korisnici ne mogu učitavati privitke." + attachment_download_requires_login: "Žao nam je, morate biti prijavljeni da biste preuzimali privitke." + cancel_composer: + confirm: "Što biste željeli učiniti sa svojim postom?" + discard: "Odbaci" + save_draft: "Spremi skicu za kasnije" + keep_editing: "Nastavite uređivati" + via_email: "ova objava stigla je preko e-maila" + via_auto_generated_email: "ovaj je post stigao putem automatski generirane e-pošte" + whisper: "ovu objavu potiho pošaljite moderatorima" + wiki: + about: "ova objava je zajednička wiki objava" + few_likes_left: "Hvala za dijeljenje ljubavi! Za danas vam je ostalo samo nekoliko lajkova." + controls: + reply: "počnite sa sastavljanjem odgovora na ovu objavu" + like: "Lajkuj objavu" + has_liked: "sviđa vam se ova objava" + read_indicator: "članovi koji su pročitali ovaj post" + undo_like: "poništi lajk" + edit: "izmijeni ovu objavu" + edit_action: "Izmijeni" + edit_anonymous: "Žao nam je, morate biti prijavljeni da bi ste izmjenili ovu objavu." + flag: "privatno označi objavu za pozornost ili pošalji privatnu obavijest o njoj" + delete: "obriši ovu objavu" + undelete: "poništi brisanje objave" + share: "podijeli poveznicu na ovu objavu" + more: "Više" + delete_replies: + confirm: "Želite li i izbrisati odgovore na ovaj post?" + direct_replies: + one: "Da, i %{count} direktan odgovor" + few: "Da, i %{count} direktanih odgovora" + other: "Da, i %{count} direktnih odgovora" + all_replies: + one: "Da, i %{count} odgovor" + few: "Da, i %{count} odgovora" + other: "Da, i svih %{count} odgovora" + just_the_post: "Ne, samo ovu objavu" + admin: "administratorske akcije na objavi" + permanently_delete: "Trajno izbriši" + permanently_delete_confirmation: "Jeste li sigurni da trajno želite izbrisati ovaj post? Nećete ga moći oporaviti." + wiki: "Učini zajedničkom, wiki objavom" + unwiki: "Ukloni status zajedničke objave" + convert_to_moderator: "Dodaj boje osoblja" + revert_to_regular: "Ukloni boje osoblja" + rebake: "Popravi HTML" + publish_page: "Objava stranica" + unhide: "Poništi skrivanje" + change_owner: "Promjena vlasništva..." + grant_badge: "Dodijelite značku ..." + lock_post: "Zaključaj post" + lock_post_description: "zabranite autoru uređivanje objave" + unlock_post: "Otključaj post" + unlock_post_description: "dopustite autoru uređivanje objave" + delete_topic_disallowed_modal: "Nemate dozvolu za brisanje ove teme. Ako stvarno želite da se obriše, pošaljite zastavu za pažnju moderatora zajedno s obrazloženjem." + delete_topic_disallowed: "nemate dozvolu za brisanje ove teme" + delete_topic_confirm_modal: + one: "Ova tema trenutno ima više od %{count} pregleda i možda je popularno odredište za pretraživanje. Jeste li sigurni da želite u potpunosti izbrisati ovu temu, umjesto da je uređujete kako biste je poboljšali?" + few: "Ova tema trenutno ima više od %{count} pregleda i možda je popularno odredište za pretraživanje. Jeste li sigurni da želite u potpunosti izbrisati ovu temu, umjesto da je uređujete kako biste je poboljšali?" + other: "Ova tema trenutno ima više od %{count} pregleda i možda je popularno odredište za pretraživanje. Jeste li sigurni da želite u potpunosti izbrisati ovu temu, umjesto da je uređujete kako biste je poboljšali?" + delete_topic_confirm_modal_yes: "Da, izbriši ovu temu" + delete_topic_confirm_modal_no: "Ne, zadrži ovu temu" + delete_topic_error: "Došlo je do pogreške prilikom brisanja ove teme" + delete_topic: "obriši temu" + add_post_notice: "Dodaj obavijest o osoblju..." + change_post_notice: "Promjena obavijesti o osoblju..." + delete_post_notice: "Izbriši obavijest osoblja" + remove_timer: "ukloni tajmer" + edit_timer: "uredi mjerač vremena" + actions: + people: + like: + one: "like-ao objavu" + few: "like-ao objavu" + other: "like-ao objavu" + read: + one: "pročitajte ovo" + few: "pročitajte ovo" + other: "pročitajte ovo" + like_capped: + one: "i %{count} drugima se svidjelo ovo" + few: "i %{count} drugima se svidjelo ovo" + other: "i %{count} drugima se svidjelo ovo" + read_capped: + one: "i %{count} je ovo pročitao/la" + few: "i %{count} drugih ovo su pročitali" + other: "i %{count} drugih ovo su pročitali" + sr_post_likers_list_description: "korisnika kojima se ovaj post svidio" + sr_post_readers_list_description: "korisnika koji čitaju ovaj post" + by_you: + off_topic: "Označili ste ovo kao van teme" + spam: "Označili ste ovo kao neželjeno" + inappropriate: "Označili ste ovo kao van neprikladno" + notify_moderators: "Označili ste ovo za moderatore" + notify_user: "Poslali ste poruku ovom korisniku" + delete: + confirm: + one: "Jeste li sigurni da želite izbrisati taj post?" + few: "Jeste li sigurni da želite izbrisati %{count}\" postova?" + other: "Jeste li sigurni da želite izbrisati te %{count} postove?" + merge: + confirm: + one: "Jeste li sigurni da želite spojiti taj post?" + few: "Jeste li sigurni da želite spojiti %{count} postova?" + other: "Jeste li sigurni da želite spojiti ovih %{count} objava?" + revisions: + controls: + first: "Prva revizija" + previous: "Prethodna revizija" + next: "Sljedeća revizija" + last: "Zadnja revizija" + hide: "Sakrij reviziju" + show: "Pokaži reviziju" + revert: "Vrati se na reviziju %{revision}" + edit_wiki: "Uredite Wiki" + edit_post: "Izmjeni objavu" + comparing_previous_to_current_out_of_total: "%{previous} %{icon} %{current} / %{total}" + displays: + inline: + title: "Prikaži izvedeni ispis s dodavanjima i oduzimanjima u liniji." + button: "HTML" + side_by_side: + title: "Prikaži izračunate odlazne podatke s razlikama nasuprot." + button: "HTML" + side_by_side_markdown: + title: "Prikaži razlike u sirovom izvoru jednu nasuprot druge." + button: "Sirovo" + raw_email: + displays: + raw: + title: "Prikažite neobrađenu e -poštu" + button: "Sirovo" + text_part: + title: "Prikaz tekstnog dijela e-pošte" + button: "Tekst" + html_part: + title: "Pokažite html dio e-pošte" + button: "HTML" + bookmarks: + create: "Stvori oznaku" + create_for_topic: "Napravite oznaku za temu" + edit: "Uredi oznaku" + edit_for_topic: "Uredi oznaku za temu" + created: "Stvoreno" + updated: "Ažurirano" + name: "Ime i prezime" + name_placeholder: "Čemu služi ova oznaka?" + set_reminder: "Podsjeti me" + options: "Mogućnosti" + actions: + delete_bookmark: + name: "Izbriši oznaku" + description: "Uklanja oznaku s vašeg profila i zaustavlja sve podsjetnike za nju" + edit_bookmark: + name: "Uredi oznaku" + description: "Uredite naziv oznake ili promijenite datum i vrijeme podsjetnika" + clear_bookmark_reminder: + name: "Očisti podsjetnik" + description: "Brisanje datuma i vremena podsjetnika" + pin_bookmark: + name: "Označi oznaku" + description: "Prikvačite oznaku. To će se pojaviti na vrhu vašeg popisa oznaka." + unpin_bookmark: + name: "Otkvačite oznaku" + description: "Otkvačite oznaku. Više se neće prikazivati na vrhu vašeg popisa oznaka." + filtered_replies: + viewing_posts_by: "Pregledavanje %{post_count} postova od" + viewing_subset: "Neki odgovori su sažeti" + viewing_summary: "Pregled sažetka ove teme" + post_number: "%{username}, post #%{post_number}" + show_all: "Prikaži sve" + share: + title: "Podijelite objavu %{post_number}" + instructions: "Podijeli poveznicu na ovu objavu:" + category: + none: "(nema kategorije)" + all: "Sve kategorije" + choose: "kategorija…" + edit: "Izmijeni" + edit_dialog_title: "Uredi: %{categoryName}" + view: "Prikaži teme u kategoriji" + back: "Natrag u kategoriju" + general: "Općenito" + settings: "Postavke" + topic_template: "Predložak teme" + tags: "Oznake" + tags_allowed_tags: "Ograniči ove oznake na ovu kategoriju:" + tags_allowed_tag_groups: "Ograniči ove grupe oznaka na ovu kategoriju:" + tags_placeholder: "(Izborno) popis dopuštenih oznaka" + tags_tab_description: "Oznake i grupe oznaka koje su gore navedene bit će dostupne samo u ovoj kategoriji i ostalim kategorijama koje ih također navode. Oni neće biti dostupni za upotrebu u drugim kategorijama." + tag_groups_placeholder: "(Izborno) popis dopuštenih grupa oznaka" + manage_tag_groups_link: "Upravljanje grupama oznaka" + allow_global_tags_label: "Dopustite i druge oznake" + required_tag_group: + description: "Zahtijevajte da nove teme imaju oznake iz grupa oznaka:" + delete: "Pobriši" + add: "Dodajte potrebnu grupu oznaka" + placeholder: "odaberite grupu oznaka..." + topic_featured_link_allowed: "Dopusti istaknute poveznice u ovoj kategoriji" + delete: "Obriši kategoriju" + create: "Nova kategorija" + create_long: "Stvorite novu kategoriju" + save: "Spremi kategoriju" + slug: "Kod kategorije" + slug_placeholder: "(Neobavezno) spojene-riječi za url" + creation_error: Dogodila se greška pri stvaranju kategorije. + save_error: Dogodila se greška pri spremanju kategorije. + name: "Ime kategorije" + description: "Opis" + logo: "Logo kategorije" + background_image: "Pozadinska slika kategorije" + badge_colors: "Boje značke" + background_color: "Pozadinska boja" + foreground_color: "Istaknuta boja." + name_placeholder: "Jedna ili dvije riječi" + color_placeholder: "bilo koja boja" + delete_confirm: "Jeste li sigurni da želite izbrisati ovu kategoriju?" + delete_error: "Dogodila se greška pri brisanju kategrije." + list: "Popis kategorija" + no_description: "Molimo dodajte opis ovoj kategoriji." + change_in_category_topic: "Izmjena opisa" + already_used: "Ova boja se koristi u drugoj kategoriji." + security: "Sigurnost" + security_add_group: "Dodajte grupu" + permissions: + group: "Grupa" + see: "Vidi" + reply: "Odgovor" + create: "Kreiraj" + no_groups_selected: "Nijedna grupa nije dobila pristup; ova će kategorija biti vidljiva samo osoblju." + everyone_has_access: 'Ova je kategorija javna, svi mogu vidjeti, odgovarati i stvarati postove. Da biste ograničili dopuštenja, uklonite jedno ili više dopuštenja dodijeljenih grupi "svi".' + toggle_reply: "Uključi / isključi dozvolu za odgovor" + toggle_full: "Uključi / isključi dozvolu za stvaranje" + inherited: 'Ovo je dopuštenje naslijeđeno od "svih"' + special_warning: "Upozorenje: Ova je kategorija unaprijed postavljena kategorija i sigurnosne postavke ne mogu se uređivati. Ako ne želite koristiti ovu kategoriju, izbrišite je umjesto da je prenamijenite." + uncategorized_security_warning: "Ova kategorija je posebna. Namijenjen je kao prostor za čuvanje tema koje nemaju kategoriju; ne može imati sigurnosne postavke." + uncategorized_general_warning: 'Ova kategorija je posebna. Koristi se kao zadana kategorija za nove teme za koje nije odabrana kategorija. Ako želite spriječiti ovo ponašanje i prisiliti odabir kategorije, onemogućite postavku ovdje. Ako želite promijeniti naziv ili opis, idite na Prilagodi / Tekstualni sadržaj.' + pending_permission_change_alert: "Niste dodali %{group} ovoj kategoriji; kliknite ovaj gumb da biste ih dodali." + images: "Slike" + email_in: "Prilagođena adresa dolaznog e-maila." + email_in_allow_strangers: "Prihvati emailove anonimnih korisnika bez računa" + email_in_disabled: "Objavljivanje novih tema preko emaila je onemogućeno postavkama stranice. Da to omogućite objavljivanje tema preko emaila" + email_in_disabled_click: 'omogućite "pošalji email u" opciju.' + mailinglist_mirror: "Kategorija odražava mailing listu" + show_subcategory_list: "Prikaži popis potkategorija iznad tema u ovoj kategoriji." + read_only_banner: "Tekst bannera kada korisnik ne može stvoriti temu u ovoj kategoriji:" + num_featured_topics: "Broj tema prikazanih na stranici kategorija:" + subcategory_num_featured_topics: "Broj istaknutih tema na stranici nadređene kategorije:" + all_topics_wiki: "Učinite nove teme wikijima prema zadanim postavkama" + allow_unlimited_owner_edits_on_first_post: "Dopusti neograničeno uređivanje vlasniku na prvom postu" + subcategory_list_style: "Stil popisa potkategorija:" + sort_order: "Popis tema Sortiraj po:" + default_view: "Zadani popis tema:" + default_top_period: "Zadani Period Top Tema" + default_list_filter: "Zadani filtar popisa:" + allow_badges_label: "Dozvoli dodjeljivanje značaka u ovoj kategoriji." + edit_permissions: "Izmjeni dozvole" + reviewable_by_group: "Osim osoblja, sadržaj u ovoj kategoriji mogu pregledati i:" + review_group_name: "ime grupe" + require_topic_approval: "Zahtijeva odobrenje moderatora za sve nove teme" + require_reply_approval: "Zahtijeva odobrenje moderatora za sve nove odgovore" + this_year: "ove godine" + position: "Položaj na stranici kategorija:" + default_position: "Zadana pozicija" + position_disabled: "Kategorije će u nizu biti prikazivane po razini aktivnosti. Za kontrolu redosljeda kategorija u popisu," + position_disabled_click: 'omogućite postavku "fiksa pozicija kategorija".' + minimum_required_tags: "Minimalan broj oznaka potrebnih u temi:" + default_slow_mode: 'Omogućite "Spori način rada" za nove teme u ovoj kategoriji.' + parent: "Nadkategorija" + num_auto_bump_daily: "Broj otvorenih tema koje se automatski mijenjaju dnevno:" + navigate_to_first_post_after_read: "Idite na prvi post nakon čitanja tema" + notifications: + title: "promijeniti razinu obavijesti za ovu kategoriju" + watching: + title: "Promatrano" + description: "Automatski ćete gledati sve teme u ovoj kategoriji. Bit ćete obaviješteni o svakom novom postu u svakoj temi i prikazat će se broj novih odgovora." + watching_first_post: + title: "Pratim prvi natpis" + description: "Bit ćete obaviješteni o novim temama u ovoj kategoriji, ali ne i odgovorima na teme." + tracking: + title: "Praćeno" + description: "Automatski ćete pratiti sve teme u ovoj kategoriji. Bit ćete obaviješteni ako netko spomene vaše @ime ili vam odgovori, a prikazat će se i broj novih odgovora." + regular: + title: "Normalno" + description: "Bit ćete obaviješteni ako netko spomene vaše @ime ili vam odgovori." + muted: + title: "Utišano" + description: "Nikada nećete biti obaviješteni o bilo čemu o novim temama u ovoj kategoriji i one se neće pojaviti najnovije." + search_priority: + label: "Prioritet pretraživanja" + options: + normal: "Normalno" + ignore: "Zanemari" + very_low: "Vrlo nisko" + low: "Nizak" + high: "Visok" + very_high: "Vrlo visoko" + sort_options: + default: "zadano" + likes: "Like-ova" + op_likes: "Broj lajkova orginalne objave" + views: "Pogledi" + posts: "Objave" + activity: "Aktivnosti" + posters: "Objavili" + category: "Kategorija" + created: "Stvoreno" + sort_ascending: "Uzlazno" + sort_descending: "Silazno" + subcategory_list_styles: + rows: "Redovi" + rows_with_featured_topics: "Redovi s istaknutim temama" + boxes: "Kutije" + boxes_with_featured_topics: "Kutije s istaknutim temama" + settings_sections: + general: "Općenito" + moderation: "Moderiranje" + appearance: "Izgled" + email: "Email" + list_filters: + all: "sve teme" + none: "nema podkategorija" + colors_disabled: "Ne možete odabrati boje jer imate stil kategorije nijedna." + flagging: + title: "Hvala što pomažete održavati našu zajednicu pristojnom." + action: "Označi objavu zastavicom" + take_action: "Poduzmi akciju..." + take_action_options: + default: + title: "Poduzmi akciju" + details: "Dođite odmah o praga za oznake, umjesto da čekate još oznaka od zajednice." + suspend: + title: "Suspendiraj korisnika" + details: "Dosegnite prag zastavice i suspendirajte korisnika" + silence: + title: "Ušuti korisnika" + details: "Dosegnite prag zastavice i utišajte korisnika" + notify_action: "Poruka" + official_warning: "Službeno upozorenje" + delete_spammer: "Obriši korisnika zbog nepoželjnih objava." + flag_for_review: "Red čekanja za pregled" + delete_confirm_MF: "Spremate se izbrisati {POSTS, plural, one {# post} few {# postova} other {# postova}} i {TOPICS, plural, one {# tema} few {# tema} other {# tema}} od ovog korisnika, uklonite njegov račun, blokirajte prijave s njegove IP adrese {ip_address}i dodajte njegovu e-adresu {email} na trajni popis blokiranih. Jeste li sigurni da je ovaj korisnik doista neželjena pošta?" + yes_delete_spammer: "Da, obriši korisnika." + ip_address_missing: "(N/A)" + hidden_email_address: "(skriveno)" + submit_tooltip: "Priložite privatnu zastavicu." + take_action_tooltip: "Dođite odmah o praga za oznake, umjesto da čekate još oznaka od zajednice." + cant: "Žao nam je, trenutno ne možete označiti ovu objavu zastavicom." + notify_staff: "Obavijesti osoblje privatno" + formatted_name: + off_topic: "Van teme je." + inappropriate: "Neprikladno je." + spam: "Nepoželjno je." + custom_placeholder_notify_user: "Budite specifični, konstruktivni i ljubazni." + custom_placeholder_notify_moderators: "Recite nam točno što vas brine, i gdje je moguće dajte relevantne poveznice i primjere." + custom_message: + at_least: + one: "unesite barem %{count} znak" + few: "unesite barem %{count} znakova" + other: "unesite barem %{count} znakova" + more: + one: "%{count} do kraja ..." + few: "%{count} do kraja ..." + other: "%{count} do kraja ..." + left: + one: "%{count} preostalih" + few: "%{count} preostalih" + other: "%{count} preostalih" + flagging_topic: + title: "Hvala što pomažete održavati našu zajednicu pristojnom." + action: "Označi temu zastavicom" + notify_action: "Poruka" + topic_map: + title: "Sažetak teme" + participants_title: "Često objavljuju" + links_title: "Popularne poveznice" + links_shown: "prikaži više poveznica..." + clicks: + one: "%{count} klik" + few: "%{count} klikova" + other: "%{count} klikova" + post_links: + about: "prikaži više poveznica za ovu objavu" + title: + one: "još %{count} " + few: "%{count} više" + other: "%{count} više" + topic_statuses: + warning: + help: "Ovo je službeno upozorenje." + bookmarked: + help: "Zabilježili ste ovu temu" + locked: + help: "Ova je tema zatvoreno, više ne prihvaća nove odgovore" + archived: + help: "Ova je tema arhivirana; zamrznuta je i ne može se mijenjati." + locked_and_archived: + help: "Ova je tema zatvorena i arhivirana; više ne prihvaća nove odgovore i ne može se mijenjati" + unpinned: + title: "Odkvačeno" + help: "Ova vam je tema odkvačena, prikazivati će se u standardnom redoslijedu." + pinned_globally: + title: "Globalno zakvačeno" + help: "Ova vam je tema zakvačena, prikazivati će se na vrhu svoje kategorije." + pinned: + title: "Zakvačeno" + help: "Ova vam je tema zakvačena, prikazivati će se na vrhu svoje kategorije." + unlisted: + help: "Ova je tema ne-popisana; neće se prikazivati na popisima tema i može joj se pristupiti samo preko direktne poveznice." + personal_message: + title: "Ova tema je osobna poruka" + help: "Ova tema je osobna poruka" + posts: "Objave" + pending_posts: + label: "Na čekanju" + label_with_count: "Na čekanju (%{count})" + posts_likes_MF: | + Ova tema ima {count, plural, one {# Odgovori} few {# Odgovori} other {# Odgovori}} {ratio, select, + low {s visokim kao da post omjer} + med {s vrlo visokim kao da post omjer} + high {s iznimno visokim kao da post omjer} + other {}} + original_post: "Originalna objava" + views: "Pogledi" + sr_views: "Poredaj po pregledima" + views_lowercase: + one: "pregledi" + few: "pogledi" + other: "pregledi" + replies: "Odgovori" + sr_replies: "Poredaj po odgovorima" + views_long: + one: "ova je tema pregledana %{count} puta" + few: "ova je tema pregledana %{count} puta" + other: "ova je tema pregledana %{count} puta" + activity: "Aktivnosti" + sr_activity: "Poredaj po aktivnosti" + likes: "Like-ova" + sr_likes: "Poredaj po lajkovima" + sr_op_likes: "Poredaj prema lajkovima originalne objave" + likes_lowercase: + one: "likeovi" + few: "sviđanja" + other: "sviđanja" + users: "Korisnici" + users_lowercase: + one: "korisnik" + few: "korisnika" + other: "korisnika" + category_title: "Kategorija" + history_capped_revisions: "Povijest, posljednjih 100 izmjena" + history: "Povijest" + changed_by: "od %{author}" + raw_email: + title: "Dolazna e-pošta" + not_available: "Nije dostupno!" + categories_list: "Popis kategorija" + filters: + with_topics: "%{filter} teme" + with_category: "%{filter} %{category} teme" + latest: + title: "Zadnje" + title_with_count: + one: "Najnovije (%{count})" + few: "Novi (%{count})" + other: "Novi (%{count})" + help: "teme s nedavnim objavama" + read: + title: "Pročitano" + help: "Teme koje ste pročitali, po redu po kojem ste ih zadnji put čitali" + categories: + title: "Kategorije" + title_in: "Kategorija - %{categoryName}" + help: "sve teme grupirane po kategorijama" + unread: + title: "Nepročitano" + title_with_count: + one: "Nepročitano (%{count})" + few: "Nepročitanih (%{count})" + other: "Nepročitanih (%{count})" + help: "teme koje trenutno promatrate ili pratite s nepročitanim objavama" + lower_title_with_count: + one: "%{count} nepročitano" + few: "%{count} nepročitano" + other: "%{count} nepročitano" + unseen: + title: "Neviđeno" + lower_title: "neviđeno" + help: "nove teme i teme koje trenutno gledate ili pratite s nepročitanim objavama" + new: + lower_title_with_count: + one: "%{count} novi" + few: "%{count} novih" + other: "%{count} novih" + lower_title: "novo" + title: "Novo" + title_with_count: + one: "Novi (%{count})" + few: "Novih (%{count})" + other: "Novih (%{count})" + help: "teme stvorene u zadnjih par dana" + posted: + title: "Moje objave" + help: "teme u kojima si objavljivao" + bookmarks: + title: "Zabilješke" + help: "teme koje ste zabilježili" + category: + title: "%{categoryName}" + title_with_count: + one: "%{categoryName} (%{count})" + few: "%{categoryName} (%{count})" + other: "%{categoryName} (%{count})" + help: "posljednje teme u %{categoryName} kategoriji" + top: + title: "Najaktivnije" + help: "najaktivnije teme u zadnjih godinu, mjesec, tjedan ili dan" + all: + title: "Ukupno" + yearly: + title: "Godišnje" + quarterly: + title: "Tromjesečno" + monthly: + title: "Mjesečno" + weekly: + title: "Tjedno" + daily: + title: "Dnevno" + all_time: "Ukupno" + this_year: "Godina" + this_quarter: "Tromjesječje" + this_month: "Mjesec" + this_week: "Tjedan" + today: "Danas" + other_periods: "vidi vrh:" + browser_update: 'Nažalost, vaš preglednik je prestar za rad na ovoj stranici. Molimo nadogradite svoj preglednik kako biste vidjeli bogat sadržaj, prijavili se i odgovorili.' + permission_types: + full: "Otvoriti / Odgovoriti / Vidjeti" + create_post: "Odgovoriti / Vidjeti" + readonly: "Vidjeti" + lightbox: + download: "preuzmi" + open: "izvorna slika" + previous: "Prethodno (tipka sa strelicom ulijevo)" + next: "Sljedeće (tipka sa strelicom desno)" + counter: "%curr% od %total%" + close: "Zatvori (Esc)" + content_load_error: 'Sadržaj nije moguće učitati.' + image_load_error: 'Slika nije mogla biti učitana.' + cannot_render_video: Ovaj videozapis nije moguće prikazati jer vaš preglednik ne podržava kodek. + keyboard_shortcuts_help: + shortcut_key_delimiter_comma: ", " + shortcut_key_delimiter_plus: "+" + shortcut_delimiter_or: "%{shortcut1} ili %{shortcut2}" + shortcut_delimiter_slash: "%{shortcut1}/%{shortcut2}" + shortcut_delimiter_space: "%{shortcut1} %{shortcut2}" + title: "Prečaći tipkovnice" + jump_to: + title: "Idi na" + home: "%{shortcut} Početak" + latest: "%{shortcut} Posljednje" + new: "%{shortcut} Novo" + unread: "%{shortcut} Nepročitano" + categories: "%{shortcut} Kategorije" + top: "%{shortcut} Na vrhu" + bookmarks: "%{shortcut} Označeno" + profile: "%{shortcut} Profil" + messages: "%{shortcut} Poruke" + drafts: "%{shortcut} Nacrti" + next: "%{shortcut} Sljedeća tema" + previous: "%{shortcut} Prethodna tema" + navigation: + title: "Navigacija" + jump: "%{shortcut} Idi na objavu broj" + back: "%{shortcut} Natrag" + up_down: "%{shortcut} Premjesti odabir ↑ ↓" + open: "%{shortcut} Otvori odabranu temu" + next_prev: "%{shortcut} Slijedeći/predhodni dio" + go_to_unread_post: "%{shortcut} Idi na prvi nepročitani post" + application: + title: "Aplikacija" + create: "%{shortcut} Otvori novu temu" + notifications: "%{shortcut} Otvori obavijesti" + hamburger_menu: "%{shortcut} Otvori izbornik" + user_profile_menu: "%{shortcut} Otvori korisnički izbornik" + show_incoming_updated_topics: "%{shortcut} Pokaži ažurirane teme" + search: "%{shortcut} Traži" + help: "%{shortcut} Otvori pomoć tipkovnice" + dismiss_new: "%{shortcut} Odbaci novo" + dismiss_topics: "%{shortcut} Odbaci teme" + log_out: "%{shortcut} Odjava" + composing: + title: "Sastavljanje" + return: "%{shortcut} Povratak skladatelju" + fullscreen: "%{shortcut} Kompozitor preko cijelog zaslona" + bookmarks: + title: "Označavanje" + enter: "%{shortcut} Spremi i zatvori" + later_today: "%{shortcut} Kasnije danas" + later_this_week: "%{shortcut} Kasnije ovog tjedna" + tomorrow: "%{shortcut} Sutra" + next_week: "%{shortcut} Sljedeći tjedan" + next_month: "%{shortcut} Sljedeći mjesec" + next_business_week: "%{shortcut} Početak sljedećeg tjedna" + next_business_day: "%{shortcut} Sljedeći radni dan" + custom: "%{shortcut} Prilagođeni datum i vrijeme" + none: "%{shortcut} Bez podsjetnika" + delete: "%{shortcut} Izbriši oznaku" + actions: + title: "Akcije" + bookmark_topic: "%{shortcut} Promjeni zabilježenu temu" + pin_unpin_topic: "%{shortcut} Zakvači/odkvači temu" + share_topic: "%{shortcut} Podijeli temu" + share_post: "%{shortcut} Podijeli objavu" + reply_as_new_topic: "%{shortcut} Odgovori u povezanoj temu" + reply_topic: "%{shortcut} Odgovori u temu" + reply_post: "%{shortcut} Odgovori na objavu" + quote_post: "%{shortcut} Citiraj objavu" + like: "%{shortcut} Sviđa se" + flag: "%{shortcut} Označi objavu zastavom" + bookmark: "%{shortcut} Zabilježi objavu" + edit: "%{shortcut} Uredi objavu" + delete: "%{shortcut} Izbriši objavu" + mark_muted: "%{shortcut} Utišaj objavu" + mark_regular: "%{shortcut} Standardna tema" + mark_tracking: "%{shortcut} Prati temu" + mark_watching: "%{shortcut} Promatraj temu" + print: "%{shortcut} Ispis teme" + defer: "%{shortcut} Odgodi temu" + topic_admin_actions: "%{shortcut} Otvorite administratorske akcije teme" + search_menu: + title: "Izbornik za pretraživanje" + prev_next: "%{shortcut} Premještanje odabira gore i dolje" + insert_url: "%{shortcut} Umetnite odabir u otvoreni skladatelj" + full_page_search: "%{shortcut} Pokreće pretraživanje cijele stranice" + badges: + earned_n_times: + one: "Dobio ovu značku %{count} put" + few: "Dobio ovu značku %{count} puta" + other: "Dobio ovu značku %{count} puta" + granted_on: "Odobreno %{date}" + others_count: "Ostali s ovom značkom (%{count})" + title: Značke + allow_title: "Ovu značku možete upotrijebiti kao naslov" + multiple_grant: "To možete zaraditi više puta" + badge_count: + one: "%{count} Značka" + few: "%{count} Značke" + other: "%{count} Značke" + more_badges: + one: "+ još %{count}" + few: "+%{count} više" + other: "+%{count} više" + granted: + one: "%{count} odobreno" + few: "%{count} odobreno" + other: "%{count} odobreno" + select_badge_for_title: Odaberi značku koju češ koristit kao titulu + none: "(ništa)" + successfully_granted: "Uspješno dodijeljeno %{badge} korisniku %{username}" + badge_grouping: + getting_started: + name: Početak + community: + name: Zajednica + trust_level: + name: Razina povjerenja + other: + name: Drugo + posting: + name: Objavljivanje + favorite_max_reached: "Ne možeš favorirati više značke." + favorite_max_not_reached: "Označi ovu značku kao omiljenu" + favorite_count: "%{count}/%{max} značke označene kao omiljene" + download_calendar: + title: "Preuzmite kalendar" + save_ics: "Preuzmite .ics datoteku" + save_google: "Dodaj u Google kalendar" + remember: "Ne pitaj me više" + remember_explanation: "(možete promijeniti ovu postavku u svojim korisničkim postavkama)" + download: "Preuzmi" + default_calendar: "Zadani kalendar" + default_calendar_instruction: "Odredite koji kalendar treba koristiti kada se datumi spremaju" + add_to_calendar: "Dodaj u kalendar" + google: "Google kalendar" + ics: "ICS" + tagging: + all_tags: "Sve oznake" + other_tags: "Ostale oznake" + selector_all_tags: "sve oznake" + selector_no_tags: "bez oznaka" + changed: "promijenjene oznake:" + tags: "Oznake" + choose_for_topic: "neobavezne oznake" + choose_for_topic_required: + one: "odaberite najmanje %{count} oznaka..." + few: "odaberite najmanje %{count} oznaka..." + other: "odaberite najmanje %{count} oznaka..." + choose_for_topic_required_group: + one: "odaberite oznaku %{count} iz '%{name}..." + few: "odaberite oznake %{count} iz '%{name}..." + other: "odaberite %{count} oznake iz '%{name}'..." + info: "Info" + default_info: "Ova oznaka nije ograničena ni na jednu kategoriju i nema sinonime." + staff_info: "Da biste dodali ograničenja, stavite ovu oznaku u grupu oznaka ." + category_restricted: "Ova je oznaka ograničena na kategorije kojima nemate dopuštenje za pristup." + synonyms: "Sinonimi" + synonyms_description: "Kada se koriste sljedeće oznake, one će biti zamijenjene s %{base_tag_name}." + save: "Spremite naziv i opis oznake" + tag_groups_info: + one: 'Ova oznaka pripada grupi "%{tag_groups}".' + few: "Ova oznaka pripada grupama \"%{tag_groups}\"." + other: "Ova oznaka pripada ovim grupama: %{tag_groups}." + category_restrictions: + one: "Može se koristiti samo u ovoj kategoriji:" + few: "Može se koristiti samo u ovoj kategorijama:" + other: "Može se koristiti samo u sljedećim kategorijama:" + edit_synonyms: "Uredite sinonime" + add_synonyms_label: "Dodajte sinonime:" + add_synonyms: "Dodaj" + add_synonyms_explanation: + one: "Bilo koje mjesto koje trenutno koristi ovu oznaku bit će promijenjeno tako da umjesto toga %{tag_name} Jeste li sigurni da želite napraviti ovu promjenu?" + few: "Bilo koje mjesto koje trenutno koristi ove oznake bit će promijenjene tako da umjesto toga koriste %{tag_name} Jeste li sigurni da želite napraviti ove promjene?" + other: "Bilo koje mjesto koje trenutno koristi ovu oznaku bit će promijenjeno tako da umjesto toga %{tag_name} Jeste li sigurni da želite napraviti ove promjene?" + add_synonyms_failed: "Sljedeće oznake nisu se mogle dodati kao sinonimi: %{tag_names}. Osigurajte da nemaju sinonime i da nisu sinonimi druge oznake." + remove_synonym: "Ukloni sinonim" + delete_synonym_confirm: 'Jeste li sigurni da želite izbrisati sinonim "%{tag_name}"?' + delete_tag: "Izbriši oznaku" + delete_confirm: + one: "Jeste li sigurni da želite izbrisati ovu oznaku i ukloniti je iz %{count} teme kojoj je dodijeljena?" + few: "Jeste li sigurni da želite izbrisati ovu oznaku i ukloniti ga iz %{count} tema je dodijeljena?" + other: "Jeste li sigurni da želite izbrisati ovu oznaku i ukloniti je iz %{count} tema kojima je dodijeljena?" + delete_confirm_no_topics: "Jeste li sigurni da želite izbrisati ovu oznaku?" + delete_confirm_synonyms: + one: "Njegov sinonim također će biti izbrisan." + few: "Njegovi %{count} sinonima također će biti izbrisani." + other: "Njegovi %{count} sinonimi također će biti izbrisani." + edit_tag: "Uredite naziv i opis oznake" + description: "Opis" + sort_by: "Sortiraj po:" + sort_by_count: "količina" + sort_by_name: "ime" + manage_groups: "Upravljanje grupama oznaka" + manage_groups_description: "Definirajte grupe za organiziranje oznaka" + upload: "Prijenos oznaka" + upload_description: "Prenesite CSV datoteku za skupno stvaranje oznaka" + upload_instructions: "Jedna u retku, po želji s grupom oznaka u formatu 'tag_name, tag_group'." + upload_successful: "Oznake su uspješno prenesene" + delete_unused_confirmation: + one: "%{count} bit će izbrisana: %{tags}" + few: "%{count} bit će izbrisana: %{tags}" + other: "%{count} tagovi će biti izbrisani: %{tags}" + delete_unused_confirmation_more_tags: + one: "%{tags} i još %{count}" + few: "%{tags} i %{count} više" + other: "%{tags} i %{count} više" + delete_no_unused_tags: "Nema neiskorištenih oznaka." + tag_list_joiner: ", " + delete_unused: "Izbriši neiskorištene oznake" + delete_unused_description: "Izbrišite sve tagove koji nisu dodijeljeni niti jednoj temi ili privatnoj poruci." + cancel_delete_unused: "Odustani" + filters: + without_category: "%{filter} %{tag} teme" + with_category: "%{filter} %{tag} tema u %{category}" + untagged_without_category: "%{filter} neoznačenih tema" + untagged_with_category: "%{filter} neoznačenih tema u %{category}" + notifications: + watching: + title: "Promatrano" + description: "Automatski ćete gledati sve teme s ovom oznakom. Bit ćete obaviješteni o svim novim postovima i temama, a pored broja prikazat će se i broj nepročitanih i novih postova." + watching_first_post: + title: "Pratim prvi natpis" + description: "Bit ćete obaviješteni o novim temama u ovoj oznaci, ali ne i o odgovorima na teme." + tracking: + title: "Praćenje" + description: "Automatski ćete pratiti sve teme s ovom oznakom. Uz temu će se pojaviti broj nepročitanih i novih postova." + regular: + title: "Stalni član" + description: "Biti će te obaviješteni ako vas netko spomene @imenom ili odgovori na vaš članak." + muted: + title: "Utišano" + description: "O ovoj temi nećete biti obaviješteni o ničemu o novim temama i neće se pojaviti na vašoj nepročitanoj kartici." + groups: + back_btn: "Natrag na sve oznake" + title: "Grupe oznaka" + about_heading: "Odaberite grupu oznaka ili izradite novu" + about_heading_empty: "Stvorite novu grupu oznaka da biste započeli" + about_description: "Grupe oznaka pomažu vam u upravljanju dozvolama za mnoge oznake na jednom mjestu." + new: "Nova grupa" + new_title: "Stvori novu grupu" + edit_title: "Uredi grupu oznaka" + tags_label: "Oznake u ovoj grupi" + parent_tag_label: "Nadređena oznaka" + parent_tag_description: "Oznake iz ove grupe mogu se koristiti samo ako je prisutna nadređena oznaka." + one_per_topic_label: "Ograničite jednu oznaku po temi iz ove grupe" + new_name: "Nova grupa oznaka" + name_placeholder: "Ime" + save: "Spremi" + delete: "Pobriši" + confirm_delete: "Jeste li sigurni da želite izbrisati ovu grupu oznaka?" + everyone_can_use: "Oznake mogu koristiti svi" + usable_only_by_groups: "Oznake su vidljive svima, ali samo ih sljedeće grupe mogu koristiti" + visible_only_to_groups: "Oznake su vidljive samo sljedećim grupama" + cannot_save: "Nije moguće spremiti grupu oznaka. Provjerite je li prisutna barem jedna oznaka, naziv grupe oznaka nije prazan i grupa je odabrana za dopuštenje oznaka." + tags_placeholder: "Pretražite ili izradite oznake" + parent_tag_placeholder: "Neobvezno" + select_groups_placeholder: "Odaberite grupe..." + disabled: "Označavanje je onemogućeno. " + topics: + none: + unread: "Nemate nepročitanih tema." + unseen: "Nemate neviđenih tema." + new: "Nemate novih članaka." + read: "Niste još pročitali ni jednu temu." + posted: "Niste još objavljivali ni u jendoj temi." + latest: "Nema najnovijih tema." + bookmarks: "Još nemate zabiljženih tema." + top: "Nema glavnih tema." + invite: + custom_message: "Neka vaša pozivnica bude malo osobnija tako što ćete napisati prilagođenu poruku." + custom_message_placeholder: "Unesite prilagođenu poruku" + approval_not_required: "Korisnik će biti automatski odobren čim prihvati ovu pozivnicu." + custom_message_template_forum: "Hej, trebali biste se pridružiti ovom forumu!" + custom_message_template_topic: "Hej, mislio sam da bi ti mogla prijati ova tema!" + forced_anonymous: "Zbog ekstremnog opterećenja, ovo se privremeno prikazuje svima." + forced_anonymous_login_required: "Stranica je pod velikim opterećenjem i trenutno je nije moguće učitati, pokušajte ponovo za nekoliko minuta." + footer_nav: + back: "Natrag" + forward: "Naprijed" + share: "Dijeli" + dismiss: "Skloni" + safe_mode: + enabled: "Omogućen je siguran način rada, da biste izašli iz sigurnog načina rada, zatvorite ovaj prozor preglednika" + image_removed: "(slika uklonjena)" + do_not_disturb: + title: "Ne ometaj za ..." + label: "Ne smetaj" + remaining: "%{remaining} preostalih" + options: + half_hour: "30 minuta" + one_hour: "1 sat" + two_hours: "2 sata" + tomorrow: "Do sutra" + custom: "Posebna" + set_schedule: "Postavite raspored obavijesti" + trust_levels: + names: + newuser: "novi korisnik" + basic: "korisnik" + member: "član" + regular: "stalni član" + leader: "vođa" + detailed_name: "%{level}: %{name}" + pick_files_button: + unsupported_file_picked: "Odabrali ste datoteku koja nije podržana. Podržane vrste datoteka — %{types}." + user_activity: + no_activity_title: "Još nema aktivnosti" + no_activity_body: "Dobrodošli u našu zajednicu! Ovdje ste potpuno novi i još niste sudjelovali u raspravama. Kao prvi korak, posjetite Top ili kategorije i samo počnite čitati! Odaberite %{heartIcon} na objavama koje vam se sviđaju ili o kojima želite saznati više. Dok budete sudjelovali, vaša će aktivnost biti navedena ovdje." + no_activity_others: "Nema aktivnosti." + no_replies_title: "Još niste odgovorili ni na jednu temu" + no_replies_others: "Nema odgovora." + no_drafts_title: "Niste pokrenuli nikakve skice" + no_drafts_body: "Niste baš spremni za objavu? Automatski ćemo spremiti novu skicu i navesti je ovdje kad god počnete sastavljati temu, odgovor ili osobnu poruku. Odaberite gumb za odustajanje da biste odbacili ili spremili skicu za nastavak kasnije." + no_likes_title: "Još vam se nije svidjela nijedna tema" + no_likes_body: "Sjajan način da uskočite i počnete pridonositi je da počnete čitati razgovore koji su se već vodili i odaberete %{heartIcon} na objavama koje vam se sviđaju!" + no_likes_others: "Nema likeanih objava." + no_topics_title: "Još niste pokrenuli nijednu temu" + no_topics_title_others: "%{username} još nije pokrenuo nijednu temu" + no_read_topics_title: "Još niste pročitali nijednu temu" + no_read_topics_body: "Kada počnete čitati rasprave, ovdje ćete vidjeti popis. Da biste počeli čitati, potražite teme koje vas zanimaju u Top ili kategorije ili pretražite po ključnoj riječi %{searchIcon}" + no_group_messages_title: "Nisu pronađene grupne poruke" + topic_entrance: + sr_jump_top_button: "Skoči na prvi post" + sr_jump_bottom_button: "Skoči na zadnji post" + fullscreen_table: + expand_btn: "Proširite tablicu" + second_factor_auth: + redirect_after_success: "Provjera autentičnosti drugog faktora je uspješna. Preusmjeravanje na prethodnu stranicu…" + sidebar: + unread_count: "%{count} nepročitano" + new_count: "%{count} novi" + sections: + messages: + header_link_text: "Poruke" + links: + inbox: "Primljeno" + sent: "Poslano" + new: "Novo" + unread: "Nepročitano" + archive: "Arhiva" + tags: + header_link_title: "sve oznake" + header_link_text: "Oznake" + categories: + header_link_title: "sve kategorije" + header_link_text: "Kategorije" + topics: + header_link_title: "naslovnica" + header_link_text: "Teme" + header_action_title: "otvori novu temu" + links: + everything: + content: "Sve" + title: "Sve teme" + tracked: + content: "Praćeno" + title: "Sve praćene teme" + bookmarked: + content: "Označeno" + title: "Sve označene teme" + my_posts: + content: "Moje objave" + admin_js: + type_to_filter: "upiši za filtriranje" + admin: + title: "Administrator" + moderator: "Moderator" + tags: + remove_muted_tags_from_latest: + always: "uvijek" + only_muted: "kada se koristi samostalno ili s drugim prigušenim oznakama" + never: "nikad" + reports: + title: "Popis dostupnih izvještaja" + dashboard: + title: "Nadzorna ploča" + last_updated: "Nadzorna ploča ažurirana:" + discourse_last_updated: "Ažuriran Discourse:" + version: "Verzija" + up_to_date: "Imate trenutnu verziju!" + critical_available: "Dostupna je kritična dopuna." + updates_available: "Dopune su dostupne." + please_upgrade: "Molimo nadogradite!" + no_check_performed: "Nije učinjena provjera za nadogradnje. Provjerite radi li pomoćnik." + stale_data: "Nije dugo učinjena provjera za nadogradnje. Provjerite radi li pomoćnik." + version_check_pending: "Čini se da ste nedavno nadogradili. Fantastično!" + installed_version: "Instalirano" + latest_version: "Posljednje" + problems_found: "Nekoliko savjeta na temelju vaših trenutnih postavki web mjesta" + new_features: + title: "\U0001F381 Nove značajke" + dismiss: "Skloni" + learn_more: "Saznajte više" + last_checked: "Zadnje provjereno" + refresh_problems: "Osviježi" + no_problems: "Nisu pronađeni problemi." + moderators: "Moderatori:" + admins: "Admini:" + silenced: "Utišano:" + suspended: "Suspendirani:" + private_messages_short: "Poruke" + private_messages_title: "Poruke" + mobile_title: "Mobilni" + space_used: "%{usedSize} korišteno" + space_used_and_free: "%{usedSize} (%{freeSize} dostupno)" + uploads: "Prijenosi" + backups: "Sigurnosne kopije" + backup_count: + one: "%{count} sigurnosna kopija na %{location}" + few: "%{count} sigurnosna kopija na %{location}" + other: "%{count} sigurnosnih kopije na %{location}" + lastest_backup: "Najnovije: %{date}" + traffic_short: "Promet" + traffic: "Aplikacijski internetski upiti" + page_views: "Pregledi stranica" + page_views_short: "Pregledi stranica" + show_traffic_report: "Prikaži detaljni izvještaj o prometu" + community_health: Zdravlje zajednice + moderators_activity: Aktivnost moderatora + whats_new_in_discourse: Što ima novo u Discourse? + activity_metrics: Metrika aktivnosti + all_reports: "Sva izvješća" + general_tab: "Općenito" + moderation_tab: "Moderiranje" + security_tab: "Sigurnost" + reports_tab: "Izvještaji" + report_filter_any: "bilo koji" + disabled: Onemogućeno + timeout_error: Žao nam je, upit traje predugo, odaberite kraći interval + exception_error: Nažalost, dogodila se pogreška prilikom izvršavanja upita + too_many_requests: Ovu ste radnju izvršili previše puta. Pričekajte prije ponovnog pokušaja. + not_found_error: Nažalost, ovo izvješće ne postoji + filter_reports: Filtrirajte izvješća + reports: + trend_title: "%{percent} promjena. Trenutno %{current}, bilo je %{prev} u prethodnom razdoblju." + today: "Danas" + yesterday: "Jučer" + last_7_days: "Zadnjih 7" + last_30_days: "Zadnjih 30" + all_time: "Oduvijek" + 7_days_ago: "Prije 7 dana" + 30_days_ago: "Prije 30 dana" + all: "Sve" + view_table: "tablica" + view_graph: "grafikon" + refresh_report: "Osviježi prijave" + daily: Dnevno + monthly: Mjesečno + weekly: Tjedno + dates: "Datumi (UTC)" + groups: "Sve grupe" + disabled: "Ovo je izvješće onemogućeno" + totals_for_sample: "Ukupno za uzorak" + average_for_sample: "Prosjek za uzorak" + total: "Ukupno vrijeme" + no_data: "Nema podataka za prikaz." + trending_search: + more: 'Dnevnici pretraživanja' + disabled: 'Izvješće o pretraživanju u trendu je onemogućeno. Omogućite upita za pretraživanje dnevnika za prikupljanje podataka.' + average_chart_label: Prosječno + filters: + file_extension: + label: Proširenje datoteke + group: + label: Grupa + category: + label: Kategorija + include_subcategories: + label: "Uključi podkategorije" + groups: + new: + title: "Nova grupa" + create: "Kreiraj" + name: + too_short: "Naziv grupe je prekratak" + too_long: "Naziv grupe je predugačak" + checking: "Provjera dostupnosti naziva grupe ..." + available: "Dostupan je naziv grupe" + not_available: "Naziv grupe nije dostupan" + blank: "Polje za naziv grupe ne može biti prazno" + manage: + interaction: + email: Email + incoming_email: "Prilagođena dolazna adresa e-pošte" + incoming_email_placeholder: "unesite adresu e-pošte" + visibility: Vidljivost + visibility_levels: + title: "Tko može vidjeti ovu grupu?" + public: "Svi" + logged_on_users: "Prijavljeni korisnici" + members: "Vlasnici grupa, članovi i moderatori" + staff: "Vlasnici i moderatori grupa" + owners: "Vlasnici grupe" + description: "Administratori mogu vidjeti sve grupe." + members_visibility_levels: + title: "Tko može vidjeti članove ove grupe?" + description: "Administratori mogu vidjeti članove svih grupa. Flair je vidljiv svim korisnicima." + publish_read_state: "U grupnim porukama objavite stanje čitanja" + membership: + automatic: Automatska + trust_levels_title: "Razina povjerenja dodjeljenja novim članovima:" + effects: Efekti + trust_levels_none: "ništa" + automatic_membership_email_domains: "Korisnici koji se registriraju s email domenom koja točno odgovora jednoj na ovom popisu biti će automatski dodani u ovu grupu:" + automatic_membership_user_count: + one: "%{count} korisnik ima nove domene e-pošte i bit će dodan u grupu." + few: "%{count} korisnika ima nove domene e-pošte i bit će dodan u grupu." + other: "%{count} korisnici imaju nove domene e-pošte i bit će dodani u grupu." + automatic_membership_associated_groups: "Korisnici koji su članovi grupe na ovdje navedenoj usluzi automatski će se dodati u ovu grupu kada se prijave s uslugom." + primary_group: "Automatski postavi kao primarnu grupu" + name_placeholder: "Ime grupe, bez razmaka, ista pravila kao za korisničko ime" + primary: "Primarna grupa" + no_primary: "(nema primarne grupe)" + title: "Grupe" + edit: "Izmijeni grupe" + refresh: "Osviježi" + about: "Ovdje izmijenite svoje članstvo u grupama i imena grupa" + group_members: "Članovi grupe" + delete: "Obriši" + delete_confirm: "Obriši ovu grupu?" + delete_with_messages_confirm: + one: "Brisanjem ove grupe %{count} poruka ostati bez roditelja, članovi grupe više joj neće imati pristup.

Jeste li sigurni?" + few: "Brisanjem ove grupe %{count} poruka ostati bez roditelja, članovi grupe više joj neće imati pristup.

Jeste li sigurni?" + other: "Brisanjem ove grupe %{count} poruka ostati bez roditelja, članovi grupe im više neće imati pristup.

Jeste li sigurni?" + delete_failed: "Nemoguće obrisati grupu. Ako je ovo automatska grupa, ne možete ju obrisati." + delete_automatic_group: Ovo je automatska grupa i ne može se izbrisati. + delete_owner_confirm: "Ukloniti vlasničku privilegiju za \"%{username}\"?" + add: "Dodaj" + custom: "Posebna" + automatic: "Automatska" + default_title: "Zadani naslov" + default_title_description: "će se primijeniti na sve korisnike u grupi" + group_owners: Vlasnici + add_owners: Dodajte vlasnike + none_selected: "Odaberite grupu za početak" + no_custom_groups: "Stvaranje nove prilagođene grupe" + api: + generate_master: "Generiraj glavni API ključ" + none: "Nema aktivnih API ključeva" + user: "Korisnik" + title: "API" + key: "Ključ" + created: Stvoreno + updated: Ažurirano + last_used: Posljednji put korišteno + never_used: (nikada) + generate: "Generiraj" + undo_revoke: "Poništi opoziv" + revoke: "Povuci" + all_users: "Svi korisnici" + active_keys: "Aktivni API ključevi" + manage_keys: Upravljanje ključevima + show_details: Detalji + description: Opis + no_description: (bez opisa) + all_api_keys: Svi API ključevi + user_mode: Korisnička razina + scope_mode: Opsezi + impersonate_all_users: Predstavljajte se kao bilo koji korisnik + single_user: "Jedan korisnik" + user_placeholder: Unesite korisničko ime + description_placeholder: Za što će se koristiti ovaj ključ? + save: Spremi + new_key: Novi API ključ + revoked: Opoziv + delete: Trajno izbriši + not_shown_again: Ovaj ključ se više neće prikazati. Provjerite jeste li uzeti kopiju prije nastavka. + continue: Nastavi + scopes: + description: | + Prilikom korištenja opsega, API ključ možete ograničiti na određeni skup krajnjih točaka. + Također možete definirati koji će parametri biti dopušteni. Zarezima odvojite više vrijednosti. + title: Opsezi + granular: Granularni + read_only: Samo za čitanje + global: Globalno + global_description: API ključ nema ograničenja i sve krajnje točke su dostupne. + resource: Resurs + action: Akcija + allowed_parameters: Dopušteni parametri + optional_allowed_parameters: Dopušteni parametri (izborno) + any_parameter: (bilo koji parametar) + allowed_urls: Dopušteni URL -ovi + descriptions: + global: + read: Ograničite API ključ na krajnje točke samo za čitanje. + topics: + read: Pročitajte temu ili određeni post u njoj. RSS je također podržan. + write: Napravite novu temu ili objavite postojeću. + update: Ažurirajte temu. Promijenite naslov, kategoriju, oznake itd. + read_lists: Čitajte popise tema kao što su vrhunski, novi, najnoviji itd. RSS je također podržan. + posts: + edit: Uredite bilo koji post ili određeni. + categories: + list: Dohvatite popis kategorija. + show: Dohvati jednu kategoriju prema ID-u. + uploads: + create: Prenesite novu datoteku ili pokrenite izravni prijenos pojedinačnih ili više dijelova na vanjsku pohranu. + users: + bookmarks: Navedite oznake korisnika. Vraća podsjetnike na oznake prilikom korištenja ICS formata. + sync_sso: Sinkronizirajte korisnika pomoću DiscourseConnect. + show: Pribavite informacije o korisniku. + check_emails: Popis e-adresa korisnika. + update: Ažurirajte informacije korisničkog profila. + log_out: Odjavite se sve sesije za korisnika. + anonymize: Anonimizirajte korisničke račune. + delete: Izbrišite korisničke račune. + list: Dobijte popis korisnika. + email: + receive_emails: Kombinirajte ovaj opseg s prijemnikom pošte za obradu dolaznih e-poruka. + badges: + create: Izradite novu značku. + show: Pribavite informacije o znački. + update: Ažuriraj značku. + delete: Izbriši značku. + list_user_badges: Popis korisničkih znački. + assign_badge_to_user: Dodijelite značku korisniku. + revoke_badge_from_user: Opozovi značku od korisnika. + wordpress: + publishing: Neophodno za značajke objavljivanja dodatka WP Discourse (obavezno). + commenting: Neophodan za značajke komentiranja dodatka WP Discourse. + discourse_connect: Neophodan za značajke dodatka WP Discourse DiscourseConnect. + utilities: Neophodno ako koristite dodatak WP Discourse Utilities. + web_hooks: + title: "Webhooks" + none: "Trenutno nema webhooks." + instruction: "Webhooks omogućuje Discourseu da obavještava vanjske usluge kada se na vašoj web lokaciji dogodi određeni događaj. Kada se pokrene webhook, POST zahtjev će poslati na navedene URL-ove." + detailed_instruction: "POST zahtjev će se poslati na navedeni URL kada se dogodi odabrani događaj." + new: "Novi Webhook" + create: "Kreiraj" + save: "Spremi" + destroy: "Pobriši" + description: "Opis" + controls: "Kontrole" + go_back: "Povratak na popis" + payload_url: "URL nosivosti" + payload_url_placeholder: "https://example.com/postreceive" + warn_local_payload_url: "Čini se da pokušavate postaviti webhook na lokalni url. Događaj dostavljen na lokalnu adresu može uzrokovati nuspojave ili neočekivana ponašanja. Nastaviti?" + secret_invalid: "Tajna ne smije imati praznih znakova." + secret_too_short: "Tajna treba sadržavati najmanje 12 znakova." + secret_placeholder: "Izborni niz koji se koristi za generiranje potpisa" + event_type_missing: "Morate postaviti barem jedan tip događaja." + content_type: "Vrsta sadržaja" + secret: "Tajna" + event_chooser: "Koji bi događaji trebali pokrenuti ovu web-dojavnicu?" + wildcard_event: "Pošalji mi sve." + individual_event: "Odaberite pojedinačne događaje." + verify_certificate: "Provjerite TLS certifikat o url nosivosti" + active: "Aktivno" + active_notice: "Detalje događaja dostavit ćemo kad se to dogodi." + categories_filter_instructions: "Relevantne web dojave pokrenut će se samo ako je događaj povezan s navedenim kategorijama. Ostavite prazno da biste pokrenuli web dojave za sve kategorije." + categories_filter: "Pokrenute kategorije" + tags_filter_instructions: "Relevantne web dojave pokrenut će se samo ako je događaj povezan s navedenim oznakama. Ostavite prazno da biste pokrenuli web dojave za sve oznake." + tags_filter: "Pokrenute oznake" + groups_filter_instructions: "Relevantne web dojave pokrenut će se samo ako je događaj povezan s navedenim skupinama. Ostavite prazno da biste pokrenuli web dojave za sve grupe." + groups_filter: "Pokrenute grupe" + delete_confirm: "Izbrisati ovu web dojavu?" + topic_event: + name: "Tema događaja" + details: "Kada postoji nova tema, revidirana, promijenjena ili izbrisana." + post_event: + name: "Post Event" + details: "Kada postoji novi odgovor, uređivanje, brisanje ili oporavak." + user_event: + name: "Korisnički događaj" + details: "Kada se korisnik prijavi, odjavljuje se, potvrđuje svoju e-poštu, kreira račun, odobrava ili ažurira." + group_event: + name: "Grupni događaj" + details: "Kada se grupa kreira, ažurira ili uništi." + category_event: + name: "Kategorija Događaj" + details: "Kada se kategorija kreira, ažurira ili uništi." + tag_event: + name: "Tag Event" + details: "Kada se oznaka kreira, ažurira ili uništi." + reviewable_event: + name: "Događaj koji se može pregledati" + details: "Kada je nova stavka spremna za pregled i kada se njezin status ažurira." + notification_event: + name: "Događaj obavijesti" + details: "Kada korisnik primi obavijest u svom feedu." + user_promoted_event: + name: "Događaj koji promovira korisnik" + details: "Kada se korisnik unaprijedi s jedne razine povjerenja na drugu." + user_badge_event: + name: "Događaj za dodjelu značke" + details: "Kada korisnik dobije značku." + group_user_event: + name: "Grupni korisnički događaj" + details: "Kada se korisnik doda ili ukloni u grupi." + like_event: + name: "Kao Događaj" + details: "Kada korisnik lajka objavu." + delivery_status: + title: "Status isporuke" + inactive: "Neaktivan" + failed: "Neuspješno" + successful: "Uspješno" + disabled: "Onemogućeno" + events: + none: "Nema povezanih događaja." + redeliver: "Ponovo isporuči" + incoming: + one: "Postoji novi događaj." + few: "Postoji %{count} novih događaja." + other: "Postoji %{count} novih događaja." + completed_in: + one: "Dovršeno za %{count} sekundi." + few: "Dovršeno za %{count} sekundi." + other: "Dovršeno za %{count} sekundi." + request: "Zahtjev" + response: "Odgovor" + redeliver_confirm: "Jeste li sigurni da želite ponovno isporučiti isti teret?" + headers: "Zaglavlja" + payload: "Teret" + body: "Tijelo" + go_list: "Idi na popis" + go_details: "Uredi webhook" + go_events: "Idi na događaje" + ping: "Ping" + status: "Statusni kod" + event_id: "ID" + timestamp: "Stvoreno" + completion: "Vrijeme završetka" + actions: "Akcije" + plugins: + title: "Dodaci" + installed: "Instalirani dodaci" + name: "Naziv" + none_installed: "Nemate još instalirano dodatka." + version: "Inačica" + enabled: "Omogućeno?" + is_enabled: "D" + not_enabled: "N" + change_settings: "Promijenite postavke" + change_settings_short: "Postavke" + howto: "Kako instalirati dodatke?" + official: "Službeni dodatak" + backups: + title: "Sigurnosne kopije" + menu: + backups: "Sigurnosne kopije" + logs: "Zapisi" + none: "Nema dostupne kopije." + read_only: + enable: + title: "Omogući \"samo čitanje\" model." + label: "Omogući samo za čitanje" + confirm: "Jeste li sigurni da želite omogućiti način rada samo za čitanje?" + disable: + title: "Onemogući \"samo čitanje\" model." + label: "Onemogući samo za čitanje" + logs: + none: "Još nema zapisa..." + columns: + filename: "Ime datoteke" + size: "Veličina" + upload: + label: "Učitaj" + title: "Prenesite sigurnosnu kopiju na ovu instancu" + uploading: "Učitavanje..." + uploading_progress: "Prijenos... %{progress}%" + success: "'%{filename}' je uspješno učitan. Datoteka se procesira i potrajat će do minute kako bi bila prikazana na popisu." + error: "Dogodila se greška pri učitavanju '%{filename}': %{message}" + operations: + is_running: "Operacija je trenutno u toku..." + failed: "%{operation} neuspješna. Molimo provjerite zapise." + cancel: + label: "Odustani" + title: "Odustani od trenutne operacije." + confirm: "Jeste li sigurni da želite odustati od trenutne operacije?" + backup: + label: "Napravi sigurnosnu kopiju" + title: "Stvori sigurnosnu kopiju" + confirm: "Želite li pokrenuti novu sigurnosnu kopiju?" + without_uploads: "Da (ne uključuje prijenose)" + download: + label: "Preuzmi" + title: "Pošaljite e-poštu s vezom za preuzimanje" + alert: "Veza za preuzimanje ove sigurnosne kopije poslana vam je e-poštom." + destroy: + title: "Ukloni kopiju" + confirm: "Jeste li sigurni da želite uništiti ovu sigurnosnu kopiju?" + restore: + is_disabled: "Obnova je onemogućena u postavkama stranice." + label: "Obnovi" + title: "Obnovi sig. kopiju" + confirm: "Jeste li sigurni da želite vratiti ovu sigurnosnu kopiju?" + rollback: + label: "Vrati" + title: "Vrati bazu podataka na prethodno radno stanje" + confirm: "Jeste li sigurni da želite vratiti bazu podataka na prethodno radno stanje?" + location: + local: "Lokalna pohrana" + s3: "S3" + backup_storage_error: "Pristup sigurnosnoj pohrani nije uspio: %{error_message}" + export_csv: + success: "Izvoz je pokrenut, bit ćete obaviješteni putem poruke kada se proces završi." + failed: "Izvoz neuspješan. Molimo provijerite zapise." + button_text: "Izvoz" + button_title: + user: "Izvezi cijelu listu korisnika u CSV formatu." + staff_action: "Izvezi cijeli zapis akcija osoblja u CSV formatu." + screened_email: "Izvezi popis svih provjeravanih emailova u CSV formatu." + screened_ip: "Izvezi popis svih provjeravanih IP-eva u CSV formatu." + screened_url: "Izvezi popis svih provjeravanih URL-ova u CSV formatu." + export_json: + button_text: "Izvoz" + invite: + button_text: "Pošalji pozivnice" + button_title: "Pošalji pozivnice" + customize: + title: "Prilagodba" + long_title: "Prilagodba stranice" + preview: "predprikaz" + explain_preview: "Pogledajte web-mjesto s omogućenom ovom temom" + save: "Spremi" + new: "Novo" + new_style: "Novi stil" + install: "Instalirajte" + delete: "Obriši" + delete_confirm: 'Jeste li sigurni da želite izbrisati "%{theme_name}"?' + color: "Boja" + opacity: "Prozirnost" + copy: "Dupliciraj" + copy_to_clipboard: "Kopirati u međuspremnik" + copied_to_clipboard: "Kopirano u međuspremnik" + copy_to_clipboard_error: "Greška pri kopiranju podataka u međuspremnik" + theme_owner: "Nije moguće uređivati, u vlasništvu:" + email_templates: + title: "Email" + subject: "predmet" + multiple_subjects: "Ovaj predložak e-pošte ima više tema." + body: "Tijelo" + revert: "Vrati promjene" + revert_confirm: "Jeste li sigurni da želite poništiti svoje promjene?" + theme: + theme: "Tema" + component: "Komponenta" + components: "Komponente" + filter_placeholder: "upišite za filtriranje…" + theme_name: "Naziv teme" + component_name: "Naziv komponente" + themes_intro: "Odaberite postojeću temu ili instalirajte novu da biste započeli" + themes_intro_emoji: "umjetnica emoji" + beginners_guide_title: "Vodič za početnike za korištenje tema diskursa" + developers_guide_title: "Vodič za programere za teme diskursa" + browse_themes: "Pregledaj teme zajednice" + customize_desc: "Prilagodba:" + title: "Teme" + create: "Kreiraj" + create_type: "Tip" + create_name: "Ime" + long_title: "Izmjena boja, CSS i HTML sadržaja vaše stranice" + edit: "Izmijeni" + edit_confirm: "Ovo je udaljena tema. Ako uredite CSS/HTML, vaše će se izmjene izbrisati pri sljedećem ažuriranju teme." + update_confirm: "Ove će se lokalne izmjene izbrisati ažuriranjem. Jeste li sigurni da želite nastaviti?" + update_confirm_yes: "Da, nastavite s ažuriranjem" + common: "Uobičajen" + desktop: "Desktop" + mobile: "Mobilni" + settings: "Postavke" + translations: "Prijevodi" + extra_scss: "Dodatni SCSS" + extra_files: "Dodatne datoteke" + extra_files_upload: "Izvezite temu za pregled ovih datoteka." + extra_files_remote: "Izvezite temu ili provjerite git spremište za pregled ovih datoteka." + preview: "Pregled" + show_advanced: "Prikaži napredna polja" + hide_advanced: "Sakrij napredna polja" + hide_unused_fields: "Sakrij neiskorištena polja" + is_default: "Tema je omogućena prema zadanim postavkama" + user_selectable: "Temu mogu odabrati korisnici" + color_scheme_user_selectable: "Shemu boja mogu odabrati korisnici" + auto_update: "Automatsko ažuriranje kada se diskurs ažurira" + color_scheme: "Paleta boja" + default_light_scheme: "Svjetlo (zadano)" + color_scheme_select: "Odaberite boje koje će se koristiti po temi" + custom_sections: "Prilagođeni odjeljci:" + theme_components: "Komponente teme" + add_all_themes: "Dodajte sve teme" + convert: "Pretvoriti" + convert_component_alert: "Jeste li sigurni da želite pretvoriti ovu komponentu u temu? Uklonit će se kao komponenta s %{relatives}." + convert_component_tooltip: "Pretvorite ovu komponentu u temu" + convert_component_alert_generic: "Jeste li sigurni da želite pretvoriti ovu komponentu u temu?" + convert_theme_alert: "Jeste li sigurni da želite pretvoriti ovu temu u komponentu? Uklonit će se kao roditelj iz %{relatives}." + convert_theme_alert_generic: "Jeste li sigurni da želite pretvoriti ovu temu u komponentu?" + convert_theme_tooltip: "Pretvorite ovu temu u komponentu" + inactive_themes: "Neaktivne teme:" + inactive_components: "Neiskorištene komponente:" + broken_theme_tooltip: "Ova tema ima pogreške u svom CSS -u, HTML -u ili YAML -u" + disabled_component_tooltip: "Ova komponenta je onemogućena" + default_theme_tooltip: "Ova je tema zadana tema web stranice" + updates_available_tooltip: "Dostupna su ažuriranja za ovu temu" + and_x_more: "i %{count} više." + collapse: Sakrij + uploads: "Prijenosi" + no_uploads: "Možete prenijeti materijale povezane s vašom temom, poput fontova i slika" + add_upload: "Dodaj prijenos" + upload_file_tip: "Odaberite materijal za prijenos (png, woff2, itd ...)" + variable_name: "SCSS var naziv:" + variable_name_invalid: "Neispravan naziv varijable. Dozvoljeni su samo alfanumerički znakovi. Mora biti jedinstvena." + variable_name_error: + invalid_syntax: "Nevažeći naziv varijable. Dopušteno samo alfanumeričko. Mora početi slovom." + no_overwrite: "Nevažeći naziv varijable. Ne smije prebrisati postojeću varijablu." + must_be_unique: "Neispravan naziv varijable. Naziv mora biti jedinstven." + upload: "Učitaj" + select_component: "Odaberite komponentu..." + unsaved_changes_alert: "Još niste spremili svoje promjene, želite li ih odbaciti i nastaviti dalje?" + unsaved_parent_themes: "Niste dodijelili komponentu temama, želite li krenuti dalje?" + discard: "Odbaci" + stay: "Ostanite" + css_html: "Prilagođeni CSS/HTML" + edit_css_html: "Uredite CSS/HTML" + edit_css_html_help: "Niste uredili nijedan CSS ili HTML" + delete_upload_confirm: "Izbrisati ovaj prijenos? (CSS teme može prestati raditi!)" + component_on_themes: "Uključite komponentu na ove teme" + included_components: "Uključene komponente" + add_all: "Dodaj Sve" + import_web_tip: "Repozitorijum koji sadrži temu" + direct_install_tip: "Jeste li sigurni da želite instalirati %{name} iz dolje navedenog spremišta?" + import_web_advanced: "Napredna..." + import_file_tip: ".tar.gz, .zip ili .dcstyle.json datoteka koja sadrži temu" + is_private: "Tema je u privatnom git repozitoriju" + remote_branch: "Naziv podružnice (izborno)" + public_key: "Omogućite pristup sljedećem javnom ključu repo:" + public_key_note: "Nakon unosa važećeg URL-a privatnog spremišta gore, ovdje će se generirati i prikazati SSH ključ." + install: "Instaliraj" + installed: "Instalirano" + install_popular: "Popularno" + install_upload: "Sa svog uređaja" + install_git_repo: "Iz git repozitorija" + install_create: "Stvori novo" + duplicate_remote_theme: "Komponenta teme “%{name}” je već instalirana, jeste li sigurni da želite instalirati još jednu kopiju?" + about_theme: "O nama" + license: "Licenca" + version: "Verzija:" + authors: "Autor:" + creator: "Napravio:" + source_url: "Izvor" + enable: "Omogući" + disable: "Onemogući" + disabled: "Ova komponenta je onemogućena." + disabled_by: "Ovu komponentu je onemogućio" + required_version: + error: "Ova je tema automatski onemogućena jer nije kompatibilna s ovom verzijom Discoursa." + minimum: "Zahtijeva verziju Discoursa %{version} ili noviju." + maximum: "Zahtijeva verziju Discoursa %{version} ili nižu." + component_of: "Komponenta:" + update_to_latest: "Ažurirajte na najnovije" + check_for_updates: "Provjerite ima li ažuriranja" + updating: "Ažuriranje..." + up_to_date: "Tema je ažurirana, zadnja provjera:" + has_overwritten_history: "Trenutna verzija teme više ne postoji jer je povijest Gita prepisana prisilnim pritiskom." + add: "Dodaj" + theme_settings: "Postavke teme" + no_settings: "Ova tema nema postavke." + theme_translations: "Tematski prijevodi" + empty: "Nema stvari" + commits_behind: + one: "Tema je %{count} obrada iza!" + few: "Tema je %{count} obrada iza!" + other: "Tema je zaostala za %{count} obrada!" + compare_commits: "(Vidi nove obveze)" + remote_theme_edits: "Ako želite urediti ovu temu, morate podnijeti promjenu u njenom spremištu" + repo_unreachable: "Nije moguće kontaktirati Git repozitorij ove teme. Poruka o pogrešci:" + imported_from_archive: "Ova je tema uvezena iz .zip datoteke" + scss: + text: "CSS" + title: "Unesite prilagođeni CSS, prihvaćamo sve važeće CSS i SCSS stilove" + header: + text: "Uzglavlje" + title: "Unesite HTML za prikaz iznad zaglavlja web-mjesta" + after_header: + text: "Nakon zaglavlja" + title: "Unesite HTML za prikaz na svim stranicama nakon zaglavlja" + footer: + text: "Podnožje" + title: "Unesite HTML za prikaz u podnožju stranice" + embedded_scss: + text: "Ugrađeni CSS" + title: "Unesite prilagođeni CSS za isporuku s ugrađenom verzijom komentara" + color_definitions: + text: "Definicije boja" + title: "Unesite prilagođene definicije boja (samo napredni korisnici)" + placeholder: |2- + + Koristite ovu stilsku listu da biste dodali prilagođene boje na listu CSS prilagođenih svojstava. + + Primjer: + + %{example} + + Prefiksiranje imena svojstava preporučuje se kako bi se izbjegli sukobi s dodacima i/ili jezgrom. + head_tag: + text: "Glava" + title: "HTML koji će biti umetnut prije oznake head" + body_tag: + text: "Tijelo" + title: "HTML koji će biti umetnut prije oznake tijela" + yaml: + text: "YAML" + title: "Definirajte postavke teme u YAML formatu" + scss_color_variables_warning: 'Korištenje jezgrenih SCSS varijabli boja u temama je zastarjelo. Umjesto toga koristite prilagođena svojstva CSS -a. Za više pojedinosti ovaj vodič' + scss_warning_inline: "Korištenje jezgrenih SCSS varijabli boja u temama je zastarjelo." + colors: + select_base: + title: "Odaberite osnovnu paletu boja" + description: "Osnovna paleta:" + title: "Boje" + edit: "Uređivanje paleta boja" + long_title: "Palete boja" + about: "Izmijenite boje koje koriste vaše teme. Za početak izradite novu paletu boja." + new_name: "Nova paleta boja" + copy_name_prefix: "Kopija" + delete_confirm: "Izbrisati ovu paletu boja?" + undo: "poništi" + undo_title: "Poništi promjene ovoj boji od zadnjeg puta kad su spremljene." + revert: "vrati" + revert_title: "Vratite ovu boju na zadanu paletu boja diskursa." + primary: + name: "primarna" + description: "Većina teksta, ikonica i granica." + primary-medium: + name: "primarni-srednji" + primary-low-mid: + name: "primarno-nisko-srednje" + secondary: + name: "sekundarna" + description: "Glavna pozadinska boja, i boja teksta na nekim gumbima." + tertiary: + name: "tercijarna" + description: "Poveznice, neki gumbi, obavijesti i boja naglaska." + quaternary: + name: "kvartarna" + description: "Poveznice za navigaciju" + header_background: + name: "pozadina zaglavlja" + description: "Pozadinska boja zaglavlja stranice." + header_primary: + name: "primarna zaglavlja" + description: "Tekst i ikonice u zaglavlju." + highlight: + name: "naglasak" + description: "Pozadinska boja naglašenih elemenata na stranici, kao što su objave i teme." + highlight-high: + name: "istaknuti-visoko" + highlight-medium: + name: "istaknuti-srednji" + highlight-low: + name: "istaknuti-nisko" + danger: + name: "opasnost" + description: "Boja naglaska za akcije kao što su brisanje teme ili objave." + success: + name: "uspješno" + description: "Koristi se da pokaže da je neka akcija bila uspješna." + love: + name: "like" + description: "Boja gumba za like." + robots: + title: "Nadjačaj datoteku robots.txt vaše web stranice:" + warning: "To će trajno nadjačati sve povezane postavke web lokacije." + overridden: Zadana datoteka robots.txt web-mjesta nadjačana je. + email_style: + title: "Stil e-pošte" + heading: "Prilagodite stil e -pošte" + html: "HTML predložak" + css: "CSS" + reset: "Vrati na zadano" + reset_confirm: "Jeste li sigurni da želite vratiti zadane %{fieldName} i izgubiti sve promjene?" + save_error_with_reason: "Vaše promjene nisu spremljene. %{error}" + instructions: "Prilagodite predložak u kojem se generiraju sve html e -poruke i stilizirajte ga pomoću CSS -a." + email: + title: "E-adrese" + settings: "Postavke" + templates: "Predlošci" + preview_digest: "Sažetak pregleda" + advanced_test: + title: "Napredni test" + desc: "Pogledajte kako Discourse obrađuje primljene e -poruke. Da biste mogli ispravno obraditi e -poštu, zalijepite ispod cijele izvorne poruke e -pošte." + email: "Izvorna poruka" + run: "Pokreni test" + text: "Odabrano tijelo teksta" + elided: "Poništeni tekst" + sending_test: "Šaljem testni email..." + error: "GREŠKA - %{server_error}" + test_error: "Dogodio se problem pri slanju testnog emaila. Molimo provjerite postavke emaila, potvrdite da domaćin ne blokira email veze i pokušajte ponovo." + sent: "Poslano" + skipped: "Preskočeno" + bounced: "Odskočio" + received: "Primljeno" + rejected: "Odbio" + sent_at: "Poslano" + time: "Vrijeme" + user: "¸Korisnik" + email_type: "Vrsta emaila" + details_title: "Prikaži detalje e-pošte" + to_address: "Na adresu" + test_email_address: "adresa email za testiranje" + send_test: "Pošalji testni email" + sent_test: "poslano!" + delivery_method: "Metoda dostave" + preview_digest_desc: "Pregledajte sadržaj sažetih poruka e -pošte poslanih neaktivnim korisnicima." + refresh: "Osviježi" + send_digest_label: "Pošaljite ovaj rezultat na:" + send_digest: "Pošalji" + sending_email: "Email se šalje..." + format: "Formatiraj" + html: "hmtl" + text: "tekst" + html_preview: "Pregled sadržaja e-pošte" + last_seen_user: "Posljednji viđeni korisnik:" + no_result: "Nije pronađen rezultat za sažetak." + reply_key: "Ključ odgovora" + skipped_reason: "Preskoči razlog" + incoming_emails: + from_address: "Od" + to_addresses: "Za" + cc_addresses: "Cc" + subject: "predmet" + error: "Greška" + none: "Nema dolaznih e-mailova pronađenih." + modal: + title: "Podaci o dolaznoj e-pošti" + error: "Greška" + headers: "Zaglavlja" + subject: "predmet" + body: "Tijelo" + rejection_message: "Mail za odbijanje" + filters: + from_placeholder: "from@example.com" + to_placeholder: "to@example.com" + cc_placeholder: "cc@example.com" + subject_placeholder: "Predmet..." + error_placeholder: "Greška" + logs: + none: "Nema pronađenih zapisa." + filters: + title: "Filter" + user_placeholder: "korisničko ime" + address_placeholder: "ime@primjer.com" + type_placeholder: "sažetak, prijava..." + reply_key_placeholder: "ključ odgovora" + moderation_history: + performed_by: "Izvođeno od" + no_results: "Povijest moderiranja nije dostupna." + actions: + delete_user: "Korisnik je izbrisan" + suspend_user: "Korisnik suspendiran" + silence_user: "Korisnik je utišan" + delete_post: "Objava je izbrisana" + delete_topic: "Tema je obrisana" + post_approved: "Objava odobrena" + logs: + title: "Zapisi" + action: "Akcija" + created_at: "Stvoreno" + last_match_at: "Zadnji usklađeni" + match_count: "Usklađeni" + ip_address: "IP" + topic_id: "ID teme" + post_id: "ID objave" + category_id: "ID kategorije" + delete: "Obriši" + edit: "Izmjeni" + save: "Spremi" + screened_actions: + block: "blokiraj" + do_nothing: "ne učini ništa" + staff_actions: + all: "sve" + filter: "Filter:" + title: "Akcije osoblja" + clear_filters: "Prikaži sve" + staff_user: "Korisnik" + target_user: "Ciljani korisnik" + subject: "predmet" + when: "Kada" + context: "Kontekst" + details: "Detalji" + previous_value: "Predhodno" + new_value: "Novo" + show: "Pokaži" + modal_title: "Detalji" + no_previous: "Nema predhodne vrijednosti." + deleted: "Nema nove vrijednosti. Zapis je obrisan." + actions: + delete_user: "obriši korisnika" + change_trust_level: "promijeni razinu povjerenja" + change_username: "promijeni korisničko ime" + change_site_setting: "promijeni postavke stranice" + change_theme: "promijeniti temu" + delete_theme: "izbrisati temu" + change_site_text: "promijeniti tekst stranice" + suspend_user: "suspendiraj korisnika" + unsuspend_user: "od-suspendiraj korisnika" + removed_suspend_user: "suspendirati korisnika (uklonjeno)" + removed_unsuspend_user: "poništiti suspenziju korisnika (uklonjeno)" + grant_badge: "dodijeli značku" + revoke_badge: "povuci značku" + check_email: "provjeri email" + delete_topic: "obriši temu" + recover_topic: "poništi brisanje teme" + delete_post: "obriši objavu" + impersonate: "predstavi se kao" + anonymize_user: "anonimizirajte korisnika" + roll_up: "povrati IP blokove" + change_category_settings: "promijeni postavke kategorije" + delete_category: "obriši kategoriju" + create_category: "stvorite novu kategoriju" + silence_user: "ušuti korisnika" + unsilence_user: "utišavanje ušutkivanja korisnika" + removed_silence_user: "utišati korisnika (uklonjeno)" + removed_unsilence_user: "isključiti utišavanje korisnika (uklonjeno)" + grant_admin: "dodijeli administraciju" + revoke_admin: "poništi administraciju" + grant_moderation: "dodijeli moderaciju" + revoke_moderation: "poništi moderaciju" + backup_create: "stvori sigurnosnu kopiju" + deleted_tag: "izbriši oznaku" + deleted_unused_tags: "izbriši neiskorištene oznake" + renamed_tag: "preimenuj oznaku" + revoke_email: "ukloni e-poštu" + lock_trust_level: "zaključaj razinu povjerenja" + unlock_trust_level: "odključaj razinu povjerenja" + activate_user: "aktivirati korisnika" + deactivate_user: "deaktivirati korisnika" + change_readonly_mode: "Omogući \"samo čitanje\" model." + backup_download: "preuzimanje sigurnosne kopije" + backup_destroy: "uništiti sigurnosnu kopiju" + reviewed_post: "pregledan post" + custom_staff: "prilagođena radnja dodatka" + post_locked: "post zaključan" + post_edit: "uređivanje posta" + post_unlocked: "post otključan" + check_personal_message: "provjerite osobne poruke" + disabled_second_factor: "onemogućite dvofaktorsku autentifikaciju" + topic_published: "tema objavljena" + post_approved: "post odobren" + post_rejected: "post odbijen" + create_badge: "kreiraj značku" + change_badge: "promijeni značku" + delete_badge: "izbriši značku" + merge_user: "spajanje korisnika" + entity_export: "izvozni subjekt" + change_name: "promijeni naziv" + topic_timestamps_changed: "promijenjene vremenske oznake teme" + approve_user: "odobreni korisnik" + web_hook_create: "izradite webhook" + web_hook_update: "ažuriranje webhooka" + web_hook_destroy: "webhook uništiti" + web_hook_deactivate: "webhook deaktivirati" + embeddable_host_create: "stvorite ugradbeni host " + embeddable_host_update: "ugradivo ažuriranje hosta" + embeddable_host_destroy: "embeddable host uništiti" + change_theme_setting: "promijeniti postavku teme" + disable_theme_component: "onemogućite komponentu teme" + enable_theme_component: "omogući komponentu teme" + revoke_title: "opozvati naslov" + change_title: "promijeni naslov" + api_key_create: "kreiraj api ključ" + api_key_update: "ažuriranje api ključa" + api_key_destroy: "api ključ uništiti" + override_upload_secure_status: "nadjačati siguran status prijenosa" + page_published: "stranica objavljena" + page_unpublished: "stranica neobjavljena" + add_email: "dodaj e-poštu" + update_email: "ažurirajte e-poštu" + destroy_email: "uništi e-poštu" + topic_closed: "tema zatvorena" + topic_opened: "tema otvorena" + topic_archived: "tema arhivirana" + topic_unarchived: "nearhivirana tema" + post_staff_note_create: "dodati bilješku o osoblju" + post_staff_note_destroy: "uništiti bilješku osoblja" + delete_group: "izbriši grupu" + watched_word_create: "dodaj promatranu riječ" + watched_word_destroy: "izbriši gledanu riječ" + screened_emails: + title: "Provjereni emailovi" + description: "Kad netko pokuša stvoriti račun, slijedeće email adrese će biti provjerene i registracija će biti blokirana ili će neke druge akcije biti poduzete." + email: "Email adresa" + actions: + allow: "Dopusti" + screened_urls: + title: "Provjereni URL-ovi" + description: "URL-ovi s ovog popisa su korišteni u objavama korisnika koji su identificirani kao spammer (objavljivali nepoželjan sadržaj)." + url: "URL" + domain: "Domena" + screened_ips: + title: "Provjereni IP-pvi" + description: 'IP adrese koje se promatraju. Upotrijebite "Dopusti" da biste dopustili IP adrese.' + delete_confirm: "Jeste li sigruni da želite ukloniti pravilo za %{ip_address}?" + actions: + block: "Blokiraj" + do_nothing: "Dopusti" + allow_admin: "Dopusti administratoru" + form: + label: "Novo:" + ip_address: "IP adrese" + add: "Dodaj" + filter: "Pretraži" + roll_up: + text: "Zaokruži" + title: "Stvori nove unose zabrana za podmreže ako ima barem 'min_ban_entries_for_roll_up' unosa." + search_logs: + title: "Pretraživanje dnevnika" + term: "Pojam" + searches: "Pretraživanja" + click_through_rate: "CTR" + types: + all_search_types: "Sve vrste pretraživanja" + header: "Uzglavlje" + full_page: "Cijela stranica" + click_through_only: "Sve (samo kliknite)" + header_search_results: "Rezultati pretraživanja zaglavlja" + logster: + title: "Zapis grešak" + watched_words: + title: "Gledane riječi" + search: "pretraži" + clear_filter: "Izbriši" + show_words: + one: "pokazati %{count} riječ" + few: "Prikaži %{count} riječi" + other: "Prikaži %{count} riječi" + download: Preuzmi + clear_all: Obriši sve + clear_all_confirm: "Jeste li sigurni da želite očistiti sve promatrane riječi za %{action} akciju?" + invalid_regex: 'Promatrana riječ "%{word}" je nevažeći regularni izraz.' + regex_warning: 'Gledane riječi su regularni izrazi i ne uključuju automatski granice riječi. Ako želite da regularni izraz odgovara cijelim riječima, uključite \b na početak i kraj vašeg regularnog izraza.' + actions: + block: "Blokiraj" + censor: "Cenzor" + require_approval: "Zahtijevati odobrenje" + flag: "Označi zastavicom" + replace: "Zamijeni" + tag: "Označiti" + silence: "Utišaj" + link: "Veza" + action_descriptions: + block: "Spriječite objave koje sadrže ove riječi. Autor objave dobiti će poruku o grešci kada pokuša poslati objavu." + censor: "Dopustite postove koji sadrže ove riječi, ali ih zamijenite znakovima koji skrivaju cenzurirane riječi." + require_approval: "Objave koje sadrže ove riječi zahtijevat će odobrenje osoblja prije nego što budu vidljive." + flag: "Dopustite postove koji sadrže ove riječi, ali ih označite kao neprikladne kako bi ih moderatori mogli pregledati." + replace: "Zamijenite riječi u objavama drugim riječima" + tag: "Automatski označi teme na temelju prvog posta" + silence: "Prve objave korisnika koje sadrže ove riječi zahtijevat će odobrenje osoblja prije nego što budu vidljive i korisnik će biti automatski ušutkan." + link: "Zamijenite riječi u objavama vezama" + form: + label: "Ima riječ ili frazu" + placeholder: "Unesite riječ ili izraz (* je zamjenski znak)" + placeholder_regexp: "regularni izraz" + replace_label: "Zamjena" + replace_placeholder: "primjer" + tag_label: "Označiti" + link_label: "Veza" + link_placeholder: "https://example.com" + add: "Dodaj" + success: "Uspjeh" + exists: "Već postoji" + upload: "Dodaj iz datoteke" + upload_successful: "Prijenos uspješno. Riječi su dodane." + test: + button_label: "Test" + modal_title: "%{action}: Testirajte gledane riječi" + description: "Unesite tekst ispod da provjerite podudaranja s gledanim riječima" + found_matches: "Pronađeno podudaranja:" + no_matches: "Nema pronađenih podudaranja" + impersonate: + title: "Predstavi se kao" + help: "Koristite ovaj alat da se predstavljate kao određeni korisnički račun za potrebe ispravljanja grešaka. Morat će te se odjaviti kad završite." + not_found: "Taj se korisnik ne može pronaći." + invalid: "Nažalost, ne smijete se lažno predstavljati kao taj korisnik." + users: + title: "Korisnici" + create: "Dodaj administratora" + last_emailed: "Zadnji emailan" + not_found: "Žao nam je, taj korisnik ne postoji." + id_not_found: "Žao nam je, taj korisnički ID ne postoji." + active: "Aktivirano" + show_emails: "Pokaži emailove" + hide_emails: "Sakrij e-poštu" + nav: + new: "Novo" + active: "Aktivno" + staff: "Osoblje" + suspended: "Suspendirani" + silenced: "Utišano" + staged: "Priređen" + approved: "Potvrđeni?" + titles: + active: "Aktivni korisnici" + new: "Novi korisnici" + pending: "Korisnici koji čekaju reviziju" + newuser: "Korisnici na razini povjerenja 0 (novi korisnici)" + basic: "Korisnici na razini povjerenja 1 (osnovni korisnici)" + member: "Korisnici na razini povjerenja 2 (članovi)" + regular: "Korisnici na razini povjerenja 3 (stalni)" + leader: "Korisnici na razini povjerenja 4 (vođe)" + staff: "Osoblje" + admins: "Administratori" + moderators: "Moderatori" + silenced: "Ušutkani korisnici" + suspended: "Suspendirani korisnici" + staged: "Postupni korisnici" + not_verified: "Nije odobreno" + check_email: + title: "Otkrij korisnikovu email adresu." + text: "Pokaži" + check_sso: + title: "Otkrivanje korisnog opterećenja SSO-a" + text: "Pokaži" + user: + suspend_failed: "Nešto je pošlo po krivu pri suspenziji ovog korisnika %{error}" + unsuspend_failed: "Nešto je pošlo po krivu pri ukidanju suspenzije ovog korisnika %{error}" + suspend_duration: "Koliko dugo će ovaj korisnik biti suspendiran?" + suspend_reason_label: "Zažto ga suspendiraš? Ovaj tekst će biti vidljiv svima na korisničkom profilu korisnika, i biti će prikazan korisniku kad se pokuša prijaviti. Neka je kratak." + suspend_reason_hidden_label: "Zašto suspendiraš? Ovaj tekst će biti prikazan korisniku kada se pokuša prijaviti. Držite ga kratko." + suspend_reason: "Razlog" + suspend_reason_title: "Razlog suspenzije" + suspend_reasons: + not_listening_to_staff: "Ne sluša na informacije osoblja" + consuming_staff_time: "Potrošeno nerazmjerno mnogo vremena osoblja" + combative: "Previše agresivan" + in_wrong_place: "Na krivom mjestu" + no_constructive_purpose: "Njihova djela nemaju konstruktivnu svrhu osim stvaranja neslaganja unutar zajednice" + custom: "Prilagođeno..." + suspend_message: "Pošaljite poruku emailom." + suspend_message_placeholder: "Po želji, pružite više informacija o suspenziji i bit će poslana korisniku." + suspended_by: "Suspendirao" + silence_reason: "Razlog" + silenced_by: "Ušutkano od strane" + silence_modal_title: "Ušuti korisnika" + silence_duration: "Koliko dugo će korisnik biti ušutkan?" + silence_reason_label: "Zašto ušutkavate ovog korisnika?" + silence_reason_placeholder: "Razlog šutnje" + silence_message: "Pošaljite poruku emailom." + silence_message_placeholder: "(ostavite praznim za slanje zadane poruke)" + suspended_until: "(do %{until})" + cant_suspend: "Ovaj korisnik ne može biti suspendiran." + delete_posts_failed: "Došlo je do problema pri brisanju postova." + post_edits: "Post izmjene" + view_edits: "Prikaz uređivanja" + penalty_post_actions: "Što biste željeli učiniti s pripadajućim postom?" + penalty_post_delete: "Obrišite post" + penalty_post_delete_replies: "Izbriši post + sve odgovore" + penalty_post_edit: "Uredi post" + penalty_post_none: "Ne radi ništa" + penalty_count: "Broj kazni" + penalty_history: "Povijest kazni" + clear_penalty_history: + title: "Obriši povijest kazne" + description: "korisnici s kaznama ne mogu doseći TL3" + delete_all_posts_confirm_MF: "Uskoro ćete izbrisati {POSTS, plural, one {# post} few {# postova} other {# postova}} i {TOPICS, plural, one {# tema} few {# tema} other {# tema}}. Jesi li siguran?" + silence: "Utišaj" + unsilence: "Vrati šutnju" + silenced: "Utišano?" + moderator: "Moderator?" + admin: "Administrator?" + suspended: "Suspendiran?" + staged: "Priređen?" + show_admin_profile: "Administrator" + show_public_profile: "Pokaži javni profil" + impersonate: "Predstavi se kao" + action_logs: "Dnevnici akcija" + ip_lookup: "IP pregled" + log_out: "Odjava" + logged_out: "Korisnik je odjavljen sa svih uređaja" + revoke_admin: "Poništi administraciju" + grant_admin: "Dodijeli administraciju" + grant_admin_success: "Novi administrator je potvrđen." + grant_admin_confirm: "Poslali smo vam e-mail da biste provjerili novog administratora. Otvorite ga i slijedite upute." + revoke_moderation: "Poništi moderaciju" + grant_moderation: "Dodijeli moderaciju" + unsuspend: "Poništi suspenziju" + suspend: "Suspendiraj" + show_flags_received: "Prikaži primljene prijave" + flags_received_by: "Prijava primljena od %{username}" + flags_received_none: "Ovaj korisnik nije primio prijave." + reputation: Reputacija + permissions: Dozvole + activity: Aktivnosti + like_count: Like-ovi dani / primljeni + last_100_days: "U zadnjih 100 dana" + private_topics_count: Privatne teme + posts_read_count: Objava pročitano + post_count: Objava stvoreno + second_factor_enabled: Omogućena dvofaktorska provjera autentičnosti + topics_entered: Tema pogledano + flags_given_count: Zastavica dano + flags_received_count: Zastavica primljeno + warnings_received_count: Upozorenja primljeno + warnings_list_warning: | + Kao moderator, možda nećete moći vidjeti sve ove teme. Ako je potrebno, zatražite od administratora ili moderatora izdavanja da @moderators pristupi poruci. + flags_given_received_count: "Zastavica primljeno / dano" + approve: "Odobri" + approved_by: "Odobrio" + approve_success: "Korisnik odobren i email s aktivacijom poslan." + approve_bulk_success: "Uspjeh! Svi označeni korisnici su odobreni i obaviješteni." + time_read: "Vrijeme čitanja" + post_edits_count: "Post izmjene" + anonymize: "Anonimiziraj korisnika" + anonymize_confirm: "Jeste li sigurni da želite anonimizirati ovaj račun? To će promijeniti korisničko ime i e-mail, i resetirati sve informacije o profilu." + anonymize_yes: "Da, anonimizirajte ovaj račun" + anonymize_failed: "Došlo je do problema s anonimiziranjem računa." + delete: "Obriši korisnika" + delete_posts: + button: "Obriši sve objave" + progress: + title: "Napredak brisanja postova" + description: "Brisanje postova..." + confirmation: + title: "Izbrišite sve postove do @%{username}" + description: | +

Jeste li sigurni da želite izbrisati %{post_count} postove by @%{username}? + +

Ovo se ne može poništiti!

+ +

Za nastavak tipa: %{text}

+ text: "izbriši postove od @%{username}" + delete: "Izbriši postove od @%{username}" + cancel: "Odustani" + merge: + button: "Spoji" + prompt: + title: "Prijenos i brisanje @%{username}" + description: | +

Molimo odaberite novog vlasnika za @%{username} sadržaja.

+ +

Sve teme, postovi, poruke i drugi sadržaj kreiran od strane @%{username} će biti prenesen.

+ target_username_placeholder: "Korisničko ime novog vlasnika" + transfer_and_delete: "Prijenos i brisanje @%{username}" + cancel: "Odustani" + progress: + title: "Spoji napredak" + confirmation: + title: "Prijenos i brisanje @%{username}" + description: | +

Sve @%{username}je sadržaj će biti prebačen i pripisuje @%{targetUsername}. Nakon prijenosa sadržaja, @%{username} račun će biti izbrisan.

+ +

Ovo se ne može poništiti!

+ +

Za nastavak tipa: %{text}

+ text: "prijenos @%{username} u @%{targetUsername}" + transfer_and_delete: "Prijenos i brisanje @%{username}" + cancel: "Odustani" + merging_user: "Spajanje korisnika..." + merge_failed: "Došlo je do pogreške prilikom spajanja korisnika." + delete_forbidden_because_staff: "Admini i moderatori ne mogu biti obrisani." + delete_posts_forbidden_because_staff: "Nije moguće obrisati sve objave administratora i moderatora." + delete_forbidden: + one: "Korisnici ne mogu biti obrisani ako imaju objava. Obrišite sve njihove objave prije nego pokušate obrisati korisnika. (Objave starije od %{dana] ne mogu biti obrisane.)" + few: "Korisnici ne mogu biti obrisani ako imaju objava. Obrišite sve njihove objave prije nego pokušate obrisati korisnika. (Objave starije od %{dana] ne mogu biti obrisane.)" + other: "Korisnici ne mogu biti obrisani ako imaju objava. Obrišite sve njihove objave prije nego pokušate obrisati korisnika. (Objave starije od %{dana] ne mogu biti obrisane.)" + cant_delete_all_posts: + one: "Ne možete obrisati sve objave. Neke su starije od %{count} dana. (Postavke brisanja objava maximalne starosti.)" + few: "Ne možete obrisati sve objave. Neke su starije od %{count} dana. (Postavke brisanja objava maximalne starosti.)" + other: "Ne možete obrisati sve objave. Neke su starije od %{count} dana. (Postavke brisanja objava maximalne starosti.)" + cant_delete_all_too_many_posts: + one: "Ne možete obrisati sve objave jer korisnik ima više od %{count} objava. (briši_sve_objave_max)" + few: "Ne možete obrisati sve objave jer korisnik ima više od %{count} objava. (briši_sve_objave_max)" + other: "Ne možete obrisati sve objave jer korisnik ima više od %{count} objava. (briši_sve_objave_max)" + delete_confirm: "Općenito je poželjno anonimizirati korisnike umjesto brisanja, kako bi se izbjeglo uklanjanje sadržaja iz postojećih rasprava.

Jeste li sigurni da želite izbrisati ovog korisnika? Ovo je trajno!" + delete_and_block: "Obriši i block ovaj email i IP adresu" + delete_dont_block: "Samo obriši" + deleting_user: "Brisanje korisnika..." + deleted: "Korisnik je obrisan." + delete_failed: "Dogodila se greška pri brisanju korisnika. Provjerite da li ste obrisali sve njegove objave prije nego ga ponovo pokušate brisati." + send_activation_email: "Pošalji aktivacijski email" + activation_email_sent: "Aktivacijski email poslan." + send_activation_email_failed: "Dogodila se greška pri slanju još jednog aktivacijskog emaila. %{error}" + activate: "Aktiviraj račun" + activate_failed: "Dogodio se problem s aktivacijom korisnika." + deactivate_account: "Deaktiviraj račun" + deactivate_failed: "Dogodio se problem s deaktivacijom korisnika." + unsilence_failed: "Došlo je do problema sa ušitkavanjem korisnika." + silence_failed: "Došlo je do problema s utišavanjem korisnika." + silence_confirm: "Jeste li sigurni da želite ušutkati ovog korisnika? Neće moći stvarati nove teme ili postove." + silence_accept: "Da, utišajte ovog korisnika" + bounce_score: "Bounce rezultat" + reset_bounce_score: + label: "Resetirati" + title: "Reset bounce rezultat natrag na 0" + visit_profile: "Posjetite stranicu preferenci ovog korisnika da biste uredili njihov profil" + deactivate_explanation: "Deaktivirani korisnik mora ponovo potvrditi svoj email." + suspended_explanation: "Suspendirani korisnik se ne može prijaviti." + silence_explanation: "Ušutkani korisnik ne može objavljivati niti započeti teme." + staged_explanation: "Postupni korisnik može objavljivati samo putem e-maila u određenim temama." + bounce_score_explanation: + none: "Nedavno nije primljeno odbijanje od tog e-maila." + some: "Nedavno su primljeni neki odbijeni poslovi iz te e -pošte." + threshold_reached: "Dobio sam previše odbijanja od te e -pošte." + trust_level_change_failed: "Dogodio se problem s promjenom razine povjerenje korisnika." + suspend_modal_title: "Suspendiraj korisnika" + confirm_cancel_penalty: "Jeste li sigurni da želite odbaciti kaznu?" + trust_level_2_users: "Korisnici na razini povjerenja 2" + trust_level_3_requirements: "Predispozicije za razinu povjerenja 3" + trust_level_locked_tip: "razina povjerenja zaključana, sistem neće promovirati ili demotirati korisnika" + trust_level_unlocked_tip: "razina povjerenja odključana, sistem će promovirati ili demotirati korisnika" + lock_trust_level: "Zaključaj razinu povjerenja" + unlock_trust_level: "Odključaj razinu povjerenja" + silenced_count: "Utišano" + suspended_count: "Suspendirani" + last_six_months: "Posljednjih 6 mjeseci" + tl3_requirements: + title: "Predispozicije za razinu povjerenja 3" + table_title: + one: "Posljednjeg dana:" + few: "Posljednjih %{count} dana:" + other: "U zadnjih %{count} dana:" + value_heading: "Vrijednost" + requirement_heading: "Predispozicija" + visits: "Posjeta" + days: "dana" + topics_replied_to: "Odgovoreno u teme" + topics_viewed: "Tema pogledano" + topics_viewed_all_time: "Tema pogledano (sve vrijeme)" + posts_read: "Objava pročitano" + posts_read_all_time: "Objava pročitano (sve vrijeme)" + flagged_posts: "Objava označeno zastavicom" + flagged_by_users: "Korisnici koji su označili zastavicom" + likes_given: "Like-ova dano" + likes_received: "Like-ova primljeno" + likes_received_days: "Likeova primljeno: jedinstveni dani" + likes_received_users: "Likeova primljeno: jedinstveni korisnici" + suspended: "Suspendiran (zadnjih 6 mjeseci)" + silenced: "Ušutkano (zadnjih 6 mjeseci)" + qualifies: "Kvalificira se za razinu povjerenja 3." + does_not_qualify: "Ne kvalificira se za razinu povjerenja 3." + will_be_promoted: "Uskoro će biti promovisan." + will_be_demoted: "Uskoro će biti demotiran." + on_grace_period: "Trenutno u periodu milosti zbog promocije, neće biti demotiran." + locked_will_not_be_promoted: "Razina povjerenja zaključana. Neće biti promoviran." + locked_will_not_be_demoted: "Razina povjerenja zaključana. Neće biti demotiran." + discourse_connect: + title: "Jednokratna prijava na DiscourseConnect" + external_id: "Vanjski ID" + external_username: "Korisničko ime" + external_name: "Ime" + external_email: "Email" + external_avatar_url: "URL profilne slike" + last_payload: "Zadnja nosivost" + delete_sso_record: "Izbriši SSO zapis" + confirm_delete: "Jeste li sigurni da želite izbrisati ovaj DiscourSeconnect zapis?" + user_fields: + title: "Korisnička polja" + help: "Dodaj polja koja korisnici mogu popuniti." + create: "Stvori korisnička polja" + untitled: "Nenaslovljeno" + name: "Ime polja" + type: "Tip polja" + description: "Opis polja" + save: "Spremi" + edit: "Izmijeni" + delete: "Obriši" + cancel: "Odustani" + delete_confirm: "Jeste li sigurni da želite obrisati to korisničko polje?" + options: "Mogućnosti" + required: + title: "Potrebno pri registraciji?" + enabled: "potrebno" + disabled: "nije potrebno" + editable: + title: "Izmijenjivo nakon registracije?" + enabled: "izmijenjivo" + disabled: "nije izmijenjivo" + show_on_profile: + title: "Prikaži na javnom profilu?" + enabled: "prikazano na profilu" + disabled: "nije prikazano na profilu" + show_on_user_card: + title: "Prikaži na korisničkoj kartici?" + enabled: "prikaži na korisničkoj kartici" + disabled: "nije prikazano na korisničkoj kartici" + searchable: + title: "Može se pretraživati?" + enabled: "može se pretraživati" + disabled: "nije moguće pretraživati" + field_types: + text: "Tekst polje" + confirm: "Potvrda" + dropdown: "Padajući izbornik" + multiselect: "Višestruki odabir" + site_text: + description: "Možete prilagoditi bilo koji tekst na forumu. Molimo vas da započnete pretraživanjem ispod:" + search: "Potražite tekst koji želite urediti" + title: "Tekst" + edit: "izmijeni" + revert: "Vrati promjene" + revert_confirm: "Jeste li sigurni da želite poništiti promjene?" + go_back: "Natrag na pretraživanje" + recommended: "Preporučujemo da prilagodite sljedeći tekst tako da odgovara vašim potrebama:" + show_overriden: "Pokazuj samo promijenjeno" + locale: "Jezik:" + fallback_locale_warning: "Uređujete jezik temeljen na %{fallback}. Korisnici koji odaberu %{fallback} kao jezik sučelja neće vidjeti vaše promjene." + more_than_50_results: "Postoji više od 50 rezultata. Molimo suzite kriterije vaše pretrage." + settings: + show_overriden: "Pokazuj samo promijenjeno" + history: "Prikaz povijesti promjena" + reset: "resetiraj" + none: "ništa" + site_settings: + emoji_list: + invalid_input: "Emotikona lista treba sadržavati samo važeća imena emoji, npr.: zagrljaji" + add_emoji_button: + label: "Dodaj emotikone" + title: "Postavke" + no_results: "Nema pronađenih rezultata" + more_than_30_results: "Postoji više od 30 rezultata. Molimo suzite kriterije vaše pretrage ili odaberite kategoriju." + clear_filter: "Izbriši" + add_url: "dodaj URL" + add_host: "dodaj host" + add_group: "dodaj grupu" + uploaded_image_list: + label: "Uredi popis" + empty: "Još nema slika. Prenesite jednu." + upload: + label: "Učitaj" + title: "Prijenos slika" + selectable_avatars: + title: "Popis avatara koji korisnici mogu birati" + categories: + all_results: "Sve" + required: "Potrebno" + branding: "Brendiranje" + basic: "Osnovne postavke" + users: "Korisnici" + posting: "Objavljivanje" + email: "Email" + files: "Datoteke" + trust: "Razine povjerenja" + security: "Sigurnost" + onebox: "Onebox" + seo: "SEO" + spam: "Nepoželjno" + rate_limits: "Granica omjera" + developer: "Programer" + embedding: "Ugrađivanje" + legal: "Legalno" + api: "API" + user_api: "Korisnički API" + uncategorized: "Ostalo" + backups: "Sigurnosne kopije" + login: "Prijava" + plugins: "Dodaci" + user_preferences: "Korisničke postavke" + tags: "Oznake" + search: "Pretraži" + groups: "Grupe" + dashboard: "Nadzorna ploča" + secret_list: + invalid_input: "Polja za unos ne mogu biti prazna niti sadrže znak vertikalne trake." + default_categories: + modal_description: "Želite li povijesno primijeniti ovu promjenu? Time će se promijeniti postavke za %{count} postojećih korisnika." + modal_yes: "Da" + modal_no: "Ne, samo ubuduće primijeni promjenu" + simple_list: + add_item: "Dodaj stavku..." + json_schema: + edit: Pokreni uređivač + modal_title: "Uredi %{name}" + badges: + title: Značke + new_badge: Nova značka + new: Novo + name: Ime + badge: Značka + display_name: Ime prikaza + description: Opis + long_description: dugi opis + badge_type: Tip značke + badge_grouping: Grupa + badge_groupings: + modal_title: Grupiranja znački + granted_by: Dodijelio + granted_at: Dodijeljeno pri + reason_help: (Link na post ili temu) + save: Spremi + delete: Obriši + delete_confirm: Jeste li sigurni da želite izbrisati ovu značku? + revoke: Povuci + reason: Razlog + expand: Proširi … + revoke_confirm: Jeste li sigurni da želite povući ovu značku? + edit_badges: Izmijeni značke + grant_badge: Dodijeli značku + granted_badges: Dodijeljene značke + grant: Dodijeli + no_user_badges: "%{name} nije dodijeljena ni jedna značka" + no_badges: Nema znački za dodijeliti. + none_selected: "Odaberite značku da počnete" + allow_title: Dopusti da se značka koristi kao titula + multiple_grant: Može biti dodijeljena više puta + listable: Prikaži značku na javnoj stranici sa značkama + enabled: Omogući značku + icon: Ikona + image: Slika + graphic: Grafički + icon_help: "Unesite naziv ikone Font Awesome (koristite prefiks 'far-' za obične ikone i 'fab-' za ikone marke)" + image_help: "Prijenos slike nadjačava polje ikone ako su postavljena oba." + select_an_icon: "Odaberite ikonu" + upload_an_image: "Prenesite sliku" + read_only_setting_help: "Prilagodite tekst" + query: Upitnik značke (SQL) + target_posts: Upitnik cilja objave + auto_revoke: Svakodnevno pokreni upitnik o povlačenju + show_posts: Prikaži objave zbog kojih je značka dodijeljena na stranici značke + trigger: Okidač + trigger_type: + none: "Ažuriraj dnevno" + post_action: "Kad korisnik reagira na objavu" + post_revision: "Kad korisnik izmjeni ili objavi objavu" + trust_level_change: "Kad korisnik promijeni razinu povjerenja" + user_change: "Kad je korisnik izmjenjen ili stvoren" + preview: + link_text: "Predpregledaj dodijeljene značke" + plan_text: "Predpregled s planom upita" + modal_title: "Predpregled upita značke" + sql_error_header: "Dogodila se greška s upitom." + error_help: "Pogledaj slijedeće poveznice za pomoć s upitima znački." + bad_count_warning: + header: "UPOZORENJE!" + text: "Postoje izgubljeni uzorci dodjele. Ovo se dogodi kad upit značke vrati korisnički ID ili ID objave koja ne postoji. Ovo može prouzročiti nepredviđene probleme kasnije - molimo provjerite svoj upit." + no_grant_count: "Nema značaka za dodijeliti." + grant_count: + one: "%{count} značka za dodjelu." + few: "%{count} znački za dodjelu." + other: "%{count} znački za dodjelu." + sample: "Uzorak:" + grant: + with: %{username} + with_post: %{username} za objavu u %{link} + with_post_time: %{username} za objavu u %{link} u %{time} + with_time: %{username} u %{time} + badge_intro: + title: "Odaberite postojeću značku ili izradite novu da biste započeli" + emoji: "studentica emoji" + what_are_badges_title: "Što su značke?" + badge_query_examples_title: "Primjeri upita za značke" + mass_award: + title: Skupna nagrada + description: Dodijelite istu značku velikom broju korisnika odjednom. + no_badge_selected: Odaberite značku da biste započeli. + perform: "Dodijelite značku korisnicima" + upload_csv: Prenesite CSV s korisničkim adresama e-pošte ili korisničkim imenima + aborted: Prenesite CSV koji sadrži korisničke poruke e-pošte ili korisnička imena + success: Vaš CSV je primljen i %{count} korisnika će uskoro dobiti svoju značku. + csv_has_unmatched_users: "Sljedeće stavke nalaze se u CSV datoteci, ali se ne mogu uskladiti s postojećim korisnicima i stoga neće dobiti značku:" + csv_has_unmatched_users_truncated_list: "Bilo je %{count} unosa u CSV datoteci koji se ne mogu upariti s postojećim korisnicima, pa stoga neće dobiti značku. Zbog velikog broja neusklađenih unosa, prikazano je samo prvih 100:" + replace_owners: Uklonite značku od prethodnih vlasnika + grant_existing_holders: Dodijelite dodatne značke postojećim nositeljima bedževa + emoji: + title: "Emoji" + help: "Dodajte nove emojije koji će biti dostupni svima. Povucite i ispustite više datoteka odjednom bez unosa naziva da biste stvorili emojije koristeći njihove nazive datoteka. Odabrana grupa će se koristiti za sve datoteke koje se dodaju u isto vrijeme. Također možete kliknuti \"Dodaj novi emoji\" da otvorite alat za odabir datoteka." + add: "Dodaj novi emoji" + choose_files: "Odaberite Datoteke" + uploading: "Učitavanje..." + name: "Ime" + group: "Grupa" + image: "Slika" + alt: "prilagođeni pregled emotikona" + delete_confirm: "Jeste li sigurni da želite izbrisati :%{name}: emoji?" + embedding: + get_started: "Ako želite ugraditi Discourse na drugu web-stranicu, počnite dodavanjem njegovog hosta." + confirm_delete: "Jeste li sigurni da želite izbrisati tog hosta?" + sample: "Zalijepite sljedeći HTML kod na svoju web-lokaciju kako biste stvorili i ugradili teme diskursa. Zamijenite ZAMJENI_ME kanonskim URL-om stranice na koju ga ugrađujete." + title: "Ugrađivanje" + host: "Dopušteni hostovi" + class_name: "Naziv klase" + allowed_paths: "Dopuštane putanje" + edit: "izmijeni" + category: "Post u kategoriju" + add_host: "Dodaj host" + settings: "Postavke ugrađivanja" + crawling_settings: "Postavke alata za indeksiranje" + crawling_description: "Kada Discourse kreira teme za vaše postove, ako RSS/ATOM feed nije prisutan, pokušat će raščlaniti vaš sadržaj iz vašeg HTML-a. Ponekad može biti izazovno izdvojiti svoj sadržaj, pa nudimo mogućnost određivanja CSS pravila kako bismo olakšali ekstrakciju." + embed_by_username: "Korisničko ime za kreiranje teme" + embed_post_limit: "Maksimalan broj postova za ugradnju" + embed_title_scrubber: "Redoviti izraz koji se koristi za pročišćavanje naslova postova" + embed_truncate: "Skratite ugrađene postove" + embed_unlisted: "Uvezene teme bit će nenavedene dok ne dođe do odgovora." + allowed_embed_selectors: "CSS selektor za elemente koji su dopušteni u ugrađivanje" + blocked_embed_selectors: "CSS selektor za elemente koji su uklonjeni iz ugrađivača" + allowed_embed_classnames: "Dopušteni nazivi CSS klasa" + save: "Spremi postavke ugrađivanja" + permalink: + title: "Poveznice" + description: "Preusmjeravanja za prijavu za URL-ove koje forum ne zna." + url: "URL" + topic_id: "ID teme" + topic_title: "Tema" + post_id: "ID objave" + post_title: "Objava" + category_id: "ID kategorije" + category_title: "Kategorija" + tag_name: "Naziv oznake" + external_url: "Vanjski ili relativni URL" + destination: "Odredište" + copy_to_clipboard: "Kopirajte stalnu vezu u međuspremnik" + delete_confirm: Jeste li sigurni da želite izbrisati ovu stalnu vezu? + form: + label: "Novo:" + add: "Dodaj" + filter: "Pretraživanje (URL ili vanjski URL)" + reseed: + action: + label: "Zamijeni tekst…" + title: "Zamijenite tekst kategorija i tema prijevodima" + modal: + title: "Zamijeni tekst" + subtitle: "Zamijenite tekst sustava generiranih kategorija i tema najnovijim prijevodima" + categories: "Kategorije" + topics: "Teme" + replace: "Zamijeni" + wizard_js: + wizard: + done: "Gotovo" + finish: "Završi" + back: "Natrag" + next: "Sljedeći" + step: "%{current} od %{total}" + upload: "Učitaj" + uploading: "Učitavanje..." + upload_error: "Žao nam je, dogodila se greška pri učitavanju te datoteke. Molimo pokušajte ponovo." + quit: "Možda kasnije" + staff_count: + one: "Vaša zajednica ima %{count} djelatnika (vi)." + few: "Vaša zajednica ima %{count} djelatnika (vi)." + other: "Vaša zajednica ima %{count} djelatnika, uključujući vas." + invites: + add_user: "dodaj" + none_added: "Nisi pozvao nijedno osoblje. Jeste li sigurni da želite nastaviti?" + roles: + admin: "Administrator" + moderator: "Moderator" + regular: "Stalni član" + previews: + topic_title: "Naslov teme za raspravu" + share_button: "Dijeli" + reply_button: "Odgovori" + topic_preview: "Pregled teme" + homepage_preview: "Pretpregled početne stranice" diff --git a/config/locales/server.hr.yml b/config/locales/server.hr.yml new file mode 100644 index 0000000000..73286fc51f --- /dev/null +++ b/config/locales/server.hr.yml @@ -0,0 +1,1085 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + dates: + short_date_no_year: "D MMM" + short_date: "D MMM, GGGG" + long_date: "MMMM D, GGGG h:mm" + datetime_formats: &datetime_formats + formats: + short: "%m-%d-%Y" + short_no_year: "%B %-d" + date_only: "%B %Y %-d" + long: "%B %Y %H:%M %-d" + no_day: "%B %Y" + calendar_ics: "%Y%m%dT%H%M%SZ" + date: + month_names: + - null + - siječanj + - veljača + - ožujak + - travanj + - svibanj + - lipanj + - srpanj + - kolovoz + - rujan + - listopad + - studeni + - prosinac + <<: *datetime_formats + time: + am: "am" + pm: "pp" + <<: *datetime_formats + title: "Discourse" + topics: "Teme" + posts: "objave" + views: "pregledi" + loading: "Učitavanje" + powered_by_html: 'Pogonjeno Discourse platformom, preporučen pregled uz omogućen JavaScript' + sign_up: "Učlani se" + log_in: "Prijava" + submit: "Pošalji" + purge_reason: "Automatski izbrisao kao napušteni, deaktivirani račun." + disable_remote_images_download_reason: "Daljinsko preuzimanje slika je onemogućeno jer nema dovoljno dostupnog prostora na disku." + anonymous: "Anonimno" + remove_posts_deleted_by_author: "Izbrisao autor" + redirect_warning: "Nismo mogli potvrditi da je poveznica koju ste odabrali stvarno bila objavljena na forumu. Želite li svejedno nastaviti, odaberite poveznicu u nastavku." + on_another_topic: "Na drugoj temi" + inline_oneboxer: + topic_page_title_post_number: "#%{post_number}" + topic_page_title_post_number_by_user: "#%{post_number} od %{username}" + themes: + bad_color_scheme: "Tema se ne može ažurirati, nevažeća paleta boja" + other_error: "Dogodila se greška pri ažuriranju teme" + ember_selector_error: "Žao nam je – korištenje #ember ili .ember-view CSS selektora nije dopušteno, jer se ti nazivi dinamički generiraju tijekom izvođenja i mijenjat će se tijekom vremena, što će na kraju rezultirati neispravnim CSS-om. Pokušajte s drugim selektorom." + compile_error: + unrecognized_extension: "Nastavak datoteke %{extension} nije prepoznat" + import_error: + generic: Došlo je do greške prilikom uvoza te teme + upload: "Greška pri snimanju materijala: %{name}. %{errors}" + about_json: "Pogreška pri uvozu: about.json ne postoji ili je nevažeći. Jeste li sigurni da je ovo tema za Diskurs?" + about_json_values: "about.json sadrži nevažeće vrijednosti: %{errors}" + modifier_values: "about.json modifikatori sadrže nevažeće vrijednosti: %{errors}" + git: "Greška pri kloniranju git repozitorija, pristup je odbijen ili repozitorij nije pronađen" + git_ref_not_found: "Nije moguće provjeriti git referencu: %{ref}" + unpack_failed: "Greška pri otpakiravanju datoteke" + file_too_big: "Nekomprimirana datoteka je prevelika." + unknown_file_type: "Datoteka koju ste učitali ne čini se važećom Discourse temom." + not_allowed_theme: "`%{repo}` nije na popisu dopuštenih tema (provjerite globalnu postavku `allowed_theme_repos`)." + errors: + component_no_user_selectable: "Komponente teme ne može birati korisnik" + component_no_default: "Komponente teme ne mogu biti podrazumijevana tema." + component_no_color_scheme: "Komponente teme ne mogu imati palete boja" + no_multilevels_components: "Teme s vezanim temama ne mogu same biti vezane teme." + optimized_link: Optimizirane veze na slike su prolazne i ne bi trebale biti uključene u izvorni kod teme. + settings_errors: + invalid_yaml: "Zadani YAML je nevažeći" + data_type_not_a_number: "Vrsta postavke `%{name}` nije podržana. Podržane vrste su `integer`, `bool`, `list`, `enum` i `upload`" + name_too_long: "Postoji postavka s predugim imenom. Najveća dopuštena duljina je 255" + default_value_missing: "Postavka `%{name}` nema podrazumijevanu vrijednost." + default_not_match_type: "Tip zadane vrijednosti postavke `%{name}` ne podudara se s postavljenom vrijednošću postavke." + default_out_range: "Podrazumijevana vrijednost postavke `%{name}` nije u zadanom rasponu." + enum_value_not_valid: "Izabrana vrijednost nije jedna od izbora u enum nizu." + number_value_not_valid: "Nova vrijednost nije u dozvoljenom rasponu." + number_value_not_valid_min_max: "Mora biti između %{min} i %{max}." + number_value_not_valid_min: "Mora biti veće od ili jednako %{min}." + number_value_not_valid_max: "Mora biti manje od ili jednako %{max}." + string_value_not_valid: "Duljina nove vrijednosti nije u dozvoljenom rasponu." + string_value_not_valid_min_max: "Mora biti duga barem %{min} i najviše %{max} znakova." + string_value_not_valid_min: "Mora biti duga barem %{min} znakova." + string_value_not_valid_max: "Mora imati najviše %{max} znakova." + locale_errors: + top_level_locale: "Ključ najviše razine u datoteci jezika mora odgovarati nazivu jezika" + invalid_yaml: "Nevažeći YAML s prijevodom." + emails: + incoming: + default_subject: "Temi je nužno dodati naslov" + show_trimmed_content: "Prikaži skraćeni sadržaj" + no_subject: "(bez naslova)" + no_body: "(bez tijela poruke)" + missing_attachment: "(Nedostaje prilog %{filename})" + continuing_old_discussion: + one: "Nastavak rasprave od [%{title}](%{url}), jer je stvorena prije više od %{count} dana." + few: "Nastavak rasprave od [%{title}](%{url}), jer je stvorena prije više od %{count} dana." + other: "Nastavak rasprave od [%{title}](%{url}), jer je stvorena prije više od %{count} dana." + errors: + empty_email_error: "Događa se kada je izvorno primljeni mail prazan." + no_message_id_error: "Događa se kada mail nema 'Message-Id' zaglavlje." + auto_generated_email_error: "Događa se kada je 'precedence' zaglavlje postavljeno na: 'list', 'junk', 'bulk' ili 'auto_reply', ili bilo koje drugo zaglavlje sadrži: 'auto-submitted', 'auto-replied' ili 'auto-generated'." + no_body_detected_error: "Događa se kada nismo mogli otpakirati tijelo poruke i nije bilo priloga poruke." + no_sender_detected_error: "Događa se kada važeća email adresa nije pronađena u 'From' zaglavlju." + from_reply_by_address_error: "Događa se kada se zaglavlje pošiljaoca podudara s odgovorom prema adresi e-pošte." + inactive_user_error: "Događa se kada pošiljatelj nije aktivan." + silenced_user_error: "Događa se kada je pošiljatelj prigušen." + bad_destination_address: "Događa se kada nijedna adresa e-pošte u poljima Za/Kopija ne odgovara konfiguriranoj dolaznoj adresi e-pošte." + strangers_not_allowed_error: "Događa se kada korisnik pokuša započeti novu temu u kategoriji čiji nije član." + insufficient_trust_level_error: "Događa se kada korisnik pokuša započeti novu temu u kategoriji za koju nema potrebnu razinu povjerenja." + reply_user_not_matching_error: "Događa se kada je stigao odgovor s druge adrese e-pošte u odnosu na onu s koje je poslana obavijest." + topic_not_found_error: "Događa se kada je stigao odgovor, ali je povezana tema izbrisana." + topic_closed_error: "Događa se kada je stigao odgovor, ali je povezana tema zatvorena." + bounced_email_error: "E-pošta je izvješće o odbijenoj e-pošti." + screened_email_error: "Događa se kada je adresa e-pošte pošiljatelja već provjerena." + unsubscribe_not_allowed: "Događa se kada ovom korisniku nije dopuštena odjava putem e-pošte." + email_not_allowed: "Događa se kada adresa e-pošte nije na popisu dopuštenih ili je na popisu blokiranih." + unrecognized_error: "Neprepoznata pogreška" + secure_media_placeholder: "Redigirano: Ova stranica ima omogućen siguran medij. Posjetite temu ili kliknite Prikaži medije da biste vidjeli priloženi medij." + view_redacted_media: "Pregledajte medije" + errors: &errors + format: ! "%{attribute} %{message}" + format_with_full_message: "%{attribute}: %{message}" + messages: + too_long_validation: "je ograničeno na %{max} znakova; vi ste unjeli %{length}." + invalid_boolean: "Nevažeća boolean vrijednost." + taken: "je već zauzeto" + accepted: mora biti prihvaćena + blank: ne može biti prazno + present: mora biti prazno + confirmation: ! "ne odgovara %{attribute}" + empty: ne može biti prazno + equal_to: mora biti jednako %{count} + even: mora biti parno + exclusion: je rezervirano + greater_than: mora biti veće od %{count} + greater_than_or_equal_to: mora biti veće ili jednako %{count} + has_already_been_used: "je već korišteno" + inclusion: nije na listi + invalid: nije važeće + is_invalid: "čini se nejasno, jeste li napisali potpunu rečenicu?" + is_invalid_meaningful: "čini se nejasnim, većina riječi sadrži ista slova iznova i iznova?" + is_invalid_unpretentious: "čini se nejasnim, jedna ili više riječi su jako dugačke?" + is_invalid_quiet: "čini se nejasnim, jeste li mislili unijeti SVA VELIKA SLOVA?" + invalid_timezone: "'%{tz}' nije važeća vremenska zona" + contains_censored_words: "sadrži sljedeće cenzurirane riječi: %{censored_words}" + less_than: mora biti manje od %{count} + less_than_or_equal_to: mora biti manje ili jednako %{count} + not_a_number: nije broj + not_an_integer: mora biti cijeli broj + odd: mora biti neparan + record_invalid: ! "Provjera valjanosti nije uspjela: %{errors}" + max_emojis: "ne možete imati više od %{max_emojis_count} emojija" + emojis_disabled: "ne mogu imati emoji" + ip_address_already_screened: "je već uključeno u postojeće pravilo" + restrict_dependent_destroy: + one: "Nije moguće izbrisati zapis jer postoji ovisni %{record}" + few: "Nije moguće izbrisati zapis jer postoje ovisni %{record}" + other: "Nije moguće izbrisati zapis jer postoje ovisni %{record}" + too_long: + one: je predug (maksimalno %{count} znakova) + few: su predugi (maksimalno %{count} znakova) + other: je predug (maksimalno %{count} znakova) + too_short: + one: je prekratak (minimum je %{count} znakova) + few: su prekratki (minimum je %{count} znakova) + other: su prekratki (minimum je %{count} znakova) + wrong_length: + one: je pogrešna duljina (treba biti %{count} znak) + few: je pogrešna duljina (treba biti %{count} znakova) + other: je pogrešna duljina (treba biti %{count} znakova) + other_than: "mora biti različito od %{count}" + auth_overrides_username: "Korisničko ime se mora ažurirati na strani davatelja provjere autentičnosti jer je omogućena postavka \"auth_overrides_username\"." + template: + body: ! "Bilo je problema sa sljedećim poljima:" + embed: + load_from_remote: "Dogodila se greška pri učitavanju objave." + bulk_invite: + error: "Dogodila se greška pri učitavanju te datoteke. Molimo kasnije pokušajte ponovno." + backup: + operation_already_running: "Operacija je trenutno u toku. Trenutno ne možemo započeti novi posao." + backup_file_should_be_tar_gz: "Sigurnosna kopija bi trebala biti .tar.gz arhiv." + not_enough_space_on_disk: "Nema dovoljno prostora na disku za učitanje sigurnosne kopije." + not_logged_in: "Morate biti prijavljeni da to napravite." + read_only_mode_enabled: "Ova je stranica u \"samo čitanje\" modelu. Interakcije nemoguće." + not_in_group: + join_group: "Pristupi grupi" + reading_time: "Vrijeme čitanja" + likes: "Like-ova" + too_many_replies: + one: "Žao nam je ali novi korisnici su privremeno ograničeni na %{count} odgovora u istoj temi." + few: "Žao nam je ali novi korisnici su privremeno ograničeni na %{count} odgovora u istoj temi." + other: "Žao nam je ali novi korisnici su privremeno ograničeni na %{count} odgovora u istoj temi." + max_consecutive_replies: + one: "Nije dopušteno više odgovora za redom. Molimo uredite vaš prethodni odgovor ili pričekajte da vam netko drugi odgovori." + few: "Dopušteno je najviše %{count} odgovora za redom. Molimo uredite vaš prethodni odgovor ili pričekajte da vam netko drugi odgovori." + other: "Dopušteno je najviše %{count} odgovora za redom. Molimo uredite vaš prethodni odgovor ili pričekajte da vam netko drugi odgovori." + embed: + start_discussion: "Pokreni raspravu" + continue: "Nastavi raspravu" + more_replies: + one: "još %{count} odgovor" + few: "još %{count} odgovora" + other: "još %{count} odgovora" + loading: "Učitavanje rasprave" + permalink: "Poveznica" + imported_from: "Ovo je prateća tema rasprave za originalni unos na %{link}" + in_reply_to: "▶ %{username}" + replies: + one: "%{count} odgovor" + few: "%{count} odgovora" + other: "%{count} odgovora" + likes: + one: "%{count} like" + few: "%{count} likeova" + other: "%{count} likeova" + last_reply: "Posljednji odgovor" + created: "Stvoreno" + no_mentions_allowed: "Žao nam je, ne možete spominjati druge korisnike." + too_many_mentions: + one: "Žao nam je, možete spomenuti samo jednog korisnika u objavi." + few: "Žao nam je, možete spomenuti najviše %{count} korisnika u objavi." + other: "Žao nam je, možete spomenuti najviše %{count} korisnika u objavi." + no_mentions_allowed_newuser: "Žao nam je, novi korisnici ne mogu spominjati druge korisnike." + too_many_mentions_newuser: + one: "Žao nam je, novi korisnici mogu spomenuti samo jednog korisnika u objavi." + few: "Žao nam je, novi korisnici mogu spomenuti samo %{count} korisnika u objavi." + other: "Žao nam je, novi korisnici mogu spomenuti samo %{count} korisnika u objavi." + no_attachments_allowed: "Žao nam je novi korisnici ne mogu stavljati privitke u objave." + too_many_attachments: + one: "Žao nam je, novi korisnici mogu dodati samo jedan prilog u objavu." + few: "Žao nam je, novi korisnici mogu dodati najviše %{count} priloga u objavu." + other: "Žao nam je, novi korisnici mogu dodati najviše %{count} priloga u objavu." + no_links_allowed: "Žao nam je. novi korisnici ne mogu stavljati poveznice." + links_require_trust: "Žao nam je, ne možete dodati poveznice u objavu." + too_many_links: + one: "Žao nam je, novi korisnici mogu dodati samo jednu poveznicu u objavu." + few: "Žao nam je, novi korisnici mogu dodati najviše %{count} poveznica u objavu." + other: "Žao nam je, novi korisnici mogu dodati najviše %{count} poveznica u objavu." + spamming_host: "Žao nam je, ne možete objaviti poveznicu na tog domaćina." + user_is_suspended: "Suspendiranim korisnicima nije dozvoljeno objavljivanje." + not_accepting_pms: "Žao nam je, %{username} trenutno ne prihvaća poruke." + pm_reached_recipients_limit: "Žao nam je, ne možete imati više od %{recipients_limit} primatelja u poruci." + just_posted_that: "je pre slično nečemu što ste nedavno objavili" + invalid_characters: "sadrži nevažeće znakove" + is_invalid: "čini se nejasno, jeste li napisali potpunu rečenicu?" + next_page: "slijedeća stranica →" + prev_page: "← prethodna stranica" + page_num: "Stranica %{num}" + home_title: "Naslovnica" + topics_in_category: "Teme u '%{category}' kategoriji." + rss_posts_in_topic: "RSS kanal '%{topic}'" + rss_topics_in_category: "RSS kanal tema u '%{category}' kategoriji" + rss_num_posts: + one: "%{count} objava" + few: "%{count} objava" + other: "%{count} objava" + read_full_topic: "Pročitao cijelu temu" + private_message_abbrev: "Poruka" + rss_description: + latest: "Posljednje teme" + top: "Top teme" + top_all: "Top teme svih vremena" + top_yearly: "Top teme godine" + top_quarterly: "Top teme kvartala" + top_monthly: "Top teme mjeseca" + top_weekly: "Tjedne top teme" + top_daily: "Top teme dana" + posts: "Najnovije objave" + private_posts: "Najnovije osobne poruke" + group_posts: "Najnovije objave grupe %{group_name}" + group_mentions: "Najnovije spominjanje od grupe %{group_name}" + user_posts: "Najnovije objave @%{username}" + user_topics: "Najnovije teme @%{username}" + tag: "Tagirane teme" + badge: "%{display_name} značka na %{site_title}" + too_late_to_edit: "Ova je objava stvorena pre davno. Ne može se više izmijenjivati ni brisati." + edit_conflict: "Ovu objavu uredio je drugi korisnik i vaše promjene ne mogu biti spremljene." + excerpt_image: "slika" + bookmarks: + reminders: + at_desktop: "Sljedeći put kad sam kod svoje radne površine" + later_today: "Kasnije danas" + next_business_day: "Idući radni rad" + tomorrow: "Sutra" + next_week: "Idući tjedan" + next_month: "Idući mjesec" + custom: "Prilagođeni datum i vrijeme" + groups: + success: + bulk_add: + one: "%{count} korisnik je dodan u grupu." + few: "%{count} korisnika je dodano u grupu." + other: "%{count} korisnika je dodano u grupu." + errors: + grant_trust_level_not_valid: "Razina povjerenja '%{trust_level}' nije važeća." + invalid_domain: "'%{domain}' nije važeća domena." + invalid_incoming_email: "'%{email}' nije važeća email adresa." + usernames_or_emails_required: "Moraju biti prisutna korisnička imena ili e-mail adrese" + default_names: + everyone: "svi" + admins: "administratori" + moderators: "moderatori" + staff: "osoblje" + trust_level_0: "razina_povjerenja_0" + trust_level_1: "razina_povjerenja_1" + trust_level_2: "razina_povjerenja_2" + trust_level_3: "razina_povjerenja_3" + trust_level_4: "razina_povjerenja_4" + request_accepted_pm: + title: "Primljeni ste u @%{group_name}" + education: + until_posts: + one: "%{count} objava" + few: "%{count} objava" + other: "%{count} objava" + activerecord: + attributes: + category: + name: "Ime kategorije" + topic: + title: "Naslov" + category_id: "Kategorija" + post: + raw: "Tijelo" + user_profile: + bio_raw: "O meni" + errors: + models: + topic: + attributes: + base: + too_many_users: "Upozorenja možete slati samo po jednom korisniku od jednom." + no_user_selected: "Morate odabrati važećeg korisnika." + user: + attributes: + password: + common: "je jedna od 10000 najčešćih zaporki. Molimo odaberite sigurniju zaporku." + same_as_username: "je ista kao vaše korisničko ime. Molimo upotrijebite sigurniju lozinku." + same_as_email: "je ista kao vaš email. Molimo upotrijebite sigurniju lozinku." + same_as_current: "je ista kao vaša trenutna lozinka." + same_as_name: "je ista kao vaše ime." + unique_characters: "ima previše ponovljenih znakova. Molimo upotrijebite sigurniju lozinku." + username: + same_as_password: "je ista kao vaša lozinka." + name: + same_as_password: "je ista kao vaša lozinka." + ip_address: + signup_not_allowed: "Prijava nije dopuštena ovom računu." + color_scheme_color: + attributes: + hex: + invalid: "nije važeća boja" + web_hook: + attributes: + payload_url: + invalid: "URL nije važeći. URL mora sadržavati http:// ili https:// i ne smije imati razmake." + topic_timer: + attributes: + execute_at: + in_the_past: "mora biti u budućnosti." + <<: *errors + uncategorized_category_name: "Nekategorizirano" + vip_category_name: "Foaje" + vip_category_description: "Kategorija ekskluzivna članovima koji imaju razinu povjerenja 3 ili više." + meta_category_name: "Povratne informacije o stranici" + meta_category_description: "Rasprave o ovoj stranici, njezinoj organizaciji, kako radi i kako je se može poboljšati." + staff_category_name: "Osoblje" + staff_category_description: "Privatna kategorija za rasprave osoblja. Teme su vidljive samo administratorima i moderatorima." + discourse_welcome_topic: + title: "Dobrodošli na Discourse" + lounge_welcome: + title: "Dobrodošli u Foaje" + admin_quick_start_title: "PROČITATI PRVO: brzi vodič za administratore" + category: + topic_prefix: "O %{category} kategoriji" + errors: + not_found: "Kategorija nije pronađena!" + uncategorized_parent: "Nekategorizirano ne može imati nadkategoriju" + self_parent: "Kategorija ne može biti sama sebi nadkategorija" + depth: "Ne možete postaviti podkategoriju pod neku drugu" + invalid_email_in: "'%{email}' nije važeća email adresa." + cannot_delete: + has_subcategories: "Ne možete obrisati ovu kategoriju jer ima podkategorije" + topic_exists_no_oldest: "Ne možete obrisati ovu kategoriju jer je broj tema %{count}." + trust_levels: + admin: "Administrator" + staff: "Osoblje" + change_failed_explanation: "Pokušali ste sanjiti razinu povjerenja %{user_name} na '%{new_trust_level}'. Ali njihova razina povjerenja već jest '%{current_trust_level}'. %{user_name} će ostati na '%{current_trust_level}' - ako želite korisniku smanjiti razinu povjerenja prvo je zaključajte." + post: + has_likes: + one: "%{count} Like" + few: "%{count} Likeova" + other: "%{count} Likeova" + rate_limiter: + hours: + one: "%{count} sat" + few: "%{count} sati" + other: "%{count} sati" + minutes: + one: "%{count} minuta" + few: "%{count} minuta" + other: "%{count} minuta" + seconds: + one: "%{count} sekunda" + few: "%{count} sekundi" + other: "%{count} sekundi" + datetime: + distance_in_words: + half_a_minute: "< 1m" + less_than_x_seconds: + one: "< %{count}s" + few: "< %{count}s" + other: "< %{count}s" + x_seconds: + one: "%{count}s" + few: "%{count}s" + other: "%{count}s" + less_than_x_minutes: + one: "< %{count}m" + few: "< %{count}m" + other: "< %{count}m" + x_minutes: + one: "%{count}m" + few: "%{count}m" + other: "%{count}m" + about_x_hours: + one: "%{count}h" + few: "%{count}h" + other: "%{count}h" + x_days: + one: "%{count}d" + few: "%{count}d" + other: "%{count}d" + about_x_months: + one: "%{count}mjesec" + few: "%{count}mjeseci" + other: "%{count}mjeseci" + x_months: + one: "%{count}mjesec" + few: "%{count}mjeseci" + other: "%{count}mjeseci" + about_x_years: + one: "%{count}god." + few: "%{count}god." + other: "%{count}god." + over_x_years: + one: "> %{count}god." + few: "> %{count}god." + other: "> %{count}god." + almost_x_years: + one: "%{count}god." + few: "%{count}god." + other: "%{count}god." + distance_in_words_verbose: + half_a_minute: "upravo sad" + less_than_x_seconds: "upravo sad" + x_seconds: + one: "prije %{count} sekunde" + few: "prije %{count} sekundi" + other: "prije %{count} sekundi" + less_than_x_minutes: + one: "prije manje od %{count} minuta" + few: "prije manje od %{count} minuta" + other: "prije manje od %{count} minuta" + x_minutes: + one: "prije %{count} minuta" + few: "prije %{count} minuta" + other: "prije %{count} minuta" + about_x_hours: + one: "prije %{count} sat" + few: "prije %{count} sati" + other: "prije %{count} sati" + x_days: + one: "prije %{count} dana" + few: "prije %{count} dana" + other: "prije %{count} dana" + about_x_months: + one: "prije otprilike %{count} mjesec" + few: "prije otprilike %{count} mjeseci" + other: "prije otprilike %{count} mjeseci" + x_months: + one: "prije %{count} mjesec" + few: "prije %{count} mjeseci" + other: "prije %{count} mjeseci" + about_x_years: + one: "prije otprilike %{count} godinu" + few: "prije otprilike %{count} godina" + other: "prije otprilike %{count} godina" + over_x_years: + one: "prije više od %{count} godinu" + few: "prije više od %{count} godina" + other: "prije više od %{count} godina" + almost_x_years: + one: "prije skoro %{count} godinu" + few: "prije skoro %{count} godina" + other: "prije skoro %{count} godina" + password_reset: + choose_new: "Izaberite novu lozinku" + choose: "Izaberite lozinku" + update: "Ažurirajte zaporku" + save: "Postavite zaporku" + title: "Ponovo postavite zaporku" + success: "Uspiješno ste promijenili zaporku i sad ste prijavljeni." + success_unapproved: "Uspiješno ste promijenili zaporku." + email_login: + invalid_token: "Žao nam je, ta poveznica za prijavu emailom je prestara. Odaberite gumb 'Prijava' i kliknite na 'Zaboravljena lozinka' kako bi dobili novu poveznicu." + title: "Prijava emailom" + user_auth_tokens: + browser: + chrome: "Google Chrome" + discoursehub: "DiscourseHub aplikacija" + edge: "Microsoft Edge" + firefox: "Firefox" + ie: "Internet Explorer" + opera: "Opera" + safari: "Safari" + unknown: "nepoznati preglednik" + device: + android: "Android uređaj" + ipad: "iPad" + iphone: "iPhone" + ipod: "iPod" + linux: "GNU/Linux računalo" + mac: "Mac" + mobile: "Mobilni uređaj" + windows: "Windows računalo" + unknown: "nepoznati uređaj" + os: + android: "Android" + ios: "iOS" + linux: "Linux" + macos: "macOS" + windows: "Microsoft Windows" + unknown: "nepoznati operacijski sustav" + change_email: + confirmed: "Email vam je ažuriran." + please_continue: "Nastavite na %{site_name}" + error: "Dogodila se greška u promijeni tvog e-maila. Možda se ta adresa već koristi?" + error_staged: "Dogodila se greška pri promjeni vaše email adrese. Adresu već koristi drugi korisnik." + already_done: "Žao nam je, ova poveznica za potvrdu nije više važeća. Možda je vaš email već promijenjen?" + associated_accounts: + connected: "(povezano)" + activation: + action: "Kliknite ovdje kako bi aktivirali vaš korisnički račun" + already_done: "Žao nam je, poveznica za aktivaciju računa više nije važeća. Možda vam je račun već aktivan?" + please_continue: "Vaš novi račun je potvrđen, biti će te preusmjereni na glavnu stranicu." + continue_button: "Nastavite na %{site_name}" + welcome_to: "Dobrodošli na %{site_name}" + approval_required: "Moderator vam mora ručno odobriti račun da bi mogli pristupiti ovom forumu. Dobiti će te email kad vam račun bude odobren!" + missing_session: "Ne možemo utvrditi je li vaš korisnički račun kreiran, molimo osigurajte da su vam omogućeni cookieji (kolačići)." + activated: "Žao nam je, ovaj korisnički račun je već aktiviran." + admin_confirm: + title: "Potvrdite račun administratora." + grant: "Dozvolite administratorski pristup" + complete: "%{target_username} je sada administrator." + back_to: "Vratite se na %{title}" + reviewable_score_types: + needs_approval: + title: "Potrebna potvrda" + post_action_types: + off_topic: + title: "Van teme" + description: "Ova objava nije relevantna za trenutnu raspravu opisanu naslovom i prvom objavom, Vjerovatno ju se treba premjestiti." + short_description: "Nije bitno za raspravu" + spam: + title: "Nepoželjno" + description: "Ova objava je oglas ili vandalizam. Nije korisna ili bitna za trenutnu temu." + short_description: "Ovo je oglas ili vandalizam." + email_title: '"%{title}" je označeno kao spam' + email_body: "%{link}\n\n%{message}" + inappropriate: + title: "Neprikladno" + notify_user: + title: "Pošaljite poruku @%{username}" + email_title: 'Vaša objava u "%{title}"' + email_body: "%{link}\n\n%{message}" + notify_moderators: + title: "Nešto drugo" + email_body: "%{link}\n\n%{message}" + bookmark: + title: "Zabilješka" + description: "Zabilježi objavu" + short_description: "Zabilježi objavu" + like: + title: "Like-aj" + description: "Like-aj objavu" + short_description: "Like-aj objavu" + topic_flag_types: + spam: + title: "Nepoželjno" + description: "Ova tema je reklama. Nije korisno niti relevantno ovoj stranici već služi u promotivne svrhe." + long_form: "označio kao nepoželjno" + short_description: "Ovo je oglas" + inappropriate: + title: "Neprikladno" + long_form: "označio ovo kao neprikladno" + notify_moderators: + title: "Nešto drugo" + long_form: "označio za moderatorsku pozornost" + email_title: 'Tema "%{title}" zahtjeva moderatorsku pozornost' + email_body: "%{link}\n\n%{message}" + flagging: + user_must_edit: "

Ovu je objavu zajednica označila zastavicom i privremeno je skrivena.

" + archetypes: + regular: + title: "Obična tema" + banner: + title: "istaknuta objava" + message: + make: "Ova tema je sada \"banner\". Pojaviti će se na vrhu svake stranice dok ju korisnik ne odbaci." + remove: "Ova tema više nije \"banner\". Neće se više prikazivati na vrhu svake stranice." + unsubscribe: + title: "Odjava" + stop_watching_topic: "Prestanite pratiti ovu temu, %{link}" + mute_topic: "Prigušite sve obavijesti za ovu temu, %{link}" + log_out: "Odjava" + submit: "Spremite postavke" + digest_frequency: + never: "nikad" + every_30_minutes: "svakih 30 minuta" + every_hour: "svaki sat" + daily: "dnevno" + weekly: "tjedno" + every_month: "svaki mjesec" + every_six_months: "svakih šest mjeseci" + user_api_key: + read: "pročitano" + otp_confirmation: + confirm_title: Nastavite na %{site_name} + logging_in_as: Prijava kao %{username} + confirm_button: Završi prijavu + scopes: + push: "Push obavijesti vanjskim uslugama" + reports: + default: + labels: + count: Količina + day: Dan + post_edits: + labels: + edited_at: Datum + post: Objava + editor: Urednik + edit_reason: Razlog + user_flagging_ratio: + labels: + user: Korisnik + score: Ocjena + moderators_activity: + labels: + moderator: Moderator + flags_status: + labels: + flag: Tip + visits: + title: "Posjete korisnika" + xaxis: "Dan" + yaxis: "Broj posjeta" + signups: + xaxis: "Dan" + new_contributors: + xaxis: "Dan" + trust_level_growth: + yaxis: "Dan" + consolidated_page_views: + yaxis: "Dan" + labels: + post: Objava + editor: Urednik + edit_reason: Razlog + dau_by_mau: + xaxis: "Dan" + daily_engaged_users: + xaxis: "Dan" + profile_views: + xaxis: "Dan" + topics: + title: "Teme" + xaxis: "Dan" + yaxis: "Broj novih tema" + posts: + title: "Objave" + xaxis: "Dan" + yaxis: "Broj novih objava" + likes: + title: "Like-ova" + xaxis: "Dan" + yaxis: "Broj novih like-ova" + description: "Broj novih like-ova." + flags: + title: "Oznake zastavicom" + xaxis: "Dan" + yaxis: "Broj oznaka zastavicom" + bookmarks: + title: "Zabilješke" + xaxis: "Dan" + yaxis: "Broj novih zabilješki" + users_by_trust_level: + title: "Korisnika po razini povjerenja" + xaxis: "Razina povjerenja" + yaxis: "Broj korisnika" + description_link: "https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/" + users_by_type: + xaxis: "Tip" + yaxis: "Broj korisnika" + labels: + type: Tip + xaxis_labels: + admin: Administrator + moderator: Moderator + suspended: Suspendirani + silenced: Utišano + emails: + title: "Poslano emailova" + xaxis: "Dan" + yaxis: "Broj emailova" + user_to_user_private_messages: + xaxis: "Dan" + user_to_user_private_messages_with_replies: + xaxis: "Dan" + system_private_messages: + title: "Sistem" + xaxis: "Dan" + moderator_warning_private_messages: + title: "Moderatorskih upozorenja" + xaxis: "Dan" + notify_moderators_private_messages: + title: "Obavijesti moderatore" + xaxis: "Dan" + notify_user_private_messages: + title: "Obavijesti korisnika" + xaxis: "Dan" + top_referrers: + title: "Najaktivniji preporučitelji" + xaxis: "Korisnik" + num_clicks: "Klikova" + num_topics: "Tema" + labels: + user: "Korisnik" + num_clicks: "Klikova" + num_topics: "Teme" + top_traffic_sources: + title: "Najaktivnijih izvora prometa" + xaxis: "Domena" + num_clicks: "Klikova" + num_topics: "Tema" + num_users: "Korisnika" + labels: + domain: Domena + num_clicks: Klikova + num_topics: Teme + top_referred_topics: + title: "Najpreporučavanije teme" + labels: + num_clicks: "Klikova" + topic: "Teme" + page_view_anon_reqs: + title: "Anonimno" + xaxis: "Dan" + page_view_logged_in_reqs: + xaxis: "Dan" + page_view_crawler_reqs: + xaxis: "Dan" + page_view_total_reqs: + title: "Pregledi stranica" + xaxis: "Dan" + page_view_logged_in_mobile_reqs: + xaxis: "Dan" + page_view_anon_mobile_reqs: + xaxis: "Dan" + http_background_reqs: + xaxis: "Dan" + http_2xx_reqs: + title: "Status 2xx (OK)" + xaxis: "Dan" + http_3xx_reqs: + xaxis: "Dan" + http_4xx_reqs: + title: "HTTP 4xx (Klijentska greška)" + xaxis: "Dan" + yaxis: "Klijentske greške (Status 4xx)" + http_5xx_reqs: + xaxis: "Dan" + http_total_reqs: + title: "Ukupno" + xaxis: "Dan" + time_to_first_response: + xaxis: "Dan" + yaxis: "Prosječno vrijeme (u satima)" + description: "Prosječno vrijeme (u satima) do prvog odgovora u novim temama." + topics_with_no_response: + xaxis: "Dan" + yaxis: "Ukupno" + mobile_visits: + xaxis: "Dan" + yaxis: "Broj posjeta" + web_crawlers: + labels: + page_views: "Pregledi stranica" + suspicious_logins: + labels: + user: Korisnik + location: Lokacija + staff_logins: + labels: + user: Korisnik + location: Lokacija + top_uploads: + labels: + filename: Ime datoteke + top_users_by_likes_received: + labels: + user: Korisnik + qtt_like: Like-ova primljeno + top_users_by_likes_received_from_inferior_trust_level: + labels: + user: Korisnik + qtt_like: Like-ova primljeno + top_users_by_likes_received_from_a_variety_of_people: + labels: + user: Korisnik + qtt_like: Like-ova primljeno + dashboard: + rails_env_warning: "Server vam radi na %{env} način." + host_names_warning: "Vaša config/database.yml datoteka koristi standardno ime lokalnog domaćina (localhost hostname). Ažurirajte je da koristi ime vaše stranice." + sidekiq_warning: 'Sidekiq ne radi. Mnogi zadatci, kao slanje emailova, obavljaju se asinhrono putem sidekiqa. Molimo osigurajte da barem jedan sidekiq provess radi. Ovdje naučite o sidekiqu.' + queue_size_warning: "Broj predbilježenih zadataka je %{queue_size}, što je velik broj. Ovo može biti pokazatelj problema za Sidekiq procesom(ima), ili trebate dodati još Sidekiq \"radnika\"." + memory_warning: "Server vam radi s manje od 1 GB ukupne memorije. Barem 1 GB se preporuča." + site_settings: + disabled: "onemogućen" + version_checks: "Pingaj Discourse Hub za dostupna ažuriranja i prikaži obavijesti o novim verzijama na /admin nadzornoj ploči" + dark_mode_none: "ništa" + emoji_set: "Kakave emoji-e želite?" + default_other_skip_new_user_tips: "Preskoči savjete i značke novog korisnika." + company_name: "Ime tvrtke" + errors: + invalid_string_max: "Ne smije imati više od %{max} znakova." + search: + within_post: "#%{post_number} od %{username}" + types: + category: "Kategorije" + topic: "Rezultati" + user: "Korisnici" + results_page: "Rezultati pretraživanja za '%{term}'" + publish_page: + slug_errors: + blank: "ne može biti prazno" + invalid: "sadrži nevažeće znakove" + login: + security_key_description: "Kada pripremite svoj fizički sigurnosni ključ, pritisnite gumb Autentifikacija sa sigurnosnim ključem u nastavku." + security_key_alternative: "Pokušaj na drugi način" + security_key_authenticate: "Autentifikacija sa sigurnosnim ključem" + security_key_not_allowed_error: "Proces provjere autentičnosti sigurnosnog ključa je istekao ili je otkazan." + security_key_no_matching_credential_error: "U priloženom sigurnosnom ključu nije moguće pronaći odgovarajuće vjerodajnice." + security_key_support_missing_error: "Vaš trenutni uređaj ili preglednik ne podržava korištenje sigurnosnih tipki. Molimo koristite drugu metodu." + not_approved: "Vaš račun još nije odobren. Bit ćete obaviješteni putem e-maila kada budete spremni za prijavu." + admin_not_allowed_from_ip_address: "Ne možete se prijaviti kao administrator s te IP adrese." + errors: "%{errors}" + not_available: "Nije dostupno. Pokušajte %{suggestion}?" + omniauth_confirm_button: "Nastavi" + second_factor_title: "Dvofaktorska autentifikacija" + second_factor_backup_description: "Unesite jedan od vaših rezervnih kodova:" + second_factor_toggle: + backup_code: "Umjesto toga upotrijebite sigurnosni kod" + admin: + email: + sent_test: "poslano!" + user: + username: + long: "ne smije imati više od %{max} znakova" + must_not_contain_two_special_chars_in_seq: "ne smije sadržavati niz od 2 ili više specijalna znaka (.-_)" + system_messages: + queued_by_staff: + title: "natpis čeka potvrdu" + welcome_user: + subject_template: "Dobrodošli na %{site_name}" + welcome_invite: + subject_template: "Dobrodošli na %{site_name}" + dashboard_problems: + title: "Problemi nadzorne ploče" + subject_template: "Novi savjeti na vašoj nadzornoj ploči" + user_notifications: + unsubscribe: + title: "Odjava" + user_quoted: + title: "Korisnik je citirao" + digest: + unread_notifications: "Nepročitane obavijesti" + liked_received: "Like-ova primljeno" + new_users: "Novi korisnici" + join_the_discussion: "Pročitaj više" + forgot_password: + title: "Zaboravljena zaporka" + set_password: + title: "Postavite zaporku" + post_approved: + title: "Vaš post je odobren" + page_not_found: + page_title: "Stranica nije pronađena" + popular_topics: "Popularno" + see_more: "Više" + search_title: "Pretraži ovu stranicu" + search_button: "Pretraži" + login_required: + welcome_message: | + ## [Dobrodošli na %{title}](#welcome) + Nužno je prijaviti se korisničkim računom. Molimo učlanite se ili prijavite za nastavak. + welcome_message_invite_only: | + ## [Dobrodošli na %{title}] (#welcome) + Nužno je prijaviti se korisničkim računom. Molimo upitajte postojećeg korisnika za pozivnicu ili se prijavite za nastavak. + deleted: "izbrisao" + image: "slika" + upload: + unauthorized: "Nažalost, datoteka koju pokušavate prenijeti nije autorizirana (autorizirana proširenja: %{authorized_extensions})." + color_schemes: + default_theme_name: "Zadano" + csv_export: + boolean_yes: "Da" + boolean_no: "Ne" + rate_limit_error: "Objave mogu biti preuzimane jednom na dan, molimo pokušajte sutra." + tos_topic: + title: "Uvjeti korištenja" + privacy_topic: + title: "Izjava o privatnosti" + badges: + editor: + name: Urednik + description: Prva izmjena objave + basic_user: + name: Osnovno + member: + name: Član + regular: + name: Stalni član + leader: + name: Vođa + welcome: + name: Dobrodošao + description: Primio like + autobiographer: + name: Autobiograf + anniversary: + name: Godišnjica + description: Aktivni član kroz jednu godinu, postao najmanje jednom + nice_topic: + name: Ljepa tema + good_topic: + name: Dobra tema + great_topic: + name: Odlična tema + nice_share: + name: Ljepo dijeljenje + description: Podijelio objavu s 25 jedinstvenih posjetitelja. + good_share: + name: Dobro dijeljenje + description: Podijelio objavu s 300 jedinstvenih posjetitelja. + great_share: + name: Odlično dijeljenje + description: Podijelio objavu s 1000 jedinstvenih posjetitelja. + first_like: + name: Prvi like + description: Like-ao post + first_flag: + name: Prva zastavica + description: Označio objavu + first_share: + name: Prva podjela + description: Podijelio objavu + first_link: + name: Prva poveznica + first_quote: + name: Prvi citat + read_guidelines: + name: Pročitao smjernice + reader: + name: Čitatelj + badge_title_metadata: "%{display_name} značka na %{site_title}" + tags: + title: "Oznake" + finish_installation: + register: + button: "Registracija" + resend_email: + title: "Ponovno pošaljite e-poštu za aktivaciju" + wizard: + step: + forum_title: + title: "Ime i prezime" + introduction: + fields: + welcome: + label: "Rasprava dobrodošlice" + privacy: + title: "Pristup" + fields: + privacy: + choices: + open: + label: "Javno" + restricted: + label: "Privatno" + contact: + fields: + contact_email: + placeholder: "ime@primjer.com" + contact_url: + placeholder: "https://www.example.com/contact-us" + corporate: + title: "Organizacija / Poduzeće" + fields: + company_name: + label: "Ime tvrtke" + styling: + fields: + homepage_style: + choices: + categories_only: + label: "Samo kategorije" + categories_with_featured_topics: + label: "Kategorije s istaknutim temama" + categories_and_latest_topics: + label: "Kategorije i najnovije teme" + categories_and_top_topics: + label: "Kategorije i glavne teme" + subcategories_with_featured_topics: + label: "Potkategorije s istaknutim temama" + joined: "Prijavljen" + discourse_push_notifications: + popup: + mentioned: '%{username} vas je spomenuo u "%{topic}" - %{site_title}' + group_mentioned: '%{username} vas je spomenuo u "%{topic}" - %{site_title}' + quoted: '%{username} citirao vas je u "%{topic}" - %{site_title}' + replied: '%{username} odgovorio u "%{topic}" - %{site_title}' + posted: '%{username} objavio u "%{topic}" - %{site_title}' + linked: '%{username} se povezao s tvojom objavom "%{topic}" - %{site_title}' + watching_first_post: '%{username} stvorio je novu temu "%{topic}" - %{site_title}' + confirm_title: "Obavijesti omogućene - %{site_title}" + confirm_body: "Uspjeh! Obavijesti su omogućene." + custom: "Obavijest od %{username} %{site_title}" + reviewables: + priorities: + low: "Nizak" + medium: "Srednji" + high: "Visok" + sensitivity: + disabled: "Onemogućeno" + low: "Nizak" + medium: "Srednji" + high: "Visok" + actions: + agree_and_suspend: + title: "Suspendiraj korisnika" + agree_and_silence: + title: "Ušuti korisnika" + delete_single: + title: "Pobriši" + disagree: + title: "Odbaci" + ignore: + title: "Zanemari" + approve: + title: "Odobri" + reject_user: + delete: + title: "Obriši korisnika" + reject: + title: "Odbij" + fallback_username: "korisnik" + activemodel: + errors: + <<: *errors diff --git a/lib/javascripts/locale/hr.js b/lib/javascripts/locale/hr.js index 668e28e2b1..815bf174ab 100644 --- a/lib/javascripts/locale/hr.js +++ b/lib/javascripts/locale/hr.js @@ -6,9 +6,5 @@ MessageFormat.locale.hr = function (n) { ((n % 100) < 12 || (n % 100) > 14) && n == Math.floor(n)) { return 'few'; } - if ((n % 10) === 0 || ((n % 10) >= 5 && (n % 10) <= 9) || - ((n % 100) >= 11 && (n % 100) <= 14) && n == Math.floor(n)) { - return 'many'; - } return 'other'; }; diff --git a/plugins/discourse-details/config/locales/client.hr.yml b/plugins/discourse-details/config/locales/client.hr.yml new file mode 100644 index 0000000000..2205ea4fdf --- /dev/null +++ b/plugins/discourse-details/config/locales/client.hr.yml @@ -0,0 +1,13 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + details: + title: Sakrij detalje + composer: + details_title: Sažetak + details_text: "Ovaj će tekst biti skriven" diff --git a/plugins/discourse-details/config/locales/server.hr.yml b/plugins/discourse-details/config/locales/server.hr.yml new file mode 100644 index 0000000000..81b5e168b4 --- /dev/null +++ b/plugins/discourse-details/config/locales/server.hr.yml @@ -0,0 +1,9 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + details: + excerpt_details: "(pritisni za više detalja)" diff --git a/plugins/discourse-local-dates/config/locales/client.hr.yml b/plugins/discourse-local-dates/config/locales/client.hr.yml new file mode 100644 index 0000000000..97bcd019af --- /dev/null +++ b/plugins/discourse-local-dates/config/locales/client.hr.yml @@ -0,0 +1,42 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + discourse_local_dates: + relative_dates: + today: Danas %{time} + tomorrow: Sutra %{time} + yesterday: Jučer %{time} + countdown: + passed: datum je prošao + title: Umetni datum / vrijeme + create: + form: + insert: Umetni + advanced_mode: Napredni način rada + simple_mode: Jednostavni način rada + format_description: "Format koji se koristi za prikaz datuma korisniku. Pomoću Z možete prikazati pomak i zz za naziv vremenske zone." + timezones_title: Vremenske zone za prikazati + timezones_description: Vremenske zone će se koristiti za prikaz datuma u predprikazu i rezervnom prikazu. + recurring_title: Ponavljanje + recurring_description: "Definirajte ponavljanje događaja. Možete ručno urediti ponavljajuču opciju koju je izgenerirala forma, te koristiti neku od sljedećih ključnih riječi: years, quarters, months, weeks, days, hours, minutes, seconds, milliseconds." + recurring_none: Nema ponavljanja + invalid_date: Nevažeći datum, provjerite ispravnost datuma i vremena + date_title: Datum + time_title: Vrijeme + format_title: Format datuma + timezone: Vremenska zona + until: Do... + recurring: + every_day: "Svaki dan" + every_week: "Svaki tjedan" + every_two_weeks: "Svaka dva tjedna" + every_month: "Svaki mjesec" + every_two_months: "Svaka dva mjeseca" + every_three_months: "Svaka tri mjeseca" + every_six_months: "Svakih šest mjeseci" + every_year: "Svake godine" diff --git a/plugins/discourse-local-dates/config/locales/server.hr.yml b/plugins/discourse-local-dates/config/locales/server.hr.yml new file mode 100644 index 0000000000..eca6352f7a --- /dev/null +++ b/plugins/discourse-local-dates/config/locales/server.hr.yml @@ -0,0 +1,12 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + site_settings: + discourse_local_dates_enabled: "Uključire discourse-local-dates mogućnost. Ovo će dati podršku datumima iz objava koji ovise o lokalnim vremenskim zonama i koriste [date] element." + discourse_local_dates_default_formats: "Često upotrebljavani formati datuma i vremena, vidi: momentjs string format" + discourse_local_dates_default_timezones: "Zadana lista vremenskih zona, mora biti važeća TZ" + discourse_local_dates_email_format: "Format koji se koristi za prikaz datuma u email-ovima." diff --git a/plugins/discourse-narrative-bot/config/locales/client.hr.yml b/plugins/discourse-narrative-bot/config/locales/client.hr.yml new file mode 100644 index 0000000000..82c0b6efa6 --- /dev/null +++ b/plugins/discourse-narrative-bot/config/locales/client.hr.yml @@ -0,0 +1,12 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + discourse_narrative_bot: + welcome_post_type: + new_user_track: "Pokreni tutorial za sve nove korisnike" + welcome_message: "Pošalji poruku dobrodošlice sa uputama svim novim korisnicima." diff --git a/plugins/discourse-narrative-bot/config/locales/server.hr.yml b/plugins/discourse-narrative-bot/config/locales/server.hr.yml new file mode 100644 index 0000000000..ea990d182d --- /dev/null +++ b/plugins/discourse-narrative-bot/config/locales/server.hr.yml @@ -0,0 +1,98 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + discourse_narrative_bot: + quote: + trigger: "citat" + magic_8_ball: + answers: + "1": "Sigurno je" + "2": "It is decidedly so" + "3": "Without a doubt" + "4": "Yes definitely" + "5": "You may rely on it" + "6": "As I see it, yes" + "7": "Najvjerojatnije" + "8": "Outlook good" + "9": "Da" + "10": "Signs point to yes" + "11": "Reply hazy, try again" + "12": "Ask again later" + "13": "Better not tell you now" + "14": "Cannot predict now" + "15": "Concentrate and ask again" + "16": "Don't count on it" + "17": "My reply is no" + "18": "My sources say no" + "19": "Izgledi nisu baš dobri" + "20": "Very doubtful" + new_user_narrative: + hello: + title: "Pozdrav!" + advanced_user_narrative: + change_topic_notification_level: + reply: |- + Sjajan rad! Nadam se da niste isključili ovu temu jer ponekad znam biti malo pričljiv :grin:. + + Imajte na umu da kada odgovarate na temu ili čitate temu dulje od nekoliko minuta, ona se automatski postavlja na razinu obavijesti "praćenja". To možete promijeniti u [vašim korisničkim postavkama](%{base_uri}/my/preferences). + poll: + instructions: |- + Jeste li znali da svakoj objavi možete dodati anketu? Pokušajte upotrijebiti zupčanik u uređivaču da **izradite anketu**. + not_found: |- + Ups! U vašem odgovoru nije bilo ankete. + + Koristite ikonu zupčanika u uređivaču ili kopirajte i zalijepite ovu anketu u svoj sljedeći odgovor: + + ```tekst + [poll] + * :cat: + * :dog: + [/poll] + ``` + reply: |- + Hej, dobra anketa! Kako sam te podučio? + + [poll] + * :+1: + * :-1: + [/poll] + details: + instructions: |- + Ponekad ćete možda poželjeti **sakriti detalje** u svojim odgovorima: + + - Kada raspravljate o točkama zapleta filma ili TV emisije koje bi se smatrale spojlerom. + + - Kada vaš post treba puno neobaveznih detalja koji mogu biti neodoljivi kada ih pročitate odjednom. + + [details=Odaberite ovo da vidite kako radi!] + 1. Odaberite stupnja prijenosa u uređivaču. + 2. Odaberite "Sakrij pojedinosti". + 3. Uredite sažetak pojedinosti i dodajte svoj sadržaj. + [/details] + + Možete li koristiti zupčanika u uređivaču da dodate odjeljak s detaljima u svoj sljedeći odgovor? + not_found: |- + Imate problema s kreiranjem widgeta s detaljima? Pokušajte uključiti sljedeće u svoj sljedeći odgovor: + + ```tekst + [details=Odaberite me za detalje] + Ovdje su detalji + [/details] + ``` + reply: |- + Sjajan posao - vaša pozornost na _detalje_ je vrijedna divljenja! + end: + message: |- + Prošao si kroz ovo kao _napredni korisnik_ doista :bow: + + %{certificate} + + To je sve što imam za tebe. + + Zbogom za sada! Ako želite ponovo razgovarati sa mnom, pošaljite mi poruku bilo kada :sunglasses: + certificate: + alt: "Napredna potvrda o postignuću korisnika" diff --git a/plugins/discourse-presence/config/locales/client.hr.yml b/plugins/discourse-presence/config/locales/client.hr.yml new file mode 100644 index 0000000000..55e4e39b3d --- /dev/null +++ b/plugins/discourse-presence/config/locales/client.hr.yml @@ -0,0 +1,21 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + presence: + replying: + one: "odgovaranje" + few: "odgovaranje" + other: "odgovaranje" + editing: + one: "uređivanje" + few: "uređivanje" + other: "uređivanje" + replying_to_topic: + one: "odgovaranje" + few: "odgovaranja" + other: "odgovaranja" diff --git a/plugins/discourse-presence/config/locales/server.hr.yml b/plugins/discourse-presence/config/locales/server.hr.yml new file mode 100644 index 0000000000..f7ecfa9600 --- /dev/null +++ b/plugins/discourse-presence/config/locales/server.hr.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + site_settings: + presence_enabled: "Prikaži korisnike koji trenutno odgovaraju na temu ili uređuju već postojeću objavu?" + presence_max_users_shown: "Maksimalan broj prikazanih korisnika." diff --git a/plugins/poll/config/locales/client.hr.yml b/plugins/poll/config/locales/client.hr.yml new file mode 100644 index 0000000000..5a5ed89827 --- /dev/null +++ b/plugins/poll/config/locales/client.hr.yml @@ -0,0 +1,125 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + poll: + voters: + one: "glasać" + few: "glasaći" + other: "glasaći" + total_votes: + one: "ukupno glasova" + few: "ukupno glasova" + other: "ukupno glasova" + average_rating: "Prosječna ocjena: %{average}." + public: + title: "Glasovi su javni." + results: + groups: + title: "Morate biti član %{groups} da biste glasali u ovoj anketi." + vote: + title: "Rezultati će biti prikazani nakon glasanja." + closed: + title: "Rezultati će biti prikazani nakon zatvaranja." + staff: + title: "Rezultati se prikazuju samo članovima osoblja." + multiple: + help: + at_least_min_options: + one: "Odaberite najmanje %{count} opciju." + few: "Odaberite najmanje %{count} opcije." + other: "Odaberite najmanje %{count} opcija." + up_to_max_options: + one: "Odaberite najviše %{count} opciju." + few: "Odaberite najviše %{count} opcije." + other: "Odaberite najviše %{count} opcija." + x_options: + one: "Odaberite %{count} opciju." + few: "Odaberite %{count} opcije." + other: "Odaberite %{count} opcija." + between_min_and_max_options: "Odaberite između %{min} i %{max} mogućnosti." + cast-votes: + title: "Dajte svoj glas" + label: "Glasaj sada!" + show-results: + title: "Prikaži resultate anketa" + label: "Pokaži rezultate" + remove-vote: + title: "Ukloni svoj glas" + label: "Ukloni glas" + hide-results: + title: "Nazad na vaše glasove" + label: "Prikaži glasovanje" + group-results: + title: "Grupiranje glasova prema korisničkom polju" + label: "Prikaži detaljnu podjelu" + export-results: + title: "Izvoz rezultata ankete" + label: "Izvoz" + open: + title: "Otvori anketu" + label: "Otvori" + confirm: "Jeste li sigurni da želite otvoriti anketu?" + close: + title: "Zatvori anketu" + label: "Zatvori" + confirm: "Jeste li sigurni da želite zatvoriti anketu?" + automatic_close: + closes_in: "Zatvara se u %{timeLeft}." + age: "Zatvoreno %{age}" + breakdown: + title: "Rezultati ankete" + votes: "%{count} glasova" + breakdown: "Slom" + percentage: "Postotak" + count: "Količina" + error_while_toggling_status: "Nažalost, došlo je do pogreške prilikom izmjene statusa ove ankete." + error_while_casting_votes: "Žao nam je, došlo je do pogreške prilikom vašeg glasanja." + error_while_fetching_voters: "Žao nam je, došlo je do pogreške prilikom prikaza glasača." + error_while_exporting_results: "Žao nam je, došlo je do pogreške prilikom izvoza rezultata ankete." + ui_builder: + title: Kreiraj anketu + insert: Umetni anketu + help: + options_min_count: Unesite barem 1 opciju. + options_max_count: Unesite najviše %{count} opcija. + invalid_min_value: Minimalna vrijednost mora biti najmanje 1. + invalid_max_value: Maksimalna vrijednost mora biti najmanje 1, ali manja ili jednaka broju opcija. + invalid_values: Minimalna vrijednost mora biti manja od maksimalne vrijednosti. + min_step_value: Minimalna vrijednost koraka je 1 + poll_type: + label: Tip + regular: Jednostruki izbor + multiple: Višestruki izbor + number: Brojčana ocjena + poll_result: + label: Prikaži rezultate... + always: Uvijek vidljiv + vote: Tek nakon glasanja + closed: Kada je anketa zatvorena + staff: Samo osoblje + poll_groups: + label: Ograničite glasanje na ove grupe + poll_chart_type: + label: Grafikon rezultata + bar: Trakasti (Bar) + pie: Tortni (Pie) + poll_config: + max: Maks. izbori + min: Min Izbori + step: Korak + poll_public: + label: Pokažite tko je glasao + poll_title: + label: Naslov (izborno) + poll_options: + label: Opcije (jedna po liniji) + add: Dodaj opciju + automatic_close: + label: Automatski zatvori anketu + show_advanced: "Prikaži napredne mogućnosti" + hide_advanced: "Sakrij napredne opcije" diff --git a/plugins/poll/config/locales/server.hr.yml b/plugins/poll/config/locales/server.hr.yml new file mode 100644 index 0000000000..6ec71a5f3a --- /dev/null +++ b/plugins/poll/config/locales/server.hr.yml @@ -0,0 +1,60 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + site_settings: + poll_enabled: "Dozvoli ankete?" + poll_maximum_options: "Maksimalan broj opcija dozvoljenih u anketi." + poll_edit_window_mins: "Vrijeme nakon objave posta kada anketa može biti uređena." + poll_minimum_trust_level_to_create: "Definirajte minimalnu razinu povjerenja potrebnu za izradu anketa." + poll_groupable_user_fields: "Skup imena korisničkih polja koja se mogu koristiti za grupiranje i filtriranje rezultata ankete." + poll_export_data_explorer_query_id: "ID upita Data Explorera koji će se koristiti za izvoz rezultata ankete (0 za onemogućavanje)." + poll: + poll: "anketa" + invalid_argument: "Neispravna vrijednost '%{value}' za argument '%{argument}'." + multiple_polls_without_name: "Trenutno postoji više anketa bez imena. Koristite 'ime' da identificirate svoju anketu." + multiple_polls_with_same_name: "Postoji više anketa s istim nazivom:%{name}. Upotrijebite atribut 'name' da biste jedinstveno identificirali svoje ankete.." + default_poll_must_have_at_least_1_option: "Anketa mora imati najmanje 1 opciju." + named_poll_must_have_at_least_1_option: "Anketa %{name} mora imati najmanje 1 opciju." + default_poll_must_have_less_options: + one: "Anketa mora imati manje od %{count} opcije." + few: "Anketa mora imati manje od %{count} opcije." + other: "Anketa mora imati manje od %{count} opcija." + named_poll_must_have_less_options: + one: "Anketa %{name} mora imati manje od %{count} opcije." + few: "Anketa %{name} mora imati manje od %{count} opcije." + other: "Anketa %{name} mora imati manje od %{count} opcija." + default_poll_must_have_different_options: "Anketa mora imati različite opcije." + named_poll_must_have_different_options: "Anketa %{name} mora imati različite opcije." + default_poll_must_not_have_any_empty_options: "Anketa ne smije imati prazne opcije." + named_poll_must_not_have_any_empty_options: "Anketa %{name} ne smije imati prazne opcije." + default_poll_with_multiple_choices_has_invalid_parameters: "Anketa sa više odabira sadrži nevažeće parametre." + named_poll_with_multiple_choices_has_invalid_parameters: "Anketa %{name} sa više odabira sadrži nevažeće parametre." + requires_at_least_1_valid_option: "Morate odabrati najmanje 1 važeću opciju." + edit_window_expired: + cannot_edit_default_poll_with_votes: "Ne možete promijeniti anketu nakon prvih %{minutes} minuta." + cannot_edit_named_poll_with_votes: "Ne možete promijeniti naziv ankete ${name} nakon prvih %{minutes} minuta." + no_poll_with_this_name: "Nijedna anketa pod nazivom %{name} povezana s ovim postom." + post_is_deleted: "Ne može djelovati na izbrisanom postu." + user_cant_post_in_topic: "Ne možete glasati jer ne možete objavljivati u ovoj temi." + topic_must_be_open_to_vote: "Tema mora biti otvorena za glasovanje." + poll_must_be_open_to_vote: "Anketa biranja mora biti otvorena za glasovanje." + one_vote_per_user: "Za ovu anketu dopušten je samo 1 glas." + max_vote_per_user: + one: Za ovu anketu dopušteno je samo %{count} glas. + few: Za ovu anketu dopušteno je samo %{count} glasova. + other: Za ovu anketu dopušteno je najviše %{count} glasova. + min_vote_per_user: + one: Za ovu anketu dopušteno je najmanje %{count} glas. + few: Za ovu anketu dopušteno je najmanje %{count} glasa. + other: Za ovu anketu dopušteno je najmanje %{count} glasova. + topic_must_be_open_to_toggle_status: "Tema mora biti otvorena za prebacivanje statusa." + only_staff_or_op_can_toggle_status: "Samo član osoblja ili izvorni poster mogu prebaciti status ankete." + insufficient_rights_to_create: "Nemate dopuštenje za stvaranje anketa." + email: + link_to_poll: "Kliknite za pregled ankete." + user_field: + no_data: "Nema podataka" diff --git a/plugins/styleguide/config/locales/client.hr.yml b/plugins/styleguide/config/locales/client.hr.yml new file mode 100644 index 0000000000..be9f0b8989 --- /dev/null +++ b/plugins/styleguide/config/locales/client.hr.yml @@ -0,0 +1,89 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + js: + styleguide: + title: "Vodič za stil" + welcome: "Da biste započeli, odaberite odjeljak s izbornika s lijeve strane." + categories: + atoms: Atomi + molecules: Molekule + organisms: Organizmi + sections: + typography: + title: "Tipografija" + example: "Dobrodošli na Discourse" + paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud istezanje ullamco Laboris nisi ut aliquip ex ea commodo posljedica. Duis aute irure dolor in rephenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est Laborum." + date_time_inputs: + title: "Unosi datuma/vremena" + font_scale: + title: "Sustav fonta" + colors: + title: "Boje" + icons: + title: "Ikone" + full_list: "Pogledajte cijeli popis ikona Font Awesome" + input_fields: + title: "Polja za unos" + buttons: + title: "Gumbi" + dropdowns: + title: "Padajući izbornici" + categories: + title: "Kategorije" + bread_crumbs: + title: "Bread Crumbs" + navigation: + title: "Navigacija" + navigation_bar: + title: "Navigacijska traka" + navigation_stacked: + title: "Navigacija složena" + categories_list: + title: "Popis kategorija" + topic_link: + title: "Veza na temu" + topic_list_item: + title: "Stavka popisa tema" + topic_statuses: + title: "Statusi tema" + topic_list: + title: "Popis tema" + basic_topic_list: + title: "Popis osnovnih tema" + footer_message: + title: "Poruka podnožja" + signup_cta: + title: "Registracija CTA" + topic_timer_info: + title: "Mjerači vremena teme" + topic_footer_buttons: + title: "Gumbi podnožja teme" + topic_notifications: + title: "Obavijesti o temama" + post: + title: "Objava" + topic_map: + title: "Karta teme" + site_header: + title: "Zaglavlje web lokacije" + suggested_topics: + title: "Preporučene teme" + post_menu: + title: "Izbornik objava" + modal: + title: "Modalni" + header: "Modalni naslov" + footer: "Modalno podnožje" + user_about: + title: "O korisniku, box" + header_icons: + title: "Ikone zaglavlja" + spinners: + title: "Spinneri" + empty_state: + title: "Prazno stanje" diff --git a/plugins/styleguide/config/locales/server.hr.yml b/plugins/styleguide/config/locales/server.hr.yml new file mode 100644 index 0000000000..4fac518136 --- /dev/null +++ b/plugins/styleguide/config/locales/server.hr.yml @@ -0,0 +1,10 @@ +# WARNING: Never edit this file. +# It will be overwritten when translations are pulled from Crowdin. +# +# To work with us on translations, join this project: +# https://translate.discourse.org/ + +hr: + site_settings: + styleguide_enabled: 'Omogućite put "/styleguide" za pomoć u oblikovanju Discoursea' + styleguide_admin_only: "Ograničava vidljivost stilskog vodiča na administratore" diff --git a/public/403.hr.html b/public/403.hr.html new file mode 100644 index 0000000000..593dfee79f --- /dev/null +++ b/public/403.hr.html @@ -0,0 +1,28 @@ + + + + Ne možete to napraviti (403) + + + + + +
+

403

+

Ne možete vidjeti te resurse!

+ +

Ovo će biti zamijenjeno korisničkom Discourse 403 stranicom.

+
+ + diff --git a/public/422.hr.html b/public/422.hr.html new file mode 100644 index 0000000000..69ce633c99 --- /dev/null +++ b/public/422.hr.html @@ -0,0 +1,27 @@ + + + + Promjena nije dozvoljena (422) + + + + + + +
+

Promjena nije dozvoljena.

+

Možda ste pokušali promijeniti nešto čemu niste imali pristup.

+
+ + diff --git a/public/500.hr.html b/public/500.hr.html new file mode 100644 index 0000000000..f859c82c06 --- /dev/null +++ b/public/500.hr.html @@ -0,0 +1,13 @@ + + + + Ups - Greška 500 + + + +

Ups

+

Softver koji pokreće ovaj forum za diskusiju naišao je na neočekivani problem. Ispričavamo se zbog neugodnosti.

+

Zabilježene su detaljne informacije o pogrešci i generirana je automatska obavijest. Provjeriti ćemo što se desilo.

+

Daljnje akcije nisu potrebne. Međutim, ako se stanje pogreške nastavi, možete navesti dodatne detalje, uključujući korake za reprodukciju pogreške, tako da objavite temu rasprave u kategoriji povratne informacije web-mjesta.

+ + diff --git a/public/503.hr.html b/public/503.hr.html new file mode 100644 index 0000000000..9c0c64cf78 --- /dev/null +++ b/public/503.hr.html @@ -0,0 +1,12 @@ + + + + Stranic je u tijeku održavanja + + + +

Trenutno ne radimo zbog planiranog održavanja web stranice.

+

Molimo provjerite opet za nekoliko minuta .

+

Oprostite na smetnji!

+ + From 410ab5d5872d59795200af579633fa4ff8751df8 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 19 Jun 2022 19:02:55 +0100 Subject: [PATCH 046/184] FIX: Skip invalid values in site setting upload references migration (#17138) --- ..._copy_site_settings_uploads_to_upload_references.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/db/migrate/20220330160747_copy_site_settings_uploads_to_upload_references.rb b/db/migrate/20220330160747_copy_site_settings_uploads_to_upload_references.rb index 28fa3cabf8..be16548f3f 100644 --- a/db/migrate/20220330160747_copy_site_settings_uploads_to_upload_references.rb +++ b/db/migrate/20220330160747_copy_site_settings_uploads_to_upload_references.rb @@ -4,9 +4,13 @@ class CopySiteSettingsUploadsToUploadReferences < ActiveRecord::Migration[6.1] def up execute <<~SQL WITH site_settings_uploads AS ( - SELECT id, unnest(string_to_array(value, '|'))::integer upload_id - FROM site_settings - WHERE data_type = 17 + SELECT id, raw_upload_id::integer AS upload_id + FROM ( + SELECT id, unnest(string_to_array(value, '|')) AS raw_upload_id + FROM site_settings + WHERE data_type = 17 + ) raw + WHERE raw_upload_id ~ '^\d+$' UNION SELECT id, value::integer FROM site_settings From 804b8fd9f94d0d67152d6177241b14722aba6f61 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:47:37 +0800 Subject: [PATCH 047/184] DEV: Defer loading core/plugin/theme JS files (#17063) This is pre-request work to introduce a splash screen while site assets load. The only change this commit introduces is that it ensures we add the defer attribute to core/plugin/theme .JS files. This will allow us to insert markup before the browser starts evaluating those scripts later on. It has no visual or functional impact on core. This will not have any impact on how themes and plugins work. The only exception is themes loading external scripts in the theme field directly via script tags. Everything will work the same but those would need to add the defer attribute if they want to keep the benefits introduced in this PR. --- app/assets/javascripts/discourse/app/index.html | 8 +++++--- app/assets/javascripts/discourse/ember-cli-build.js | 1 + .../javascripts/discourse/lib/bootstrap-json/index.js | 8 ++++---- app/assets/javascripts/discourse/tests/index.html | 2 ++ .../javascripts/discourse/tests/test_starter.js | 11 ++++++----- app/helpers/application_helper.rb | 2 +- app/models/theme.rb | 4 ++-- app/models/theme_field.rb | 4 ++-- app/views/layouts/application.html.erb | 4 ++-- spec/helpers/application_helper_spec.rb | 2 +- spec/models/theme_field_spec.rb | 8 ++++---- 11 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html index ba049ba54c..0dde7cb0ab 100644 --- a/app/assets/javascripts/discourse/app/index.html +++ b/app/assets/javascripts/discourse/app/index.html @@ -11,8 +11,10 @@ {{content-for "before-script-load"}} - - + + + + {{content-for "head"}} @@ -27,7 +29,7 @@ - + {{content-for "body-footer"}} diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index d9232652ab..7065afc557 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -29,6 +29,7 @@ module.exports = function (defaults) { }, autoImport: { forbidEval: true, + insertScriptsAt: "ember-auto-import-scripts", }, fingerprint: { // Handled by Rails asset pipeline diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index a1fcc95436..46c891ecc6 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -89,7 +89,7 @@ function head(buffer, bootstrap, headers, baseURL) { let { admin, staff } = user; if (staff) { - buffer.push(``); + buffer.push(``); } if (admin) { @@ -98,7 +98,7 @@ function head(buffer, bootstrap, headers, baseURL) { } bootstrap.plugin_js.forEach((src) => - buffer.push(``) + buffer.push(``) ); buffer.push(bootstrap.theme_html.translations); @@ -108,14 +108,14 @@ function head(buffer, bootstrap, headers, baseURL) { } function localeScript(buffer, bootstrap) { - buffer.push(``); + buffer.push(``); } function beforeScriptLoad(buffer, bootstrap) { buffer.push(bootstrap.html.before_script_load); localeScript(buffer, bootstrap); (bootstrap.extra_locales || []).forEach((l) => - buffer.push(``) + buffer.push(``) ); } diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html index cc64b77714..8917097f05 100644 --- a/app/assets/javascripts/discourse/tests/index.html +++ b/app/assets/javascripts/discourse/tests/index.html @@ -46,7 +46,9 @@ + + diff --git a/app/assets/javascripts/discourse/tests/test_starter.js b/app/assets/javascripts/discourse/tests/test_starter.js index 0ab61639ba..067e890fc1 100644 --- a/app/assets/javascripts/discourse/tests/test_starter.js +++ b/app/assets/javascripts/discourse/tests/test_starter.js @@ -1,10 +1,11 @@ // discourse-skip-module -document.write( - '
' -); -document.write( - "" +document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` ); let setupTestsLegacy = require("discourse/tests/setup-tests").setupTestsLegacy; diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5773efd851..f069f5560c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -153,7 +153,7 @@ module ApplicationHelper def preload_script_url(url) <<~HTML.html_safe - + HTML end diff --git a/app/models/theme.rb b/app/models/theme.rb index 5c510086f7..6488b36e52 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -154,7 +154,7 @@ class Theme < ActiveRecord::Base SvgSprite.expire_cache end - BASE_COMPILER_VERSION = 55 + BASE_COMPILER_VERSION = 56 def self.compiler_version get_set_cache "compiler_version" do dependencies = [ @@ -391,7 +391,7 @@ class Theme < ActiveRecord::Base end caches = JavascriptCache.where(theme_id: theme_ids) caches = caches.sort_by { |cache| theme_ids.index(cache.theme_id) } - return caches.map { |c| "" }.join("\n") + return caches.map { |c| "" }.join("\n") end list_baked_fields(theme_ids, target, name).map { |f| f.value_baked || f.value }.join("\n") end diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index bdc017c661..a48b5f9218 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -149,7 +149,7 @@ class ThemeField < ActiveRecord::Base javascript_cache.content = js_compiler.content javascript_cache.save! - doc.add_child("") if javascript_cache.content.present? + doc.add_child("") if javascript_cache.content.present? [doc.to_s, errors&.join("\n")] end @@ -265,7 +265,7 @@ class ThemeField < ActiveRecord::Base javascript_cache.content = js_compiler.content javascript_cache.save! doc = "" - doc = "" if javascript_cache.content.present? + doc = "" if javascript_cache.content.present? [doc, errors&.join("\n")] end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 83e56dff16..0ab67a126c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -112,11 +112,11 @@ - + <%= yield :data %> - + <%- unless customization_disabled? %> <%= theme_lookup("body_tag") %> diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index e7af0c6811..fdf4ce1faa 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -7,7 +7,7 @@ describe ApplicationHelper do def preload_link(url) <<~HTML - + HTML end diff --git a/spec/models/theme_field_spec.rb b/spec/models/theme_field_spec.rb index ad43c0d1e0..c4eca24304 100644 --- a/spec/models/theme_field_spec.rb +++ b/spec/models/theme_field_spec.rb @@ -75,7 +75,7 @@ describe ThemeField do theme_field = ThemeField.create!(theme_id: 1, target_id: 0, name: "header", value: html) theme_field.ensure_baked! - expect(theme_field.value_baked).to include("") + expect(theme_field.value_baked).to include("") expect(theme_field.value_baked).to include("external-script.js") expect(theme_field.value_baked).to include('") + expect(field.value_baked).to include("") expect(field.javascript_cache.content).to include("Theme Transpilation Error:") field.update!(value: '') @@ -130,7 +130,7 @@ HTML theme_field.ensure_baked! javascript_cache = theme_field.javascript_cache - expect(theme_field.value_baked).to include("") + expect(theme_field.value_baked).to include("") expect(javascript_cache.content).to include("testing-div") expect(javascript_cache.content).to include("string_setting") expect(javascript_cache.content).to include("test text \\\" 123!") @@ -378,7 +378,7 @@ HTML describe "javascript cache" do it "is generated correctly" do fr1.ensure_baked! - expect(fr1.value_baked).to include("") + expect(fr1.value_baked).to include("") expect(fr1.javascript_cache.content).to include("bonjourworld") expect(fr1.javascript_cache.content).to include("helloworld") expect(fr1.javascript_cache.content).to include("enval1") From 93393d69fc134c91a1298a3ccdc8ed54bb00aa5c Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Mon, 20 Jun 2022 04:05:36 +0200 Subject: [PATCH 048/184] DEV: Remove duplicate JS files (#17132) Those files are stored as versioned files in subdirectories. --- public/javascripts/Chart.min.js | 7 - .../chartjs-plugin-datalabels.min.js | 7 - public/javascripts/diffhtml.min.js | 1 - .../javascripts/jquery.magnific-popup.min.js | 4 - public/javascripts/pikaday.js | 1257 ----------------- 5 files changed, 1276 deletions(-) delete mode 100644 public/javascripts/Chart.min.js delete mode 100644 public/javascripts/chartjs-plugin-datalabels.min.js delete mode 100644 public/javascripts/diffhtml.min.js delete mode 100644 public/javascripts/jquery.magnific-popup.min.js delete mode 100644 public/javascripts/pikaday.js diff --git a/public/javascripts/Chart.min.js b/public/javascripts/Chart.min.js deleted file mode 100644 index a87f61443e..0000000000 --- a/public/javascripts/Chart.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Chart.js v2.9.4 - * https://www.chartjs.org - * (c) 2020 Chart.js Contributors - * Released under the MIT License - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(function(){try{return require("moment")}catch(t){}}()):"function"==typeof define&&define.amd?define(["require"],(function(t){return e(function(){try{return t("moment")}catch(t){}}())})):(t=t||self).Chart=e(t.moment)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},n=function(t,e){return t(e={exports:{}},e.exports),e.exports}((function(t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[e[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=n[t];if(i)return i;var a,r,o,s=1/0;for(var l in e)if(e.hasOwnProperty(l)){var u=e[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));n.rgb,n.hsl,n.hsv,n.hwb,n.cmyk,n.xyz,n.lab,n.lch,n.hex,n.keyword,n.ansi16,n.ansi256,n.hcg,n.apple,n.gray;function i(t){var e=function(){for(var t={},e=Object.keys(n),i=e.length,a=0;a1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var s=o,l={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},u={getRgba:d,getHsla:h,getRgb:function(t){var e=d(t);return e&&e.slice(0,3)},getHsl:function(t){var e=h(t);return e&&e.slice(0,3)},getHwb:c,getAlpha:function(t){var e=d(t);if(e)return e[3];if(e=h(t))return e[3];if(e=c(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+v(t[0])+v(t[1])+v(t[2])+(e>=0&&e<1?v(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return f(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:f,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:g,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return b[t.slice(0,3)]}};function d(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new y,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},y.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},y.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},y.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i=0;a--)e.call(n,t[a],a);else for(a=0;a=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-C.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*C.easeInBounce(2*t):.5*C.easeOutBounce(2*t-1)+.5}},P={effects:C};S.easingEffects=C;var A=Math.PI,D=A/180,T=2*A,I=A/2,F=A/4,O=2*A/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),se.left-1e-6&&t.xe.top-1e-6&&t.y0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r=n?(H.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},Q=H.options.resolve,tt=["push","pop","shift","splice","unshift"];function et(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(tt.forEach((function(e){delete t[e]})),delete t._chartjs)}}var nt=function(t,e){this.initialize(t,e)};H.extend(nt.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&et(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;tn&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;na?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function ot(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+at,rt(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=at,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+at,n.startAngle,!0),a=0;as;)a-=at;for(;a=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/at)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+at,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;tt.x&&(e=bt(e,"left","right")):t.basen?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function yt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&vt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}N._set("global",{elements:{rectangle:{backgroundColor:pt,borderColor:pt,borderSkipped:"bottom",borderWidth:0}}});var _t=K.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=vt(t),n=e.right-e.left,i=e.bottom-e.top,a=xt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return yt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return mt(n)?yt(n,t,null):yt(n,null,e)},inXRange:function(t){return yt(this._view,t,null)},inYRange:function(t){return yt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return mt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return mt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),kt={},wt=st,Mt=dt,St=gt,Ct=_t;kt.Arc=wt,kt.Line=Mt,kt.Point=St,kt.Rectangle=Ct;var Pt=H._deprecated,At=H.valueOrDefault;function Dt(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=H.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return H.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}N._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),N._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Tt=it.extend({dataElementType:kt.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;it.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Pt("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Pt("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Pt("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Pt("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Pt("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e=0&&p.min>=0?p.min:p.max,y=void 0===p.start?p.end:p.max>=0&&p.min>=0?p.max-p.min:p.min-p.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(p.min<0&&r<0||p.max>=0&&r>0)&&(x+=r));return o=h.getPixelForValue(x),l=(s=h.getPixelForValue(x+y))-o,void 0!==m&&Math.abs(l)=0&&!c||y<0&&c?o-m:o+m),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t=Rt?-zt:b<-Rt?zt:0)+m,y=Math.cos(b),_=Math.sin(b),k=Math.cos(x),w=Math.sin(x),M=b<=0&&x>=0||x>=zt,S=b<=Nt&&x>=Nt||x>=zt+Nt,C=b<=-Nt&&x>=-Nt||x>=Rt+Nt,P=b===-Rt||x>=Rt?-1:Math.min(y,y*p,k,k*p),A=C?-1:Math.min(_,_*p,w,w*p),D=M?1:Math.max(y,y*p,k,k*p),T=S?1:Math.max(_,_*p,w,w*p);u=(D-P)/2,d=(T-A)/2,h=-(D+P)/2,c=-(T+A)/2}for(i=0,a=g.length;i0&&!isNaN(t)?zt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n0&&Ht(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ae(t,e,{intersect:!1})},point:function(t,e){return ee(t,Qt(e,t))},nearest:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis);return ne(t,i,n.intersect,a)},x:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},oe=H.extend;function se(t,e){return H.where(t,(function(t){return t.pos===e}))}function le(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function ue(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function de(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-ue(o,t,"left","right"),a=e.outerHeight-ue(o,t,"top","bottom"),i!==t.w||a!==t.h){t.w=i,t.h=a;var l=n.horizontal?[i,t.w]:[a,t.h];return!(l[0]===l[1]||isNaN(l[0])&&isNaN(l[1]))}}function he(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function ce(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;idiv{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&ge.default||ge,ve="$chartjs",be="chartjs-size-monitor",xe="chartjs-render-monitor",ye="chartjs-render-animation",_e=["animationstart","webkitAnimationStart"],ke={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function we(t,e){var n=H.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var Me=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Se(t,e,n){t.addEventListener(e,n,Me)}function Ce(t,e,n){t.removeEventListener(e,n,Me)}function Pe(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Ae(t){var e=document.createElement("div");return e.className=t||"",e}function De(t,e,n){var i,a,r,o,s=t[ve]||(t[ve]={}),l=s.resizer=function(t){var e=Ae(be),n=Ae(be+"-expand"),i=Ae(be+"-shrink");n.appendChild(Ae()),i.appendChild(Ae()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Se(n,"scroll",a.bind(n,"expand")),Se(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Pe("resize",n)),i&&i.clientWidth0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index-1?t.split("\n"):t}function Ve(t){var e=N.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:ze(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:ze(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:ze(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:ze(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:ze(t.titleFontStyle,e.defaultFontStyle),titleFontSize:ze(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:ze(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:ze(t.footerFontStyle,e.defaultFontStyle),footerFontSize:ze(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function He(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function je(t){return Ee([],We(t))}var qe=K.extend({initialize:function(){this._model=Ve(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=Ee(o,We(i)),o=Ee(o,We(a)),o=Ee(o,We(r))},getBeforeBody:function(){return je(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return H.each(t,(function(t){var r={before:[],lines:[],after:[]};Ee(r.before,We(i.beforeLabel.call(n,t,e))),Ee(r.lines,i.label.call(n,t,e)),Ee(r.after,We(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return je(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=Ee(r,We(n)),r=Ee(r,We(i)),r=Ee(r,We(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=Ve(c),p=h._active,m=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},x={width:f.width,height:f.height},y={x:f.caretX,y:f.caretY};if(p.length){g.opacity=1;var _=[],k=[];y=Be[c.position].call(h,p,h._eventPosition);var w=[];for(e=0,n=p.length;ei.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,x,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.yl.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,x),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=x.width,g.height=x.height,g.caretX=y.x,g.caretY=y.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===c)s=g+m/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+p)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+p-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+m)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=Ne(e.rtl,e.x,e.width);for(t.x=He(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=H.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,H.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),H.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!H.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),Ue=Be,Ye=qe;Ye.positioners=Ue;var Ge=H.valueOrDefault;function Xe(){return H.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?H.merge(e[t][a],[Re.getScaleDefaults(r),o]):H.merge(e[t][a],o)}else H._merger(t,e,n,i)}})}function Ke(){return H.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||Object.create(null),r=n[t];"scales"===t?e[t]=Xe(a,r):"scale"===t?e[t]=H.merge(a,[Re.getScaleDefaults(r.type),r]):H._merger(t,e,n,i)}})}function Ze(t){var e=t.options;H.each(t.scales,(function(e){pe.removeBox(t,e)})),e=Ke(N.global,N[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function $e(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(H.findIndex(t,a)>=0);return i}function Je(t){return"top"===t||"bottom"===t}function Qe(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}N._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var tn=function(t,e){return this.construct(t,e),this};H.extend(tn.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||Object.create(null)).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=Ke(N.global,N[t.type],t.options||{}),t}(e);var i=Oe.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=H.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,tn.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),H.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return H.canvas.clear(this),this},stop:function(){return J.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(H.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:H.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",H.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;H.each(e.xAxes,(function(t,n){t.id||(t.id=$e(e.xAxes,"x-axis-",n))})),H.each(e.yAxes,(function(t,n){t.id||(t.id=$e(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),H.each(i,(function(e){var i=e.options,r=i.id,o=Ge(i.type,e.dtype);Je(i.position)!==Je(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Re.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),H.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Re.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return re.modes.single(this,t)},getElementsAtEvent:function(t){return re.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return re.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=re.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return re.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=H.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=H.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(H.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},sn=H.isArray,ln=H.isNullOrUndef,un=H.valueOrDefault,dn=H.valueAtIndexOrDefault;function hn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=rl+1e-6)))return o}function cn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,p,m,v=n.length,b=[],x=[],y=[],_=0,k=0;for(a=0;ae){for(n=0;n=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-fn(l.gridLines)-u.padding-gn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=H.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){H.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){H.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=fn(o)+gn(r)),u?s&&(e.height=fn(o)+gn(r)):e.height=t.maxHeight,a.display&&s){var d=mn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,p=h.highest,m=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,x=H.toRadians(t.labelRotation),y=Math.cos(x),_=Math.sin(x),k=_*g.width+y*(p.height-(b?p.offset:0))+(b?0:m);e.height=Math.min(t.maxHeight,e.height+k+v);var w,M,S=t.getPixelForTick(0)-t.left,C=t.right-t.getPixelForTick(t.getTicks().length-1);b?(w=l?y*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):y*f.width+_*f.offset):(w=c.width/2,M=f.width/2),t.paddingLeft=Math.max((w-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-C)*t.width/(t.width-C),0)+3}else{var P=a.mirror?0:g.width+v+m;e.width=Math.min(t.maxWidth,e.width+P),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){H.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(ln(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;nn-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;es)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;iu)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e1?(h-d)/(u-1):null,bn(t,i,H.isNullOrUndef(a)?0:d-a,d),bn(t,i,h,H.isNullOrUndef(a)?t.length:h+a),vn(t)}return bn(t,i),vn(t)},_tickSize:function(){var t=this.options.ticks,e=H.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;yn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return _n(e)||_n(n)||(t=o.chart.data.datasets[n].data[e]),_n(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=H.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),wn={position:"bottom"};kn._defaults=wn;var Mn=H.noop,Sn=H.isNullOrUndef;var Cn=yn.extend({getRightValue:function(t){return"string"==typeof t?+t:yn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=H.sign(t.min),i=H.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Mn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:H.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,p=H.niceNum((g-f)/u/l)*l;if(p<1e-14&&Sn(d)&&Sn(h))return[f,g];(r=Math.ceil(g/p)-Math.floor(f/p))>u&&(p=H.niceNum(r*p/u/l)*l),s||Sn(c)?n=Math.pow(10,H._decimalPlaces(p)):(n=Math.pow(10,c),p=Math.ceil(p*n)/n),i=Math.floor(f/p)*p,a=Math.ceil(g/p)*p,s&&(!Sn(d)&&H.almostWhole(d/p,p/1e3)&&(i=d),!Sn(h)&&H.almostWhole(h/p,p/1e3)&&(a=h)),r=(a-i)/p,r=H.almostEquals(r,Math.round(r),p/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Sn(d)?i:d);for(var m=1;me.length-1?null:this.getPixelForValue(e[t])}}),In=Pn;Tn._defaults=In;var Fn=H.valueOrDefault,On=H.math.log10;var Ln={position:"left",ticks:{callback:on.formatters.logarithmic}};function Rn(t,e){return H.isFinite(t)&&t>=0?t:e}var zn=yn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t0){var e=H.min(t),n=H.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(On(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:Rn(e.min),max:Rn(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=Fn(t.min,Math.pow(10,Math.floor(On(e.min)))),o=Math.floor(On(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(On(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(On(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(ne.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(On(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;yn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=Fn(t.options.ticks.fontSize,N.global.defaultFontSize)/t._length),t._startValue=On(e),t._valueOffset=n,t._valueRange=(On(t.max)-On(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(On(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),Nn=Ln;zn._defaults=Nn;var Bn=H.valueOrDefault,En=H.valueAtIndexOrDefault,Wn=H.options.resolve,Vn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:on.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Hn(t){var e=t.ticks;return e.display&&t.display?Bn(e.fontSize,N.global.defaultFontSize)+2*e.backdropPaddingY:0}function jn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:ta?{start:e-n,end:e}:{start:e,end:e+n}}function qn(t){return 0===t||180===t?"center":t<180?"left":"right"}function Un(t,e,n,i){var a,r,o=n.y+i/2;if(H.isArray(e))for(a=0,r=e.length;a270||t<90)&&(n.y-=e.h)}function Gn(t){return H.isNumber(t)?t:0}var Xn=Cn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Hn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;H.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);H.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Hn(this.options))},convertTicksToLabels:function(){var t=this;Cn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=H.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=H.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;er.r&&(r.r=f.end,o.r=h),g.startr.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=Gn(a),r=Gn(r),o=Gn(o),s=Gn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(H.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Bn(s.lineWidth,o.lineWidth),u=Bn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Hn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=H.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=En(i.fontColor,s,N.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=H.toDegrees(h);e.textAlign=qn(c),Yn(c,t._pointLabelSizes[s],u),Un(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&H.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=En(e.color,i-1),u=En(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=H.options._parseFont(n),s=Bn(n.fontColor,N.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",H.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:H.noop}),Kn=Vn;Xn._defaults=Kn;var Zn=H._deprecated,$n=H.options.resolve,Jn=H.valueOrDefault,Qn=Number.MIN_SAFE_INTEGER||-9007199254740991,ti=Number.MAX_SAFE_INTEGER||9007199254740991,ei={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ni=Object.keys(ei);function ii(t,e){return t-e}function ai(t){return H.valueOrDefault(t.time.min,t.ticks.min)}function ri(t){return H.valueOrDefault(t.time.max,t.ticks.max)}function oi(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function si(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),H.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),H.isFinite(o)||(o=n.parse(o))),o)}function li(t,e){if(H.isNullOrUndef(e))return null;var n=t.options.time,i=si(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function ui(t,e,n,i){var a,r,o,s=ni.length;for(a=ni.indexOf(t);a=0&&(e[r].major=!0);return e}(t,r,o,n):r}var hi=yn.extend({initialize:function(){this.mergeTicksOptions(),yn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new rn._date(e.adapters.date);return Zn("time scale",n.format,"time.format","time.parser"),Zn("time scale",n.min,"time.min","ticks.min"),Zn("time scale",n.max,"time.max","ticks.max"),H.mergeIf(n.displayFormats,i.formats()),yn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),yn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=ti,f=Qn,g=[],p=[],m=[],v=s._getLabels();for(t=0,n=v.length;t1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?ui(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ni.length-1;r>=ni.indexOf(n);r--)if(o=ni[r],ei[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ni[n?ni.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ni.indexOf(t)+1,n=ni.length;ee&&s=0&&t0?s:1}}),ci={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};hi._defaults=ci;var fi={category:kn,linear:Tn,logarithmic:zn,radialLinear:Xn,time:hi},gi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};rn._date.override("function"==typeof t?{_id:"moment",formats:function(){return gi},parse:function(e,n){return"string"==typeof e&&"string"==typeof n?e=t(e,n):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,n){return t(e).format(n)},add:function(e,n,i){return t(e).add(n,i).valueOf()},diff:function(e,n,i){return t(e).diff(t(n),i)},startOf:function(e,n,i){return e=t(e),"isoWeek"===n?e.isoWeekday(i).valueOf():e.startOf(n).valueOf()},endOf:function(e,n){return t(e).endOf(n).valueOf()},_create:function(e){return t(e)}}:{}),N._set("global",{plugins:{filler:{propagate:!0}}});var pi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function vi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a0;--r)H.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function ki(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,p=i.spanGaps,m=[],v=[],b=0,x=0;for(t.beginPath(),o=0,s=g;o=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||N.global.defaultColor,o&&s&&r.length&&(H.canvas.clipArea(u,t.chartArea),ki(u,r,o,a,s,i._loop),H.canvas.unclipArea(u)))}},Mi=H.rtl.getRtlAdapter,Si=H.noop,Ci=H.valueOrDefault;function Pi(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}N._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;el.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],p=n.padding,m=0,v=0;H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(p+=m+n.padding,f.push(m),g.push(v),m=0,v=0),m=Math.max(m,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),p+=m,f.push(m),g.push(v),l.width+=p}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Si,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=N.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=Mi(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Ci(n.fontColor,i.defaultFontColor),g=H.options._parseFont(n),p=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var m=Pi(n,p),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},x=t.isHorizontal();d=x?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},H.rtl.overrideTextDirection(t.ctx,e.textDirection);var y=p+n.padding;H.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=m+p/2+f,_=d.x,k=d.y;h.setWidth(t.minSize.width),x?i>0&&_+g+n.padding>t.left+t.minSize.width&&(k=d.y+=y,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&k+y>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,k=d.y=t.top+b(o,s[d.line]));var w=h.x(_);!function(t,e,i){if(!(isNaN(m)||m<=0)){c.save();var o=Ci(i.lineWidth,r.borderWidth);if(c.fillStyle=Ci(i.fillStyle,a),c.lineCap=Ci(i.lineCap,r.borderCapStyle),c.lineDashOffset=Ci(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Ci(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Ci(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Ci(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=m*Math.SQRT2/2,l=h.xPlus(t,m/2),u=e+p/2;H.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,m),e,m,p),0!==o&&c.strokeRect(h.leftForLtr(t,m),e,m,p);c.restore()}}(w,k,e),v[i].left=h.leftForLtr(w,v[i].width),v[i].top=k,function(t,e,n,i){var a=p/2,r=h.xPlus(t,m+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(w,k,e,f),x?d.x+=g+n.padding:d.y+=y})),H.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Di(t,e){var n=new Ai({ctx:t.ctx,options:e,chart:t});pe.configure(t,n,e),pe.addBox(t,n),t.legend=n}var Ti={id:"legend",_element:Ai,beforeInit:function(t){var e=t.options.legend;e&&Di(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(H.mergeIf(e,N.global.legend),n?(pe.configure(t,n,e),n.options=e):Di(t,e)):n&&(pe.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Ii=H.noop;N._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Fi=K.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Ii,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Ii,beforeSetDimensions:Ii,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Ii,beforeBuildLabels:Ii,buildLabels:Ii,afterBuildLabels:Ii,beforeFit:Ii,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(H.isArray(n.text)?n.text.length:1)*H.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Ii,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=H.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=H.valueOrDefault(n.fontColor,N.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(H.isArray(g))for(var p=0,m=0;m=0;i--){var a=t[i];if(e(a))return a}},H.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},H.almostEquals=function(t,e,n){return Math.abs(t-e)=t},H.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},H.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},H.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},H.toRadians=function(t){return t*(Math.PI/180)},H.toDegrees=function(t){return t*(180/Math.PI)},H._decimalPlaces=function(t){if(H.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},H.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},H.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},H.aliasPixel=function(t){return t%2==0?0:.5},H._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},H.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},H.EPSILON=Number.EPSILON||1e-14,H.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e0?d[e-1]:null,(a=e0?d[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},H.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},H.niceNum=function(t,e){var n=Math.floor(H.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},H.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},H.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(H.getStyle(r,"padding-left")),u=parseFloat(H.getStyle(r,"padding-top")),d=parseFloat(H.getStyle(r,"padding-right")),h=parseFloat(H.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},H.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},H.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},H._calculatePadding=function(t,e,n){return(e=H.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},H._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},H.getMaximumWidth=function(t){var e=H._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-H._calculatePadding(e,"padding-left",n)-H._calculatePadding(e,"padding-right",n),a=H.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},H.getMaximumHeight=function(t){var e=H._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-H._calculatePadding(e,"padding-top",n)-H._calculatePadding(e,"padding-bottom",n),a=H.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},H.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},H.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},H.fontString=function(t,e,n){return e+" "+t+"px "+n},H.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;on.length){for(o=0;oi&&(i=r),i},H.numberOfLabelLines=function(t){var e=1;return H.each(t,(function(t){H.isArray(t)&&t.length>e&&(e=t.length)})),e},H.color=_?function(t){return t instanceof CanvasGradient&&(t=N.global.defaultColor),_(t)}:function(t){return console.error("Color.js not found!"),t},H.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:H.color(t).saturate(.5).darken(.1).rgbString()}}(),en._adapters=rn,en.Animation=$,en.animationService=J,en.controllers=Jt,en.DatasetController=it,en.defaults=N,en.Element=K,en.elements=kt,en.Interaction=re,en.layouts=pe,en.platform=Oe,en.plugins=Le,en.Scale=yn,en.scaleService=Re,en.Ticks=on,en.Tooltip=Ye,en.helpers.each(fi,(function(t,e){en.scaleService.registerScaleType(e,t,t._defaults)})),Li)Li.hasOwnProperty(Bi)&&en.plugins.register(Li[Bi]);en.platform.initialize();var Ei=en;return"undefined"!=typeof window&&(window.Chart=en),en.Chart=en,en.Legend=Li.legend._element,en.Title=Li.title._element,en.pluginService=en.plugins,en.PluginBase=en.Element.extend({}),en.canvasHelpers=en.helpers.canvas,en.layoutService=en.layouts,en.LinearScaleBase=Cn,en.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){en[t]=function(e,n){return new en(e,en.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Ei})); diff --git a/public/javascripts/chartjs-plugin-datalabels.min.js b/public/javascripts/chartjs-plugin-datalabels.min.js deleted file mode 100644 index 75eb420336..0000000000 --- a/public/javascripts/chartjs-plugin-datalabels.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * chartjs-plugin-datalabels v0.7.0 - * https://chartjs-plugin-datalabels.netlify.com - * (c) 2019 Chart.js Contributors - * Released under the MIT license - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):(t=t||self).ChartDataLabels=e(t.Chart)}(this,function(t){"use strict";var e=(t=t&&t.hasOwnProperty("default")?t.default:t).helpers,r=function(){if("undefined"!=typeof window){if(window.devicePixelRatio)return window.devicePixelRatio;var t=window.screen;if(t)return(t.deviceXDPI||1)/(t.logicalXDPI||1)}return 1}(),n={toTextLines:function(t){var r,n=[];for(t=[].concat(t);t.length;)"string"==typeof(r=t.pop())?n.unshift.apply(n,r.split("\n")):Array.isArray(r)?t.push.apply(t,r):e.isNullOrUndef(t)||n.unshift(""+r);return n},toFontString:function(t){return!t||e.isNullOrUndef(t.size)||e.isNullOrUndef(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family},textSize:function(t,e,r){var n,i=[].concat(e),o=i.length,a=t.font,l=0;for(t.font=r.string,n=0;nr.right&&(n|=l),er.bottom&&(n|=s),n}function d(t,e){var r,n,i=e.anchor,o=t;return e.clamp&&(o=function(t,e){for(var r,n,i,o=t.x0,d=t.y0,c=t.x1,h=t.y1,x=f(o,d,e),y=f(c,h,e);x|y&&!(x&y);)(r=x||y)&u?(n=o+(c-o)*(e.top-d)/(h-d),i=e.top):r&s?(n=o+(c-o)*(e.bottom-d)/(h-d),i=e.bottom):r&l?(i=d+(h-d)*(e.right-o)/(c-o),n=e.right):r&a&&(i=d+(h-d)*(e.left-o)/(c-o),n=e.left),r===x?x=f(o=n,d=i,e):y=f(c=n,h=i,e);return{x0:o,x1:c,y0:d,y1:h}}(o,e.area)),"start"===i?(r=o.x0,n=o.y0):"end"===i?(r=o.x1,n=o.y1):(r=(o.x0+o.x1)/2,n=(o.y0+o.y1)/2),function(t,e,r,n,i){switch(i){case"center":r=n=0;break;case"bottom":r=0,n=1;break;case"right":r=1,n=0;break;case"left":r=-1,n=0;break;case"top":r=0,n=-1;break;case"start":r=-r,n=-n;break;case"end":break;default:i*=Math.PI/180,r=Math.cos(i),n=Math.sin(i)}return{x:t,y:e,vx:r,vy:n}}(r,n,t.vx,t.vy,e.align)}var c={arc:function(t,e){var r=(t.startAngle+t.endAngle)/2,n=Math.cos(r),i=Math.sin(r),o=t.innerRadius,a=t.outerRadius;return d({x0:t.x+n*o,y0:t.y+i*o,x1:t.x+n*a,y1:t.y+i*a,vx:n,vy:i},e)},point:function(t,e){var r=i(t,e.origin),n=r.x*t.radius,o=r.y*t.radius;return d({x0:t.x-n,y0:t.y-o,x1:t.x+n,y1:t.y+o,vx:r.x,vy:r.y},e)},rect:function(t,e){var r=i(t,e.origin),n=t.x,o=t.y,a=0,l=0;return t.horizontal?(n=Math.min(t.x,t.base),a=Math.abs(t.base-t.x)):(o=Math.min(t.y,t.base),l=Math.abs(t.base-t.y)),d({x0:n,y0:o+l,x1:n+a,y1:o,vx:r.x,vy:r.y},e)},fallback:function(t,e){var r=i(t,e.origin);return d({x0:t.x,y0:t.y,x1:t.x,y1:t.y,vx:r.x,vy:r.y},e)}},h=t.helpers,x=n.rasterize;function y(t){var e=t._model.horizontal,r=t._scale||e&&t._xScale||t._yScale;if(!r)return null;if(void 0!==r.xCenter&&void 0!==r.yCenter)return{x:r.xCenter,y:r.yCenter};var n=r.getBasePixel();return e?{x:n,y:null}:{x:null,y:n}}function v(t,e,r){var n=t.shadowBlur,i=r.stroked,o=x(r.x),a=x(r.y),l=x(r.w);i&&t.strokeText(e,o,a,l),r.filled&&(n&&i&&(t.shadowBlur=0),t.fillText(e,o,a,l),n&&i&&(t.shadowBlur=n))}var _=function(t,e,r,n){var i=this;i._config=t,i._index=n,i._model=null,i._rects=null,i._ctx=e,i._el=r};h.extend(_.prototype,{_modelize:function(e,r,i,o){var a,l=this._index,s=h.options.resolve,u=n.parseFont(s([i.font,{}],o,l)),f=s([i.color,t.defaults.global.defaultFontColor],o,l);return{align:s([i.align,"center"],o,l),anchor:s([i.anchor,"center"],o,l),area:o.chart.chartArea,backgroundColor:s([i.backgroundColor,null],o,l),borderColor:s([i.borderColor,null],o,l),borderRadius:s([i.borderRadius,0],o,l),borderWidth:s([i.borderWidth,0],o,l),clamp:s([i.clamp,!1],o,l),clip:s([i.clip,!1],o,l),color:f,display:e,font:u,lines:r,offset:s([i.offset,0],o,l),opacity:s([i.opacity,1],o,l),origin:y(this._el),padding:h.options.toPadding(s([i.padding,0],o,l)),positioner:(a=this._el,a instanceof t.elements.Arc?c.arc:a instanceof t.elements.Point?c.point:a instanceof t.elements.Rectangle?c.rect:c.fallback),rotation:s([i.rotation,0],o,l)*(Math.PI/180),size:n.textSize(this._ctx,r,u),textAlign:s([i.textAlign,"start"],o,l),textShadowBlur:s([i.textShadowBlur,0],o,l),textShadowColor:s([i.textShadowColor,f],o,l),textStrokeColor:s([i.textStrokeColor,f],o,l),textStrokeWidth:s([i.textStrokeWidth,0],o,l)}},update:function(t){var e,r,i,o=this,a=null,l=null,s=o._index,u=o._config,f=h.options.resolve([u.display,!0],t,s);f&&(e=t.dataset.data[s],r=h.valueOrDefault(h.callback(u.formatter,[e,t]),e),(i=h.isNullOrUndef(r)?[]:n.toTextLines(r)).length&&(l=function(t){var e=t.borderWidth||0,r=t.padding,n=t.size.height,i=t.size.width,o=-i/2,a=-n/2;return{frame:{x:o-r.left-e,y:a-r.top-e,w:i+r.width+2*e,h:n+r.height+2*e},text:{x:o,y:a,w:i,h:n}}}(a=o._modelize(f,i,u,t)))),o._model=a,o._rects=l},geometry:function(){return this._rects?this._rects.frame:{}},rotation:function(){return this._model?this._model.rotation:0},visible:function(){return this._model&&this._model.opacity},model:function(){return this._model},draw:function(t,e){var r,i=t.ctx,o=this._model,a=this._rects;this.visible()&&(i.save(),o.clip&&(r=o.area,i.beginPath(),i.rect(r.left,r.top,r.right-r.left,r.bottom-r.top),i.clip()),i.globalAlpha=n.bound(0,o.opacity,1),i.translate(x(e.x),x(e.y)),i.rotate(o.rotation),function(t,e,r){var n=r.backgroundColor,i=r.borderColor,o=r.borderWidth;(n||i&&o)&&(t.beginPath(),h.canvas.roundedRect(t,x(e.x)+o/2,x(e.y)+o/2,x(e.w)-o,x(e.h)-o,r.borderRadius),t.closePath(),n&&(t.fillStyle=n,t.fill()),i&&o&&(t.strokeStyle=i,t.lineWidth=o,t.lineJoin="miter",t.stroke()))}(i,a.frame,o),function(t,e,r,n){var i,o=n.textAlign,a=n.color,l=!!a,s=n.font,u=e.length,f=n.textStrokeColor,d=n.textStrokeWidth,c=f&&d;if(u&&(l||c))for(r=function(t,e,r){var n=r.lineHeight,i=t.w,o=t.x;return"center"===e?o+=i/2:"end"!==e&&"right"!==e||(o+=i),{h:n,w:i,x:o,y:t.y+n/2}}(r,o,s),t.font=s.string,t.textAlign=o,t.textBaseline="middle",t.shadowBlur=n.textShadowBlur,t.shadowColor=n.textShadowColor,l&&(t.fillStyle=a),c&&(t.lineJoin="round",t.lineWidth=d,t.strokeStyle=f),i=0,u=e.length;ie.x+e.w+2||t.y>e.y+e.h+2)},intersects:function(t){var e,r,n,i=this._points(),o=t._points(),a=[k(i[0],i[1]),k(i[0],i[3])];for(this._rotation!==t._rotation&&a.push(k(o[0],o[1]),k(o[0],o[3])),e=0;e=0;--r)for(i=t[r].$layout,n=r-1;n>=0&&i._visible;--n)(o=t[n].$layout)._visible&&i._box.intersects(o._box)&&e(i,o)})(t,function(t,e){var r=t._hidable,n=e._hidable;r&&n||n?e._visible=!1:r&&(t._visible=!1)})}(t)},lookup:function(t,e){var r,n;for(r=t.length-1;r>=0;--r)if((n=t[r].$layout)&&n._visible&&n._box.contains(e))return t[r];return null},draw:function(t,e){var r,n,i,o,a,l;for(r=0,n=e.length;re.length)&&(t=e.length);for(var r=0,n=new Array(t);r2&&void 0!==arguments[2]?arguments[2]:typeof t,n=arguments.length>3?arguments[3]:void 0;if(n&&e in n)return n[e];var a="DIFF_".concat(e.replace(/[^a-zA-Z0-9]/,"")).toLowerCase();if(fe){var o=new de(ce.search);if(o.has(a))return h(decodeURIComponent(String(o.get(a))),r)}return he&&a in ae.env?h(ae.env[a],r):t}function v(e,t,n){var a=null;if(Se.protected.has(e)||Se.allocated.has(e))a=e;else if(!e||we(e)){var o=e?e.length:0;n=[];for(var i=0;i3?O-3:0),x=3;x2&&void 0!==arguments[2]?arguments[2]:{};r.parser||(r.parser={}),t||(t=Ae);var n=new Set(p("rawElements",Le,"array",r.parser)),a=new Set(p("selfClosingElements",Ie,"array",r.parser)),o=/)-->|<(\/?)([a-z\-\_][a-z0-9\-\_]*)\s*([^>]*?)(\/?)>/gi,i=v("#document-fragment",null,[]),s=[i],c=i,d=-1;if(!e.includes("<")&&e)return Ve(c,e,t),i;for(var l,u,f=0;l=o.exec(e);f++){d>-1&&d+l[0].length0){var m=e.slice(0,h);m&&!Me.exec(m)&&Ve(c,m,t)}if(d=o.lastIndex,"!"!==l[0][1]){var g=xe.exec(l[2]),N=g&&t.tags[g[1]],b=N?N.name||N:l[2];if(!l[1]&&(!l[4]&&De[c.rawNodeName]&&De[c.rawNodeName][b]&&(s.pop(),c=s[s.length-1]),c=c.childNodes[c.childNodes.push(Pe(l[2],l[3],t))-1],s.push(c),r.parser.strict||n.has(b))){var y=""),T=e.indexOf(y,o.lastIndex);if(n.has(b)){-1===T?d=o.lastIndex=e.length+1:(d=T+y.length,o.lastIndex=d,l[1]=" ");var E=e.slice(l.index+l[0].length,T);Ve(c,E,t)}}if(l[1]||l[4]||a.has(b))for(;c;){if("/"===l[4]&&g){s.pop(),c=s[s.length-1];break}if(N&&c.rawNodeName===b){s.pop(),c=s[s.length-1];break}if(c.rawNodeName===b){s.pop(),c=s[s.length-1];break}var w=je[c.rawNodeName];{if(!w||!w[b])break;s.pop(),c=s[s.length-1]}}}}var S=e.slice(-1===d?0:d);if(S&&Ve(c,S,t),i.childNodes.length&&"html"===i.childNodes[0].nodeName){var k={before:[],after:[]},R={after:[]},O=i.childNodes[0],M=!0,x=!0;if(O.childNodes=O.childNodes.filter(function(e){if("body"===e.nodeName||"head"===e.nodeName)return"head"===e.nodeName&&(M=!1),"body"===e.nodeName&&(x=!1),!0;e.nodeType===G.ELEMENT&&(M&&x?k.before.push(e):!M&&x?k.after.push(e):x||R.after.push(e))}),O.childNodes[0]&&"head"===O.childNodes[0].nodeName){var A=O.childNodes[0].childNodes;A.unshift.apply(A,k.before),A.push.apply(A,k.after)}else{var C=v("head",null,[]);if(C){var _=C.childNodes;_.unshift.apply(_,k.before),_.push.apply(_,k.after),O.childNodes.unshift(C)}}if(O.childNodes[1]&&"body"===O.childNodes[1].nodeName){var L=O.childNodes[1].childNodes;L.push.apply(L,R.after)}else{var I=v("body",null,[]);if(I){var D=I.childNodes;D.push.apply(D,R.after),O.childNodes.push(I)}}}return i}function g(e){if(He(e),e.childNodes.length)for(var t=0;t1&&(e.newTree=v(u.childNodes))}e.newTree||(e.newTree=v(n)),e.oldTree=t.oldTree;var h=e.oldTree,p=e.newTree;if(o&&h&&p){var m="string"!=typeof p.rawNodeName,N=p.nodeType===G.FRAGMENT,b=N&&!m?p.childNodes:p;e.newTree=v(d,l,b)}}function k(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},a=arguments.length>4&&void 0!==arguments[4]&&arguments[4];e||(e=X.OBJ),t||(t=X.OBJ);var o=n.svgElements,i=void 0===o?new Set:o,s=e.nodeName,c=t.nodeName,d=e===X.OBJ||a,l="svg"===c||i.has(t),u=null;if(te.size&&te.forEach(function(n){var a=n(e,t);a&&a===e?u=r:!1===a?u=!1:a&&(t=a)}),null!==u||!t)return u;if(c===Ze){if(s===Ze&&e.nodeValue!==t.nodeValue)return r.push(Y.NODE_VALUE,e,t.nodeValue,e.nodeValue),e.nodeValue=t.nodeValue,r;if(d)return r.push(Y.NODE_VALUE,t,t.nodeValue,null),r}var f=t.childNodes||[];if(t.nodeType===G.ELEMENT){var h=d?X.OBJ:e.attributes,p=t.attributes||{};for(var v in p){var m=p[v];v in h&&h[v]===p[v]||(d||(h[v]=m),(e&&"script"===e.nodeName||"script"!==t.nodeName||"type"!==v)&&r.push(Y.SET_ATTRIBUTE,d?t:e,v,m))}if(!d)for(var g in h)g in p||(r.push(Y.REMOVE_ATTRIBUTE,e,g),delete h[g])}if(a){for(var N=0;NA&&M.splice(F,1),r.push(Y.REPLACE_CHILD,_,C)}}else f.splice(A,0,C),x+=1}else M.push(_),k(null,_,r,n,!0),r.push(Y.INSERT_BEFORE,e,_,null);else!1===k(C,null,r,n,!0)&&f.splice(A,0,C)}if(M.length!==f.length){for(var B=f.length;B1&&void 0!==arguments[1]?arguments[1]:oe.document,r=arguments.length>2?arguments[2]:void 0,n=v(e),a=K.get(n);if(a)return a;var o=n.nodeName,i=n.rawNodeName,s=void 0===i?o:i,c=n.childNodes,d=void 0===c?[]:c;r=r||"svg"===o;var l=null,u=null;if(ee.forEach(function(e){(u=e(n))&&(l=u)}),!t)return l;null===l&&(l="#text"===o?t.createTextNode(n.nodeValue||X.STR):"#document-fragment"===o?t.createDocumentFragment():r?t.createElementNS(qe,s):t.createElement(s),"script"===o&&(l.type="no-execute"));var f=l;K.set(n,f);for(var h=0;h1?t-1:0),n=1;n1&&void 0!==arguments[1]?arguments[1]:X.OBJ,r=[],n=t.ownerDocument,a=t.svgElements,o=void 0===a?new Set:a,i=e.length,c=0;;){var d=e[c];if(c===i)break;switch(d){case Y.SET_ATTRIBUTE:if("break"===function(){var t=e[c+1],a=e[c+2],i=C(e[c+3]);c+=4;var d=o.has(t),l=R(t,n,d);if(!l)return"break";var u=l.getAttribute(a),f=A("attributeChanged",t,a,u,i);return g(t),f.length?(Promise.all(f).then(function(){return rt(t,l,a,i)}),r.push.apply(r,s(f))):rt(t,l,a,i),"break"}())break;case Y.REMOVE_ATTRIBUTE:if("break"===function(){var t=e[c+1],a=e[c+2];c+=3;var i=o.has(t),d=R(t,n,i);if(!d)return"break";var l=d.getAttribute(a),u=A("attributeChanged",t,a,l,null);return g(t),u.length?(Promise.all(u).then(function(){return nt(d,a)}),r.push.apply(r,s(u))):nt(d,a),"break"}())break;case Y.NODE_VALUE:if("break"===function(){var t=e[c+1],a=e[c+2],i=e[c+3],d=o.has(t);c+=4;var l=R(t,n,d);if(!l)return"break";g(t);var u=A("textChanged",t,i,a);return u.length?(Promise.all(u).then(function(){return at(l,a)}),r.push.apply(r,s(u))):at(l,a),"break"}())break;case Y.INSERT_BEFORE:var l=e[c+1],u=e[c+2],f=e[c+3];c+=4;var h=K.get(l);if(!h)break;var p=o.has(u);g(u);var v=f&&R(f,n,p),m=R(u,n,p);h.insertBefore(m,v||null),r.push.apply(r,s(A("attached",u)));break;case Y.REPLACE_CHILD:if("break"===function(){var t,a,i,d=e[c+1],l=e[c+2];c+=3;var u=o.has(d),f=K.get(l),h=R(d,n,u);if(!f||!f.parentNode)return"break";g(d);var p=null===(t=Z.get("attached"))||void 0===t?void 0:t.size,v=null===(a=Z.get("detached"))||void 0===a?void 0:a.size,m=null===(i=Z.get("replaced"))||void 0===i?void 0:i.size;if(!p&&!v&&!m)return f.parentNode&&(f.parentNode.replaceChild(h,f),N(l)),"break";f.parentNode&&f.parentNode.insertBefore(h,f);var b=[].concat(s(p&&A("attached",d)||X.ARR),s(v&&A("detached",l)||X.ARR),s(m&&A("replaced",l,d)||X.ARR));return b.length?(Promise.all(b).then(function(){f.parentNode&&f.parentNode.removeChild(f),N(l)}),r.push.apply(r,s(b))):(f.parentNode&&f.parentNode.removeChild(f),N(l)),"break"}())break;case Y.REMOVE_CHILD:if("break"===function(){var t=e[c+1];c+=2;var n=K.get(t);if(!n||!n.parentNode)return"break";var a=A("detached",t);return a.length?(Promise.all(a).then(function(){n.parentNode&&n.parentNode.removeChild(n),N(t)}),r.push.apply(r,s(a))):(n.parentNode&&n.parentNode.removeChild(n),N(t)),"break"}())break}}return r}function L(e){var t=e.mount,r=e.state,n=e.patches,a=r.mutationObserver,o=r.measure,i=r.scriptsToExecute;o("patch node");var c=t.ownerDocument,d=e.promises||[];r.ownerDocument=c||oe.document,a&&a.disconnect();var l=function(e){"script"===e.nodeName&&i.set(e,e.attributes.type)};ee.add(l),r.ownerDocument&&d.push.apply(d,s(_(n,r))),ee.delete(l),e.promises=d,o("patch node")}function I(e){var t=e.promises;return t&&t.length?e.promise=Promise.all(t).then(function(){return e.end()}):e.promise=Promise.resolve(e.end())}function D(e){var t=e.state,r=e.input,n=e.config,a=t.measure,o=n.inner;if("string"==typeof r){a("parsing input for new tree");var i,s=m(r,void 0,n),c=s.childNodes;i=v(o?c:c[0]||c),i&&(e.newTree=i),a("parsing input for new tree")}}function j(e){return e.replace(/[&<>]/g,function(e){return"&#".concat(e.charCodeAt(0),";")})}function V(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:X.STR,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r.inner=!0,r.executeScripts=!("executeScripts"in r)||r.executeScripts,r.tasks=r.tasks||ot,st.create(e,t,r).start()}function P(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:X.STR,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r.inner=!1,r.executeScripts=!("executeScripts"in r)||r.executeScripts,r.tasks=r.tasks||ot,st.create(e,t,r).start()}function H(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=v(),n=new Set(t.tasks||ot);n.delete(it.endAsPromise),n.add(function(e){return B(e.oldTree)}),t.tasks=s(n),t.inner=!0;var a="";try{a=st.create(r,e,t).start()}catch(e){throw w(r),e}return w(r),a}function F(e){var t=ft(e);return t.length?" "+t.map(function(t){var r=e[t],n=!r,a="object"==typeof r||"function"==typeof r;return"".concat(t).concat(n||a?"":'="'.concat(String(r),'"'))}).join(" "):""}function B(e){var t="";if(!e)return t;var r=e.childNodes,n=e.nodeType,a=e.nodeName,o=e.nodeValue,i=e.attributes;if(11===n)for(var s=0;s").concat(c,"")}}else t+="<".concat(a).concat(F(i),">");return t}function z(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n")1?t-1:0),n=1;n/i,xe=new RegExp("".concat(Oe,"([^_]*)__")),Ae={tags:[],attributes:{},children:{}},Ce=Object.assign,_e=Array.isArray,Le=["script","noscript","style","template"],Ie=["meta","img","link","input","area","br","hr","area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"],De={li:{li:!0},p:{p:!0,div:!0},td:{td:!0,th:!0},th:{td:!0,th:!0}},je={li:{ul:!0,ol:!0},a:{div:!0},b:{div:!0},i:{div:!0},p:{div:!0},td:{tr:!0,table:!0},th:{tr:!0,table:!0}},Ve=function(e,t,r){var n;if(!("childNodes"in e.attributes)){if(t&&!Me.test(t)&&!xe.test(t))return e.childNodes.push(v("#text",t));for(var a=[],o=t.split(xe),i=0;i\/=\uFDD0-\uFDEF\uFFFE\uFFFF]*)\s*(=\s*("([^"]+)"|'([^']+)'|(\S+)))?/gi;if(a=xe.exec(e))return Pe(r.tags[a[1]],t,r);for(var i,s={};i=o.exec(t||X.STR);){var c="string"==typeof e,d=i[1],l=void 0;if(""===d){var u=t.match(xe);l=u?u[0]:""}var f=i[6]||i[5]||i[4],h=l||f||(c?i[1]:f||!0),p=String(h).match(xe);if(p&&p.length)for(var m=String(h).split(xe),g=xe.exec(d),N=g?r.attributes[g[1]]:d,b=0;b0&&void 0!==arguments[0]?arguments[0]:X.STR,t=arguments.length>1?arguments[1]:void 0,r=K.get(t);e?r.setAttribute("type",e):r.removeAttribute("type")}),t.previousMarkup="outerHTML"in c?c.outerHTML:X.STR,r.executeScripts&&s.forEach(function(e,r){var n=K.get(r),a=n.cloneNode(!0);n&&(q.has(n)&&(w(n),q.set(a,t)),K.set(r,a),n.parentNode&&n.parentNode.replaceChild(a,n))}),s.clear(),Ee.memory.free.size<2*Ee.memory.allocated.size&&b(),this.endedCallbacks.forEach(function(t){return t(e)}),this.endedCallbacks.clear(),o("finalize"),o("render"),this}},{key:"onceEnded",value:function(e){this.endedCallbacks.add(e)}}],[{key:"create",value:function(t,r,n){return new e(t,r,n)}},{key:"flow",value:function(e,t){for(var r=e,n=0;n(a||v.height())},_setFocus:function(){(b.st.focus?b.content.find(b.st.focus).eq(0):b.wrap).focus()},_onFocusIn:function(c){return c.target===b.wrap[0]||a.contains(b.wrap[0],c.target)?void 0:(b._setFocus(),!1)},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),y(l,[b,c,d]),a.each(c,function(c,d){if(void 0===d||d===!1)return!0;if(e=c.split("_"),e.length>1){var f=b.find(p+"-"+e[0]);if(f.length>0){var g=e[1];"replaceWith"===g?f[0]!==d[0]&&f.replaceWith(d):"img"===g?f.is("img")?f.attr("src",d):f.replaceWith(a("").attr("src",d).attr("class",f.attr("class"))):f.attr(e[1],d)}}else b.find(p+"-"+c).html(d)})},_getScrollbarSize:function(){if(void 0===b.scrollbarSize){var a=document.createElement("div");a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),b.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return b.scrollbarSize}},a.magnificPopup={instance:null,proto:t.prototype,modules:[],open:function(b,c){return A(),b=b?a.extend(!0,{},b):{},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'',tClose:"Close (Esc)",tLoading:"Loading...",autoFocusLast:!0}},a.fn.magnificPopup=function(c){A();var d=a(this);if("string"==typeof c)if("open"===c){var e,f=u?d.data("magnificPopup"):d[0].magnificPopup,g=parseInt(arguments[1],10)||0;f.items?e=f.items[g]:(e=d,f.delegate&&(e=e.find(f.delegate)),e=e.eq(g)),b._openClick({mfpEl:e},d,f)}else b.isOpen&&b[c].apply(b,Array.prototype.slice.call(arguments,1));else c=a.extend(!0,{},c),u?d.data("magnificPopup",c):d[0].magnificPopup=c,b.addGroup(d,c);return d};var C,D,E,F="inline",G=function(){E&&(D.after(E.addClass(C)).detach(),E=null)};a.magnificPopup.registerModule(F,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){b.types.push(F),w(h+"."+F,function(){G()})},getInline:function(c,d){if(G(),c.src){var e=b.st.inline,f=a(c.src);if(f.length){var g=f[0].parentNode;g&&g.tagName&&(D||(C=e.hiddenClass,D=x(C),C="mfp-"+C),E=f.after(D).detach().removeClass(C)),b.updateStatus("ready")}else b.updateStatus("error",e.tNotFound),f=a("
");return c.inlineElement=f,f}return b.updateStatus("ready"),b._parseMarkup(d,{},c),d}}});var H,I="ajax",J=function(){H&&a(document.body).removeClass(H)},K=function(){J(),b.req&&b.req.abort()};a.magnificPopup.registerModule(I,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'The content could not be loaded.'},proto:{initAjax:function(){b.types.push(I),H=b.st.ajax.cursor,w(h+"."+I,K),w("BeforeChange."+I,K)},getAjax:function(c){H&&a(document.body).addClass(H),b.updateStatus("loading");var d=a.extend({url:c.src,success:function(d,e,f){var g={data:d,xhr:f};y("ParseAjax",g),b.appendContent(a(g.data),I),c.finished=!0,J(),b._setFocus(),setTimeout(function(){b.wrap.addClass(q)},16),b.updateStatus("ready"),y("AjaxContentAdded")},error:function(){J(),c.finished=c.loadError=!0,b.updateStatus("error",b.st.ajax.tError.replace("%url%",c.src))}},b.st.ajax.settings);return b.req=a.ajax(d),""}}});var L,M=function(c){if(c.data&&void 0!==c.data.title)return c.data.title;var d=b.st.image.titleSrc;if(d){if(a.isFunction(d))return d.call(b,c);if(c.el)return c.el.attr(d)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'The image could not be loaded.'},proto:{initImage:function(){var c=b.st.image,d=".image";b.types.push("image"),w(m+d,function(){"image"===b.currItem.type&&c.cursor&&a(document.body).addClass(c.cursor)}),w(h+d,function(){c.cursor&&a(document.body).removeClass(c.cursor),v.off("resize"+p)}),w("Resize"+d,b.resizeImage),b.isLowIE&&w("AfterChange",b.resizeImage)},resizeImage:function(){var a=b.currItem;if(a&&a.img&&b.st.image.verticalFit){var c=0;b.isLowIE&&(c=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",b.wH-c)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,L&&clearInterval(L),a.isCheckingImgSize=!1,y("ImageHasSize",a),a.imgHidden&&(b.content&&b.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var c=0,d=a.img[0],e=function(f){L&&clearInterval(L),L=setInterval(function(){return d.naturalWidth>0?void b._onImageHasSize(a):(c>200&&clearInterval(L),c++,void(3===c?e(10):40===c?e(50):100===c&&e(500)))},f)};e(1)},getImage:function(c,d){var e=0,f=function(){c&&(c.img[0].complete?(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("ready")),c.hasSize=!0,c.loaded=!0,y("ImageLoadComplete")):(e++,200>e?setTimeout(f,100):g()))},g=function(){c&&(c.img.off(".mfploader"),c===b.currItem&&(b._onImageHasSize(c),b.updateStatus("error",h.tError.replace("%url%",c.src))),c.hasSize=!0,c.loaded=!0,c.loadError=!0)},h=b.st.image,i=d.find(".mfp-img");if(i.length){var j=document.createElement("img");j.className="mfp-img",c.el&&c.el.find("img").length&&(j.alt=c.el.find("img").attr("alt")),c.img=a(j).on("load.mfploader",f).on("error.mfploader",g),j.src=c.src,i.is("img")&&(c.img=c.img.clone()),j=c.img[0],j.naturalWidth>0?c.hasSize=!0:j.width||(c.hasSize=!1)}return b._parseMarkup(d,{title:M(c),img_replaceWith:c.img},c),b.resizeImage(),c.hasSize?(L&&clearInterval(L),c.loadError?(d.addClass("mfp-loading"),b.updateStatus("error",h.tError.replace("%url%",c.src))):(d.removeClass("mfp-loading"),b.updateStatus("ready")),d):(b.updateStatus("loading"),c.loading=!0,c.hasSize||(c.imgHidden=!0,d.addClass("mfp-loading"),b.findImageSize(c)),d)}}});var N,O=function(){return void 0===N&&(N=void 0!==document.createElement("p").style.MozTransform),N};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a,c=b.st.zoom,d=".zoom";if(c.enabled&&b.supportsTransition){var e,f,g=c.duration,j=function(a){var b=a.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+c.duration/1e3+"s "+c.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,b.css(e),b},k=function(){b.content.css("visibility","visible")};w("BuildControls"+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.content.css("visibility","hidden"),a=b._getItemToZoom(),!a)return void k();f=j(a),f.css(b._getOffset()),b.wrap.append(f),e=setTimeout(function(){f.css(b._getOffset(!0)),e=setTimeout(function(){k(),setTimeout(function(){f.remove(),a=f=null,y("ZoomAnimationEnded")},16)},g)},16)}}),w(i+d,function(){if(b._allowZoom()){if(clearTimeout(e),b.st.removalDelay=g,!a){if(a=b._getItemToZoom(),!a)return;f=j(a)}f.css(b._getOffset(!0)),b.wrap.append(f),b.content.css("visibility","hidden"),setTimeout(function(){f.css(b._getOffset())},16)}}),w(h+d,function(){b._allowZoom()&&(k(),f&&f.remove(),a=null)})}},_allowZoom:function(){return"image"===b.currItem.type},_getItemToZoom:function(){return b.currItem.hasSize?b.currItem.img:!1},_getOffset:function(c){var d;d=c?b.currItem.img:b.st.zoom.opener(b.currItem.el||b.currItem);var e=d.offset(),f=parseInt(d.css("padding-top"),10),g=parseInt(d.css("padding-bottom"),10);e.top-=a(window).scrollTop()-f;var h={width:d.width(),height:(u?d.innerHeight():d[0].offsetHeight)-g-f};return O()?h["-moz-transform"]=h.transform="translate("+e.left+"px,"+e.top+"px)":(h.left=e.left,h.top=e.top),h}}});var P="iframe",Q="//about:blank",R=function(a){if(b.currTemplate[P]){var c=b.currTemplate[P].find("iframe");c.length&&(a||(c[0].src=Q),b.isIE8&&c.css("display",a?"block":"none"))}};a.magnificPopup.registerModule(P,{options:{markup:'
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){b.types.push(P),w("BeforeChange",function(a,b,c){b!==c&&(b===P?R():c===P&&R(!0))}),w(h+"."+P,function(){R()})},getIframe:function(c,d){var e=c.src,f=b.st.iframe;a.each(f.patterns,function(){return e.indexOf(this.index)>-1?(this.id&&(e="string"==typeof this.id?e.substr(e.lastIndexOf(this.id)+this.id.length,e.length):this.id.call(this,e)),e=this.src.replace("%id%",e),!1):void 0});var g={};return f.srcAction&&(g[f.srcAction]=e),b._parseMarkup(d,g,c),b.updateStatus("ready"),d}}});var S=function(a){var c=b.items.length;return a>c-1?a-c:0>a?c+a:a},T=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=b.st.gallery,e=".mfp-gallery";return b.direction=!0,c&&c.enabled?(f+=" mfp-gallery",w(m+e,function(){c.navigateByImgClick&&b.wrap.on("click"+e,".mfp-img",function(){return b.items.length>1?(b.next(),!1):void 0}),d.on("keydown"+e,function(a){37===a.keyCode?b.prev():39===a.keyCode&&b.next()})}),w("UpdateStatus"+e,function(a,c){c.text&&(c.text=T(c.text,b.currItem.index,b.items.length))}),w(l+e,function(a,d,e,f){var g=b.items.length;e.counter=g>1?T(c.tCounter,f.index,g):""}),w("BuildControls"+e,function(){if(b.items.length>1&&c.arrows&&!b.arrowLeft){var d=c.arrowMarkup,e=b.arrowLeft=a(d.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(s),f=b.arrowRight=a(d.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(s);e.click(function(){b.prev()}),f.click(function(){b.next()}),b.container.append(e.add(f))}}),w(n+e,function(){b._preloadTimeout&&clearTimeout(b._preloadTimeout),b._preloadTimeout=setTimeout(function(){b.preloadNearbyImages(),b._preloadTimeout=null},16)}),void w(h+e,function(){d.off(e),b.wrap.off("click"+e),b.arrowRight=b.arrowLeft=null})):!1},next:function(){b.direction=!0,b.index=S(b.index+1),b.updateItemHTML()},prev:function(){b.direction=!1,b.index=S(b.index-1),b.updateItemHTML()},goTo:function(a){b.direction=a>=b.index,b.index=a,b.updateItemHTML()},preloadNearbyImages:function(){var a,c=b.st.gallery.preload,d=Math.min(c[0],b.items.length),e=Math.min(c[1],b.items.length);for(a=1;a<=(b.direction?e:d);a++)b._preloadItem(b.index+a);for(a=1;a<=(b.direction?d:e);a++)b._preloadItem(b.index-a)},_preloadItem:function(c){if(c=S(c),!b.items[c].preloaded){var d=b.items[c];d.parsed||(d=b.parseEl(c)),y("LazyLoad",d),"image"===d.type&&(d.img=a('').on("load.mfploader",function(){d.hasSize=!0}).on("error.mfploader",function(){d.hasSize=!0,d.loadError=!0,y("LazyLoadError",d)}).attr("src",d.src)),d.preloaded=!0}}}});var U="retina";a.magnificPopup.registerModule(U,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=b.st.retina,c=a.ratio;c=isNaN(c)?c():c,c>1&&(w("ImageHasSize."+U,function(a,b){b.img.css({"max-width":b.img[0].naturalWidth/c,width:"100%"})}),w("ElementParse."+U,function(b,d){d.src=a.replaceSrc(d,c)}))}}}}),A()}); \ No newline at end of file diff --git a/public/javascripts/pikaday.js b/public/javascripts/pikaday.js deleted file mode 100644 index 8dae8382cd..0000000000 --- a/public/javascripts/pikaday.js +++ /dev/null @@ -1,1257 +0,0 @@ -/*! - * Pikaday - * - * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/Pikaday/Pikaday - */ - -(function (root, factory) -{ - 'use strict'; - - var moment; - if (typeof exports === 'object') { - // CommonJS module - // Load moment.js as an optional dependency - try { moment = require('moment'); } catch (e) {} - module.exports = factory(moment); - } else if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(function (req) - { - // Load moment.js as an optional dependency - var id = 'moment'; - try { moment = req(id); } catch (e) {} - return factory(moment); - }); - } else { - root.Pikaday = factory(root.moment); - } -}(this, function (moment) -{ - 'use strict'; - - /** - * feature detection and helper functions - */ - var hasMoment = typeof moment === 'function', - - hasEventListeners = !!window.addEventListener, - - document = window.document, - - sto = window.setTimeout, - - addEvent = function(el, e, callback, capture) - { - if (hasEventListeners) { - el.addEventListener(e, callback, !!capture); - } else { - el.attachEvent('on' + e, callback); - } - }, - - removeEvent = function(el, e, callback, capture) - { - if (hasEventListeners) { - el.removeEventListener(e, callback, !!capture); - } else { - el.detachEvent('on' + e, callback); - } - }, - - trim = function(str) - { - return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,''); - }, - - hasClass = function(el, cn) - { - return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1; - }, - - addClass = function(el, cn) - { - if (!hasClass(el, cn)) { - el.className = (el.className === '') ? cn : el.className + ' ' + cn; - } - }, - - removeClass = function(el, cn) - { - el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' ')); - }, - - isArray = function(obj) - { - return (/Array/).test(Object.prototype.toString.call(obj)); - }, - - isDate = function(obj) - { - return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); - }, - - isWeekend = function(date) - { - var day = date.getDay(); - return day === 0 || day === 6; - }, - - isLeapYear = function(year) - { - // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 - return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; - }, - - getDaysInMonth = function(year, month) - { - return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; - }, - - setToStartOfDay = function(date) - { - if (isDate(date)) date.setHours(0,0,0,0); - }, - - compareDates = function(a,b) - { - // weak date comparison (use setToStartOfDay(date) to ensure correct result) - return a.getTime() === b.getTime(); - }, - - extend = function(to, from, overwrite) - { - var prop, hasProp; - for (prop in from) { - hasProp = to[prop] !== undefined; - if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) { - if (isDate(from[prop])) { - if (overwrite) { - to[prop] = new Date(from[prop].getTime()); - } - } - else if (isArray(from[prop])) { - if (overwrite) { - to[prop] = from[prop].slice(0); - } - } else { - to[prop] = extend({}, from[prop], overwrite); - } - } else if (overwrite || !hasProp) { - to[prop] = from[prop]; - } - } - return to; - }, - - fireEvent = function(el, eventName, data) - { - var ev; - - if (document.createEvent) { - ev = document.createEvent('HTMLEvents'); - ev.initEvent(eventName, true, false); - ev = extend(ev, data); - el.dispatchEvent(ev); - } else if (document.createEventObject) { - ev = document.createEventObject(); - ev = extend(ev, data); - el.fireEvent('on' + eventName, ev); - } - }, - - adjustCalendar = function(calendar) { - if (calendar.month < 0) { - calendar.year -= Math.ceil(Math.abs(calendar.month)/12); - calendar.month += 12; - } - if (calendar.month > 11) { - calendar.year += Math.floor(Math.abs(calendar.month)/12); - calendar.month -= 12; - } - return calendar; - }, - - /** - * defaults and localisation - */ - defaults = { - - // bind the picker to a form field - field: null, - - // automatically show/hide the picker on `field` focus (default `true` if `field` is set) - bound: undefined, - - // data-attribute on the input field with an aria assistance tekst (only applied when `bound` is set) - ariaLabel: 'Use the arrow keys to pick a date', - - // position of the datepicker, relative to the field (default to bottom & left) - // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position) - position: 'bottom left', - - // automatically fit in the viewport even if it means repositioning from the position option - reposition: true, - - // the default output format for `.toString()` and `field` value - format: 'YYYY-MM-DD', - - // the toString function which gets passed a current date object and format - // and returns a string - toString: null, - - // used to create date object from current input string - parse: null, - - // the initial date to view when first opened - defaultDate: null, - - // make the `defaultDate` the initial selected value - setDefaultDate: false, - - // first day of week (0: Sunday, 1: Monday etc) - firstDay: 0, - - // the default flag for moment's strict date parsing - formatStrict: false, - - // the minimum/earliest date that can be selected - minDate: null, - // the maximum/latest date that can be selected - maxDate: null, - - // number of years either side, or array of upper/lower range - yearRange: 10, - - // show week numbers at head of row - showWeekNumber: false, - - // Week picker mode - pickWholeWeek: false, - - // used internally (don't config outside) - minYear: 0, - maxYear: 9999, - minMonth: undefined, - maxMonth: undefined, - - startRange: null, - endRange: null, - - isRTL: false, - - // Additional text to append to the year in the calendar title - yearSuffix: '', - - // Render the month after year in the calendar title - showMonthAfterYear: false, - - // Render days of the calendar grid that fall in the next or previous month - showDaysInNextAndPreviousMonths: false, - - // Allows user to select days that fall in the next or previous month - enableSelectionDaysInNextAndPreviousMonths: false, - - // how many months are visible - numberOfMonths: 1, - - // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`) - // only used for the first display or when a selected date is not visible - mainCalendar: 'left', - - // Specify a DOM element to render the calendar in - container: undefined, - - // Blur field when date is selected - blurFieldOnSelect : true, - - // internationalization - i18n: { - previousMonth : 'Previous Month', - nextMonth : 'Next Month', - months : ['January','February','March','April','May','June','July','August','September','October','November','December'], - weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], - weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] - }, - - // Theme Classname - theme: null, - - // events array - events: [], - - // callback function - onSelect: null, - onOpen: null, - onClose: null, - onDraw: null, - - // Enable keyboard input - keyboardInput: true - }, - - - /** - * templating functions to abstract HTML rendering - */ - renderDayName = function(opts, day, abbr) - { - day += opts.firstDay; - while (day >= 7) { - day -= 7; - } - return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day]; - }, - - renderDay = function(opts) - { - var arr = []; - var ariaSelected = 'false'; - if (opts.isEmpty) { - if (opts.showDaysInNextAndPreviousMonths) { - arr.push('is-outside-current-month'); - - if(!opts.enableSelectionDaysInNextAndPreviousMonths) { - arr.push('is-selection-disabled'); - } - - } else { - return '
' + - '' + - '' + weekNum + '
' + renderDayName(opts, i, true) + '
' + renderHead(opts) + renderBody(data) + '
'; - }, - - - /** - * Pikaday constructor - */ - Pikaday = function(options) - { - var self = this, - opts = self.config(options); - - self._onMouseDown = function(e) - { - if (!self._v) { - return; - } - e = e || window.event; - var target = e.target || e.srcElement; - if (!target) { - return; - } - - if (!hasClass(target, 'is-disabled')) { - if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) { - self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day'))); - if (opts.bound) { - sto(function() { - self.hide(); - if (opts.blurFieldOnSelect && opts.field) { - opts.field.blur(); - } - }, 100); - } - } - else if (hasClass(target, 'pika-prev')) { - self.prevMonth(); - } - else if (hasClass(target, 'pika-next')) { - self.nextMonth(); - } - } - if (!hasClass(target, 'pika-select')) { - // if this is touch event prevent mouse events emulation - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - return false; - } - } else { - self._c = true; - } - }; - - self._onChange = function(e) - { - e = e || window.event; - var target = e.target || e.srcElement; - if (!target) { - return; - } - if (hasClass(target, 'pika-select-month')) { - self.gotoMonth(target.value); - } - else if (hasClass(target, 'pika-select-year')) { - self.gotoYear(target.value); - } - }; - - self._onKeyChange = function(e) - { - e = e || window.event; - - if (self.isVisible()) { - - switch(e.keyCode){ - case 13: - case 27: - if (opts.field) { - opts.field.blur(); - } - break; - case 37: - e.preventDefault(); - self.adjustDate('subtract', 1); - break; - case 38: - self.adjustDate('subtract', 7); - break; - case 39: - self.adjustDate('add', 1); - break; - case 40: - self.adjustDate('add', 7); - break; - } - } - }; - - self._onInputChange = function(e) - { - var date; - - if (e.firedBy === self) { - return; - } - if (opts.parse) { - date = opts.parse(opts.field.value, opts.format); - } else if (hasMoment) { - date = moment(opts.field.value, opts.format, opts.formatStrict); - date = (date && date.isValid()) ? date.toDate() : null; - } - else { - date = new Date(Date.parse(opts.field.value)); - } - if (isDate(date)) { - self.setDate(date); - } - if (!self._v) { - self.show(); - } - }; - - self._onInputFocus = function() - { - self.show(); - }; - - self._onInputClick = function() - { - self.show(); - }; - - self._onInputBlur = function() - { - // IE allows pika div to gain focus; catch blur the input field - var pEl = document.activeElement; - do { - if (hasClass(pEl, 'pika-single')) { - return; - } - } - while ((pEl = pEl.parentNode)); - - if (!self._c) { - self._b = sto(function() { - self.hide(); - }, 50); - } - self._c = false; - }; - - self._onClick = function(e) - { - e = e || window.event; - var target = e.target || e.srcElement, - pEl = target; - if (!target) { - return; - } - if (!hasEventListeners && hasClass(target, 'pika-select')) { - if (!target.onchange) { - target.setAttribute('onchange', 'return;'); - addEvent(target, 'change', self._onChange); - } - } - do { - if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) { - return; - } - } - while ((pEl = pEl.parentNode)); - if (self._v && target !== opts.trigger && pEl !== opts.trigger) { - self.hide(); - } - }; - - self.el = document.createElement('div'); - self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : ''); - - addEvent(self.el, 'mousedown', self._onMouseDown, true); - addEvent(self.el, 'touchend', self._onMouseDown, true); - addEvent(self.el, 'change', self._onChange); - - if (opts.keyboardInput) { - addEvent(document, 'keydown', self._onKeyChange); - } - - if (opts.field) { - if (opts.container) { - opts.container.appendChild(self.el); - } else if (opts.bound) { - document.body.appendChild(self.el); - } else { - opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling); - } - addEvent(opts.field, 'change', self._onInputChange); - - if (!opts.defaultDate) { - if (hasMoment && opts.field.value) { - opts.defaultDate = moment(opts.field.value, opts.format).toDate(); - } else { - opts.defaultDate = new Date(Date.parse(opts.field.value)); - } - opts.setDefaultDate = true; - } - } - - var defDate = opts.defaultDate; - - if (isDate(defDate)) { - if (opts.setDefaultDate) { - self.setDate(defDate, true); - } else { - self.gotoDate(defDate); - } - } else { - self.gotoDate(new Date()); - } - - if (opts.bound) { - this.hide(); - self.el.className += ' is-bound'; - addEvent(opts.trigger, 'click', self._onInputClick); - addEvent(opts.trigger, 'focus', self._onInputFocus); - addEvent(opts.trigger, 'blur', self._onInputBlur); - } else { - this.show(); - } - }; - - - /** - * public Pikaday API - */ - Pikaday.prototype = { - - - /** - * configure functionality - */ - config: function(options) - { - if (!this._o) { - this._o = extend({}, defaults, true); - } - - var opts = extend(this._o, options, true); - - opts.isRTL = !!opts.isRTL; - - opts.field = (opts.field && opts.field.nodeName) ? opts.field : null; - - opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null; - - opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field); - - opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field; - - opts.disableWeekends = !!opts.disableWeekends; - - opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null; - - var nom = parseInt(opts.numberOfMonths, 10) || 1; - opts.numberOfMonths = nom > 4 ? 4 : nom; - - if (!isDate(opts.minDate)) { - opts.minDate = false; - } - if (!isDate(opts.maxDate)) { - opts.maxDate = false; - } - if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) { - opts.maxDate = opts.minDate = false; - } - if (opts.minDate) { - this.setMinDate(opts.minDate); - } - if (opts.maxDate) { - this.setMaxDate(opts.maxDate); - } - - if (isArray(opts.yearRange)) { - var fallback = new Date().getFullYear() - 10; - opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback; - opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback; - } else { - opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange; - if (opts.yearRange > 100) { - opts.yearRange = 100; - } - } - - return opts; - }, - - /** - * return a formatted string of the current selection (using Moment.js if available) - */ - toString: function(format) - { - format = format || this._o.format; - if (!isDate(this._d)) { - return ''; - } - if (this._o.toString) { - return this._o.toString(this._d, format); - } - if (hasMoment) { - return moment(this._d).format(format); - } - return this._d.toDateString(); - }, - - /** - * return a Moment.js object of the current selection (if available) - */ - getMoment: function() - { - return hasMoment ? moment(this._d) : null; - }, - - /** - * set the current selection from a Moment.js object (if available) - */ - setMoment: function(date, preventOnSelect) - { - if (hasMoment && moment.isMoment(date)) { - this.setDate(date.toDate(), preventOnSelect); - } - }, - - /** - * return a Date object of the current selection - */ - getDate: function() - { - return isDate(this._d) ? new Date(this._d.getTime()) : null; - }, - - /** - * set the current selection - */ - setDate: function(date, preventOnSelect) - { - if (!date) { - this._d = null; - - if (this._o.field) { - this._o.field.value = ''; - fireEvent(this._o.field, 'change', { firedBy: this }); - } - - return this.draw(); - } - if (typeof date === 'string') { - date = new Date(Date.parse(date)); - } - if (!isDate(date)) { - return; - } - - var min = this._o.minDate, - max = this._o.maxDate; - - if (isDate(min) && date < min) { - date = min; - } else if (isDate(max) && date > max) { - date = max; - } - - this._d = new Date(date.getTime()); - setToStartOfDay(this._d); - this.gotoDate(this._d); - - if (this._o.field) { - this._o.field.value = this.toString(); - fireEvent(this._o.field, 'change', { firedBy: this }); - } - if (!preventOnSelect && typeof this._o.onSelect === 'function') { - this._o.onSelect.call(this, this.getDate()); - } - }, - - /** - * change view to a specific date - */ - gotoDate: function(date) - { - var newCalendar = true; - - if (!isDate(date)) { - return; - } - - if (this.calendars) { - var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), - lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1), - visibleDate = date.getTime(); - // get the end of the month - lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1); - lastVisibleDate.setDate(lastVisibleDate.getDate()-1); - newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate); - } - - if (newCalendar) { - this.calendars = [{ - month: date.getMonth(), - year: date.getFullYear() - }]; - if (this._o.mainCalendar === 'right') { - this.calendars[0].month += 1 - this._o.numberOfMonths; - } - } - - this.adjustCalendars(); - }, - - adjustDate: function(sign, days) { - - var day = this.getDate() || new Date(); - var difference = parseInt(days)*24*60*60*1000; - - var newDay; - - if (sign === 'add') { - newDay = new Date(day.valueOf() + difference); - } else if (sign === 'subtract') { - newDay = new Date(day.valueOf() - difference); - } - - this.setDate(newDay); - }, - - adjustCalendars: function() { - this.calendars[0] = adjustCalendar(this.calendars[0]); - for (var c = 1; c < this._o.numberOfMonths; c++) { - this.calendars[c] = adjustCalendar({ - month: this.calendars[0].month + c, - year: this.calendars[0].year - }); - } - this.draw(); - }, - - gotoToday: function() - { - this.gotoDate(new Date()); - }, - - /** - * change view to a specific month (zero-index, e.g. 0: January) - */ - gotoMonth: function(month) - { - if (!isNaN(month)) { - this.calendars[0].month = parseInt(month, 10); - this.adjustCalendars(); - } - }, - - nextMonth: function() - { - this.calendars[0].month++; - this.adjustCalendars(); - }, - - prevMonth: function() - { - this.calendars[0].month--; - this.adjustCalendars(); - }, - - /** - * change view to a specific full year (e.g. "2012") - */ - gotoYear: function(year) - { - if (!isNaN(year)) { - this.calendars[0].year = parseInt(year, 10); - this.adjustCalendars(); - } - }, - - /** - * change the minDate - */ - setMinDate: function(value) - { - if(value instanceof Date) { - setToStartOfDay(value); - this._o.minDate = value; - this._o.minYear = value.getFullYear(); - this._o.minMonth = value.getMonth(); - } else { - this._o.minDate = defaults.minDate; - this._o.minYear = defaults.minYear; - this._o.minMonth = defaults.minMonth; - this._o.startRange = defaults.startRange; - } - - this.draw(); - }, - - /** - * change the maxDate - */ - setMaxDate: function(value) - { - if(value instanceof Date) { - setToStartOfDay(value); - this._o.maxDate = value; - this._o.maxYear = value.getFullYear(); - this._o.maxMonth = value.getMonth(); - } else { - this._o.maxDate = defaults.maxDate; - this._o.maxYear = defaults.maxYear; - this._o.maxMonth = defaults.maxMonth; - this._o.endRange = defaults.endRange; - } - - this.draw(); - }, - - setStartRange: function(value) - { - this._o.startRange = value; - }, - - setEndRange: function(value) - { - this._o.endRange = value; - }, - - /** - * refresh the HTML - */ - draw: function(force) - { - if (!this._v && !force) { - return; - } - var opts = this._o, - minYear = opts.minYear, - maxYear = opts.maxYear, - minMonth = opts.minMonth, - maxMonth = opts.maxMonth, - html = '', - randId; - - if (this._y <= minYear) { - this._y = minYear; - if (!isNaN(minMonth) && this._m < minMonth) { - this._m = minMonth; - } - } - if (this._y >= maxYear) { - this._y = maxYear; - if (!isNaN(maxMonth) && this._m > maxMonth) { - this._m = maxMonth; - } - } - - randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2); - - for (var c = 0; c < opts.numberOfMonths; c++) { - html += '
' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '
'; - } - - this.el.innerHTML = html; - - if (opts.bound) { - if(opts.field.type !== 'hidden') { - sto(function() { - opts.trigger.focus(); - }, 1); - } - } - - if (typeof this._o.onDraw === 'function') { - this._o.onDraw(this); - } - - if (opts.bound) { - // let the screen reader user know to use arrow keys - opts.field.setAttribute('aria-label', opts.ariaLabel); - } - }, - - adjustPosition: function() - { - var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect, leftAligned, bottomAligned; - - if (this._o.container) return; - - this.el.style.position = 'absolute'; - - field = this._o.trigger; - pEl = field; - width = this.el.offsetWidth; - height = this.el.offsetHeight; - viewportWidth = window.innerWidth || document.documentElement.clientWidth; - viewportHeight = window.innerHeight || document.documentElement.clientHeight; - scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; - leftAligned = true; - bottomAligned = true; - - if (typeof field.getBoundingClientRect === 'function') { - clientRect = field.getBoundingClientRect(); - left = clientRect.left + window.pageXOffset; - top = clientRect.bottom + window.pageYOffset; - } else { - left = pEl.offsetLeft; - top = pEl.offsetTop + pEl.offsetHeight; - while((pEl = pEl.offsetParent)) { - left += pEl.offsetLeft; - top += pEl.offsetTop; - } - } - - // default position is bottom & left - if ((this._o.reposition && left + width > viewportWidth) || - ( - this._o.position.indexOf('right') > -1 && - left - width + field.offsetWidth > 0 - ) - ) { - left = left - width + field.offsetWidth; - leftAligned = false; - } - if ((this._o.reposition && top + height > viewportHeight + scrollTop) || - ( - this._o.position.indexOf('top') > -1 && - top - height - field.offsetHeight > 0 - ) - ) { - top = top - height - field.offsetHeight; - bottomAligned = false; - } - - this.el.style.left = left + 'px'; - this.el.style.top = top + 'px'; - - addClass(this.el, leftAligned ? 'left-aligned' : 'right-aligned'); - addClass(this.el, bottomAligned ? 'bottom-aligned' : 'top-aligned'); - removeClass(this.el, !leftAligned ? 'left-aligned' : 'right-aligned'); - removeClass(this.el, !bottomAligned ? 'bottom-aligned' : 'top-aligned'); - }, - - /** - * render HTML for a particular month - */ - render: function(year, month, randId) - { - var opts = this._o, - now = new Date(), - days = getDaysInMonth(year, month), - before = new Date(year, month, 1).getDay(), - data = [], - row = []; - setToStartOfDay(now); - if (opts.firstDay > 0) { - before -= opts.firstDay; - if (before < 0) { - before += 7; - } - } - var previousMonth = month === 0 ? 11 : month - 1, - nextMonth = month === 11 ? 0 : month + 1, - yearOfPreviousMonth = month === 0 ? year - 1 : year, - yearOfNextMonth = month === 11 ? year + 1 : year, - daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth); - var cells = days + before, - after = cells; - while(after > 7) { - after -= 7; - } - cells += 7 - after; - var isWeekSelected = false; - for (var i = 0, r = 0; i < cells; i++) - { - var day = new Date(year, month, 1 + (i - before)), - isSelected = isDate(this._d) ? compareDates(day, this._d) : false, - isToday = compareDates(day, now), - hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false, - isEmpty = i < before || i >= (days + before), - dayNumber = 1 + (i - before), - monthNumber = month, - yearNumber = year, - isStartRange = opts.startRange && compareDates(opts.startRange, day), - isEndRange = opts.endRange && compareDates(opts.endRange, day), - isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, - isDisabled = (opts.minDate && day < opts.minDate) || - (opts.maxDate && day > opts.maxDate) || - (opts.disableWeekends && isWeekend(day)) || - (opts.disableDayFn && opts.disableDayFn(day)); - - if (isEmpty) { - if (i < before) { - dayNumber = daysInPreviousMonth + dayNumber; - monthNumber = previousMonth; - yearNumber = yearOfPreviousMonth; - } else { - dayNumber = dayNumber - days; - monthNumber = nextMonth; - yearNumber = yearOfNextMonth; - } - } - - var dayConfig = { - day: dayNumber, - month: monthNumber, - year: yearNumber, - hasEvent: hasEvent, - isSelected: isSelected, - isToday: isToday, - isDisabled: isDisabled, - isEmpty: isEmpty, - isStartRange: isStartRange, - isEndRange: isEndRange, - isInRange: isInRange, - showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths, - enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths - }; - - if (opts.pickWholeWeek && isSelected) { - isWeekSelected = true; - } - - row.push(renderDay(dayConfig)); - - if (++r === 7) { - if (opts.showWeekNumber) { - row.unshift(renderWeek(i - before, month, year)); - } - data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected)); - row = []; - r = 0; - isWeekSelected = false; - } - } - return renderTable(opts, data, randId); - }, - - isVisible: function() - { - return this._v; - }, - - show: function() - { - if (!this.isVisible()) { - this._v = true; - this.draw(); - removeClass(this.el, 'is-hidden'); - if (this._o.bound) { - addEvent(document, 'click', this._onClick); - this.adjustPosition(); - } - if (typeof this._o.onOpen === 'function') { - this._o.onOpen.call(this); - } - } - }, - - hide: function() - { - var v = this._v; - if (v !== false) { - if (this._o.bound) { - removeEvent(document, 'click', this._onClick); - } - this.el.style.position = 'static'; // reset - this.el.style.left = 'auto'; - this.el.style.top = 'auto'; - addClass(this.el, 'is-hidden'); - this._v = false; - if (v !== undefined && typeof this._o.onClose === 'function') { - this._o.onClose.call(this); - } - } - }, - - /** - * GAME OVER - */ - destroy: function() - { - var opts = this._o; - - this.hide(); - removeEvent(this.el, 'mousedown', this._onMouseDown, true); - removeEvent(this.el, 'touchend', this._onMouseDown, true); - removeEvent(this.el, 'change', this._onChange); - if (opts.keyboardInput) { - removeEvent(document, 'keydown', this._onKeyChange); - } - if (opts.field) { - removeEvent(opts.field, 'change', this._onInputChange); - if (opts.bound) { - removeEvent(opts.trigger, 'click', this._onInputClick); - removeEvent(opts.trigger, 'focus', this._onInputFocus); - removeEvent(opts.trigger, 'blur', this._onInputBlur); - } - } - if (this.el.parentNode) { - this.el.parentNode.removeChild(this.el); - } - } - - }; - - return Pikaday; -})); From 78fab9b7e1cd3ade1a1effe46965319f41d154eb Mon Sep 17 00:00:00 2001 From: Penar Musaraj Date: Mon, 20 Jun 2022 02:05:46 +0000 Subject: [PATCH 049/184] DEV: Add `before-list-area` plugin outlet (#17136) --- app/assets/javascripts/discourse/app/templates/discovery.hbs | 1 + app/assets/javascripts/discourse/app/templates/tag/show.hbs | 1 + 2 files changed, 2 insertions(+) diff --git a/app/assets/javascripts/discourse/app/templates/discovery.hbs b/app/assets/javascripts/discourse/app/templates/discovery.hbs index d9768f12fa..b71dba0b36 100644 --- a/app/assets/javascripts/discourse/app/templates/discovery.hbs +++ b/app/assets/javascripts/discourse/app/templates/discovery.hbs @@ -29,6 +29,7 @@
+ {{plugin-outlet name="before-list-area"}}
{{plugin-outlet name="discovery-list-container-top" tagName="" connectorTagName="span" args=(hash category=category listLoading=loading)}} diff --git a/app/assets/javascripts/discourse/app/templates/tag/show.hbs b/app/assets/javascripts/discourse/app/templates/tag/show.hbs index 49dedfdf66..1d08c56a8c 100644 --- a/app/assets/javascripts/discourse/app/templates/tag/show.hbs +++ b/app/assets/javascripts/discourse/app/templates/tag/show.hbs @@ -48,6 +48,7 @@
+ {{plugin-outlet name="before-list-area"}}
{{#unless loading}} {{#discovery-topics-list From 222a7755a044ea454b8db2da6ec633e41caae74a Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Mon, 20 Jun 2022 10:06:15 +0800 Subject: [PATCH 050/184] DEV: Rely on route action to open composer. (#17118) Avoid duplicating composer logic in multiple places. --- .../app/components/sidebar/messages-section.js | 13 ------------- .../components/sidebar/messages-section.hbs | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js index 9c964bf4ab..44ba8e2310 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js @@ -1,9 +1,6 @@ -import { action } from "@ember/object"; import { cached } from "@glimmer/tracking"; import GlimmerComponent from "discourse/components/glimmer"; -import Composer from "discourse/models/composer"; -import { getOwner } from "discourse-common/lib/get-owner"; import GroupMessageSectionLink from "discourse/lib/sidebar/messages-section/group-message-section-link"; import PersonalMessageSectionLink from "discourse/lib/sidebar/messages-section/personal-message-section-link"; @@ -90,14 +87,4 @@ export default class SidebarMessagesSection extends GlimmerComponent { return links; } - - @action - composePersonalMessage() { - const composerArgs = { - action: Composer.PRIVATE_MESSAGE, - draftKey: Composer.NEW_TOPIC_KEY, - }; - - getOwner(this).lookup("controller:composer").open(composerArgs); - } } diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs index bed39761ef..f02235cd28 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs @@ -2,7 +2,7 @@ @sectionName="messages" @headerRoute="userPrivateMessages.index" @headerModel={{this.currentUser}} - @headerAction={{this.composePersonalMessage}} + @headerAction={{fn (route-action "composePrivateMessage") null null}} @headerActionIcon="plus" @headerLinkText={{i18n "sidebar.sections.messages.header_link_text"}} @headerLinkTitle={{i18n "sidebar.sections.messages.header_link_title"}} > From ef5b504e9ab97f9ad8ee157e91921972db3ec651 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Mon, 20 Jun 2022 10:07:06 +0800 Subject: [PATCH 051/184] DEV: Remove ember legacy env conditional in sidebar tests (#17140) --- .../sidebar-categories-section-test.js | 683 ++++----- .../sidebar-messages-section-test.js | 376 +++-- .../acceptance/sidebar-tags-section-test.js | 555 ++++--- .../tests/acceptance/sidebar-test.js | 60 +- .../acceptance/sidebar-topics-section-test.js | 1346 ++++++++--------- 5 files changed, 1397 insertions(+), 1623 deletions(-) diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js index d9d2b3281a..9269776a2e 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js @@ -1,17 +1,16 @@ import I18n from "I18n"; +import { test } from "qunit"; import { click, currentURL, settled, visit } from "@ember/test-helpers"; import { acceptance, - conditionalTest, exists, publishToMessageBus, query, queryAll, } from "discourse/tests/helpers/qunit-helpers"; import selectKit from "discourse/tests/helpers/select-kit-helper"; -import { isLegacyEmber } from "discourse-common/config/environment"; import Site from "discourse/models/site"; import { NotificationLevels } from "discourse/lib/notification-levels"; import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures"; @@ -27,38 +26,34 @@ acceptance( needs.user({ experimental_sidebar_enabled: true }); - conditionalTest( - "uncategorized category is not shown", - !isLegacyEmber(), - async function (assert) { - const categories = Site.current().categories; - const category1 = categories[0]; + test("uncategorized category is not shown", async function (assert) { + const categories = Site.current().categories; + const category1 = categories[0]; - const uncategorizedCategory = categories.find((category) => { - return category.id === Site.current().uncategorized_category_id; - }); + const uncategorizedCategory = categories.find((category) => { + return category.id === Site.current().uncategorized_category_id; + }); - category1.set("notification_level", NotificationLevels.TRACKING); + category1.set("notification_level", NotificationLevels.TRACKING); - uncategorizedCategory.set( - "notification_level", - NotificationLevels.TRACKING - ); + uncategorizedCategory.set( + "notification_level", + NotificationLevels.TRACKING + ); - await visit("/"); + await visit("/"); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link").length, - 1, - "there should only be one section link under the section" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link").length, + 1, + "there should only be one section link under the section" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}`), - `only the ${category1.slug} section link is shown` - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}`), + `only the ${category1.slug} section link is shown` + ); + }); } ); @@ -97,398 +92,358 @@ acceptance("Sidebar - Categories Section", function (needs) { return { category1, category2 }; }; - conditionalTest( - "clicking on section header link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-categories .sidebar-section-header-link"); + test("clicking on section header link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-categories .sidebar-section-header-link"); - assert.strictEqual( - currentURL(), - "/categories", - "it should transition to the categories page" - ); - } - ); + assert.strictEqual( + currentURL(), + "/categories", + "it should transition to the categories page" + ); + }); - conditionalTest( - "category section links when user does not have any tracked categories", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("category section links when user does not have any tracked categories", async function (assert) { + await visit("/"); - assert.strictEqual( - query(".sidebar-section-message").textContent.trim(), - I18n.t("sidebar.sections.categories.no_tracked_categories"), - "the no tracked categories message is displayed" - ); - } - ); + assert.strictEqual( + query(".sidebar-section-message").textContent.trim(), + I18n.t("sidebar.sections.categories.no_tracked_categories"), + "the no tracked categories message is displayed" + ); + }); - conditionalTest( - "uncategorized category is shown when tracked", - !isLegacyEmber(), - async function (assert) { - const categories = Site.current().categories; + test("uncategorized category is shown when tracked", async function (assert) { + const categories = Site.current().categories; - const uncategorizedCategory = categories.find((category) => { - return category.id === Site.current().uncategorized_category_id; - }); + const uncategorizedCategory = categories.find((category) => { + return category.id === Site.current().uncategorized_category_id; + }); - uncategorizedCategory.set( - "notification_level", - NotificationLevels.TRACKING - ); + uncategorizedCategory.set( + "notification_level", + NotificationLevels.TRACKING + ); - await visit("/"); + await visit("/"); - assert.ok( - exists(`.sidebar-section-link-${uncategorizedCategory.slug}`), - `displays the section link for ${uncategorizedCategory.slug} category` - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${uncategorizedCategory.slug}`), + `displays the section link for ${uncategorizedCategory.slug} category` + ); + }); - conditionalTest( - "category section links for tracked categories", - !isLegacyEmber(), - async function (assert) { - const { category1, category2 } = setupTrackedCategories(); + test("category section links for tracked categories", async function (assert) { + const { category1, category2 } = setupTrackedCategories(); - await visit("/"); + await visit("/"); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link").length, - 2, - "there should only be two section link under the section" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link").length, + 2, + "there should only be two section link under the section" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug} .badge-category`), - "category1 section link is rendered with category badge" - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug} .badge-category`), + "category1 section link is rendered with category badge" + ); - assert.strictEqual( - query(`.sidebar-section-link-${category1.slug}`).textContent.trim(), - category1.name, - "displays category1's name for the link text" - ); + assert.strictEqual( + query(`.sidebar-section-link-${category1.slug}`).textContent.trim(), + category1.name, + "displays category1's name for the link text" + ); - await click(`.sidebar-section-link-${category1.slug}`); + await click(`.sidebar-section-link-${category1.slug}`); - assert.strictEqual( - currentURL(), - `/c/${category1.slug}/${category1.id}/l/latest`, - "it should transition to the category1's discovery page" - ); + assert.strictEqual( + currentURL(), + `/c/${category1.slug}/${category1.id}/l/latest`, + "it should transition to the category1's discovery page" + ); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link.active") - .length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link.active") + .length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}.active`), - "the category1 section link is marked as active" - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}.active`), + "the category1 section link is marked as active" + ); - await click(`.sidebar-section-link-${category2.slug}`); + await click(`.sidebar-section-link-${category2.slug}`); - assert.strictEqual( - currentURL(), - `/c/${category2.slug}/${category2.id}/l/latest`, - "it should transition to the category2's discovery page" - ); + assert.strictEqual( + currentURL(), + `/c/${category2.slug}/${category2.id}/l/latest`, + "it should transition to the category2's discovery page" + ); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link.active") - .length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link.active") + .length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-${category2.slug}.active`), - "the category2 section link is marked as active" - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category2.slug}.active`), + "the category2 section link is marked as active" + ); + }); - conditionalTest( - "visiting category discovery new route for tracked categories", - !isLegacyEmber(), - async function (assert) { - const { category1 } = setupTrackedCategories(); + test("visiting category discovery new route for tracked categories", async function (assert) { + const { category1 } = setupTrackedCategories(); - await visit(`/c/${category1.slug}/${category1.id}/l/new`); + await visit(`/c/${category1.slug}/${category1.id}/l/new`); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link.active") - .length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link.active") + .length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}.active`), - "the category1 section link is marked as active for the new route" - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}.active`), + "the category1 section link is marked as active for the new route" + ); + }); - conditionalTest( - "visiting category discovery unread route for tracked categories", - !isLegacyEmber(), - async function (assert) { - const { category1 } = setupTrackedCategories(); + test("visiting category discovery unread route for tracked categories", async function (assert) { + const { category1 } = setupTrackedCategories(); - await visit(`/c/${category1.slug}/${category1.id}/l/unread`); + await visit(`/c/${category1.slug}/${category1.id}/l/unread`); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link.active") - .length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link.active") + .length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}.active`), - "the category1 section link is marked as active for the unread route" - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}.active`), + "the category1 section link is marked as active for the unread route" + ); + }); - conditionalTest( - "visiting category discovery top route for tracked categories", - !isLegacyEmber(), - async function (assert) { - const { category1 } = setupTrackedCategories(); + test("visiting category discovery top route for tracked categories", async function (assert) { + const { category1 } = setupTrackedCategories(); - await visit(`/c/${category1.slug}/${category1.id}/l/top`); + await visit(`/c/${category1.slug}/${category1.id}/l/top`); - assert.strictEqual( - queryAll(".sidebar-section-categories .sidebar-section-link.active") - .length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-categories .sidebar-section-link.active") + .length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}.active`), - "the category1 section link is marked as active for the top route" - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}.active`), + "the category1 section link is marked as active for the top route" + ); + }); - conditionalTest( - "updating category notification level", - !isLegacyEmber(), - async function (assert) { - const { category1, category2 } = setupTrackedCategories(); + test("updating category notification level", async function (assert) { + const { category1, category2 } = setupTrackedCategories(); - await visit(`/c/${category1.slug}/${category1.id}/l/top`); + await visit(`/c/${category1.slug}/${category1.id}/l/top`); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}`), - `has ${category1.name} section link in sidebar` - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}`), + `has ${category1.name} section link in sidebar` + ); - assert.ok( - exists(`.sidebar-section-link-${category2.slug}`), - `has ${category2.name} section link in sidebar` - ); + assert.ok( + exists(`.sidebar-section-link-${category2.slug}`), + `has ${category2.name} section link in sidebar` + ); - const notificationLevelsDropdown = selectKit(".notifications-button"); + const notificationLevelsDropdown = selectKit(".notifications-button"); - await notificationLevelsDropdown.expand(); + await notificationLevelsDropdown.expand(); - await notificationLevelsDropdown.selectRowByValue( - NotificationLevels.REGULAR - ); + await notificationLevelsDropdown.selectRowByValue( + NotificationLevels.REGULAR + ); - assert.ok( - !exists(`.sidebar-section-link-${category1.slug}`), - `does not have ${category1.name} section link in sidebar` - ); + assert.ok( + !exists(`.sidebar-section-link-${category1.slug}`), + `does not have ${category1.name} section link in sidebar` + ); - assert.ok( - exists(`.sidebar-section-link-${category2.slug}`), - `has ${category2.name} section link in sidebar` - ); + assert.ok( + exists(`.sidebar-section-link-${category2.slug}`), + `has ${category2.name} section link in sidebar` + ); - await notificationLevelsDropdown.expand(); + await notificationLevelsDropdown.expand(); - await notificationLevelsDropdown.selectRowByValue( - NotificationLevels.TRACKING - ); + await notificationLevelsDropdown.selectRowByValue( + NotificationLevels.TRACKING + ); - assert.ok( - exists(`.sidebar-section-link-${category1.slug}`), - `has ${category1.name} section link in sidebar` - ); + assert.ok( + exists(`.sidebar-section-link-${category1.slug}`), + `has ${category1.name} section link in sidebar` + ); - assert.ok( - exists(`.sidebar-section-link-${category2.slug}`), - `has ${category2.name} section link in sidebar` - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-${category2.slug}`), + `has ${category2.name} section link in sidebar` + ); + }); - conditionalTest( - "new and unread count for categories link", - !isLegacyEmber(), - async function (assert) { - const { category1, category2 } = setupTrackedCategories(); + test("new and unread count for categories link", async function (assert) { + const { category1, category2 } = setupTrackedCategories(); - this.container.lookup("topic-tracking-state:main").loadStates([ - { - topic_id: 1, - highest_post_number: 1, - last_read_post_number: null, - created_at: "2022-05-11T03:09:31.959Z", - category_id: category1.id, - notification_level: null, - created_in_new_period: true, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 2, - highest_post_number: 12, - last_read_post_number: 11, - created_at: "2020-02-09T09:40:02.672Z", - category_id: category1.id, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 3, - highest_post_number: 15, - last_read_post_number: 14, - created_at: "2021-06-14T12:41:02.477Z", - category_id: category2.id, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 4, - highest_post_number: 17, - last_read_post_number: 16, - created_at: "2020-10-31T03:41:42.257Z", - category_id: category2.id, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - ]); - - await visit("/"); - - assert.strictEqual( - query( - `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.unread_count", { count: 1 }), - `displays 1 unread count for ${category1.slug} section link` - ); - - assert.strictEqual( - query( - `.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.unread_count", { count: 2 }), - `displays 2 unread count for ${category2.slug} section link` - ); - - publishToMessageBus("/unread", { - topic_id: 2, - message_type: "read", - payload: { - last_read_post_number: 12, - highest_post_number: 12, - }, - }); - - await settled(); - - assert.strictEqual( - query( - `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.new_count", { count: 1 }), - `displays 1 new count for ${category1.slug} section link` - ); - - publishToMessageBus("/unread", { + this.container.lookup("topic-tracking-state:main").loadStates([ + { topic_id: 1, - message_type: "read", - payload: { - last_read_post_number: 1, - highest_post_number: 1, - }, - }); - - await settled(); - - assert.ok( - !exists( - `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` - ), - `does not display any badge ${category1.slug} section link` - ); - - publishToMessageBus("/unread", { + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category1.id, + notification_level: null, + created_in_new_period: true, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category1.id, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 3, - message_type: "read", - payload: { - last_read_post_number: 15, - highest_post_number: 15, - }, - }); + highest_post_number: 15, + last_read_post_number: 14, + created_at: "2021-06-14T12:41:02.477Z", + category_id: category2.id, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 4, + highest_post_number: 17, + last_read_post_number: 16, + created_at: "2020-10-31T03:41:42.257Z", + category_id: category2.id, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); - await settled(); + await visit("/"); - assert.strictEqual( - query( - `.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.unread_count", { count: 1 }), - `displays 1 unread count for ${category2.slug} section link` - ); - } - ); + assert.strictEqual( + query( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.unread_count", { count: 1 }), + `displays 1 unread count for ${category1.slug} section link` + ); - conditionalTest( - "clean up topic tracking state state changed callbacks when section is destroyed", - !isLegacyEmber(), - async function (assert) { - setupTrackedCategories(); + assert.strictEqual( + query( + `.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.unread_count", { count: 2 }), + `displays 2 unread count for ${category2.slug} section link` + ); - await visit("/"); + publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); - const topicTrackingState = this.container.lookup( - "topic-tracking-state:main" - ); + await settled(); - const initialCallbackCount = Object.keys( - topicTrackingState.stateChangeCallbacks - ).length; + assert.strictEqual( + query( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.new_count", { count: 1 }), + `displays 1 new count for ${category1.slug} section link` + ); - await click(".header-sidebar-toggle .btn"); - await click(".header-sidebar-toggle .btn"); + publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); - assert.strictEqual( - Object.keys(topicTrackingState.stateChangeCallbacks).length, - initialCallbackCount - ); - } - ); + await settled(); + + assert.ok( + !exists( + `.sidebar-section-link-${category1.slug} .sidebar-section-link-content-badge` + ), + `does not display any badge ${category1.slug} section link` + ); + + publishToMessageBus("/unread", { + topic_id: 3, + message_type: "read", + payload: { + last_read_post_number: 15, + highest_post_number: 15, + }, + }); + + await settled(); + + assert.strictEqual( + query( + `.sidebar-section-link-${category2.slug} .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.unread_count", { count: 1 }), + `displays 1 unread count for ${category2.slug} section link` + ); + }); + + test("clean up topic tracking state state changed callbacks when section is destroyed", async function (assert) { + setupTrackedCategories(); + + await visit("/"); + + const topicTrackingState = this.container.lookup( + "topic-tracking-state:main" + ); + + const initialCallbackCount = Object.keys( + topicTrackingState.stateChangeCallbacks + ).length; + + await click(".header-sidebar-toggle .btn"); + await click(".header-sidebar-toggle .btn"); + + assert.strictEqual( + Object.keys(topicTrackingState.stateChangeCallbacks).length, + initialCallbackCount + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js index 5eeda44c28..5865a4a45b 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js @@ -1,13 +1,13 @@ +import { test } from "qunit"; + import { click, currentURL, visit } from "@ember/test-helpers"; import { acceptance, - conditionalTest, exists, queryAll, updateCurrentUser, } from "discourse/tests/helpers/qunit-helpers"; -import { isLegacyEmber } from "discourse-common/config/environment"; acceptance( "Sidebar - Messages Section - enable_personal_messages disabled", @@ -20,18 +20,14 @@ acceptance( enable_personal_messages: false, }); - conditionalTest( - "clicking on section header button", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("clicking on section header button", async function (assert) { + await visit("/"); - assert.ok( - !exists(".sidebar-section-messages"), - "does not display messages section in sidebar" - ); - } - ); + assert.ok( + !exists(".sidebar-section-messages"), + "does not display messages section in sidebar" + ); + }); } ); @@ -69,59 +65,87 @@ acceptance( }); }); - conditionalTest( - "clicking on section header button", - !isLegacyEmber(), - async function (assert) { + test("clicking on section header button", async function (assert) { + await visit("/"); + + await click(".sidebar-section-messages .sidebar-section-header-button"); + + assert.ok( + exists("#reply-control.private-message"), + "it opens the composer" + ); + }); + + test("clicking on section header link", async function (assert) { + await visit("/"); + await click(".sidebar-section-messages .sidebar-section-header-link"); + + assert.strictEqual( + currentURL(), + `/u/eviltrout/messages`, + "it should transistion to the user's messages" + ); + }); + + test("personal messages section links", async function (assert) { + await visit("/"); + + assert.ok( + exists( + ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox" + ), + "displays the personal message inbox link" + ); + + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link").length, + 1, + "only displays the personal message inbox link" + ); + + await click( + ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox" + ); + + assert.ok( + exists( + ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox.active" + ), + "personal message inbox link is marked as active" + ); + + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link").length, + 5, + "expands and displays the links for personal messages" + ); + }); + + ["new", "archive", "sent", "unread"].forEach((type) => { + test(`${type} personal messages section link`, async function (assert) { await visit("/"); - await click(".sidebar-section-messages .sidebar-section-header-button"); - - assert.ok( - exists("#reply-control.private-message"), - "it opens the composer" - ); - } - ); - - conditionalTest( - "clicking on section header link", - !isLegacyEmber(), - async function (assert) { - await visit("/"); - await click(".sidebar-section-messages .sidebar-section-header-link"); - - assert.strictEqual( - currentURL(), - `/u/eviltrout/messages`, - "it should transistion to the user's messages" - ); - } - ); - - conditionalTest( - "personal messages section links", - !isLegacyEmber(), - async function (assert) { - await visit("/"); - - assert.ok( - exists( - ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox" - ), - "displays the personal message inbox link" - ); - - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link").length, - 1, - "only displays the personal message inbox link" - ); - await click( ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox" ); + await click( + `.sidebar-section-messages .sidebar-section-link-personal-messages-${type}` + ); + + assert.strictEqual( + currentURL(), + `/u/eviltrout/messages/${type}`, + `it should transition to user's ${type} personal messages` + ); + + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link.active") + .length, + 2, + "only two links are marked as active in the sidebar" + ); + assert.ok( exists( ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox.active" @@ -129,63 +153,85 @@ acceptance( "personal message inbox link is marked as active" ); - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link").length, - 5, - "expands and displays the links for personal messages" + assert.ok( + exists( + `.sidebar-section-messages .sidebar-section-link-personal-messages-${type}.active` + ), + `personal message ${type} link is marked as active` ); - } - ); + }); + }); - ["new", "archive", "sent", "unread"].forEach((type) => { - conditionalTest( - `${type} personal messages section link`, - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("group messages section links", async function (assert) { + updateCurrentUser({ + groups: [ + { + name: "group1", + has_messages: true, + }, + { + name: "group2", + has_messages: false, + }, + { + name: "group3", + has_messages: true, + }, + ], + }); - await click( - ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox" - ); + await visit("/"); - await click( - `.sidebar-section-messages .sidebar-section-link-personal-messages-${type}` - ); + assert.ok( + exists( + ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1" + ), + "displays group1 inbox link" + ); - assert.strictEqual( - currentURL(), - `/u/eviltrout/messages/${type}`, - `it should transition to user's ${type} personal messages` - ); + assert.ok( + exists( + ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group3" + ), + "displays group3 inbox link" + ); - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link.active") - .length, - 2, - "only two links are marked as active in the sidebar" - ); + await visit("/u/eviltrout/messages/group/GrOuP1"); - assert.ok( - exists( - ".sidebar-section-messages .sidebar-section-link-personal-messages-inbox.active" - ), - "personal message inbox link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link").length, + 6, + "expands and displays the links for group1 group messages" + ); - assert.ok( - exists( - `.sidebar-section-messages .sidebar-section-link-personal-messages-${type}.active` - ), - `personal message ${type} link is marked as active` - ); - } + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link.group1") + .length, + 4, + "expands the links for group1 group messages" + ); + + await click( + ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group3" + ); + + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link.group1") + .length, + 1, + "collapses the links for group1 group messages" + ); + + assert.strictEqual( + queryAll(".sidebar-section-messages .sidebar-section-link.group3") + .length, + 4, + "expands the links for group3 group messages" ); }); - conditionalTest( - "group messages section links", - !isLegacyEmber(), - async function (assert) { + ["new", "archive", "unread"].forEach((type) => { + test(`${type} group messages section link`, async function (assert) { updateCurrentUser({ groups: [ { @@ -205,115 +251,41 @@ acceptance( await visit("/"); - assert.ok( - exists( - ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1" - ), - "displays group1 inbox link" - ); - - assert.ok( - exists( - ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group3" - ), - "displays group3 inbox link" - ); - - await visit("/u/eviltrout/messages/group/GrOuP1"); - - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link").length, - 6, - "expands and displays the links for group1 group messages" - ); - - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link.group1") - .length, - 4, - "expands the links for group1 group messages" + await click( + `.sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1` ); await click( - ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group3" + `.sidebar-section-messages .sidebar-section-link-group-messages-${type}.group1` ); assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link.group1") - .length, - 1, - "collapses the links for group1 group messages" + currentURL(), + `/u/eviltrout/messages/group/group1/${type}`, + `it should transition to user's ${type} personal messages` ); assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link.group3") + queryAll(".sidebar-section-messages .sidebar-section-link.active") .length, - 4, - "expands the links for group3 group messages" + 2, + "only two links are marked as active in the sidebar" ); - } - ); - ["new", "archive", "unread"].forEach((type) => { - conditionalTest( - `${type} group messages section link`, - !isLegacyEmber(), - async function (assert) { - updateCurrentUser({ - groups: [ - { - name: "group1", - has_messages: true, - }, - { - name: "group2", - has_messages: false, - }, - { - name: "group3", - has_messages: true, - }, - ], - }); + assert.ok( + exists( + ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1.active" + ), + "group1 group message inbox link is marked as active" + ); - await visit("/"); - - await click( - `.sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1` - ); - - await click( - `.sidebar-section-messages .sidebar-section-link-group-messages-${type}.group1` - ); - - assert.strictEqual( - currentURL(), - `/u/eviltrout/messages/group/group1/${type}`, - `it should transition to user's ${type} personal messages` - ); - - assert.strictEqual( - queryAll(".sidebar-section-messages .sidebar-section-link.active") - .length, - 2, - "only two links are marked as active in the sidebar" - ); - - assert.ok( - exists( - ".sidebar-section-messages .sidebar-section-link-group-messages-inbox.group1.active" - ), - "group1 group message inbox link is marked as active" - ); - - assert.ok( - exists( - `.sidebar-section-messages .sidebar-section-link-group-messages-${type}.group1.active` - ), - `group1 group message ${type} link is marked as active` - ); - } - ); + assert.ok( + exists( + `.sidebar-section-messages .sidebar-section-link-group-messages-${type}.group1.active` + ), + `group1 group message ${type} link is marked as active` + ); + }); }); } ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js index cf220996fb..17aa7a7861 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js @@ -1,17 +1,16 @@ import I18n from "I18n"; +import { test } from "qunit"; import { click, currentURL, settled, visit } from "@ember/test-helpers"; import { acceptance, - conditionalTest, exists, publishToMessageBus, query, queryAll, updateCurrentUser, } from "discourse/tests/helpers/qunit-helpers"; -import { isLegacyEmber } from "discourse-common/config/environment"; import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures"; import { cloneJSON } from "discourse-common/lib/object"; import selectKit from "discourse/tests/helpers/select-kit-helper"; @@ -24,18 +23,14 @@ acceptance("Sidebar - Tags section - tagging disabled", function (needs) { needs.user({ experimental_sidebar_enabled: true }); - conditionalTest( - "tags section is not shown", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("tags section is not shown", async function (assert) { + await visit("/"); - assert.ok( - !exists(".sidebar-section-tags"), - "does not display the tags section" - ); - } - ); + assert.ok( + !exists(".sidebar-section-tags"), + "does not display the tags section" + ); + }); }); acceptance("Sidebar - Tags section", function (needs) { @@ -76,331 +71,291 @@ acceptance("Sidebar - Tags section", function (needs) { }); }); - conditionalTest( - "clicking on section header link", - !isLegacyEmber(), - async function (assert) { - await visit("/"); - await click(".sidebar-section-tags .sidebar-section-header-link"); + test("clicking on section header link", async function (assert) { + await visit("/"); + await click(".sidebar-section-tags .sidebar-section-header-link"); - assert.strictEqual( - currentURL(), - "/tags", - "it should transition to the tags page" - ); - } - ); + assert.strictEqual( + currentURL(), + "/tags", + "it should transition to the tags page" + ); + }); - conditionalTest( - "section content when user does not have any tracked tags", - !isLegacyEmber(), - async function (assert) { - updateCurrentUser({ - tracked_tags: [], - watched_tags: [], - watching_first_post_tags: [], - }); + test("section content when user does not have any tracked tags", async function (assert) { + updateCurrentUser({ + tracked_tags: [], + watched_tags: [], + watching_first_post_tags: [], + }); - await visit("/"); + await visit("/"); - assert.strictEqual( - query( - ".sidebar-section-tags .sidebar-section-message" - ).textContent.trim(), - I18n.t("sidebar.sections.tags.no_tracked_tags"), - "the no tracked tags message is displayed" - ); - } - ); + assert.strictEqual( + query( + ".sidebar-section-tags .sidebar-section-message" + ).textContent.trim(), + I18n.t("sidebar.sections.tags.no_tracked_tags"), + "the no tracked tags message is displayed" + ); + }); - conditionalTest( - "tag section links for tracked tags", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("tag section links for tracked tags", async function (assert) { + await visit("/"); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link").length, - 3, - "3 section links under the section" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link").length, + 3, + "3 section links under the section" + ); - assert.strictEqual( - query(".sidebar-section-link-tag1").textContent.trim(), - "tag1", - "displays the tag1 name for the link text" - ); + assert.strictEqual( + query(".sidebar-section-link-tag1").textContent.trim(), + "tag1", + "displays the tag1 name for the link text" + ); - assert.strictEqual( - query(".sidebar-section-link-tag2").textContent.trim(), - "tag2", - "displays the tag2 name for the link text" - ); + assert.strictEqual( + query(".sidebar-section-link-tag2").textContent.trim(), + "tag2", + "displays the tag2 name for the link text" + ); - assert.strictEqual( - query(".sidebar-section-link-tag3").textContent.trim(), - "tag3", - "displays the tag3 name for the link text" - ); + assert.strictEqual( + query(".sidebar-section-link-tag3").textContent.trim(), + "tag3", + "displays the tag3 name for the link text" + ); - await click(".sidebar-section-link-tag1"); + await click(".sidebar-section-link-tag1"); - assert.strictEqual( - currentURL(), - "/tag/tag1", - "it should transition to tag1's topics discovery page" - ); + assert.strictEqual( + currentURL(), + "/tag/tag1", + "it should transition to tag1's topics discovery page" + ); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-tag1.active`), - "the tag1 section link is marked as active" - ); + assert.ok( + exists(`.sidebar-section-link-tag1.active`), + "the tag1 section link is marked as active" + ); - await click(".sidebar-section-link-tag2"); + await click(".sidebar-section-link-tag2"); - assert.strictEqual( - currentURL(), - "/tag/tag2", - "it should transition to tag2's topics discovery page" - ); + assert.strictEqual( + currentURL(), + "/tag/tag2", + "it should transition to tag2's topics discovery page" + ); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(`.sidebar-section-link-tag2.active`), - "the tag2 section link is marked as active" - ); - } - ); + assert.ok( + exists(`.sidebar-section-link-tag2.active`), + "the tag2 section link is marked as active" + ); + }); - conditionalTest( - "visiting tag discovery top route for tracked tags", - !isLegacyEmber(), - async function (assert) { - await visit(`/tag/tag1/l/top`); + test("visiting tag discovery top route for tracked tags", async function (assert) { + await visit(`/tag/tag1/l/top`); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-link-tag1.active"), - "the tag1 section link is marked as active for the top route" - ); - } - ); + assert.ok( + exists(".sidebar-section-link-tag1.active"), + "the tag1 section link is marked as active for the top route" + ); + }); - conditionalTest( - "visiting tag discovery new route for tracked tags", - !isLegacyEmber(), - async function (assert) { - await visit(`/tag/tag1/l/new`); + test("visiting tag discovery new route for tracked tags", async function (assert) { + await visit(`/tag/tag1/l/new`); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-link-tag1.active"), - "the tag1 section link is marked as active for the new route" - ); - } - ); + assert.ok( + exists(".sidebar-section-link-tag1.active"), + "the tag1 section link is marked as active for the new route" + ); + }); - conditionalTest( - "visiting tag discovery unread route for tracked tags", - !isLegacyEmber(), - async function (assert) { - await visit(`/tag/tag1/l/unread`); + test("visiting tag discovery unread route for tracked tags", async function (assert) { + await visit(`/tag/tag1/l/unread`); - assert.strictEqual( - queryAll(".sidebar-section-tags .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-tags .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-link-tag1.active"), - "the tag1 section link is marked as active for the unread route" - ); - } - ); + assert.ok( + exists(".sidebar-section-link-tag1.active"), + "the tag1 section link is marked as active for the unread route" + ); + }); - conditionalTest( - "new and unread count for tag section links", - !isLegacyEmber(), - async function (assert) { - this.container.lookup("topic-tracking-state:main").loadStates([ - { - topic_id: 1, - highest_post_number: 1, - last_read_post_number: null, - created_at: "2022-05-11T03:09:31.959Z", - category_id: 1, - notification_level: null, - created_in_new_period: true, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - tags: ["tag1"], - }, - { - topic_id: 2, - highest_post_number: 12, - last_read_post_number: 11, - created_at: "2020-02-09T09:40:02.672Z", - category_id: 2, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - tags: ["tag1"], - }, - { - topic_id: 3, - highest_post_number: 15, - last_read_post_number: 14, - created_at: "2021-06-14T12:41:02.477Z", - category_id: 3, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - tags: ["tag2"], - }, - { - topic_id: 4, - highest_post_number: 17, - last_read_post_number: 16, - created_at: "2020-10-31T03:41:42.257Z", - category_id: 4, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - tags: ["tag4"], - }, - ]); - - await visit("/"); - - assert.strictEqual( - query( - `.sidebar-section-link-tag1 .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.unread_count", { count: 1 }), - `displays 1 unread count for tag1 section link` - ); - - assert.strictEqual( - query( - `.sidebar-section-link-tag2 .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.unread_count", { count: 1 }), - `displays 1 unread count for tag2 section link` - ); - - assert.ok( - !exists( - `.sidebar-section-link-tag3 .sidebar-section-link-content-badge` - ), - "does not display any badge for tag3 section link" - ); - - publishToMessageBus("/unread", { - topic_id: 2, - message_type: "read", - payload: { - last_read_post_number: 12, - highest_post_number: 12, - }, - }); - - await settled(); - - assert.strictEqual( - query( - `.sidebar-section-link-tag1 .sidebar-section-link-content-badge` - ).textContent.trim(), - I18n.t("sidebar.new_count", { count: 1 }), - `displays 1 new count for tag1 section link` - ); - - publishToMessageBus("/unread", { + test("new and unread count for tag section links", async function (assert) { + this.container.lookup("topic-tracking-state:main").loadStates([ + { topic_id: 1, - message_type: "read", - payload: { - last_read_post_number: 1, - highest_post_number: 1, - }, - }); + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + { + topic_id: 3, + highest_post_number: 15, + last_read_post_number: 14, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag2"], + }, + { + topic_id: 4, + highest_post_number: 17, + last_read_post_number: 16, + created_at: "2020-10-31T03:41:42.257Z", + category_id: 4, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag4"], + }, + ]); - await settled(); + await visit("/"); - assert.ok( - !exists( - `.sidebar-section-link-tag1 .sidebar-section-link-content-badge` - ), - `does not display any badge tag1 section link` - ); - } - ); + assert.strictEqual( + query( + `.sidebar-section-link-tag1 .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.unread_count", { count: 1 }), + `displays 1 unread count for tag1 section link` + ); - conditionalTest( - "cleans up topic tracking state state changed callbacks when section is destroyed", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + assert.strictEqual( + query( + `.sidebar-section-link-tag2 .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.unread_count", { count: 1 }), + `displays 1 unread count for tag2 section link` + ); - const topicTrackingState = this.container.lookup( - "topic-tracking-state:main" - ); + assert.ok( + !exists(`.sidebar-section-link-tag3 .sidebar-section-link-content-badge`), + "does not display any badge for tag3 section link" + ); - const initialCallbackCount = Object.keys( - topicTrackingState.stateChangeCallbacks - ).length; + publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); - await click(".header-sidebar-toggle .btn"); - await click(".header-sidebar-toggle .btn"); + await settled(); - assert.strictEqual( - Object.keys(topicTrackingState.stateChangeCallbacks).length, - initialCallbackCount - ); - } - ); + assert.strictEqual( + query( + `.sidebar-section-link-tag1 .sidebar-section-link-content-badge` + ).textContent.trim(), + I18n.t("sidebar.new_count", { count: 1 }), + `displays 1 new count for tag1 section link` + ); - conditionalTest( - "updating tags notification levels", - !isLegacyEmber(), - async function (assert) { - await visit(`/tag/tag1/l/unread`); + publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); - const notificationLevelsDropdown = selectKit(".notifications-button"); + await settled(); - await notificationLevelsDropdown.expand(); + assert.ok( + !exists(`.sidebar-section-link-tag1 .sidebar-section-link-content-badge`), + `does not display any badge tag1 section link` + ); + }); - await notificationLevelsDropdown.selectRowByValue( - NotificationLevels.REGULAR - ); + test("cleans up topic tracking state state changed callbacks when section is destroyed", async function (assert) { + await visit("/"); - assert.ok( - !exists(".sidebar-section-tags .sidebar-section-link-tag1"), - "tag1 section link is removed from sidebar" - ); - } - ); + const topicTrackingState = this.container.lookup( + "topic-tracking-state:main" + ); + + const initialCallbackCount = Object.keys( + topicTrackingState.stateChangeCallbacks + ).length; + + await click(".header-sidebar-toggle .btn"); + await click(".header-sidebar-toggle .btn"); + + assert.strictEqual( + Object.keys(topicTrackingState.stateChangeCallbacks).length, + initialCallbackCount + ); + }); + + test("updating tags notification levels", async function (assert) { + await visit(`/tag/tag1/l/unread`); + + const notificationLevelsDropdown = selectKit(".notifications-button"); + + await notificationLevelsDropdown.expand(); + + await notificationLevelsDropdown.selectRowByValue( + NotificationLevels.REGULAR + ); + + assert.ok( + !exists(".sidebar-section-tags .sidebar-section-link-tag1"), + "tag1 section link is removed from sidebar" + ); + }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js index c173b7e4d1..69424130d5 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js @@ -1,11 +1,7 @@ -import { click, visit } from "@ember/test-helpers"; -import { - acceptance, - conditionalTest, - exists, -} from "discourse/tests/helpers/qunit-helpers"; import { test } from "qunit"; -import { isLegacyEmber } from "discourse-common/config/environment"; + +import { click, visit } from "@ember/test-helpers"; +import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; acceptance("Sidebar - Anon User", function () { // Don't show sidebar for anon user until we know what we want to display @@ -20,46 +16,38 @@ acceptance("Sidebar - Anon User", function () { acceptance("Sidebar - User with sidebar disabled", function (needs) { needs.user({ experimental_sidebar_enabled: false }); - conditionalTest( - "sidebar is not displayed", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("sidebar is not displayed", async function (assert) { + await visit("/"); - assert.ok(!exists("#main-outlet-wrapper.has-sidebar")); - assert.ok(!exists(".sidebar-wrapper")); - } - ); + assert.ok(!exists("#main-outlet-wrapper.has-sidebar")); + assert.ok(!exists(".sidebar-wrapper")); + }); }); acceptance("Sidebar - User with sidebar enabled", function (needs) { needs.user({ experimental_sidebar_enabled: true }); - conditionalTest( - "hiding and displaying sidebar", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("hiding and displaying sidebar", async function (assert) { + await visit("/"); - assert.ok( - exists("#main-outlet-wrapper.has-sidebar"), - "adds sidebar utility class on main outlet wrapper" - ); + assert.ok( + exists("#main-outlet-wrapper.has-sidebar"), + "adds sidebar utility class on main outlet wrapper" + ); - assert.ok(exists(".sidebar-wrapper"), "displays the sidebar by default"); + assert.ok(exists(".sidebar-wrapper"), "displays the sidebar by default"); - await click(".header-sidebar-toggle .btn"); + await click(".header-sidebar-toggle .btn"); - assert.ok( - !exists("#main-outlet-wrapper.has-sidebar"), - "removes sidebar utility class from main outlet wrapper" - ); + assert.ok( + !exists("#main-outlet-wrapper.has-sidebar"), + "removes sidebar utility class from main outlet wrapper" + ); - assert.ok(!exists(".sidebar-wrapper"), "hides the sidebar"); + assert.ok(!exists(".sidebar-wrapper"), "hides the sidebar"); - await click(".header-sidebar-toggle .btn"); + await click(".header-sidebar-toggle .btn"); - assert.ok(exists(".sidebar-wrapper"), "displays the sidebar"); - } - ); + assert.ok(exists(".sidebar-wrapper"), "displays the sidebar"); + }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js index 898faa49bc..5f04e100d6 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-topics-section-test.js @@ -1,15 +1,15 @@ +import { test } from "qunit"; + import { click, currentURL, settled, visit } from "@ember/test-helpers"; import { acceptance, - conditionalTest, exists, loggedInUser, publishToMessageBus, query, queryAll, } from "discourse/tests/helpers/qunit-helpers"; -import { isLegacyEmber } from "discourse-common/config/environment"; import topicFixtures from "discourse/tests/fixtures/discovery-fixtures"; import { cloneJSON } from "discourse-common/lib/object"; import { withPluginApi } from "discourse/lib/plugin-api"; @@ -38,799 +38,703 @@ acceptance("Sidebar - Topics Section", function (needs) { }); }); - conditionalTest( - "clicking on section header button", - !isLegacyEmber(), - async function (assert) { - await visit("/"); - await click(".sidebar-section-topics .sidebar-section-header-button"); + test("clicking on section header button", async function (assert) { + await visit("/"); + await click(".sidebar-section-topics .sidebar-section-header-button"); - assert.ok(exists("#reply-control"), "it opens the composer"); - } - ); + assert.ok(exists("#reply-control"), "it opens the composer"); + }); - conditionalTest( - "clicking on section header button while viewing a category", - !isLegacyEmber(), - async function (assert) { - await visit("/c/bug"); - await click(".sidebar-section-topics .sidebar-section-header-button"); + test("clicking on section header button while viewing a category", async function (assert) { + await visit("/c/bug"); + await click(".sidebar-section-topics .sidebar-section-header-button"); - assert.ok(exists("#reply-control"), "it opens the composer"); + assert.ok(exists("#reply-control"), "it opens the composer"); - assert.strictEqual( - query(".category-input .selected-name .category-name").textContent, - "bug", - "the current category is prefilled in the composer input" - ); - } - ); + assert.strictEqual( + query(".category-input .selected-name .category-name").textContent, + "bug", + "the current category is prefilled in the composer input" + ); + }); - conditionalTest( - "clicking on section caret button", - !isLegacyEmber(), - async function (assert) { - await visit("/"); + test("clicking on section caret button", async function (assert) { + await visit("/"); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-content"), - "shows content section" - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-content"), + "shows content section" + ); - await click(".sidebar-section-topics .sidebar-section-header-caret"); + await click(".sidebar-section-topics .sidebar-section-header-caret"); - assert.ok( - !exists(".sidebar-section-topics .sidebar-section-content"), - "hides content section" - ); + assert.ok( + !exists(".sidebar-section-topics .sidebar-section-content"), + "hides content section" + ); - await click(".sidebar-section-topics .sidebar-section-header-caret"); + await click(".sidebar-section-topics .sidebar-section-header-caret"); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-content"), - "shows content section" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-content"), + "shows content section" + ); + }); - conditionalTest( - "clicking on section header link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-topics .sidebar-section-header-link"); + test("clicking on section header link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-topics .sidebar-section-header-link"); - assert.strictEqual( - currentURL(), - "/latest", - "it should transistion to the homepage" - ); + assert.strictEqual( + currentURL(), + "/latest", + "it should transistion to the homepage" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-everything.active" - ), - "the everything link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-everything.active"), + "the everything link is marked as active" + ); + }); - conditionalTest( - "clicking on everything link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-topics .sidebar-section-link-everything"); + test("clicking on everything link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-topics .sidebar-section-link-everything"); - assert.strictEqual( - currentURL(), - "/latest", - "it should transistion to the latest page" - ); + assert.strictEqual( + currentURL(), + "/latest", + "it should transistion to the latest page" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-everything.active" - ), - "the everything link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-everything.active"), + "the everything link is marked as active" + ); + }); - conditionalTest( - "clicking on tracked link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-topics .sidebar-section-link-tracked"); + test("clicking on tracked link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-topics .sidebar-section-link-tracked"); - assert.strictEqual( - currentURL(), - "/latest?f=tracked", - "it should transistion to the tracked url" - ); + assert.strictEqual( + currentURL(), + "/latest?f=tracked", + "it should transistion to the tracked url" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), - "the tracked link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), + "the tracked link is marked as active" + ); + }); - conditionalTest( - "clicking on bookmarked link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-topics .sidebar-section-link-bookmarked"); + test("clicking on bookmarked link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-topics .sidebar-section-link-bookmarked"); - assert.strictEqual( - currentURL(), - `/u/${loggedInUser().username}/activity/bookmarks`, - "it should transistion to the bookmarked url" - ); + assert.strictEqual( + currentURL(), + `/u/${loggedInUser().username}/activity/bookmarks`, + "it should transistion to the bookmarked url" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-bookmarked.active" - ), - "the bookmarked link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-bookmarked.active"), + "the bookmarked link is marked as active" + ); + }); - conditionalTest( - "clicking on my posts link", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); - await click(".sidebar-section-topics .sidebar-section-link-my-posts"); + test("clicking on my posts link", async function (assert) { + await visit("/t/280"); + await click(".sidebar-section-topics .sidebar-section-link-my-posts"); - assert.strictEqual( - currentURL(), - `/u/${loggedInUser().username}/activity`, - "it should transistion to the user's activity url" - ); + assert.strictEqual( + currentURL(), + `/u/${loggedInUser().username}/activity`, + "it should transistion to the user's activity url" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), - "the my posts link is marked as active" - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), + "the my posts link is marked as active" + ); - await visit(`/u/${loggedInUser().username}/activity/drafts`); + await visit(`/u/${loggedInUser().username}/activity/drafts`); - assert.notOk( - exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), - "the my posts link is not marked as active when user has no drafts and visiting the user activity drafts URL" - ); - } - ); + assert.notOk( + exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), + "the my posts link is not marked as active when user has no drafts and visiting the user activity drafts URL" + ); + }); - conditionalTest( - "clicking on my posts link when user has a draft", - !isLegacyEmber(), - async function (assert) { - await visit("/t/280"); + test("clicking on my posts link when user has a draft", async function (assert) { + await visit("/t/280"); - publishToMessageBus(`/user-drafts/${loggedInUser().id}`, { - draft_count: 1, - }); + publishToMessageBus(`/user-drafts/${loggedInUser().id}`, { + draft_count: 1, + }); - await settled(); + await settled(); - await click(".sidebar-section-topics .sidebar-section-link-my-posts"); + await click(".sidebar-section-topics .sidebar-section-link-my-posts"); - assert.strictEqual( - currentURL(), - `/u/${loggedInUser().username}/activity/drafts`, - "it transistions to the user's activity drafts url" - ); + assert.strictEqual( + currentURL(), + `/u/${loggedInUser().username}/activity/drafts`, + "it transistions to the user's activity drafts url" + ); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), - "the my posts link is marked as active" - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), + "the my posts link is marked as active" + ); - await visit(`/u/${loggedInUser().username}/activity`); + await visit(`/u/${loggedInUser().username}/activity`); - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), - "the my posts link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-my-posts.active"), + "the my posts link is marked as active" + ); + }); - conditionalTest( - "visiting top route", - !isLegacyEmber(), - async function (assert) { - await visit("/top"); + test("visiting top route", async function (assert) { + await visit("/top"); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-everything.active" - ), - "the everything link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-everything.active"), + "the everything link is marked as active" + ); + }); - conditionalTest( - "visiting unread route", - !isLegacyEmber(), - async function (assert) { - await visit("/unread"); + test("visiting unread route", async function (assert) { + await visit("/unread"); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-everything.active" - ), - "the everything link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-everything.active"), + "the everything link is marked as active" + ); + }); - conditionalTest( - "visiting new route", - !isLegacyEmber(), - async function (assert) { - await visit("/new"); + test("visiting new route", async function (assert) { + await visit("/new"); - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); - assert.ok( - exists( - ".sidebar-section-topics .sidebar-section-link-everything.active" - ), - "the everything link is marked as active" - ); - } - ); + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-everything.active"), + "the everything link is marked as active" + ); + }); - conditionalTest( - "new and unread count for everything link", - !isLegacyEmber(), - async function (assert) { - this.container.lookup("topic-tracking-state:main").loadStates([ - { - topic_id: 1, - highest_post_number: 1, - last_read_post_number: null, - created_at: "2022-05-11T03:09:31.959Z", - category_id: 1, - notification_level: null, - created_in_new_period: true, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 2, - highest_post_number: 12, - last_read_post_number: 11, - created_at: "2020-02-09T09:40:02.672Z", - category_id: 2, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 3, - highest_post_number: 15, - last_read_post_number: 14, - created_at: "2021-06-14T12:41:02.477Z", - category_id: 3, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 4, - highest_post_number: 17, - last_read_post_number: 16, - created_at: "2020-10-31T03:41:42.257Z", - category_id: 4, - notification_level: 2, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - ]); - - await visit("/"); - - assert.strictEqual( - query( - ".sidebar-section-link-everything .sidebar-section-link-content-badge" - ).textContent.trim(), - "3 unread", - "it displays the right unread count" - ); - - assert.ok( - query(".sidebar-section-link-everything").href.endsWith("/unread"), - "it links to unread filter" - ); - - // simulate reading topic 2 - publishToMessageBus("/unread", { + test("new and unread count for everything link", async function (assert) { + this.container.lookup("topic-tracking-state:main").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 2, - message_type: "read", - payload: { - last_read_post_number: 12, - highest_post_number: 12, - notification_level: 2, - }, - }); - - await settled(); - - assert.strictEqual( - query( - ".sidebar-section-link-everything .sidebar-section-link-content-badge" - ).textContent.trim(), - "2 unread", - "it updates the unread count" - ); - - // simulate reading topic 3 - publishToMessageBus("/unread", { + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 3, - message_type: "read", - payload: { - last_read_post_number: 15, - highest_post_number: 15, - notification_level: 2, - }, - }); - - // simulate reading topic 4 - publishToMessageBus("/unread", { + highest_post_number: 15, + last_read_post_number: 14, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 4, - message_type: "read", - payload: { - last_read_post_number: 17, - highest_post_number: 17, - notification_level: 2, - }, - }); + highest_post_number: 17, + last_read_post_number: 16, + created_at: "2020-10-31T03:41:42.257Z", + category_id: 4, + notification_level: 2, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); - await settled(); + await visit("/"); - assert.strictEqual( - query( - ".sidebar-section-link-everything .sidebar-section-link-content-badge" - ).textContent.trim(), - "1 new", - "it displays the new count once there are no unread topics" - ); + assert.strictEqual( + query( + ".sidebar-section-link-everything .sidebar-section-link-content-badge" + ).textContent.trim(), + "3 unread", + "it displays the right unread count" + ); - assert.ok( - query(".sidebar-section-link-everything").href.endsWith("/new"), - "it links to new filter" - ); + assert.ok( + query(".sidebar-section-link-everything").href.endsWith("/unread"), + "it links to unread filter" + ); - publishToMessageBus("/unread", { + // simulate reading topic 2 + publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + notification_level: 2, + }, + }); + + await settled(); + + assert.strictEqual( + query( + ".sidebar-section-link-everything .sidebar-section-link-content-badge" + ).textContent.trim(), + "2 unread", + "it updates the unread count" + ); + + // simulate reading topic 3 + publishToMessageBus("/unread", { + topic_id: 3, + message_type: "read", + payload: { + last_read_post_number: 15, + highest_post_number: 15, + notification_level: 2, + }, + }); + + // simulate reading topic 4 + publishToMessageBus("/unread", { + topic_id: 4, + message_type: "read", + payload: { + last_read_post_number: 17, + highest_post_number: 17, + notification_level: 2, + }, + }); + + await settled(); + + assert.strictEqual( + query( + ".sidebar-section-link-everything .sidebar-section-link-content-badge" + ).textContent.trim(), + "1 new", + "it displays the new count once there are no unread topics" + ); + + assert.ok( + query(".sidebar-section-link-everything").href.endsWith("/new"), + "it links to new filter" + ); + + publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + notification_level: 2, + }, + }); + + await settled(); + + assert.ok( + !exists( + ".sidebar-section-link-everything .sidebar-section-link-content-badge" + ), + "it removes new count once there are no new topics" + ); + + assert.ok( + query(".sidebar-section-link-everything").href.endsWith("/latest"), + "it links to latest filter" + ); + }); + + test("visiting top route with tracked filter", async function (assert) { + await visit("/top?f=tracked"); + + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); + + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), + "the tracked link is marked as active" + ); + }); + + test("visiting unread route with tracked filter", async function (assert) { + await visit("/unread?f=tracked"); + + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); + + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), + "the tracked link is marked as active" + ); + }); + + test("visiting new route with tracked filter", async function (assert) { + await visit("/new?f=tracked"); + + assert.strictEqual( + queryAll(".sidebar-section-topics .sidebar-section-link.active").length, + 1, + "only one link is marked as active" + ); + + assert.ok( + exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), + "the tracked link is marked as active" + ); + }); + + test("new and unread count for tracked link", async function (assert) { + const categories = Site.current().categories; + + // Category id 1001 has two subcategories + const category = categories.find((c) => c.id === 1001); + category.set("notification_level", NotificationLevels.TRACKING); + + this.container.lookup("topic-tracking-state:main").loadStates([ + { topic_id: 1, - message_type: "read", - payload: { - last_read_post_number: 1, - highest_post_number: 1, - notification_level: 2, - }, - }); - - await settled(); - - assert.ok( - !exists( - ".sidebar-section-link-everything .sidebar-section-link-content-badge" - ), - "it removes new count once there are no new topics" - ); - - assert.ok( - query(".sidebar-section-link-everything").href.endsWith("/latest"), - "it links to latest filter" - ); - } - ); - - conditionalTest( - "visiting top route with tracked filter", - !isLegacyEmber(), - async function (assert) { - await visit("/top?f=tracked"); - - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); - - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), - "the tracked link is marked as active" - ); - } - ); - - conditionalTest( - "visiting unread route with tracked filter", - !isLegacyEmber(), - async function (assert) { - await visit("/unread?f=tracked"); - - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); - - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), - "the tracked link is marked as active" - ); - } - ); - - conditionalTest( - "visiting new route with tracked filter", - !isLegacyEmber(), - async function (assert) { - await visit("/new?f=tracked"); - - assert.strictEqual( - queryAll(".sidebar-section-topics .sidebar-section-link.active").length, - 1, - "only one link is marked as active" - ); - - assert.ok( - exists(".sidebar-section-topics .sidebar-section-link-tracked.active"), - "the tracked link is marked as active" - ); - } - ); - - conditionalTest( - "new and unread count for tracked link", - !isLegacyEmber(), - async function (assert) { - const categories = Site.current().categories; - - // Category id 1001 has two subcategories - const category = categories.find((c) => c.id === 1001); - category.set("notification_level", NotificationLevels.TRACKING); - - this.container.lookup("topic-tracking-state:main").loadStates([ - { - topic_id: 1, - highest_post_number: 1, - last_read_post_number: null, - created_at: "2022-05-11T03:09:31.959Z", - category_id: category.id, - notification_level: NotificationLevels.TRACKING, - created_in_new_period: true, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 2, - highest_post_number: 12, - last_read_post_number: 11, - created_at: "2020-02-09T09:40:02.672Z", - category_id: category.subcategories[0].id, - notification_level: NotificationLevels.TRACKING, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 3, - highest_post_number: 12, - last_read_post_number: 11, - created_at: "2020-02-09T09:40:02.672Z", - category_id: category.subcategories[0].subcategories[0].id, - notification_level: NotificationLevels.TRACKING, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 4, - highest_post_number: 15, - last_read_post_number: 14, - created_at: "2021-06-14T12:41:02.477Z", - category_id: 3, - notification_level: NotificationLevels.TRACKING, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 5, - highest_post_number: 1, - last_read_post_number: null, - created_at: "2021-06-14T12:41:02.477Z", - category_id: 3, - notification_level: null, - created_in_new_period: true, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - }, - { - topic_id: 6, - highest_post_number: 17, - last_read_post_number: 16, - created_at: "2020-10-31T03:41:42.257Z", - category_id: 1234, - notification_level: NotificationLevels.TRACKING, - created_in_new_period: false, - unread_not_too_old: true, - treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", - tags: ["tag3"], - }, - ]); - - await visit("/"); - - assert.strictEqual( - query( - ".sidebar-section-link-tracked .sidebar-section-link-content-badge" - ).textContent.trim(), - "3 unread", - "it displays the right unread count" - ); - - assert.ok( - query(".sidebar-section-link-tracked").href.endsWith( - "/unread?f=tracked" - ), - "it links to unread url with tracked filter" - ); - - // simulate reading topic id 2 - publishToMessageBus("/unread", { + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category.id, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: true, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 2, - message_type: "read", - payload: { - last_read_post_number: 12, - highest_post_number: 12, - }, - }); - - await settled(); - - assert.strictEqual( - query( - ".sidebar-section-link-tracked .sidebar-section-link-content-badge" - ).textContent.trim(), - "2 unread", - "it updates the unread count" - ); - - // simulate reading topic id 3 - publishToMessageBus("/unread", { + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category.subcategories[0].id, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 3, - message_type: "read", - payload: { - last_read_post_number: 17, - highest_post_number: 17, - }, - }); - - // simulate reading topic id 6 - publishToMessageBus("/unread", { + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category.subcategories[0].subcategories[0].id, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 4, + highest_post_number: 15, + last_read_post_number: 14, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 5, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: null, + created_in_new_period: true, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { topic_id: 6, - message_type: "read", - payload: { - last_read_post_number: 17, - highest_post_number: 17, - }, + highest_post_number: 17, + last_read_post_number: 16, + created_at: "2020-10-31T03:41:42.257Z", + category_id: 1234, + notification_level: NotificationLevels.TRACKING, + created_in_new_period: false, + unread_not_too_old: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag3"], + }, + ]); + + await visit("/"); + + assert.strictEqual( + query( + ".sidebar-section-link-tracked .sidebar-section-link-content-badge" + ).textContent.trim(), + "3 unread", + "it displays the right unread count" + ); + + assert.ok( + query(".sidebar-section-link-tracked").href.endsWith("/unread?f=tracked"), + "it links to unread url with tracked filter" + ); + + // simulate reading topic id 2 + publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); + + await settled(); + + assert.strictEqual( + query( + ".sidebar-section-link-tracked .sidebar-section-link-content-badge" + ).textContent.trim(), + "2 unread", + "it updates the unread count" + ); + + // simulate reading topic id 3 + publishToMessageBus("/unread", { + topic_id: 3, + message_type: "read", + payload: { + last_read_post_number: 17, + highest_post_number: 17, + }, + }); + + // simulate reading topic id 6 + publishToMessageBus("/unread", { + topic_id: 6, + message_type: "read", + payload: { + last_read_post_number: 17, + highest_post_number: 17, + }, + }); + + assert.strictEqual( + query( + ".sidebar-section-link-tracked .sidebar-section-link-content-badge" + ).textContent.trim(), + "1 new", + "it displays the new count once there are no tracked unread topics" + ); + + assert.ok( + query(".sidebar-section-link-tracked").href.endsWith("/new?f=tracked"), + "it links to new url with tracked filter" + ); + + // simulate reading topic id 1 + publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 1, + highest_post_number: 1, + }, + }); + + await settled(); + + assert.ok( + !exists( + ".sidebar-section-link-tracked .sidebar-section-link-content-badge" + ), + "it removes new count once there are no tracked new topics" + ); + + assert.ok( + query(".sidebar-section-link-tracked").href.endsWith("/latest?f=tracked"), + "it links to latest url with tracked filter" + ); + }); + + test("adding section link via plugin API with Object", async function (assert) { + withPluginApi("1.2.0", (api) => { + api.addTopicsSectionLink({ + name: "unread", + route: "discovery.unread", + text: "unread topics", + title: "List of unread topics", }); + }); - assert.strictEqual( - query( - ".sidebar-section-link-tracked .sidebar-section-link-content-badge" - ).textContent.trim(), - "1 new", - "it displays the new count once there are no tracked unread topics" - ); + await visit("/"); - assert.ok( - query(".sidebar-section-link-tracked").href.endsWith("/new?f=tracked"), - "it links to new url with tracked filter" - ); + assert.strictEqual( + query(".sidebar-section-link-unread").textContent.trim(), + "unread topics", + "displays the right text for the link" + ); - // simulate reading topic id 1 - publishToMessageBus("/unread", { - topic_id: 1, - message_type: "read", - payload: { - last_read_post_number: 1, - highest_post_number: 1, - }, + assert.strictEqual( + query(".sidebar-section-link-unread").title, + "List of unread topics", + "displays the right title for the link" + ); + + await click(".sidebar-section-link-unread"); + + assert.strictEqual(currentURL(), "/unread", "links to the right URL"); + }); + + test("adding section link via plugin API with callback function", async function (assert) { + withPluginApi("1.2.0", (api) => { + api.addTopicsSectionLink((baseSectionLink) => { + return class CustomSectionLink extends baseSectionLink { + get name() { + return "user-summary"; + } + + get route() { + return "user.summary"; + } + + get model() { + return this.currentUser; + } + + get title() { + return `${this.currentUser.username} summary`; + } + + get text() { + return "my summary"; + } + }; }); + }); - await settled(); + await visit("/"); + await click(".sidebar-section-link-user-summary"); - assert.ok( - !exists( - ".sidebar-section-link-tracked .sidebar-section-link-content-badge" - ), - "it removes new count once there are no tracked new topics" - ); + assert.strictEqual( + currentURL(), + "/u/eviltrout/summary", + "links to the right URL" + ); - assert.ok( - query(".sidebar-section-link-tracked").href.endsWith( - "/latest?f=tracked" - ), - "it links to latest url with tracked filter" - ); - } - ); + assert.strictEqual( + query(".sidebar-section-link-user-summary").textContent.trim(), + "my summary", + "displays the right text for the link" + ); - conditionalTest( - "adding section link via plugin API with Object", - !isLegacyEmber(), - async function (assert) { - withPluginApi("1.2.0", (api) => { - api.addTopicsSectionLink({ - name: "unread", - route: "discovery.unread", - text: "unread topics", - title: "List of unread topics", - }); - }); + assert.strictEqual( + query(".sidebar-section-link-user-summary").title, + "eviltrout summary", + "displays the right title for the link" + ); + }); - await visit("/"); + test("clean up topic tracking state state changed callbacks when section is destroyed", async function (assert) { + await visit("/"); - assert.strictEqual( - query(".sidebar-section-link-unread").textContent.trim(), - "unread topics", - "displays the right text for the link" - ); + const topicTrackingState = this.container.lookup( + "topic-tracking-state:main" + ); - assert.strictEqual( - query(".sidebar-section-link-unread").title, - "List of unread topics", - "displays the right title for the link" - ); + const initialCallbackCount = Object.keys( + topicTrackingState.stateChangeCallbacks + ).length; - await click(".sidebar-section-link-unread"); + await click(".header-sidebar-toggle .btn"); + await click(".header-sidebar-toggle .btn"); - assert.strictEqual(currentURL(), "/unread", "links to the right URL"); - } - ); - - conditionalTest( - "adding section link via plugin API with callback function", - !isLegacyEmber(), - async function (assert) { - withPluginApi("1.2.0", (api) => { - api.addTopicsSectionLink((baseSectionLink) => { - return class CustomSectionLink extends baseSectionLink { - get name() { - return "user-summary"; - } - - get route() { - return "user.summary"; - } - - get model() { - return this.currentUser; - } - - get title() { - return `${this.currentUser.username} summary`; - } - - get text() { - return "my summary"; - } - }; - }); - }); - - await visit("/"); - await click(".sidebar-section-link-user-summary"); - - assert.strictEqual( - currentURL(), - "/u/eviltrout/summary", - "links to the right URL" - ); - - assert.strictEqual( - query(".sidebar-section-link-user-summary").textContent.trim(), - "my summary", - "displays the right text for the link" - ); - - assert.strictEqual( - query(".sidebar-section-link-user-summary").title, - "eviltrout summary", - "displays the right title for the link" - ); - } - ); - - conditionalTest( - "clean up topic tracking state state changed callbacks when section is destroyed", - !isLegacyEmber(), - async function (assert) { - await visit("/"); - - const topicTrackingState = this.container.lookup( - "topic-tracking-state:main" - ); - - const initialCallbackCount = Object.keys( - topicTrackingState.stateChangeCallbacks - ).length; - - await click(".header-sidebar-toggle .btn"); - await click(".header-sidebar-toggle .btn"); - - assert.strictEqual( - Object.keys(topicTrackingState.stateChangeCallbacks).length, - initialCallbackCount - ); - } - ); + assert.strictEqual( + Object.keys(topicTrackingState.stateChangeCallbacks).length, + initialCallbackCount + ); + }); }); From 2f66eb59c20a4797c9975100b4d72c668c38b424 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:37:28 +0800 Subject: [PATCH 052/184] DEV: Slightly defer loading Discourse stylesheets (#17078) This is related to #17063 and is also a pre-request for the splash screen work. This PR introduces 0 visual or functional changes. It just relocates the stylesheets in the load order. `.css` stylesheets block the browser render. We need to move those out of the tag. However, they still need to be loaded before core/plugin/theme rendered HTML to avoid FOUC. --- .../javascripts/discourse/app/index.html | 7 +++ .../discourse/lib/bootstrap-json/index.js | 43 ++++++++++--------- app/views/layouts/application.html.erb | 8 +++- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html index 0dde7cb0ab..4a5295d14e 100644 --- a/app/assets/javascripts/discourse/app/index.html +++ b/app/assets/javascripts/discourse/app/index.html @@ -20,6 +20,13 @@ {{content-for "head"}} + + + + {{content-for "discourse-stylesheets"}} + + + {{content-for "body"}} diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index 46c891ecc6..98fc1ed882 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -64,26 +64,6 @@ function head(buffer, bootstrap, headers, baseURL) { }); buffer.push(``); - (bootstrap.stylesheets || []).forEach((s) => { - let attrs = []; - if (s.media) { - attrs.push(`media="${s.media}"`); - } - if (s.target) { - attrs.push(`data-target="${s.target}"`); - } - if (s.theme_id) { - attrs.push(`data-theme-id="${s.theme_id}"`); - } - if (s.class) { - attrs.push(`class="${s.class}"`); - } - let link = ``; - buffer.push(link); - }); - if (bootstrap.preloaded.currentUser) { const user = JSON.parse(bootstrap.preloaded.currentUser); let { admin, staff } = user; @@ -119,6 +99,28 @@ function beforeScriptLoad(buffer, bootstrap) { ); } +function discourseStylesheets(buffer, bootstrap) { + (bootstrap.stylesheets || []).forEach((s) => { + let attrs = []; + if (s.media) { + attrs.push(`media="${s.media}"`); + } + if (s.target) { + attrs.push(`data-target="${s.target}"`); + } + if (s.theme_id) { + attrs.push(`data-theme-id="${s.theme_id}"`); + } + if (s.class) { + attrs.push(`class="${s.class}"`); + } + let link = ``; + buffer.push(link); + }); +} + function body(buffer, bootstrap) { buffer.push(bootstrap.theme_html.header); buffer.push(bootstrap.html.header); @@ -162,6 +164,7 @@ const BUILDERS = { "before-script-load": beforeScriptLoad, head, body, + "discourse-stylesheets": discourseStylesheets, "hidden-login-form": hiddenLoginForm, preloaded, "body-footer": bodyFooter, diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0ab67a126c..3c6f20cbad 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -9,8 +9,6 @@ <%= render partial: "layouts/head" %> <%= discourse_csrf_tags %> - <%= render partial: "common/discourse_stylesheet" %> - <%- if SiteSetting.enable_escaped_fragments? %> <%- end %> @@ -73,6 +71,12 @@ + + + <%= render partial: "common/discourse_stylesheet" %> + + + <%- if allow_plugins? %> <%= build_plugin_html 'server:after-body-open' %> <%- end -%> From 986060a850a61cc0666a6d265f8fa0fc094abb03 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Mon, 20 Jun 2022 11:43:01 +0800 Subject: [PATCH 053/184] FEATURE: Expand messages filter links when viewing private messages. (#17106) --- .../components/sidebar/messages-section.js | 56 +++++++++++---- .../group-message-section-link.js | 34 +++------- .../messages-section/message-section-link.js | 46 +++++++++++++ .../personal-message-section-link.js | 32 +++------ .../javascripts/discourse/app/models/topic.js | 1 + .../components/sidebar/messages-section.hbs | 1 + .../sidebar-messages-section-test.js | 68 +++++++++++++++++++ 7 files changed, 177 insertions(+), 61 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js index 44ba8e2310..ac57cf3218 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js @@ -1,5 +1,6 @@ import { cached } from "@glimmer/tracking"; +import { getOwner } from "discourse-common/lib/get-owner"; import GlimmerComponent from "discourse/components/glimmer"; import GroupMessageSectionLink from "discourse/lib/sidebar/messages-section/group-message-section-link"; import PersonalMessageSectionLink from "discourse/lib/sidebar/messages-section/personal-message-section-link"; @@ -10,8 +11,15 @@ const SENT = "sent"; const NEW = "new"; const ARCHIVE = "archive"; -export const PERSONAL_MESSAGES_INBOXES = [INBOX, UNREAD, NEW, SENT, ARCHIVE]; -export const GROUP_MESSAGES_INBOXES = [INBOX, UNREAD, NEW, ARCHIVE]; +export const PERSONAL_MESSAGES_INBOX_FILTERS = [ + INBOX, + NEW, + UNREAD, + SENT, + ARCHIVE, +]; + +export const GROUP_MESSAGES_INBOX_FILTERS = [INBOX, NEW, UNREAD, ARCHIVE]; export default class SidebarMessagesSection extends GlimmerComponent { constructor() { @@ -37,19 +45,30 @@ export default class SidebarMessagesSection extends GlimmerComponent { currentRouteParentName, currentRouteParams, }) { - const sectionLinks = [ - ...this.personalMessagesSectionLinks, - ...this.groupMessagesSectionLinks, - ]; - - if (currentRouteParentName !== "userPrivateMessages") { - sectionLinks.forEach((sectionLink) => { + if ( + currentRouteParentName !== "userPrivateMessages" && + currentRouteParentName !== "topic" + ) { + for (const sectionLink of this.allSectionLinks) { sectionLink.collapse(); - }); + } } else { - sectionLinks.forEach((sectionLink) => { - sectionLink.pageChanged(currentRouteName, currentRouteParams); - }); + const attrs = { + currentRouteName, + currentRouteParams, + }; + + if (currentRouteParentName === "topic") { + const topicController = getOwner(this).lookup("controller:topic"); + + if (topicController.model.isPrivateMessage) { + attrs.privateMessageTopic = topicController.model; + } + } + + for (const sectionLink of this.allSectionLinks) { + sectionLink.pageChanged(attrs); + } } } @@ -57,7 +76,7 @@ export default class SidebarMessagesSection extends GlimmerComponent { get personalMessagesSectionLinks() { const links = []; - PERSONAL_MESSAGES_INBOXES.forEach((type) => { + PERSONAL_MESSAGES_INBOX_FILTERS.forEach((type) => { links.push( new PersonalMessageSectionLink({ currentUser: this.currentUser, @@ -74,7 +93,7 @@ export default class SidebarMessagesSection extends GlimmerComponent { const links = []; this.currentUser.groupsWithMessages.forEach((group) => { - GROUP_MESSAGES_INBOXES.forEach((groupMessageLink) => { + GROUP_MESSAGES_INBOX_FILTERS.forEach((groupMessageLink) => { links.push( new GroupMessageSectionLink({ group, @@ -87,4 +106,11 @@ export default class SidebarMessagesSection extends GlimmerComponent { return links; } + + get allSectionLinks() { + return [ + ...this.groupMessagesSectionLinks, + ...this.personalMessagesSectionLinks, + ]; + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js index 9de4993327..83192a8765 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js @@ -1,13 +1,9 @@ import I18n from "I18n"; -import { tracked } from "@glimmer/tracking"; import { capitalize } from "@ember/string"; +import MessageSectionLink from "discourse/lib/sidebar/messages-section/message-section-link"; -import { INBOX } from "discourse/components/sidebar/messages-section"; - -export default class GroupMessageSectionLink { - @tracked shouldDisplay = this._isInbox; - +export default class GroupMessageSectionLink extends MessageSectionLink { routeNames = new Set([ "userPrivateMessages.group", "userPrivateMessages.groupUnread", @@ -15,13 +11,6 @@ export default class GroupMessageSectionLink { "userPrivateMessages.groupArchive", ]); - constructor({ group, type, currentUser, router }) { - this.group = group; - this.type = type; - this.currentUser = currentUser; - this.router = router; - } - get name() { return `group-messages-${this.type}`; } @@ -56,25 +45,22 @@ export default class GroupMessageSectionLink { } } - collapse() { + pageChanged({ currentRouteName, currentRouteParams, privateMessageTopic }) { if (this._isInbox) { return; } - this.shouldDisplay = false; - } - - pageChanged(currentRouteName, currentRouteParams) { - if (this._isInbox) { + if ( + privateMessageTopic?.allowedGroups?.some( + (g) => g.name === this.group.name + ) + ) { + this.setDisplayState = true; return; } - this.shouldDisplay = + this.setDisplayState = this.routeNames.has(currentRouteName) && currentRouteParams.name.toLowerCase() === this.group.name.toLowerCase(); } - - get _isInbox() { - return this.type === INBOX; - } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js new file mode 100644 index 0000000000..fcd029e218 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js @@ -0,0 +1,46 @@ +import { tracked } from "@glimmer/tracking"; + +import { INBOX } from "discourse/components/sidebar/messages-section"; + +export default class MessageSectionLink { + @tracked shouldDisplay = this._isInbox; + + constructor({ group, currentUser, type }) { + this.group = group; + this.currentUser = currentUser; + this.type = type; + } + + set setDisplayState(value) { + this.shouldDisplay = value; + } + + get inboxFilter() { + throw "not implemented"; + } + + expand() { + if (this._isInbox) { + return; + } + + this.setDisplayState = true; + } + + collapse() { + if (this._isInbox) { + return; + } + + this.setDisplayState = false; + } + + // eslint-disable-next-line no-unused-vars + pageChanged({ currentRouteName, currentRouteParams, privateMessageTopic }) { + throw "not implemented"; + } + + get _isInbox() { + return this.type === INBOX; + } +} diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js index 66bc38e856..dc2c42646f 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js @@ -1,11 +1,8 @@ import I18n from "I18n"; -import { tracked } from "@glimmer/tracking"; -import { INBOX } from "discourse/components/sidebar/messages-section"; - -export default class PersonalMessageSectionLink { - @tracked shouldDisplay = this._isInbox; +import MessageSectionLink from "discourse/lib/sidebar/messages-section/message-section-link"; +export default class PersonalMessageSectionLink extends MessageSectionLink { routeNames = new Set([ "userPrivateMessages.index", "userPrivateMessages.unread", @@ -14,16 +11,14 @@ export default class PersonalMessageSectionLink { "userPrivateMessages.archive", ]); - constructor({ currentUser, type, router }) { - this.currentUser = currentUser; - this.type = type; - this.router = router; - } - get name() { return `personal-messages-${this.type}`; } + get class() { + return `personal-messages`; + } + get route() { if (this._isInbox) { return "userPrivateMessages.index"; @@ -46,23 +41,16 @@ export default class PersonalMessageSectionLink { return I18n.t(`sidebar.sections.messages.links.${this.type}`); } - collapse() { + pageChanged({ currentRouteName, privateMessageTopic }) { if (this._isInbox) { return; } - this.shouldDisplay = false; - } - - pageChanged(currentRouteName) { - if (this._isInbox) { + if (privateMessageTopic?.allowedGroups?.length === 0) { + this.setDisplayState = true; return; } - this.shouldDisplay = this.routeNames.has(currentRouteName); - } - - get _isInbox() { - return this.type === INBOX; + this.setDisplayState = this.routeNames.has(currentRouteName); } } diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js index ccb9f1a675..9b00d32746 100644 --- a/app/assets/javascripts/discourse/app/models/topic.js +++ b/app/assets/javascripts/discourse/app/models/topic.js @@ -70,6 +70,7 @@ const Topic = RestModel.extend({ lastPosterUser: alias("lastPoster.user"), lastPosterGroup: alias("lastPoster.primary_group"), + allowedGroups: alias("details.allowed_groups"), @discourseComputed("posters.[]", "participants.[]", "allowed_user_count") featuredUsers(posters, participants, allowedUserCount) { diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs index f02235cd28..a9c9cc3f19 100644 --- a/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar/messages-section.hbs @@ -11,6 +11,7 @@ {{#if personalMessageSectionLink.shouldDisplay}} Date: Mon, 20 Jun 2022 06:49:33 +0300 Subject: [PATCH 054/184] UX: Change wording for 'regular' categories to 'normal' (#17134) At some point in the past we decided to rename the 'regular' notification state of topics/categories to 'normal'. However, some UI copy was missed when the initial renaming was done so this commit changes the spots that were missed to the new name. --- .../admin/addon/mixins/setting-component.js | 2 +- .../discourse/app/lib/notification-levels.js | 2 ++ .../admin/site_settings_controller.rb | 2 +- app/models/category_user.rb | 2 +- app/models/user.rb | 4 ++-- config/locales/client.en.yml | 6 +++--- config/locales/server.en.yml | 2 +- config/site_settings.yml | 2 +- ...name_default_categories_regular_setting.rb | 19 +++++++++++++++++++ lib/notification_levels.rb | 1 + lib/site_settings/deprecated_settings.rb | 1 + lib/site_settings/validations.rb | 8 ++++---- lib/topic_query.rb | 2 +- spec/lib/topic_query_spec.rb | 2 +- spec/models/category_user_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- spec/serializers/site_serializer_spec.rb | 2 +- 17 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 db/post_migrate/20220617151846_rename_default_categories_regular_setting.rb diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js index ebf012aae9..78ab3304cf 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-component.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js @@ -150,7 +150,7 @@ export default Mixin.create({ "default_categories_tracking", "default_categories_muted", "default_categories_watching_first_post", - "default_categories_regular", + "default_categories_normal", "default_tags_watching", "default_tags_tracking", "default_tags_muted", diff --git a/app/assets/javascripts/discourse/app/lib/notification-levels.js b/app/assets/javascripts/discourse/app/lib/notification-levels.js index 8bd3efaaa2..c3423f5ac2 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-levels.js +++ b/app/assets/javascripts/discourse/app/lib/notification-levels.js @@ -1,5 +1,6 @@ const MUTED = 0; const REGULAR = 1; +const NORMAL = 1; // alias for REGULAR const TRACKING = 2; const WATCHING = 3; const WATCHING_FIRST_POST = 4; @@ -9,6 +10,7 @@ export const NotificationLevels = { WATCHING, TRACKING, REGULAR, + NORMAL, MUTED, }; diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb index 197269f0d0..524b47827a 100644 --- a/app/controllers/admin/site_settings_controller.rb +++ b/app/controllers/admin/site_settings_controller.rb @@ -216,7 +216,7 @@ class Admin::SiteSettingsController < Admin::AdminController NotificationLevels.all[:muted] when "default_categories_watching_first_post" NotificationLevels.all[:watching_first_post] - when "default_categories_regular" + when "default_categories_normal" NotificationLevels.all[:regular] end end diff --git a/app/models/category_user.rb b/app/models/category_user.rb index 21e27db0cb..b07ac73e71 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -209,7 +209,7 @@ class CategoryUser < ActiveRecord::Base SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map do |id| [id.to_i, self.notification_levels[:regular]] end diff --git a/app/models/user.rb b/app/models/user.rb index 2ca32db206..f0b4a91504 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1671,9 +1671,9 @@ class User < ActiveRecord::Base # * default_categories_watching # * default_categories_tracking # * default_categories_watching_first_post - # * default_categories_regular + # * default_categories_normal # * default_categories_muted - %w{watching watching_first_post tracking regular muted}.each do |setting| + %w{watching watching_first_post tracking normal muted}.each do |setting| category_ids = SiteSetting.get("default_categories_#{setting}").split("|").map(&:to_i) category_ids.each do |category_id| next if category_id == 0 diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 67688a4e85..b25a4e1b85 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1204,7 +1204,7 @@ en: muted_categories: "Muted" muted_categories_instructions: "You will not be notified of anything about new topics in these categories, and they will not appear on the categories or latest pages." muted_categories_instructions_dont_hide: "You will not be notified of anything about new topics in these categories." - regular_categories: "Regular" + regular_categories: "Normal" regular_categories_instructions: "You will see these categories in the “Latest” and “Top” topic lists." no_category_access: "As a moderator you have limited category access, save is disabled." delete_account: "Delete My Account" @@ -3777,7 +3777,7 @@ en: edit: "%{shortcut} Edit post" delete: "%{shortcut} Delete post" mark_muted: "%{shortcut} Mute topic" - mark_regular: "%{shortcut} Regular (default) topic" + mark_regular: "%{shortcut} Normal (default) topic" mark_tracking: "%{shortcut} Track topic" mark_watching: "%{shortcut} Watch topic" print: "%{shortcut} Print topic" @@ -3921,7 +3921,7 @@ en: title: "Tracking" description: "You will automatically track all topics with this tag. A count of unread and new posts will appear next to the topic." regular: - title: "Regular" + title: "Normal" description: "You will be notified if someone mentions your @name or replies to your post." muted: title: "Muted" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 703bce9c81..984813b64a 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2295,7 +2295,7 @@ en: default_categories_tracking: "List of categories that are tracked by default." default_categories_muted: "List of categories that are muted by default." default_categories_watching_first_post: "List of categories in which first post in each new topic will be watched by default." - default_categories_regular: "List of categories that are not muted by default. Useful when `mute_all_categories_by_default` site setting is enabled." + default_categories_normal: "List of categories that are not muted by default. Useful when `mute_all_categories_by_default` site setting is enabled." mute_all_categories_by_default: "Set the default notification level of all the categories to muted. Require users opt-in to categories for them to appear in 'latest' and 'categories' pages. If you wish to amend the defaults for anonymous users set 'default_categories_' settings." default_tags_watching: "List of tags that are watched by default." diff --git a/config/site_settings.yml b/config/site_settings.yml index e2be4ea867..49b3e59824 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -2485,7 +2485,7 @@ user_preferences: default_categories_watching_first_post: type: category_list default: "" - default_categories_regular: + default_categories_normal: type: category_list default: "" mute_all_categories_by_default: diff --git a/db/post_migrate/20220617151846_rename_default_categories_regular_setting.rb b/db/post_migrate/20220617151846_rename_default_categories_regular_setting.rb new file mode 100644 index 0000000000..775d80acf7 --- /dev/null +++ b/db/post_migrate/20220617151846_rename_default_categories_regular_setting.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RenameDefaultCategoriesRegularSetting < ActiveRecord::Migration[7.0] + def up + execute <<~SQL + UPDATE site_settings + SET name = 'default_categories_normal' + WHERE name = 'default_categories_regular' + SQL + end + + def down + execute <<~SQL + UPDATE site_settings + SET name = 'default_categories_regular' + WHERE name = 'default_categories_normal' + SQL + end +end diff --git a/lib/notification_levels.rb b/lib/notification_levels.rb index ccf2454160..01ee132b3b 100644 --- a/lib/notification_levels.rb +++ b/lib/notification_levels.rb @@ -4,6 +4,7 @@ module NotificationLevels def self.all @all_levels ||= Enum.new(muted: 0, regular: 1, + normal: 1, # alias for regular tracking: 2, watching: 3, watching_first_post: 4) diff --git a/lib/site_settings/deprecated_settings.rb b/lib/site_settings/deprecated_settings.rb index 45bfc46953..cb8f352287 100644 --- a/lib/site_settings/deprecated_settings.rb +++ b/lib/site_settings/deprecated_settings.rb @@ -6,6 +6,7 @@ module SiteSettings::DeprecatedSettings SETTINGS = [ # [, , , ] ['search_tokenize_chinese_japanese_korean', 'search_tokenize_chinese', true, '2.9'], + ['default_categories_regular', 'default_categories_normal', true, '3.0'], ] def setup_deprecated_methods diff --git a/lib/site_settings/validations.rb b/lib/site_settings/validations.rb index bc5a3b80c5..e154bb5fee 100644 --- a/lib/site_settings/validations.rb +++ b/lib/site_settings/validations.rb @@ -46,7 +46,7 @@ module SiteSettings::Validations SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_muted.split("|"), SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map(&:to_i).to_set validate_default_categories(category_ids, default_categories_selected) @@ -59,7 +59,7 @@ module SiteSettings::Validations SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_muted.split("|"), SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map(&:to_i).to_set validate_default_categories(category_ids, default_categories_selected) @@ -72,7 +72,7 @@ module SiteSettings::Validations SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map(&:to_i).to_set validate_default_categories(category_ids, default_categories_selected) @@ -85,7 +85,7 @@ module SiteSettings::Validations SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_muted.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map(&:to_i).to_set validate_default_categories(category_ids, default_categories_selected) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 123e47f348..56c02a167a 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -866,7 +866,7 @@ class TopicQuery SiteSetting.default_categories_watching.split("|"), SiteSetting.default_categories_tracking.split("|"), SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_regular.split("|") + SiteSetting.default_categories_normal.split("|") ].flatten.map(&:to_i) category_ids << category_id if category_id.present? && category_ids.exclude?(category_id) diff --git a/spec/lib/topic_query_spec.rb b/spec/lib/topic_query_spec.rb index 37275cb3e8..fdb6925209 100644 --- a/spec/lib/topic_query_spec.rb +++ b/spec/lib/topic_query_spec.rb @@ -549,7 +549,7 @@ describe TopicQuery do end it 'should include default regular category topics in latest list for anonymous users' do - SiteSetting.default_categories_regular = category.id.to_s + SiteSetting.default_categories_normal = category.id.to_s expect(TopicQuery.new.list_latest.topics.map(&:id)).to include(topic.id) end diff --git a/spec/models/category_user_spec.rb b/spec/models/category_user_spec.rb index 2b8d4f33e1..815f094d6c 100644 --- a/spec/models/category_user_spec.rb +++ b/spec/models/category_user_spec.rb @@ -232,7 +232,7 @@ describe CategoryUser do SiteSetting.default_categories_watching = category1.id.to_s SiteSetting.default_categories_tracking = category2.id.to_s SiteSetting.default_categories_watching_first_post = category3.id.to_s - SiteSetting.default_categories_regular = category4.id.to_s + SiteSetting.default_categories_normal = category4.id.to_s SiteSetting.default_categories_muted = category5.id.to_s end it "every category from the default_categories_* site settings get overridden to regular, except for muted" do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8269cc4b9f..173eadff6e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1727,7 +1727,7 @@ RSpec.describe User do SiteSetting.default_categories_tracking = category1.id.to_s SiteSetting.default_categories_muted = category2.id.to_s SiteSetting.default_categories_watching_first_post = category3.id.to_s - SiteSetting.default_categories_regular = category4.id.to_s + SiteSetting.default_categories_normal = category4.id.to_s end it "has overridden preferences" do diff --git a/spec/serializers/site_serializer_spec.rb b/spec/serializers/site_serializer_spec.rb index e2188f3416..c16ac11e2b 100644 --- a/spec/serializers/site_serializer_spec.rb +++ b/spec/serializers/site_serializer_spec.rb @@ -61,7 +61,7 @@ describe SiteSerializer do it "returns correct notification level for categories" do SiteSetting.mute_all_categories_by_default = true - SiteSetting.default_categories_regular = category.id.to_s + SiteSetting.default_categories_normal = category.id.to_s serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json categories = serialized[:categories] From 9361d9a58721d2dce377efe00d2f400ae4953569 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 20 Jun 2022 16:57:46 +1000 Subject: [PATCH 055/184] FIX: stop logging blank and invalid CSP reports (#17144) Certain rogue bots such as Yandex may send across invalid CSP reports when CSP report collection is enabled. This ensures that invalid reports will not cause log floods and simply returns a 422 error. Co-authored-by: Alan Guo Xiang Tan --- app/controllers/csp_reports_controller.rb | 49 +++++++++++++------- spec/requests/csp_reports_controller_spec.rb | 12 +++++ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/app/controllers/csp_reports_controller.rb b/app/controllers/csp_reports_controller.rb index 30824e3536..d18fd7d1c2 100644 --- a/app/controllers/csp_reports_controller.rb +++ b/app/controllers/csp_reports_controller.rb @@ -5,28 +5,43 @@ class CspReportsController < ApplicationController def create raise Discourse::NotFound unless report_collection_enabled? - Logster.add_to_env(request.env, 'CSP Report', report) - Rails.logger.warn("CSP Violation: '#{report['blocked-uri']}' \n\n#{report['script-sample']}") + report = parse_report - head :ok + if report.blank? + render_json_error("empty CSP report", status: 422) + else + Logster.add_to_env(request.env, 'CSP Report', report) + Rails.logger.warn("CSP Violation: '#{report['blocked-uri']}' \n\n#{report['script-sample']}") + + head :ok + end + + rescue JSON::ParserError + render_json_error("invalid CSP report", status: 422) end private - def report - @report ||= JSON.parse(request.body.read)['csp-report'].slice( - 'blocked-uri', - 'disposition', - 'document-uri', - 'effective-directive', - 'original-policy', - 'referrer', - 'script-sample', - 'status-code', - 'violated-directive', - 'line-number', - 'source-file' - ) + def parse_report + obj = JSON.parse(request.body.read) + if Hash === obj + obj = obj['csp-report'] + if Hash === obj + obj.slice( + 'blocked-uri', + 'disposition', + 'document-uri', + 'effective-directive', + 'original-policy', + 'referrer', + 'script-sample', + 'status-code', + 'violated-directive', + 'line-number', + 'source-file' + ) + end + end end def report_collection_enabled? diff --git a/spec/requests/csp_reports_controller_spec.rb b/spec/requests/csp_reports_controller_spec.rb index 87ecbcd6da..911c30f71a 100644 --- a/spec/requests/csp_reports_controller_spec.rb +++ b/spec/requests/csp_reports_controller_spec.rb @@ -32,6 +32,18 @@ describe CspReportsController do }.to_json, headers: { "Content-Type": "application/csp-report" } end + it 'returns an error for invalid reports' do + SiteSetting.content_security_policy_collect_reports = true + + post '/csp_reports', params: "[ not-json", headers: { "Content-Type": "application/csp-report" } + + expect(response.status).to eq(422) + + post '/csp_reports', params: ["yes json"].to_json, headers: { "Content-Type": "application/csp-report" } + + expect(response.status).to eq(422) + end + it 'is enabled by SiteSetting' do SiteSetting.content_security_policy = false SiteSetting.content_security_policy_report_only = false From b2bf1db36f3a9e418330ae9e988779f3d2eb637b Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:39:11 +0800 Subject: [PATCH 056/184] DEV: Move preloaded `json` into `` element (#17145) This PR introduces 0 visual or functional changes. The only thing that it changes is that it moves the data-preloaded div (which has the app boot json into the element. See #17078 for a bit more context. The reason behind this change is that it makes devTools element view a little bit less cluttered. --- app/assets/javascripts/discourse/app/index.html | 4 +++- app/views/layouts/application.html.erb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html index 4a5295d14e..96b9f9baee 100644 --- a/app/assets/javascripts/discourse/app/index.html +++ b/app/assets/javascripts/discourse/app/index.html @@ -25,6 +25,9 @@ {{content-for "discourse-stylesheets"}} + + + @@ -34,7 +37,6 @@ - diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 3c6f20cbad..52c5302136 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -75,6 +75,9 @@ <%= render partial: "common/discourse_stylesheet" %> + + + <%- if allow_plugins? %> @@ -114,7 +117,6 @@ <% end %> - From 9d86a2b56788027d86e22af3c76c15a23a342576 Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:59:42 +0800 Subject: [PATCH 057/184] DEV: Adds missing defer attribute to wizard js in dev layout (#17146) Small followup to #17063 The wizard `js` file slipped through the cracks. This only affected dev installs. --- app/assets/javascripts/discourse/lib/bootstrap-json/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index 98fc1ed882..e44465f8a2 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -73,7 +73,7 @@ function head(buffer, bootstrap, headers, baseURL) { } if (admin) { - buffer.push(``); + buffer.push(``); } } From 93b8811f087f8fe2e5aa64eb6ab6f68b8b327bab Mon Sep 17 00:00:00 2001 From: Joe <33972521+hnb-ku@users.noreply.github.com> Date: Mon, 20 Jun 2022 20:58:50 +0800 Subject: [PATCH 058/184] DEV: Moves SVG sprite to `` element (#17148) Similar to #17145 This commit moves the SVG sprite container to the element. There is 0 visual or functional changes in this PR. It just tidies up the element view in devTools. --- app/assets/javascripts/discourse/app/index.html | 1 + app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js | 3 ++- app/views/layouts/application.html.erb | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html index 96b9f9baee..ad51651ba8 100644 --- a/app/assets/javascripts/discourse/app/index.html +++ b/app/assets/javascripts/discourse/app/index.html @@ -28,6 +28,7 @@ + diff --git a/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js b/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js index d08ebd2304..42f033c413 100644 --- a/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js +++ b/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js @@ -7,7 +7,8 @@ export function loadSprites(spritePath, spriteName) { if (!spriteContainer) { spriteContainer = document.createElement("div"); spriteContainer.id = SVG_CONTAINER_ID; - document.body.appendChild(spriteContainer); + const spriteWrapper = document.querySelector("discourse-assets-icons"); + spriteWrapper?.appendChild(spriteContainer); } let sprites = spriteContainer.querySelector(`.${spriteName}`); diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 52c5302136..2449c821fe 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -78,6 +78,7 @@ + <%- if allow_plugins? %> From 051167c98ae608304911d743334c7485340a963e Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 20 Jun 2022 15:13:42 +0200 Subject: [PATCH 059/184] DEV: Fix `this.clearRender` deprecation warning (#17150) ``` {"type":"warn","text":"DEPRECATION: Using this.clearRender has been deprecated, consider using `clearRender` imported from `@ember/test-helpers`. [deprecation id: ember-test-helpers.setup-rendering-context.clearRender]"} ``` --- .../javascripts/discourse/tests/unit/utils/decorators-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js b/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js index c85e4eb81e..65239a6dbd 100644 --- a/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js +++ b/app/assets/javascripts/discourse/tests/unit/utils/decorators-test.js @@ -1,4 +1,5 @@ import Component from "@ember/component"; +import { clearRender } from "@ember/test-helpers"; import discourseComputed, { afterRender, } from "discourse-common/utils/decorators"; @@ -55,7 +56,7 @@ discourseModule("Unit | Utils | decorators", function (hooks) { assert.ok(exists(document.querySelector(".foo-component"))); assert.strictEqual(this.baz, 1); - await this.clearRender(); + await clearRender(); assert.ok(!exists(document.querySelector(".foo-component"))); assert.strictEqual(this.baz, 1); From 1c6f8f8a362ec745d6dcd7f6dd9d6106b0c7491b Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Mon, 20 Jun 2022 15:25:13 +0200 Subject: [PATCH 060/184] DEV: Upgrade Markdown.it to v13.0.1 (#17099) Updates markdown-it to v13.0.1 Noteworthy changes: * `markdownit()` is now available on `globalThis` instead of `window`. * The `text_collapse` rule was renamed to `fragments_join` which affected the `bbcode-inline` implementation. * The `linkify` rule was added to the `inline` chain which affected the handling of the `[url]` BBCode. If available, our implementation reuses `link_open` and `link_close` tokens created by linkify in order to prevent duplicate links. * The rendered HTML for code changed slightly. There's now a linebreak before the `` tag. The tests were adjusted accordingly. --- .eslintrc | 1 + .../tests/unit/lib/pretty-text-test.js | 6 +- .../addon/engines/discourse-markdown-it.js | 4 +- .../discourse-markdown/bbcode-inline.js | 62 +- package.json | 6 +- vendor/assets/javascripts/markdown-it.js | 16178 ++++++++-------- yarn.lock | 35 +- 7 files changed, 8321 insertions(+), 7971 deletions(-) diff --git a/.eslintrc b/.eslintrc index 621a902c84..4aee50ed2a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ "fillIn": "off", "find": "off", "getSettledState": "off", + "globalThis": "readonly", "hasModule": "off", "invisible": "off", "jQuery": "off", diff --git a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js index 630457df32..0742c4bcec 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js @@ -877,7 +877,7 @@ eviltrout

assert.cooked( " ```\n hello\n ```", - "
```\nhello\n```
", + "
```\nhello\n```\n
", "only detect ``` at the beginning of lines" ); @@ -925,13 +925,13 @@ eviltrout

assert.cooked( "
test
", - "
<pre>test</pre>
", + "
<pre>test</pre>\n
", "it does not parse other block types in markdown code blocks" ); assert.cooked( " [quote]test[/quote]", - "
[quote]test[/quote]
", + "
[quote]test[/quote]\n
", "it does not parse other block types in markdown code blocks" ); diff --git a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js index 80381858cd..cf33e248ce 100644 --- a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js +++ b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js @@ -344,9 +344,9 @@ function buildCustomMarkdownCookFunction(engineOpts, defaultEngineOpts) { function createMarkdownItEngineWithOpts(markdownitOpts, ruleOverrides) { if (ruleOverrides !== undefined) { // Preset for "zero", https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js - return window.markdownit("zero", markdownitOpts).enable(ruleOverrides); + return globalThis.markdownit("zero", markdownitOpts).enable(ruleOverrides); } - return window.markdownit(markdownitOpts); + return globalThis.markdownit(markdownitOpts); } function overrideMarkdownFeatures(features, featureOverrides) { diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-inline.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-inline.js index 26dd393b1f..8e8bbeba31 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-inline.js +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-inline.js @@ -85,7 +85,6 @@ function processBBCode(state, silent) { let i, startDelim, endDelim, - token, tagInfo, delimiters = state.delimiters, max = delimiters.length; @@ -108,9 +107,11 @@ function processBBCode(state, silent) { endDelim = delimiters[startDelim.end]; - token = state.tokens[startDelim.token]; let tag, className; + const startToken = state.tokens[startDelim.token]; + const endToken = state.tokens[endDelim.token]; + if (typeof tagInfo.rule.wrap === "function") { let content = ""; for (let j = startDelim.token + 1; j < endDelim.token; j++) { @@ -119,7 +120,7 @@ function processBBCode(state, silent) { content += inner.content; } } - tagInfo.rule.wrap(token, state.tokens[endDelim.token], tagInfo, content); + tagInfo.rule.wrap(startToken, endToken, tagInfo, content, state); continue; } else { let split = tagInfo.rule.wrap.split("."); @@ -127,21 +128,20 @@ function processBBCode(state, silent) { className = split.slice(1).join(" "); } - token.type = "bbcode_" + tagInfo.tag + "_open"; - token.tag = tag; + startToken.type = "bbcode_" + tagInfo.tag + "_open"; + startToken.tag = tag; if (className) { - token.attrs = [["class", className]]; + startToken.attrs = [["class", className]]; } - token.nesting = 1; - token.markup = token.content; - token.content = ""; + startToken.nesting = 1; + startToken.markup = startToken.content; + startToken.content = ""; - token = state.tokens[endDelim.token]; - token.type = "bbcode_" + tagInfo.tag + "_close"; - token.tag = tag; - token.nesting = -1; - token.markup = token.content; - token.content = ""; + endToken.type = "bbcode_" + tagInfo.tag + "_close"; + endToken.tag = tag; + endToken.nesting = -1; + endToken.markup = startToken.content; + endToken.content = ""; } return false; } @@ -164,7 +164,7 @@ export function setup(helper) { md.inline.ruler.push("bbcode-inline", (state, silent) => tokenizeBBCode(state, silent, ruler) ); - md.inline.ruler2.before("text_collapse", "bbcode-inline", processBBCode); + md.inline.ruler2.before("fragments_join", "bbcode-inline", processBBCode); ruler.push("code", { tag: "code", @@ -176,13 +176,35 @@ export function setup(helper) { }, }); - const simpleUrlRegex = /^http[s]?:\/\//; + const simpleUrlRegex = /^https?:\/\//; ruler.push("url", { tag: "url", - wrap(startToken, endToken, tagInfo, content) { + wrap(startToken, endToken, tagInfo, content, state) { const url = (tagInfo.attrs["_default"] || content).trim(); + let linkifyFound = false; - if (simpleUrlRegex.test(url)) { + if (state.md.options.linkify) { + const tokens = state.tokens; + const startIndex = tokens.indexOf(startToken); + const endIndex = tokens.indexOf(endToken); + + // reuse existing tokens from linkify if they exist + for (let index = startIndex + 1; index < endIndex; index++) { + const token = tokens[index]; + + if ( + token.markup === "linkify" && + token.info === "auto" && + token.type === "link_open" + ) { + linkifyFound = true; + token.attrs.push(["data-bbcode", "true"]); + break; + } + } + } + + if (!linkifyFound && simpleUrlRegex.test(url)) { startToken.type = "link_open"; startToken.tag = "a"; startToken.attrs = [ @@ -214,7 +236,7 @@ export function setup(helper) { tag: "email", replace(state, tagInfo, content) { let token; - let email = tagInfo.attrs["_default"] || content; + const email = tagInfo.attrs["_default"] || content; token = state.push("link_open", "a", 1); token.attrs = [ diff --git a/package.json b/package.json index 3d80aaada9..e055507870 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,10 @@ "license": "GPL-2.0-only", "dependencies": { "@discourse/itsatrap": "^2.0.10", - "@fortawesome/fontawesome-free": "5.15.4", "@discourse/moment-timezone-names-translations": "^1.0.0", + "@fortawesome/fontawesome-free": "5.15.4", "@highlightjs/cdn-assets": "^10.7.0", "@json-editor/json-editor": "^2.6.1", - "tippy.js": "^6.3.7", "@popperjs/core": "v2.10.2", "@uppy/aws-s3": "^2.0.8", "@uppy/aws-s3-multipart": "^2.2.1", @@ -29,10 +28,11 @@ "handlebars": "^4.7.7", "jquery": "3.5.1", "magnific-popup": "1.1.0", - "markdown-it": "10.0.0", + "markdown-it": "13.0.1", "moment": "2.29.2", "moment-timezone": "0.5.31", "pikaday": "1.8.0", + "tippy.js": "^6.3.7", "workbox-cacheable-response": "^4.3.1", "workbox-core": "^4.3.1", "workbox-expiration": "^4.3.1", diff --git a/vendor/assets/javascripts/markdown-it.js b/vendor/assets/javascripts/markdown-it.js index b2b5123dbf..938677aea6 100644 --- a/vendor/assets/javascripts/markdown-it.js +++ b/vendor/assets/javascripts/markdown-it.js @@ -1,5105 +1,6516 @@ -/*! markdown-it 10.0.0 https://github.com//markdown-it/markdown-it @license MIT */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.markdownit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } -// -'use strict'; - -/*eslint quotes:0*/ -module.exports = require('entities/lib/maps/entities.json'); - -},{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ -// List of valid html blocks names, accorting to commonmark spec -// http://jgm.github.io/CommonMark/spec.html#html-blocks - -'use strict'; - - -module.exports = [ - 'address', - 'article', - 'aside', - 'base', - 'basefont', - 'blockquote', - 'body', - 'caption', - 'center', - 'col', - 'colgroup', - 'dd', - 'details', - 'dialog', - 'dir', - 'div', - 'dl', - 'dt', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', - 'header', - 'hr', - 'html', - 'iframe', - 'legend', - 'li', - 'link', - 'main', - 'menu', - 'menuitem', - 'meta', - 'nav', - 'noframes', - 'ol', - 'optgroup', - 'option', - 'p', - 'param', - 'section', - 'source', - 'summary', - 'table', - 'tbody', - 'td', - 'tfoot', - 'th', - 'thead', - 'title', - 'tr', - 'track', - 'ul' -]; - -},{}],3:[function(require,module,exports){ -// Regexps to match html elements - -'use strict'; - -var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; - -var unquoted = '[^"\'=<>`\\x00-\\x20]+'; -var single_quoted = "'[^']*'"; -var double_quoted = '"[^"]*"'; - -var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; - -var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; - -var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; - -var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; -var comment = '|'; -var processing = '<[?].*?[?]>'; -var declaration = ']*>'; -var cdata = ''; - -var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + - '|' + processing + '|' + declaration + '|' + cdata + ')'); -var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); - -module.exports.HTML_TAG_RE = HTML_TAG_RE; -module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; - -},{}],4:[function(require,module,exports){ -// Utilities -// -'use strict'; - - -function _class(obj) { return Object.prototype.toString.call(obj); } - -function isString(obj) { return _class(obj) === '[object String]'; } - -var _hasOwnProperty = Object.prototype.hasOwnProperty; - -function has(object, key) { - return _hasOwnProperty.call(object, key); -} - -// Merge objects -// -function assign(obj /*from1, from2, from3, ...*/) { - var sources = Array.prototype.slice.call(arguments, 1); - - sources.forEach(function (source) { - if (!source) { return; } - - if (typeof source !== 'object') { - throw new TypeError(source + 'must be object'); - } - - Object.keys(source).forEach(function (key) { - obj[key] = source[key]; +/*! markdown-it 13.0.1 https://github.com/markdown-it/markdown-it @license MIT */ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, + global.markdownit = factory()); +})(this, (function() { + "use strict"; + function createCommonjsModule(fn, basedir, module) { + return module = { + path: basedir, + exports: {}, + require: function(path, base) { + return commonjsRequire(path, base === undefined || base === null ? module.path : base); + } + }, fn(module, module.exports), module.exports; + } + function getAugmentedNamespace(n) { + if (n.__esModule) return n; + var a = Object.defineProperty({}, "__esModule", { + value: true }); - }); - - return obj; -} - -// Remove element from array and put another array at those position. -// Useful for some operations with tokens -function arrayReplaceAt(src, pos, newElements) { - return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); -} - -//////////////////////////////////////////////////////////////////////////////// - -function isValidEntityCode(c) { - /*eslint no-bitwise:0*/ - // broken sequence - if (c >= 0xD800 && c <= 0xDFFF) { return false; } - // never used - if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } - if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } - // control codes - if (c >= 0x00 && c <= 0x08) { return false; } - if (c === 0x0B) { return false; } - if (c >= 0x0E && c <= 0x1F) { return false; } - if (c >= 0x7F && c <= 0x9F) { return false; } - // out of range - if (c > 0x10FFFF) { return false; } - return true; -} - -function fromCodePoint(c) { - /*eslint no-bitwise:0*/ - if (c > 0xffff) { - c -= 0x10000; - var surrogate1 = 0xd800 + (c >> 10), - surrogate2 = 0xdc00 + (c & 0x3ff); - - return String.fromCharCode(surrogate1, surrogate2); + Object.keys(n).forEach((function(k) { + var d = Object.getOwnPropertyDescriptor(n, k); + Object.defineProperty(a, k, d.get ? d : { + enumerable: true, + get: function() { + return n[k]; + } + }); + })); + return a; } - return String.fromCharCode(c); -} - - -var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; -var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; -var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); - -var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; - -var entities = require('./entities'); - -function replaceEntityPattern(match, name) { - var code = 0; - - if (has(entities, name)) { - return entities[name]; + function commonjsRequire() { + throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs"); } - - if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { - code = name[1].toLowerCase() === 'x' ? - parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); - - if (isValidEntityCode(code)) { - return fromCodePoint(code); + var require$$0 = { + Aacute: "\xc1", + aacute: "\xe1", + Abreve: "\u0102", + abreve: "\u0103", + ac: "\u223e", + acd: "\u223f", + acE: "\u223e\u0333", + Acirc: "\xc2", + acirc: "\xe2", + acute: "\xb4", + Acy: "\u0410", + acy: "\u0430", + AElig: "\xc6", + aelig: "\xe6", + af: "\u2061", + Afr: "\ud835\udd04", + afr: "\ud835\udd1e", + Agrave: "\xc0", + agrave: "\xe0", + alefsym: "\u2135", + aleph: "\u2135", + Alpha: "\u0391", + alpha: "\u03b1", + Amacr: "\u0100", + amacr: "\u0101", + amalg: "\u2a3f", + amp: "&", + AMP: "&", + andand: "\u2a55", + And: "\u2a53", + and: "\u2227", + andd: "\u2a5c", + andslope: "\u2a58", + andv: "\u2a5a", + ang: "\u2220", + ange: "\u29a4", + angle: "\u2220", + angmsdaa: "\u29a8", + angmsdab: "\u29a9", + angmsdac: "\u29aa", + angmsdad: "\u29ab", + angmsdae: "\u29ac", + angmsdaf: "\u29ad", + angmsdag: "\u29ae", + angmsdah: "\u29af", + angmsd: "\u2221", + angrt: "\u221f", + angrtvb: "\u22be", + angrtvbd: "\u299d", + angsph: "\u2222", + angst: "\xc5", + angzarr: "\u237c", + Aogon: "\u0104", + aogon: "\u0105", + Aopf: "\ud835\udd38", + aopf: "\ud835\udd52", + apacir: "\u2a6f", + ap: "\u2248", + apE: "\u2a70", + ape: "\u224a", + apid: "\u224b", + apos: "'", + ApplyFunction: "\u2061", + approx: "\u2248", + approxeq: "\u224a", + Aring: "\xc5", + aring: "\xe5", + Ascr: "\ud835\udc9c", + ascr: "\ud835\udcb6", + Assign: "\u2254", + ast: "*", + asymp: "\u2248", + asympeq: "\u224d", + Atilde: "\xc3", + atilde: "\xe3", + Auml: "\xc4", + auml: "\xe4", + awconint: "\u2233", + awint: "\u2a11", + backcong: "\u224c", + backepsilon: "\u03f6", + backprime: "\u2035", + backsim: "\u223d", + backsimeq: "\u22cd", + Backslash: "\u2216", + Barv: "\u2ae7", + barvee: "\u22bd", + barwed: "\u2305", + Barwed: "\u2306", + barwedge: "\u2305", + bbrk: "\u23b5", + bbrktbrk: "\u23b6", + bcong: "\u224c", + Bcy: "\u0411", + bcy: "\u0431", + bdquo: "\u201e", + becaus: "\u2235", + because: "\u2235", + Because: "\u2235", + bemptyv: "\u29b0", + bepsi: "\u03f6", + bernou: "\u212c", + Bernoullis: "\u212c", + Beta: "\u0392", + beta: "\u03b2", + beth: "\u2136", + between: "\u226c", + Bfr: "\ud835\udd05", + bfr: "\ud835\udd1f", + bigcap: "\u22c2", + bigcirc: "\u25ef", + bigcup: "\u22c3", + bigodot: "\u2a00", + bigoplus: "\u2a01", + bigotimes: "\u2a02", + bigsqcup: "\u2a06", + bigstar: "\u2605", + bigtriangledown: "\u25bd", + bigtriangleup: "\u25b3", + biguplus: "\u2a04", + bigvee: "\u22c1", + bigwedge: "\u22c0", + bkarow: "\u290d", + blacklozenge: "\u29eb", + blacksquare: "\u25aa", + blacktriangle: "\u25b4", + blacktriangledown: "\u25be", + blacktriangleleft: "\u25c2", + blacktriangleright: "\u25b8", + blank: "\u2423", + blk12: "\u2592", + blk14: "\u2591", + blk34: "\u2593", + block: "\u2588", + bne: "=\u20e5", + bnequiv: "\u2261\u20e5", + bNot: "\u2aed", + bnot: "\u2310", + Bopf: "\ud835\udd39", + bopf: "\ud835\udd53", + bot: "\u22a5", + bottom: "\u22a5", + bowtie: "\u22c8", + boxbox: "\u29c9", + boxdl: "\u2510", + boxdL: "\u2555", + boxDl: "\u2556", + boxDL: "\u2557", + boxdr: "\u250c", + boxdR: "\u2552", + boxDr: "\u2553", + boxDR: "\u2554", + boxh: "\u2500", + boxH: "\u2550", + boxhd: "\u252c", + boxHd: "\u2564", + boxhD: "\u2565", + boxHD: "\u2566", + boxhu: "\u2534", + boxHu: "\u2567", + boxhU: "\u2568", + boxHU: "\u2569", + boxminus: "\u229f", + boxplus: "\u229e", + boxtimes: "\u22a0", + boxul: "\u2518", + boxuL: "\u255b", + boxUl: "\u255c", + boxUL: "\u255d", + boxur: "\u2514", + boxuR: "\u2558", + boxUr: "\u2559", + boxUR: "\u255a", + boxv: "\u2502", + boxV: "\u2551", + boxvh: "\u253c", + boxvH: "\u256a", + boxVh: "\u256b", + boxVH: "\u256c", + boxvl: "\u2524", + boxvL: "\u2561", + boxVl: "\u2562", + boxVL: "\u2563", + boxvr: "\u251c", + boxvR: "\u255e", + boxVr: "\u255f", + boxVR: "\u2560", + bprime: "\u2035", + breve: "\u02d8", + Breve: "\u02d8", + brvbar: "\xa6", + bscr: "\ud835\udcb7", + Bscr: "\u212c", + bsemi: "\u204f", + bsim: "\u223d", + bsime: "\u22cd", + bsolb: "\u29c5", + bsol: "\\", + bsolhsub: "\u27c8", + bull: "\u2022", + bullet: "\u2022", + bump: "\u224e", + bumpE: "\u2aae", + bumpe: "\u224f", + Bumpeq: "\u224e", + bumpeq: "\u224f", + Cacute: "\u0106", + cacute: "\u0107", + capand: "\u2a44", + capbrcup: "\u2a49", + capcap: "\u2a4b", + cap: "\u2229", + Cap: "\u22d2", + capcup: "\u2a47", + capdot: "\u2a40", + CapitalDifferentialD: "\u2145", + caps: "\u2229\ufe00", + caret: "\u2041", + caron: "\u02c7", + Cayleys: "\u212d", + ccaps: "\u2a4d", + Ccaron: "\u010c", + ccaron: "\u010d", + Ccedil: "\xc7", + ccedil: "\xe7", + Ccirc: "\u0108", + ccirc: "\u0109", + Cconint: "\u2230", + ccups: "\u2a4c", + ccupssm: "\u2a50", + Cdot: "\u010a", + cdot: "\u010b", + cedil: "\xb8", + Cedilla: "\xb8", + cemptyv: "\u29b2", + cent: "\xa2", + centerdot: "\xb7", + CenterDot: "\xb7", + cfr: "\ud835\udd20", + Cfr: "\u212d", + CHcy: "\u0427", + chcy: "\u0447", + check: "\u2713", + checkmark: "\u2713", + Chi: "\u03a7", + chi: "\u03c7", + circ: "\u02c6", + circeq: "\u2257", + circlearrowleft: "\u21ba", + circlearrowright: "\u21bb", + circledast: "\u229b", + circledcirc: "\u229a", + circleddash: "\u229d", + CircleDot: "\u2299", + circledR: "\xae", + circledS: "\u24c8", + CircleMinus: "\u2296", + CirclePlus: "\u2295", + CircleTimes: "\u2297", + cir: "\u25cb", + cirE: "\u29c3", + cire: "\u2257", + cirfnint: "\u2a10", + cirmid: "\u2aef", + cirscir: "\u29c2", + ClockwiseContourIntegral: "\u2232", + CloseCurlyDoubleQuote: "\u201d", + CloseCurlyQuote: "\u2019", + clubs: "\u2663", + clubsuit: "\u2663", + colon: ":", + Colon: "\u2237", + Colone: "\u2a74", + colone: "\u2254", + coloneq: "\u2254", + comma: ",", + commat: "@", + comp: "\u2201", + compfn: "\u2218", + complement: "\u2201", + complexes: "\u2102", + cong: "\u2245", + congdot: "\u2a6d", + Congruent: "\u2261", + conint: "\u222e", + Conint: "\u222f", + ContourIntegral: "\u222e", + copf: "\ud835\udd54", + Copf: "\u2102", + coprod: "\u2210", + Coproduct: "\u2210", + copy: "\xa9", + COPY: "\xa9", + copysr: "\u2117", + CounterClockwiseContourIntegral: "\u2233", + crarr: "\u21b5", + cross: "\u2717", + Cross: "\u2a2f", + Cscr: "\ud835\udc9e", + cscr: "\ud835\udcb8", + csub: "\u2acf", + csube: "\u2ad1", + csup: "\u2ad0", + csupe: "\u2ad2", + ctdot: "\u22ef", + cudarrl: "\u2938", + cudarrr: "\u2935", + cuepr: "\u22de", + cuesc: "\u22df", + cularr: "\u21b6", + cularrp: "\u293d", + cupbrcap: "\u2a48", + cupcap: "\u2a46", + CupCap: "\u224d", + cup: "\u222a", + Cup: "\u22d3", + cupcup: "\u2a4a", + cupdot: "\u228d", + cupor: "\u2a45", + cups: "\u222a\ufe00", + curarr: "\u21b7", + curarrm: "\u293c", + curlyeqprec: "\u22de", + curlyeqsucc: "\u22df", + curlyvee: "\u22ce", + curlywedge: "\u22cf", + curren: "\xa4", + curvearrowleft: "\u21b6", + curvearrowright: "\u21b7", + cuvee: "\u22ce", + cuwed: "\u22cf", + cwconint: "\u2232", + cwint: "\u2231", + cylcty: "\u232d", + dagger: "\u2020", + Dagger: "\u2021", + daleth: "\u2138", + darr: "\u2193", + Darr: "\u21a1", + dArr: "\u21d3", + dash: "\u2010", + Dashv: "\u2ae4", + dashv: "\u22a3", + dbkarow: "\u290f", + dblac: "\u02dd", + Dcaron: "\u010e", + dcaron: "\u010f", + Dcy: "\u0414", + dcy: "\u0434", + ddagger: "\u2021", + ddarr: "\u21ca", + DD: "\u2145", + dd: "\u2146", + DDotrahd: "\u2911", + ddotseq: "\u2a77", + deg: "\xb0", + Del: "\u2207", + Delta: "\u0394", + delta: "\u03b4", + demptyv: "\u29b1", + dfisht: "\u297f", + Dfr: "\ud835\udd07", + dfr: "\ud835\udd21", + dHar: "\u2965", + dharl: "\u21c3", + dharr: "\u21c2", + DiacriticalAcute: "\xb4", + DiacriticalDot: "\u02d9", + DiacriticalDoubleAcute: "\u02dd", + DiacriticalGrave: "`", + DiacriticalTilde: "\u02dc", + diam: "\u22c4", + diamond: "\u22c4", + Diamond: "\u22c4", + diamondsuit: "\u2666", + diams: "\u2666", + die: "\xa8", + DifferentialD: "\u2146", + digamma: "\u03dd", + disin: "\u22f2", + div: "\xf7", + divide: "\xf7", + divideontimes: "\u22c7", + divonx: "\u22c7", + DJcy: "\u0402", + djcy: "\u0452", + dlcorn: "\u231e", + dlcrop: "\u230d", + dollar: "$", + Dopf: "\ud835\udd3b", + dopf: "\ud835\udd55", + Dot: "\xa8", + dot: "\u02d9", + DotDot: "\u20dc", + doteq: "\u2250", + doteqdot: "\u2251", + DotEqual: "\u2250", + dotminus: "\u2238", + dotplus: "\u2214", + dotsquare: "\u22a1", + doublebarwedge: "\u2306", + DoubleContourIntegral: "\u222f", + DoubleDot: "\xa8", + DoubleDownArrow: "\u21d3", + DoubleLeftArrow: "\u21d0", + DoubleLeftRightArrow: "\u21d4", + DoubleLeftTee: "\u2ae4", + DoubleLongLeftArrow: "\u27f8", + DoubleLongLeftRightArrow: "\u27fa", + DoubleLongRightArrow: "\u27f9", + DoubleRightArrow: "\u21d2", + DoubleRightTee: "\u22a8", + DoubleUpArrow: "\u21d1", + DoubleUpDownArrow: "\u21d5", + DoubleVerticalBar: "\u2225", + DownArrowBar: "\u2913", + downarrow: "\u2193", + DownArrow: "\u2193", + Downarrow: "\u21d3", + DownArrowUpArrow: "\u21f5", + DownBreve: "\u0311", + downdownarrows: "\u21ca", + downharpoonleft: "\u21c3", + downharpoonright: "\u21c2", + DownLeftRightVector: "\u2950", + DownLeftTeeVector: "\u295e", + DownLeftVectorBar: "\u2956", + DownLeftVector: "\u21bd", + DownRightTeeVector: "\u295f", + DownRightVectorBar: "\u2957", + DownRightVector: "\u21c1", + DownTeeArrow: "\u21a7", + DownTee: "\u22a4", + drbkarow: "\u2910", + drcorn: "\u231f", + drcrop: "\u230c", + Dscr: "\ud835\udc9f", + dscr: "\ud835\udcb9", + DScy: "\u0405", + dscy: "\u0455", + dsol: "\u29f6", + Dstrok: "\u0110", + dstrok: "\u0111", + dtdot: "\u22f1", + dtri: "\u25bf", + dtrif: "\u25be", + duarr: "\u21f5", + duhar: "\u296f", + dwangle: "\u29a6", + DZcy: "\u040f", + dzcy: "\u045f", + dzigrarr: "\u27ff", + Eacute: "\xc9", + eacute: "\xe9", + easter: "\u2a6e", + Ecaron: "\u011a", + ecaron: "\u011b", + Ecirc: "\xca", + ecirc: "\xea", + ecir: "\u2256", + ecolon: "\u2255", + Ecy: "\u042d", + ecy: "\u044d", + eDDot: "\u2a77", + Edot: "\u0116", + edot: "\u0117", + eDot: "\u2251", + ee: "\u2147", + efDot: "\u2252", + Efr: "\ud835\udd08", + efr: "\ud835\udd22", + eg: "\u2a9a", + Egrave: "\xc8", + egrave: "\xe8", + egs: "\u2a96", + egsdot: "\u2a98", + el: "\u2a99", + Element: "\u2208", + elinters: "\u23e7", + ell: "\u2113", + els: "\u2a95", + elsdot: "\u2a97", + Emacr: "\u0112", + emacr: "\u0113", + empty: "\u2205", + emptyset: "\u2205", + EmptySmallSquare: "\u25fb", + emptyv: "\u2205", + EmptyVerySmallSquare: "\u25ab", + emsp13: "\u2004", + emsp14: "\u2005", + emsp: "\u2003", + ENG: "\u014a", + eng: "\u014b", + ensp: "\u2002", + Eogon: "\u0118", + eogon: "\u0119", + Eopf: "\ud835\udd3c", + eopf: "\ud835\udd56", + epar: "\u22d5", + eparsl: "\u29e3", + eplus: "\u2a71", + epsi: "\u03b5", + Epsilon: "\u0395", + epsilon: "\u03b5", + epsiv: "\u03f5", + eqcirc: "\u2256", + eqcolon: "\u2255", + eqsim: "\u2242", + eqslantgtr: "\u2a96", + eqslantless: "\u2a95", + Equal: "\u2a75", + equals: "=", + EqualTilde: "\u2242", + equest: "\u225f", + Equilibrium: "\u21cc", + equiv: "\u2261", + equivDD: "\u2a78", + eqvparsl: "\u29e5", + erarr: "\u2971", + erDot: "\u2253", + escr: "\u212f", + Escr: "\u2130", + esdot: "\u2250", + Esim: "\u2a73", + esim: "\u2242", + Eta: "\u0397", + eta: "\u03b7", + ETH: "\xd0", + eth: "\xf0", + Euml: "\xcb", + euml: "\xeb", + euro: "\u20ac", + excl: "!", + exist: "\u2203", + Exists: "\u2203", + expectation: "\u2130", + exponentiale: "\u2147", + ExponentialE: "\u2147", + fallingdotseq: "\u2252", + Fcy: "\u0424", + fcy: "\u0444", + female: "\u2640", + ffilig: "\ufb03", + fflig: "\ufb00", + ffllig: "\ufb04", + Ffr: "\ud835\udd09", + ffr: "\ud835\udd23", + filig: "\ufb01", + FilledSmallSquare: "\u25fc", + FilledVerySmallSquare: "\u25aa", + fjlig: "fj", + flat: "\u266d", + fllig: "\ufb02", + fltns: "\u25b1", + fnof: "\u0192", + Fopf: "\ud835\udd3d", + fopf: "\ud835\udd57", + forall: "\u2200", + ForAll: "\u2200", + fork: "\u22d4", + forkv: "\u2ad9", + Fouriertrf: "\u2131", + fpartint: "\u2a0d", + frac12: "\xbd", + frac13: "\u2153", + frac14: "\xbc", + frac15: "\u2155", + frac16: "\u2159", + frac18: "\u215b", + frac23: "\u2154", + frac25: "\u2156", + frac34: "\xbe", + frac35: "\u2157", + frac38: "\u215c", + frac45: "\u2158", + frac56: "\u215a", + frac58: "\u215d", + frac78: "\u215e", + frasl: "\u2044", + frown: "\u2322", + fscr: "\ud835\udcbb", + Fscr: "\u2131", + gacute: "\u01f5", + Gamma: "\u0393", + gamma: "\u03b3", + Gammad: "\u03dc", + gammad: "\u03dd", + gap: "\u2a86", + Gbreve: "\u011e", + gbreve: "\u011f", + Gcedil: "\u0122", + Gcirc: "\u011c", + gcirc: "\u011d", + Gcy: "\u0413", + gcy: "\u0433", + Gdot: "\u0120", + gdot: "\u0121", + ge: "\u2265", + gE: "\u2267", + gEl: "\u2a8c", + gel: "\u22db", + geq: "\u2265", + geqq: "\u2267", + geqslant: "\u2a7e", + gescc: "\u2aa9", + ges: "\u2a7e", + gesdot: "\u2a80", + gesdoto: "\u2a82", + gesdotol: "\u2a84", + gesl: "\u22db\ufe00", + gesles: "\u2a94", + Gfr: "\ud835\udd0a", + gfr: "\ud835\udd24", + gg: "\u226b", + Gg: "\u22d9", + ggg: "\u22d9", + gimel: "\u2137", + GJcy: "\u0403", + gjcy: "\u0453", + gla: "\u2aa5", + gl: "\u2277", + glE: "\u2a92", + glj: "\u2aa4", + gnap: "\u2a8a", + gnapprox: "\u2a8a", + gne: "\u2a88", + gnE: "\u2269", + gneq: "\u2a88", + gneqq: "\u2269", + gnsim: "\u22e7", + Gopf: "\ud835\udd3e", + gopf: "\ud835\udd58", + grave: "`", + GreaterEqual: "\u2265", + GreaterEqualLess: "\u22db", + GreaterFullEqual: "\u2267", + GreaterGreater: "\u2aa2", + GreaterLess: "\u2277", + GreaterSlantEqual: "\u2a7e", + GreaterTilde: "\u2273", + Gscr: "\ud835\udca2", + gscr: "\u210a", + gsim: "\u2273", + gsime: "\u2a8e", + gsiml: "\u2a90", + gtcc: "\u2aa7", + gtcir: "\u2a7a", + gt: ">", + GT: ">", + Gt: "\u226b", + gtdot: "\u22d7", + gtlPar: "\u2995", + gtquest: "\u2a7c", + gtrapprox: "\u2a86", + gtrarr: "\u2978", + gtrdot: "\u22d7", + gtreqless: "\u22db", + gtreqqless: "\u2a8c", + gtrless: "\u2277", + gtrsim: "\u2273", + gvertneqq: "\u2269\ufe00", + gvnE: "\u2269\ufe00", + Hacek: "\u02c7", + hairsp: "\u200a", + half: "\xbd", + hamilt: "\u210b", + HARDcy: "\u042a", + hardcy: "\u044a", + harrcir: "\u2948", + harr: "\u2194", + hArr: "\u21d4", + harrw: "\u21ad", + Hat: "^", + hbar: "\u210f", + Hcirc: "\u0124", + hcirc: "\u0125", + hearts: "\u2665", + heartsuit: "\u2665", + hellip: "\u2026", + hercon: "\u22b9", + hfr: "\ud835\udd25", + Hfr: "\u210c", + HilbertSpace: "\u210b", + hksearow: "\u2925", + hkswarow: "\u2926", + hoarr: "\u21ff", + homtht: "\u223b", + hookleftarrow: "\u21a9", + hookrightarrow: "\u21aa", + hopf: "\ud835\udd59", + Hopf: "\u210d", + horbar: "\u2015", + HorizontalLine: "\u2500", + hscr: "\ud835\udcbd", + Hscr: "\u210b", + hslash: "\u210f", + Hstrok: "\u0126", + hstrok: "\u0127", + HumpDownHump: "\u224e", + HumpEqual: "\u224f", + hybull: "\u2043", + hyphen: "\u2010", + Iacute: "\xcd", + iacute: "\xed", + ic: "\u2063", + Icirc: "\xce", + icirc: "\xee", + Icy: "\u0418", + icy: "\u0438", + Idot: "\u0130", + IEcy: "\u0415", + iecy: "\u0435", + iexcl: "\xa1", + iff: "\u21d4", + ifr: "\ud835\udd26", + Ifr: "\u2111", + Igrave: "\xcc", + igrave: "\xec", + ii: "\u2148", + iiiint: "\u2a0c", + iiint: "\u222d", + iinfin: "\u29dc", + iiota: "\u2129", + IJlig: "\u0132", + ijlig: "\u0133", + Imacr: "\u012a", + imacr: "\u012b", + image: "\u2111", + ImaginaryI: "\u2148", + imagline: "\u2110", + imagpart: "\u2111", + imath: "\u0131", + Im: "\u2111", + imof: "\u22b7", + imped: "\u01b5", + Implies: "\u21d2", + incare: "\u2105", + in: "\u2208", + infin: "\u221e", + infintie: "\u29dd", + inodot: "\u0131", + intcal: "\u22ba", + int: "\u222b", + Int: "\u222c", + integers: "\u2124", + Integral: "\u222b", + intercal: "\u22ba", + Intersection: "\u22c2", + intlarhk: "\u2a17", + intprod: "\u2a3c", + InvisibleComma: "\u2063", + InvisibleTimes: "\u2062", + IOcy: "\u0401", + iocy: "\u0451", + Iogon: "\u012e", + iogon: "\u012f", + Iopf: "\ud835\udd40", + iopf: "\ud835\udd5a", + Iota: "\u0399", + iota: "\u03b9", + iprod: "\u2a3c", + iquest: "\xbf", + iscr: "\ud835\udcbe", + Iscr: "\u2110", + isin: "\u2208", + isindot: "\u22f5", + isinE: "\u22f9", + isins: "\u22f4", + isinsv: "\u22f3", + isinv: "\u2208", + it: "\u2062", + Itilde: "\u0128", + itilde: "\u0129", + Iukcy: "\u0406", + iukcy: "\u0456", + Iuml: "\xcf", + iuml: "\xef", + Jcirc: "\u0134", + jcirc: "\u0135", + Jcy: "\u0419", + jcy: "\u0439", + Jfr: "\ud835\udd0d", + jfr: "\ud835\udd27", + jmath: "\u0237", + Jopf: "\ud835\udd41", + jopf: "\ud835\udd5b", + Jscr: "\ud835\udca5", + jscr: "\ud835\udcbf", + Jsercy: "\u0408", + jsercy: "\u0458", + Jukcy: "\u0404", + jukcy: "\u0454", + Kappa: "\u039a", + kappa: "\u03ba", + kappav: "\u03f0", + Kcedil: "\u0136", + kcedil: "\u0137", + Kcy: "\u041a", + kcy: "\u043a", + Kfr: "\ud835\udd0e", + kfr: "\ud835\udd28", + kgreen: "\u0138", + KHcy: "\u0425", + khcy: "\u0445", + KJcy: "\u040c", + kjcy: "\u045c", + Kopf: "\ud835\udd42", + kopf: "\ud835\udd5c", + Kscr: "\ud835\udca6", + kscr: "\ud835\udcc0", + lAarr: "\u21da", + Lacute: "\u0139", + lacute: "\u013a", + laemptyv: "\u29b4", + lagran: "\u2112", + Lambda: "\u039b", + lambda: "\u03bb", + lang: "\u27e8", + Lang: "\u27ea", + langd: "\u2991", + langle: "\u27e8", + lap: "\u2a85", + Laplacetrf: "\u2112", + laquo: "\xab", + larrb: "\u21e4", + larrbfs: "\u291f", + larr: "\u2190", + Larr: "\u219e", + lArr: "\u21d0", + larrfs: "\u291d", + larrhk: "\u21a9", + larrlp: "\u21ab", + larrpl: "\u2939", + larrsim: "\u2973", + larrtl: "\u21a2", + latail: "\u2919", + lAtail: "\u291b", + lat: "\u2aab", + late: "\u2aad", + lates: "\u2aad\ufe00", + lbarr: "\u290c", + lBarr: "\u290e", + lbbrk: "\u2772", + lbrace: "{", + lbrack: "[", + lbrke: "\u298b", + lbrksld: "\u298f", + lbrkslu: "\u298d", + Lcaron: "\u013d", + lcaron: "\u013e", + Lcedil: "\u013b", + lcedil: "\u013c", + lceil: "\u2308", + lcub: "{", + Lcy: "\u041b", + lcy: "\u043b", + ldca: "\u2936", + ldquo: "\u201c", + ldquor: "\u201e", + ldrdhar: "\u2967", + ldrushar: "\u294b", + ldsh: "\u21b2", + le: "\u2264", + lE: "\u2266", + LeftAngleBracket: "\u27e8", + LeftArrowBar: "\u21e4", + leftarrow: "\u2190", + LeftArrow: "\u2190", + Leftarrow: "\u21d0", + LeftArrowRightArrow: "\u21c6", + leftarrowtail: "\u21a2", + LeftCeiling: "\u2308", + LeftDoubleBracket: "\u27e6", + LeftDownTeeVector: "\u2961", + LeftDownVectorBar: "\u2959", + LeftDownVector: "\u21c3", + LeftFloor: "\u230a", + leftharpoondown: "\u21bd", + leftharpoonup: "\u21bc", + leftleftarrows: "\u21c7", + leftrightarrow: "\u2194", + LeftRightArrow: "\u2194", + Leftrightarrow: "\u21d4", + leftrightarrows: "\u21c6", + leftrightharpoons: "\u21cb", + leftrightsquigarrow: "\u21ad", + LeftRightVector: "\u294e", + LeftTeeArrow: "\u21a4", + LeftTee: "\u22a3", + LeftTeeVector: "\u295a", + leftthreetimes: "\u22cb", + LeftTriangleBar: "\u29cf", + LeftTriangle: "\u22b2", + LeftTriangleEqual: "\u22b4", + LeftUpDownVector: "\u2951", + LeftUpTeeVector: "\u2960", + LeftUpVectorBar: "\u2958", + LeftUpVector: "\u21bf", + LeftVectorBar: "\u2952", + LeftVector: "\u21bc", + lEg: "\u2a8b", + leg: "\u22da", + leq: "\u2264", + leqq: "\u2266", + leqslant: "\u2a7d", + lescc: "\u2aa8", + les: "\u2a7d", + lesdot: "\u2a7f", + lesdoto: "\u2a81", + lesdotor: "\u2a83", + lesg: "\u22da\ufe00", + lesges: "\u2a93", + lessapprox: "\u2a85", + lessdot: "\u22d6", + lesseqgtr: "\u22da", + lesseqqgtr: "\u2a8b", + LessEqualGreater: "\u22da", + LessFullEqual: "\u2266", + LessGreater: "\u2276", + lessgtr: "\u2276", + LessLess: "\u2aa1", + lesssim: "\u2272", + LessSlantEqual: "\u2a7d", + LessTilde: "\u2272", + lfisht: "\u297c", + lfloor: "\u230a", + Lfr: "\ud835\udd0f", + lfr: "\ud835\udd29", + lg: "\u2276", + lgE: "\u2a91", + lHar: "\u2962", + lhard: "\u21bd", + lharu: "\u21bc", + lharul: "\u296a", + lhblk: "\u2584", + LJcy: "\u0409", + ljcy: "\u0459", + llarr: "\u21c7", + ll: "\u226a", + Ll: "\u22d8", + llcorner: "\u231e", + Lleftarrow: "\u21da", + llhard: "\u296b", + lltri: "\u25fa", + Lmidot: "\u013f", + lmidot: "\u0140", + lmoustache: "\u23b0", + lmoust: "\u23b0", + lnap: "\u2a89", + lnapprox: "\u2a89", + lne: "\u2a87", + lnE: "\u2268", + lneq: "\u2a87", + lneqq: "\u2268", + lnsim: "\u22e6", + loang: "\u27ec", + loarr: "\u21fd", + lobrk: "\u27e6", + longleftarrow: "\u27f5", + LongLeftArrow: "\u27f5", + Longleftarrow: "\u27f8", + longleftrightarrow: "\u27f7", + LongLeftRightArrow: "\u27f7", + Longleftrightarrow: "\u27fa", + longmapsto: "\u27fc", + longrightarrow: "\u27f6", + LongRightArrow: "\u27f6", + Longrightarrow: "\u27f9", + looparrowleft: "\u21ab", + looparrowright: "\u21ac", + lopar: "\u2985", + Lopf: "\ud835\udd43", + lopf: "\ud835\udd5d", + loplus: "\u2a2d", + lotimes: "\u2a34", + lowast: "\u2217", + lowbar: "_", + LowerLeftArrow: "\u2199", + LowerRightArrow: "\u2198", + loz: "\u25ca", + lozenge: "\u25ca", + lozf: "\u29eb", + lpar: "(", + lparlt: "\u2993", + lrarr: "\u21c6", + lrcorner: "\u231f", + lrhar: "\u21cb", + lrhard: "\u296d", + lrm: "\u200e", + lrtri: "\u22bf", + lsaquo: "\u2039", + lscr: "\ud835\udcc1", + Lscr: "\u2112", + lsh: "\u21b0", + Lsh: "\u21b0", + lsim: "\u2272", + lsime: "\u2a8d", + lsimg: "\u2a8f", + lsqb: "[", + lsquo: "\u2018", + lsquor: "\u201a", + Lstrok: "\u0141", + lstrok: "\u0142", + ltcc: "\u2aa6", + ltcir: "\u2a79", + lt: "<", + LT: "<", + Lt: "\u226a", + ltdot: "\u22d6", + lthree: "\u22cb", + ltimes: "\u22c9", + ltlarr: "\u2976", + ltquest: "\u2a7b", + ltri: "\u25c3", + ltrie: "\u22b4", + ltrif: "\u25c2", + ltrPar: "\u2996", + lurdshar: "\u294a", + luruhar: "\u2966", + lvertneqq: "\u2268\ufe00", + lvnE: "\u2268\ufe00", + macr: "\xaf", + male: "\u2642", + malt: "\u2720", + maltese: "\u2720", + Map: "\u2905", + map: "\u21a6", + mapsto: "\u21a6", + mapstodown: "\u21a7", + mapstoleft: "\u21a4", + mapstoup: "\u21a5", + marker: "\u25ae", + mcomma: "\u2a29", + Mcy: "\u041c", + mcy: "\u043c", + mdash: "\u2014", + mDDot: "\u223a", + measuredangle: "\u2221", + MediumSpace: "\u205f", + Mellintrf: "\u2133", + Mfr: "\ud835\udd10", + mfr: "\ud835\udd2a", + mho: "\u2127", + micro: "\xb5", + midast: "*", + midcir: "\u2af0", + mid: "\u2223", + middot: "\xb7", + minusb: "\u229f", + minus: "\u2212", + minusd: "\u2238", + minusdu: "\u2a2a", + MinusPlus: "\u2213", + mlcp: "\u2adb", + mldr: "\u2026", + mnplus: "\u2213", + models: "\u22a7", + Mopf: "\ud835\udd44", + mopf: "\ud835\udd5e", + mp: "\u2213", + mscr: "\ud835\udcc2", + Mscr: "\u2133", + mstpos: "\u223e", + Mu: "\u039c", + mu: "\u03bc", + multimap: "\u22b8", + mumap: "\u22b8", + nabla: "\u2207", + Nacute: "\u0143", + nacute: "\u0144", + nang: "\u2220\u20d2", + nap: "\u2249", + napE: "\u2a70\u0338", + napid: "\u224b\u0338", + napos: "\u0149", + napprox: "\u2249", + natural: "\u266e", + naturals: "\u2115", + natur: "\u266e", + nbsp: "\xa0", + nbump: "\u224e\u0338", + nbumpe: "\u224f\u0338", + ncap: "\u2a43", + Ncaron: "\u0147", + ncaron: "\u0148", + Ncedil: "\u0145", + ncedil: "\u0146", + ncong: "\u2247", + ncongdot: "\u2a6d\u0338", + ncup: "\u2a42", + Ncy: "\u041d", + ncy: "\u043d", + ndash: "\u2013", + nearhk: "\u2924", + nearr: "\u2197", + neArr: "\u21d7", + nearrow: "\u2197", + ne: "\u2260", + nedot: "\u2250\u0338", + NegativeMediumSpace: "\u200b", + NegativeThickSpace: "\u200b", + NegativeThinSpace: "\u200b", + NegativeVeryThinSpace: "\u200b", + nequiv: "\u2262", + nesear: "\u2928", + nesim: "\u2242\u0338", + NestedGreaterGreater: "\u226b", + NestedLessLess: "\u226a", + NewLine: "\n", + nexist: "\u2204", + nexists: "\u2204", + Nfr: "\ud835\udd11", + nfr: "\ud835\udd2b", + ngE: "\u2267\u0338", + nge: "\u2271", + ngeq: "\u2271", + ngeqq: "\u2267\u0338", + ngeqslant: "\u2a7e\u0338", + nges: "\u2a7e\u0338", + nGg: "\u22d9\u0338", + ngsim: "\u2275", + nGt: "\u226b\u20d2", + ngt: "\u226f", + ngtr: "\u226f", + nGtv: "\u226b\u0338", + nharr: "\u21ae", + nhArr: "\u21ce", + nhpar: "\u2af2", + ni: "\u220b", + nis: "\u22fc", + nisd: "\u22fa", + niv: "\u220b", + NJcy: "\u040a", + njcy: "\u045a", + nlarr: "\u219a", + nlArr: "\u21cd", + nldr: "\u2025", + nlE: "\u2266\u0338", + nle: "\u2270", + nleftarrow: "\u219a", + nLeftarrow: "\u21cd", + nleftrightarrow: "\u21ae", + nLeftrightarrow: "\u21ce", + nleq: "\u2270", + nleqq: "\u2266\u0338", + nleqslant: "\u2a7d\u0338", + nles: "\u2a7d\u0338", + nless: "\u226e", + nLl: "\u22d8\u0338", + nlsim: "\u2274", + nLt: "\u226a\u20d2", + nlt: "\u226e", + nltri: "\u22ea", + nltrie: "\u22ec", + nLtv: "\u226a\u0338", + nmid: "\u2224", + NoBreak: "\u2060", + NonBreakingSpace: "\xa0", + nopf: "\ud835\udd5f", + Nopf: "\u2115", + Not: "\u2aec", + not: "\xac", + NotCongruent: "\u2262", + NotCupCap: "\u226d", + NotDoubleVerticalBar: "\u2226", + NotElement: "\u2209", + NotEqual: "\u2260", + NotEqualTilde: "\u2242\u0338", + NotExists: "\u2204", + NotGreater: "\u226f", + NotGreaterEqual: "\u2271", + NotGreaterFullEqual: "\u2267\u0338", + NotGreaterGreater: "\u226b\u0338", + NotGreaterLess: "\u2279", + NotGreaterSlantEqual: "\u2a7e\u0338", + NotGreaterTilde: "\u2275", + NotHumpDownHump: "\u224e\u0338", + NotHumpEqual: "\u224f\u0338", + notin: "\u2209", + notindot: "\u22f5\u0338", + notinE: "\u22f9\u0338", + notinva: "\u2209", + notinvb: "\u22f7", + notinvc: "\u22f6", + NotLeftTriangleBar: "\u29cf\u0338", + NotLeftTriangle: "\u22ea", + NotLeftTriangleEqual: "\u22ec", + NotLess: "\u226e", + NotLessEqual: "\u2270", + NotLessGreater: "\u2278", + NotLessLess: "\u226a\u0338", + NotLessSlantEqual: "\u2a7d\u0338", + NotLessTilde: "\u2274", + NotNestedGreaterGreater: "\u2aa2\u0338", + NotNestedLessLess: "\u2aa1\u0338", + notni: "\u220c", + notniva: "\u220c", + notnivb: "\u22fe", + notnivc: "\u22fd", + NotPrecedes: "\u2280", + NotPrecedesEqual: "\u2aaf\u0338", + NotPrecedesSlantEqual: "\u22e0", + NotReverseElement: "\u220c", + NotRightTriangleBar: "\u29d0\u0338", + NotRightTriangle: "\u22eb", + NotRightTriangleEqual: "\u22ed", + NotSquareSubset: "\u228f\u0338", + NotSquareSubsetEqual: "\u22e2", + NotSquareSuperset: "\u2290\u0338", + NotSquareSupersetEqual: "\u22e3", + NotSubset: "\u2282\u20d2", + NotSubsetEqual: "\u2288", + NotSucceeds: "\u2281", + NotSucceedsEqual: "\u2ab0\u0338", + NotSucceedsSlantEqual: "\u22e1", + NotSucceedsTilde: "\u227f\u0338", + NotSuperset: "\u2283\u20d2", + NotSupersetEqual: "\u2289", + NotTilde: "\u2241", + NotTildeEqual: "\u2244", + NotTildeFullEqual: "\u2247", + NotTildeTilde: "\u2249", + NotVerticalBar: "\u2224", + nparallel: "\u2226", + npar: "\u2226", + nparsl: "\u2afd\u20e5", + npart: "\u2202\u0338", + npolint: "\u2a14", + npr: "\u2280", + nprcue: "\u22e0", + nprec: "\u2280", + npreceq: "\u2aaf\u0338", + npre: "\u2aaf\u0338", + nrarrc: "\u2933\u0338", + nrarr: "\u219b", + nrArr: "\u21cf", + nrarrw: "\u219d\u0338", + nrightarrow: "\u219b", + nRightarrow: "\u21cf", + nrtri: "\u22eb", + nrtrie: "\u22ed", + nsc: "\u2281", + nsccue: "\u22e1", + nsce: "\u2ab0\u0338", + Nscr: "\ud835\udca9", + nscr: "\ud835\udcc3", + nshortmid: "\u2224", + nshortparallel: "\u2226", + nsim: "\u2241", + nsime: "\u2244", + nsimeq: "\u2244", + nsmid: "\u2224", + nspar: "\u2226", + nsqsube: "\u22e2", + nsqsupe: "\u22e3", + nsub: "\u2284", + nsubE: "\u2ac5\u0338", + nsube: "\u2288", + nsubset: "\u2282\u20d2", + nsubseteq: "\u2288", + nsubseteqq: "\u2ac5\u0338", + nsucc: "\u2281", + nsucceq: "\u2ab0\u0338", + nsup: "\u2285", + nsupE: "\u2ac6\u0338", + nsupe: "\u2289", + nsupset: "\u2283\u20d2", + nsupseteq: "\u2289", + nsupseteqq: "\u2ac6\u0338", + ntgl: "\u2279", + Ntilde: "\xd1", + ntilde: "\xf1", + ntlg: "\u2278", + ntriangleleft: "\u22ea", + ntrianglelefteq: "\u22ec", + ntriangleright: "\u22eb", + ntrianglerighteq: "\u22ed", + Nu: "\u039d", + nu: "\u03bd", + num: "#", + numero: "\u2116", + numsp: "\u2007", + nvap: "\u224d\u20d2", + nvdash: "\u22ac", + nvDash: "\u22ad", + nVdash: "\u22ae", + nVDash: "\u22af", + nvge: "\u2265\u20d2", + nvgt: ">\u20d2", + nvHarr: "\u2904", + nvinfin: "\u29de", + nvlArr: "\u2902", + nvle: "\u2264\u20d2", + nvlt: "<\u20d2", + nvltrie: "\u22b4\u20d2", + nvrArr: "\u2903", + nvrtrie: "\u22b5\u20d2", + nvsim: "\u223c\u20d2", + nwarhk: "\u2923", + nwarr: "\u2196", + nwArr: "\u21d6", + nwarrow: "\u2196", + nwnear: "\u2927", + Oacute: "\xd3", + oacute: "\xf3", + oast: "\u229b", + Ocirc: "\xd4", + ocirc: "\xf4", + ocir: "\u229a", + Ocy: "\u041e", + ocy: "\u043e", + odash: "\u229d", + Odblac: "\u0150", + odblac: "\u0151", + odiv: "\u2a38", + odot: "\u2299", + odsold: "\u29bc", + OElig: "\u0152", + oelig: "\u0153", + ofcir: "\u29bf", + Ofr: "\ud835\udd12", + ofr: "\ud835\udd2c", + ogon: "\u02db", + Ograve: "\xd2", + ograve: "\xf2", + ogt: "\u29c1", + ohbar: "\u29b5", + ohm: "\u03a9", + oint: "\u222e", + olarr: "\u21ba", + olcir: "\u29be", + olcross: "\u29bb", + oline: "\u203e", + olt: "\u29c0", + Omacr: "\u014c", + omacr: "\u014d", + Omega: "\u03a9", + omega: "\u03c9", + Omicron: "\u039f", + omicron: "\u03bf", + omid: "\u29b6", + ominus: "\u2296", + Oopf: "\ud835\udd46", + oopf: "\ud835\udd60", + opar: "\u29b7", + OpenCurlyDoubleQuote: "\u201c", + OpenCurlyQuote: "\u2018", + operp: "\u29b9", + oplus: "\u2295", + orarr: "\u21bb", + Or: "\u2a54", + or: "\u2228", + ord: "\u2a5d", + order: "\u2134", + orderof: "\u2134", + ordf: "\xaa", + ordm: "\xba", + origof: "\u22b6", + oror: "\u2a56", + orslope: "\u2a57", + orv: "\u2a5b", + oS: "\u24c8", + Oscr: "\ud835\udcaa", + oscr: "\u2134", + Oslash: "\xd8", + oslash: "\xf8", + osol: "\u2298", + Otilde: "\xd5", + otilde: "\xf5", + otimesas: "\u2a36", + Otimes: "\u2a37", + otimes: "\u2297", + Ouml: "\xd6", + ouml: "\xf6", + ovbar: "\u233d", + OverBar: "\u203e", + OverBrace: "\u23de", + OverBracket: "\u23b4", + OverParenthesis: "\u23dc", + para: "\xb6", + parallel: "\u2225", + par: "\u2225", + parsim: "\u2af3", + parsl: "\u2afd", + part: "\u2202", + PartialD: "\u2202", + Pcy: "\u041f", + pcy: "\u043f", + percnt: "%", + period: ".", + permil: "\u2030", + perp: "\u22a5", + pertenk: "\u2031", + Pfr: "\ud835\udd13", + pfr: "\ud835\udd2d", + Phi: "\u03a6", + phi: "\u03c6", + phiv: "\u03d5", + phmmat: "\u2133", + phone: "\u260e", + Pi: "\u03a0", + pi: "\u03c0", + pitchfork: "\u22d4", + piv: "\u03d6", + planck: "\u210f", + planckh: "\u210e", + plankv: "\u210f", + plusacir: "\u2a23", + plusb: "\u229e", + pluscir: "\u2a22", + plus: "+", + plusdo: "\u2214", + plusdu: "\u2a25", + pluse: "\u2a72", + PlusMinus: "\xb1", + plusmn: "\xb1", + plussim: "\u2a26", + plustwo: "\u2a27", + pm: "\xb1", + Poincareplane: "\u210c", + pointint: "\u2a15", + popf: "\ud835\udd61", + Popf: "\u2119", + pound: "\xa3", + prap: "\u2ab7", + Pr: "\u2abb", + pr: "\u227a", + prcue: "\u227c", + precapprox: "\u2ab7", + prec: "\u227a", + preccurlyeq: "\u227c", + Precedes: "\u227a", + PrecedesEqual: "\u2aaf", + PrecedesSlantEqual: "\u227c", + PrecedesTilde: "\u227e", + preceq: "\u2aaf", + precnapprox: "\u2ab9", + precneqq: "\u2ab5", + precnsim: "\u22e8", + pre: "\u2aaf", + prE: "\u2ab3", + precsim: "\u227e", + prime: "\u2032", + Prime: "\u2033", + primes: "\u2119", + prnap: "\u2ab9", + prnE: "\u2ab5", + prnsim: "\u22e8", + prod: "\u220f", + Product: "\u220f", + profalar: "\u232e", + profline: "\u2312", + profsurf: "\u2313", + prop: "\u221d", + Proportional: "\u221d", + Proportion: "\u2237", + propto: "\u221d", + prsim: "\u227e", + prurel: "\u22b0", + Pscr: "\ud835\udcab", + pscr: "\ud835\udcc5", + Psi: "\u03a8", + psi: "\u03c8", + puncsp: "\u2008", + Qfr: "\ud835\udd14", + qfr: "\ud835\udd2e", + qint: "\u2a0c", + qopf: "\ud835\udd62", + Qopf: "\u211a", + qprime: "\u2057", + Qscr: "\ud835\udcac", + qscr: "\ud835\udcc6", + quaternions: "\u210d", + quatint: "\u2a16", + quest: "?", + questeq: "\u225f", + quot: '"', + QUOT: '"', + rAarr: "\u21db", + race: "\u223d\u0331", + Racute: "\u0154", + racute: "\u0155", + radic: "\u221a", + raemptyv: "\u29b3", + rang: "\u27e9", + Rang: "\u27eb", + rangd: "\u2992", + range: "\u29a5", + rangle: "\u27e9", + raquo: "\xbb", + rarrap: "\u2975", + rarrb: "\u21e5", + rarrbfs: "\u2920", + rarrc: "\u2933", + rarr: "\u2192", + Rarr: "\u21a0", + rArr: "\u21d2", + rarrfs: "\u291e", + rarrhk: "\u21aa", + rarrlp: "\u21ac", + rarrpl: "\u2945", + rarrsim: "\u2974", + Rarrtl: "\u2916", + rarrtl: "\u21a3", + rarrw: "\u219d", + ratail: "\u291a", + rAtail: "\u291c", + ratio: "\u2236", + rationals: "\u211a", + rbarr: "\u290d", + rBarr: "\u290f", + RBarr: "\u2910", + rbbrk: "\u2773", + rbrace: "}", + rbrack: "]", + rbrke: "\u298c", + rbrksld: "\u298e", + rbrkslu: "\u2990", + Rcaron: "\u0158", + rcaron: "\u0159", + Rcedil: "\u0156", + rcedil: "\u0157", + rceil: "\u2309", + rcub: "}", + Rcy: "\u0420", + rcy: "\u0440", + rdca: "\u2937", + rdldhar: "\u2969", + rdquo: "\u201d", + rdquor: "\u201d", + rdsh: "\u21b3", + real: "\u211c", + realine: "\u211b", + realpart: "\u211c", + reals: "\u211d", + Re: "\u211c", + rect: "\u25ad", + reg: "\xae", + REG: "\xae", + ReverseElement: "\u220b", + ReverseEquilibrium: "\u21cb", + ReverseUpEquilibrium: "\u296f", + rfisht: "\u297d", + rfloor: "\u230b", + rfr: "\ud835\udd2f", + Rfr: "\u211c", + rHar: "\u2964", + rhard: "\u21c1", + rharu: "\u21c0", + rharul: "\u296c", + Rho: "\u03a1", + rho: "\u03c1", + rhov: "\u03f1", + RightAngleBracket: "\u27e9", + RightArrowBar: "\u21e5", + rightarrow: "\u2192", + RightArrow: "\u2192", + Rightarrow: "\u21d2", + RightArrowLeftArrow: "\u21c4", + rightarrowtail: "\u21a3", + RightCeiling: "\u2309", + RightDoubleBracket: "\u27e7", + RightDownTeeVector: "\u295d", + RightDownVectorBar: "\u2955", + RightDownVector: "\u21c2", + RightFloor: "\u230b", + rightharpoondown: "\u21c1", + rightharpoonup: "\u21c0", + rightleftarrows: "\u21c4", + rightleftharpoons: "\u21cc", + rightrightarrows: "\u21c9", + rightsquigarrow: "\u219d", + RightTeeArrow: "\u21a6", + RightTee: "\u22a2", + RightTeeVector: "\u295b", + rightthreetimes: "\u22cc", + RightTriangleBar: "\u29d0", + RightTriangle: "\u22b3", + RightTriangleEqual: "\u22b5", + RightUpDownVector: "\u294f", + RightUpTeeVector: "\u295c", + RightUpVectorBar: "\u2954", + RightUpVector: "\u21be", + RightVectorBar: "\u2953", + RightVector: "\u21c0", + ring: "\u02da", + risingdotseq: "\u2253", + rlarr: "\u21c4", + rlhar: "\u21cc", + rlm: "\u200f", + rmoustache: "\u23b1", + rmoust: "\u23b1", + rnmid: "\u2aee", + roang: "\u27ed", + roarr: "\u21fe", + robrk: "\u27e7", + ropar: "\u2986", + ropf: "\ud835\udd63", + Ropf: "\u211d", + roplus: "\u2a2e", + rotimes: "\u2a35", + RoundImplies: "\u2970", + rpar: ")", + rpargt: "\u2994", + rppolint: "\u2a12", + rrarr: "\u21c9", + Rrightarrow: "\u21db", + rsaquo: "\u203a", + rscr: "\ud835\udcc7", + Rscr: "\u211b", + rsh: "\u21b1", + Rsh: "\u21b1", + rsqb: "]", + rsquo: "\u2019", + rsquor: "\u2019", + rthree: "\u22cc", + rtimes: "\u22ca", + rtri: "\u25b9", + rtrie: "\u22b5", + rtrif: "\u25b8", + rtriltri: "\u29ce", + RuleDelayed: "\u29f4", + ruluhar: "\u2968", + rx: "\u211e", + Sacute: "\u015a", + sacute: "\u015b", + sbquo: "\u201a", + scap: "\u2ab8", + Scaron: "\u0160", + scaron: "\u0161", + Sc: "\u2abc", + sc: "\u227b", + sccue: "\u227d", + sce: "\u2ab0", + scE: "\u2ab4", + Scedil: "\u015e", + scedil: "\u015f", + Scirc: "\u015c", + scirc: "\u015d", + scnap: "\u2aba", + scnE: "\u2ab6", + scnsim: "\u22e9", + scpolint: "\u2a13", + scsim: "\u227f", + Scy: "\u0421", + scy: "\u0441", + sdotb: "\u22a1", + sdot: "\u22c5", + sdote: "\u2a66", + searhk: "\u2925", + searr: "\u2198", + seArr: "\u21d8", + searrow: "\u2198", + sect: "\xa7", + semi: ";", + seswar: "\u2929", + setminus: "\u2216", + setmn: "\u2216", + sext: "\u2736", + Sfr: "\ud835\udd16", + sfr: "\ud835\udd30", + sfrown: "\u2322", + sharp: "\u266f", + SHCHcy: "\u0429", + shchcy: "\u0449", + SHcy: "\u0428", + shcy: "\u0448", + ShortDownArrow: "\u2193", + ShortLeftArrow: "\u2190", + shortmid: "\u2223", + shortparallel: "\u2225", + ShortRightArrow: "\u2192", + ShortUpArrow: "\u2191", + shy: "\xad", + Sigma: "\u03a3", + sigma: "\u03c3", + sigmaf: "\u03c2", + sigmav: "\u03c2", + sim: "\u223c", + simdot: "\u2a6a", + sime: "\u2243", + simeq: "\u2243", + simg: "\u2a9e", + simgE: "\u2aa0", + siml: "\u2a9d", + simlE: "\u2a9f", + simne: "\u2246", + simplus: "\u2a24", + simrarr: "\u2972", + slarr: "\u2190", + SmallCircle: "\u2218", + smallsetminus: "\u2216", + smashp: "\u2a33", + smeparsl: "\u29e4", + smid: "\u2223", + smile: "\u2323", + smt: "\u2aaa", + smte: "\u2aac", + smtes: "\u2aac\ufe00", + SOFTcy: "\u042c", + softcy: "\u044c", + solbar: "\u233f", + solb: "\u29c4", + sol: "/", + Sopf: "\ud835\udd4a", + sopf: "\ud835\udd64", + spades: "\u2660", + spadesuit: "\u2660", + spar: "\u2225", + sqcap: "\u2293", + sqcaps: "\u2293\ufe00", + sqcup: "\u2294", + sqcups: "\u2294\ufe00", + Sqrt: "\u221a", + sqsub: "\u228f", + sqsube: "\u2291", + sqsubset: "\u228f", + sqsubseteq: "\u2291", + sqsup: "\u2290", + sqsupe: "\u2292", + sqsupset: "\u2290", + sqsupseteq: "\u2292", + square: "\u25a1", + Square: "\u25a1", + SquareIntersection: "\u2293", + SquareSubset: "\u228f", + SquareSubsetEqual: "\u2291", + SquareSuperset: "\u2290", + SquareSupersetEqual: "\u2292", + SquareUnion: "\u2294", + squarf: "\u25aa", + squ: "\u25a1", + squf: "\u25aa", + srarr: "\u2192", + Sscr: "\ud835\udcae", + sscr: "\ud835\udcc8", + ssetmn: "\u2216", + ssmile: "\u2323", + sstarf: "\u22c6", + Star: "\u22c6", + star: "\u2606", + starf: "\u2605", + straightepsilon: "\u03f5", + straightphi: "\u03d5", + strns: "\xaf", + sub: "\u2282", + Sub: "\u22d0", + subdot: "\u2abd", + subE: "\u2ac5", + sube: "\u2286", + subedot: "\u2ac3", + submult: "\u2ac1", + subnE: "\u2acb", + subne: "\u228a", + subplus: "\u2abf", + subrarr: "\u2979", + subset: "\u2282", + Subset: "\u22d0", + subseteq: "\u2286", + subseteqq: "\u2ac5", + SubsetEqual: "\u2286", + subsetneq: "\u228a", + subsetneqq: "\u2acb", + subsim: "\u2ac7", + subsub: "\u2ad5", + subsup: "\u2ad3", + succapprox: "\u2ab8", + succ: "\u227b", + succcurlyeq: "\u227d", + Succeeds: "\u227b", + SucceedsEqual: "\u2ab0", + SucceedsSlantEqual: "\u227d", + SucceedsTilde: "\u227f", + succeq: "\u2ab0", + succnapprox: "\u2aba", + succneqq: "\u2ab6", + succnsim: "\u22e9", + succsim: "\u227f", + SuchThat: "\u220b", + sum: "\u2211", + Sum: "\u2211", + sung: "\u266a", + sup1: "\xb9", + sup2: "\xb2", + sup3: "\xb3", + sup: "\u2283", + Sup: "\u22d1", + supdot: "\u2abe", + supdsub: "\u2ad8", + supE: "\u2ac6", + supe: "\u2287", + supedot: "\u2ac4", + Superset: "\u2283", + SupersetEqual: "\u2287", + suphsol: "\u27c9", + suphsub: "\u2ad7", + suplarr: "\u297b", + supmult: "\u2ac2", + supnE: "\u2acc", + supne: "\u228b", + supplus: "\u2ac0", + supset: "\u2283", + Supset: "\u22d1", + supseteq: "\u2287", + supseteqq: "\u2ac6", + supsetneq: "\u228b", + supsetneqq: "\u2acc", + supsim: "\u2ac8", + supsub: "\u2ad4", + supsup: "\u2ad6", + swarhk: "\u2926", + swarr: "\u2199", + swArr: "\u21d9", + swarrow: "\u2199", + swnwar: "\u292a", + szlig: "\xdf", + Tab: "\t", + target: "\u2316", + Tau: "\u03a4", + tau: "\u03c4", + tbrk: "\u23b4", + Tcaron: "\u0164", + tcaron: "\u0165", + Tcedil: "\u0162", + tcedil: "\u0163", + Tcy: "\u0422", + tcy: "\u0442", + tdot: "\u20db", + telrec: "\u2315", + Tfr: "\ud835\udd17", + tfr: "\ud835\udd31", + there4: "\u2234", + therefore: "\u2234", + Therefore: "\u2234", + Theta: "\u0398", + theta: "\u03b8", + thetasym: "\u03d1", + thetav: "\u03d1", + thickapprox: "\u2248", + thicksim: "\u223c", + ThickSpace: "\u205f\u200a", + ThinSpace: "\u2009", + thinsp: "\u2009", + thkap: "\u2248", + thksim: "\u223c", + THORN: "\xde", + thorn: "\xfe", + tilde: "\u02dc", + Tilde: "\u223c", + TildeEqual: "\u2243", + TildeFullEqual: "\u2245", + TildeTilde: "\u2248", + timesbar: "\u2a31", + timesb: "\u22a0", + times: "\xd7", + timesd: "\u2a30", + tint: "\u222d", + toea: "\u2928", + topbot: "\u2336", + topcir: "\u2af1", + top: "\u22a4", + Topf: "\ud835\udd4b", + topf: "\ud835\udd65", + topfork: "\u2ada", + tosa: "\u2929", + tprime: "\u2034", + trade: "\u2122", + TRADE: "\u2122", + triangle: "\u25b5", + triangledown: "\u25bf", + triangleleft: "\u25c3", + trianglelefteq: "\u22b4", + triangleq: "\u225c", + triangleright: "\u25b9", + trianglerighteq: "\u22b5", + tridot: "\u25ec", + trie: "\u225c", + triminus: "\u2a3a", + TripleDot: "\u20db", + triplus: "\u2a39", + trisb: "\u29cd", + tritime: "\u2a3b", + trpezium: "\u23e2", + Tscr: "\ud835\udcaf", + tscr: "\ud835\udcc9", + TScy: "\u0426", + tscy: "\u0446", + TSHcy: "\u040b", + tshcy: "\u045b", + Tstrok: "\u0166", + tstrok: "\u0167", + twixt: "\u226c", + twoheadleftarrow: "\u219e", + twoheadrightarrow: "\u21a0", + Uacute: "\xda", + uacute: "\xfa", + uarr: "\u2191", + Uarr: "\u219f", + uArr: "\u21d1", + Uarrocir: "\u2949", + Ubrcy: "\u040e", + ubrcy: "\u045e", + Ubreve: "\u016c", + ubreve: "\u016d", + Ucirc: "\xdb", + ucirc: "\xfb", + Ucy: "\u0423", + ucy: "\u0443", + udarr: "\u21c5", + Udblac: "\u0170", + udblac: "\u0171", + udhar: "\u296e", + ufisht: "\u297e", + Ufr: "\ud835\udd18", + ufr: "\ud835\udd32", + Ugrave: "\xd9", + ugrave: "\xf9", + uHar: "\u2963", + uharl: "\u21bf", + uharr: "\u21be", + uhblk: "\u2580", + ulcorn: "\u231c", + ulcorner: "\u231c", + ulcrop: "\u230f", + ultri: "\u25f8", + Umacr: "\u016a", + umacr: "\u016b", + uml: "\xa8", + UnderBar: "_", + UnderBrace: "\u23df", + UnderBracket: "\u23b5", + UnderParenthesis: "\u23dd", + Union: "\u22c3", + UnionPlus: "\u228e", + Uogon: "\u0172", + uogon: "\u0173", + Uopf: "\ud835\udd4c", + uopf: "\ud835\udd66", + UpArrowBar: "\u2912", + uparrow: "\u2191", + UpArrow: "\u2191", + Uparrow: "\u21d1", + UpArrowDownArrow: "\u21c5", + updownarrow: "\u2195", + UpDownArrow: "\u2195", + Updownarrow: "\u21d5", + UpEquilibrium: "\u296e", + upharpoonleft: "\u21bf", + upharpoonright: "\u21be", + uplus: "\u228e", + UpperLeftArrow: "\u2196", + UpperRightArrow: "\u2197", + upsi: "\u03c5", + Upsi: "\u03d2", + upsih: "\u03d2", + Upsilon: "\u03a5", + upsilon: "\u03c5", + UpTeeArrow: "\u21a5", + UpTee: "\u22a5", + upuparrows: "\u21c8", + urcorn: "\u231d", + urcorner: "\u231d", + urcrop: "\u230e", + Uring: "\u016e", + uring: "\u016f", + urtri: "\u25f9", + Uscr: "\ud835\udcb0", + uscr: "\ud835\udcca", + utdot: "\u22f0", + Utilde: "\u0168", + utilde: "\u0169", + utri: "\u25b5", + utrif: "\u25b4", + uuarr: "\u21c8", + Uuml: "\xdc", + uuml: "\xfc", + uwangle: "\u29a7", + vangrt: "\u299c", + varepsilon: "\u03f5", + varkappa: "\u03f0", + varnothing: "\u2205", + varphi: "\u03d5", + varpi: "\u03d6", + varpropto: "\u221d", + varr: "\u2195", + vArr: "\u21d5", + varrho: "\u03f1", + varsigma: "\u03c2", + varsubsetneq: "\u228a\ufe00", + varsubsetneqq: "\u2acb\ufe00", + varsupsetneq: "\u228b\ufe00", + varsupsetneqq: "\u2acc\ufe00", + vartheta: "\u03d1", + vartriangleleft: "\u22b2", + vartriangleright: "\u22b3", + vBar: "\u2ae8", + Vbar: "\u2aeb", + vBarv: "\u2ae9", + Vcy: "\u0412", + vcy: "\u0432", + vdash: "\u22a2", + vDash: "\u22a8", + Vdash: "\u22a9", + VDash: "\u22ab", + Vdashl: "\u2ae6", + veebar: "\u22bb", + vee: "\u2228", + Vee: "\u22c1", + veeeq: "\u225a", + vellip: "\u22ee", + verbar: "|", + Verbar: "\u2016", + vert: "|", + Vert: "\u2016", + VerticalBar: "\u2223", + VerticalLine: "|", + VerticalSeparator: "\u2758", + VerticalTilde: "\u2240", + VeryThinSpace: "\u200a", + Vfr: "\ud835\udd19", + vfr: "\ud835\udd33", + vltri: "\u22b2", + vnsub: "\u2282\u20d2", + vnsup: "\u2283\u20d2", + Vopf: "\ud835\udd4d", + vopf: "\ud835\udd67", + vprop: "\u221d", + vrtri: "\u22b3", + Vscr: "\ud835\udcb1", + vscr: "\ud835\udccb", + vsubnE: "\u2acb\ufe00", + vsubne: "\u228a\ufe00", + vsupnE: "\u2acc\ufe00", + vsupne: "\u228b\ufe00", + Vvdash: "\u22aa", + vzigzag: "\u299a", + Wcirc: "\u0174", + wcirc: "\u0175", + wedbar: "\u2a5f", + wedge: "\u2227", + Wedge: "\u22c0", + wedgeq: "\u2259", + weierp: "\u2118", + Wfr: "\ud835\udd1a", + wfr: "\ud835\udd34", + Wopf: "\ud835\udd4e", + wopf: "\ud835\udd68", + wp: "\u2118", + wr: "\u2240", + wreath: "\u2240", + Wscr: "\ud835\udcb2", + wscr: "\ud835\udccc", + xcap: "\u22c2", + xcirc: "\u25ef", + xcup: "\u22c3", + xdtri: "\u25bd", + Xfr: "\ud835\udd1b", + xfr: "\ud835\udd35", + xharr: "\u27f7", + xhArr: "\u27fa", + Xi: "\u039e", + xi: "\u03be", + xlarr: "\u27f5", + xlArr: "\u27f8", + xmap: "\u27fc", + xnis: "\u22fb", + xodot: "\u2a00", + Xopf: "\ud835\udd4f", + xopf: "\ud835\udd69", + xoplus: "\u2a01", + xotime: "\u2a02", + xrarr: "\u27f6", + xrArr: "\u27f9", + Xscr: "\ud835\udcb3", + xscr: "\ud835\udccd", + xsqcup: "\u2a06", + xuplus: "\u2a04", + xutri: "\u25b3", + xvee: "\u22c1", + xwedge: "\u22c0", + Yacute: "\xdd", + yacute: "\xfd", + YAcy: "\u042f", + yacy: "\u044f", + Ycirc: "\u0176", + ycirc: "\u0177", + Ycy: "\u042b", + ycy: "\u044b", + yen: "\xa5", + Yfr: "\ud835\udd1c", + yfr: "\ud835\udd36", + YIcy: "\u0407", + yicy: "\u0457", + Yopf: "\ud835\udd50", + yopf: "\ud835\udd6a", + Yscr: "\ud835\udcb4", + yscr: "\ud835\udcce", + YUcy: "\u042e", + yucy: "\u044e", + yuml: "\xff", + Yuml: "\u0178", + Zacute: "\u0179", + zacute: "\u017a", + Zcaron: "\u017d", + zcaron: "\u017e", + Zcy: "\u0417", + zcy: "\u0437", + Zdot: "\u017b", + zdot: "\u017c", + zeetrf: "\u2128", + ZeroWidthSpace: "\u200b", + Zeta: "\u0396", + zeta: "\u03b6", + zfr: "\ud835\udd37", + Zfr: "\u2128", + ZHcy: "\u0416", + zhcy: "\u0436", + zigrarr: "\u21dd", + zopf: "\ud835\udd6b", + Zopf: "\u2124", + Zscr: "\ud835\udcb5", + zscr: "\ud835\udccf", + zwj: "\u200d", + zwnj: "\u200c" + }; + /*eslint quotes:0*/ var entities = require$$0; + var regex$4 = /[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/; + var encodeCache = {}; + // Create a lookup array where anything but characters in `chars` string + // and alphanumeric chars is percent-encoded. + + function getEncodeCache(exclude) { + var i, ch, cache = encodeCache[exclude]; + if (cache) { + return cache; } + cache = encodeCache[exclude] = []; + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch); + } else { + cache.push("%" + ("0" + i.toString(16).toUpperCase()).slice(-2)); + } + } + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i]; + } + return cache; } - - return match; -} - -/*function replaceEntities(str) { - if (str.indexOf('&') < 0) { return str; } - - return str.replace(ENTITY_RE, replaceEntityPattern); -}*/ - -function unescapeMd(str) { - if (str.indexOf('\\') < 0) { return str; } - return str.replace(UNESCAPE_MD_RE, '$1'); -} - -function unescapeAll(str) { - if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } - - return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { - if (escaped) { return escaped; } - return replaceEntityPattern(match, entity); - }); -} - -//////////////////////////////////////////////////////////////////////////////// - -var HTML_ESCAPE_TEST_RE = /[&<>"]/; -var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; -var HTML_REPLACEMENTS = { - '&': '&', - '<': '<', - '>': '>', - '"': '"' -}; - -function replaceUnsafeChar(ch) { - return HTML_REPLACEMENTS[ch]; -} - -function escapeHtml(str) { - if (HTML_ESCAPE_TEST_RE.test(str)) { - return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + // Encode unsafe characters with percent-encoding, skipping already + // encoded sequences. + + // - string - string to encode + // - exclude - list of characters to ignore (in addition to a-zA-Z0-9) + // - keepEscaped - don't encode '%' in a correct escape sequence (default: true) + + function encode$2(string, exclude, keepEscaped) { + var i, l, code, nextCode, cache, result = ""; + if (typeof exclude !== "string") { + // encode(string, keepEscaped) + keepEscaped = exclude; + exclude = encode$2.defaultChars; + } + if (typeof keepEscaped === "undefined") { + keepEscaped = true; + } + cache = getEncodeCache(exclude); + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i); + if (keepEscaped && code === 37 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3); + i += 2; + continue; + } + } + if (code < 128) { + result += cache[code]; + continue; + } + if (code >= 55296 && code <= 57343) { + if (code >= 55296 && code <= 56319 && i + 1 < l) { + nextCode = string.charCodeAt(i + 1); + if (nextCode >= 56320 && nextCode <= 57343) { + result += encodeURIComponent(string[i] + string[i + 1]); + i++; + continue; + } + } + result += "%EF%BF%BD"; + continue; + } + result += encodeURIComponent(string[i]); + } + return result; } - return str; -} - -//////////////////////////////////////////////////////////////////////////////// - -var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; - -function escapeRE(str) { - return str.replace(REGEXP_ESCAPE_RE, '\\$&'); -} - -//////////////////////////////////////////////////////////////////////////////// - -function isSpace(code) { - switch (code) { - case 0x09: - case 0x20: + encode$2.defaultChars = ";/?:@&=+$,-_.!~*'()#"; + encode$2.componentChars = "-_.!~*'()"; + var encode_1 = encode$2; + /* eslint-disable no-bitwise */ var decodeCache = {}; + function getDecodeCache(exclude) { + var i, ch, cache = decodeCache[exclude]; + if (cache) { + return cache; + } + cache = decodeCache[exclude] = []; + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i); + cache.push(ch); + } + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i); + cache[ch] = "%" + ("0" + ch.toString(16).toUpperCase()).slice(-2); + } + return cache; + } + // Decode percent-encoded string. + + function decode$2(string, exclude) { + var cache; + if (typeof exclude !== "string") { + exclude = decode$2.defaultChars; + } + cache = getDecodeCache(exclude); + return string.replace(/(%[a-f0-9]{2})+/gi, (function(seq) { + var i, l, b1, b2, b3, b4, chr, result = ""; + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16); + if (b1 < 128) { + result += cache[b1]; + continue; + } + if ((b1 & 224) === 192 && i + 3 < l) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + if ((b2 & 192) === 128) { + chr = b1 << 6 & 1984 | b2 & 63; + if (chr < 128) { + result += "\ufffd\ufffd"; + } else { + result += String.fromCharCode(chr); + } + i += 3; + continue; + } + } + if ((b1 & 240) === 224 && i + 6 < l) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + if ((b2 & 192) === 128 && (b3 & 192) === 128) { + chr = b1 << 12 & 61440 | b2 << 6 & 4032 | b3 & 63; + if (chr < 2048 || chr >= 55296 && chr <= 57343) { + result += "\ufffd\ufffd\ufffd"; + } else { + result += String.fromCharCode(chr); + } + i += 6; + continue; + } + } + if ((b1 & 248) === 240 && i + 9 < l) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16); + b3 = parseInt(seq.slice(i + 7, i + 9), 16); + b4 = parseInt(seq.slice(i + 10, i + 12), 16); + if ((b2 & 192) === 128 && (b3 & 192) === 128 && (b4 & 192) === 128) { + chr = b1 << 18 & 1835008 | b2 << 12 & 258048 | b3 << 6 & 4032 | b4 & 63; + if (chr < 65536 || chr > 1114111) { + result += "\ufffd\ufffd\ufffd\ufffd"; + } else { + chr -= 65536; + result += String.fromCharCode(55296 + (chr >> 10), 56320 + (chr & 1023)); + } + i += 9; + continue; + } + } + result += "\ufffd"; + } + return result; + })); + } + decode$2.defaultChars = ";/?:@&=+$,#"; + decode$2.componentChars = ""; + var decode_1 = decode$2; + var format$1 = function format(url) { + var result = ""; + result += url.protocol || ""; + result += url.slashes ? "//" : ""; + result += url.auth ? url.auth + "@" : ""; + if (url.hostname && url.hostname.indexOf(":") !== -1) { + // ipv6 address + result += "[" + url.hostname + "]"; + } else { + result += url.hostname || ""; + } + result += url.port ? ":" + url.port : ""; + result += url.pathname || ""; + result += url.search || ""; + result += url.hash || ""; + return result; + }; + // Copyright Joyent, Inc. and other Node contributors. + + // Changes from joyent/node: + + // 1. No leading slash in paths, + // e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` + + // 2. Backslashes are not replaced with slashes, + // so `http:\\example.org\` is treated like a relative path + + // 3. Trailing colon is treated like a part of the path, + // i.e. in `http://example.org:foo` pathname is `:foo` + + // 4. Nothing is URL-encoded in the resulting object, + // (in joyent/node some chars in auth and paths are encoded) + + // 5. `url.parse()` does not have `parseQueryString` argument + + // 6. Removed extraneous result properties: `host`, `path`, `query`, etc., + // which can be constructed using other parts of the url. + + function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.pathname = null; + } + // Reference: RFC 3986, RFC 1808, RFC 2396 + // define these here so at least they only have to be + // compiled once on the first module load. + var protocolPattern = /^([a-z0-9.+-]+:)/i, portPattern = /:[0-9]*$/, + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = [ "<", ">", '"', "`", " ", "\r", "\n", "\t" ], + // RFC 2396: characters not allowed for various reasons. + unwise = [ "{", "}", "|", "\\", "^", "`" ].concat(delims), + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = [ "'" ].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = [ "%", "/", "?", ";", "#" ].concat(autoEscape), hostEndingChars = [ "/", "?", "#" ], hostnameMaxLen = 255, hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + javascript: true, + "javascript:": true + }, + // protocols that always contain a // bit. + slashedProtocol = { + http: true, + https: true, + ftp: true, + gopher: true, + file: true, + "http:": true, + "https:": true, + "ftp:": true, + "gopher:": true, + "file:": true + }; + /* eslint-enable no-script-url */ function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { + return url; + } + var u = new Url; + u.parse(url, slashesDenoteHost); + return u; + } + Url.prototype.parse = function(url, slashesDenoteHost) { + var i, l, lowerProto, hec, slashes, rest = url; + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + if (!slashesDenoteHost && url.split("#").length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + } + return this; + } + } + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = proto; + rest = rest.substr(proto.length); + } + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === "//"; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + if (!hostlessProtocol[proto] && (slashes || proto && !slashedProtocol[proto])) { + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf("@"); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf("@", hostEnd); + } + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = auth; + } + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec; + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length; + } + if (rest[hostEnd - 1] === ":") { + hostEnd--; + } + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + // pull out port. + this.parseHost(host); + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ""; + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === "[" && this.hostname[this.hostname.length - 1] === "]"; + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) { + continue; + } + if (!part.match(hostnamePartPattern)) { + var newpart = ""; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += "x"; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = notHost.join(".") + rest; + } + this.hostname = validParts.join("."); + break; + } + } + } + } + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ""; + } + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + } + } + // chop off from the tail first. + var hash = rest.indexOf("#"); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf("?"); + if (qm !== -1) { + this.search = rest.substr(qm); + rest = rest.slice(0, qm); + } + if (rest) { + this.pathname = rest; + } + if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { + this.pathname = ""; + } + return this; + }; + Url.prototype.parseHost = function(host) { + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ":") { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) { + this.hostname = host; + } + }; + var parse$1 = urlParse; + var encode$1 = encode_1; + var decode$1 = decode_1; + var format = format$1; + var parse = parse$1; + var mdurl = { + encode: encode$1, + decode: decode$1, + format: format, + parse: parse + }; + var regex$3 = /[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + var regex$2 = /[\0-\x1F\x7F-\x9F]/; + var regex$1 = /[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/; + var regex = /[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/; + var Any = regex$3; + var Cc = regex$2; + var Cf = regex$1; + var P = regex$4; + var Z = regex; + var uc_micro = { + Any: Any, + Cc: Cc, + Cf: Cf, + P: P, + Z: Z + }; + var utils = createCommonjsModule((function(module, exports) { + function _class(obj) { + return Object.prototype.toString.call(obj); + } + function isString(obj) { + return _class(obj) === "[object String]"; + } + var _hasOwnProperty = Object.prototype.hasOwnProperty; + function has(object, key) { + return _hasOwnProperty.call(object, key); + } + // Merge objects + + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + sources.forEach((function(source) { + if (!source) { + return; + } + if (typeof source !== "object") { + throw new TypeError(source + "must be object"); + } + Object.keys(source).forEach((function(key) { + obj[key] = source[key]; + })); + })); + return obj; + } + // Remove element from array and put another array at those position. + // Useful for some operations with tokens + function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); + } + //////////////////////////////////////////////////////////////////////////////// + function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 55296 && c <= 57343) { + return false; + } + // never used + if (c >= 64976 && c <= 65007) { + return false; + } + if ((c & 65535) === 65535 || (c & 65535) === 65534) { + return false; + } + // control codes + if (c >= 0 && c <= 8) { + return false; + } + if (c === 11) { + return false; + } + if (c >= 14 && c <= 31) { + return false; + } + if (c >= 127 && c <= 159) { + return false; + } + // out of range + if (c > 1114111) { + return false; + } return true; - } - return false; -} + } + function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 65535) { + c -= 65536; + var surrogate1 = 55296 + (c >> 10), surrogate2 = 56320 + (c & 1023); + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); + } + var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; + var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; + var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + "|" + ENTITY_RE.source, "gi"); + var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + function replaceEntityPattern(match, name) { + var code = 0; + if (has(entities, name)) { + return entities[name]; + } + if (name.charCodeAt(0) === 35 /* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === "x" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + return match; + } + /*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } -// Zs (unicode class) || [\t\f\v\r\n] -function isWhiteSpace(code) { - if (code >= 0x2000 && code <= 0x200A) { return true; } - switch (code) { - case 0x09: // \t - case 0x0A: // \n - case 0x0B: // \v - case 0x0C: // \f - case 0x0D: // \r - case 0x20: - case 0xA0: - case 0x1680: - case 0x202F: - case 0x205F: - case 0x3000: - return true; - } - return false; -} - -//////////////////////////////////////////////////////////////////////////////// - -/*eslint-disable max-len*/ -var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); - -// Currently without astral characters support. -function isPunctChar(ch) { - return UNICODE_PUNCT_RE.test(ch); -} - - -// Markdown ASCII punctuation characters. -// -// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ -// http://spec.commonmark.org/0.15/#ascii-punctuation-character -// -// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. -// -function isMdAsciiPunct(ch) { - switch (ch) { - case 0x21/* ! */: - case 0x22/* " */: - case 0x23/* # */: - case 0x24/* $ */: - case 0x25/* % */: - case 0x26/* & */: - case 0x27/* ' */: - case 0x28/* ( */: - case 0x29/* ) */: - case 0x2A/* * */: - case 0x2B/* + */: - case 0x2C/* , */: - case 0x2D/* - */: - case 0x2E/* . */: - case 0x2F/* / */: - case 0x3A/* : */: - case 0x3B/* ; */: - case 0x3C/* < */: - case 0x3D/* = */: - case 0x3E/* > */: - case 0x3F/* ? */: - case 0x40/* @ */: - case 0x5B/* [ */: - case 0x5C/* \ */: - case 0x5D/* ] */: - case 0x5E/* ^ */: - case 0x5F/* _ */: - case 0x60/* ` */: - case 0x7B/* { */: - case 0x7C/* | */: - case 0x7D/* } */: - case 0x7E/* ~ */: - return true; - default: + return str.replace(ENTITY_RE, replaceEntityPattern); + }*/ function unescapeMd(str) { + if (str.indexOf("\\") < 0) { + return str; + } + return str.replace(UNESCAPE_MD_RE, "$1"); + } + function unescapeAll(str) { + if (str.indexOf("\\") < 0 && str.indexOf("&") < 0) { + return str; + } + return str.replace(UNESCAPE_ALL_RE, (function(match, escaped, entity) { + if (escaped) { + return escaped; + } + return replaceEntityPattern(match, entity); + })); + } + //////////////////////////////////////////////////////////////////////////////// + var HTML_ESCAPE_TEST_RE = /[&<>"]/; + var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; + var HTML_REPLACEMENTS = { + "&": "&", + "<": "<", + ">": ">", + '"': """ + }; + function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; + } + function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; + } + //////////////////////////////////////////////////////////////////////////////// + var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, "\\$&"); + } + //////////////////////////////////////////////////////////////////////////////// + function isSpace(code) { + switch (code) { + case 9: + case 32: + return true; + } return false; - } -} + } + // Zs (unicode class) || [\t\f\v\r\n] + function isWhiteSpace(code) { + if (code >= 8192 && code <= 8202) { + return true; + } + switch (code) { + case 9: + // \t + case 10: + // \n + case 11: + // \v + case 12: + // \f + case 13: + // \r + case 32: + case 160: + case 5760: + case 8239: + case 8287: + case 12288: + return true; + } + return false; + } + //////////////////////////////////////////////////////////////////////////////// + /*eslint-disable max-len*/ + // Currently without astral characters support. + function isPunctChar(ch) { + return regex$4.test(ch); + } + // Markdown ASCII punctuation characters. + + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + + // Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. + + function isMdAsciiPunct(ch) { + switch (ch) { + case 33 /* ! */ : + case 34 /* " */ : + case 35 /* # */ : + case 36 /* $ */ : + case 37 /* % */ : + case 38 /* & */ : + case 39 /* ' */ : + case 40 /* ( */ : + case 41 /* ) */ : + case 42 /* * */ : + case 43 /* + */ : + case 44 /* , */ : + case 45 /* - */ : + case 46 /* . */ : + case 47 /* / */ : + case 58 /* : */ : + case 59 /* ; */ : + case 60 /* < */ : + case 61 /* = */ : + case 62 /* > */ : + case 63 /* ? */ : + case 64 /* @ */ : + case 91 /* [ */ : + case 92 /* \ */ : + case 93 /* ] */ : + case 94 /* ^ */ : + case 95 /* _ */ : + case 96 /* ` */ : + case 123 /* { */ : + case 124 /* | */ : + case 125 /* } */ : + case 126 /* ~ */ : + return true; -// Hepler to unify [reference labels]. -// -function normalizeReference(str) { - // Trim and collapse whitespace - // - str = str.trim().replace(/\s+/g, ' '); - - // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug - // fixed in v12 (couldn't find any details). - // - // So treat this one as a special case - // (remove this when node v10 is no longer supported). - // - if ('ẞ'.toLowerCase() === 'Ṿ') { - str = str.replace(/ẞ/g, 'ß'); - } - - // .toLowerCase().toUpperCase() should get rid of all differences - // between letter variants. - // - // Simple .toLowerCase() doesn't normalize 125 code points correctly, - // and .toUpperCase doesn't normalize 6 of them (list of exceptions: - // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently - // uppercased versions). - // - // Here's an example showing how it happens. Lets take greek letter omega: - // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) - // - // Unicode entries: - // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; - // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 - // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 - // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; - // - // Case-insensitive comparison should treat all of them as equivalent. - // - // But .toLowerCase() doesn't change ϑ (it's already lowercase), - // and .toUpperCase() doesn't change ϴ (already uppercase). - // - // Applying first lower then upper case normalizes any character: - // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' - // - // Note: this is equivalent to unicode case folding; unicode normalization - // is a different step that is not required here. - // - // Final result should be uppercased, because it's later stored in an object - // (this avoid a conflict with Object.prototype members, - // most notably, `__proto__`) - // - return str.toLowerCase().toUpperCase(); -} - -//////////////////////////////////////////////////////////////////////////////// - -// Re-export libraries commonly used in both markdown-it and its plugins, -// so plugins won't have to depend on them explicitly, which reduces their -// bundled size (e.g. a browser build). -// -exports.lib = {}; -exports.lib.mdurl = require('mdurl'); -exports.lib.ucmicro = require('uc.micro'); - -exports.assign = assign; -exports.isString = isString; -exports.has = has; -exports.unescapeMd = unescapeMd; -exports.unescapeAll = unescapeAll; -exports.isValidEntityCode = isValidEntityCode; -exports.fromCodePoint = fromCodePoint; -// exports.replaceEntities = replaceEntities; -exports.escapeHtml = escapeHtml; -exports.arrayReplaceAt = arrayReplaceAt; -exports.isSpace = isSpace; -exports.isWhiteSpace = isWhiteSpace; -exports.isMdAsciiPunct = isMdAsciiPunct; -exports.isPunctChar = isPunctChar; -exports.escapeRE = escapeRE; -exports.normalizeReference = normalizeReference; - -},{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ -// Just a shortcut for bulk export -'use strict'; - - -exports.parseLinkLabel = require('./parse_link_label'); -exports.parseLinkDestination = require('./parse_link_destination'); -exports.parseLinkTitle = require('./parse_link_title'); - -},{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ -// Parse link destination -// -'use strict'; - - -var unescapeAll = require('../common/utils').unescapeAll; - - -module.exports = function parseLinkDestination(str, pos, max) { - var code, level, - lines = 0, - start = pos, - result = { - ok: false, - pos: 0, - lines: 0, - str: '' - }; - - if (str.charCodeAt(pos) === 0x3C /* < */) { - pos++; + default: + return false; + } + } + // Hepler to unify [reference labels]. + + function normalizeReference(str) { + // Trim and collapse whitespace + str = str.trim().replace(/\s+/g, " "); + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + + if ("\u1e9e".toLowerCase() === "\u1e7e") { + str = str.replace(/\u1e9e/g, "\xdf"); + } + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + + // Case-insensitive comparison should treat all of them as equivalent. + + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + + return str.toLowerCase().toUpperCase(); + } + //////////////////////////////////////////////////////////////////////////////// + // Re-export libraries commonly used in both markdown-it and its plugins, + // so plugins won't have to depend on them explicitly, which reduces their + // bundled size (e.g. a browser build). + + exports.lib = {}; + exports.lib.mdurl = mdurl; + exports.lib.ucmicro = uc_micro; + exports.assign = assign; + exports.isString = isString; + exports.has = has; + exports.unescapeMd = unescapeMd; + exports.unescapeAll = unescapeAll; + exports.isValidEntityCode = isValidEntityCode; + exports.fromCodePoint = fromCodePoint; + // exports.replaceEntities = replaceEntities; + exports.escapeHtml = escapeHtml; + exports.arrayReplaceAt = arrayReplaceAt; + exports.isSpace = isSpace; + exports.isWhiteSpace = isWhiteSpace; + exports.isMdAsciiPunct = isMdAsciiPunct; + exports.isPunctChar = isPunctChar; + exports.escapeRE = escapeRE; + exports.normalizeReference = normalizeReference; + })); + // Parse link label + var parse_link_label = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, labelEnd = -1, max = state.posMax, oldPos = state.pos; + state.pos = start + 1; + level = 1; + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 93 /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 91 /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + if (found) { + labelEnd = state.pos; + } + // restore old state + state.pos = oldPos; + return labelEnd; + }; + var unescapeAll$2 = utils.unescapeAll; + var parse_link_destination = function parseLinkDestination(str, pos, max) { + var code, level, lines = 0, start = pos, result = { + ok: false, + pos: 0, + lines: 0, + str: "" + }; + if (str.charCodeAt(pos) === 60 /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 10 /* \n */) { + return result; + } + if (code === 60 /* < */) { + return result; + } + if (code === 62 /* > */) { + result.pos = pos + 1; + result.str = unescapeAll$2(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 92 /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + pos++; + } + // no closing '>' + return result; + } + // this should be ... } else { ... branch + level = 0; while (pos < max) { code = str.charCodeAt(pos); - if (code === 0x0A /* \n */) { return result; } - if (code === 0x3E /* > */) { - result.pos = pos + 1; - result.str = unescapeAll(str.slice(start + 1, pos)); - result.ok = true; - return result; + if (code === 32) { + break; } - if (code === 0x5C /* \ */ && pos + 1 < max) { + // ascii control characters + if (code < 32 || code === 127) { + break; + } + if (code === 92 /* \ */ && pos + 1 < max) { + if (str.charCodeAt(pos + 1) === 32) { + break; + } pos += 2; continue; } - - pos++; - } - - // no closing '>' - return result; - } - - // this should be ... } else { ... branch - - level = 0; - while (pos < max) { - code = str.charCodeAt(pos); - - if (code === 0x20) { break; } - - // ascii control characters - if (code < 0x20 || code === 0x7F) { break; } - - if (code === 0x5C /* \ */ && pos + 1 < max) { - pos += 2; - continue; - } - - if (code === 0x28 /* ( */) { - level++; - } - - if (code === 0x29 /* ) */) { - if (level === 0) { break; } - level--; - } - - pos++; - } - - if (start === pos) { return result; } - if (level !== 0) { return result; } - - result.str = unescapeAll(str.slice(start, pos)); - result.lines = lines; - result.pos = pos; - result.ok = true; - return result; -}; - -},{"../common/utils":4}],7:[function(require,module,exports){ -// Parse link label -// -// this function assumes that first character ("[") already matches; -// returns the end of the label -// -'use strict'; - -module.exports = function parseLinkLabel(state, start, disableNested) { - var level, found, marker, prevPos, - labelEnd = -1, - max = state.posMax, - oldPos = state.pos; - - state.pos = start + 1; - level = 1; - - while (state.pos < max) { - marker = state.src.charCodeAt(state.pos); - if (marker === 0x5D /* ] */) { - level--; - if (level === 0) { - found = true; - break; - } - } - - prevPos = state.pos; - state.md.inline.skipToken(state); - if (marker === 0x5B /* [ */) { - if (prevPos === state.pos - 1) { - // increase level if we find text `[`, which is not a part of any token + if (code === 40 /* ( */) { level++; - } else if (disableNested) { - state.pos = oldPos; - return -1; + if (level > 32) { + return result; + } + } + if (code === 41 /* ) */) { + if (level === 0) { + break; + } + level--; } - } - } - - if (found) { - labelEnd = state.pos; - } - - // restore old state - state.pos = oldPos; - - return labelEnd; -}; - -},{}],8:[function(require,module,exports){ -// Parse link title -// -'use strict'; - - -var unescapeAll = require('../common/utils').unescapeAll; - - -module.exports = function parseLinkTitle(str, pos, max) { - var code, - marker, - lines = 0, - start = pos, - result = { - ok: false, - pos: 0, - lines: 0, - str: '' - }; - - if (pos >= max) { return result; } - - marker = str.charCodeAt(pos); - - if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } - - pos++; - - // if opening marker is "(", switch it to closing marker ")" - if (marker === 0x28) { marker = 0x29; } - - while (pos < max) { - code = str.charCodeAt(pos); - if (code === marker) { - result.pos = pos + 1; - result.lines = lines; - result.str = unescapeAll(str.slice(start + 1, pos)); - result.ok = true; - return result; - } else if (code === 0x0A) { - lines++; - } else if (code === 0x5C /* \ */ && pos + 1 < max) { pos++; - if (str.charCodeAt(pos) === 0x0A) { - lines++; - } } - - pos++; - } - - return result; -}; - -},{"../common/utils":4}],9:[function(require,module,exports){ -// Main parser class - -'use strict'; - - -var utils = require('./common/utils'); -var helpers = require('./helpers'); -var Renderer = require('./renderer'); -var ParserCore = require('./parser_core'); -var ParserBlock = require('./parser_block'); -var ParserInline = require('./parser_inline'); -var LinkifyIt = require('linkify-it'); -var mdurl = require('mdurl'); -var punycode = require('punycode'); - - -var config = { - 'default': require('./presets/default'), - zero: require('./presets/zero'), - commonmark: require('./presets/commonmark') -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// This validator can prohibit more than really needed to prevent XSS. It's a -// tradeoff to keep code simple and to be secure by default. -// -// If you need different setup - override validator method as you wish. Or -// replace it with dummy function and use external sanitizer. -// - -var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; -var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; - -function validateLink(url) { - // url should be normalized at this point, and existing entities are decoded - var str = url.trim().toLowerCase(); - - return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; -} - -//////////////////////////////////////////////////////////////////////////////// - - -var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; - -function normalizeLink(url) { - var parsed = mdurl.parse(url, true); - - if (parsed.hostname) { - // Encode hostnames in urls like: - // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` - // - // We don't encode unknown schemas, because it's likely that we encode - // something we shouldn't (e.g. `skype:name` treated as `skype:host`) - // - if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { - try { - parsed.hostname = punycode.toASCII(parsed.hostname); - } catch (er) { /**/ } + if (start === pos) { + return result; } - } - - return mdurl.encode(mdurl.format(parsed)); -} - -function normalizeLinkText(url) { - var parsed = mdurl.parse(url, true); - - if (parsed.hostname) { - // Encode hostnames in urls like: - // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` - // - // We don't encode unknown schemas, because it's likely that we encode - // something we shouldn't (e.g. `skype:name` treated as `skype:host`) - // - if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { - try { - parsed.hostname = punycode.toUnicode(parsed.hostname); - } catch (er) { /**/ } + if (level !== 0) { + return result; } - } - - return mdurl.decode(mdurl.format(parsed)); -} - - -/** - * class MarkdownIt - * - * Main parser/renderer class. - * - * ##### Usage - * - * ```javascript - * // node.js, "classic" way: - * var MarkdownIt = require('markdown-it'), - * md = new MarkdownIt(); - * var result = md.render('# markdown-it rulezz!'); - * - * // node.js, the same, but with sugar: - * var md = require('markdown-it')(); - * var result = md.render('# markdown-it rulezz!'); - * - * // browser without AMD, added to "window" on script load - * // Note, there are no dash. - * var md = window.markdownit(); - * var result = md.render('# markdown-it rulezz!'); - * ``` - * - * Single line rendering, without paragraph wrap: - * - * ```javascript - * var md = require('markdown-it')(); - * var result = md.renderInline('__markdown-it__ rulezz!'); - * ``` - **/ - -/** - * new MarkdownIt([presetName, options]) - * - presetName (String): optional, `commonmark` / `zero` - * - options (Object) - * - * Creates parser instanse with given config. Can be called without `new`. - * - * ##### presetName - * - * MarkdownIt provides named presets as a convenience to quickly - * enable/disable active syntax rules and options for common use cases. - * - * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - - * configures parser to strict [CommonMark](http://commonmark.org/) mode. - * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - - * similar to GFM, used when no preset name given. Enables all available rules, - * but still without html, typographer & autolinker. - * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - - * all rules disabled. Useful to quickly setup your config via `.enable()`. - * For example, when you need only `bold` and `italic` markup and nothing else. - * - * ##### options: - * - * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! - * That's not safe! You may need external sanitizer to protect output from XSS. - * It's better to extend features via plugins, instead of enabling HTML. - * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags - * (`
`). This is needed only for full CommonMark compatibility. In real - * world you will need HTML output. - * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
`. - * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. - * Can be useful for external highlighters. - * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. - * - __typographer__ - `false`. Set `true` to enable [some language-neutral - * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + - * quotes beautification (smartquotes). - * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement - * pairs, when typographer enabled and smartquotes on. For example, you can - * use `'«»„“'` for Russian, `'„“‚‘'` for German, and - * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). - * - __highlight__ - `null`. Highlighter function for fenced code blocks. - * Highlighter `function (str, lang)` should return escaped HTML. It can also - * return empty string if the source was not changed and should be escaped - * externaly. If result starts with `): - * - * ```javascript - * var hljs = require('highlight.js') // https://highlightjs.org/ - * - * // Actual default values - * var md = require('markdown-it')({ - * highlight: function (str, lang) { - * if (lang && hljs.getLanguage(lang)) { - * try { - * return '
' +
- *                hljs.highlight(lang, str, true).value +
- *                '
'; - * } catch (__) {} - * } - * - * return '
' + md.utils.escapeHtml(str) + '
'; - * } - * }); - * ``` - * - **/ -function MarkdownIt(presetName, options) { - if (!(this instanceof MarkdownIt)) { - return new MarkdownIt(presetName, options); - } - - if (!options) { - if (!utils.isString(presetName)) { - options = presetName || {}; - presetName = 'default'; - } - } - - /** - * MarkdownIt#inline -> ParserInline - * - * Instance of [[ParserInline]]. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.inline = new ParserInline(); - - /** - * MarkdownIt#block -> ParserBlock - * - * Instance of [[ParserBlock]]. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.block = new ParserBlock(); - - /** - * MarkdownIt#core -> Core - * - * Instance of [[Core]] chain executor. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.core = new ParserCore(); - - /** - * MarkdownIt#renderer -> Renderer - * - * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering - * rules for new token types, generated by plugins. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * function myToken(tokens, idx, options, env, self) { - * //... - * return result; - * }; - * - * md.renderer.rules['my_token'] = myToken - * ``` - * - * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). - **/ - this.renderer = new Renderer(); - - /** - * MarkdownIt#linkify -> LinkifyIt - * - * [linkify-it](https://github.com/markdown-it/linkify-it) instance. - * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) - * rule. - **/ - this.linkify = new LinkifyIt(); - - /** - * MarkdownIt#validateLink(url) -> Boolean - * - * Link validation function. CommonMark allows too much in links. By default - * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas - * except some embedded image types. - * - * You can change this behaviour: - * - * ```javascript - * var md = require('markdown-it')(); - * // enable everything - * md.validateLink = function () { return true; } - * ``` - **/ - this.validateLink = validateLink; - - /** - * MarkdownIt#normalizeLink(url) -> String - * - * Function used to encode link url to a machine-readable format, - * which includes url-encoding, punycode, etc. - **/ - this.normalizeLink = normalizeLink; - - /** - * MarkdownIt#normalizeLinkText(url) -> String - * - * Function used to decode link url to a human-readable format` - **/ - this.normalizeLinkText = normalizeLinkText; - - - // Expose utils & helpers for easy acces from plugins - - /** - * MarkdownIt#utils -> utils - * - * Assorted utility functions, useful to write plugins. See details - * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). - **/ - this.utils = utils; - - /** - * MarkdownIt#helpers -> helpers - * - * Link components parser functions, useful to write plugins. See details - * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). - **/ - this.helpers = utils.assign({}, helpers); - - - this.options = {}; - this.configure(presetName); - - if (options) { this.set(options); } -} - - -/** chainable - * MarkdownIt.set(options) - * - * Set parser options (in the same format as in constructor). Probably, you - * will never need it, but you can change options after constructor call. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')() - * .set({ html: true, breaks: true }) - * .set({ typographer, true }); - * ``` - * - * __Note:__ To achieve the best possible performance, don't modify a - * `markdown-it` instance options on the fly. If you need multiple configurations - * it's best to create multiple instances and initialize each with separate - * config. - **/ -MarkdownIt.prototype.set = function (options) { - utils.assign(this.options, options); - return this; -}; - - -/** chainable, internal - * MarkdownIt.configure(presets) - * - * Batch load of all options and compenent settings. This is internal method, - * and you probably will not need it. But if you with - see available presets - * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) - * - * We strongly recommend to use presets instead of direct config loads. That - * will give better compatibility with next versions. - **/ -MarkdownIt.prototype.configure = function (presets) { - var self = this, presetName; - - if (utils.isString(presets)) { - presetName = presets; - presets = config[presetName]; - if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } - } - - if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } - - if (presets.options) { self.set(presets.options); } - - if (presets.components) { - Object.keys(presets.components).forEach(function (name) { - if (presets.components[name].rules) { - self[name].ruler.enableOnly(presets.components[name].rules); - } - if (presets.components[name].rules2) { - self[name].ruler2.enableOnly(presets.components[name].rules2); - } - }); - } - return this; -}; - - -/** chainable - * MarkdownIt.enable(list, ignoreInvalid) - * - list (String|Array): rule name or list of rule names to enable - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable list or rules. It will automatically find appropriate components, - * containing rules with given names. If rule not found, and `ignoreInvalid` - * not set - throws exception. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')() - * .enable(['sub', 'sup']) - * .disable('smartquotes'); - * ``` - **/ -MarkdownIt.prototype.enable = function (list, ignoreInvalid) { - var result = []; - - if (!Array.isArray(list)) { list = [ list ]; } - - [ 'core', 'block', 'inline' ].forEach(function (chain) { - result = result.concat(this[chain].ruler.enable(list, true)); - }, this); - - result = result.concat(this.inline.ruler2.enable(list, true)); - - var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); - - if (missed.length && !ignoreInvalid) { - throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); - } - - return this; -}; - - -/** chainable - * MarkdownIt.disable(list, ignoreInvalid) - * - list (String|Array): rule name or list of rule names to disable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * The same as [[MarkdownIt.enable]], but turn specified rules off. - **/ -MarkdownIt.prototype.disable = function (list, ignoreInvalid) { - var result = []; - - if (!Array.isArray(list)) { list = [ list ]; } - - [ 'core', 'block', 'inline' ].forEach(function (chain) { - result = result.concat(this[chain].ruler.disable(list, true)); - }, this); - - result = result.concat(this.inline.ruler2.disable(list, true)); - - var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); - - if (missed.length && !ignoreInvalid) { - throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); - } - return this; -}; - - -/** chainable - * MarkdownIt.use(plugin, params) - * - * Load specified plugin with given params into current parser instance. - * It's just a sugar to call `plugin(md, params)` with curring. - * - * ##### Example - * - * ```javascript - * var iterator = require('markdown-it-for-inline'); - * var md = require('markdown-it')() - * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { - * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); - * }); - * ``` - **/ -MarkdownIt.prototype.use = function (plugin /*, params, ... */) { - var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); - plugin.apply(plugin, args); - return this; -}; - - -/** internal - * MarkdownIt.parse(src, env) -> Array - * - src (String): source string - * - env (Object): environment sandbox - * - * Parse input string and returns list of block tokens (special token type - * "inline" will contain list of inline tokens). You should not call this - * method directly, until you write custom renderer (for example, to produce - * AST). - * - * `env` is used to pass data between "distributed" rules and return additional - * metadata like reference info, needed for the renderer. It also can be used to - * inject data in specific cases. Usually, you will be ok to pass `{}`, - * and then pass updated object to renderer. - **/ -MarkdownIt.prototype.parse = function (src, env) { - if (typeof src !== 'string') { - throw new Error('Input data should be a String'); - } - - var state = new this.core.State(src, this, env); - - this.core.process(state); - - return state.tokens; -}; - - -/** - * MarkdownIt.render(src [, env]) -> String - * - src (String): source string - * - env (Object): environment sandbox - * - * Render markdown string into html. It does all magic for you :). - * - * `env` can be used to inject additional metadata (`{}` by default). - * But you will not need it with high probability. See also comment - * in [[MarkdownIt.parse]]. - **/ -MarkdownIt.prototype.render = function (src, env) { - env = env || {}; - - return this.renderer.render(this.parse(src, env), this.options, env); -}; - - -/** internal - * MarkdownIt.parseInline(src, env) -> Array - * - src (String): source string - * - env (Object): environment sandbox - * - * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the - * block tokens list with the single `inline` element, containing parsed inline - * tokens in `children` property. Also updates `env` object. - **/ -MarkdownIt.prototype.parseInline = function (src, env) { - var state = new this.core.State(src, this, env); - - state.inlineMode = true; - this.core.process(state); - - return state.tokens; -}; - - -/** - * MarkdownIt.renderInline(src [, env]) -> String - * - src (String): source string - * - env (Object): environment sandbox - * - * Similar to [[MarkdownIt.render]] but for single paragraph content. Result - * will NOT be wrapped into `

` tags. - **/ -MarkdownIt.prototype.renderInline = function (src, env) { - env = env || {}; - - return this.renderer.render(this.parseInline(src, env), this.options, env); -}; - - -module.exports = MarkdownIt; - -},{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ -/** internal - * class ParserBlock - * - * Block-level tokenizer. - **/ -'use strict'; - - -var Ruler = require('./ruler'); - - -var _rules = [ - // First 2 params - rule name & source. Secondary array - list of rules, - // which can be terminated by this one. - [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], - [ 'code', require('./rules_block/code') ], - [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'reference', require('./rules_block/reference') ], - [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'lheading', require('./rules_block/lheading') ], - [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'paragraph', require('./rules_block/paragraph') ] -]; - - -/** - * new ParserBlock() - **/ -function ParserBlock() { - /** - * ParserBlock#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of block rules. - **/ - this.ruler = new Ruler(); - - for (var i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); - } -} - - -// Generate tokens for input range -// -ParserBlock.prototype.tokenize = function (state, startLine, endLine) { - var ok, i, - rules = this.ruler.getRules(''), - len = rules.length, - line = startLine, - hasEmptyLines = false, - maxNesting = state.md.options.maxNesting; - - while (line < endLine) { - state.line = line = state.skipEmptyLines(line); - if (line >= endLine) { break; } - - // Termination condition for nested calls. - // Nested calls currently used for blockquotes & lists - if (state.sCount[line] < state.blkIndent) { break; } - - // If nesting level exceeded - skip tail to the end. That's not ordinary - // situation and we should not care about content. - if (state.level >= maxNesting) { - state.line = endLine; - break; - } - - // Try all possible rules. - // On success, rule should: - // - // - update `state.line` - // - update `state.tokens` - // - return true - - for (i = 0; i < len; i++) { - ok = rules[i](state, line, endLine, false); - if (ok) { break; } - } - - // set state.tight if we had an empty line before current tag - // i.e. latest empty line should not count - state.tight = !hasEmptyLines; - - // paragraph might "eat" one newline after it in nested lists - if (state.isEmpty(state.line - 1)) { - hasEmptyLines = true; - } - - line = state.line; - - if (line < endLine && state.isEmpty(line)) { - hasEmptyLines = true; - line++; - state.line = line; - } - } -}; - - -/** - * ParserBlock.parse(str, md, env, outTokens) - * - * Process input string and push block tokens into `outTokens` - **/ -ParserBlock.prototype.parse = function (src, md, env, outTokens) { - var state; - - if (!src) { return; } - - state = new this.State(src, md, env, outTokens); - - this.tokenize(state, state.line, state.lineMax); -}; - - -ParserBlock.prototype.State = require('./rules_block/state_block'); - - -module.exports = ParserBlock; - -},{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ -/** internal - * class Core - * - * Top-level rules executor. Glues block/inline parsers and does intermediate - * transformations. - **/ -'use strict'; - - -var Ruler = require('./ruler'); - - -var _rules = [ - [ 'normalize', require('./rules_core/normalize') ], - [ 'block', require('./rules_core/block') ], - [ 'inline', require('./rules_core/inline') ], - [ 'linkify', require('./rules_core/linkify') ], - [ 'replacements', require('./rules_core/replacements') ], - [ 'smartquotes', require('./rules_core/smartquotes') ] -]; - - -/** - * new Core() - **/ -function Core() { - /** - * Core#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of core rules. - **/ - this.ruler = new Ruler(); - - for (var i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1]); - } -} - - -/** - * Core.process(state) - * - * Executes core chain rules. - **/ -Core.prototype.process = function (state) { - var i, l, rules; - - rules = this.ruler.getRules(''); - - for (i = 0, l = rules.length; i < l; i++) { - rules[i](state); - } -}; - -Core.prototype.State = require('./rules_core/state_core'); - - -module.exports = Core; - -},{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ -/** internal - * class ParserInline - * - * Tokenizes paragraph content. - **/ -'use strict'; - - -var Ruler = require('./ruler'); - - -//////////////////////////////////////////////////////////////////////////////// -// Parser rules - -var _rules = [ - [ 'text', require('./rules_inline/text') ], - [ 'newline', require('./rules_inline/newline') ], - [ 'escape', require('./rules_inline/escape') ], - [ 'backticks', require('./rules_inline/backticks') ], - [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], - [ 'emphasis', require('./rules_inline/emphasis').tokenize ], - [ 'link', require('./rules_inline/link') ], - [ 'image', require('./rules_inline/image') ], - [ 'autolink', require('./rules_inline/autolink') ], - [ 'html_inline', require('./rules_inline/html_inline') ], - [ 'entity', require('./rules_inline/entity') ] -]; - -var _rules2 = [ - [ 'balance_pairs', require('./rules_inline/balance_pairs') ], - [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], - [ 'emphasis', require('./rules_inline/emphasis').postProcess ], - [ 'text_collapse', require('./rules_inline/text_collapse') ] -]; - - -/** - * new ParserInline() - **/ -function ParserInline() { - var i; - - /** - * ParserInline#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of inline rules. - **/ - this.ruler = new Ruler(); - - for (i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1]); - } - - /** - * ParserInline#ruler2 -> Ruler - * - * [[Ruler]] instance. Second ruler used for post-processing - * (e.g. in emphasis-like rules). - **/ - this.ruler2 = new Ruler(); - - for (i = 0; i < _rules2.length; i++) { - this.ruler2.push(_rules2[i][0], _rules2[i][1]); - } -} - - -// Skip single token by running all rules in validation mode; -// returns `true` if any rule reported success -// -ParserInline.prototype.skipToken = function (state) { - var ok, i, pos = state.pos, - rules = this.ruler.getRules(''), - len = rules.length, - maxNesting = state.md.options.maxNesting, - cache = state.cache; - - - if (typeof cache[pos] !== 'undefined') { - state.pos = cache[pos]; - return; - } - - if (state.level < maxNesting) { - for (i = 0; i < len; i++) { - // Increment state.level and decrement it later to limit recursion. - // It's harmless to do here, because no tokens are created. But ideally, - // we'd need a separate private state variable for this purpose. - // - state.level++; - ok = rules[i](state, true); - state.level--; - - if (ok) { break; } - } - } else { - // Too much nesting, just skip until the end of the paragraph. - // - // NOTE: this will cause links to behave incorrectly in the following case, - // when an amount of `[` is exactly equal to `maxNesting + 1`: - // - // [[[[[[[[[[[[[[[[[[[[[foo]() - // - // TODO: remove this workaround when CM standard will allow nested links - // (we can replace it by preventing links from being parsed in - // validation mode) - // - state.pos = state.posMax; - } - - if (!ok) { state.pos++; } - cache[pos] = state.pos; -}; - - -// Generate tokens for input range -// -ParserInline.prototype.tokenize = function (state) { - var ok, i, - rules = this.ruler.getRules(''), - len = rules.length, - end = state.posMax, - maxNesting = state.md.options.maxNesting; - - while (state.pos < end) { - // Try all possible rules. - // On success, rule should: - // - // - update `state.pos` - // - update `state.tokens` - // - return true - - if (state.level < maxNesting) { - for (i = 0; i < len; i++) { - ok = rules[i](state, false); - if (ok) { break; } - } - } - - if (ok) { - if (state.pos >= end) { break; } - continue; - } - - state.pending += state.src[state.pos++]; - } - - if (state.pending) { - state.pushPending(); - } -}; - - -/** - * ParserInline.parse(str, md, env, outTokens) - * - * Process input string and push inline tokens into `outTokens` - **/ -ParserInline.prototype.parse = function (str, md, env, outTokens) { - var i, rules, len; - var state = new this.State(str, md, env, outTokens); - - this.tokenize(state); - - rules = this.ruler2.getRules(''); - len = rules.length; - - for (i = 0; i < len; i++) { - rules[i](state); - } -}; - - -ParserInline.prototype.State = require('./rules_inline/state_inline'); - - -module.exports = ParserInline; - -},{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ -// Commonmark default options - -'use strict'; - - -module.exports = { - options: { - html: true, // Enable HTML tags in source - xhtmlOut: true, // Use '/' to close single tags (
) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links - - // Enable some language-neutral replacements + quotes beautification - typographer: false, - - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ - - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with ) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links - - // Enable some language-neutral replacements + quotes beautification - typographer: false, - - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ - - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with ) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links - - // Enable some language-neutral replacements + quotes beautification - typographer: false, - - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ - - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with ' + - escapeHtml(tokens[idx].content) + - ''; -}; - - -default_rules.code_block = function (tokens, idx, options, env, slf) { - var token = tokens[idx]; - - return '' + - escapeHtml(tokens[idx].content) + - '\n'; -}; - - -default_rules.fence = function (tokens, idx, options, env, slf) { - var token = tokens[idx], - info = token.info ? unescapeAll(token.info).trim() : '', - langName = '', - highlighted, i, tmpAttrs, tmpToken; - - if (info) { - langName = info.split(/\s+/g)[0]; - } - - if (options.highlight) { - highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); - } else { - highlighted = escapeHtml(token.content); - } - - if (highlighted.indexOf('' - + highlighted - + '\n'; - } - - - return '

'
-        + highlighted
-        + '
\n'; -}; - - -default_rules.image = function (tokens, idx, options, env, slf) { - var token = tokens[idx]; - - // "alt" attr MUST be set, even if empty. Because it's mandatory and - // should be placed on proper position for tests. - // - // Replace content with actual value - - token.attrs[token.attrIndex('alt')][1] = - slf.renderInlineAsText(token.children, options, env); - - return slf.renderToken(tokens, idx, options); -}; - - -default_rules.hardbreak = function (tokens, idx, options /*, env */) { - return options.xhtmlOut ? '
\n' : '
\n'; -}; -default_rules.softbreak = function (tokens, idx, options /*, env */) { - return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n'; -}; - - -default_rules.text = function (tokens, idx /*, options, env */) { - return escapeHtml(tokens[idx].content); -}; - - -default_rules.html_block = function (tokens, idx /*, options, env */) { - return tokens[idx].content; -}; -default_rules.html_inline = function (tokens, idx /*, options, env */) { - return tokens[idx].content; -}; - - -/** - * new Renderer() - * - * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. - **/ -function Renderer() { - + if (pos >= max) { + return result; + } + marker = str.charCodeAt(pos); + if (marker !== 34 /* " */ && marker !== 39 /* ' */ && marker !== 40 /* ( */) { + return result; + } + pos++; + // if opening marker is "(", switch it to closing marker ")" + if (marker === 40) { + marker = 41; + } + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll$1(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 40 /* ( */ && marker === 41 /* ) */) { + return result; + } else if (code === 10) { + lines++; + } else if (code === 92 /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 10) { + lines++; + } + } + pos++; + } + return result; + }; + var parseLinkLabel = parse_link_label; + var parseLinkDestination = parse_link_destination; + var parseLinkTitle = parse_link_title; + var helpers = { + parseLinkLabel: parseLinkLabel, + parseLinkDestination: parseLinkDestination, + parseLinkTitle: parseLinkTitle + }; + var assign$1 = utils.assign; + var unescapeAll = utils.unescapeAll; + var escapeHtml = utils.escapeHtml; + //////////////////////////////////////////////////////////////////////////////// + var default_rules = {}; + default_rules.code_inline = function(tokens, idx, options, env, slf) { + var token = tokens[idx]; + return "" + escapeHtml(tokens[idx].content) + ""; + }; + default_rules.code_block = function(tokens, idx, options, env, slf) { + var token = tokens[idx]; + return "" + escapeHtml(tokens[idx].content) + "\n"; + }; + default_rules.fence = function(tokens, idx, options, env, slf) { + var token = tokens[idx], info = token.info ? unescapeAll(token.info).trim() : "", langName = "", langAttrs = "", highlighted, i, arr, tmpAttrs, tmpToken; + if (info) { + arr = info.split(/(\s+)/g); + langName = arr[0]; + langAttrs = arr.slice(2).join(""); + } + if (options.highlight) { + highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + if (highlighted.indexOf("" + highlighted + "\n"; + } + return "
" + highlighted + "
\n"; + }; + default_rules.image = function(tokens, idx, options, env, slf) { + var token = tokens[idx]; + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + + // Replace content with actual value + token.attrs[token.attrIndex("alt")][1] = slf.renderInlineAsText(token.children, options, env); + return slf.renderToken(tokens, idx, options); + }; + default_rules.hardbreak = function(tokens, idx, options /*, env */) { + return options.xhtmlOut ? "
\n" : "
\n"; + }; + default_rules.softbreak = function(tokens, idx, options /*, env */) { + return options.breaks ? options.xhtmlOut ? "
\n" : "
\n" : "\n"; + }; + default_rules.text = function(tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); + }; + default_rules.html_block = function(tokens, idx /*, options, env */) { + return tokens[idx].content; + }; + default_rules.html_inline = function(tokens, idx /*, options, env */) { + return tokens[idx].content; + }; /** - * Renderer#rules -> Object - * - * Contains render rules for tokens. Can be updated and extended. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.renderer.rules.strong_open = function () { return ''; }; - * md.renderer.rules.strong_close = function () { return ''; }; - * - * var result = md.renderInline(...); - * ``` - * - * Each rule is called as independent static function with fixed signature: - * - * ```javascript - * function my_token_render(tokens, idx, options, env, renderer) { - * // ... - * return renderedHTML; - * } - * ``` - * - * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) - * for more details and examples. - **/ - this.rules = assign({}, default_rules); -} - - -/** - * Renderer.renderAttrs(token) -> String - * - * Render token attributes to string. - **/ -Renderer.prototype.renderAttrs = function renderAttrs(token) { - var i, l, result; - - if (!token.attrs) { return ''; } - - result = ''; - - for (i = 0, l = token.attrs.length; i < l; i++) { - result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ function Renderer() { + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign$1({}, default_rules); } - - return result; -}; - - -/** - * Renderer.renderToken(tokens, idx, options) -> String - * - tokens (Array): list of tokens - * - idx (Numbed): token index to render - * - options (Object): params of parser instance - * - * Default token renderer. Can be overriden by custom function - * in [[Renderer#rules]]. - **/ -Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { - var nextToken, - result = '', - needLf = false, - token = tokens[idx]; - - // Tight list paragraphs - if (token.hidden) { - return ''; + /** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + if (!token.attrs) { + return ""; + } + result = ""; + for (i = 0, l = token.attrs.length; i < l; i++) { + result += " " + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + return result; + }; + /** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, result = "", needLf = false, token = tokens[idx]; + // Tight list paragraphs + if (token.hidden) { + return ""; + } + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + + // For example, here we should insert a newline before blockquote: + // - a + // > + + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += "\n"; + } + // Add token name, e.g. ``. + needLf = false; + } + } + } + } + result += needLf ? ">\n" : ">"; + return result; + }; + /** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ Renderer.prototype.renderInline = function(tokens, options, env) { + var type, result = "", rules = this.rules; + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + if (typeof rules[type] !== "undefined") { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + return result; + }; + /** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ Renderer.prototype.renderInlineAsText = function(tokens, options, env) { + var result = ""; + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === "text") { + result += tokens[i].content; + } else if (tokens[i].type === "image") { + result += this.renderInlineAsText(tokens[i].children, options, env); + } else if (tokens[i].type === "softbreak") { + result += "\n"; + } + } + return result; + }; + /** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ Renderer.prototype.render = function(tokens, options, env) { + var i, len, type, result = "", rules = this.rules; + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + if (type === "inline") { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== "undefined") { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + return result; + }; + var renderer = Renderer; + /** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ + /** + * new Ruler() + **/ function Ruler() { + // List of added rules. Each element is: + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + this.__rules__ = []; + // Cached rule chains. + + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + + this.__cache__ = null; } - - // Insert a newline between hidden paragraph and subsequent opening - // block-level tag. - // - // For example, here we should insert a newline before blockquote: - // - a - // > - // - if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { - result += '\n'; + //////////////////////////////////////////////////////////////////////////////// + // Helper methods, should not be used directly + // Find rule index by name + + Ruler.prototype.__find__ = function(name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; + }; + // Build rules lookup cache + + Ruler.prototype.__compile__ = function() { + var self = this; + var chains = [ "" ]; + // collect unique names + self.__rules__.forEach((function(rule) { + if (!rule.enabled) { + return; + } + rule.alt.forEach((function(altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + })); + })); + self.__cache__ = {}; + chains.forEach((function(chain) { + self.__cache__[chain] = []; + self.__rules__.forEach((function(rule) { + if (!rule.enabled) { + return; + } + if (chain && rule.alt.indexOf(chain) < 0) { + return; + } + self.__cache__[chain].push(rule.fn); + })); + })); + }; + /** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ Ruler.prototype.at = function(name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + if (index === -1) { + throw new Error("Parser rule not found: " + name); + } + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; + }; + /** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ Ruler.prototype.before = function(beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + if (index === -1) { + throw new Error("Parser rule not found: " + beforeName); + } + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + this.__cache__ = null; + }; + /** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ Ruler.prototype.after = function(afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + if (index === -1) { + throw new Error("Parser rule not found: " + afterName); + } + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + this.__cache__ = null; + }; + /** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ Ruler.prototype.push = function(ruleName, fn, options) { + var opt = options || {}; + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + this.__cache__ = null; + }; + /** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ Ruler.prototype.enable = function(list, ignoreInvalid) { + if (!Array.isArray(list)) { + list = [ list ]; + } + var result = []; + // Search by name and enable + list.forEach((function(name) { + var idx = this.__find__(name); + if (idx < 0) { + if (ignoreInvalid) { + return; + } + throw new Error("Rules manager: invalid rule name " + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }), this); + this.__cache__ = null; + return result; + }; + /** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ Ruler.prototype.enableOnly = function(list, ignoreInvalid) { + if (!Array.isArray(list)) { + list = [ list ]; + } + this.__rules__.forEach((function(rule) { + rule.enabled = false; + })); + this.enable(list, ignoreInvalid); + }; + /** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ Ruler.prototype.disable = function(list, ignoreInvalid) { + if (!Array.isArray(list)) { + list = [ list ]; + } + var result = []; + // Search by name and disable + list.forEach((function(name) { + var idx = this.__find__(name); + if (idx < 0) { + if (ignoreInvalid) { + return; + } + throw new Error("Rules manager: invalid rule name " + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }), this); + this.__cache__ = null; + return result; + }; + /** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ Ruler.prototype.getRules = function(chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; + }; + var ruler = Ruler; + // Normalize input string + // https://spec.commonmark.org/0.29/#line-ending + var NEWLINES_RE = /\r\n?|\n/g; + var NULL_RE = /\0/g; + var normalize = function normalize(state) { + var str; + // Normalize newlines + str = state.src.replace(NEWLINES_RE, "\n"); + // Replace NULL characters + str = str.replace(NULL_RE, "\ufffd"); + state.src = str; + }; + var block = function block(state) { + var token; + if (state.inlineMode) { + token = new state.Token("inline", "", 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } + }; + var inline = function inline(state) { + var tokens = state.tokens, tok, i, l; + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === "inline") { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } + }; + var arrayReplaceAt = utils.arrayReplaceAt; + function isLinkOpen$1(str) { + return /^\s]/i.test(str); } - - // Add token name, e.g. `/i.test(str); } - - // Check if we need to add a newline after this tag - if (token.block) { - needLf = true; - - if (token.nesting === 1) { - if (idx + 1 < tokens.length) { - nextToken = tokens[idx + 1]; - - if (nextToken.type === 'inline' || nextToken.hidden) { - // Block-level tag containing an inline tag. - // - needLf = false; - - } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) { - // Opening tag + closing tag of the same type. E.g. `
  • `. - // - needLf = false; + var linkify$1 = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, level, htmlLinkLevel, url, fullUrl, urlText, blockTokens = state.tokens, links; + if (!state.md.options.linkify) { + return; + } + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== "inline" || !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + tokens = blockTokens[j].children; + htmlLinkLevel = 0; + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + // Skip content of markdown links + if (currentToken.type === "link_close") { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== "link_open") { + i--; + } + continue; + } + // Skip content of html tag links + if (currentToken.type === "html_inline") { + if (isLinkOpen$1(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose$1(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { + continue; + } + if (currentToken.type === "text" && state.md.linkify.test(currentToken.content)) { + text = currentToken.content; + links = state.md.linkify.match(text); + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + // forbid escape sequence at the start of the string, + // this avoids http\://example.com/ from being linkified as + // http://example.com/ + if (links.length > 0 && links[0].index === 0 && i > 0 && tokens[i - 1].type === "text_special") { + links = links.slice(1); + } + for (ln = 0; ln < links.length; ln++) { + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { + continue; + } + urlText = links[ln].text; + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText("http://" + urlText).replace(/^http:\/\//, ""); + } else if (links[ln].schema === "mailto:" && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText("mailto:" + urlText).replace(/^mailto:/, ""); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + pos = links[ln].index; + if (pos > lastPos) { + token = new state.Token("text", "", 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + token = new state.Token("link_open", "a", 1); + token.attrs = [ [ "href", fullUrl ] ]; + token.level = level++; + token.markup = "linkify"; + token.info = "auto"; + nodes.push(token); + token = new state.Token("text", "", 0); + token.content = urlText; + token.level = level; + nodes.push(token); + token = new state.Token("link_close", "a", -1); + token.level = --level; + token.markup = "linkify"; + token.info = "auto"; + nodes.push(token); + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token("text", "", 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } + }; + // Simple typographic replacements + // TODO: + // - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ + // - multiplications 2 x 4 -> 2 × 4 + var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + // Workaround for phantomjs - need regex without /g flag, + // or root check will fail every second time + var SCOPED_ABBR_TEST_RE = /\((c|tm|r)\)/i; + var SCOPED_ABBR_RE = /\((c|tm|r)\)/gi; + var SCOPED_ABBR = { + c: "\xa9", + r: "\xae", + tm: "\u2122" + }; + function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; + } + function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + if (token.type === "text" && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + if (token.type === "link_open" && token.info === "auto") { + inside_autolink--; + } + if (token.type === "link_close" && token.info === "auto") { + inside_autolink++; + } + } + } + function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + if (token.type === "text" && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content.replace(/\+-/g, "\xb1").replace(/\.{2,}/g, "\u2026").replace(/([?!])\u2026/g, "$1..").replace(/([?!]){4,}/g, "$1$1$1").replace(/,{2,}/g, ",").replace(/(^|[^-])---(?=[^-]|$)/gm, "$1\u2014").replace(/(^|\s)--(?=\s|$)/gm, "$1\u2013").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm, "$1\u2013"); + } + } + if (token.type === "link_open" && token.info === "auto") { + inside_autolink--; + } + if (token.type === "link_close" && token.info === "auto") { + inside_autolink++; + } + } + } + var replacements = function replace(state) { + var blkIdx; + if (!state.md.options.typographer) { + return; + } + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + if (state.tokens[blkIdx].type !== "inline") { + continue; + } + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + } + }; + var isWhiteSpace$1 = utils.isWhiteSpace; + var isPunctChar$1 = utils.isPunctChar; + var isMdAsciiPunct$1 = utils.isMdAsciiPunct; + var QUOTE_TEST_RE = /['"]/; + var QUOTE_RE = /['"]/g; + var APOSTROPHE = "\u2019"; + /* ’ */ function replaceAt(str, index, ch) { + return str.slice(0, index) + ch + str.slice(index + 1); + } + function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + stack = []; + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + thisLevel = tokens[i].level; + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { + break; + } + } + stack.length = j + 1; + if (token.type !== "text") { + continue; + } + text = token.content; + pos = 0; + max = text.length; + /*eslint no-labels:0,block-scoped-var:0*/ OUTER: while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { + break; + } + canOpen = canClose = true; + pos = t.index + 1; + isSingle = t[0] === "'"; + // Find previous character, + // default to space if it's the beginning of the line + + lastChar = 32; + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === "softbreak" || tokens[j].type === "hardbreak") break; + // lastChar defaults to 0x20 + if (!tokens[j].content) continue; + // should skip all tokens except 'text', 'html_inline' or 'code_inline' + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + // Find next character, + // default to space if it's the end of the line + + nextChar = 32; + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === "softbreak" || tokens[j].type === "hardbreak") break; + // nextChar defaults to 0x20 + if (!tokens[j].content) continue; + // should skip all tokens except 'text', 'html_inline' or 'code_inline' + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + isLastPunctChar = isMdAsciiPunct$1(lastChar) || isPunctChar$1(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct$1(nextChar) || isPunctChar$1(String.fromCharCode(nextChar)); + isLastWhiteSpace = isWhiteSpace$1(lastChar); + isNextWhiteSpace = isWhiteSpace$1(nextChar); + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + if (nextChar === 34 /* " */ && t[0] === '"') { + if (lastChar >= 48 /* 0 */ && lastChar <= 57 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + if (canOpen && canClose) { + // Replace quotes in the middle of punctuation sequence, but not + // in the middle of the words, i.e.: + // 1. foo " bar " baz - not replaced + // 2. foo-"-bar-"-baz - replaced + // 3. foo"bar"baz - not replaced + canOpen = isLastPunctChar; + canClose = isNextPunctChar; + } + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { + break; + } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, openQuote); + pos += closeQuote.length - 1; + if (item.token === i) { + pos += openQuote.length - 1; + } + text = token.content; + max = text.length; + stack.length = j; + continue OUTER; + } + } + } + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); } } } } - - result += needLf ? '>\n' : '>'; - - return result; -}; - - -/** - * Renderer.renderInline(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * The same as [[Renderer.render]], but for single token of `inline` type. - **/ -Renderer.prototype.renderInline = function (tokens, options, env) { - var type, - result = '', - rules = this.rules; - - for (var i = 0, len = tokens.length; i < len; i++) { - type = tokens[i].type; - - if (typeof rules[type] !== 'undefined') { - result += rules[type](tokens, i, options, env, this); - } else { - result += this.renderToken(tokens, i, options); + var smartquotes = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + if (!state.md.options.typographer) { + return; } - } - - return result; -}; - - -/** internal - * Renderer.renderInlineAsText(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * Special kludge for image `alt` attributes to conform CommonMark spec. - * Don't try to use it! Spec requires to show `alt` content with stripped markup, - * instead of simple escaping. - **/ -Renderer.prototype.renderInlineAsText = function (tokens, options, env) { - var result = ''; - - for (var i = 0, len = tokens.length; i < len; i++) { - if (tokens[i].type === 'text') { - result += tokens[i].content; - } else if (tokens[i].type === 'image') { - result += this.renderInlineAsText(tokens[i].children, options, env); - } - } - - return result; -}; - - -/** - * Renderer.render(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * Takes token stream and generates HTML. Probably, you will never need to call - * this method directly. - **/ -Renderer.prototype.render = function (tokens, options, env) { - var i, len, type, - result = '', - rules = this.rules; - - for (i = 0, len = tokens.length; i < len; i++) { - type = tokens[i].type; - - if (type === 'inline') { - result += this.renderInline(tokens[i].children, options, env); - } else if (typeof rules[type] !== 'undefined') { - result += rules[tokens[i].type](tokens, i, options, env, this); - } else { - result += this.renderToken(tokens, i, options, env); - } - } - - return result; -}; - -module.exports = Renderer; - -},{"./common/utils":4}],17:[function(require,module,exports){ -/** - * class Ruler - * - * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and - * [[MarkdownIt#inline]] to manage sequences of functions (rules): - * - * - keep rules in defined order - * - assign the name to each rule - * - enable/disable rules - * - add/replace rules - * - allow assign rules to additional named chains (in the same) - * - cacheing lists of active rules - * - * You will not need use this class directly until write plugins. For simple - * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and - * [[MarkdownIt.use]]. - **/ -'use strict'; - - -/** - * new Ruler() - **/ -function Ruler() { - // List of added rules. Each element is: - // - // { - // name: XXX, - // enabled: Boolean, - // fn: Function(), - // alt: [ name2, name3 ] - // } - // - this.__rules__ = []; - - // Cached rule chains. - // - // First level - chain name, '' for default. - // Second level - diginal anchor for fast filtering by charcodes. - // - this.__cache__ = null; -} - -//////////////////////////////////////////////////////////////////////////////// -// Helper methods, should not be used directly - - -// Find rule index by name -// -Ruler.prototype.__find__ = function (name) { - for (var i = 0; i < this.__rules__.length; i++) { - if (this.__rules__[i].name === name) { - return i; - } - } - return -1; -}; - - -// Build rules lookup cache -// -Ruler.prototype.__compile__ = function () { - var self = this; - var chains = [ '' ]; - - // collect unique names - self.__rules__.forEach(function (rule) { - if (!rule.enabled) { return; } - - rule.alt.forEach(function (altName) { - if (chains.indexOf(altName) < 0) { - chains.push(altName); + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + if (state.tokens[blkIdx].type !== "inline" || !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; } - }); - }); - - self.__cache__ = {}; - - chains.forEach(function (chain) { - self.__cache__[chain] = []; - self.__rules__.forEach(function (rule) { - if (!rule.enabled) { return; } - - if (chain && rule.alt.indexOf(chain) < 0) { return; } - - self.__cache__[chain].push(rule.fn); - }); - }); -}; - - -/** - * Ruler.at(name, fn [, options]) - * - name (String): rule name to replace. - * - fn (Function): new rule function. - * - options (Object): new rule options (not mandatory). - * - * Replace rule by name with new function & options. Throws error if name not - * found. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * Replace existing typographer replacement rule with new one: - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.core.ruler.at('replacements', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.at = function (name, fn, options) { - var index = this.__find__(name); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + name); } - - this.__rules__[index].fn = fn; - this.__rules__[index].alt = opt.alt || []; - this.__cache__ = null; -}; - - -/** - * Ruler.before(beforeName, ruleName, fn [, options]) - * - beforeName (String): new rule will be added before this one. - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Add new rule to chain before one with given name. See also - * [[Ruler.after]], [[Ruler.push]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.before = function (beforeName, ruleName, fn, options) { - var index = this.__find__(beforeName); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } - - this.__rules__.splice(index, 0, { - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - - -/** - * Ruler.after(afterName, ruleName, fn [, options]) - * - afterName (String): new rule will be added after this one. - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Add new rule to chain after one with given name. See also - * [[Ruler.before]], [[Ruler.push]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.inline.ruler.after('text', 'my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.after = function (afterName, ruleName, fn, options) { - var index = this.__find__(afterName); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } - - this.__rules__.splice(index + 1, 0, { - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - -/** - * Ruler.push(ruleName, fn [, options]) - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Push new rule to the end of chain. See also - * [[Ruler.before]], [[Ruler.after]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.core.ruler.push('my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.push = function (ruleName, fn, options) { - var opt = options || {}; - - this.__rules__.push({ - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - - -/** - * Ruler.enable(list [, ignoreInvalid]) -> Array - * - list (String|Array): list of rule names to enable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable rules with given names. If any rule name not found - throw Error. - * Errors can be disabled by second param. - * - * Returns list of found rule names (if no exception happened). - * - * See also [[Ruler.disable]], [[Ruler.enableOnly]]. - **/ -Ruler.prototype.enable = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - var result = []; - - // Search by name and enable - list.forEach(function (name) { - var idx = this.__find__(name); - - if (idx < 0) { - if (ignoreInvalid) { return; } - throw new Error('Rules manager: invalid rule name ' + name); + process_inlines(state.tokens[blkIdx].children, state); } - this.__rules__[idx].enabled = true; - result.push(name); - }, this); - - this.__cache__ = null; - return result; -}; - - -/** - * Ruler.enableOnly(list [, ignoreInvalid]) - * - list (String|Array): list of rule names to enable (whitelist). - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable rules with given names, and disable everything else. If any rule name - * not found - throw Error. Errors can be disabled by second param. - * - * See also [[Ruler.disable]], [[Ruler.enable]]. - **/ -Ruler.prototype.enableOnly = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - this.__rules__.forEach(function (rule) { rule.enabled = false; }); - - this.enable(list, ignoreInvalid); -}; - - -/** - * Ruler.disable(list [, ignoreInvalid]) -> Array - * - list (String|Array): list of rule names to disable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Disable rules with given names. If any rule name not found - throw Error. - * Errors can be disabled by second param. - * - * Returns list of found rule names (if no exception happened). - * - * See also [[Ruler.enable]], [[Ruler.enableOnly]]. - **/ -Ruler.prototype.disable = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - var result = []; - - // Search by name and disable - list.forEach(function (name) { - var idx = this.__find__(name); - - if (idx < 0) { - if (ignoreInvalid) { return; } - throw new Error('Rules manager: invalid rule name ' + name); + }; + // Join raw text tokens with the rest of the text + var text_join = function text_join(state) { + var j, l, tokens, curr, max, last, blockTokens = state.tokens; + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== "inline") continue; + tokens = blockTokens[j].children; + max = tokens.length; + for (curr = 0; curr < max; curr++) { + if (tokens[curr].type === "text_special") { + tokens[curr].type = "text"; + } + } + for (curr = last = 0; curr < max; curr++) { + if (tokens[curr].type === "text" && curr + 1 < max && tokens[curr + 1].type === "text") { + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { + tokens[last] = tokens[curr]; + } + last++; + } + } + if (curr !== last) { + tokens.length = last; + } } - this.__rules__[idx].enabled = false; - result.push(name); - }, this); - - this.__cache__ = null; - return result; -}; - - -/** - * Ruler.getRules(chainName) -> Array - * - * Return array of active functions (rules) for given chain name. It analyzes - * rules configuration, compiles caches if not exists and returns result. - * - * Default chain name is `''` (empty string). It can't be skipped. That's - * done intentionally, to keep signature monomorphic for high speed. - **/ -Ruler.prototype.getRules = function (chainName) { - if (this.__cache__ === null) { - this.__compile__(); + }; + // Token class + /** + * class Token + **/ + /** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ this.tag = tag; + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ this.attrs = null; + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ this.map = null; + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ this.nesting = nesting; + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ this.level = 0; + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ this.children = null; + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ this.content = ""; + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ this.markup = ""; + /** + * Token#info -> String + * + * Additional information: + * + * - Info string for "fence" tokens + * - The value "auto" for autolink "link_open" and "link_close" tokens + * - The string value of the item marker for ordered-list "list_item_open" tokens + **/ this.info = ""; + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ this.meta = null; + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ this.block = false; + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ this.hidden = false; } - - // Chain can be empty, if rules disabled. But we still have to return Array. - return this.__cache__[chainName] || []; -}; - -module.exports = Ruler; - -},{}],18:[function(require,module,exports){ -// Block quotes - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function blockquote(state, startLine, endLine, silent) { - var adjustTab, - ch, - i, - initial, - l, - lastLineEmpty, - lines, - nextLine, - offset, - oldBMarks, - oldBSCount, - oldIndent, - oldParentType, - oldSCount, - oldTShift, - spaceAfterMarker, - terminate, - terminatorRules, - token, - wasOutdented, - oldLineMax = state.lineMax, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - // check the block quote marker - if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } - - // we know that it's going to be a valid blockquote, - // so no point trying to find the end of it in silent mode - if (silent) { return true; } - - // skip spaces after ">" and re-calculate offset - initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); - - // skip one optional space after '>' - if (state.src.charCodeAt(pos) === 0x20 /* space */) { - // ' > test ' - // ^ -- position start of line here: - pos++; - initial++; - offset++; - adjustTab = false; - spaceAfterMarker = true; - } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { - spaceAfterMarker = true; - - if ((state.bsCount[startLine] + offset) % 4 === 3) { - // ' >\t test ' - // ^ -- position start of line here (tab has width===1) + /** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + if (!this.attrs) { + return -1; + } + attrs = this.attrs; + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { + return i; + } + } + return -1; + }; + /** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } + }; + /** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), attrData = [ name, value ]; + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } + }; + /** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; + }; + /** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + " " + value; + } + }; + var token = Token; + function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; + // link to parser instance + } + // re-export Token class to use in core rules + StateCore.prototype.Token = token; + var state_core = StateCore; + var _rules$2 = [ [ "normalize", normalize ], [ "block", block ], [ "inline", inline ], [ "linkify", linkify$1 ], [ "replacements", replacements ], [ "smartquotes", smartquotes ], + // `text_join` finds `text_special` tokens (for escape sequences) + // and joins them with the rest of the text + [ "text_join", text_join ] ]; + /** + * new Core() + **/ function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new ruler; + for (var i = 0; i < _rules$2.length; i++) { + this.ruler.push(_rules$2[i][0], _rules$2[i][1]); + } + } + /** + * Core.process(state) + * + * Executes core chain rules. + **/ Core.prototype.process = function(state) { + var i, l, rules; + rules = this.ruler.getRules(""); + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } + }; + Core.prototype.State = state_core; + var parser_core = Core; + var isSpace$a = utils.isSpace; + function getLine(state, line) { + var pos = state.bMarks[line] + state.tShift[line], max = state.eMarks[line]; + return state.src.slice(pos, max); + } + function escapedSplit(str) { + var result = [], pos = 0, max = str.length, ch, isEscaped = false, lastPos = 0, current = ""; + ch = str.charCodeAt(pos); + while (pos < max) { + if (ch === 124 /* | */) { + if (!isEscaped) { + // pipe separating cells, '|' + result.push(current + str.substring(lastPos, pos)); + current = ""; + lastPos = pos + 1; + } else { + // escaped pipe, '\|' + current += str.substring(lastPos, pos - 1); + lastPos = pos; + } + } + isEscaped = ch === 92 /* \ */; + pos++; + ch = str.charCodeAt(pos); + } + result.push(current + str.substring(lastPos)); + return result; + } + var table = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, l, nextLine, columns, columnCount, token, aligns, t, tableLines, tbodyLines, oldParentType, terminate, terminatorRules, firstCh, secondCh; + // should have at least two lines + if (startLine + 2 > endLine) { + return false; + } + nextLine = startLine + 1; + if (state.sCount[nextLine] < state.blkIndent) { + return false; + } + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { + return false; + } + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { + return false; + } + firstCh = state.src.charCodeAt(pos++); + if (firstCh !== 124 /* | */ && firstCh !== 45 /* - */ && firstCh !== 58 /* : */) { + return false; + } + if (pos >= state.eMarks[nextLine]) { + return false; + } + secondCh = state.src.charCodeAt(pos++); + if (secondCh !== 124 /* | */ && secondCh !== 45 /* - */ && secondCh !== 58 /* : */ && !isSpace$a(secondCh)) { + return false; + } + // if first character is '-', then second character must not be a space + // (due to parsing ambiguity with list) + if (firstCh === 45 /* - */ && isSpace$a(secondCh)) { + return false; + } + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + if (ch !== 124 /* | */ && ch !== 45 /* - */ && ch !== 58 /* : */ && !isSpace$a(ch)) { + return false; + } + pos++; + } + lineText = getLine(state, startLine + 1); + columns = lineText.split("|"); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + if (!/^:?-+:?$/.test(t)) { + return false; + } + if (t.charCodeAt(t.length - 1) === 58 /* : */) { + aligns.push(t.charCodeAt(0) === 58 /* : */ ? "center" : "right"); + } else if (t.charCodeAt(0) === 58 /* : */) { + aligns.push("left"); + } else { + aligns.push(""); + } + } + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf("|") === -1) { + return false; + } + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + columns = escapedSplit(lineText); + if (columns.length && columns[0] === "") columns.shift(); + if (columns.length && columns[columns.length - 1] === "") columns.pop(); + // header row will define an amount of columns in the entire table, + // and align row should be exactly the same (the rest of the rows can differ) + columnCount = columns.length; + if (columnCount === 0 || columnCount !== aligns.length) { + return false; + } + if (silent) { + return true; + } + oldParentType = state.parentType; + state.parentType = "table"; + // use 'blockquote' lists for termination because it's + // the most similar to tables + terminatorRules = state.md.block.ruler.getRules("blockquote"); + token = state.push("table_open", "table", 1); + token.map = tableLines = [ startLine, 0 ]; + token = state.push("thead_open", "thead", 1); + token.map = [ startLine, startLine + 1 ]; + token = state.push("tr_open", "tr", 1); + token.map = [ startLine, startLine + 1 ]; + for (i = 0; i < columns.length; i++) { + token = state.push("th_open", "th", 1); + if (aligns[i]) { + token.attrs = [ [ "style", "text-align:" + aligns[i] ] ]; + } + token = state.push("inline", "", 0); + token.content = columns[i].trim(); + token.children = []; + token = state.push("th_close", "th", -1); + } + token = state.push("tr_close", "tr", -1); + token = state.push("thead_close", "thead", -1); + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { + break; + } + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + break; + } + lineText = getLine(state, nextLine).trim(); + if (!lineText) { + break; + } + if (state.sCount[nextLine] - state.blkIndent >= 4) { + break; + } + columns = escapedSplit(lineText); + if (columns.length && columns[0] === "") columns.shift(); + if (columns.length && columns[columns.length - 1] === "") columns.pop(); + if (nextLine === startLine + 2) { + token = state.push("tbody_open", "tbody", 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + } + token = state.push("tr_open", "tr", 1); + token.map = [ nextLine, nextLine + 1 ]; + for (i = 0; i < columnCount; i++) { + token = state.push("td_open", "td", 1); + if (aligns[i]) { + token.attrs = [ [ "style", "text-align:" + aligns[i] ] ]; + } + token = state.push("inline", "", 0); + token.content = columns[i] ? columns[i].trim() : ""; + token.children = []; + token = state.push("td_close", "td", -1); + } + token = state.push("tr_close", "tr", -1); + } + if (tbodyLines) { + token = state.push("tbody_close", "tbody", -1); + tbodyLines[1] = nextLine; + } + token = state.push("table_close", "table", -1); + tableLines[1] = nextLine; + state.parentType = oldParentType; + state.line = nextLine; + return true; + }; + // Code block (4 spaces padded) + var code = function code(state, startLine, endLine /*, silent*/) { + var nextLine, last, token; + if (state.sCount[startLine] - state.blkIndent < 4) { + return false; + } + last = nextLine = startLine + 1; + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + state.line = last; + token = state.push("code_block", "code", 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, false) + "\n"; + token.map = [ startLine, state.line ]; + return true; + }; + // fences (``` lang, ~~~ lang) + var fence = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, haveEndMarker = false, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + if (pos + 3 > max) { + return false; + } + marker = state.src.charCodeAt(pos); + if (marker !== 126 /* ~ */ && marker !== 96 /* ` */) { + return false; + } + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + len = pos - mem; + if (len < 3) { + return false; + } + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + if (marker === 96 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + // Since start is found, we can report success here in validation mode + if (silent) { + return true; + } + // search end of block + nextLine = startLine; + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + if (state.src.charCodeAt(pos) !== marker) { + continue; + } + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + pos = state.skipChars(pos, marker); + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { + continue; + } + // make sure tail has spaces only + pos = state.skipSpaces(pos); + if (pos < max) { + continue; + } + haveEndMarker = true; + // found! + break; + } + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + state.line = nextLine + (haveEndMarker ? 1 : 0); + token = state.push("fence", "code", 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + return true; + }; + var isSpace$9 = utils.isSpace; + var blockquote = function blockquote(state, startLine, endLine, silent) { + var adjustTab, ch, i, initial, l, lastLineEmpty, lines, nextLine, offset, oldBMarks, oldBSCount, oldIndent, oldParentType, oldSCount, oldTShift, spaceAfterMarker, terminate, terminatorRules, token, isOutdented, oldLineMax = state.lineMax, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 62 /* > */) { + return false; + } + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { + return true; + } + // set offset past spaces and ">" + initial = offset = state.sCount[startLine] + 1; + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 32 /* space */) { + // ' > test ' + // ^ -- position start of line here: pos++; initial++; offset++; adjustTab = false; - } else { - // ' >\t test ' - // ^ -- position start of line here + shift bsCount slightly - // to make extra space appear - adjustTab = true; - } - } else { - spaceAfterMarker = false; - } - - oldBMarks = [ state.bMarks[startLine] ]; - state.bMarks[startLine] = pos; - - while (pos < max) { - ch = state.src.charCodeAt(pos); - - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; - } else { - offset++; - } - } else { - break; - } - - pos++; - } - - oldBSCount = [ state.bsCount[startLine] ]; - state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); - - lastLineEmpty = pos >= max; - - oldSCount = [ state.sCount[startLine] ]; - state.sCount[startLine] = offset - initial; - - oldTShift = [ state.tShift[startLine] ]; - state.tShift[startLine] = pos - state.bMarks[startLine]; - - terminatorRules = state.md.block.ruler.getRules('blockquote'); - - oldParentType = state.parentType; - state.parentType = 'blockquote'; - wasOutdented = false; - - // Search the end of the block - // - // Block ends with either: - // 1. an empty line outside: - // ``` - // > test - // - // ``` - // 2. an empty line inside: - // ``` - // > - // test - // ``` - // 3. another tag: - // ``` - // > test - // - - - - // ``` - for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { - // check if it's outdented, i.e. it's inside list item and indented - // less than said list item: - // - // ``` - // 1. anything - // > current blockquote - // 2. checking this line - // ``` - if (state.sCount[nextLine] < state.blkIndent) wasOutdented = true; - - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - - if (pos >= max) { - // Case 1: line is not inside the blockquote, and this line is empty. - break; - } - - if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !wasOutdented) { - // This line is inside the blockquote. - - // skip spaces after ">" and re-calculate offset - initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); - - // skip one optional space after '>' - if (state.src.charCodeAt(pos) === 0x20 /* space */) { - // ' > test ' - // ^ -- position start of line here: + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 9 /* tab */) { + spaceAfterMarker = true; + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) pos++; initial++; offset++; adjustTab = false; - spaceAfterMarker = true; - } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { - spaceAfterMarker = true; - - if ((state.bsCount[nextLine] + offset) % 4 === 3) { - // ' >\t test ' - // ^ -- position start of line here (tab has width===1) + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (isSpace$9(ch)) { + if (ch === 9) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + pos++; + } + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + lastLineEmpty = pos >= max; + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + terminatorRules = state.md.block.ruler.getRules("blockquote"); + oldParentType = state.parentType; + state.parentType = "blockquote"; + // Search the end of the block + + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + isOutdented = state.sCount[nextLine] < state.blkIndent; + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + if (state.src.charCodeAt(pos++) === 62 /* > */ && !isOutdented) { + // This line is inside the blockquote. + // set offset past spaces and ">" + initial = offset = state.sCount[nextLine] + 1; + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 32 /* space */) { + // ' > test ' + // ^ -- position start of line here: pos++; initial++; offset++; adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 9 /* tab */) { + spaceAfterMarker = true; + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } } else { - // ' >\t test ' - // ^ -- position start of line here + shift bsCount slightly - // to make extra space appear - adjustTab = true; + spaceAfterMarker = false; } - } else { - spaceAfterMarker = false; + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (isSpace$9(ch)) { + if (ch === 9) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + pos++; + } + lastLineEmpty = pos >= max; + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { + break; + } + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + break; } - oldBMarks.push(state.bMarks[nextLine]); - state.bMarks[nextLine] = pos; - + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + // A negative indentation means that this is a paragraph continuation + + state.sCount[nextLine] = -1; + } + oldIndent = state.blkIndent; + state.blkIndent = 0; + token = state.push("blockquote_open", "blockquote", 1); + token.markup = ">"; + token.map = lines = [ startLine, 0 ]; + state.md.block.tokenize(state, startLine, nextLine); + token = state.push("blockquote_close", "blockquote", -1); + token.markup = ">"; + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + return true; + }; + var isSpace$8 = utils.isSpace; + var hr = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + marker = state.src.charCodeAt(pos++); + // Check hr marker + if (marker !== 42 /* * */ && marker !== 45 /* - */ && marker !== 95 /* _ */) { + return false; + } + // markers can be mixed with spaces, but there should be at least 3 of them + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace$8(ch)) { + return false; + } + if (ch === marker) { + cnt++; + } + } + if (cnt < 3) { + return false; + } + if (silent) { + return true; + } + state.line = startLine + 1; + token = state.push("hr", "hr", 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + return true; + }; + var isSpace$7 = utils.isSpace; + // Search `[-+*][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 42 /* * */ && marker !== 45 /* - */ && marker !== 43 /* + */) { + return -1; + } + if (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace$7(ch)) { + // " -test " - is not a list item + return -1; + } + } + return pos; + } + // Search `\d+[.)][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipOrderedListMarker(state, startLine) { + var ch, start = state.bMarks[startLine] + state.tShift[startLine], pos = start, max = state.eMarks[startLine]; + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { + return -1; + } + ch = state.src.charCodeAt(pos++); + if (ch < 48 /* 0 */ || ch > 57 /* 9 */) { + return -1; + } + for (;;) { + // EOL -> fail + if (pos >= max) { + return -1; + } + ch = state.src.charCodeAt(pos++); + if (ch >= 48 /* 0 */ && ch <= 57 /* 9 */) { + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { + return -1; + } + continue; + } + // found valid marker + if (ch === 41 /* ) */ || ch === 46 /* . */) { + break; + } + return -1; + } + if (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace$7(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; + } + function markTightParagraphs(state, idx) { + var i, l, level = state.level + 2; + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === "paragraph_open") { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } + } + var list = function list(state, startLine, endLine, silent) { + var ch, contentStart, i, indent, indentAfterMarker, initial, isOrdered, itemLines, l, listLines, listTokIdx, markerCharCode, markerValue, max, nextLine, offset, oldListIndent, oldParentType, oldSCount, oldTShift, oldTight, pos, posAfterMarker, prevEmptyEnd, start, terminate, terminatorRules, token, isTerminatingParagraph = false, tight = true; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && state.sCount[startLine] - state.listIndent >= 4 && state.sCount[startLine] < state.blkIndent) { + return false; + } + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === "paragraph") { + // Next list item should still terminate previous list item; + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + if (state.sCount[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.slice(start, posAfterMarker - 1)); + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + } else { + return false; + } + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + // For validation mode we can terminate immediately + if (silent) { + return true; + } + // Start list + listTokIdx = state.tokens.length; + if (isOrdered) { + token = state.push("ordered_list_open", "ol", 1); + if (markerValue !== 1) { + token.attrs = [ [ "start", markerValue ] ]; + } + } else { + token = state.push("bullet_list_open", "ul", 1); + } + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // Iterate list items + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules("list"); + oldParentType = state.parentType; + state.parentType = "list"; + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); while (pos < max) { ch = state.src.charCodeAt(pos); - - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + if (ch === 9) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 32) { + offset++; + } else { + break; + } + pos++; + } + contentStart = pos; + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { + indentAfterMarker = 1; + } + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + // Run subparser & write tokens + token = state.push("list_item_open", "li", 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + if (isOrdered) { + token.info = state.src.slice(start, posAfterMarker - 1); + } + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = state.line - startLine > 1 && state.isEmpty(state.line - 1); + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + token = state.push("list_item_close", "li", -1); + token.markup = String.fromCharCode(markerCharCode); + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + if (nextLine >= endLine) { + break; + } + + // Try to check if list is terminated or continued. + + if (state.sCount[nextLine] < state.blkIndent) { + break; + } + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + break; + } + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + break; + } + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { + break; + } + start = state.bMarks[nextLine] + state.tShift[nextLine]; + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { + break; + } + } + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { + break; + } + } + // Finalize list + if (isOrdered) { + token = state.push("ordered_list_close", "ol", -1); + } else { + token = state.push("bullet_list_close", "ul", -1); + } + token.markup = String.fromCharCode(markerCharCode); + listLines[1] = nextLine; + state.line = nextLine; + state.parentType = oldParentType; + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + return true; + }; + var normalizeReference$2 = utils.normalizeReference; + var isSpace$6 = utils.isSpace; + var reference = function reference(state, startLine, _endLine, silent) { + var ch, destEndPos, destEndLineNo, endLine, href, i, l, label, labelEnd, oldParentType, res, start, str, terminate, terminatorRules, title, lines = 0, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine], nextLine = startLine + 1; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + if (state.src.charCodeAt(pos) !== 91 /* [ */) { + return false; + } + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 93 /* ] */ && state.src.charCodeAt(pos - 1) !== 92 /* \ */) { + if (pos + 1 === max) { + return false; + } + if (state.src.charCodeAt(pos + 1) !== 58 /* : */) { + return false; + } + break; + } + } + endLine = state.lineMax; + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules("reference"); + oldParentType = state.parentType; + state.parentType = "reference"; + for (;nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue; + } + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue; + } + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + break; + } + } + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 91 /* [ */) { + return false; + } else if (ch === 93 /* ] */) { + labelEnd = pos; + break; + } else if (ch === 10 /* \n */) { + lines++; + } else if (ch === 92 /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 10) { + lines++; + } + } + } + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 58 /* : */) { + return false; + } + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 10) { + lines++; + } else if (isSpace$6(ch)) ; else { + break; + } + } + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { + return false; + } + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { + return false; + } + pos = res.pos; + lines += res.lines; + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (;pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 10) { + lines++; + } else if (isSpace$6(ch)) ; else { + break; + } + } + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ""; + pos = destEndPos; + lines = destEndLineNo; + } + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace$6(ch)) { + break; + } + pos++; + } + if (pos < max && str.charCodeAt(pos) !== 10) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ""; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace$6(ch)) { + break; + } + pos++; + } + } + } + if (pos < max && str.charCodeAt(pos) !== 10) { + // garbage at the end of the line + return false; + } + label = normalizeReference$2(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ if (silent) { + return true; + } + if (typeof state.env.references === "undefined") { + state.env.references = {}; + } + if (typeof state.env.references[label] === "undefined") { + state.env.references[label] = { + title: title, + href: href + }; + } + state.parentType = oldParentType; + state.line = startLine + lines + 1; + return true; + }; + // List of valid html blocks names, accorting to commonmark spec + var html_blocks = [ "address", "article", "aside", "base", "basefont", "blockquote", "body", "caption", "center", "col", "colgroup", "dd", "details", "dialog", "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "iframe", "legend", "li", "link", "main", "menu", "menuitem", "nav", "noframes", "ol", "optgroup", "option", "p", "param", "section", "source", "summary", "table", "tbody", "td", "tfoot", "th", "thead", "title", "tr", "track", "ul" ]; + // Regexps to match html elements + var attr_name = "[a-zA-Z_:][a-zA-Z0-9:._-]*"; + var unquoted = "[^\"'=<>`\\x00-\\x20]+"; + var single_quoted = "'[^']*'"; + var double_quoted = '"[^"]*"'; + var attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")"; + var attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)"; + var open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>"; + var close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>"; + var comment = "\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e"; + var processing = "<[?][\\s\\S]*?[?]>"; + var declaration = "]*>"; + var cdata = ""; + var HTML_TAG_RE$1 = new RegExp("^(?:" + open_tag + "|" + close_tag + "|" + comment + "|" + processing + "|" + declaration + "|" + cdata + ")"); + var HTML_OPEN_CLOSE_TAG_RE$1 = new RegExp("^(?:" + open_tag + "|" + close_tag + ")"); + var HTML_TAG_RE_1 = HTML_TAG_RE$1; + var HTML_OPEN_CLOSE_TAG_RE_1 = HTML_OPEN_CLOSE_TAG_RE$1; + var html_re = { + HTML_TAG_RE: HTML_TAG_RE_1, + HTML_OPEN_CLOSE_TAG_RE: HTML_OPEN_CLOSE_TAG_RE_1 + }; + var HTML_OPEN_CLOSE_TAG_RE = html_re.HTML_OPEN_CLOSE_TAG_RE; + // An array of opening and corresponding closing sequences for html tags, + // last argument defines whether it can terminate a paragraph or not + + var HTML_SEQUENCES = [ [ /^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true ], [ /^/, true ], [ /^<\?/, /\?>/, true ], [ /^/, true ], [ /^/, true ], [ new RegExp("^|$))", "i"), /^$/, true ], [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + "\\s*$"), /^$/, false ] ]; + var html_block = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + if (!state.md.options.html) { + return false; + } + if (state.src.charCodeAt(pos) !== 60 /* < */) { + return false; + } + lineText = state.src.slice(pos, max); + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { + break; + } + } + if (i === HTML_SEQUENCES.length) { + return false; + } + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + nextLine = startLine + 1; + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (;nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { + break; + } + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { + nextLine++; + } + break; + } + } + } + state.line = nextLine; + token = state.push("html_block", "", 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + return true; + }; + var isSpace$5 = utils.isSpace; + var heading = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + ch = state.src.charCodeAt(pos); + if (ch !== 35 /* # */ || pos >= max) { + return false; + } + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 35 /* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + if (level > 6 || pos < max && !isSpace$5(ch)) { + return false; + } + if (silent) { + return true; + } + // Let's cut tails like ' ### ' from the end of string + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 35, pos); + // # + if (tmp > pos && isSpace$5(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + state.line = startLine + 1; + token = state.push("heading_open", "h" + String(level), 1); + token.markup = "########".slice(0, level); + token.map = [ startLine, state.line ]; + token = state.push("inline", "", 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + token = state.push("heading_close", "h" + String(level), -1); + token.markup = "########".slice(0, level); + return true; + }; + // lheading (---, ===) + var lheading = function lheading(state, startLine, endLine /*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, nextLine = startLine + 1, oldParentType, terminatorRules = state.md.block.ruler.getRules("paragraph"); + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false; + } + oldParentType = state.parentType; + state.parentType = "paragraph"; + // use paragraph to match terminatorRules + // jump line-by-line until empty one or EOF + for (;nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue; + } + + // Check for underline in setext header + + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + if (pos < max) { + marker = state.src.charCodeAt(pos); + if (marker === 45 /* - */ || marker === 61 /* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + if (pos >= max) { + level = marker === 61 /* = */ ? 1 : 2; + break; + } + } + } + } + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue; + } + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + break; + } + } + if (!level) { + // Didn't find valid underline + return false; + } + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + state.line = nextLine + 1; + token = state.push("heading_open", "h" + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + token = state.push("inline", "", 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + token = state.push("heading_close", "h" + String(level), -1); + token.markup = String.fromCharCode(marker); + state.parentType = oldParentType; + return true; + }; + // Paragraph + var paragraph = function paragraph(state, startLine /*, endLine*/) { + var content, terminate, i, l, token, oldParentType, nextLine = startLine + 1, terminatorRules = state.md.block.ruler.getRules("paragraph"), endLine = state.lineMax; + oldParentType = state.parentType; + state.parentType = "paragraph"; + // jump line-by-line until empty one or EOF + for (;nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue; + } + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue; + } + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { + break; + } + } + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + state.line = nextLine; + token = state.push("paragraph_open", "p", 1); + token.map = [ startLine, state.line ]; + token = state.push("inline", "", 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + token = state.push("paragraph_close", "p", -1); + state.parentType = oldParentType; + return true; + }; + var isSpace$4 = utils.isSpace; + function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + this.src = src; + // link to parser instance + this.md = md; + this.env = env; + + // Internal state vartiables + + this.tokens = tokens; + this.bMarks = []; + // line begin offsets for fast jumps + this.eMarks = []; + // line end offsets for fast jumps + this.tShift = []; + // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; + // indents for each line (tabs expanded) + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + + this.bsCount = []; + // block parser variables + this.blkIndent = 0; + // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; + // line index in src + this.lineMax = 0; + // lines count + this.tight = false; + // loose/tight mode for lists + this.ddIndent = -1; + // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; + // indent of the current list block (-1 if there isn't any) + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = "root"; + this.level = 0; + // renderer + this.result = ""; + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + if (!indent_found) { + if (isSpace$4(ch)) { + indent++; + if (ch === 9) { + offset += 4 - offset % 4; } else { offset++; } + continue; + } else { + indent_found = true; + } + } + if (ch === 10 || pos === len - 1) { + if (ch !== 10) { + pos++; + } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + this.lineMax = this.bMarks.length - 1; + // don't count last fake line + } + // Push new token to "stream". + + StateBlock.prototype.push = function(type, tag, nesting) { + var token$1 = new token(type, tag, nesting); + token$1.block = true; + if (nesting < 0) this.level--; + // closing tag + token$1.level = this.level; + if (nesting > 0) this.level++; + // opening tag + this.tokens.push(token$1); + return token$1; + }; + StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; + }; + StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; + }; + // Skip spaces from given position. + StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace$4(ch)) { + break; + } + } + return pos; + }; + // Skip spaces from given position in reverse. + StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { + return pos; + } + while (pos > min) { + if (!isSpace$4(this.src.charCodeAt(--pos))) { + return pos + 1; + } + } + return pos; + }; + // Skip char codes from given position + StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { + break; + } + } + return pos; + }; + // Skip char codes reverse from given position - 1 + StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { + return pos; + } + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { + return pos + 1; + } + } + return pos; + }; + // cut lines range from source. + StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, line = begin; + if (begin >= end) { + return ""; + } + queue = new Array(end - begin); + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + if (isSpace$4(ch)) { + if (ch === 9) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; } else { break; } - - pos++; + first++; + } + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(" ") + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); } - - lastLineEmpty = pos >= max; - - oldBSCount.push(state.bsCount[nextLine]); - state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); - - oldSCount.push(state.sCount[nextLine]); - state.sCount[nextLine] = offset - initial; - - oldTShift.push(state.tShift[nextLine]); - state.tShift[nextLine] = pos - state.bMarks[nextLine]; - continue; } - - // Case 2: line is not inside the blockquote, and the last line was empty. - if (lastLineEmpty) { break; } - - // Case 3: another tag found. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; + return queue.join(""); + }; + // re-export Token class to use in block rules + StateBlock.prototype.Token = token; + var state_block = StateBlock; + var _rules$1 = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ "table", table, [ "paragraph", "reference" ] ], [ "code", code ], [ "fence", fence, [ "paragraph", "reference", "blockquote", "list" ] ], [ "blockquote", blockquote, [ "paragraph", "reference", "blockquote", "list" ] ], [ "hr", hr, [ "paragraph", "reference", "blockquote", "list" ] ], [ "list", list, [ "paragraph", "reference", "blockquote" ] ], [ "reference", reference ], [ "html_block", html_block, [ "paragraph", "reference", "blockquote" ] ], [ "heading", heading, [ "paragraph", "reference", "blockquote" ] ], [ "lheading", lheading ], [ "paragraph", paragraph ] ]; + /** + * new ParserBlock() + **/ function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new ruler; + for (var i = 0; i < _rules$1.length; i++) { + this.ruler.push(_rules$1[i][0], _rules$1[i][1], { + alt: (_rules$1[i][2] || []).slice() + }); + } + } + // Generate tokens for input range + + ParserBlock.prototype.tokenize = function(state, startLine, endLine) { + var ok, i, rules = this.ruler.getRules(""), len = rules.length, line = startLine, hasEmptyLines = false, maxNesting = state.md.options.maxNesting; + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } - } - - if (terminate) { - // Quirk to enforce "hard termination mode" for paragraphs; - // normally if you call `tokenize(state, startLine, nextLine)`, - // paragraphs will look below nextLine for paragraph continuation, - // but if blockquote is terminated by another tag, they shouldn't - state.lineMax = nextLine; - - if (state.blkIndent !== 0) { - // state.blkIndent was non-zero, we now set it to zero, - // so we need to re-calculate all offsets to appear as - // if indent wasn't changed - oldBMarks.push(state.bMarks[nextLine]); - oldBSCount.push(state.bsCount[nextLine]); - oldTShift.push(state.tShift[nextLine]); - oldSCount.push(state.sCount[nextLine]); - state.sCount[nextLine] -= state.blkIndent; + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { + break; + } + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + // Try all possible rules. + // On success, rule should: + + // - update `state.line` + // - update `state.tokens` + // - return true + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { + break; + } + } + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + line = state.line; + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; } - - break; } - - oldBMarks.push(state.bMarks[nextLine]); - oldBSCount.push(state.bsCount[nextLine]); - oldTShift.push(state.tShift[nextLine]); - oldSCount.push(state.sCount[nextLine]); - - // A negative indentation means that this is a paragraph continuation - // - state.sCount[nextLine] = -1; - } - - oldIndent = state.blkIndent; - state.blkIndent = 0; - - token = state.push('blockquote_open', 'blockquote', 1); - token.markup = '>'; - token.map = lines = [ startLine, 0 ]; - - state.md.block.tokenize(state, startLine, nextLine); - - token = state.push('blockquote_close', 'blockquote', -1); - token.markup = '>'; - - state.lineMax = oldLineMax; - state.parentType = oldParentType; - lines[1] = state.line; - - // Restore original tShift; this might not be necessary since the parser - // has already been here, but just to make sure we can do that. - for (i = 0; i < oldTShift.length; i++) { - state.bMarks[i + startLine] = oldBMarks[i]; - state.tShift[i + startLine] = oldTShift[i]; - state.sCount[i + startLine] = oldSCount[i]; - state.bsCount[i + startLine] = oldBSCount[i]; - } - state.blkIndent = oldIndent; - - return true; -}; - -},{"../common/utils":4}],19:[function(require,module,exports){ -// Code block (4 spaces padded) - -'use strict'; - - -module.exports = function code(state, startLine, endLine/*, silent*/) { - var nextLine, last, token; - - if (state.sCount[startLine] - state.blkIndent < 4) { return false; } - - last = nextLine = startLine + 1; - - while (nextLine < endLine) { - if (state.isEmpty(nextLine)) { - nextLine++; - continue; + }; + /** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ ParserBlock.prototype.parse = function(src, md, env, outTokens) { + var state; + if (!src) { + return; } + state = new this.State(src, md, env, outTokens); + this.tokenize(state, state.line, state.lineMax); + }; + ParserBlock.prototype.State = state_block; + var parser_block = ParserBlock; + // Skip text characters for text token, place those to pending buffer + // Rule to skip pure text + // '{}$%@~+=:' reserved for extentions + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + // !!!! Don't confuse with "Markdown ASCII Punctuation" chars + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + function isTerminatorChar(ch) { + switch (ch) { + case 10 /* \n */ : + case 33 /* ! */ : + case 35 /* # */ : + case 36 /* $ */ : + case 37 /* % */ : + case 38 /* & */ : + case 42 /* * */ : + case 43 /* + */ : + case 45 /* - */ : + case 58 /* : */ : + case 60 /* < */ : + case 61 /* = */ : + case 62 /* > */ : + case 64 /* @ */ : + case 91 /* [ */ : + case 92 /* \ */ : + case 93 /* ] */ : + case 94 /* ^ */ : + case 95 /* _ */ : + case 96 /* ` */ : + case 123 /* { */ : + case 125 /* } */ : + case 126 /* ~ */ : + return true; - if (state.sCount[nextLine] - state.blkIndent >= 4) { - nextLine++; - last = nextLine; - continue; - } - break; - } - - state.line = last; - - token = state.push('code_block', 'code', 0); - token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); - token.map = [ startLine, state.line ]; - - return true; -}; - -},{}],20:[function(require,module,exports){ -// fences (``` lang, ~~~ lang) - -'use strict'; - - -module.exports = function fence(state, startLine, endLine, silent) { - var marker, len, params, nextLine, mem, token, markup, - haveEndMarker = false, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - if (pos + 3 > max) { return false; } - - marker = state.src.charCodeAt(pos); - - if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { - return false; - } - - // scan marker length - mem = pos; - pos = state.skipChars(pos, marker); - - len = pos - mem; - - if (len < 3) { return false; } - - markup = state.src.slice(mem, pos); - params = state.src.slice(pos, max); - - if (marker === 0x60 /* ` */) { - if (params.indexOf(String.fromCharCode(marker)) >= 0) { + default: return false; } } - - // Since start is found, we can report success here in validation mode - if (silent) { return true; } - - // search end of block - nextLine = startLine; - - for (;;) { - nextLine++; - if (nextLine >= endLine) { - // unclosed block should be autoclosed by end of document. - // also block seems to be autoclosed by end of parent - break; + var text = function text(state, silent) { + var pos = state.pos; + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; } - - pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - - if (pos < max && state.sCount[nextLine] < state.blkIndent) { - // non-empty line with negative indent should stop the list: - // - ``` - // test - break; + if (pos === state.pos) { + return false; } - - if (state.src.charCodeAt(pos) !== marker) { continue; } - - if (state.sCount[nextLine] - state.blkIndent >= 4) { - // closing fence should be indented less than 4 spaces - continue; + if (!silent) { + state.pending += state.src.slice(state.pos, pos); } - - pos = state.skipChars(pos, marker); - - // closing code fence must be at least as long as the opening one - if (pos - mem < len) { continue; } - - // make sure tail has spaces only - pos = state.skipSpaces(pos); - - if (pos < max) { continue; } - - haveEndMarker = true; - // found! - break; + state.pos = pos; + return true; + }; + // Process links like https://example.org/ + // RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + var SCHEME_RE = /(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$/i; + var linkify = function linkify(state, silent) { + var pos, max, match, proto, link, url, fullUrl, token; + if (!state.md.options.linkify) return false; + if (state.linkLevel > 0) return false; + pos = state.pos; + max = state.posMax; + if (pos + 3 > max) return false; + if (state.src.charCodeAt(pos) !== 58 /* : */) return false; + if (state.src.charCodeAt(pos + 1) !== 47 /* / */) return false; + if (state.src.charCodeAt(pos + 2) !== 47 /* / */) return false; + match = state.pending.match(SCHEME_RE); + if (!match) return false; + proto = match[1]; + link = state.md.linkify.matchAtStart(state.src.slice(pos - proto.length)); + if (!link) return false; + url = link.url; + // disallow '*' at the end of the link (conflicts with emphasis) + url = url.replace(/\*+$/, ""); + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) return false; + if (!silent) { + state.pending = state.pending.slice(0, -proto.length); + token = state.push("link_open", "a", 1); + token.attrs = [ [ "href", fullUrl ] ]; + token.markup = "linkify"; + token.info = "auto"; + token = state.push("text", "", 0); + token.content = state.md.normalizeLinkText(url); + token = state.push("link_close", "a", -1); + token.markup = "linkify"; + token.info = "auto"; + } + state.pos += url.length - proto.length; + return true; + }; + var isSpace$3 = utils.isSpace; + var newline = function newline(state, silent) { + var pmax, max, ws, pos = state.pos; + if (state.src.charCodeAt(pos) !== 10 /* \n */) { + return false; + } + pmax = state.pending.length - 1; + max = state.posMax; + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 32) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 32) { + // Find whitespaces tail of pending chars. + ws = pmax - 1; + while (ws >= 1 && state.pending.charCodeAt(ws - 1) === 32) ws--; + state.pending = state.pending.slice(0, ws); + state.push("hardbreak", "br", 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push("softbreak", "br", 0); + } + } else { + state.push("softbreak", "br", 0); + } + } + pos++; + // skip heading spaces for next line + while (pos < max && isSpace$3(state.src.charCodeAt(pos))) { + pos++; + } + state.pos = pos; + return true; + }; + var isSpace$2 = utils.isSpace; + var ESCAPED = []; + for (var i = 0; i < 256; i++) { + ESCAPED.push(0); } - - // If a fence has heading spaces, they should be removed from its inner block - len = state.sCount[startLine]; - - state.line = nextLine + (haveEndMarker ? 1 : 0); - - token = state.push('fence', 'code', 0); - token.info = params; - token.content = state.getLines(startLine + 1, nextLine, len, true); - token.markup = markup; - token.map = [ startLine, state.line ]; - - return true; -}; - -},{}],21:[function(require,module,exports){ -// heading (#, ##, ...) - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function heading(state, startLine, endLine, silent) { - var ch, level, tmp, token, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - ch = state.src.charCodeAt(pos); - - if (ch !== 0x23/* # */ || pos >= max) { return false; } - - // count heading level - level = 1; - ch = state.src.charCodeAt(++pos); - while (ch === 0x23/* # */ && pos < max && level <= 6) { - level++; - ch = state.src.charCodeAt(++pos); - } - - if (level > 6 || (pos < max && !isSpace(ch))) { return false; } - - if (silent) { return true; } - - // Let's cut tails like ' ### ' from the end of string - - max = state.skipSpacesBack(max, pos); - tmp = state.skipCharsBack(max, 0x23, pos); // # - if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { - max = tmp; - } - - state.line = startLine + 1; - - token = state.push('heading_open', 'h' + String(level), 1); - token.markup = '########'.slice(0, level); - token.map = [ startLine, state.line ]; - - token = state.push('inline', '', 0); - token.content = state.src.slice(pos, max).trim(); - token.map = [ startLine, state.line ]; - token.children = []; - - token = state.push('heading_close', 'h' + String(level), -1); - token.markup = '########'.slice(0, level); - - return true; -}; - -},{"../common/utils":4}],22:[function(require,module,exports){ -// Horizontal rule - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function hr(state, startLine, endLine, silent) { - var marker, cnt, ch, token, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - marker = state.src.charCodeAt(pos++); - - // Check hr marker - if (marker !== 0x2A/* * */ && - marker !== 0x2D/* - */ && - marker !== 0x5F/* _ */) { - return false; - } - - // markers can be mixed with spaces, but there should be at least 3 of them - - cnt = 1; - while (pos < max) { - ch = state.src.charCodeAt(pos++); - if (ch !== marker && !isSpace(ch)) { return false; } - if (ch === marker) { cnt++; } - } - - if (cnt < 3) { return false; } - - if (silent) { return true; } - - state.line = startLine + 1; - - token = state.push('hr', 'hr', 0); - token.map = [ startLine, state.line ]; - token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); - - return true; -}; - -},{"../common/utils":4}],23:[function(require,module,exports){ -// HTML block - -'use strict'; - - -var block_names = require('../common/html_blocks'); -var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; - -// An array of opening and corresponding closing sequences for html tags, -// last argument defines whether it can terminate a paragraph or not -// -var HTML_SEQUENCES = [ - [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], - [ /^/, true ], - [ /^<\?/, /\?>/, true ], - [ /^/, true ], - [ /^/, true ], - [ new RegExp('^|$))', 'i'), /^$/, true ], - [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] -]; - - -module.exports = function html_block(state, startLine, endLine, silent) { - var i, nextLine, token, lineText, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - if (!state.md.options.html) { return false; } - - if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } - - lineText = state.src.slice(pos, max); - - for (i = 0; i < HTML_SEQUENCES.length; i++) { - if (HTML_SEQUENCES[i][0].test(lineText)) { break; } - } - - if (i === HTML_SEQUENCES.length) { return false; } - - if (silent) { - // true if this sequence can be a terminator, false otherwise - return HTML_SEQUENCES[i][2]; - } - - nextLine = startLine + 1; - - // If we are here - we detected HTML block. - // Let's roll down till block end. - if (!HTML_SEQUENCES[i][1].test(lineText)) { - for (; nextLine < endLine; nextLine++) { - if (state.sCount[nextLine] < state.blkIndent) { break; } - - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - lineText = state.src.slice(pos, max); - - if (HTML_SEQUENCES[i][1].test(lineText)) { - if (lineText.length !== 0) { nextLine++; } - break; + "\\!\"#$%&'()*+,./:;<=>?@[]^_`{|}~-".split("").forEach((function(ch) { + ESCAPED[ch.charCodeAt(0)] = 1; + })); + var _escape = function escape(state, silent) { + var ch1, ch2, origStr, escapedStr, token, pos = state.pos, max = state.posMax; + if (state.src.charCodeAt(pos) !== 92 /* \ */) return false; + pos++; + // '\' at the end of the inline block + if (pos >= max) return false; + ch1 = state.src.charCodeAt(pos); + if (ch1 === 10) { + if (!silent) { + state.push("hardbreak", "br", 0); + } + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch1 = state.src.charCodeAt(pos); + if (!isSpace$2(ch1)) break; + pos++; + } + state.pos = pos; + return true; + } + escapedStr = state.src[pos]; + if (ch1 >= 55296 && ch1 <= 56319 && pos + 1 < max) { + ch2 = state.src.charCodeAt(pos + 1); + if (ch2 >= 56320 && ch2 <= 57343) { + escapedStr += state.src[pos + 1]; + pos++; + } + } + origStr = "\\" + escapedStr; + if (!silent) { + token = state.push("text_special", "", 0); + if (ch1 < 256 && ESCAPED[ch1] !== 0) { + token.content = escapedStr; + } else { + token.content = origStr; + } + token.markup = origStr; + token.info = "escape"; + } + state.pos = pos + 1; + return true; + }; + // Parse backticks + var backticks = function backtick(state, silent) { + var start, max, marker, token, matchStart, matchEnd, openerLength, closerLength, pos = state.pos, ch = state.src.charCodeAt(pos); + if (ch !== 96 /* ` */) { + return false; + } + start = pos; + pos++; + max = state.posMax; + // scan marker length + while (pos < max && state.src.charCodeAt(pos) === 96 /* ` */) { + pos++; + } + marker = state.src.slice(start, pos); + openerLength = marker.length; + if (state.backticksScanned && (state.backticks[openerLength] || 0) <= start) { + if (!silent) state.pending += marker; + state.pos += openerLength; + return true; + } + matchStart = matchEnd = pos; + // Nothing found in the cache, scan until the end of the line (or until marker is found) + while ((matchStart = state.src.indexOf("`", matchEnd)) !== -1) { + matchEnd = matchStart + 1; + // scan marker length + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 96 /* ` */) { + matchEnd++; + } + closerLength = matchEnd - matchStart; + if (closerLength === openerLength) { + // Found matching closer length. + if (!silent) { + token = state.push("code_inline", "code", 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart).replace(/\n/g, " ").replace(/^ (.+) $/, "$1"); + } + state.pos = matchEnd; + return true; + } + // Some different length found, put it in cache as upper limit of where closer can be found + state.backticks[closerLength] = matchStart; + } + // Scanned through the end, didn't find anything + state.backticksScanned = true; + if (!silent) state.pending += marker; + state.pos += openerLength; + return true; + }; + // ~~strike through~~ + // Insert each marker as a separate text token, and add it to delimiter list + + var tokenize$1 = function strikethrough(state, silent) { + var i, scanned, token, len, ch, start = state.pos, marker = state.src.charCodeAt(start); + if (silent) { + return false; + } + if (marker !== 126 /* ~ */) { + return false; + } + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + if (len < 2) { + return false; + } + if (len % 2) { + token = state.push("text", "", 0); + token.content = ch; + len--; + } + for (i = 0; i < len; i += 2) { + token = state.push("text", "", 0); + token.content = ch + ch; + state.delimiters.push({ + marker: marker, + length: 0, + // disable "rule of 3" length checks meant for emphasis + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + state.pos += scanned.length; + return true; + }; + function postProcess$1(state, delimiters) { + var i, j, startDelim, endDelim, token, loneMarkers = [], max = delimiters.length; + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + if (startDelim.marker !== 126 /* ~ */) { + continue; + } + if (startDelim.end === -1) { + continue; + } + endDelim = delimiters[startDelim.end]; + token = state.tokens[startDelim.token]; + token.type = "s_open"; + token.tag = "s"; + token.nesting = 1; + token.markup = "~~"; + token.content = ""; + token = state.tokens[endDelim.token]; + token.type = "s_close"; + token.tag = "s"; + token.nesting = -1; + token.markup = "~~"; + token.content = ""; + if (state.tokens[endDelim.token - 1].type === "text" && state.tokens[endDelim.token - 1].content === "~") { + loneMarkers.push(endDelim.token - 1); + } + } + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + + // So, we have to move all those markers after subsequent s_close tags. + + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + while (j < state.tokens.length && state.tokens[j].type === "s_close") { + j++; + } + j--; + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; } } } - - state.line = nextLine; - - token = state.push('html_block', '', 0); - token.map = [ startLine, nextLine ]; - token.content = state.getLines(startLine, nextLine, state.blkIndent, true); - - return true; -}; - -},{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ -// lheading (---, ===) - -'use strict'; - - -module.exports = function lheading(state, startLine, endLine/*, silent*/) { - var content, terminate, i, l, token, pos, max, level, marker, - nextLine = startLine + 1, oldParentType, - terminatorRules = state.md.block.ruler.getRules('paragraph'); - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - oldParentType = state.parentType; - state.parentType = 'paragraph'; // use paragraph to match terminatorRules - - // jump line-by-line until empty one or EOF - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } - - // - // Check for underline in setext header - // - if (state.sCount[nextLine] >= state.blkIndent) { - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - - if (pos < max) { - marker = state.src.charCodeAt(pos); - - if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { - pos = state.skipChars(pos, marker); - pos = state.skipSpaces(pos); - - if (pos >= max) { - level = (marker === 0x3D/* = */ ? 1 : 2); + // Walk through delimiter list and replace text tokens with tags + + var postProcess_1$1 = function strikethrough(state) { + var curr, tokens_meta = state.tokens_meta, max = state.tokens_meta.length; + postProcess$1(state, state.delimiters); + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess$1(state, tokens_meta[curr].delimiters); + } + } + }; + var strikethrough = { + tokenize: tokenize$1, + postProcess: postProcess_1$1 + }; + // Process *this* and _that_ + // Insert each marker as a separate text token, and add it to delimiter list + + var tokenize = function emphasis(state, silent) { + var i, scanned, token, start = state.pos, marker = state.src.charCodeAt(start); + if (silent) { + return false; + } + if (marker !== 95 /* _ */ && marker !== 42 /* * */) { + return false; + } + scanned = state.scanDelims(state.pos, marker === 42); + for (i = 0; i < scanned.length; i++) { + token = state.push("text", "", 0); + token.content = String.fromCharCode(marker); + state.delimiters.push({ + // Char code of the starting marker (number). + marker: marker, + // Total length of these series of delimiters. + length: scanned.length, + // A position of the token this delimiter corresponds to. + token: state.tokens.length - 1, + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + end: -1, + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + open: scanned.can_open, + close: scanned.can_close + }); + } + state.pos += scanned.length; + return true; + }; + function postProcess(state, delimiters) { + var i, startDelim, endDelim, token, ch, isStrong, max = delimiters.length; + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + if (startDelim.marker !== 95 /* _ */ && startDelim.marker !== 42 /* * */) { + continue; + } + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + endDelim = delimiters[startDelim.end]; + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + + // `whatever` -> `whatever` + + isStrong = i > 0 && delimiters[i - 1].end === startDelim.end + 1 && + // check that first two markers match and adjacent + delimiters[i - 1].marker === startDelim.marker && delimiters[i - 1].token === startDelim.token - 1 && + // check that last two markers are adjacent (we can safely assume they match) + delimiters[startDelim.end + 1].token === endDelim.token + 1; + ch = String.fromCharCode(startDelim.marker); + token = state.tokens[startDelim.token]; + token.type = isStrong ? "strong_open" : "em_open"; + token.tag = isStrong ? "strong" : "em"; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ""; + token = state.tokens[endDelim.token]; + token.type = isStrong ? "strong_close" : "em_close"; + token.tag = isStrong ? "strong" : "em"; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ""; + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ""; + state.tokens[delimiters[startDelim.end + 1].token].content = ""; + i--; + } + } + } + // Walk through delimiter list and replace text tokens with tags + + var postProcess_1 = function emphasis(state) { + var curr, tokens_meta = state.tokens_meta, max = state.tokens_meta.length; + postProcess(state, state.delimiters); + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } + }; + var emphasis = { + tokenize: tokenize, + postProcess: postProcess_1 + }; + var normalizeReference$1 = utils.normalizeReference; + var isSpace$1 = utils.isSpace; + var link = function link(state, silent) { + var attrs, code, label, labelEnd, labelStart, pos, res, ref, token, href = "", title = "", oldPos = state.pos, max = state.posMax, start = state.pos, parseReference = true; + if (state.src.charCodeAt(state.pos) !== 91 /* [ */) { + return false; + } + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { + return false; + } + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 40 /* ( */) { + // Inline link + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace$1(code) && code !== 10) { + break; + } + } + if (pos >= max) { + return false; + } + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ""; + } + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace$1(code) && code !== 10) { break; } } + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + // [link]( "title" ) + // ^^ skipping these spaces + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace$1(code) && code !== 10) { + break; + } + } + } } - } - - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } - - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; + if (pos >= max || state.src.charCodeAt(pos) !== 41 /* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; } - } - if (terminate) { break; } - } - - if (!level) { - // Didn't find valid underline - return false; - } - - content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); - - state.line = nextLine + 1; - - token = state.push('heading_open', 'h' + String(level), 1); - token.markup = String.fromCharCode(marker); - token.map = [ startLine, state.line ]; - - token = state.push('inline', '', 0); - token.content = content; - token.map = [ startLine, state.line - 1 ]; - token.children = []; - - token = state.push('heading_close', 'h' + String(level), -1); - token.markup = String.fromCharCode(marker); - - state.parentType = oldParentType; - - return true; -}; - -},{}],25:[function(require,module,exports){ -// Lists - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -// Search `[-+*][\n ]`, returns next pos after marker on success -// or -1 on fail. -function skipBulletListMarker(state, startLine) { - var marker, pos, max, ch; - - pos = state.bMarks[startLine] + state.tShift[startLine]; - max = state.eMarks[startLine]; - - marker = state.src.charCodeAt(pos++); - // Check bullet - if (marker !== 0x2A/* * */ && - marker !== 0x2D/* - */ && - marker !== 0x2B/* + */) { - return -1; - } - - if (pos < max) { - ch = state.src.charCodeAt(pos); - - if (!isSpace(ch)) { - // " -test " - is not a list item - return -1; - } - } - - return pos; -} - -// Search `\d+[.)][\n ]`, returns next pos after marker on success -// or -1 on fail. -function skipOrderedListMarker(state, startLine) { - var ch, - start = state.bMarks[startLine] + state.tShift[startLine], - pos = start, - max = state.eMarks[startLine]; - - // List marker should have at least 2 chars (digit + dot) - if (pos + 1 >= max) { return -1; } - - ch = state.src.charCodeAt(pos++); - - if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } - - for (;;) { - // EOL -> fail - if (pos >= max) { return -1; } - - ch = state.src.charCodeAt(pos++); - - if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { - - // List marker should have no more than 9 digits - // (prevents integer overflow in browsers) - if (pos - start >= 10) { return -1; } - - continue; - } - - // found valid marker - if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { - break; - } - - return -1; - } - - - if (pos < max) { - ch = state.src.charCodeAt(pos); - - if (!isSpace(ch)) { - // " 1.test " - is not a list item - return -1; - } - } - return pos; -} - -function markTightParagraphs(state, idx) { - var i, l, - level = state.level + 2; - - for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { - if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { - state.tokens[i + 2].hidden = true; - state.tokens[i].hidden = true; - i += 2; - } - } -} - - -module.exports = function list(state, startLine, endLine, silent) { - var ch, - contentStart, - i, - indent, - indentAfterMarker, - initial, - isOrdered, - itemLines, - l, - listLines, - listTokIdx, - markerCharCode, - markerValue, - max, - nextLine, - offset, - oldListIndent, - oldParentType, - oldSCount, - oldTShift, - oldTight, - pos, - posAfterMarker, - prevEmptyEnd, - start, - terminate, - terminatorRules, - token, - isTerminatingParagraph = false, - tight = true; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - // Special case: - // - item 1 - // - item 2 - // - item 3 - // - item 4 - // - this one is a paragraph continuation - if (state.listIndent >= 0 && - state.sCount[startLine] - state.listIndent >= 4 && - state.sCount[startLine] < state.blkIndent) { - return false; - } - - // limit conditions when list can interrupt - // a paragraph (validation mode only) - if (silent && state.parentType === 'paragraph') { - // Next list item should still terminate previous list item; - // - // This code can fail if plugins use blkIndent as well as lists, - // but I hope the spec gets fixed long before that happens. - // - if (state.tShift[startLine] >= state.blkIndent) { - isTerminatingParagraph = true; - } - } - - // Detect list type and position after marker - if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { - isOrdered = true; - start = state.bMarks[startLine] + state.tShift[startLine]; - markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); - - // If we're starting a new ordered list right after - // a paragraph, it should start with 1. - if (isTerminatingParagraph && markerValue !== 1) return false; - - } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { - isOrdered = false; - - } else { - return false; - } - - // If we're starting a new unordered list right after - // a paragraph, first line should not be empty. - if (isTerminatingParagraph) { - if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; - } - - // We should terminate list on style change. Remember first one to compare. - markerCharCode = state.src.charCodeAt(posAfterMarker - 1); - - // For validation mode we can terminate immediately - if (silent) { return true; } - - // Start list - listTokIdx = state.tokens.length; - - if (isOrdered) { - token = state.push('ordered_list_open', 'ol', 1); - if (markerValue !== 1) { - token.attrs = [ [ 'start', markerValue ] ]; - } - - } else { - token = state.push('bullet_list_open', 'ul', 1); - } - - token.map = listLines = [ startLine, 0 ]; - token.markup = String.fromCharCode(markerCharCode); - - // - // Iterate list items - // - - nextLine = startLine; - prevEmptyEnd = false; - terminatorRules = state.md.block.ruler.getRules('list'); - - oldParentType = state.parentType; - state.parentType = 'list'; - - while (nextLine < endLine) { - pos = posAfterMarker; - max = state.eMarks[nextLine]; - - initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); - - while (pos < max) { - ch = state.src.charCodeAt(pos); - - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[nextLine]) % 4; - } else if (ch === 0x20) { - offset++; - } else { - break; - } - pos++; } - - contentStart = pos; - - if (contentStart >= max) { - // trimming space in "- \n 3" case, indent is 1 here - indentAfterMarker = 1; - } else { - indentAfterMarker = offset - initial; - } - - // If we have more than 4 spaces, the indent is 1 - // (the rest is just indented code block) - if (indentAfterMarker > 4) { indentAfterMarker = 1; } - - // " - test" - // ^^^^^ - calculating total length of this thing - indent = initial + indentAfterMarker; - - // Run subparser & write tokens - token = state.push('list_item_open', 'li', 1); - token.markup = String.fromCharCode(markerCharCode); - token.map = itemLines = [ startLine, 0 ]; - - // change current state, then restore it after parser subcall - oldTight = state.tight; - oldTShift = state.tShift[startLine]; - oldSCount = state.sCount[startLine]; - - // - example list - // ^ listIndent position will be here - // ^ blkIndent position will be here - // - oldListIndent = state.listIndent; - state.listIndent = state.blkIndent; - state.blkIndent = indent; - - state.tight = true; - state.tShift[startLine] = contentStart - state.bMarks[startLine]; - state.sCount[startLine] = offset; - - if (contentStart >= max && state.isEmpty(startLine + 1)) { - // workaround for this case - // (list item is empty, list terminates before "foo"): - // ~~~~~~~~ - // - - // - // foo - // ~~~~~~~~ - state.line = Math.min(state.line + 2, endLine); - } else { - state.md.block.tokenize(state, startLine, endLine, true); - } - - // If any of list item is tight, mark list as tight - if (!state.tight || prevEmptyEnd) { - tight = false; - } - // Item become loose if finish with empty line, - // but we should filter last element, because it means list finish - prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); - - state.blkIndent = state.listIndent; - state.listIndent = oldListIndent; - state.tShift[startLine] = oldTShift; - state.sCount[startLine] = oldSCount; - state.tight = oldTight; - - token = state.push('list_item_close', 'li', -1); - token.markup = String.fromCharCode(markerCharCode); - - nextLine = startLine = state.line; - itemLines[1] = nextLine; - contentStart = state.bMarks[startLine]; - - if (nextLine >= endLine) { break; } - - // - // Try to check if list is terminated or continued. - // - if (state.sCount[nextLine] < state.blkIndent) { break; } - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { break; } - - // fail if terminating block found - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - - // fail if list has another type - if (isOrdered) { - posAfterMarker = skipOrderedListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } else { - posAfterMarker = skipBulletListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } - - if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } - } - - // Finalize list - if (isOrdered) { - token = state.push('ordered_list_close', 'ol', -1); - } else { - token = state.push('bullet_list_close', 'ul', -1); - } - token.markup = String.fromCharCode(markerCharCode); - - listLines[1] = nextLine; - state.line = nextLine; - - state.parentType = oldParentType; - - // mark paragraphs tight if needed - if (tight) { - markTightParagraphs(state, listTokIdx); - } - - return true; -}; - -},{"../common/utils":4}],26:[function(require,module,exports){ -// Paragraph - -'use strict'; - - -module.exports = function paragraph(state, startLine/*, endLine*/) { - var content, terminate, i, l, token, oldParentType, - nextLine = startLine + 1, - terminatorRules = state.md.block.ruler.getRules('paragraph'), - endLine = state.lineMax; - - oldParentType = state.parentType; - state.parentType = 'paragraph'; - - // jump line-by-line until empty one or EOF - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } - - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } - - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - } - - content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); - - state.line = nextLine; - - token = state.push('paragraph_open', 'p', 1); - token.map = [ startLine, state.line ]; - - token = state.push('inline', '', 0); - token.content = content; - token.map = [ startLine, state.line ]; - token.children = []; - - token = state.push('paragraph_close', 'p', -1); - - state.parentType = oldParentType; - - return true; -}; - -},{}],27:[function(require,module,exports){ -'use strict'; - - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function reference(state, startLine, _endLine, silent) { - var ch, - destEndPos, - destEndLineNo, - endLine, - href, - i, - l, - label, - labelEnd, - oldParentType, - res, - start, - str, - terminate, - terminatorRules, - title, - lines = 0, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine], - nextLine = startLine + 1; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } - - // Simple check to quickly interrupt scan on [link](url) at the start of line. - // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 - while (++pos < max) { - if (state.src.charCodeAt(pos) === 0x5D /* ] */ && - state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { - if (pos + 1 === max) { return false; } - if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } - break; - } - } - - endLine = state.lineMax; - - // jump line-by-line until empty one or EOF - terminatorRules = state.md.block.ruler.getRules('reference'); - - oldParentType = state.parentType; - state.parentType = 'reference'; - - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } - - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } - - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - } - - str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); - max = str.length; - - for (pos = 1; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x5B /* [ */) { - return false; - } else if (ch === 0x5D /* ] */) { - labelEnd = pos; - break; - } else if (ch === 0x0A /* \n */) { - lines++; - } else if (ch === 0x5C /* \ */) { - pos++; - if (pos < max && str.charCodeAt(pos) === 0x0A) { - lines++; - } - } - } - - if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } - - // [label]: destination 'title' - // ^^^ skip optional whitespace here - for (pos = labelEnd + 2; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x0A) { - lines++; - } else if (isSpace(ch)) { - /*eslint no-empty:0*/ - } else { - break; - } - } - - // [label]: destination 'title' - // ^^^^^^^^^^^ parse this - res = state.md.helpers.parseLinkDestination(str, pos, max); - if (!res.ok) { return false; } - - href = state.md.normalizeLink(res.str); - if (!state.md.validateLink(href)) { return false; } - - pos = res.pos; - lines += res.lines; - - // save cursor state, we could require to rollback later - destEndPos = pos; - destEndLineNo = lines; - - // [label]: destination 'title' - // ^^^ skipping those spaces - start = pos; - for (; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x0A) { - lines++; - } else if (isSpace(ch)) { - /*eslint no-empty:0*/ - } else { - break; - } - } - - // [label]: destination 'title' - // ^^^^^^^ parse this - res = state.md.helpers.parseLinkTitle(str, pos, max); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - lines += res.lines; - } else { - title = ''; - pos = destEndPos; - lines = destEndLineNo; - } - - // skip trailing spaces until the rest of the line - while (pos < max) { - ch = str.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } - - if (pos < max && str.charCodeAt(pos) !== 0x0A) { - if (title) { - // garbage at the end of the line after title, - // but it could still be a valid reference if we roll back - title = ''; - pos = destEndPos; - lines = destEndLineNo; - while (pos < max) { - ch = str.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } - } - } - - if (pos < max && str.charCodeAt(pos) !== 0x0A) { - // garbage at the end of the line - return false; - } - - label = normalizeReference(str.slice(1, labelEnd)); - if (!label) { - // CommonMark 0.20 disallows empty labels - return false; - } - - // Reference can not terminate anything. This check is for safety only. - /*istanbul ignore if*/ - if (silent) { return true; } - - if (typeof state.env.references === 'undefined') { - state.env.references = {}; - } - if (typeof state.env.references[label] === 'undefined') { - state.env.references[label] = { title: title, href: href }; - } - - state.parentType = oldParentType; - - state.line = startLine + lines + 1; - return true; -}; - -},{"../common/utils":4}],28:[function(require,module,exports){ -// Parser state class - -'use strict'; - -var Token = require('../token'); -var isSpace = require('../common/utils').isSpace; - - -function StateBlock(src, md, env, tokens) { - var ch, s, start, pos, len, indent, offset, indent_found; - - this.src = src; - - // link to parser instance - this.md = md; - - this.env = env; - - // - // Internal state vartiables - // - - this.tokens = tokens; - - this.bMarks = []; // line begin offsets for fast jumps - this.eMarks = []; // line end offsets for fast jumps - this.tShift = []; // offsets of the first non-space characters (tabs not expanded) - this.sCount = []; // indents for each line (tabs expanded) - - // An amount of virtual spaces (tabs expanded) between beginning - // of each line (bMarks) and real beginning of that line. - // - // It exists only as a hack because blockquotes override bMarks - // losing information in the process. - // - // It's used only when expanding tabs, you can think about it as - // an initial tab length, e.g. bsCount=21 applied to string `\t123` - // means first tab should be expanded to 4-21%4 === 3 spaces. - // - this.bsCount = []; - - // block parser variables - this.blkIndent = 0; // required block content indent (for example, if we are - // inside a list, it would be positioned after list marker) - this.line = 0; // line index in src - this.lineMax = 0; // lines count - this.tight = false; // loose/tight mode for lists - this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) - this.listIndent = -1; // indent of the current list block (-1 if there isn't any) - - // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' - // used in lists to determine if they interrupt a paragraph - this.parentType = 'root'; - - this.level = 0; - - // renderer - this.result = ''; - - // Create caches - // Generate markers. - s = this.src; - indent_found = false; - - for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { - ch = s.charCodeAt(pos); - - if (!indent_found) { - if (isSpace(ch)) { - indent++; - - if (ch === 0x09) { - offset += 4 - offset % 4; - } else { - offset++; - } - continue; - } else { - indent_found = true; - } - } - - if (ch === 0x0A || pos === len - 1) { - if (ch !== 0x0A) { pos++; } - this.bMarks.push(start); - this.eMarks.push(pos); - this.tShift.push(indent); - this.sCount.push(offset); - this.bsCount.push(0); - - indent_found = false; - indent = 0; - offset = 0; - start = pos + 1; - } - } - - // Push fake entry to simplify cache bounds checks - this.bMarks.push(s.length); - this.eMarks.push(s.length); - this.tShift.push(0); - this.sCount.push(0); - this.bsCount.push(0); - - this.lineMax = this.bMarks.length - 1; // don't count last fake line -} - -// Push new token to "stream". -// -StateBlock.prototype.push = function (type, tag, nesting) { - var token = new Token(type, tag, nesting); - token.block = true; - - if (nesting < 0) this.level--; // closing tag - token.level = this.level; - if (nesting > 0) this.level++; // opening tag - - this.tokens.push(token); - return token; -}; - -StateBlock.prototype.isEmpty = function isEmpty(line) { - return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; -}; - -StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { - for (var max = this.lineMax; from < max; from++) { - if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { - break; - } - } - return from; -}; - -// Skip spaces from given position. -StateBlock.prototype.skipSpaces = function skipSpaces(pos) { - var ch; - - for (var max = this.src.length; pos < max; pos++) { - ch = this.src.charCodeAt(pos); - if (!isSpace(ch)) { break; } - } - return pos; -}; - -// Skip spaces from given position in reverse. -StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { - if (pos <= min) { return pos; } - - while (pos > min) { - if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } - } - return pos; -}; - -// Skip char codes from given position -StateBlock.prototype.skipChars = function skipChars(pos, code) { - for (var max = this.src.length; pos < max; pos++) { - if (this.src.charCodeAt(pos) !== code) { break; } - } - return pos; -}; - -// Skip char codes reverse from given position - 1 -StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { - if (pos <= min) { return pos; } - - while (pos > min) { - if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } - } - return pos; -}; - -// cut lines range from source. -StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { - var i, lineIndent, ch, first, last, queue, lineStart, - line = begin; - - if (begin >= end) { - return ''; - } - - queue = new Array(end - begin); - - for (i = 0; line < end; line++, i++) { - lineIndent = 0; - lineStart = first = this.bMarks[line]; - - if (line + 1 < end || keepLastLF) { - // No need for bounds check because we have fake entry on tail. - last = this.eMarks[line] + 1; - } else { - last = this.eMarks[line]; - } - - while (first < last && lineIndent < indent) { - ch = this.src.charCodeAt(first); - - if (isSpace(ch)) { - if (ch === 0x09) { - lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; - } else { - lineIndent++; - } - } else if (first - lineStart < this.tShift[line]) { - // patched tShift masked characters to look like spaces (blockquotes, list markers) - lineIndent++; - } else { - break; - } - - first++; - } - - if (lineIndent > indent) { - // partially expanding tabs in code blocks, e.g '\t\tfoobar' - // with indent=2 becomes ' \tfoobar' - queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); - } else { - queue[i] = this.src.slice(first, last); - } - } - - return queue.join(''); -}; - -// re-export Token class to use in block rules -StateBlock.prototype.Token = Token; - - -module.exports = StateBlock; - -},{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ -// GFM table, non-standard - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -function getLine(state, line) { - var pos = state.bMarks[line] + state.blkIndent, - max = state.eMarks[line]; - - return state.src.substr(pos, max - pos); -} - -function escapedSplit(str) { - var result = [], - pos = 0, - max = str.length, - ch, - escapes = 0, - lastPos = 0, - backTicked = false, - lastBackTick = 0; - - ch = str.charCodeAt(pos); - - while (pos < max) { - if (ch === 0x60/* ` */) { - if (backTicked) { - // make \` close code sequence, but not open it; - // the reason is: `\` is correct code block - backTicked = false; - lastBackTick = pos; - } else if (escapes % 2 === 0) { - backTicked = true; - lastBackTick = pos; - } - } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { - result.push(str.substring(lastPos, pos)); - lastPos = pos + 1; - } - - if (ch === 0x5c/* \ */) { - escapes++; - } else { - escapes = 0; - } - - pos++; - - // If there was an un-closed backtick, go back to just after - // the last backtick, but as if it was a normal character - if (pos === max && backTicked) { - backTicked = false; - pos = lastBackTick + 1; - } - - ch = str.charCodeAt(pos); - } - - result.push(str.substring(lastPos)); - - return result; -} - - -module.exports = function table(state, startLine, endLine, silent) { - var ch, lineText, pos, i, nextLine, columns, columnCount, token, - aligns, t, tableLines, tbodyLines; - - // should have at least two lines - if (startLine + 2 > endLine) { return false; } - - nextLine = startLine + 1; - - if (state.sCount[nextLine] < state.blkIndent) { return false; } - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } - - // first character of the second line should be '|', '-', ':', - // and no other characters are allowed but spaces; - // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp - - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - if (pos >= state.eMarks[nextLine]) { return false; } - - ch = state.src.charCodeAt(pos++); - if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } - - while (pos < state.eMarks[nextLine]) { - ch = state.src.charCodeAt(pos); - - if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } - - pos++; - } - - lineText = getLine(state, startLine + 1); - - columns = lineText.split('|'); - aligns = []; - for (i = 0; i < columns.length; i++) { - t = columns[i].trim(); - if (!t) { - // allow empty columns before and after table, but not in between columns; - // e.g. allow ` |---| `, disallow ` ---||--- ` - if (i === 0 || i === columns.length - 1) { - continue; - } else { + if (parseReference) { + // Link reference + if (typeof state.env.references === "undefined") { return false; } + if (pos < max && state.src.charCodeAt(pos) === 91 /* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { + label = state.src.slice(labelStart, labelEnd); + } + ref = state.env.references[normalizeReference$1(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; } - - if (!/^:?-+:?$/.test(t)) { return false; } - if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { - aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); - } else if (t.charCodeAt(0) === 0x3A/* : */) { - aligns.push('left'); + + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + token = state.push("link_open", "a", 1); + token.attrs = attrs = [ [ "href", href ] ]; + if (title) { + attrs.push([ "title", title ]); + } + state.linkLevel++; + state.md.inline.tokenize(state); + state.linkLevel--; + token = state.push("link_close", "a", -1); + } + state.pos = pos; + state.posMax = max; + return true; + }; + var normalizeReference = utils.normalizeReference; + var isSpace = utils.isSpace; + var image = function image(state, silent) { + var attrs, code, content, label, labelEnd, labelStart, pos, ref, res, title, token, tokens, start, href = "", oldPos = state.pos, max = state.posMax; + if (state.src.charCodeAt(state.pos) !== 33 /* ! */) { + return false; + } + if (state.src.charCodeAt(state.pos + 1) !== 91 /* [ */) { + return false; + } + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { + return false; + } + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 40 /* ( */) { + // Inline link + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 10) { + break; + } + } + if (pos >= max) { + return false; + } + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ""; + } + } + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 10) { + break; + } + } + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + // [link]( "title" ) + // ^^ skipping these spaces + for (;pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 10) { + break; + } + } + } else { + title = ""; + } + if (pos >= max || state.src.charCodeAt(pos) !== 41 /* ) */) { + state.pos = oldPos; + return false; + } + pos++; } else { - aligns.push(''); - } - } - - lineText = getLine(state, startLine).trim(); - if (lineText.indexOf('|') === -1) { return false; } - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); - - // header row will define an amount of columns in the entire table, - // and align row shouldn't be smaller than that (the rest of the rows can) - columnCount = columns.length; - if (columnCount > aligns.length) { return false; } - - if (silent) { return true; } - - token = state.push('table_open', 'table', 1); - token.map = tableLines = [ startLine, 0 ]; - - token = state.push('thead_open', 'thead', 1); - token.map = [ startLine, startLine + 1 ]; - - token = state.push('tr_open', 'tr', 1); - token.map = [ startLine, startLine + 1 ]; - - for (i = 0; i < columns.length; i++) { - token = state.push('th_open', 'th', 1); - token.map = [ startLine, startLine + 1 ]; - if (aligns[i]) { - token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; - } - - token = state.push('inline', '', 0); - token.content = columns[i].trim(); - token.map = [ startLine, startLine + 1 ]; - token.children = []; - - token = state.push('th_close', 'th', -1); - } - - token = state.push('tr_close', 'tr', -1); - token = state.push('thead_close', 'thead', -1); - - token = state.push('tbody_open', 'tbody', 1); - token.map = tbodyLines = [ startLine + 2, 0 ]; - - for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { - if (state.sCount[nextLine] < state.blkIndent) { break; } - - lineText = getLine(state, nextLine).trim(); - if (lineText.indexOf('|') === -1) { break; } - if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } - columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); - - token = state.push('tr_open', 'tr', 1); - for (i = 0; i < columnCount; i++) { - token = state.push('td_open', 'td', 1); - if (aligns[i]) { - token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + // Link reference + if (typeof state.env.references === "undefined") { + return false; } - - token = state.push('inline', '', 0); - token.content = columns[i] ? columns[i].trim() : ''; - token.children = []; - - token = state.push('td_close', 'td', -1); - } - token = state.push('tr_close', 'tr', -1); - } - token = state.push('tbody_close', 'tbody', -1); - token = state.push('table_close', 'table', -1); - - tableLines[1] = tbodyLines[1] = nextLine; - state.line = nextLine; - return true; -}; - -},{"../common/utils":4}],30:[function(require,module,exports){ -'use strict'; - - -module.exports = function block(state) { - var token; - - if (state.inlineMode) { - token = new state.Token('inline', '', 0); - token.content = state.src; - token.map = [ 0, 1 ]; - token.children = []; - state.tokens.push(token); - } else { - state.md.block.parse(state.src, state.md, state.env, state.tokens); - } -}; - -},{}],31:[function(require,module,exports){ -'use strict'; - -module.exports = function inline(state) { - var tokens = state.tokens, tok, i, l; - - // Parse inlines - for (i = 0, l = tokens.length; i < l; i++) { - tok = tokens[i]; - if (tok.type === 'inline') { - state.md.inline.parse(tok.content, state.md, state.env, tok.children); - } - } -}; - -},{}],32:[function(require,module,exports){ -// Replace link-like texts with link nodes. -// -// Currently restricted by `md.validateLink()` to http/https/ftp -// -'use strict'; - - -var arrayReplaceAt = require('../common/utils').arrayReplaceAt; - - -function isLinkOpen(str) { - return /^\s]/i.test(str); -} -function isLinkClose(str) { - return /^<\/a\s*>/i.test(str); -} - - -module.exports = function linkify(state) { - var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, - level, htmlLinkLevel, url, fullUrl, urlText, - blockTokens = state.tokens, - links; - - if (!state.md.options.linkify) { return; } - - for (j = 0, l = blockTokens.length; j < l; j++) { - if (blockTokens[j].type !== 'inline' || - !state.md.linkify.pretest(blockTokens[j].content)) { - continue; - } - - tokens = blockTokens[j].children; - - htmlLinkLevel = 0; - - // We scan from the end, to keep position when new tags added. - // Use reversed logic in links start/end match - for (i = tokens.length - 1; i >= 0; i--) { - currentToken = tokens[i]; - - // Skip content of markdown links - if (currentToken.type === 'link_close') { - i--; - while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { - i--; + if (pos < max && state.src.charCodeAt(pos) === 91 /* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; } - continue; - } - - // Skip content of html tag links - if (currentToken.type === 'html_inline') { - if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { - htmlLinkLevel--; - } - if (isLinkClose(currentToken.content)) { - htmlLinkLevel++; - } - } - if (htmlLinkLevel > 0) { continue; } - - if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { - - text = currentToken.content; - links = state.md.linkify.match(text); - - // Now split string to nodes - nodes = []; - level = currentToken.level; - lastPos = 0; - - for (ln = 0; ln < links.length; ln++) { - - url = links[ln].url; - fullUrl = state.md.normalizeLink(url); - if (!state.md.validateLink(fullUrl)) { continue; } - - urlText = links[ln].text; - - // Linkifier might send raw hostnames like "example.com", where url - // starts with domain name. So we prepend http:// in those cases, - // and remove it afterwards. - // - if (!links[ln].schema) { - urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); - } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { - urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); - } else { - urlText = state.md.normalizeLinkText(urlText); - } - - pos = links[ln].index; - - if (pos > lastPos) { - token = new state.Token('text', '', 0); - token.content = text.slice(lastPos, pos); - token.level = level; - nodes.push(token); - } - - token = new state.Token('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.level = level++; - token.markup = 'linkify'; - token.info = 'auto'; - nodes.push(token); - - token = new state.Token('text', '', 0); - token.content = urlText; - token.level = level; - nodes.push(token); - - token = new state.Token('link_close', 'a', -1); - token.level = --level; - token.markup = 'linkify'; - token.info = 'auto'; - nodes.push(token); - - lastPos = links[ln].lastIndex; - } - if (lastPos < text.length) { - token = new state.Token('text', '', 0); - token.content = text.slice(lastPos); - token.level = level; - nodes.push(token); - } - - // replace current node - blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); - } - } - } -}; - -},{"../common/utils":4}],33:[function(require,module,exports){ -// Normalize input string - -'use strict'; - - -// https://spec.commonmark.org/0.29/#line-ending -var NEWLINES_RE = /\r\n?|\n/g; -var NULL_RE = /\0/g; - - -module.exports = function normalize(state) { - var str; - - // Normalize newlines - str = state.src.replace(NEWLINES_RE, '\n'); - - // Replace NULL characters - str = str.replace(NULL_RE, '\uFFFD'); - - state.src = str; -}; - -},{}],34:[function(require,module,exports){ -// Simple typographic replacements -// -// (c) (C) → © -// (tm) (TM) → ™ -// (r) (R) → ® -// +- → ± -// (p) (P) -> § -// ... → … (also ?.... → ?.., !.... → !..) -// ???????? → ???, !!!!! → !!!, `,,` → `,` -// -- → –, --- → — -// -'use strict'; - -// TODO: -// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ -// - miltiplication 2 x 4 -> 2 × 4 - -var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; - -// Workaround for phantomjs - need regex without /g flag, -// or root check will fail every second time -var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; - -var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; -var SCOPED_ABBR = { - c: '©', - r: '®', - p: '§', - tm: '™' -}; - -function replaceFn(match, name) { - return SCOPED_ABBR[name.toLowerCase()]; -} - -function replace_scoped(inlineTokens) { - var i, token, inside_autolink = 0; - - for (i = inlineTokens.length - 1; i >= 0; i--) { - token = inlineTokens[i]; - - if (token.type === 'text' && !inside_autolink) { - token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); - } - - if (token.type === 'link_open' && token.info === 'auto') { - inside_autolink--; - } - - if (token.type === 'link_close' && token.info === 'auto') { - inside_autolink++; - } - } -} - -function replace_rare(inlineTokens) { - var i, token, inside_autolink = 0; - - for (i = inlineTokens.length - 1; i >= 0; i--) { - token = inlineTokens[i]; - - if (token.type === 'text' && !inside_autolink) { - if (RARE_RE.test(token.content)) { - token.content = token.content - .replace(/\+-/g, '±') - // .., ..., ....... -> … - // but ?..... & !..... -> ?.. & !.. - .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') - .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') - // em-dash - .replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2') - // en-dash - .replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2') - .replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2'); - } - } - - if (token.type === 'link_open' && token.info === 'auto') { - inside_autolink--; - } - - if (token.type === 'link_close' && token.info === 'auto') { - inside_autolink++; - } - } -} - - -module.exports = function replace(state) { - var blkIdx; - - if (!state.md.options.typographer) { return; } - - for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { - - if (state.tokens[blkIdx].type !== 'inline') { continue; } - - if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { - replace_scoped(state.tokens[blkIdx].children); - } - - if (RARE_RE.test(state.tokens[blkIdx].content)) { - replace_rare(state.tokens[blkIdx].children); - } - - } -}; - -},{}],35:[function(require,module,exports){ -// Convert straight quotation marks to typographic ones -// -'use strict'; - - -var isWhiteSpace = require('../common/utils').isWhiteSpace; -var isPunctChar = require('../common/utils').isPunctChar; -var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; - -var QUOTE_TEST_RE = /['"]/; -var QUOTE_RE = /['"]/g; -var APOSTROPHE = '\u2019'; /* ’ */ - - -function replaceAt(str, index, ch) { - return str.substr(0, index) + ch + str.substr(index + 1); -} - -function process_inlines(tokens, state) { - var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, - isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, - canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; - - stack = []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - - thisLevel = tokens[i].level; - - for (j = stack.length - 1; j >= 0; j--) { - if (stack[j].level <= thisLevel) { break; } - } - stack.length = j + 1; - - if (token.type !== 'text') { continue; } - - text = token.content; - pos = 0; - max = text.length; - - /*eslint no-labels:0,block-scoped-var:0*/ - OUTER: - while (pos < max) { - QUOTE_RE.lastIndex = pos; - t = QUOTE_RE.exec(text); - if (!t) { break; } - - canOpen = canClose = true; - pos = t.index + 1; - isSingle = (t[0] === "'"); - - // Find previous character, - // default to space if it's the beginning of the line - // - lastChar = 0x20; - - if (t.index - 1 >= 0) { - lastChar = text.charCodeAt(t.index - 1); } else { - for (j = i - 1; j >= 0; j--) { - if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 - if (tokens[j].type !== 'text') continue; - - lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); - break; - } + pos = labelEnd + 1; } - - // Find next character, - // default to space if it's the end of the line - // - nextChar = 0x20; - - if (pos < max) { - nextChar = text.charCodeAt(pos); - } else { - for (j = i + 1; j < tokens.length; j++) { - if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 - if (tokens[j].type !== 'text') continue; - - nextChar = tokens[j].content.charCodeAt(0); - break; - } + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { + label = state.src.slice(labelStart, labelEnd); } - - isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); - isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); - - isLastWhiteSpace = isWhiteSpace(lastChar); - isNextWhiteSpace = isWhiteSpace(nextChar); - - if (isNextWhiteSpace) { - canOpen = false; - } else if (isNextPunctChar) { - if (!(isLastWhiteSpace || isLastPunctChar)) { - canOpen = false; - } + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; } - - if (isLastWhiteSpace) { - canClose = false; - } else if (isLastPunctChar) { - if (!(isNextWhiteSpace || isNextPunctChar)) { - canClose = false; - } - } - - if (nextChar === 0x22 /* " */ && t[0] === '"') { - if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { - // special case: 1"" - count first quote as an inch - canClose = canOpen = false; - } - } - - if (canOpen && canClose) { - // treat this as the middle of the word - canOpen = false; - canClose = isNextPunctChar; - } - - if (!canOpen && !canClose) { - // middle of word - if (isSingle) { - token.content = replaceAt(token.content, t.index, APOSTROPHE); - } - continue; - } - - if (canClose) { - // this could be a closing quote, rewind the stack to get a match - for (j = stack.length - 1; j >= 0; j--) { - item = stack[j]; - if (stack[j].level < thisLevel) { break; } - if (item.single === isSingle && stack[j].level === thisLevel) { - item = stack[j]; - - if (isSingle) { - openQuote = state.md.options.quotes[2]; - closeQuote = state.md.options.quotes[3]; - } else { - openQuote = state.md.options.quotes[0]; - closeQuote = state.md.options.quotes[1]; - } - - // replace token.content *before* tokens[item.token].content, - // because, if they are pointing at the same token, replaceAt - // could mess up indices when quote length != 1 - token.content = replaceAt(token.content, t.index, closeQuote); - tokens[item.token].content = replaceAt( - tokens[item.token].content, item.pos, openQuote); - - pos += closeQuote.length - 1; - if (item.token === i) { pos += openQuote.length - 1; } - - text = token.content; - max = text.length; - - stack.length = j; - continue OUTER; - } - } - } - - if (canOpen) { - stack.push({ - token: i, - pos: t.index, - single: isSingle, - level: thisLevel - }); - } else if (canClose && isSingle) { - token.content = replaceAt(token.content, t.index, APOSTROPHE); + href = ref.href; + title = ref.title; + } + + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + state.md.inline.parse(content, state.md, state.env, tokens = []); + token = state.push("image", "img", 0); + token.attrs = attrs = [ [ "src", href ], [ "alt", "" ] ]; + token.children = tokens; + token.content = content; + if (title) { + attrs.push([ "title", title ]); } } - } -} - - -module.exports = function smartquotes(state) { - /*eslint max-depth:0*/ - var blkIdx; - - if (!state.md.options.typographer) { return; } - - for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { - - if (state.tokens[blkIdx].type !== 'inline' || - !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { - continue; - } - - process_inlines(state.tokens[blkIdx].children, state); - } -}; - -},{"../common/utils":4}],36:[function(require,module,exports){ -// Core state object -// -'use strict'; - -var Token = require('../token'); - - -function StateCore(src, md, env) { - this.src = src; - this.env = env; - this.tokens = []; - this.inlineMode = false; - this.md = md; // link to parser instance -} - -// re-export Token class to use in core rules -StateCore.prototype.Token = Token; - - -module.exports = StateCore; - -},{"../token":51}],37:[function(require,module,exports){ -// Process autolinks '' - -'use strict'; - - -/*eslint max-len:0*/ -var EMAIL_RE = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/; -var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; - - -module.exports = function autolink(state, silent) { - var tail, linkMatch, emailMatch, url, fullUrl, token, - pos = state.pos; - - if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } - - tail = state.src.slice(pos); - - if (tail.indexOf('>') < 0) { return false; } - - if (AUTOLINK_RE.test(tail)) { - linkMatch = tail.match(AUTOLINK_RE); - - url = linkMatch[0].slice(1, -1); - fullUrl = state.md.normalizeLink(url); - if (!state.md.validateLink(fullUrl)) { return false; } - - if (!silent) { - token = state.push('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.markup = 'autolink'; - token.info = 'auto'; - - token = state.push('text', '', 0); - token.content = state.md.normalizeLinkText(url); - - token = state.push('link_close', 'a', -1); - token.markup = 'autolink'; - token.info = 'auto'; - } - - state.pos += linkMatch[0].length; + state.pos = pos; + state.posMax = max; return true; - } - - if (EMAIL_RE.test(tail)) { - emailMatch = tail.match(EMAIL_RE); - - url = emailMatch[0].slice(1, -1); - fullUrl = state.md.normalizeLink('mailto:' + url); - if (!state.md.validateLink(fullUrl)) { return false; } - - if (!silent) { - token = state.push('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.markup = 'autolink'; - token.info = 'auto'; - - token = state.push('text', '', 0); - token.content = state.md.normalizeLinkText(url); - - token = state.push('link_close', 'a', -1); - token.markup = 'autolink'; - token.info = 'auto'; + }; + // Process autolinks '' + /*eslint max-len:0*/ var EMAIL_RE = /^([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/; + var AUTOLINK_RE = /^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$/; + var autolink = function autolink(state, silent) { + var url, fullUrl, token, ch, start, max, pos = state.pos; + if (state.src.charCodeAt(pos) !== 60 /* < */) { + return false; } - - state.pos += emailMatch[0].length; - return true; - } - - return false; -}; - -},{}],38:[function(require,module,exports){ -// Parse backticks - -'use strict'; - -module.exports = function backtick(state, silent) { - var start, max, marker, matchStart, matchEnd, token, - pos = state.pos, + start = state.pos; + max = state.posMax; + for (;;) { + if (++pos >= max) return false; ch = state.src.charCodeAt(pos); - - if (ch !== 0x60/* ` */) { return false; } - - start = pos; - pos++; - max = state.posMax; - - while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } - - marker = state.src.slice(start, pos); - - matchStart = matchEnd = pos; - - while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { - matchEnd = matchStart + 1; - - while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } - - if (matchEnd - matchStart === marker.length) { - if (!silent) { - token = state.push('code_inline', 'code', 0); - token.markup = marker; - token.content = state.src.slice(pos, matchStart) - .replace(/\n/g, ' ') - .replace(/^ (.+) $/, '$1'); + if (ch === 60 /* < */) return false; + if (ch === 62 /* > */) break; + } + url = state.src.slice(start + 1, pos); + if (AUTOLINK_RE.test(url)) { + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { + return false; } - state.pos = matchEnd; + if (!silent) { + token = state.push("link_open", "a", 1); + token.attrs = [ [ "href", fullUrl ] ]; + token.markup = "autolink"; + token.info = "auto"; + token = state.push("text", "", 0); + token.content = state.md.normalizeLinkText(url); + token = state.push("link_close", "a", -1); + token.markup = "autolink"; + token.info = "auto"; + } + state.pos += url.length + 2; return true; } - } - - if (!silent) { state.pending += marker; } - state.pos += marker.length; - return true; -}; - -},{}],39:[function(require,module,exports){ -// For each opening emphasis-like marker find a matching closing one -// -'use strict'; - - -function processDelimiters(state, delimiters) { - var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, - isOddMatch, lastJump, - openersBottom = {}, - max = delimiters.length; - - for (closerIdx = 0; closerIdx < max; closerIdx++) { - closer = delimiters[closerIdx]; - - // Length is only used for emphasis-specific "rule of 3", - // if it's not defined (in strikethrough or 3rd party plugins), - // we can default it to 0 to disable those checks. - // - closer.length = closer.length || 0; - - if (!closer.close) continue; - - // Previously calculated lower bounds (previous fails) - // for each marker and each delimiter length modulo 3. - if (!openersBottom.hasOwnProperty(closer.marker)) { - openersBottom[closer.marker] = [ -1, -1, -1 ]; - } - - minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; - newMinOpenerIdx = -1; - - openerIdx = closerIdx - closer.jump - 1; - - for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { - opener = delimiters[openerIdx]; - - if (opener.marker !== closer.marker) continue; - - if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; - - if (opener.open && - opener.end < 0 && - opener.level === closer.level) { - - isOddMatch = false; - - // from spec: - // - // If one of the delimiters can both open and close emphasis, then the - // sum of the lengths of the delimiter runs containing the opening and - // closing delimiters must not be a multiple of 3 unless both lengths - // are multiples of 3. - // - if (opener.close || closer.open) { - if ((opener.length + closer.length) % 3 === 0) { - if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { - isOddMatch = true; - } - } - } - - if (!isOddMatch) { - // If previous delimiter cannot be an opener, we can safely skip - // the entire sequence in future checks. This is required to make - // sure algorithm has linear complexity (see *_*_*_*_*_... case). - // - lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? - delimiters[openerIdx - 1].jump + 1 : - 0; - - closer.jump = closerIdx - openerIdx + lastJump; - closer.open = false; - opener.end = closerIdx; - opener.jump = lastJump; - opener.close = false; - newMinOpenerIdx = -1; - break; - } + if (EMAIL_RE.test(url)) { + fullUrl = state.md.normalizeLink("mailto:" + url); + if (!state.md.validateLink(fullUrl)) { + return false; } + if (!silent) { + token = state.push("link_open", "a", 1); + token.attrs = [ [ "href", fullUrl ] ]; + token.markup = "autolink"; + token.info = "auto"; + token = state.push("text", "", 0); + token.content = state.md.normalizeLinkText(url); + token = state.push("link_close", "a", -1); + token.markup = "autolink"; + token.info = "auto"; + } + state.pos += url.length + 2; + return true; } - - if (newMinOpenerIdx !== -1) { - // If match for this delimiter run failed, we want to set lower bound for - // future lookups. This is required to make sure algorithm has linear - // complexity. - // - // See details here: - // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 - // - openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; - } + return false; + }; + var HTML_TAG_RE = html_re.HTML_TAG_RE; + function isLinkOpen(str) { + return /^\s]/i.test(str); } -} - - -module.exports = function link_pairs(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - processDelimiters(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - processDelimiters(state, tokens_meta[curr].delimiters); - } + function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); } -}; - -},{}],40:[function(require,module,exports){ -// Process *this* and _that_ -// -'use strict'; - - -// Insert each marker as a separate text token, and add it to delimiter list -// -module.exports.tokenize = function emphasis(state, silent) { - var i, scanned, token, - start = state.pos, - marker = state.src.charCodeAt(start); - - if (silent) { return false; } - - if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } - - scanned = state.scanDelims(state.pos, marker === 0x2A); - - for (i = 0; i < scanned.length; i++) { - token = state.push('text', '', 0); - token.content = String.fromCharCode(marker); - - state.delimiters.push({ - // Char code of the starting marker (number). - // - marker: marker, - - // Total length of these series of delimiters. - // - length: scanned.length, - - // An amount of characters before this one that's equivalent to - // current one. In plain English: if this delimiter does not open - // an emphasis, neither do previous `jump` characters. - // - // Used to skip sequences like "*****" in one step, for 1st asterisk - // value will be 0, for 2nd it's 1 and so on. - // - jump: i, - - // A position of the token this delimiter corresponds to. - // - token: state.tokens.length - 1, - - // If this delimiter is matched as a valid opener, `end` will be - // equal to its position, otherwise it's `-1`. - // - end: -1, - - // Boolean flags that determine if this delimiter could open or close - // an emphasis. - // - open: scanned.can_open, - close: scanned.can_close - }); + function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 32; + // to lower case + return lc >= 97 /* a */ && lc <= 122 /* z */; } - - state.pos += scanned.length; - - return true; -}; - - -function postProcess(state, delimiters) { - var i, - startDelim, - endDelim, - token, - ch, - isStrong, - max = delimiters.length; - - for (i = max - 1; i >= 0; i--) { - startDelim = delimiters[i]; - - if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { - continue; + var html_inline = function html_inline(state, silent) { + var ch, match, max, token, pos = state.pos; + if (!state.md.options.html) { + return false; } - - // Process only opening markers - if (startDelim.end === -1) { - continue; + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 60 /* < */ || pos + 2 >= max) { + return false; } - - endDelim = delimiters[startDelim.end]; - - // If the previous delimiter has the same marker and is adjacent to this one, - // merge those into one strong delimiter. - // - // `whatever` -> `whatever` - // - isStrong = i > 0 && - delimiters[i - 1].end === startDelim.end + 1 && - delimiters[i - 1].token === startDelim.token - 1 && - delimiters[startDelim.end + 1].token === endDelim.token + 1 && - delimiters[i - 1].marker === startDelim.marker; - - ch = String.fromCharCode(startDelim.marker); - - token = state.tokens[startDelim.token]; - token.type = isStrong ? 'strong_open' : 'em_open'; - token.tag = isStrong ? 'strong' : 'em'; - token.nesting = 1; - token.markup = isStrong ? ch + ch : ch; - token.content = ''; - - token = state.tokens[endDelim.token]; - token.type = isStrong ? 'strong_close' : 'em_close'; - token.tag = isStrong ? 'strong' : 'em'; - token.nesting = -1; - token.markup = isStrong ? ch + ch : ch; - token.content = ''; - - if (isStrong) { - state.tokens[delimiters[i - 1].token].content = ''; - state.tokens[delimiters[startDelim.end + 1].token].content = ''; - i--; + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 33 /* ! */ && ch !== 63 /* ? */ && ch !== 47 /* / */ && !isLetter(ch)) { + return false; } - } -} - - -// Walk through delimiter list and replace text tokens with tags -// -module.exports.postProcess = function emphasis(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - postProcess(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - postProcess(state, tokens_meta[curr].delimiters); + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { + return false; } - } -}; - -},{}],41:[function(require,module,exports){ -// Process html entity - {, ¯, ", ... - -'use strict'; - -var entities = require('../common/entities'); -var has = require('../common/utils').has; -var isValidEntityCode = require('../common/utils').isValidEntityCode; -var fromCodePoint = require('../common/utils').fromCodePoint; - - -var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; -var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; - - -module.exports = function entity(state, silent) { - var ch, code, match, pos = state.pos, max = state.posMax; - - if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } - - if (pos + 1 < max) { + if (!silent) { + token = state.push("html_inline", "", 0); + token.content = state.src.slice(pos, pos + match[0].length); + if (isLinkOpen(token.content)) state.linkLevel++; + if (isLinkClose(token.content)) state.linkLevel--; + } + state.pos += match[0].length; + return true; + }; + var has = utils.has; + var isValidEntityCode = utils.isValidEntityCode; + var fromCodePoint = utils.fromCodePoint; + var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; + var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + var entity = function entity(state, silent) { + var ch, code, match, token, pos = state.pos, max = state.posMax; + if (state.src.charCodeAt(pos) !== 38 /* & */) return false; + if (pos + 1 >= max) return false; ch = state.src.charCodeAt(pos + 1); - - if (ch === 0x23 /* # */) { + if (ch === 35 /* # */) { match = state.src.slice(pos).match(DIGITAL_RE); if (match) { if (!silent) { - code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); - state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + code = match[1][0].toLowerCase() === "x" ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + token = state.push("text_special", "", 0); + token.content = isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(65533); + token.markup = match[0]; + token.info = "entity"; } state.pos += match[0].length; return true; @@ -5108,2575 +6519,1028 @@ module.exports = function entity(state, silent) { match = state.src.slice(pos).match(NAMED_RE); if (match) { if (has(entities, match[1])) { - if (!silent) { state.pending += entities[match[1]]; } + if (!silent) { + token = state.push("text_special", "", 0); + token.content = entities[match[1]]; + token.markup = match[0]; + token.info = "entity"; + } state.pos += match[0].length; return true; } } } - } - - if (!silent) { state.pending += '&'; } - state.pos++; - return true; -}; - -},{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ -// Process escaped chars and hardbreaks - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - -var ESCAPED = []; - -for (var i = 0; i < 256; i++) { ESCAPED.push(0); } - -'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' - .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); - - -module.exports = function escape(state, silent) { - var ch, pos = state.pos, max = state.posMax; - - if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } - - pos++; - - if (pos < max) { - ch = state.src.charCodeAt(pos); - - if (ch < 256 && ESCAPED[ch] !== 0) { - if (!silent) { state.pending += state.src[pos]; } - state.pos += 2; - return true; - } - - if (ch === 0x0A) { - if (!silent) { - state.push('hardbreak', 'br', 0); - } - - pos++; - // skip leading whitespaces from next line - while (pos < max) { - ch = state.src.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } - - state.pos = pos; - return true; - } - } - - if (!silent) { state.pending += '\\'; } - state.pos++; - return true; -}; - -},{"../common/utils":4}],43:[function(require,module,exports){ -// Process html tags - -'use strict'; - - -var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; - - -function isLetter(ch) { - /*eslint no-bitwise:0*/ - var lc = ch | 0x20; // to lower case - return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); -} - - -module.exports = function html_inline(state, silent) { - var ch, match, max, token, - pos = state.pos; - - if (!state.md.options.html) { return false; } - - // Check start - max = state.posMax; - if (state.src.charCodeAt(pos) !== 0x3C/* < */ || - pos + 2 >= max) { return false; - } - - // Quick fail on second char - ch = state.src.charCodeAt(pos + 1); - if (ch !== 0x21/* ! */ && - ch !== 0x3F/* ? */ && - ch !== 0x2F/* / */ && - !isLetter(ch)) { - return false; - } - - match = state.src.slice(pos).match(HTML_TAG_RE); - if (!match) { return false; } - - if (!silent) { - token = state.push('html_inline', '', 0); - token.content = state.src.slice(pos, pos + match[0].length); - } - state.pos += match[0].length; - return true; -}; - -},{"../common/html_re":3}],44:[function(require,module,exports){ -// Process ![image]( "title") - -'use strict'; - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function image(state, silent) { - var attrs, - code, - content, - label, - labelEnd, - labelStart, - pos, - ref, - res, - title, - token, - tokens, - start, - href = '', - oldPos = state.pos, - max = state.posMax; - - if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } - if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } - - labelStart = state.pos + 2; - labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); - - // parser failed to find ']', so it's not a valid link - if (labelEnd < 0) { return false; } - - pos = labelEnd + 1; - if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { - // - // Inline link - // - - // [link]( "title" ) - // ^^ skipping these spaces - pos++; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - if (pos >= max) { return false; } - - // [link]( "title" ) - // ^^^^^^ parsing link destination - start = pos; - res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); - if (res.ok) { - href = state.md.normalizeLink(res.str); - if (state.md.validateLink(href)) { - pos = res.pos; - } else { - href = ''; - } - } - - // [link]( "title" ) - // ^^ skipping these spaces - start = pos; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - - // [link]( "title" ) - // ^^^^^^^ parsing link title - res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - - // [link]( "title" ) - // ^^ skipping these spaces - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - } else { - title = ''; - } - - if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { - state.pos = oldPos; - return false; - } - pos++; - } else { - // - // Link reference - // - if (typeof state.env.references === 'undefined') { return false; } - - if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { - start = pos + 1; - pos = state.md.helpers.parseLinkLabel(state, pos); - if (pos >= 0) { - label = state.src.slice(start, pos++); - } else { - pos = labelEnd + 1; - } - } else { - pos = labelEnd + 1; - } - - // covers label === '' and label === undefined - // (collapsed reference link and shortcut reference link respectively) - if (!label) { label = state.src.slice(labelStart, labelEnd); } - - ref = state.env.references[normalizeReference(label)]; - if (!ref) { - state.pos = oldPos; - return false; - } - href = ref.href; - title = ref.title; - } - - // - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - content = state.src.slice(labelStart, labelEnd); - - state.md.inline.parse( - content, - state.md, - state.env, - tokens = [] - ); - - token = state.push('image', 'img', 0); - token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; - token.children = tokens; - token.content = content; - - if (title) { - attrs.push([ 'title', title ]); - } - } - - state.pos = pos; - state.posMax = max; - return true; -}; - -},{"../common/utils":4}],45:[function(require,module,exports){ -// Process [link]( "stuff") - -'use strict'; - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function link(state, silent) { - var attrs, - code, - label, - labelEnd, - labelStart, - pos, - res, - ref, - title, - token, - href = '', - oldPos = state.pos, - max = state.posMax, - start = state.pos, - parseReference = true; - - if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } - - labelStart = state.pos + 1; - labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); - - // parser failed to find ']', so it's not a valid link - if (labelEnd < 0) { return false; } - - pos = labelEnd + 1; - if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { - // - // Inline link - // - - // might have found a valid shortcut link, disable reference parsing - parseReference = false; - - // [link]( "title" ) - // ^^ skipping these spaces - pos++; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - if (pos >= max) { return false; } - - // [link]( "title" ) - // ^^^^^^ parsing link destination - start = pos; - res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); - if (res.ok) { - href = state.md.normalizeLink(res.str); - if (state.md.validateLink(href)) { - pos = res.pos; - } else { - href = ''; - } - } - - // [link]( "title" ) - // ^^ skipping these spaces - start = pos; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - - // [link]( "title" ) - // ^^^^^^^ parsing link title - res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - - // [link]( "title" ) - // ^^ skipping these spaces - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - } else { - title = ''; - } - - if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { - // parsing a valid shortcut link failed, fallback to reference - parseReference = true; - } - pos++; - } - - if (parseReference) { - // - // Link reference - // - if (typeof state.env.references === 'undefined') { return false; } - - if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { - start = pos + 1; - pos = state.md.helpers.parseLinkLabel(state, pos); - if (pos >= 0) { - label = state.src.slice(start, pos++); - } else { - pos = labelEnd + 1; - } - } else { - pos = labelEnd + 1; - } - - // covers label === '' and label === undefined - // (collapsed reference link and shortcut reference link respectively) - if (!label) { label = state.src.slice(labelStart, labelEnd); } - - ref = state.env.references[normalizeReference(label)]; - if (!ref) { - state.pos = oldPos; - return false; - } - href = ref.href; - title = ref.title; - } - - // - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - state.pos = labelStart; - state.posMax = labelEnd; - - token = state.push('link_open', 'a', 1); - token.attrs = attrs = [ [ 'href', href ] ]; - if (title) { - attrs.push([ 'title', title ]); - } - - state.md.inline.tokenize(state); - - token = state.push('link_close', 'a', -1); - } - - state.pos = pos; - state.posMax = max; - return true; -}; - -},{"../common/utils":4}],46:[function(require,module,exports){ -// Proceess '\n' - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function newline(state, silent) { - var pmax, max, pos = state.pos; - - if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } - - pmax = state.pending.length - 1; - max = state.posMax; - - // ' \n' -> hardbreak - // Lookup in pending chars is bad practice! Don't copy to other rules! - // Pending string is stored in concat mode, indexed lookups will cause - // convertion to flat mode. - if (!silent) { - if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { - if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { - state.pending = state.pending.replace(/ +$/, ''); - state.push('hardbreak', 'br', 0); - } else { - state.pending = state.pending.slice(0, -1); - state.push('softbreak', 'br', 0); - } - - } else { - state.push('softbreak', 'br', 0); - } - } - - pos++; - - // skip heading spaces for next line - while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } - - state.pos = pos; - return true; -}; - -},{"../common/utils":4}],47:[function(require,module,exports){ -// Inline parser state - -'use strict'; - - -var Token = require('../token'); -var isWhiteSpace = require('../common/utils').isWhiteSpace; -var isPunctChar = require('../common/utils').isPunctChar; -var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; - - -function StateInline(src, md, env, outTokens) { - this.src = src; - this.env = env; - this.md = md; - this.tokens = outTokens; - this.tokens_meta = Array(outTokens.length); - - this.pos = 0; - this.posMax = this.src.length; - this.level = 0; - this.pending = ''; - this.pendingLevel = 0; - - // Stores { start: end } pairs. Useful for backtrack - // optimization of pairs parse (emphasis, strikes). - this.cache = {}; - - // List of emphasis-like delimiters for current tag - this.delimiters = []; - - // Stack of delimiter lists for upper level tags - this._prev_delimiters = []; -} - - -// Flush pending text -// -StateInline.prototype.pushPending = function () { - var token = new Token('text', '', 0); - token.content = this.pending; - token.level = this.pendingLevel; - this.tokens.push(token); - this.pending = ''; - return token; -}; - - -// Push new token to "stream". -// If pending text exists - flush it as text token -// -StateInline.prototype.push = function (type, tag, nesting) { - if (this.pending) { - this.pushPending(); - } - - var token = new Token(type, tag, nesting); - var token_meta = null; - - if (nesting < 0) { - // closing tag - this.level--; - this.delimiters = this._prev_delimiters.pop(); - } - - token.level = this.level; - - if (nesting > 0) { - // opening tag - this.level++; - this._prev_delimiters.push(this.delimiters); - this.delimiters = []; - token_meta = { delimiters: this.delimiters }; - } - - this.pendingLevel = this.level; - this.tokens.push(token); - this.tokens_meta.push(token_meta); - return token; -}; - - -// Scan a sequence of emphasis-like markers, and determine whether -// it can start an emphasis sequence or end an emphasis sequence. -// -// - start - position to scan from (it should point at a valid marker); -// - canSplitWord - determine if these markers can be found inside a word -// -StateInline.prototype.scanDelims = function (start, canSplitWord) { - var pos = start, lastChar, nextChar, count, can_open, can_close, - isLastWhiteSpace, isLastPunctChar, - isNextWhiteSpace, isNextPunctChar, - left_flanking = true, - right_flanking = true, - max = this.posMax, - marker = this.src.charCodeAt(start); - - // treat beginning of the line as a whitespace - lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; - - while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } - - count = pos - start; - - // treat end of the line as a whitespace - nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; - - isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); - isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); - - isLastWhiteSpace = isWhiteSpace(lastChar); - isNextWhiteSpace = isWhiteSpace(nextChar); - - if (isNextWhiteSpace) { - left_flanking = false; - } else if (isNextPunctChar) { - if (!(isLastWhiteSpace || isLastPunctChar)) { - left_flanking = false; - } - } - - if (isLastWhiteSpace) { - right_flanking = false; - } else if (isLastPunctChar) { - if (!(isNextWhiteSpace || isNextPunctChar)) { - right_flanking = false; - } - } - - if (!canSplitWord) { - can_open = left_flanking && (!right_flanking || isLastPunctChar); - can_close = right_flanking && (!left_flanking || isNextPunctChar); - } else { - can_open = left_flanking; - can_close = right_flanking; - } - - return { - can_open: can_open, - can_close: can_close, - length: count }; -}; - - -// re-export Token class to use in block rules -StateInline.prototype.Token = Token; - - -module.exports = StateInline; - -},{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ -// ~~strike through~~ -// -'use strict'; - - -// Insert each marker as a separate text token, and add it to delimiter list -// -module.exports.tokenize = function strikethrough(state, silent) { - var i, scanned, token, len, ch, - start = state.pos, - marker = state.src.charCodeAt(start); - - if (silent) { return false; } - - if (marker !== 0x7E/* ~ */) { return false; } - - scanned = state.scanDelims(state.pos, true); - len = scanned.length; - ch = String.fromCharCode(marker); - - if (len < 2) { return false; } - - if (len % 2) { - token = state.push('text', '', 0); - token.content = ch; - len--; - } - - for (i = 0; i < len; i += 2) { - token = state.push('text', '', 0); - token.content = ch + ch; - - state.delimiters.push({ - marker: marker, - length: 0, // disable "rule of 3" length checks meant for emphasis - jump: i, - token: state.tokens.length - 1, - end: -1, - open: scanned.can_open, - close: scanned.can_close - }); - } - - state.pos += scanned.length; - - return true; -}; - - -function postProcess(state, delimiters) { - var i, j, - startDelim, - endDelim, - token, - loneMarkers = [], - max = delimiters.length; - - for (i = 0; i < max; i++) { - startDelim = delimiters[i]; - - if (startDelim.marker !== 0x7E/* ~ */) { - continue; - } - - if (startDelim.end === -1) { - continue; - } - - endDelim = delimiters[startDelim.end]; - - token = state.tokens[startDelim.token]; - token.type = 's_open'; - token.tag = 's'; - token.nesting = 1; - token.markup = '~~'; - token.content = ''; - - token = state.tokens[endDelim.token]; - token.type = 's_close'; - token.tag = 's'; - token.nesting = -1; - token.markup = '~~'; - token.content = ''; - - if (state.tokens[endDelim.token - 1].type === 'text' && - state.tokens[endDelim.token - 1].content === '~') { - - loneMarkers.push(endDelim.token - 1); - } - } - - // If a marker sequence has an odd number of characters, it's splitted - // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the - // start of the sequence. - // - // So, we have to move all those markers after subsequent s_close tags. - // - while (loneMarkers.length) { - i = loneMarkers.pop(); - j = i + 1; - - while (j < state.tokens.length && state.tokens[j].type === 's_close') { - j++; - } - - j--; - - if (i !== j) { - token = state.tokens[j]; - state.tokens[j] = state.tokens[i]; - state.tokens[i] = token; - } - } -} - - -// Walk through delimiter list and replace text tokens with tags -// -module.exports.postProcess = function strikethrough(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - postProcess(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - postProcess(state, tokens_meta[curr].delimiters); - } - } -}; - -},{}],49:[function(require,module,exports){ -// Skip text characters for text token, place those to pending buffer -// and increment current pos - -'use strict'; - - -// Rule to skip pure text -// '{}$%@~+=:' reserved for extentions - -// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ - -// !!!! Don't confuse with "Markdown ASCII Punctuation" chars -// http://spec.commonmark.org/0.15/#ascii-punctuation-character -function isTerminatorChar(ch) { - switch (ch) { - case 0x0A/* \n */: - case 0x21/* ! */: - case 0x23/* # */: - case 0x24/* $ */: - case 0x25/* % */: - case 0x26/* & */: - case 0x2A/* * */: - case 0x2B/* + */: - case 0x2D/* - */: - case 0x3A/* : */: - case 0x3C/* < */: - case 0x3D/* = */: - case 0x3E/* > */: - case 0x40/* @ */: - case 0x5B/* [ */: - case 0x5C/* \ */: - case 0x5D/* ] */: - case 0x5E/* ^ */: - case 0x5F/* _ */: - case 0x60/* ` */: - case 0x7B/* { */: - case 0x7D/* } */: - case 0x7E/* ~ */: - return true; - default: - return false; - } -} - -module.exports = function text(state, silent) { - var pos = state.pos; - - while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { - pos++; - } - - if (pos === state.pos) { return false; } - - if (!silent) { state.pending += state.src.slice(state.pos, pos); } - - state.pos = pos; - - return true; -}; - -// Alternative implementation, for memory. -// -// It costs 10% of performance, but allows extend terminators list, if place it -// to `ParcerInline` property. Probably, will switch to it sometime, such -// flexibility required. - -/* -var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; - -module.exports = function text(state, silent) { - var pos = state.pos, - idx = state.src.slice(pos).search(TERMINATOR_RE); - - // first char is terminator -> empty text - if (idx === 0) { return false; } - - // no terminator -> text till end of string - if (idx < 0) { - if (!silent) { state.pending += state.src.slice(pos); } - state.pos = state.src.length; - return true; - } - - if (!silent) { state.pending += state.src.slice(pos, pos + idx); } - - state.pos += idx; - - return true; -};*/ - -},{}],50:[function(require,module,exports){ -// Clean up tokens after emphasis and strikethrough postprocessing: -// merge adjacent text nodes into one and re-calculate all token levels -// -// This is necessary because initially emphasis delimiter markers (*, _, ~) -// are treated as their own separate text tokens. Then emphasis rule either -// leaves them as text (needed to merge with adjacent text) or turns them -// into opening/closing tags (which messes up levels inside). -// -'use strict'; - - -module.exports = function text_collapse(state) { - var curr, last, - level = 0, - tokens = state.tokens, - max = state.tokens.length; - - for (curr = last = 0; curr < max; curr++) { - // re-calculate levels after emphasis/strikethrough turns some text nodes - // into opening/closing tags - if (tokens[curr].nesting < 0) level--; // closing tag - tokens[curr].level = level; - if (tokens[curr].nesting > 0) level++; // opening tag - - if (tokens[curr].type === 'text' && - curr + 1 < max && - tokens[curr + 1].type === 'text') { - - // collapse two adjacent text nodes - tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; - } else { - if (curr !== last) { tokens[last] = tokens[curr]; } - - last++; - } - } - - if (curr !== last) { - tokens.length = last; - } -}; - -},{}],51:[function(require,module,exports){ -// Token class - -'use strict'; - - -/** - * class Token - **/ - -/** - * new Token(type, tag, nesting) - * - * Create new token and fill passed properties. - **/ -function Token(type, tag, nesting) { - /** - * Token#type -> String - * - * Type of the token (string, e.g. "paragraph_open") - **/ - this.type = type; - - /** - * Token#tag -> String - * - * html tag name, e.g. "p" - **/ - this.tag = tag; - - /** - * Token#attrs -> Array - * - * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` - **/ - this.attrs = null; - - /** - * Token#map -> Array - * - * Source map info. Format: `[ line_begin, line_end ]` - **/ - this.map = null; - - /** - * Token#nesting -> Number - * - * Level change (number in {-1, 0, 1} set), where: - * - * - `1` means the tag is opening - * - `0` means the tag is self-closing - * - `-1` means the tag is closing - **/ - this.nesting = nesting; - - /** - * Token#level -> Number - * - * nesting level, the same as `state.level` - **/ - this.level = 0; - - /** - * Token#children -> Array - * - * An array of child nodes (inline and img tokens) - **/ - this.children = null; - - /** - * Token#content -> String - * - * In a case of self-closing tag (code, html, fence, etc.), - * it has contents of this tag. - **/ - this.content = ''; - - /** - * Token#markup -> String - * - * '*' or '_' for emphasis, fence string for fence, etc. - **/ - this.markup = ''; - - /** - * Token#info -> String - * - * fence infostring - **/ - this.info = ''; - - /** - * Token#meta -> Object - * - * A place for plugins to store an arbitrary data - **/ - this.meta = null; - - /** - * Token#block -> Boolean - * - * True for block-level tokens, false for inline tokens. - * Used in renderer to calculate line breaks - **/ - this.block = false; - - /** - * Token#hidden -> Boolean - * - * If it's true, ignore this element when rendering. Used for tight lists - * to hide paragraphs. - **/ - this.hidden = false; -} - - -/** - * Token.attrIndex(name) -> Number - * - * Search attribute index by name. - **/ -Token.prototype.attrIndex = function attrIndex(name) { - var attrs, i, len; - - if (!this.attrs) { return -1; } - - attrs = this.attrs; - - for (i = 0, len = attrs.length; i < len; i++) { - if (attrs[i][0] === name) { return i; } - } - return -1; -}; - - -/** - * Token.attrPush(attrData) - * - * Add `[ name, value ]` attribute to list. Init attrs if necessary - **/ -Token.prototype.attrPush = function attrPush(attrData) { - if (this.attrs) { - this.attrs.push(attrData); - } else { - this.attrs = [ attrData ]; - } -}; - - -/** - * Token.attrSet(name, value) - * - * Set `name` attribute to `value`. Override old value if exists. - **/ -Token.prototype.attrSet = function attrSet(name, value) { - var idx = this.attrIndex(name), - attrData = [ name, value ]; - - if (idx < 0) { - this.attrPush(attrData); - } else { - this.attrs[idx] = attrData; - } -}; - - -/** - * Token.attrGet(name) - * - * Get the value of attribute `name`, or null if it does not exist. - **/ -Token.prototype.attrGet = function attrGet(name) { - var idx = this.attrIndex(name), value = null; - if (idx >= 0) { - value = this.attrs[idx][1]; - } - return value; -}; - - -/** - * Token.attrJoin(name, value) - * - * Join value to existing attribute via space. Or create new attribute if not - * exists. Useful to operate with token classes. - **/ -Token.prototype.attrJoin = function attrJoin(name, value) { - var idx = this.attrIndex(name); - - if (idx < 0) { - this.attrPush([ name, value ]); - } else { - this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; - } -}; - - -module.exports = Token; - -},{}],52:[function(require,module,exports){ -module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } - -},{}],53:[function(require,module,exports){ -'use strict'; - - -//////////////////////////////////////////////////////////////////////////////// -// Helpers - -// Merge objects -// -function assign(obj /*from1, from2, from3, ...*/) { - var sources = Array.prototype.slice.call(arguments, 1); - - sources.forEach(function (source) { - if (!source) { return; } - - Object.keys(source).forEach(function (key) { - obj[key] = source[key]; - }); - }); - - return obj; -} - -function _class(obj) { return Object.prototype.toString.call(obj); } -function isString(obj) { return _class(obj) === '[object String]'; } -function isObject(obj) { return _class(obj) === '[object Object]'; } -function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } -function isFunction(obj) { return _class(obj) === '[object Function]'; } - - -function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } - -//////////////////////////////////////////////////////////////////////////////// - - -var defaultOptions = { - fuzzyLink: true, - fuzzyEmail: true, - fuzzyIP: false -}; - - -function isOptionsObj(obj) { - return Object.keys(obj || {}).reduce(function (acc, k) { - return acc || defaultOptions.hasOwnProperty(k); - }, false); -} - - -var defaultSchemas = { - 'http:': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.http) { - // compile lazily, because "host"-containing variables can change on tlds update. - self.re.http = new RegExp( - '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' - ); + // For each opening emphasis-like marker find a matching closing one + function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, isOddMatch, lastJump, openersBottom = {}, max = delimiters.length; + if (!max) return; + // headerIdx is the first delimiter of the current (where closer is) delimiter run + var headerIdx = 0; + var lastTokenIdx = -2; + // needs any value lower than -1 + var jumps = []; + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + jumps.push(0); + // markers belong to same delimiter run if: + // - they have adjacent tokens + // - AND markers are the same + + if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) { + headerIdx = closerIdx; } - if (self.re.http.test(tail)) { - return tail.match(self.re.http)[0].length; + lastTokenIdx = closer.token; + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + + closer.length = closer.length || 0; + if (!closer.close) continue; + // Previously calculated lower bounds (previous fails) + // for each marker, each delimiter length modulo 3, + // and for whether this closer can be an opener; + // https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460 + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1, -1, -1, -1 ]; } - return 0; - } - }, - 'https:': 'http:', - 'ftp:': 'http:', - '//': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.no_http) { - // compile lazily, because "host"-containing variables can change on tlds update. - self.re.no_http = new RegExp( - '^' + - self.re.src_auth + - // Don't allow single-level domains, because of false positives like '//test' - // with code comments - '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + - self.re.src_port + - self.re.src_host_terminator + - self.re.src_path, - - 'i' - ); - } - - if (self.re.no_http.test(tail)) { - // should not be `://` & `///`, that protects from errors in protocol name - if (pos >= 3 && text[pos - 3] === ':') { return 0; } - if (pos >= 3 && text[pos - 3] === '/') { return 0; } - return tail.match(self.re.no_http)[0].length; - } - return 0; - } - }, - 'mailto:': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.mailto) { - self.re.mailto = new RegExp( - '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' - ); - } - if (self.re.mailto.test(tail)) { - return tail.match(self.re.mailto)[0].length; - } - return 0; - } - } -}; - -/*eslint-disable max-len*/ - -// RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) -var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; - -// DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead -var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); - -/*eslint-enable max-len*/ - -//////////////////////////////////////////////////////////////////////////////// - -function resetScanCache(self) { - self.__index__ = -1; - self.__text_cache__ = ''; -} - -function createValidator(re) { - return function (text, pos) { - var tail = text.slice(pos); - - if (re.test(tail)) { - return tail.match(re)[0].length; - } - return 0; - }; -} - -function createNormalizer() { - return function (match, self) { - self.normalize(match); - }; -} - -// Schemas compiler. Build regexps. -// -function compile(self) { - - // Load & clone RE patterns. - var re = self.re = require('./lib/re')(self.__opts__); - - // Define dynamic patterns - var tlds = self.__tlds__.slice(); - - self.onCompile(); - - if (!self.__tlds_replaced__) { - tlds.push(tlds_2ch_src_re); - } - tlds.push(re.src_xn); - - re.src_tlds = tlds.join('|'); - - function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } - - re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); - re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); - re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); - re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); - - // - // Compile each schema - // - - var aliases = []; - - self.__compiled__ = {}; // Reset compiled data - - function schemaError(name, val) { - throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); - } - - Object.keys(self.__schemas__).forEach(function (name) { - var val = self.__schemas__[name]; - - // skip disabled methods - if (val === null) { return; } - - var compiled = { validate: null, link: null }; - - self.__compiled__[name] = compiled; - - if (isObject(val)) { - if (isRegExp(val.validate)) { - compiled.validate = createValidator(val.validate); - } else if (isFunction(val.validate)) { - compiled.validate = val.validate; - } else { - schemaError(name, val); - } - - if (isFunction(val.normalize)) { - compiled.normalize = val.normalize; - } else if (!val.normalize) { - compiled.normalize = createNormalizer(); - } else { - schemaError(name, val); - } - - return; - } - - if (isString(val)) { - aliases.push(name); - return; - } - - schemaError(name, val); - }); - - // - // Compile postponed aliases - // - - aliases.forEach(function (alias) { - if (!self.__compiled__[self.__schemas__[alias]]) { - // Silently fail on missed schemas to avoid errons on disable. - // schemaError(alias, self.__schemas__[alias]); - return; - } - - self.__compiled__[alias].validate = - self.__compiled__[self.__schemas__[alias]].validate; - self.__compiled__[alias].normalize = - self.__compiled__[self.__schemas__[alias]].normalize; - }); - - // - // Fake record for guessed links - // - self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; - - // - // Build schema condition - // - var slist = Object.keys(self.__compiled__) - .filter(function (name) { - // Filter disabled & fake schemas - return name.length > 0 && self.__compiled__[name]; - }) - .map(escapeRE) - .join('|'); - // (?!_) cause 1.5x slowdown - self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); - self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); - - self.re.pretest = RegExp( - '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', - 'i' - ); - - // - // Cleanup - // - - resetScanCache(self); -} - -/** - * class Match - * - * Match result. Single element of array, returned by [[LinkifyIt#match]] - **/ -function Match(self, shift) { - var start = self.__index__, - end = self.__last_index__, - text = self.__text_cache__.slice(start, end); - - /** - * Match#schema -> String - * - * Prefix (protocol) for matched string. - **/ - this.schema = self.__schema__.toLowerCase(); - /** - * Match#index -> Number - * - * First position of matched string. - **/ - this.index = start + shift; - /** - * Match#lastIndex -> Number - * - * Next position after matched string. - **/ - this.lastIndex = end + shift; - /** - * Match#raw -> String - * - * Matched string. - **/ - this.raw = text; - /** - * Match#text -> String - * - * Notmalized text of matched string. - **/ - this.text = text; - /** - * Match#url -> String - * - * Normalized url of matched string. - **/ - this.url = text; -} - -function createMatch(self, shift) { - var match = new Match(self, shift); - - self.__compiled__[match.schema].normalize(match, self); - - return match; -} - - -/** - * class LinkifyIt - **/ - -/** - * new LinkifyIt(schemas, options) - * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) - * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } - * - * Creates new linkifier instance with optional additional schemas. - * Can be called without `new` keyword for convenience. - * - * By default understands: - * - * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links - * - "fuzzy" links and emails (example.com, foo@bar.com). - * - * `schemas` is an object, where each key/value describes protocol/rule: - * - * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` - * for example). `linkify-it` makes shure that prefix is not preceeded with - * alphanumeric char and symbols. Only whitespaces and punctuation allowed. - * - __value__ - rule to check tail after link prefix - * - _String_ - just alias to existing rule - * - _Object_ - * - _validate_ - validator function (should return matched length on success), - * or `RegExp`. - * - _normalize_ - optional function to normalize text & url of matched result - * (for example, for @twitter mentions). - * - * `options`: - * - * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. - * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts - * like version numbers. Default `false`. - * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. - * - **/ -function LinkifyIt(schemas, options) { - if (!(this instanceof LinkifyIt)) { - return new LinkifyIt(schemas, options); - } - - if (!options) { - if (isOptionsObj(schemas)) { - options = schemas; - schemas = {}; - } - } - - this.__opts__ = assign({}, defaultOptions, options); - - // Cache last tested result. Used to skip repeating steps on next `match` call. - this.__index__ = -1; - this.__last_index__ = -1; // Next scan position - this.__schema__ = ''; - this.__text_cache__ = ''; - - this.__schemas__ = assign({}, defaultSchemas, schemas); - this.__compiled__ = {}; - - this.__tlds__ = tlds_default; - this.__tlds_replaced__ = false; - - this.re = {}; - - compile(this); -} - - -/** chainable - * LinkifyIt#add(schema, definition) - * - schema (String): rule name (fixed pattern prefix) - * - definition (String|RegExp|Object): schema definition - * - * Add new rule definition. See constructor description for details. - **/ -LinkifyIt.prototype.add = function add(schema, definition) { - this.__schemas__[schema] = definition; - compile(this); - return this; -}; - - -/** chainable - * LinkifyIt#set(options) - * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } - * - * Set recognition options for links without schema. - **/ -LinkifyIt.prototype.set = function set(options) { - this.__opts__ = assign(this.__opts__, options); - return this; -}; - - -/** - * LinkifyIt#test(text) -> Boolean - * - * Searches linkifiable pattern and returns `true` on success or `false` on fail. - **/ -LinkifyIt.prototype.test = function test(text) { - // Reset scan cache - this.__text_cache__ = text; - this.__index__ = -1; - - if (!text.length) { return false; } - - var m, ml, me, len, shift, next, re, tld_pos, at_pos; - - // try to scan for link with schema - that's the most simple rule - if (this.re.schema_test.test(text)) { - re = this.re.schema_search; - re.lastIndex = 0; - while ((m = re.exec(text)) !== null) { - len = this.testSchemaAt(text, m[2], re.lastIndex); - if (len) { - this.__schema__ = m[2]; - this.__index__ = m.index + m[1].length; - this.__last_index__ = m.index + m[0].length + len; - break; - } - } - } - - if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { - // guess schemaless links - tld_pos = text.search(this.re.host_fuzzy_test); - if (tld_pos >= 0) { - // if tld is located after found link - no need to check fuzzy pattern - if (this.__index__ < 0 || tld_pos < this.__index__) { - if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { - - shift = ml.index + ml[1].length; - - if (this.__index__ < 0 || shift < this.__index__) { - this.__schema__ = ''; - this.__index__ = shift; - this.__last_index__ = ml.index + ml[0].length; - } - } - } - } - } - - if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { - // guess schemaless emails - at_pos = text.indexOf('@'); - if (at_pos >= 0) { - // We can't skip this check, because this cases are possible: - // 192.168.1.1@gmail.com, my.in@example.com - if ((me = text.match(this.re.email_fuzzy)) !== null) { - - shift = me.index + me[1].length; - next = me.index + me[0].length; - - if (this.__index__ < 0 || shift < this.__index__ || - (shift === this.__index__ && next > this.__last_index__)) { - this.__schema__ = 'mailto:'; - this.__index__ = shift; - this.__last_index__ = next; - } - } - } - } - - return this.__index__ >= 0; -}; - - -/** - * LinkifyIt#pretest(text) -> Boolean - * - * Very quick check, that can give false positives. Returns true if link MAY BE - * can exists. Can be used for speed optimization, when you need to check that - * link NOT exists. - **/ -LinkifyIt.prototype.pretest = function pretest(text) { - return this.re.pretest.test(text); -}; - - -/** - * LinkifyIt#testSchemaAt(text, name, position) -> Number - * - text (String): text to scan - * - name (String): rule (schema) name - * - position (Number): text offset to check from - * - * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly - * at given position. Returns length of found pattern (0 on fail). - **/ -LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { - // If not supported schema check requested - terminate - if (!this.__compiled__[schema.toLowerCase()]) { - return 0; - } - return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); -}; - - -/** - * LinkifyIt#match(text) -> Array|null - * - * Returns array of found link descriptions or `null` on fail. We strongly - * recommend to use [[LinkifyIt#test]] first, for best speed. - * - * ##### Result match description - * - * - __schema__ - link schema, can be empty for fuzzy links, or `//` for - * protocol-neutral links. - * - __index__ - offset of matched text - * - __lastIndex__ - index of next char after mathch end - * - __raw__ - matched text - * - __text__ - normalized text - * - __url__ - link, generated from matched text - **/ -LinkifyIt.prototype.match = function match(text) { - var shift = 0, result = []; - - // Try to take previous element from cache, if .test() called before - if (this.__index__ >= 0 && this.__text_cache__ === text) { - result.push(createMatch(this, shift)); - shift = this.__last_index__; - } - - // Cut head if cache was used - var tail = shift ? text.slice(shift) : text; - - // Scan string until end reached - while (this.test(tail)) { - result.push(createMatch(this, shift)); - - tail = tail.slice(this.__last_index__); - shift += this.__last_index__; - } - - if (result.length) { - return result; - } - - return null; -}; - - -/** chainable - * LinkifyIt#tlds(list [, keepOld]) -> this - * - list (Array): list of tlds - * - keepOld (Boolean): merge with current list if `true` (`false` by default) - * - * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) - * to avoid false positives. By default this algorythm used: - * - * - hostname with any 2-letter root zones are ok. - * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф - * are ok. - * - encoded (`xn--...`) root zones are ok. - * - * If list is replaced, then exact match for 2-chars root zones will be checked. - **/ -LinkifyIt.prototype.tlds = function tlds(list, keepOld) { - list = Array.isArray(list) ? list : [ list ]; - - if (!keepOld) { - this.__tlds__ = list.slice(); - this.__tlds_replaced__ = true; - compile(this); - return this; - } - - this.__tlds__ = this.__tlds__.concat(list) - .sort() - .filter(function (el, idx, arr) { - return el !== arr[idx - 1]; - }) - .reverse(); - - compile(this); - return this; -}; - -/** - * LinkifyIt#normalize(match) - * - * Default normalizer (if schema does not define it's own). - **/ -LinkifyIt.prototype.normalize = function normalize(match) { - - // Do minimal possible changes by default. Need to collect feedback prior - // to move forward https://github.com/markdown-it/linkify-it/issues/1 - - if (!match.schema) { match.url = 'http://' + match.url; } - - if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { - match.url = 'mailto:' + match.url; - } -}; - - -/** - * LinkifyIt#onCompile() - * - * Override to modify basic RegExp-s. - **/ -LinkifyIt.prototype.onCompile = function onCompile() { -}; - - -module.exports = LinkifyIt; - -},{"./lib/re":54}],54:[function(require,module,exports){ -'use strict'; - - -module.exports = function (opts) { - var re = {}; - - // Use direct extract instead of `regenerate` to reduse browserified size - re.src_Any = require('uc.micro/properties/Any/regex').source; - re.src_Cc = require('uc.micro/categories/Cc/regex').source; - re.src_Z = require('uc.micro/categories/Z/regex').source; - re.src_P = require('uc.micro/categories/P/regex').source; - - // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) - re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); - - // \p{\Z\Cc} (white spaces + control) - re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); - - // Experimental. List of chars, completely prohibited in links - // because can separate it from other part of text - var text_separators = '[><\uff5c]'; - - // All possible word characters (everything without punctuation, spaces & controls) - // Defined via punctuation & spaces to save space - // Should be something like \p{\L\N\S\M} (\w but without `_`) - re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; - // The same as abothe but without [0-9] - // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; - - //////////////////////////////////////////////////////////////////////////////// - - re.src_ip4 = - - '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; - - // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. - re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; - - re.src_port = - - '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; - - re.src_host_terminator = - - '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; - - re.src_path = - - '(?:' + - '[/?#]' + - '(?:' + - '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + - '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + - '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + - '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + - '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + - "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + - "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found - '\\.{2,4}[a-zA-Z0-9%/]|' + // github has ... in commit range links, - // google has .... in links (issue #66) - // Restrict to - // - english - // - percent-encoded - // - parts of file path - // until more examples found. - '\\.(?!' + re.src_ZCc + '|[.]).|' + - (opts && opts['---'] ? - '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate - : - '\\-+|' - ) + - '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths - '\\!(?!' + re.src_ZCc + '|[!]).|' + - '\\?(?!' + re.src_ZCc + '|[?]).' + - ')+' + - '|\\/' + - ')?'; - - // Allow anything in markdown spec, forbid quote (") at the first position - // because emails enclosed in quotes are far more common - re.src_email_name = - - '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; - - re.src_xn = - - 'xn--[a-z0-9\\-]{1,59}'; - - // More to read about domain names - // http://serverfault.com/questions/638260/ - - re.src_domain_root = - - // Allow letters & digits (http://test1) - '(?:' + - re.src_xn + - '|' + - re.src_pseudo_letter + '{1,63}' + - ')'; - - re.src_domain = - - '(?:' + - re.src_xn + - '|' + - '(?:' + re.src_pseudo_letter + ')' + - '|' + - '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + - ')'; - - re.src_host = - - '(?:' + - // Don't need IP check, because digits are already allowed in normal domain names - // src_ip4 + - // '|' + - '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + - ')'; - - re.tpl_host_fuzzy = - - '(?:' + - re.src_ip4 + - '|' + - '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + - ')'; - - re.tpl_host_no_ip_fuzzy = - - '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; - - re.src_host_strict = - - re.src_host + re.src_host_terminator; - - re.tpl_host_fuzzy_strict = - - re.tpl_host_fuzzy + re.src_host_terminator; - - re.src_host_port_strict = - - re.src_host + re.src_port + re.src_host_terminator; - - re.tpl_host_port_fuzzy_strict = - - re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; - - re.tpl_host_port_no_ip_fuzzy_strict = - - re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; - - - //////////////////////////////////////////////////////////////////////////////// - // Main rules - - // Rude test fuzzy links by host, for quick deny - re.tpl_host_fuzzy_test = - - 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; - - re.tpl_email_fuzzy = - - '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + - '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; - - re.tpl_link_fuzzy = - // Fuzzy link can't be prepended with .:/\- and non punctuation. - // but can start with > (markdown blockquote) - '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + - '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; - - re.tpl_link_no_ip_fuzzy = - // Fuzzy link can't be prepended with .:/\- and non punctuation. - // but can start with > (markdown blockquote) - '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + - '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; - - return re; -}; - -},{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ - -'use strict'; - - -/* eslint-disable no-bitwise */ - -var decodeCache = {}; - -function getDecodeCache(exclude) { - var i, ch, cache = decodeCache[exclude]; - if (cache) { return cache; } - - cache = decodeCache[exclude] = []; - - for (i = 0; i < 128; i++) { - ch = String.fromCharCode(i); - cache.push(ch); - } - - for (i = 0; i < exclude.length; i++) { - ch = exclude.charCodeAt(i); - cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); - } - - return cache; -} - - -// Decode percent-encoded string. -// -function decode(string, exclude) { - var cache; - - if (typeof exclude !== 'string') { - exclude = decode.defaultChars; - } - - cache = getDecodeCache(exclude); - - return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { - var i, l, b1, b2, b3, b4, chr, - result = ''; - - for (i = 0, l = seq.length; i < l; i += 3) { - b1 = parseInt(seq.slice(i + 1, i + 3), 16); - - if (b1 < 0x80) { - result += cache[b1]; - continue; - } - - if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { - // 110xxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); - - if ((b2 & 0xC0) === 0x80) { - chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); - - if (chr < 0x80) { - result += '\ufffd\ufffd'; - } else { - result += String.fromCharCode(chr); - } - - i += 3; - continue; - } - } - - if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { - // 1110xxxx 10xxxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); - b3 = parseInt(seq.slice(i + 7, i + 9), 16); - - if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { - chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); - - if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { - result += '\ufffd\ufffd\ufffd'; - } else { - result += String.fromCharCode(chr); - } - - i += 6; - continue; - } - } - - if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { - // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); - b3 = parseInt(seq.slice(i + 7, i + 9), 16); - b4 = parseInt(seq.slice(i + 10, i + 12), 16); - - if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { - chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); - - if (chr < 0x10000 || chr > 0x10FFFF) { - result += '\ufffd\ufffd\ufffd\ufffd'; - } else { - chr -= 0x10000; - result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); - } - - i += 9; - continue; - } - } - - result += '\ufffd'; - } - - return result; - }); -} - - -decode.defaultChars = ';/?:@&=+$,#'; -decode.componentChars = ''; - - -module.exports = decode; - -},{}],56:[function(require,module,exports){ - -'use strict'; - - -var encodeCache = {}; - - -// Create a lookup array where anything but characters in `chars` string -// and alphanumeric chars is percent-encoded. -// -function getEncodeCache(exclude) { - var i, ch, cache = encodeCache[exclude]; - if (cache) { return cache; } - - cache = encodeCache[exclude] = []; - - for (i = 0; i < 128; i++) { - ch = String.fromCharCode(i); - - if (/^[0-9a-z]$/i.test(ch)) { - // always allow unencoded alphanumeric characters - cache.push(ch); - } else { - cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); - } - } - - for (i = 0; i < exclude.length; i++) { - cache[exclude.charCodeAt(i)] = exclude[i]; - } - - return cache; -} - - -// Encode unsafe characters with percent-encoding, skipping already -// encoded sequences. -// -// - string - string to encode -// - exclude - list of characters to ignore (in addition to a-zA-Z0-9) -// - keepEscaped - don't encode '%' in a correct escape sequence (default: true) -// -function encode(string, exclude, keepEscaped) { - var i, l, code, nextCode, cache, - result = ''; - - if (typeof exclude !== 'string') { - // encode(string, keepEscaped) - keepEscaped = exclude; - exclude = encode.defaultChars; - } - - if (typeof keepEscaped === 'undefined') { - keepEscaped = true; - } - - cache = getEncodeCache(exclude); - - for (i = 0, l = string.length; i < l; i++) { - code = string.charCodeAt(i); - - if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { - if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { - result += string.slice(i, i + 3); - i += 2; - continue; - } - } - - if (code < 128) { - result += cache[code]; - continue; - } - - if (code >= 0xD800 && code <= 0xDFFF) { - if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { - nextCode = string.charCodeAt(i + 1); - if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { - result += encodeURIComponent(string[i] + string[i + 1]); - i++; - continue; - } - } - result += '%EF%BF%BD'; - continue; - } - - result += encodeURIComponent(string[i]); - } - - return result; -} - -encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; -encode.componentChars = "-_.!~*'()"; - - -module.exports = encode; - -},{}],57:[function(require,module,exports){ - -'use strict'; - - -module.exports = function format(url) { - var result = ''; - - result += url.protocol || ''; - result += url.slashes ? '//' : ''; - result += url.auth ? url.auth + '@' : ''; - - if (url.hostname && url.hostname.indexOf(':') !== -1) { - // ipv6 address - result += '[' + url.hostname + ']'; - } else { - result += url.hostname || ''; - } - - result += url.port ? ':' + url.port : ''; - result += url.pathname || ''; - result += url.search || ''; - result += url.hash || ''; - - return result; -}; - -},{}],58:[function(require,module,exports){ -'use strict'; - - -module.exports.encode = require('./encode'); -module.exports.decode = require('./decode'); -module.exports.format = require('./format'); -module.exports.parse = require('./parse'); - -},{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; - -// -// Changes from joyent/node: -// -// 1. No leading slash in paths, -// e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` -// -// 2. Backslashes are not replaced with slashes, -// so `http:\\example.org\` is treated like a relative path -// -// 3. Trailing colon is treated like a part of the path, -// i.e. in `http://example.org:foo` pathname is `:foo` -// -// 4. Nothing is URL-encoded in the resulting object, -// (in joyent/node some chars in auth and paths are encoded) -// -// 5. `url.parse()` does not have `parseQueryString` argument -// -// 6. Removed extraneous result properties: `host`, `path`, `query`, etc., -// which can be constructed using other parts of the url. -// - - -function Url() { - this.protocol = null; - this.slashes = null; - this.auth = null; - this.port = null; - this.hostname = null; - this.hash = null; - this.search = null; - this.pathname = null; -} - -// Reference: RFC 3986, RFC 1808, RFC 2396 - -// define these here so at least they only have to be -// compiled once on the first module load. -var protocolPattern = /^([a-z0-9.+-]+:)/i, - portPattern = /:[0-9]*$/, - - // Special case for a simple path URL - simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, - - // RFC 2396: characters reserved for delimiting URLs. - // We actually just auto-escape these. - delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], - - // RFC 2396: characters not allowed for various reasons. - unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), - - // Allowed by RFCs, but cause of XSS attacks. Always escape these. - autoEscape = [ '\'' ].concat(unwise), - // Characters that are never ever allowed in a hostname. - // Note that any invalid chars are also handled, but these - // are the ones that are *expected* to be seen, so we fast-path - // them. - nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), - hostEndingChars = [ '/', '?', '#' ], - hostnameMaxLen = 255, - hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, - hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, - // protocols that can allow "unsafe" and "unwise" chars. - /* eslint-disable no-script-url */ - // protocols that never have a hostname. - hostlessProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that always contain a // bit. - slashedProtocol = { - 'http': true, - 'https': true, - 'ftp': true, - 'gopher': true, - 'file': true, - 'http:': true, - 'https:': true, - 'ftp:': true, - 'gopher:': true, - 'file:': true - }; - /* eslint-enable no-script-url */ - -function urlParse(url, slashesDenoteHost) { - if (url && url instanceof Url) { return url; } - - var u = new Url(); - u.parse(url, slashesDenoteHost); - return u; -} - -Url.prototype.parse = function(url, slashesDenoteHost) { - var i, l, lowerProto, hec, slashes, - rest = url; - - // trim before proceeding. - // This is to support parse stuff like " http://foo.com \n" - rest = rest.trim(); - - if (!slashesDenoteHost && url.split('#').length === 1) { - // Try fast path regexp - var simplePath = simplePathPattern.exec(rest); - if (simplePath) { - this.pathname = simplePath[1]; - if (simplePath[2]) { - this.search = simplePath[2]; - } - return this; - } - } - - var proto = protocolPattern.exec(rest); - if (proto) { - proto = proto[0]; - lowerProto = proto.toLowerCase(); - this.protocol = proto; - rest = rest.substr(proto.length); - } - - // figure out if it's got a host - // user@server is *always* interpreted as a hostname, and url - // resolution will treat //foo/bar as host=foo,path=bar because that's - // how the browser resolves relative URLs. - if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { - slashes = rest.substr(0, 2) === '//'; - if (slashes && !(proto && hostlessProtocol[proto])) { - rest = rest.substr(2); - this.slashes = true; - } - } - - if (!hostlessProtocol[proto] && - (slashes || (proto && !slashedProtocol[proto]))) { - - // there's a hostname. - // the first instance of /, ?, ;, or # ends the host. - // - // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the last @ sign, unless some host-ending character - // comes *before* the @-sign. - // URLs are obnoxious. - // - // ex: - // http://a@b@c/ => user:a@b host:c - // http://a@b?@c => user:a host:c path:/?@c - - // v0.12 TODO(isaacs): This is not quite how Chrome does things. - // Review our test case against browsers more comprehensively. - - // find the first instance of any hostEndingChars - var hostEnd = -1; - for (i = 0; i < hostEndingChars.length; i++) { - hec = rest.indexOf(hostEndingChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { - hostEnd = hec; - } - } - - // at this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. - var auth, atSign; - if (hostEnd === -1) { - // atSign can be anywhere. - atSign = rest.lastIndexOf('@'); - } else { - // atSign must be in auth portion. - // http://a@b/c@d => host:b auth:a path:/c@d - atSign = rest.lastIndexOf('@', hostEnd); - } - - // Now we have a portion which is definitely the auth. - // Pull that off. - if (atSign !== -1) { - auth = rest.slice(0, atSign); - rest = rest.slice(atSign + 1); - this.auth = auth; - } - - // the host is the remaining to the left of the first non-host char - hostEnd = -1; - for (i = 0; i < nonHostChars.length; i++) { - hec = rest.indexOf(nonHostChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { - hostEnd = hec; - } - } - // if we still have not hit it, then the entire thing is a host. - if (hostEnd === -1) { - hostEnd = rest.length; - } - - if (rest[hostEnd - 1] === ':') { hostEnd--; } - var host = rest.slice(0, hostEnd); - rest = rest.slice(hostEnd); - - // pull out port. - this.parseHost(host); - - // we've indicated that there is a hostname, - // so even if it's empty, it has to be present. - this.hostname = this.hostname || ''; - - // if hostname begins with [ and ends with ] - // assume that it's an IPv6 address. - var ipv6Hostname = this.hostname[0] === '[' && - this.hostname[this.hostname.length - 1] === ']'; - - // validate a little. - if (!ipv6Hostname) { - var hostparts = this.hostname.split(/\./); - for (i = 0, l = hostparts.length; i < l; i++) { - var part = hostparts[i]; - if (!part) { continue; } - if (!part.match(hostnamePartPattern)) { - var newpart = ''; - for (var j = 0, k = part.length; j < k; j++) { - if (part.charCodeAt(j) > 127) { - // we replace non-ASCII char with a temporary placeholder - // we need this to make sure size of hostname is not - // broken by replacing non-ASCII by nothing - newpart += 'x'; - } else { - newpart += part[j]; + minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + closer.length % 3]; + openerIdx = headerIdx - jumps[headerIdx] - 1; + newMinOpenerIdx = openerIdx; + for (;openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) { + opener = delimiters[openerIdx]; + if (opener.marker !== closer.marker) continue; + if (opener.open && opener.end < 0) { + isOddMatch = false; + // from spec: + + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } } } - // we test again with ASCII char only - if (!newpart.match(hostnamePartPattern)) { - var validParts = hostparts.slice(0, i); - var notHost = hostparts.slice(i + 1); - var bit = part.match(hostnamePartStart); - if (bit) { - validParts.push(bit[1]); - notHost.unshift(bit[2]); - } - if (notHost.length) { - rest = notHost.join('.') + rest; - } - this.hostname = validParts.join('.'); + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? jumps[openerIdx - 1] + 1 : 0; + jumps[closerIdx] = closerIdx - openerIdx + lastJump; + jumps[openerIdx] = lastJump; + closer.open = false; + opener.end = closerIdx; + opener.close = false; + newMinOpenerIdx = -1; + // treat next token as start of run, + // it optimizes skips in **<...>**a**<...>** pathological case + lastTokenIdx = -2; break; } } } - } - - if (this.hostname.length > hostnameMaxLen) { - this.hostname = ''; - } - - // strip [ and ] from the hostname - // the host field still retains them, though - if (ipv6Hostname) { - this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length || 0) % 3] = newMinOpenerIdx; + } } } - - // chop off from the tail first. - var hash = rest.indexOf('#'); - if (hash !== -1) { - // got a fragment string. - this.hash = rest.substr(hash); - rest = rest.slice(0, hash); - } - var qm = rest.indexOf('?'); - if (qm !== -1) { - this.search = rest.substr(qm); - rest = rest.slice(0, qm); - } - if (rest) { this.pathname = rest; } - if (slashedProtocol[lowerProto] && - this.hostname && !this.pathname) { - this.pathname = ''; - } - - return this; -}; - -Url.prototype.parseHost = function(host) { - var port = portPattern.exec(host); - if (port) { - port = port[0]; - if (port !== ':') { - this.port = port.substr(1); + var balance_pairs = function link_pairs(state) { + var curr, tokens_meta = state.tokens_meta, max = state.tokens_meta.length; + processDelimiters(state, state.delimiters); + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } } - host = host.substr(0, host.length - port.length); + }; + // Clean up tokens after emphasis and strikethrough postprocessing: + var fragments_join = function fragments_join(state) { + var curr, last, level = 0, tokens = state.tokens, max = state.tokens.length; + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; + // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; + // opening tag + if (tokens[curr].type === "text" && curr + 1 < max && tokens[curr + 1].type === "text") { + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { + tokens[last] = tokens[curr]; + } + last++; + } + } + if (curr !== last) { + tokens.length = last; + } + }; + var isWhiteSpace = utils.isWhiteSpace; + var isPunctChar = utils.isPunctChar; + var isMdAsciiPunct = utils.isMdAsciiPunct; + function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ""; + this.pendingLevel = 0; + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + // List of emphasis-like delimiters for current tag + this.delimiters = []; + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; + // backtick length => last seen position + this.backticks = {}; + this.backticksScanned = false; + // Counter used to disable inline linkify-it execution + // inside and markdown links + this.linkLevel = 0; } - if (host) { this.hostname = host; } -}; - -module.exports = urlParse; - -},{}],60:[function(require,module,exports){ -(function (global){ -/*! https://mths.be/punycode v1.4.1 by @mathias */ -;(function(root) { - - /** Detect free variables */ - var freeExports = typeof exports == 'object' && exports && - !exports.nodeType && exports; - var freeModule = typeof module == 'object' && module && - !module.nodeType && module; - var freeGlobal = typeof global == 'object' && global; - if ( - freeGlobal.global === freeGlobal || - freeGlobal.window === freeGlobal || - freeGlobal.self === freeGlobal - ) { - root = freeGlobal; - } - - /** - * The `punycode` object. - * @name punycode - * @type Object - */ - var punycode, - - /** Highest positive signed 32-bit float value */ - maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 - - /** Bootstring parameters */ - base = 36, - tMin = 1, - tMax = 26, - skew = 38, - damp = 700, - initialBias = 72, - initialN = 128, // 0x80 - delimiter = '-', // '\x2D' - - /** Regular expressions */ - regexPunycode = /^xn--/, - regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars - regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators - - /** Error messages */ - errors = { - 'overflow': 'Overflow: input needs wider integers to process', - 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', - 'invalid-input': 'Invalid input' - }, - - /** Convenience shortcuts */ - baseMinusTMin = base - tMin, - floor = Math.floor, - stringFromCharCode = String.fromCharCode, - - /** Temporary variable */ - key; - - /*--------------------------------------------------------------------------*/ - - /** + // Flush pending text + + StateInline.prototype.pushPending = function() { + var token$1 = new token("text", "", 0); + token$1.content = this.pending; + token$1.level = this.pendingLevel; + this.tokens.push(token$1); + this.pending = ""; + return token$1; + }; + // Push new token to "stream". + // If pending text exists - flush it as text token + + StateInline.prototype.push = function(type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + var token$1 = new token(type, tag, nesting); + var token_meta = null; + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + token$1.level = this.level; + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { + delimiters: this.delimiters + }; + } + this.pendingLevel = this.level; + this.tokens.push(token$1); + this.tokens_meta.push(token_meta); + return token$1; + }; + // Scan a sequence of emphasis-like markers, and determine whether + // it can start an emphasis sequence or end an emphasis sequence. + + // - start - position to scan from (it should point at a valid marker); + // - canSplitWord - determine if these markers can be found inside a word + + StateInline.prototype.scanDelims = function(start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, isLastWhiteSpace, isLastPunctChar, isNextWhiteSpace, isNextPunctChar, left_flanking = true, right_flanking = true, max = this.posMax, marker = this.src.charCodeAt(start); + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 32; + while (pos < max && this.src.charCodeAt(pos) === marker) { + pos++; + } + count = pos - start; + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 32; + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + return { + can_open: can_open, + can_close: can_close, + length: count + }; + }; + // re-export Token class to use in block rules + StateInline.prototype.Token = token; + var state_inline = StateInline; + //////////////////////////////////////////////////////////////////////////////// + // Parser rules + var _rules = [ [ "text", text ], [ "linkify", linkify ], [ "newline", newline ], [ "escape", _escape ], [ "backticks", backticks ], [ "strikethrough", strikethrough.tokenize ], [ "emphasis", emphasis.tokenize ], [ "link", link ], [ "image", image ], [ "autolink", autolink ], [ "html_inline", html_inline ], [ "entity", entity ] ]; + // `rule2` ruleset was created specifically for emphasis/strikethrough + // post-processing and may be changed in the future. + + // Don't use this for anything except pairs (plugins working with `balance_pairs`). + + var _rules2 = [ [ "balance_pairs", balance_pairs ], [ "strikethrough", strikethrough.postProcess ], [ "emphasis", emphasis.postProcess ], + // rules for pairs separate '**' into its own text tokens, which may be left unused, + // rule below merges unused segments back with the rest of the text + [ "fragments_join", fragments_join ] ]; + /** + * new ParserInline() + **/ function ParserInline() { + var i; + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ this.ruler = new ruler; + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ this.ruler2 = new ruler; + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } + } + // Skip single token by running all rules in validation mode; + // returns `true` if any rule reported success + + ParserInline.prototype.skipToken = function(state) { + var ok, i, pos = state.pos, rules = this.ruler.getRules(""), len = rules.length, maxNesting = state.md.options.maxNesting, cache = state.cache; + if (typeof cache[pos] !== "undefined") { + state.pos = cache[pos]; + return; + } + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + state.level++; + ok = rules[i](state, true); + state.level--; + if (ok) { + break; + } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // [[[[[[[[[[[[[[[[[[[[[foo]() + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + state.pos = state.posMax; + } + if (!ok) { + state.pos++; + } + cache[pos] = state.pos; + }; + // Generate tokens for input range + + ParserInline.prototype.tokenize = function(state) { + var ok, i, rules = this.ruler.getRules(""), len = rules.length, end = state.posMax, maxNesting = state.md.options.maxNesting; + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // - update `state.pos` + // - update `state.tokens` + // - return true + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { + break; + } + } + } + if (ok) { + if (state.pos >= end) { + break; + } + continue; + } + state.pending += state.src[state.pos++]; + } + if (state.pending) { + state.pushPending(); + } + }; + /** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ ParserInline.prototype.parse = function(str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + this.tokenize(state); + rules = this.ruler2.getRules(""); + len = rules.length; + for (i = 0; i < len; i++) { + rules[i](state); + } + }; + ParserInline.prototype.State = state_inline; + var parser_inline = ParserInline; + var re = function(opts) { + var re = {}; + opts = opts || {}; + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = regex$3.source; + re.src_Cc = regex$2.source; + re.src_Z = regex.source; + re.src_P = regex$4.source; + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join("|"); + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [ re.src_Z, re.src_Cc ].join("|"); + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = "[><\uff5c]"; + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = "(?:(?!" + text_separators + "|" + re.src_ZPCc + ")" + re.src_Any + ")"; + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + //////////////////////////////////////////////////////////////////////////////// + re.src_ip4 = "(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = "(?:(?:(?!" + re.src_ZCc + "|[@/\\[\\]()]).)+@)?"; + re.src_port = "(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?"; + re.src_host_terminator = "(?=$|" + text_separators + "|" + re.src_ZPCc + ")" + "(?!" + (opts["---"] ? "-(?!--)|" : "-|") + "_|:\\d|\\.-|\\.(?!$|" + re.src_ZPCc + "))"; + re.src_path = "(?:" + "[/?#]" + "(?:" + "(?!" + re.src_ZCc + "|" + text_separators + "|[()[\\]{}.,\"'?!\\-;]).|" + "\\[(?:(?!" + re.src_ZCc + "|\\]).)*\\]|" + "\\((?:(?!" + re.src_ZCc + "|[)]).)*\\)|" + "\\{(?:(?!" + re.src_ZCc + "|[}]).)*\\}|" + '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + "\\'(?=" + re.src_pseudo_letter + "|[-])|" + // allow `I'm_king` if no pair found + "\\.{2,}[a-zA-Z0-9%/&]|" + // google has many dots in "google search" links (#66, #81). + // github has ... in commit range links, + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // - params separator + // until more examples found. + "\\.(?!" + re.src_ZCc + "|[.]|$)|" + (opts["---"] ? "\\-(?!--(?:[^-]|$))(?:-*)|" : "\\-+|") + ",(?!" + re.src_ZCc + "|$)|" + // allow `,,,` in paths + ";(?!" + re.src_ZCc + "|$)|" + // allow `;` if not followed by space-like char + "\\!+(?!" + re.src_ZCc + "|[!]|$)|" + // allow `!!!` in paths, but not at the end + "\\?(?!" + re.src_ZCc + "|[?]|$)" + ")+" + "|\\/" + ")?"; + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + re.src_xn = "xn--[a-z0-9\\-]{1,59}"; + // More to read about domain names + // http://serverfault.com/questions/638260/ + re.src_domain_root = + // Allow letters & digits (http://test1) + "(?:" + re.src_xn + "|" + re.src_pseudo_letter + "{1,63}" + ")"; + re.src_domain = "(?:" + re.src_xn + "|" + "(?:" + re.src_pseudo_letter + ")" + "|" + "(?:" + re.src_pseudo_letter + "(?:-|" + re.src_pseudo_letter + "){0,61}" + re.src_pseudo_letter + ")" + ")"; + re.src_host = "(?:" + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + "(?:(?:(?:" + re.src_domain + ")\\.)*" + re.src_domain /*_root*/ + ")" + ")"; + re.tpl_host_fuzzy = "(?:" + re.src_ip4 + "|" + "(?:(?:(?:" + re.src_domain + ")\\.)+(?:%TLDS%))" + ")"; + re.tpl_host_no_ip_fuzzy = "(?:(?:(?:" + re.src_domain + ")\\.)+(?:%TLDS%))"; + re.src_host_strict = re.src_host + re.src_host_terminator; + re.tpl_host_fuzzy_strict = re.tpl_host_fuzzy + re.src_host_terminator; + re.src_host_port_strict = re.src_host + re.src_port + re.src_host_terminator; + re.tpl_host_port_fuzzy_strict = re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + re.tpl_host_port_no_ip_fuzzy_strict = re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + //////////////////////////////////////////////////////////////////////////////// + // Main rules + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = "localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:" + re.src_ZPCc + "|>|$))"; + re.tpl_email_fuzzy = "(^|" + text_separators + '|"|\\(|' + re.src_ZCc + ")" + "(" + re.src_email_name + "@" + re.tpl_host_fuzzy_strict + ")"; + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + "(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|" + re.src_ZPCc + "))" + "((?![$+<=>^`|\uff5c])" + re.tpl_host_port_fuzzy_strict + re.src_path + ")"; + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + "(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|" + re.src_ZPCc + "))" + "((?![$+<=>^`|\uff5c])" + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ")"; + return re; + }; + //////////////////////////////////////////////////////////////////////////////// + // Helpers + // Merge objects + + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + sources.forEach((function(source) { + if (!source) { + return; + } + Object.keys(source).forEach((function(key) { + obj[key] = source[key]; + })); + })); + return obj; + } + function _class(obj) { + return Object.prototype.toString.call(obj); + } + function isString(obj) { + return _class(obj) === "[object String]"; + } + function isObject(obj) { + return _class(obj) === "[object Object]"; + } + function isRegExp(obj) { + return _class(obj) === "[object RegExp]"; + } + function isFunction(obj) { + return _class(obj) === "[object Function]"; + } + function escapeRE(str) { + return str.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); + } + //////////////////////////////////////////////////////////////////////////////// + var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false + }; + function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce((function(acc, k) { + return acc || defaultOptions.hasOwnProperty(k); + }), false); + } + var defaultSchemas = { + "http:": { + validate: function(text, pos, self) { + var tail = text.slice(pos); + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp("^\\/\\/" + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, "i"); + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length; + } + return 0; + } + }, + "https:": "http:", + "ftp:": "http:", + "//": { + validate: function(text, pos, self) { + var tail = text.slice(pos); + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp("^" + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + "(?:localhost|(?:(?:" + self.re.src_domain + ")\\.)+" + self.re.src_domain_root + ")" + self.re.src_port + self.re.src_host_terminator + self.re.src_path, "i"); + } + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ":") { + return 0; + } + if (pos >= 3 && text[pos - 3] === "/") { + return 0; + } + return tail.match(self.re.no_http)[0].length; + } + return 0; + } + }, + "mailto:": { + validate: function(text, pos, self) { + var tail = text.slice(pos); + if (!self.re.mailto) { + self.re.mailto = new RegExp("^" + self.re.src_email_name + "@" + self.re.src_host_strict, "i"); + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length; + } + return 0; + } + } + }; + /*eslint-disable max-len*/ + // RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) + var tlds_2ch_src_re = "a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]"; + // DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead + var tlds_default = "biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|\u0440\u0444".split("|"); + /*eslint-enable max-len*/ + //////////////////////////////////////////////////////////////////////////////// + function resetScanCache(self) { + self.__index__ = -1; + self.__text_cache__ = ""; + } + function createValidator(re) { + return function(text, pos) { + var tail = text.slice(pos); + if (re.test(tail)) { + return tail.match(re)[0].length; + } + return 0; + }; + } + function createNormalizer() { + return function(match, self) { + self.normalize(match); + }; + } + // Schemas compiler. Build regexps. + + function compile(self) { + // Load & clone RE patterns. + var re$1 = self.re = re(self.__opts__); + // Define dynamic patterns + var tlds = self.__tlds__.slice(); + self.onCompile(); + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re); + } + tlds.push(re$1.src_xn); + re$1.src_tlds = tlds.join("|"); + function untpl(tpl) { + return tpl.replace("%TLDS%", re$1.src_tlds); + } + re$1.email_fuzzy = RegExp(untpl(re$1.tpl_email_fuzzy), "i"); + re$1.link_fuzzy = RegExp(untpl(re$1.tpl_link_fuzzy), "i"); + re$1.link_no_ip_fuzzy = RegExp(untpl(re$1.tpl_link_no_ip_fuzzy), "i"); + re$1.host_fuzzy_test = RegExp(untpl(re$1.tpl_host_fuzzy_test), "i"); + + // Compile each schema + + var aliases = []; + self.__compiled__ = {}; + // Reset compiled data + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); + } + Object.keys(self.__schemas__).forEach((function(name) { + var val = self.__schemas__[name]; + // skip disabled methods + if (val === null) { + return; + } + var compiled = { + validate: null, + link: null + }; + self.__compiled__[name] = compiled; + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate); + } else if (isFunction(val.validate)) { + compiled.validate = val.validate; + } else { + schemaError(name, val); + } + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize; + } else if (!val.normalize) { + compiled.normalize = createNormalizer(); + } else { + schemaError(name, val); + } + return; + } + if (isString(val)) { + aliases.push(name); + return; + } + schemaError(name, val); + })); + + // Compile postponed aliases + + aliases.forEach((function(alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return; + } + self.__compiled__[alias].validate = self.__compiled__[self.__schemas__[alias]].validate; + self.__compiled__[alias].normalize = self.__compiled__[self.__schemas__[alias]].normalize; + })); + + // Fake record for guessed links + + self.__compiled__[""] = { + validate: null, + normalize: createNormalizer() + }; + + // Build schema condition + + var slist = Object.keys(self.__compiled__).filter((function(name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name]; + })).map(escapeRE).join("|"); + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp("(^|(?!_)(?:[><\uff5c]|" + re$1.src_ZPCc + "))(" + slist + ")", "i"); + self.re.schema_search = RegExp("(^|(?!_)(?:[><\uff5c]|" + re$1.src_ZPCc + "))(" + slist + ")", "ig"); + self.re.schema_at_start = RegExp("^" + self.re.schema_search.source, "i"); + self.re.pretest = RegExp("(" + self.re.schema_test.source + ")|(" + self.re.host_fuzzy_test.source + ")|@", "i"); + + // Cleanup + + resetScanCache(self); + } + /** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ function Match(self, shift) { + var start = self.__index__, end = self.__last_index__, text = self.__text_cache__.slice(start, end); + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ this.schema = self.__schema__.toLowerCase(); + /** + * Match#index -> Number + * + * First position of matched string. + **/ this.index = start + shift; + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ this.lastIndex = end + shift; + /** + * Match#raw -> String + * + * Matched string. + **/ this.raw = text; + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ this.text = text; + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ this.url = text; + } + function createMatch(self, shift) { + var match = new Match(self, shift); + self.__compiled__[match.schema].normalize(match, self); + return match; + } + /** + * class LinkifyIt + **/ + /** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options); + } + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas; + schemas = {}; + } + } + this.__opts__ = assign({}, defaultOptions, options); + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1; + this.__last_index__ = -1; + // Next scan position + this.__schema__ = ""; + this.__text_cache__ = ""; + this.__schemas__ = assign({}, defaultSchemas, schemas); + this.__compiled__ = {}; + this.__tlds__ = tlds_default; + this.__tlds_replaced__ = false; + this.re = {}; + compile(this); + } + /** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition; + compile(this); + return this; + }; + /** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options); + return this; + }; + /** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + if (!text.length) { + return false; + } + var m, ml, me, len, shift, next, re, tld_pos, at_pos; + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search; + re.lastIndex = 0; + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex); + if (len) { + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + break; + } + } + } + if (this.__opts__.fuzzyLink && this.__compiled__["http:"]) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test); + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + shift = ml.index + ml[1].length; + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = ""; + this.__index__ = shift; + this.__last_index__ = ml.index + ml[0].length; + } + } + } + } + } + if (this.__opts__.fuzzyEmail && this.__compiled__["mailto:"]) { + // guess schemaless emails + at_pos = text.indexOf("@"); + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + shift = me.index + me[1].length; + next = me.index + me[0].length; + if (this.__index__ < 0 || shift < this.__index__ || shift === this.__index__ && next > this.__last_index__) { + this.__schema__ = "mailto:"; + this.__index__ = shift; + this.__last_index__ = next; + } + } + } + } + return this.__index__ >= 0; + }; + /** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text); + }; + /** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0; + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); + }; + /** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ LinkifyIt.prototype.match = function match(text) { + var shift = 0, result = []; + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)); + shift = this.__last_index__; + } + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text; + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)); + tail = tail.slice(this.__last_index__); + shift += this.__last_index__; + } + if (result.length) { + return result; + } + return null; + }; + /** + * LinkifyIt#matchAtStart(text) -> Match|null + * + * Returns fully-formed (not fuzzy) link if it starts at the beginning + * of the string, and null otherwise. + **/ LinkifyIt.prototype.matchAtStart = function matchAtStart(text) { + // Reset scan cache + this.__text_cache__ = text; + this.__index__ = -1; + if (!text.length) return null; + var m = this.re.schema_at_start.exec(text); + if (!m) return null; + var len = this.testSchemaAt(text, m[2], m[0].length); + if (!len) return null; + this.__schema__ = m[2]; + this.__index__ = m.index + m[1].length; + this.__last_index__ = m.index + m[0].length + len; + return createMatch(this, 0); + }; + /** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [ list ]; + if (!keepOld) { + this.__tlds__ = list.slice(); + this.__tlds_replaced__ = true; + compile(this); + return this; + } + this.__tlds__ = this.__tlds__.concat(list).sort().filter((function(el, idx, arr) { + return el !== arr[idx - 1]; + })).reverse(); + compile(this); + return this; + }; + /** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ LinkifyIt.prototype.normalize = function normalize(match) { + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + if (!match.schema) { + match.url = "http://" + match.url; + } + if (match.schema === "mailto:" && !/^mailto:/i.test(match.url)) { + match.url = "mailto:" + match.url; + } + }; + /** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ LinkifyIt.prototype.onCompile = function onCompile() {}; + var linkifyIt = LinkifyIt; + /*! https://mths.be/punycode v1.4.1 by @mathias */ + /** Highest positive signed 32-bit float value */ var maxInt = 2147483647; + // aka. 0x7FFFFFFF or 2^31-1 + /** Bootstring parameters */ var base = 36; + var tMin = 1; + var tMax = 26; + var skew = 38; + var damp = 700; + var initialBias = 72; + var initialN = 128; + // 0x80 + var delimiter = "-"; + // '\x2D' + /** Regular expressions */ var regexPunycode = /^xn--/; + var regexNonASCII = /[^\x20-\x7E]/; + // unprintable ASCII chars + non-ASCII chars + var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; + // RFC 3490 separators + /** Error messages */ var errors = { + overflow: "Overflow: input needs wider integers to process", + "not-basic": "Illegal input >= 0x80 (not a basic code point)", + "invalid-input": "Invalid input" + }; + /** Convenience shortcuts */ var baseMinusTMin = base - tMin; + var floor = Math.floor; + var stringFromCharCode = String.fromCharCode; + /*--------------------------------------------------------------------------*/ + /** * A generic error utility function. * @private * @param {String} type The error type. * @returns {Error} Throws a `RangeError` with the applicable error message. - */ - function error(type) { - throw new RangeError(errors[type]); - } - - /** + */ function error(type) { + throw new RangeError(errors[type]); + } + /** * A generic `Array#map` utility function. * @private * @param {Array} array The array to iterate over. * @param {Function} callback The function that gets called for every array * item. * @returns {Array} A new array of values returned by the callback function. - */ - function map(array, fn) { - var length = array.length; - var result = []; - while (length--) { - result[length] = fn(array[length]); - } - return result; - } - - /** + */ function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + /** * A simple `Array#map`-like wrapper to work with domain name strings or email * addresses. * @private @@ -7685,24 +7549,22 @@ module.exports = urlParse; * character. * @returns {Array} A new string of characters returned by the callback * function. - */ - function mapDomain(string, fn) { - var parts = string.split('@'); - var result = ''; - if (parts.length > 1) { - // In email addresses, only the domain name should be punycoded. Leave - // the local part (i.e. everything up to `@`) intact. - result = parts[0] + '@'; - string = parts[1]; - } - // Avoid `split(regex)` for IE8 compatibility. See #17. - string = string.replace(regexSeparators, '\x2E'); - var labels = string.split('.'); - var encoded = map(labels, fn).join('.'); - return result + encoded; - } - - /** + */ function mapDomain(string, fn) { + var parts = string.split("@"); + var result = ""; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + "@"; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, "."); + var labels = string.split("."); + var encoded = map(labels, fn).join("."); + return result + encoded; + } + /** * Creates an array containing the numeric code points of each Unicode * character in the string. While JavaScript uses UCS-2 internally, * this function will convert a pair of surrogate halves (each of which @@ -7714,55 +7576,48 @@ module.exports = urlParse; * @name decode * @param {String} string The Unicode input string (UCS-2). * @returns {Array} The new array of code points. - */ - function ucs2decode(string) { - var output = [], - counter = 0, - length = string.length, - value, - extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - /** + */ function ucs2decode(string) { + var output = [], counter = 0, length = string.length, value, extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 55296 && value <= 56319 && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 64512) == 56320) { + // low surrogate + output.push(((value & 1023) << 10) + (extra & 1023) + 65536); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + /** * Creates a string based on an array of numeric code points. * @see `punycode.ucs2.decode` * @memberOf punycode.ucs2 * @name encode * @param {Array} codePoints The array of numeric code points. * @returns {String} The new Unicode string (UCS-2). - */ - function ucs2encode(array) { - return map(array, function(value) { - var output = ''; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - return output; - }).join(''); - } - - /** + */ function ucs2encode(array) { + return map(array, (function(value) { + var output = ""; + if (value > 65535) { + value -= 65536; + output += stringFromCharCode(value >>> 10 & 1023 | 55296); + value = 56320 | value & 1023; + } + output += stringFromCharCode(value); + return output; + })).join(""); + } + /** * Converts a basic code point into a digit/integer. * @see `digitToBasic()` * @private @@ -7770,21 +7625,19 @@ module.exports = urlParse; * @returns {Number} The numeric value of a basic code point (for use in * representing integers) in the range `0` to `base - 1`, or `base` if * the code point does not represent a value. - */ - function basicToDigit(codePoint) { - if (codePoint - 48 < 10) { - return codePoint - 22; - } - if (codePoint - 65 < 26) { - return codePoint - 65; - } - if (codePoint - 97 < 26) { - return codePoint - 97; - } - return base; - } - - /** + */ function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + /** * Converts a digit/integer into a basic code point. * @see `basicToDigit()` * @private @@ -7794,244 +7647,171 @@ module.exports = urlParse; * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is * used; else, the lowercase form is used. The behavior is undefined * if `flag` is non-zero and `digit` has no uppercase form. - */ - function digitToBasic(digit, flag) { - // 0..25 map to ASCII a..z or A..Z - // 26..35 map to ASCII 0..9 - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - } - - /** + */ function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + /** * Bias adaptation function as per section 3.4 of RFC 3492. * https://tools.ietf.org/html/rfc3492#section-3.4 * @private - */ - function adapt(delta, numPoints, firstTime) { - var k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - } - - /** + */ function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (;delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + /** * Converts a Punycode string of ASCII-only symbols to a string of Unicode * symbols. * @memberOf punycode * @param {String} input The Punycode string of ASCII-only symbols. * @returns {String} The resulting string of Unicode symbols. - */ - function decode(input) { - // Don't use UCS-2 - var output = [], - inputLength = input.length, - out, - i = 0, - n = initialN, - bias = initialBias, - basic, - j, - index, - oldi, - w, - k, - digit, - t, - /** Cached calculation results */ - baseMinusT; - - // Handle the basic code points: let `basic` be the number of input code - // points before the last delimiter, or `0` if there is none, then copy - // the first basic code points to the output. - - basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } - - for (j = 0; j < basic; ++j) { - // if it's not a basic code point - if (input.charCodeAt(j) >= 0x80) { - error('not-basic'); - } - output.push(input.charCodeAt(j)); - } - - // Main decoding loop: start just after the last delimiter if any basic code - // points were copied; start at the beginning otherwise. - - for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { - - // `index` is the index of the next character to be consumed. - // Decode a generalized variable-length integer into `delta`, - // which gets added to `i`. The overflow checking is easier - // if we increase `i` as we go, then subtract off its starting - // value at the end to obtain `delta`. - for (oldi = i, w = 1, k = base; /* no condition */; k += base) { - - if (index >= inputLength) { - error('invalid-input'); - } - - digit = basicToDigit(input.charCodeAt(index++)); - - if (digit >= base || digit > floor((maxInt - i) / w)) { - error('overflow'); - } - - i += digit * w; - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - - if (digit < t) { - break; - } - - baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error('overflow'); - } - - w *= baseMinusT; - - } - - out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); - - // `i` was supposed to wrap around from `out` to `0`, - // incrementing `n` each time, so we'll fix that now: - if (floor(i / out) > maxInt - n) { - error('overflow'); - } - - n += floor(i / out); - i %= out; - - // Insert `n` at position `i` of the output - output.splice(i++, 0, n); - - } - - return ucs2encode(output); - } - - /** + */ function decode(input) { + // Don't use UCS-2 + var output = [], inputLength = input.length, out, i = 0, n = initialN, bias = initialBias, basic, j, index, oldi, w, k, digit, t, + /** Cached calculation results */ + baseMinusT; + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 128) { + error("not-basic"); + } + output.push(input.charCodeAt(j)); + } + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; ) { + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; ;k += base) { + if (index >= inputLength) { + error("invalid-input"); + } + digit = basicToDigit(input.charCodeAt(index++)); + if (digit >= base || digit > floor((maxInt - i) / w)) { + error("overflow"); + } + i += digit * w; + t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + if (digit < t) { + break; + } + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error("overflow"); + } + w *= baseMinusT; + } + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error("overflow"); + } + n += floor(i / out); + i %= out; + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + } + return ucs2encode(output); + } + /** * Converts a string of Unicode symbols (e.g. a domain name label) to a * Punycode string of ASCII-only symbols. * @memberOf punycode * @param {String} input The string of Unicode symbols. * @returns {String} The resulting Punycode string of ASCII-only symbols. - */ - function encode(input) { - var n, - delta, - handledCPCount, - basicLength, - bias, - j, - m, - q, - k, - t, - currentValue, - output = [], - /** `inputLength` will hold the number of code points in `input`. */ - inputLength, - /** Cached calculation results */ - handledCPCountPlusOne, - baseMinusT, - qMinusT; - - // Convert the input in UCS-2 to Unicode - input = ucs2decode(input); - - // Cache the length - inputLength = input.length; - - // Initialize the state - n = initialN; - delta = 0; - bias = initialBias; - - // Handle the basic code points - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue < 0x80) { - output.push(stringFromCharCode(currentValue)); - } - } - - handledCPCount = basicLength = output.length; - - // `handledCPCount` is the number of code points that have been handled; - // `basicLength` is the number of basic code points. - - // Finish the basic string - if it is not empty - with a delimiter - if (basicLength) { - output.push(delimiter); - } - - // Main encoding loop: - while (handledCPCount < inputLength) { - - // All non-basic code points < n have been handled already. Find the next - // larger one: - for (m = maxInt, j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } - - // Increase `delta` enough to advance the decoder's state to , - // but guard against overflow - handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error('overflow'); - } - - delta += (m - n) * handledCPCountPlusOne; - n = m; - - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - - if (currentValue < n && ++delta > maxInt) { - error('overflow'); - } - - if (currentValue == n) { - // Represent delta as a generalized variable-length integer - for (q = delta, k = base; /* no condition */; k += base) { - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - if (q < t) { - break; - } - qMinusT = q - t; - baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } - - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); - delta = 0; - ++handledCPCount; - } - } - - ++delta; - ++n; - - } - return output.join(''); - } - - /** + */ function encode(input) { + var n, delta, handledCPCount, basicLength, bias, j, m, q, k, t, currentValue, output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, baseMinusT, qMinusT; + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + // Cache the length + inputLength = input.length; + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 128) { + output.push(stringFromCharCode(currentValue)); + } + } + handledCPCount = basicLength = output.length; + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + // Main encoding loop: + while (handledCPCount < inputLength) { + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error("overflow"); + } + delta += (m - n) * handledCPCountPlusOne; + n = m; + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < n && ++delta > maxInt) { + error("overflow"); + } + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; ;k += base) { + t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))); + q = floor(qMinusT / baseMinusT); + } + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + ++delta; + ++n; + } + return output.join(""); + } + /** * Converts a Punycode string representing a domain name or an email address * to Unicode. Only the Punycoded parts of the input will be converted, i.e. * it doesn't matter if you call it on a string that has already been @@ -8041,16 +7821,12 @@ module.exports = urlParse; * convert to Unicode. * @returns {String} The Unicode representation of the given Punycode * string. - */ - function toUnicode(input) { - return mapDomain(input, function(string) { - return regexPunycode.test(string) - ? decode(string.slice(4).toLowerCase()) - : string; - }); - } - - /** + */ function toUnicode(input) { + return mapDomain(input, (function(string) { + return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string; + })); + } + /** * Converts a Unicode string representing a domain name or an email address to * Punycode. Only the non-ASCII parts of the domain name will be converted, * i.e. it doesn't matter if you call it with a domain that's already in @@ -8060,95 +7836,641 @@ module.exports = urlParse; * Unicode string. * @returns {String} The Punycode representation of the given domain name or * email address. - */ - function toASCII(input) { - return mapDomain(input, function(string) { - return regexNonASCII.test(string) - ? 'xn--' + encode(string) - : string; - }); - } - - /*--------------------------------------------------------------------------*/ - - /** Define the public API */ - punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - 'version': '1.4.1', - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - 'ucs2': { - 'decode': ucs2decode, - 'encode': ucs2encode - }, - 'decode': decode, - 'encode': encode, - 'toASCII': toASCII, - 'toUnicode': toUnicode - }; - - /** Expose `punycode` */ - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define('punycode', function() { - return punycode; - }); - } else if (freeExports && freeModule) { - if (module.exports == freeExports) { - // in Node.js, io.js, or RingoJS v0.8.0+ - freeModule.exports = punycode; - } else { - // in Narwhal or RingoJS v0.7.0- - for (key in punycode) { - punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); - } - } - } else { - // in Rhino or a web browser - root.punycode = punycode; - } - -}(this)); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],61:[function(require,module,exports){ -module.exports=/[\0-\x1F\x7F-\x9F]/ -},{}],62:[function(require,module,exports){ -module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ -},{}],63:[function(require,module,exports){ -module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ -},{}],64:[function(require,module,exports){ -module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ -},{}],65:[function(require,module,exports){ -'use strict'; - -exports.Any = require('./properties/Any/regex'); -exports.Cc = require('./categories/Cc/regex'); -exports.Cf = require('./categories/Cf/regex'); -exports.P = require('./categories/P/regex'); -exports.Z = require('./categories/Z/regex'); - -},{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ -module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ -},{}],67:[function(require,module,exports){ -'use strict'; - - -module.exports = require('./lib/'); - -},{"./lib/":9}]},{},[67])(67) -}); + */ function toASCII(input) { + return mapDomain(input, (function(string) { + return regexNonASCII.test(string) ? "xn--" + encode(string) : string; + })); + } + var version = "1.4.1"; + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ var ucs2 = { + decode: ucs2decode, + encode: ucs2encode + }; + var punycode$1 = { + version: version, + ucs2: ucs2, + toASCII: toASCII, + toUnicode: toUnicode, + encode: encode, + decode: decode + }; + var punycode$2 = Object.freeze({ + __proto__: null, + decode: decode, + encode: encode, + toUnicode: toUnicode, + toASCII: toASCII, + version: version, + ucs2: ucs2, + default: punycode$1 + }); + // markdown-it default options + var _default = { + options: { + html: false, + // Enable HTML tags in source + xhtmlOut: false, + // Use '/' to close single tags (
    ) + breaks: false, + // Convert '\n' in paragraphs into
    + langPrefix: "language-", + // CSS language prefix for fenced blocks + linkify: false, + // autoconvert URL-like texts to links + // Enable some language-neutral replacements + quotes beautification + typographer: false, + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: "\u201c\u201d\u2018\u2019", + /* “”‘’ */ + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, + // Convert '\n' in paragraphs into
    + langPrefix: "language-", + // CSS language prefix for fenced blocks + linkify: false, + // autoconvert URL-like texts to links + // Enable some language-neutral replacements + quotes beautification + typographer: false, + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: "\u201c\u201d\u2018\u2019", + /* “”‘’ */ + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, + // Convert '\n' in paragraphs into
    + langPrefix: "language-", + // CSS language prefix for fenced blocks + linkify: false, + // autoconvert URL-like texts to links + // Enable some language-neutral replacements + quotes beautification + typographer: false, + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: "\u201c\u201d\u2018\u2019", + /* “”‘’ */ + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with = 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) {} + } + } + return mdurl.encode(mdurl.format(parsed)); + } + function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) {} + } + } + // add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720 + return mdurl.decode(mdurl.format(parsed), mdurl.decode.defaultChars + "%"); + } + /** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + /** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
    `). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
    `. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
    ' +
    +	 *                hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
    +	 *                '
    '; + * } catch (__) {} + * } + * + * return '
    ' + md.utils.escapeHtml(str) + '
    '; + * } + * }); + * ``` + * + **/ function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = "default"; + } + } + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ this.inline = new parser_inline; + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ this.block = new parser_block; + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ this.core = new parser_core; + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ this.renderer = new renderer; + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ this.linkify = new linkifyIt; + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ this.validateLink = validateLink; + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ this.normalizeLink = normalizeLink; + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ this.normalizeLinkText = normalizeLinkText; + // Expose utils & helpers for easy acces from plugins + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ this.utils = utils; + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ this.helpers = utils.assign({}, helpers); + this.options = {}; + this.configure(presetName); + if (options) { + this.set(options); + } + } + /** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ MarkdownIt.prototype.set = function(options) { + utils.assign(this.options, options); + return this; + }; + /** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you will - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ MarkdownIt.prototype.configure = function(presets) { + var self = this, presetName; + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { + throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); + } + } + if (!presets) { + throw new Error("Wrong `markdown-it` preset, can't be empty"); + } + if (presets.options) { + self.set(presets.options); + } + if (presets.components) { + Object.keys(presets.components).forEach((function(name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + })); + } + return this; + }; + /** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ MarkdownIt.prototype.enable = function(list, ignoreInvalid) { + var result = []; + if (!Array.isArray(list)) { + list = [ list ]; + } + [ "core", "block", "inline" ].forEach((function(chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }), this); + result = result.concat(this.inline.ruler2.enable(list, true)); + var missed = list.filter((function(name) { + return result.indexOf(name) < 0; + })); + if (missed.length && !ignoreInvalid) { + throw new Error("MarkdownIt. Failed to enable unknown rule(s): " + missed); + } + return this; + }; + /** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ MarkdownIt.prototype.disable = function(list, ignoreInvalid) { + var result = []; + if (!Array.isArray(list)) { + list = [ list ]; + } + [ "core", "block", "inline" ].forEach((function(chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }), this); + result = result.concat(this.inline.ruler2.disable(list, true)); + var missed = list.filter((function(name) { + return result.indexOf(name) < 0; + })); + if (missed.length && !ignoreInvalid) { + throw new Error("MarkdownIt. Failed to disable unknown rule(s): " + missed); + } + return this; + }; + /** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ MarkdownIt.prototype.use = function(plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; + }; + /** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and return list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ MarkdownIt.prototype.parse = function(src, env) { + if (typeof src !== "string") { + throw new Error("Input data should be a String"); + } + var state = new this.core.State(src, this, env); + this.core.process(state); + return state.tokens; + }; + /** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ MarkdownIt.prototype.render = function(src, env) { + env = env || {}; + return this.renderer.render(this.parse(src, env), this.options, env); + }; + /** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ MarkdownIt.prototype.parseInline = function(src, env) { + var state = new this.core.State(src, this, env); + state.inlineMode = true; + this.core.process(state); + return state.tokens; + }; + /** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

    ` tags. + **/ MarkdownIt.prototype.renderInline = function(src, env) { + env = env || {}; + return this.renderer.render(this.parseInline(src, env), this.options, env); + }; + var lib = MarkdownIt; + var markdownIt = lib; + return markdownIt; +})); diff --git a/yarn.lock b/yarn.lock index d0274ef25a..a3413b669b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -437,6 +437,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1375,10 +1380,10 @@ ensure-posix-path@^1.0.0: resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce" integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw== -entities@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== +entities@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: version "1.18.3" @@ -2585,10 +2590,10 @@ lighthouse-logger@^1.0.0: debug "^2.6.8" marky "^1.2.0" -linkify-it@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" - integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== +linkify-it@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" + integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== dependencies: uc.micro "^1.0.1" @@ -2669,14 +2674,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-it@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" - integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== +markdown-it@13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430" + integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q== dependencies: - argparse "^1.0.7" - entities "~2.0.0" - linkify-it "^2.0.0" + argparse "^2.0.1" + entities "~3.0.1" + linkify-it "^4.0.1" mdurl "^1.0.1" uc.micro "^1.0.5" From 321118f38417078dc86c56a21e8886bbe3a2bc16 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 20 Jun 2022 15:41:53 +0200 Subject: [PATCH 061/184] DEV: Change `locationType` to `history`. (#17152) `auto` value is deprecated, and we never really supported `hash` (which was the type `auto` was falling back into) --- app/assets/javascripts/discourse/config/environment.js | 2 +- app/helpers/application_helper.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/config/environment.js b/app/assets/javascripts/discourse/config/environment.js index 4156825449..f16ed53743 100644 --- a/app/assets/javascripts/discourse/config/environment.js +++ b/app/assets/javascripts/discourse/config/environment.js @@ -5,7 +5,7 @@ module.exports = function (environment) { modulePrefix: "discourse", environment, rootURL: process.env.DISCOURSE_RELATIVE_URL_ROOT || "/", - locationType: "auto", + locationType: "history", historySupportMiddleware: false, EmberENV: { FEATURES: { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f069f5560c..82b29b7dda 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -20,7 +20,7 @@ module ApplicationHelper modulePrefix: "discourse", environment: Rails.env, rootURL: Discourse.base_path, - locationType: "auto", + locationType: "history", historySupportMiddleware: false, EmberENV: { FEATURES: {}, From ba2c7b8f3508fbb6d55e9618c32859999063c499 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 20 Jun 2022 15:42:10 +0200 Subject: [PATCH 062/184] DEV: Use the block form of `module()` (#17151) --- .../discourse/tests/unit/models/theme-test.js | 34 +- .../javascripts/lib/details-cooked-test.js | 36 +- .../lib/date-with-zone-helper-test.js | 298 ++--- .../lib/local-date-builder-test.js | 1054 ++++++++--------- 4 files changed, 713 insertions(+), 709 deletions(-) diff --git a/app/assets/javascripts/discourse/tests/unit/models/theme-test.js b/app/assets/javascripts/discourse/tests/unit/models/theme-test.js index 1d3f2e49fc..c01ecd59db 100644 --- a/app/assets/javascripts/discourse/tests/unit/models/theme-test.js +++ b/app/assets/javascripts/discourse/tests/unit/models/theme-test.js @@ -1,22 +1,26 @@ import { module, test } from "qunit"; import Theme from "admin/models/theme"; -module("Unit | Model | theme"); +module("Unit | Model | theme", function () { + test("can add an upload correctly", function (assert) { + let theme = Theme.create(); -test("can add an upload correctly", function (assert) { - let theme = Theme.create(); + assert.strictEqual( + theme.get("uploads.length"), + 0, + "uploads should be an empty array" + ); - assert.strictEqual( - theme.get("uploads.length"), - 0, - "uploads should be an empty array" - ); + theme.setField("common", "bob", "", 999, 2); + let fields = theme.get("theme_fields"); + assert.strictEqual(fields.length, 1, "expecting 1 theme field"); + assert.strictEqual( + fields[0].upload_id, + 999, + "expecting upload id to be set" + ); + assert.strictEqual(fields[0].type_id, 2, "expecting type id to be set"); - theme.setField("common", "bob", "", 999, 2); - let fields = theme.get("theme_fields"); - assert.strictEqual(fields.length, 1, "expecting 1 theme field"); - assert.strictEqual(fields[0].upload_id, 999, "expecting upload id to be set"); - assert.strictEqual(fields[0].type_id, 2, "expecting type id to be set"); - - assert.strictEqual(theme.get("uploads.length"), 1, "expecting an upload"); + assert.strictEqual(theme.get("uploads.length"), 1, "expecting an upload"); + }); }); diff --git a/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js b/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js index d8a9ff3687..a436ce76df 100644 --- a/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js +++ b/plugins/discourse-details/test/javascripts/lib/details-cooked-test.js @@ -1,8 +1,6 @@ import PrettyText, { buildOptions } from "pretty-text/pretty-text"; import { module, test } from "qunit"; -module("lib:details-cooked-test"); - const defaultOpts = buildOptions({ siteSettings: { enable_emoji: true, @@ -14,26 +12,28 @@ const defaultOpts = buildOptions({ getURL: (url) => url, }); -test("details", function (assert) { - const cooked = (input, expected, text) => { - assert.strictEqual( - new PrettyText(defaultOpts).cook(input), - expected.replace(/\/>/g, ">"), - text +module("lib:details-cooked-test", function () { + test("details", function (assert) { + const cooked = (input, expected, text) => { + assert.strictEqual( + new PrettyText(defaultOpts).cook(input), + expected.replace(/\/>/g, ">"), + text + ); + }; + cooked( + `

    Infocoucou
    `, + `
    Infocoucou
    `, + "manual HTML for details" ); - }; - cooked( - `
    Infocoucou
    `, - `
    Infocoucou
    `, - "manual HTML for details" - ); - cooked( - "[details=testing]\ntest\n[/details]", - `
    + cooked( + "[details=testing]\ntest\n[/details]", + `
    testing

    test

    ` - ); + ); + }); }); diff --git a/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js index 7257cd8862..39f4483486 100644 --- a/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js +++ b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js @@ -4,8 +4,6 @@ import { module, test } from "qunit"; const PARIS = "Europe/Paris"; const SYDNEY = "Australia/Sydney"; -module("lib:date-with-zone-helper"); - function buildDateHelper(params = {}) { return new DateWithZoneHelper({ year: params.year || 2020, @@ -18,152 +16,154 @@ function buildDateHelper(params = {}) { }); } -test("#format", function (assert) { - let date = buildDateHelper({ - day: 15, - month: 2, - hour: 15, - minute: 36, - timezone: PARIS, +module("lib:date-with-zone-helper", function () { + test("#format", function (assert) { + let date = buildDateHelper({ + day: 15, + month: 2, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.strictEqual(date.format(), "2020-03-15T15:36:00.000+01:00"); + }); + + test("#unitRepetitionsBetweenDates", function (assert) { + let date; + + date = buildDateHelper({ + day: 15, + month: 1, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.strictEqual( + date.unitRepetitionsBetweenDates( + "1.hour", + moment.tz("2020-02-15 15:36", SYDNEY) + ), + 10, + "it correctly finds difference between timezones" + ); + + date = buildDateHelper({ + day: 15, + month: 1, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.strictEqual( + date.unitRepetitionsBetweenDates( + "1.minute", + moment.tz("2020-02-15 15:36", PARIS) + ), + 0, + "it correctly finds no difference" + ); + + date = buildDateHelper({ + day: 15, + month: 1, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.strictEqual( + date.unitRepetitionsBetweenDates( + "1.minute", + moment.tz("2020-02-15 15:37", PARIS) + ), + 1, + "it correctly finds no difference" + ); + + date = buildDateHelper({ + day: 15, + month: 1, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.strictEqual( + date.unitRepetitionsBetweenDates( + "2.minutes", + moment.tz("2020-02-15 15:41", PARIS) + ), + 6, + "it correctly finds difference with a multiplicator" + ); + }); + + test("#add", function (assert) { + let date; + let futureLocalDate; + + date = buildDateHelper({ + day: 19, + month: 2, + hour: 15, + minute: 36, + timezone: PARIS, + }); + + assert.notOk(date.isDST()); + futureLocalDate = date.add(8, "months"); + assert.notOk(futureLocalDate.isDST()); + assert.strictEqual( + futureLocalDate.format(), + "2020-11-19T15:36:00.000+01:00", + "it correctly adds from a !isDST date to a !isDST date" + ); + + date = buildDateHelper({ + day: 25, + month: 3, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.ok(date.isDST()); + futureLocalDate = date.add(1, "year"); + assert.ok(futureLocalDate.isDST()); + assert.strictEqual( + futureLocalDate.format(), + "2021-04-25T15:36:00.000+02:00", + "it correctly adds from a isDST date to a isDST date" + ); + + date = buildDateHelper({ + day: 25, + month: 2, + hour: 15, + minute: 36, + timezone: PARIS, + }); + assert.notOk(date.isDST()); + futureLocalDate = date.add(1, "week"); + assert.ok(futureLocalDate.isDST()); + assert.strictEqual( + futureLocalDate.format(), + "2020-04-01T15:36:00.000+02:00", + "it correctly adds from a !isDST date to a isDST date" + ); + + date = buildDateHelper({ + day: 1, + month: 3, + hour: 15, + minute: 36, + timezone: PARIS, + }); + + assert.ok(date.isDST()); + futureLocalDate = date.add(8, "months"); + assert.notOk(futureLocalDate.isDST()); + assert.strictEqual( + futureLocalDate.format(), + "2020-12-01T15:36:00.000+01:00", + "it correctly adds from a isDST date to a !isDST date" + ); }); - assert.strictEqual(date.format(), "2020-03-15T15:36:00.000+01:00"); -}); - -test("#unitRepetitionsBetweenDates", function (assert) { - let date; - - date = buildDateHelper({ - day: 15, - month: 1, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.strictEqual( - date.unitRepetitionsBetweenDates( - "1.hour", - moment.tz("2020-02-15 15:36", SYDNEY) - ), - 10, - "it correctly finds difference between timezones" - ); - - date = buildDateHelper({ - day: 15, - month: 1, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.strictEqual( - date.unitRepetitionsBetweenDates( - "1.minute", - moment.tz("2020-02-15 15:36", PARIS) - ), - 0, - "it correctly finds no difference" - ); - - date = buildDateHelper({ - day: 15, - month: 1, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.strictEqual( - date.unitRepetitionsBetweenDates( - "1.minute", - moment.tz("2020-02-15 15:37", PARIS) - ), - 1, - "it correctly finds no difference" - ); - - date = buildDateHelper({ - day: 15, - month: 1, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.strictEqual( - date.unitRepetitionsBetweenDates( - "2.minutes", - moment.tz("2020-02-15 15:41", PARIS) - ), - 6, - "it correctly finds difference with a multiplicator" - ); -}); - -test("#add", function (assert) { - let date; - let futureLocalDate; - - date = buildDateHelper({ - day: 19, - month: 2, - hour: 15, - minute: 36, - timezone: PARIS, - }); - - assert.notOk(date.isDST()); - futureLocalDate = date.add(8, "months"); - assert.notOk(futureLocalDate.isDST()); - assert.strictEqual( - futureLocalDate.format(), - "2020-11-19T15:36:00.000+01:00", - "it correctly adds from a !isDST date to a !isDST date" - ); - - date = buildDateHelper({ - day: 25, - month: 3, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.ok(date.isDST()); - futureLocalDate = date.add(1, "year"); - assert.ok(futureLocalDate.isDST()); - assert.strictEqual( - futureLocalDate.format(), - "2021-04-25T15:36:00.000+02:00", - "it correctly adds from a isDST date to a isDST date" - ); - - date = buildDateHelper({ - day: 25, - month: 2, - hour: 15, - minute: 36, - timezone: PARIS, - }); - assert.notOk(date.isDST()); - futureLocalDate = date.add(1, "week"); - assert.ok(futureLocalDate.isDST()); - assert.strictEqual( - futureLocalDate.format(), - "2020-04-01T15:36:00.000+02:00", - "it correctly adds from a !isDST date to a isDST date" - ); - - date = buildDateHelper({ - day: 1, - month: 3, - hour: 15, - minute: 36, - timezone: PARIS, - }); - - assert.ok(date.isDST()); - futureLocalDate = date.add(8, "months"); - assert.notOk(futureLocalDate.isDST()); - assert.strictEqual( - futureLocalDate.format(), - "2020-12-01T15:36:00.000+01:00", - "it correctly adds from a isDST date to a !isDST date" - ); }); diff --git a/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js index 86414743b2..a95bb74fa5 100644 --- a/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js +++ b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js @@ -11,8 +11,6 @@ const PARIS = "Europe/Paris"; const LAGOS = "Africa/Lagos"; const LONDON = "Europe/London"; -module("lib:local-date-builder"); - function freezeTime({ date, timezone }, cb) { date = date || "2020-01-22 10:34"; const newTimezone = timezone || PARIS; @@ -50,7 +48,7 @@ QUnit.assert.buildsCorrectDate = function (options, expected, message) { this.test.assert.strictEqual( localDateBuilder.build().formated, expected.formated, - message || "it formates the date correctly" + message || "it formats the date correctly" ); } @@ -58,535 +56,537 @@ QUnit.assert.buildsCorrectDate = function (options, expected, message) { this.test.assert.deepEqual( localDateBuilder.build().previews, expected.previews, - message || "it formates the previews correctly" + message || "it formats the previews correctly" ); } }; -test("date", function (assert) { - freezeTime({ date: "2020-03-11" }, () => { +module("lib:local-date-builder", function () { + test("date", function (assert) { + freezeTime({ date: "2020-03-11" }, () => { + assert.buildsCorrectDate( + { date: "2020-03-22", timezone: PARIS }, + { formated: "March 22, 2020" }, + "it displays the date without time" + ); + }); + }); + + test("date and time", function (assert) { assert.buildsCorrectDate( - { date: "2020-03-22", timezone: PARIS }, - { formated: "March 22, 2020" }, - "it displays the date without time" - ); - }); -}); - -test("date and time", function (assert) { - assert.buildsCorrectDate( - { date: "2020-04-11", time: "11:00" }, - { formated: "April 11, 2020 1:00 PM" }, - "it displays the date with time" - ); - - assert.buildsCorrectDate( - { date: "2020-04-11", time: "11:05:12", format: "LTS" }, - { formated: "1:05:12 PM" }, - "it displays full time (hours, minutes, seconds)" - ); -}); - -test("option[format]", function (assert) { - freezeTime({ date: "2020-03-11" }, () => { - assert.buildsCorrectDate( - { format: "YYYY" }, - { formated: "2020 (UTC)" }, - "it uses custom format" - ); - }); -}); - -test("option[displayedTimezone]", function (assert) { - freezeTime({}, () => { - assert.buildsCorrectDate( - { displayedTimezone: SYDNEY }, - { formated: "March 22, 2020 (Sydney)" }, - "it displays the timezone if the timezone is different from the date" - ); - }); - - freezeTime({}, () => { - assert.buildsCorrectDate( - { displayedTimezone: PARIS, timezone: PARIS }, - { formated: "March 22, 2020" }, - "it doesn't display the timezone if the timezone is the same than the date" - ); - }); - - freezeTime({}, () => { - assert.buildsCorrectDate( - { timezone: UTC, displayedTimezone: UTC }, - { formated: "March 22, 2020 (UTC)" }, - "it replaces `Etc/`" - ); - }); - - freezeTime({}, () => { - assert.buildsCorrectDate( - { timezone: LOS_ANGELES, displayedTimezone: LOS_ANGELES }, - { formated: "March 22, 2020 (Los Angeles)" }, - "it removes prefix and replaces `_`" - ); - }); -}); - -test("option[timezone]", function (assert) { - freezeTime({}, () => { - assert.buildsCorrectDate( - { timezone: SYDNEY, displayedTimezone: PARIS }, - { formated: "March 21, 2020" }, - "it correctly parses a date with the given timezone context" - ); - }); -}); - -test("option[recurring]", function (assert) { - freezeTime({ date: "2020-04-06 06:00", timezone: LAGOS }, () => { - assert.buildsCorrectDate( - { - date: "2019-11-25", - time: "11:00", - timezone: PARIS, - displayedTimezone: LAGOS, - recurring: "1.weeks", - }, - { - formated: "April 6, 2020 10:00 AM (Lagos)", - }, - "it correctly formats a recurring date starting from a !isDST timezone to a isDST timezone date when displayed to a user using a timezone with no DST" - ); - }); - - freezeTime({ date: "2020-04-06 01:00", timezone: SYDNEY }, () => { - assert.buildsCorrectDate( - { - date: "2020-03-09", - time: "02:00", - timezone: UTC, - recurring: "1.weeks", - calendar: false, - displayedTimezone: SYDNEY, - }, - { - formated: "April 6, 2020 12:00 PM (Sydney)", - }, - "it correctly formats a recurring date spanning over weeks" - ); - }); - - freezeTime({ date: "2020-04-07 22:00" }, () => { - assert.buildsCorrectDate( - { - date: "2019-11-25", - time: "11:00", - recurring: "1.weeks", - timezone: PARIS, - }, - { - formated: "April 13, 2020 11:00 AM", - }, - "it correctly adds from a !isDST date to a isDST date" - ); - }); - - freezeTime({ date: "2020-04-06 10:59" }, () => { - assert.buildsCorrectDate( - { - date: "2020-03-30", - time: "11:00", - recurring: "1.weeks", - timezone: PARIS, - }, - { - formated: "Today 11:00 AM", - }, - "it works to the minute" - ); - }); - - freezeTime({ date: "2020-04-06 11:01" }, () => { - assert.buildsCorrectDate( - { - date: "2020-03-30", - time: "11:00", - recurring: "1.weeks", - timezone: PARIS, - }, - { - formated: "April 13, 2020 11:00 AM", - }, - "it works to the minute" - ); - }); - - freezeTime({ date: "2020-12-28 09:16" }, () => { - assert.buildsCorrectDate( - { - date: "2021-01-24", - time: "08:30", - recurring: "1.weeks", - timezone: NEW_YORK, - }, - { - formated: "January 24, 2021 2:30 PM", - }, - "it works for a future date" - ); - }); - - freezeTime({ date: "2021-01-08 11:16" }, () => { - assert.buildsCorrectDate( - { - date: "2021-01-05", - time: "14:00", - recurring: "2.hours", - timezone: NEW_YORK, - }, - { - formated: "Today 12:00 PM", - }, - "it works with hours" - ); - }); -}); - -test("option[countown]", function (assert) { - freezeTime({ date: "2020-03-21 23:59" }, () => { - assert.buildsCorrectDate( - { - countdown: true, - timezone: PARIS, - }, - { formated: "a minute" }, - "it shows the time remaining" - ); - }); - - freezeTime({ date: "2020-03-22 00:01" }, () => { - assert.buildsCorrectDate( - { - countdown: true, - timezone: PARIS, - }, - { - formated: I18n.t( - "discourse_local_dates.relative_dates.countdown.passed" - ), - }, - "it shows the date has passed" - ); - }); -}); - -test("option[calendar]", function (assert) { - freezeTime({ date: "2020-03-23 23:00" }, () => { - assert.buildsCorrectDate( - { date: "2020-03-22", time: "23:59", timezone: PARIS }, - { formated: "Yesterday 11:59 PM" }, - "it drops calendar mode when event date is more than one day before current date" - ); - }); - - freezeTime({ date: "2020-03-20 23:59" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", time: "01:00", timezone: PARIS }, - { formated: "Tomorrow 1:00 AM" } - ) - ); - - freezeTime({ date: "2020-03-20 23:59" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", time: "00:00", timezone: PARIS }, - { formated: "Saturday" }, - "it displays the day with no time when the time in the displayed timezone is 00:00" - ) - ); - - freezeTime({ date: "2020-03-20 23:59" }, () => { - assert.buildsCorrectDate( - { date: "2020-03-21", time: "23:59", timezone: PARIS }, - { formated: "Tomorrow 11:59 PM" } - ); - }); - - freezeTime({ date: "2020-03-21 00:00" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", time: "23:00", timezone: PARIS }, - { formated: "Today 11:00 PM" } - ) - ); - - freezeTime({ date: "2020-03-22 23:59" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", time: "23:59", timezone: PARIS }, - { formated: "Yesterday 11:59 PM" } - ) - ); - - freezeTime({ date: "2020-03-22 23:59" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", time: "23:59", timezone: PARIS }, - { formated: "Yesterday 11:59 PM" } - ) - ); - - freezeTime({ date: "2020-03-22 23:59" }, () => - assert.buildsCorrectDate( - { calendar: false, date: "2020-03-21", time: "23:59", timezone: PARIS }, - { formated: "March 21, 2020 11:59 PM" }, - "it doesn't use calendar when disabled" - ) - ); - - freezeTime({ date: "2020-03-24 01:00" }, () => - assert.buildsCorrectDate( - { date: "2020-03-21", timezone: PARIS }, - { formated: "March 21, 2020" }, - "it stops formating out of calendar range" - ) - ); - - freezeTime({ date: "2020-05-12", timezone: LOS_ANGELES }, () => { - assert.buildsCorrectDate( - { - date: "2020-05-13", - time: "18:00", - localTimezone: LOS_ANGELES, - }, - { formated: "Tomorrow 11:00 AM" }, - "it correctly displays a different local timezone" - ); - }); -}); - -test("previews", function (assert) { - freezeTime({ date: "2020-03-22" }, () => { - assert.buildsCorrectDate( - { timezone: PARIS }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { timezone: PARIS, timezones: [SYDNEY] }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - { - formated: - "Sunday, March 22, 2020 10:00 AM → Monday, March 23, 2020 10:00 AM", - timezone: "Sydney", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { timezone: PARIS, displayedTimezone: LOS_ANGELES }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { timezone: PARIS, isplayedTimezone: PARIS }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { timezone: PARIS, timezones: [PARIS] }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { duration: 90, timezone: PARIS, timezones: [PARIS] }, - { - previews: [ - { - current: true, - formated: - 'Sunday, March 22, 2020
    12:00 AM → 1:30 AM', - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { duration: 1440, timezone: PARIS, timezones: [PARIS] }, - { - previews: [ - { - current: true, - formated: - "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { time: "11:34", timezone: PARIS, timezones: [PARIS] }, - { - previews: [ - { - current: true, - formated: - 'Sunday, March 22, 2020
    11:34 AM', - timezone: "Paris", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-04-06", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { - timezone: PARIS, - date: "2020-04-07", - timezones: [LONDON, LAGOS, SYDNEY], - }, - { - previews: [ - { - current: true, - formated: - "Tuesday, April 7, 2020 12:00 AM → Wednesday, April 8, 2020 12:00 AM", - timezone: "Paris", - }, - { - formated: - "Monday, April 6, 2020 11:00 PM → Tuesday, April 7, 2020 11:00 PM", - timezone: "London", - }, - { - formated: - "Monday, April 6, 2020 11:00 PM → Tuesday, April 7, 2020 11:00 PM", - timezone: "Lagos", - }, - { - formated: - "Tuesday, April 7, 2020 8:00 AM → Wednesday, April 8, 2020 8:00 AM", - timezone: "Sydney", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-04-06", timezone: PARIS }, () => { - assert.buildsCorrectDate( - { - timezone: PARIS, - date: "2020-04-07", - time: "14:54", - timezones: [LONDON, LAGOS, SYDNEY], - }, - { - previews: [ - { - current: true, - formated: - 'Tuesday, April 7, 2020
    2:54 PM', - timezone: "Paris", - }, - { - formated: - 'Tuesday, April 7, 2020
    1:54 PM', - timezone: "London", - }, - { - formated: - 'Tuesday, April 7, 2020
    1:54 PM', - timezone: "Lagos", - }, - { - formated: - 'Tuesday, April 7, 2020
    10:54 PM', - timezone: "Sydney", - }, - ], - } - ); - }); - - freezeTime({ date: "2020-05-12", timezone: LOS_ANGELES }, () => { - assert.buildsCorrectDate( - { - date: "2020-05-13", - time: "18:00", - localTimezone: LOS_ANGELES, - }, - { - previews: [ - { - current: true, - formated: - 'Wednesday, May 13, 2020
    11:00 AM', - timezone: "Los Angeles", - }, - { - formated: - 'Wednesday, May 13, 2020
    6:00 PM', - timezone: "UTC", - }, - ], - } + { date: "2020-04-11", time: "11:00" }, + { formated: "April 11, 2020 1:00 PM" }, + "it displays the date with time" ); + + assert.buildsCorrectDate( + { date: "2020-04-11", time: "11:05:12", format: "LTS" }, + { formated: "1:05:12 PM" }, + "it displays full time (hours, minutes, seconds)" + ); + }); + + test("option[format]", function (assert) { + freezeTime({ date: "2020-03-11" }, () => { + assert.buildsCorrectDate( + { format: "YYYY" }, + { formated: "2020 (UTC)" }, + "it uses custom format" + ); + }); + }); + + test("option[displayedTimezone]", function (assert) { + freezeTime({}, () => { + assert.buildsCorrectDate( + { displayedTimezone: SYDNEY }, + { formated: "March 22, 2020 (Sydney)" }, + "it displays the timezone if the timezone is different from the date" + ); + }); + + freezeTime({}, () => { + assert.buildsCorrectDate( + { displayedTimezone: PARIS, timezone: PARIS }, + { formated: "March 22, 2020" }, + "it doesn't display the timezone if the timezone is the same than the date" + ); + }); + + freezeTime({}, () => { + assert.buildsCorrectDate( + { timezone: UTC, displayedTimezone: UTC }, + { formated: "March 22, 2020 (UTC)" }, + "it replaces `Etc/`" + ); + }); + + freezeTime({}, () => { + assert.buildsCorrectDate( + { timezone: LOS_ANGELES, displayedTimezone: LOS_ANGELES }, + { formated: "March 22, 2020 (Los Angeles)" }, + "it removes prefix and replaces `_`" + ); + }); + }); + + test("option[timezone]", function (assert) { + freezeTime({}, () => { + assert.buildsCorrectDate( + { timezone: SYDNEY, displayedTimezone: PARIS }, + { formated: "March 21, 2020" }, + "it correctly parses a date with the given timezone context" + ); + }); + }); + + test("option[recurring]", function (assert) { + freezeTime({ date: "2020-04-06 06:00", timezone: LAGOS }, () => { + assert.buildsCorrectDate( + { + date: "2019-11-25", + time: "11:00", + timezone: PARIS, + displayedTimezone: LAGOS, + recurring: "1.weeks", + }, + { + formated: "April 6, 2020 10:00 AM (Lagos)", + }, + "it correctly formats a recurring date starting from a !isDST timezone to a isDST timezone date when displayed to a user using a timezone with no DST" + ); + }); + + freezeTime({ date: "2020-04-06 01:00", timezone: SYDNEY }, () => { + assert.buildsCorrectDate( + { + date: "2020-03-09", + time: "02:00", + timezone: UTC, + recurring: "1.weeks", + calendar: false, + displayedTimezone: SYDNEY, + }, + { + formated: "April 6, 2020 12:00 PM (Sydney)", + }, + "it correctly formats a recurring date spanning over weeks" + ); + }); + + freezeTime({ date: "2020-04-07 22:00" }, () => { + assert.buildsCorrectDate( + { + date: "2019-11-25", + time: "11:00", + recurring: "1.weeks", + timezone: PARIS, + }, + { + formated: "April 13, 2020 11:00 AM", + }, + "it correctly adds from a !isDST date to a isDST date" + ); + }); + + freezeTime({ date: "2020-04-06 10:59" }, () => { + assert.buildsCorrectDate( + { + date: "2020-03-30", + time: "11:00", + recurring: "1.weeks", + timezone: PARIS, + }, + { + formated: "Today 11:00 AM", + }, + "it works to the minute" + ); + }); + + freezeTime({ date: "2020-04-06 11:01" }, () => { + assert.buildsCorrectDate( + { + date: "2020-03-30", + time: "11:00", + recurring: "1.weeks", + timezone: PARIS, + }, + { + formated: "April 13, 2020 11:00 AM", + }, + "it works to the minute" + ); + }); + + freezeTime({ date: "2020-12-28 09:16" }, () => { + assert.buildsCorrectDate( + { + date: "2021-01-24", + time: "08:30", + recurring: "1.weeks", + timezone: NEW_YORK, + }, + { + formated: "January 24, 2021 2:30 PM", + }, + "it works for a future date" + ); + }); + + freezeTime({ date: "2021-01-08 11:16" }, () => { + assert.buildsCorrectDate( + { + date: "2021-01-05", + time: "14:00", + recurring: "2.hours", + timezone: NEW_YORK, + }, + { + formated: "Today 12:00 PM", + }, + "it works with hours" + ); + }); + }); + + test("option[countdown]", function (assert) { + freezeTime({ date: "2020-03-21 23:59" }, () => { + assert.buildsCorrectDate( + { + countdown: true, + timezone: PARIS, + }, + { formated: "a minute" }, + "it shows the time remaining" + ); + }); + + freezeTime({ date: "2020-03-22 00:01" }, () => { + assert.buildsCorrectDate( + { + countdown: true, + timezone: PARIS, + }, + { + formated: I18n.t( + "discourse_local_dates.relative_dates.countdown.passed" + ), + }, + "it shows the date has passed" + ); + }); + }); + + test("option[calendar]", function (assert) { + freezeTime({ date: "2020-03-23 23:00" }, () => { + assert.buildsCorrectDate( + { date: "2020-03-22", time: "23:59", timezone: PARIS }, + { formated: "Yesterday 11:59 PM" }, + "it drops calendar mode when event date is more than one day before current date" + ); + }); + + freezeTime({ date: "2020-03-20 23:59" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "01:00", timezone: PARIS }, + { formated: "Tomorrow 1:00 AM" } + ) + ); + + freezeTime({ date: "2020-03-20 23:59" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "00:00", timezone: PARIS }, + { formated: "Saturday" }, + "it displays the day with no time when the time in the displayed timezone is 00:00" + ) + ); + + freezeTime({ date: "2020-03-20 23:59" }, () => { + assert.buildsCorrectDate( + { date: "2020-03-21", time: "23:59", timezone: PARIS }, + { formated: "Tomorrow 11:59 PM" } + ); + }); + + freezeTime({ date: "2020-03-21 00:00" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "23:00", timezone: PARIS }, + { formated: "Today 11:00 PM" } + ) + ); + + freezeTime({ date: "2020-03-22 23:59" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "23:59", timezone: PARIS }, + { formated: "Yesterday 11:59 PM" } + ) + ); + + freezeTime({ date: "2020-03-22 23:59" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", time: "23:59", timezone: PARIS }, + { formated: "Yesterday 11:59 PM" } + ) + ); + + freezeTime({ date: "2020-03-22 23:59" }, () => + assert.buildsCorrectDate( + { calendar: false, date: "2020-03-21", time: "23:59", timezone: PARIS }, + { formated: "March 21, 2020 11:59 PM" }, + "it doesn't use calendar when disabled" + ) + ); + + freezeTime({ date: "2020-03-24 01:00" }, () => + assert.buildsCorrectDate( + { date: "2020-03-21", timezone: PARIS }, + { formated: "March 21, 2020" }, + "it stops formatting out of calendar range" + ) + ); + + freezeTime({ date: "2020-05-12", timezone: LOS_ANGELES }, () => { + assert.buildsCorrectDate( + { + date: "2020-05-13", + time: "18:00", + localTimezone: LOS_ANGELES, + }, + { formated: "Tomorrow 11:00 AM" }, + "it correctly displays a different local timezone" + ); + }); + }); + + test("previews", function (assert) { + freezeTime({ date: "2020-03-22" }, () => { + assert.buildsCorrectDate( + { timezone: PARIS }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { timezone: PARIS, timezones: [SYDNEY] }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + { + formated: + "Sunday, March 22, 2020 10:00 AM → Monday, March 23, 2020 10:00 AM", + timezone: "Sydney", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { timezone: PARIS, displayedTimezone: LOS_ANGELES }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { timezone: PARIS, displayedTimezone: PARIS }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { timezone: PARIS, timezones: [PARIS] }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { duration: 90, timezone: PARIS, timezones: [PARIS] }, + { + previews: [ + { + current: true, + formated: + 'Sunday, March 22, 2020
    12:00 AM → 1:30 AM', + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { duration: 1440, timezone: PARIS, timezones: [PARIS] }, + { + previews: [ + { + current: true, + formated: + "Sunday, March 22, 2020 12:00 AM → Monday, March 23, 2020 12:00 AM", + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-03-22", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { time: "11:34", timezone: PARIS, timezones: [PARIS] }, + { + previews: [ + { + current: true, + formated: + 'Sunday, March 22, 2020
    11:34 AM', + timezone: "Paris", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-04-06", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { + timezone: PARIS, + date: "2020-04-07", + timezones: [LONDON, LAGOS, SYDNEY], + }, + { + previews: [ + { + current: true, + formated: + "Tuesday, April 7, 2020 12:00 AM → Wednesday, April 8, 2020 12:00 AM", + timezone: "Paris", + }, + { + formated: + "Monday, April 6, 2020 11:00 PM → Tuesday, April 7, 2020 11:00 PM", + timezone: "London", + }, + { + formated: + "Monday, April 6, 2020 11:00 PM → Tuesday, April 7, 2020 11:00 PM", + timezone: "Lagos", + }, + { + formated: + "Tuesday, April 7, 2020 8:00 AM → Wednesday, April 8, 2020 8:00 AM", + timezone: "Sydney", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-04-06", timezone: PARIS }, () => { + assert.buildsCorrectDate( + { + timezone: PARIS, + date: "2020-04-07", + time: "14:54", + timezones: [LONDON, LAGOS, SYDNEY], + }, + { + previews: [ + { + current: true, + formated: + 'Tuesday, April 7, 2020
    2:54 PM', + timezone: "Paris", + }, + { + formated: + 'Tuesday, April 7, 2020
    1:54 PM', + timezone: "London", + }, + { + formated: + 'Tuesday, April 7, 2020
    1:54 PM', + timezone: "Lagos", + }, + { + formated: + 'Tuesday, April 7, 2020
    10:54 PM', + timezone: "Sydney", + }, + ], + } + ); + }); + + freezeTime({ date: "2020-05-12", timezone: LOS_ANGELES }, () => { + assert.buildsCorrectDate( + { + date: "2020-05-13", + time: "18:00", + localTimezone: LOS_ANGELES, + }, + { + previews: [ + { + current: true, + formated: + 'Wednesday, May 13, 2020
    11:00 AM', + timezone: "Los Angeles", + }, + { + formated: + 'Wednesday, May 13, 2020
    6:00 PM', + timezone: "UTC", + }, + ], + } + ); + }); }); }); From 1b4692039e89392731ca5f75cfc4c4febaa3fa9a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 18 Jun 2022 00:52:48 +0100 Subject: [PATCH 063/184] DEV: Remove legacy JS manifests and vendored scripts Now that we've switched to Ember CLI, these things are no longer used. - These sprockets manifests are superceded by the assets generated by ember cli - These vendored scripts are now fetched by ember-auto-import at compile time --- .eslintignore | 4 - .prettierignore | 4 - app/assets/javascripts/admin.js | 2 - app/assets/javascripts/app-boot.js | 27 - app/assets/javascripts/application.js | 104 - app/assets/javascripts/discourse-shims.js | 67 - .../discourse/tests/test-support-rails.js | 14 - .../tests/theme_qunit_ember_jquery.js | 6 - app/assets/javascripts/ember_include.js.erb | 7 - app/assets/javascripts/ember_jquery.js | 5 - app/assets/javascripts/env.js | 3 - app/assets/javascripts/main_include_admin.js | 2 - .../javascripts/set-prototype-polyfill.js | 8 - app/assets/javascripts/start-discourse.js | 5 - app/assets/javascripts/template_include.js | 2 - app/assets/javascripts/test-shims.js | 64 - app/assets/javascripts/vendor-common.js | 19 - app/assets/javascripts/vendor-theme-tests.js | 6 - app/assets/javascripts/vendor.js | 2 - app/assets/javascripts/widget-runtime.js | 11 - app/assets/javascripts/wizard.js | 2 - config/initializers/assets.rb | 12 - lib/tasks/javascript.rake | 56 - public/popper.js.map | 1 - public/route-recognizer.js.map | 1 - public/tippy.umd.js.map | 1 - .../assets/javascripts/Markdown.Converter.js | 49 - vendor/assets/javascripts/buffered-proxy.js | 169 - vendor/assets/javascripts/custom-uppy.js | 16 - vendor/assets/javascripts/ember-qunit.js | 1993 -- .../javascripts/fake_xml_http_request.js | 530 - .../assets/javascripts/handlebars.runtime.js | 1800 -- vendor/assets/javascripts/itsatrap.js | 1159 - vendor/assets/javascripts/jquery.js | 10872 ---------- vendor/assets/javascripts/popper.js | 1948 -- vendor/assets/javascripts/pretender.js | 1850 -- vendor/assets/javascripts/qunit.js | 6566 ------ vendor/assets/javascripts/route-recognizer.js | 693 - vendor/assets/javascripts/rsvp.js | 289 - vendor/assets/javascripts/sinon.js | 17447 ---------------- vendor/assets/javascripts/tippy.umd.js | 2496 --- vendor/assets/javascripts/uppy.js | 7616 ------- vendor/assets/javascripts/virtual-dom-amd.js | 4 - vendor/assets/javascripts/virtual-dom.js | 1668 -- 44 files changed, 57600 deletions(-) delete mode 100644 app/assets/javascripts/admin.js delete mode 100644 app/assets/javascripts/app-boot.js delete mode 100644 app/assets/javascripts/application.js delete mode 100644 app/assets/javascripts/discourse-shims.js delete mode 100644 app/assets/javascripts/discourse/tests/test-support-rails.js delete mode 100644 app/assets/javascripts/discourse/tests/theme_qunit_ember_jquery.js delete mode 100644 app/assets/javascripts/ember_include.js.erb delete mode 100644 app/assets/javascripts/ember_jquery.js delete mode 100644 app/assets/javascripts/env.js delete mode 100644 app/assets/javascripts/main_include_admin.js delete mode 100644 app/assets/javascripts/set-prototype-polyfill.js delete mode 100644 app/assets/javascripts/start-discourse.js delete mode 100644 app/assets/javascripts/template_include.js delete mode 100644 app/assets/javascripts/test-shims.js delete mode 100644 app/assets/javascripts/vendor-common.js delete mode 100644 app/assets/javascripts/vendor-theme-tests.js delete mode 100644 app/assets/javascripts/vendor.js delete mode 100644 app/assets/javascripts/widget-runtime.js delete mode 100644 app/assets/javascripts/wizard.js delete mode 100644 public/popper.js.map delete mode 100644 public/route-recognizer.js.map delete mode 100644 public/tippy.umd.js.map delete mode 100644 vendor/assets/javascripts/Markdown.Converter.js delete mode 100644 vendor/assets/javascripts/buffered-proxy.js delete mode 100644 vendor/assets/javascripts/custom-uppy.js delete mode 100644 vendor/assets/javascripts/ember-qunit.js delete mode 100644 vendor/assets/javascripts/fake_xml_http_request.js delete mode 100644 vendor/assets/javascripts/handlebars.runtime.js delete mode 100644 vendor/assets/javascripts/itsatrap.js delete mode 100644 vendor/assets/javascripts/jquery.js delete mode 100644 vendor/assets/javascripts/popper.js delete mode 100644 vendor/assets/javascripts/pretender.js delete mode 100644 vendor/assets/javascripts/qunit.js delete mode 100644 vendor/assets/javascripts/route-recognizer.js delete mode 100644 vendor/assets/javascripts/rsvp.js delete mode 100644 vendor/assets/javascripts/sinon.js delete mode 100644 vendor/assets/javascripts/tippy.umd.js delete mode 100644 vendor/assets/javascripts/uppy.js delete mode 100644 vendor/assets/javascripts/virtual-dom-amd.js delete mode 100644 vendor/assets/javascripts/virtual-dom.js diff --git a/.eslintignore b/.eslintignore index fbd1b3dd1b..fba2862983 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,5 @@ app/assets/javascripts/browser-update.js app/assets/javascripts/discourse-loader.js -app/assets/javascripts/env.js -app/assets/javascripts/main_include_admin.js -app/assets/javascripts/vendor.js app/assets/javascripts/locales/i18n.js app/assets/javascripts/ember-addons/ app/assets/javascripts/discourse/lib/autosize.js @@ -13,7 +10,6 @@ lib/pretty_text/ plugins/**/lib/javascripts/locale public/ vendor/ -app/assets/javascripts/discourse/tests/test-boot-rails.js app/assets/javascripts/discourse/tests/fixtures node_modules/ spec/ diff --git a/.prettierignore b/.prettierignore index cbcac8378f..40729f189a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,9 +10,6 @@ script/import_scripts/**/*.yml app/assets/javascripts/browser-update.js app/assets/javascripts/discourse-loader.js -app/assets/javascripts/env.js -app/assets/javascripts/main_include_admin.js -app/assets/javascripts/vendor.js app/assets/javascripts/locales/i18n.js app/assets/javascripts/ember-addons/ app/assets/javascripts/discourse/lib/autosize.js @@ -22,7 +19,6 @@ lib/highlight_js/ plugins/**/lib/javascripts/locale public/ vendor/ -app/assets/javascripts/discourse/tests/test-boot-rails.js app/assets/javascripts/discourse/tests/fixtures spec/ node_modules/ diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js deleted file mode 100644 index 3edc85a7d4..0000000000 --- a/app/assets/javascripts/admin.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require main_include_admin -//= require admin-plugins diff --git a/app/assets/javascripts/app-boot.js b/app/assets/javascripts/app-boot.js deleted file mode 100644 index 834d81cc31..0000000000 --- a/app/assets/javascripts/app-boot.js +++ /dev/null @@ -1,27 +0,0 @@ -// discourse-skip-module - -(function () { - if (window.unsupportedBrowser) { - throw "Unsupported browser detected"; - } - - let Discourse = requirejs("discourse/app").default.create(); - - // required for our template compiler - window.__DISCOURSE_RAW_TEMPLATES = requirejs( - "discourse-common/lib/raw-templates" - ).__DISCOURSE_RAW_TEMPLATES; - - // required for addons to work without Ember CLI - // eslint-disable-next-line no-undef - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - // eslint-disable-next-line no-undef - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - // ensure Discourse is added as a global - window.Discourse = Discourse; -})(); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js deleted file mode 100644 index dbaf4acf87..0000000000 --- a/app/assets/javascripts/application.js +++ /dev/null @@ -1,104 +0,0 @@ -//= require_tree ./truth-helpers/addon -//= require_tree ./discourse-common/addon -//= require ./polyfills -//= require_tree ./select-kit/addon -//= require ./discourse/app/app -//= require ./app-boot - -// Stuff we need to load first -//= require ./discourse/app/lib/to-markdown -//= require ./discourse/app/lib/utilities -//= require ./discourse/app/lib/user-presence -//= require ./discourse/app/lib/logout -//= require ./discourse/app/mixins/singleton -//= require ./discourse/app/models/rest -//= require ./discourse/app/models/session -//= require ./discourse/app/lib/ajax -//= require ./discourse/app/lib/text -//= require ./discourse/app/lib/hash -//= require ./discourse/app/lib/load-script -//= require ./discourse/app/lib/notification-levels -//= require ./discourse/app/services/app-events -//= require ./discourse/app/lib/offset-calculator -//= require ./discourse/app/lib/lock-on -//= require ./discourse/app/lib/url -//= require ./discourse/app/lib/email-provider-default-settings -//= require ./discourse/app/lib/debounce -//= require ./discourse/app/lib/quote -//= require ./discourse/app/lib/key-value-store -//= require ./discourse/app/lib/computed -//= require ./discourse/app/lib/formatter -//= require ./discourse/app/lib/text-direction -//= require ./discourse/app/lib/eyeline -//= require ./discourse/app/lib/show-modal -//= require ./discourse/app/lib/download-calendar -//= require ./discourse/app/mixins/scrolling -//= require ./discourse/app/lib/ajax-error -//= require ./discourse/app/models/result-set -//= require ./discourse/app/models/store -//= require ./discourse/app/models/action-summary -//= require ./discourse/app/models/permission-type -//= require ./discourse/app/models/category -//= require ./discourse/app/models/topic -//= require ./discourse/app/models/draft -//= require ./discourse/app/models/composer -//= require ./discourse/app/models/badge-grouping -//= require ./discourse/app/models/badge -//= require ./discourse/app/models/permission-type -//= require ./discourse/app/models/user-action-group -//= require ./discourse/app/models/trust-level -//= require ./discourse/app/lib/search -//= require ./discourse/app/lib/user-search -//= require ./discourse/app/lib/export-csv -//= require ./discourse/app/lib/autocomplete -//= require ./discourse/app/lib/after-transition -//= require ./discourse/app/lib/safari-hacks -//= require ./discourse/app/lib/put-cursor-at-end -//= require_tree ./discourse/app/adapters -//= require ./discourse/app/models/post-action-type -//= require ./discourse/app/models/post -//= require ./discourse/app/lib/posts-with-placeholders -//= require ./discourse/app/models/post-stream -//= require ./discourse/app/models/topic-details -//= require ./discourse/app/models/topic -//= require ./discourse/app/models/user-action -//= require ./discourse/app/models/draft -//= require ./discourse/app/models/composer -//= require ./discourse/app/models/user-badge -//= require_tree ./discourse/app/lib -//= require_tree ./discourse/app/mixins -//= require ./discourse/app/models/invite -//= require ./discourse/app/controllers/discovery-sortable -//= require ./discourse/app/controllers/navigation/default -//= require ./discourse/app/components/edit-category-panel -//= require ./discourse/app/lib/link-mentions -//= require ./discourse/app/components/site-header -//= require ./discourse/app/components/d-editor -//= require ./discourse/app/routes/discourse -//= require ./discourse/app/routes/build-topic-route -//= require ./discourse/app/routes/restricted-user -//= require ./discourse/app/routes/user-topic-list -//= require ./discourse/app/routes/user-activity-stream -//= require ./discourse/app/routes/topic-from-params -//= require ./discourse/app/components/text-field -//= require ./discourse/app/components/conditional-loading-spinner -//= require ./discourse/app/helpers/user-avatar -//= require ./discourse/app/helpers/cold-age-class -//= require ./discourse/app/helpers/loading-spinner -//= require ./discourse/app/helpers/category-link -//= require ./discourse/app/lib/export-result -//= require ./discourse/app/mapping-router - -//= require_tree ./discourse/app/controllers -//= require_tree ./discourse/app/models -//= require_tree ./discourse/app/components -//= require_tree ./discourse/app/raw-views -//= require_tree ./discourse/app/helpers -//= require_tree ./discourse/app/templates -//= require_tree ./discourse/app/routes -//= require_tree ./discourse/app/pre-initializers -//= require_tree ./discourse/app/initializers -//= require_tree ./discourse/app/services - -//= require_tree ./discourse/app/widgets -//= require ./widget-runtime diff --git a/app/assets/javascripts/discourse-shims.js b/app/assets/javascripts/discourse-shims.js deleted file mode 100644 index 7794b099b8..0000000000 --- a/app/assets/javascripts/discourse-shims.js +++ /dev/null @@ -1,67 +0,0 @@ -define("message-bus-client", ["exports"], function (__exports__) { - __exports__.default = window.MessageBus; -}); - -define("ember-buffered-proxy/proxy", ["exports"], function (__exports__) { - __exports__.default = window.BufferedProxy; -}); - -define("bootbox", ["exports"], function (__exports__) { - __exports__.default = window.bootbox; -}); - -define("xss", ["exports"], function (__exports__) { - __exports__.default = window.filterXSS; -}); - -define("@discourse/itsatrap", ["exports"], function (__exports__) { - __exports__.default = window.ItsATrap; -}); - -define("@popperjs/core", ["exports"], function (__exports__) { - __exports__.default = window.Popper; - __exports__.createPopper = window.Popper.createPopper; - __exports__.defaultModifiers = window.Popper.defaultModifiers; - __exports__.popperGenerator = window.Popper.popperGenerator; -}); - -define("tippy.js", ["exports"], function (__exports__) { - __exports__.default = window.tippy; -}); - -define("@uppy/core", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Core; - __exports__.BasePlugin = window.Uppy.Core.BasePlugin; -}); - -define("@uppy/aws-s3", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3; -}); - -define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3Multipart; -}); - -define("@uppy/xhr-upload", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.XHRUpload; -}); - -define("@uppy/drop-target", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.DropTarget; -}); - -define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.delay; -}); - -define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.EventTracker; -}); - -define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { - __exports__.AbortController = - window.Uppy.Utils.AbortControllerLib.AbortController; - __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; - __exports__.createAbortError = - window.Uppy.Utils.AbortControllerLib.createAbortError; -}); diff --git a/app/assets/javascripts/discourse/tests/test-support-rails.js b/app/assets/javascripts/discourse/tests/test-support-rails.js deleted file mode 100644 index 4f916572ef..0000000000 --- a/app/assets/javascripts/discourse/tests/test-support-rails.js +++ /dev/null @@ -1,14 +0,0 @@ -// discourse-skip-module - -//= require qunit -//= require ember-qunit -//= require fake_xml_http_request -//= require route-recognizer -//= require pretender -//= require sinon -//= require break_string -//= require test-shims -//= require jquery.magnific-popup.min.js -//= require handlebars -//= require ember-template-compiler -//= require markdown-it-bundle diff --git a/app/assets/javascripts/discourse/tests/theme_qunit_ember_jquery.js b/app/assets/javascripts/discourse/tests/theme_qunit_ember_jquery.js deleted file mode 100644 index caccb2d83d..0000000000 --- a/app/assets/javascripts/discourse/tests/theme_qunit_ember_jquery.js +++ /dev/null @@ -1,6 +0,0 @@ -// discourse-skip-module - -//= require env -//= require jquery.debug -//= require ember.debug -//= require discourse-loader diff --git a/app/assets/javascripts/ember_include.js.erb b/app/assets/javascripts/ember_include.js.erb deleted file mode 100644 index 9bb1c0c59a..0000000000 --- a/app/assets/javascripts/ember_include.js.erb +++ /dev/null @@ -1,7 +0,0 @@ -<% -if @force_ember_debug || Rails.env.development? || Rails.env.test? - require_asset ("ember.debug.js") -else - require_asset ("ember.prod.js") -end -%> diff --git a/app/assets/javascripts/ember_jquery.js b/app/assets/javascripts/ember_jquery.js deleted file mode 100644 index 51f31d5ed3..0000000000 --- a/app/assets/javascripts/ember_jquery.js +++ /dev/null @@ -1,5 +0,0 @@ -//= require set-prototype-polyfill -//= require env -//= require jquery -//= require ember_include -//= require discourse-loader diff --git a/app/assets/javascripts/env.js b/app/assets/javascripts/env.js deleted file mode 100644 index 8b897bbe23..0000000000 --- a/app/assets/javascripts/env.js +++ /dev/null @@ -1,3 +0,0 @@ -window.ENV = {}; -window.EmberENV = window.EmberENV || {}; -window.EmberENV.FORCE_JQUERY = true; diff --git a/app/assets/javascripts/main_include_admin.js b/app/assets/javascripts/main_include_admin.js deleted file mode 100644 index 18afb3ba11..0000000000 --- a/app/assets/javascripts/main_include_admin.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require discourse/app/lib/export-result -//= require_tree ./admin/addon diff --git a/app/assets/javascripts/set-prototype-polyfill.js b/app/assets/javascripts/set-prototype-polyfill.js deleted file mode 100644 index 355fa28ea5..0000000000 --- a/app/assets/javascripts/set-prototype-polyfill.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable */ -Object.setPrototypeOf = - Object.setPrototypeOf || - function (obj, proto) { - obj.__proto__ = proto; - return obj; - }; -/* eslint-enable */ diff --git a/app/assets/javascripts/start-discourse.js b/app/assets/javascripts/start-discourse.js deleted file mode 100644 index 41b452a88d..0000000000 --- a/app/assets/javascripts/start-discourse.js +++ /dev/null @@ -1,5 +0,0 @@ -// discourse-skip-module -(function () { - // eslint-disable-next-line no-undef - Discourse.start(); -})(); diff --git a/app/assets/javascripts/template_include.js b/app/assets/javascripts/template_include.js deleted file mode 100644 index e61120646e..0000000000 --- a/app/assets/javascripts/template_include.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require handlebars.runtime -//= require handlebars-shim diff --git a/app/assets/javascripts/test-shims.js b/app/assets/javascripts/test-shims.js deleted file mode 100644 index 2aaf663525..0000000000 --- a/app/assets/javascripts/test-shims.js +++ /dev/null @@ -1,64 +0,0 @@ -// discourse-skip-module - -define("sinon", () => { - return { default: window.sinon }; -}); -define("qunit", () => { - return { - default: window.QUnit, - test: window.QUnit.test, - skip: window.QUnit.skip, - module: window.QUnit.module, - }; -}); -define("ember-qunit", () => { - return { - moduleFor: window.moduleFor, - moduleForComponent: window.moduleForComponent, - }; -}); -define("htmlbars-inline-precompile", () => { - return { - default: function (str) { - // eslint-disable-next-line no-undef - return Ember.HTMLBars.compile(str[0]); - }, - }; -}); - -let _app; -define("@ember/test-helpers", () => { - let helpers = { - setApplication(app) { - _app = app; - }, - getApplication() { - return _app; - }, - async settled() { - // eslint-disable-next-line no-undef, discourse-ember/global-ember - Ember.run(() => {}); - }, - TestModuleForComponent: window.TestModuleForComponent, - }; - [ - "click", - "visit", - "currentURL", - "currentRouteName", - "fillIn", - "setResolver", - "triggerEvent", - ].forEach((attr) => { - helpers[attr] = function () { - return window[attr](...arguments); - }; - }); - helpers.triggerKeyEvent = function () { - return window.keyEvent(...arguments); - }; - return helpers; -}); -define("pretender", () => { - return { default: window.Pretender }; -}); diff --git a/app/assets/javascripts/vendor-common.js b/app/assets/javascripts/vendor-common.js deleted file mode 100644 index 9e1d806ec6..0000000000 --- a/app/assets/javascripts/vendor-common.js +++ /dev/null @@ -1,19 +0,0 @@ -//= require logster - -//= require template_include.js - -//= require message-bus -//= require Markdown.Converter.js -//= require bootbox.js -//= require popper.js -//= require tippy.umd.js -//= require bootstrap-modal.js -//= require caret_position -//= require itsatrap.js -//= require rsvp.js -//= require uppy.js -//= require buffered-proxy -//= require virtual-dom -//= require virtual-dom-amd -//= require discourse-shims -//= require pretty-text-bundle diff --git a/app/assets/javascripts/vendor-theme-tests.js b/app/assets/javascripts/vendor-theme-tests.js deleted file mode 100644 index 0c7aa8c136..0000000000 --- a/app/assets/javascripts/vendor-theme-tests.js +++ /dev/null @@ -1,6 +0,0 @@ -//= require set-prototype-polyfill -//= require env -//= require jquery -//= require ember.debug.js -//= require discourse-loader -//= require vendor-common diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js deleted file mode 100644 index 7ec26b1ca4..0000000000 --- a/app/assets/javascripts/vendor.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require ember_jquery -//= require vendor-common diff --git a/app/assets/javascripts/widget-runtime.js b/app/assets/javascripts/widget-runtime.js deleted file mode 100644 index 6ecb23c707..0000000000 --- a/app/assets/javascripts/widget-runtime.js +++ /dev/null @@ -1,11 +0,0 @@ -// discourse-skip-module - -(function (context) { - // register widget helpers for compiled `hbs` - context.__widget_helpers = { - avatar: require("discourse/widgets/post").avatarFor, - dateNode: require("discourse/helpers/node").dateNode, - iconNode: require("discourse-common/lib/icon-library").iconNode, - rawHtml: require("discourse/widgets/raw-html").default, - }; -})(this); diff --git a/app/assets/javascripts/wizard.js b/app/assets/javascripts/wizard.js deleted file mode 100644 index 7f52738184..0000000000 --- a/app/assets/javascripts/wizard.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require discourse/app/lib/export-result -//= require_tree ./wizard/addon diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 2f2aeff0ce..22bbcb067e 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -28,16 +28,13 @@ Rails.application.config.assets.precompile += %w{ browser-detect.js browser-update.js break_string.js - ember_jquery.js pretty-text-bundle.js markdown-it-bundle.js service-worker.js google-tag-manager.js google-universal-analytics-v3.js google-universal-analytics-v4.js - start-discourse.js print-page.js - omniauth-complete.js activate-account.js auto-redirect.js locales/i18n.js @@ -46,9 +43,7 @@ Rails.application.config.assets.precompile += %w{ confirm-new-email/bootstrap.js onpopstate-handler.js embed-application.js - discourse/tests/active-plugins.js admin-plugins.js - discourse/tests/test_starter.js } if EmberCli.enabled? @@ -60,13 +55,6 @@ if EmberCli.enabled? scripts/discourse-boot } Rails.application.config.assets.precompile += EmberCli::ASSETS.map { |name| name.sub('.js', '.map') } -else - Rails.application.config.assets.precompile += %w{ - application.js - discourse/tests/test-support-rails.js - discourse/tests/test-helpers-rails.js - vendor-theme-tests.js - } end # Precompile all available locales diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index b632a401b9..965ad637c7 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -96,12 +96,8 @@ def dependencies }, { source: '@highlightjs/cdn-assets/.', destination: 'highlightjs' - }, { - source: 'jquery/dist/jquery.js' }, { source: 'markdown-it/dist/markdown-it.js' - }, { - source: '@discourse/itsatrap/itsatrap.js' }, { source: 'moment/moment.js' }, { @@ -143,39 +139,6 @@ def dependencies destination: 'workbox', skip_versioning: true, public: true - }, { - source: '@popperjs/core/dist/umd/popper.js' - }, { - source: '@popperjs/core/dist/umd/popper.js.map', - public_root: true - }, { - source: 'tippy.js/dist/tippy.umd.js' - }, { - source: 'tippy.js/dist/tippy.umd.js.map', - public_root: true - }, { - source: 'tippy.js/dist/tippy.css', - destination: '../../../app/assets/stylesheets/vendor' - }, { - source: 'tippy.js/dist/svg-arrow.css', - destination: '../../../app/assets/stylesheets/vendor' - }, { - source: 'route-recognizer/dist/route-recognizer.js' - }, { - source: 'route-recognizer/dist/route-recognizer.js.map', - public_root: true - }, - { - source: 'qunit/qunit/qunit.js' - }, - { - source: 'pretender/dist/pretender.js' - }, - { - source: 'fake-xml-http-request/fake_xml_http_request.js' - }, - { - source: 'sinon/pkg/sinon.js' }, { source: 'squoosh/codecs/mozjpeg/enc/mozjpeg_enc.js', @@ -200,10 +163,6 @@ def dependencies destination: 'squoosh', public: true, skip_versioning: true - }, - { - source: 'custom-uppy-build.js', - destination: 'uppy.js' } ] end @@ -331,13 +290,6 @@ task 'javascript:update' => 'clean_up' do end end - # we need a custom build of uppy because we cannot import - # their modules easily, using browserify to do so - if src.include? "custom-uppy-build" - puts "Building custom uppy using browserify" - system("yarn run browserify #{vendor_js}/custom-uppy.js -o node_modules/custom-uppy-build.js") - end - unless File.exist?(dest) STDERR.puts "New dependency added: #{dest}" end @@ -347,14 +299,6 @@ task 'javascript:update' => 'clean_up' do else FileUtils.cp_r(src, dest) end - - # avoids noisy console warnings in dev environment for non-homepage paths - if dest.end_with? "popper.js" - absolute_sourcemap(dest) - end - if dest.end_with? "tippy.umd.js" - absolute_sourcemap(dest) - end end write_template("discourse/app/lib/public-js-versions.js", "update", <<~JS) diff --git a/public/popper.js.map b/public/popper.js.map deleted file mode 100644 index f560b118ee..0000000000 --- a/public/popper.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"popper.js","sources":["../../src/dom-utils/getBoundingClientRect.js","../../src/dom-utils/getWindow.js","../../src/dom-utils/getWindowScroll.js","../../src/dom-utils/instanceOf.js","../../src/dom-utils/getHTMLElementScroll.js","../../src/dom-utils/getNodeScroll.js","../../src/dom-utils/getNodeName.js","../../src/dom-utils/getDocumentElement.js","../../src/dom-utils/getWindowScrollBarX.js","../../src/dom-utils/getComputedStyle.js","../../src/dom-utils/isScrollParent.js","../../src/dom-utils/getCompositeRect.js","../../src/dom-utils/getLayoutRect.js","../../src/dom-utils/getParentNode.js","../../src/dom-utils/getScrollParent.js","../../src/dom-utils/listScrollParents.js","../../src/dom-utils/isTableElement.js","../../src/dom-utils/getOffsetParent.js","../../src/enums.js","../../src/utils/orderModifiers.js","../../src/utils/debounce.js","../../src/utils/format.js","../../src/utils/validateModifiers.js","../../src/utils/uniqueBy.js","../../src/utils/getBasePlacement.js","../../src/utils/mergeByName.js","../../src/dom-utils/getViewportRect.js","../../src/utils/math.js","../../src/dom-utils/getDocumentRect.js","../../src/dom-utils/contains.js","../../src/utils/rectToClientRect.js","../../src/dom-utils/getClippingRect.js","../../src/utils/getVariation.js","../../src/utils/getMainAxisFromPlacement.js","../../src/utils/computeOffsets.js","../../src/utils/getFreshSideObject.js","../../src/utils/mergePaddingObject.js","../../src/utils/expandToHashMap.js","../../src/utils/detectOverflow.js","../../src/createPopper.js","../../src/modifiers/eventListeners.js","../../src/modifiers/popperOffsets.js","../../src/modifiers/computeStyles.js","../../src/modifiers/applyStyles.js","../../src/modifiers/offset.js","../../src/utils/getOppositePlacement.js","../../src/utils/getOppositeVariationPlacement.js","../../src/utils/computeAutoPlacement.js","../../src/modifiers/flip.js","../../src/utils/getAltAxis.js","../../src/utils/within.js","../../src/modifiers/preventOverflow.js","../../src/modifiers/arrow.js","../../src/modifiers/hide.js","../../src/popper-lite.js","../../src/popper.js"],"sourcesContent":["// @flow\nimport type { ClientRectObject, VirtualElement } from '../types';\n// import { isHTMLElement } from './instanceOf';\n\nexport default function getBoundingClientRect(\n element: Element | VirtualElement,\n // eslint-disable-next-line unused-imports/no-unused-vars\n includeScale: boolean = false\n): ClientRectObject {\n const rect = element.getBoundingClientRect();\n let scaleX = 1;\n let scaleY = 1;\n\n // FIXME:\n // `offsetWidth` returns an integer while `getBoundingClientRect`\n // returns a float. This results in `scaleX` or `scaleY` being\n // non-1 when it should be for elements that aren't a full pixel in\n // width or height.\n // if (isHTMLElement(element) && includeScale) {\n // const offsetHeight = element.offsetHeight;\n // const offsetWidth = element.offsetWidth;\n\n // // Do not attempt to divide by 0, otherwise we get `Infinity` as scale\n // // Fallback to 1 in case both values are `0`\n // if (offsetWidth > 0) {\n // scaleX = rect.width / offsetWidth || 1;\n // }\n // if (offsetHeight > 0) {\n // scaleY = rect.height / offsetHeight || 1;\n // }\n // }\n\n return {\n width: rect.width / scaleX,\n height: rect.height / scaleY,\n top: rect.top / scaleY,\n right: rect.right / scaleX,\n bottom: rect.bottom / scaleY,\n left: rect.left / scaleX,\n x: rect.left / scaleX,\n y: rect.top / scaleY,\n };\n}\n","// @flow\nimport type { Window } from '../types';\ndeclare function getWindow(node: Node | Window): Window;\n\nexport default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n const ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}\n","// @flow\nimport getWindow from './getWindow';\nimport type { Window } from '../types';\n\nexport default function getWindowScroll(node: Node | Window) {\n const win = getWindow(node);\n const scrollLeft = win.pageXOffset;\n const scrollTop = win.pageYOffset;\n\n return {\n scrollLeft,\n scrollTop,\n };\n}\n","// @flow\nimport getWindow from './getWindow';\n\ndeclare function isElement(node: mixed): boolean %checks(node instanceof\n Element);\nfunction isElement(node) {\n const OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\ndeclare function isHTMLElement(node: mixed): boolean %checks(node instanceof\n HTMLElement);\nfunction isHTMLElement(node) {\n const OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\ndeclare function isShadowRoot(node: mixed): boolean %checks(node instanceof\n ShadowRoot);\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n const OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };\n","// @flow\n\nexport default function getHTMLElementScroll(element: HTMLElement) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop,\n };\n}\n","// @flow\nimport getWindowScroll from './getWindowScroll';\nimport getWindow from './getWindow';\nimport { isHTMLElement } from './instanceOf';\nimport getHTMLElementScroll from './getHTMLElementScroll';\nimport type { Window } from '../types';\n\nexport default function getNodeScroll(node: Node | Window) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}\n","// @flow\nimport type { Window } from '../types';\n\nexport default function getNodeName(element: ?Node | Window): ?string {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}\n","// @flow\nimport { isElement } from './instanceOf';\nimport type { Window } from '../types';\n\nexport default function getDocumentElement(\n element: Element | Window\n): HTMLElement {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return (\n (isElement(element)\n ? element.ownerDocument\n : // $FlowFixMe[prop-missing]\n element.document) || window.document\n ).documentElement;\n}\n","// @flow\nimport getBoundingClientRect from './getBoundingClientRect';\nimport getDocumentElement from './getDocumentElement';\nimport getWindowScroll from './getWindowScroll';\n\nexport default function getWindowScrollBarX(element: Element): number {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return (\n getBoundingClientRect(getDocumentElement(element)).left +\n getWindowScroll(element).scrollLeft\n );\n}\n","// @flow\nimport getWindow from './getWindow';\n\nexport default function getComputedStyle(\n element: Element\n): CSSStyleDeclaration {\n return getWindow(element).getComputedStyle(element);\n}\n","// @flow\nimport getComputedStyle from './getComputedStyle';\n\nexport default function isScrollParent(element: HTMLElement): boolean {\n // Firefox wants us to check `-x` and `-y` variations as well\n const { overflow, overflowX, overflowY } = getComputedStyle(element);\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}\n","// @flow\nimport type { Rect, VirtualElement, Window } from '../types';\nimport getBoundingClientRect from './getBoundingClientRect';\nimport getNodeScroll from './getNodeScroll';\nimport getNodeName from './getNodeName';\nimport { isHTMLElement } from './instanceOf';\nimport getWindowScrollBarX from './getWindowScrollBarX';\nimport getDocumentElement from './getDocumentElement';\nimport isScrollParent from './isScrollParent';\n\nfunction isElementScaled(element: HTMLElement) {\n const rect = element.getBoundingClientRect();\n const scaleX = rect.width / element.offsetWidth || 1;\n const scaleY = rect.height / element.offsetHeight || 1;\n\n return scaleX !== 1 || scaleY !== 1;\n}\n\n// Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\nexport default function getCompositeRect(\n elementOrVirtualElement: Element | VirtualElement,\n offsetParent: Element | Window,\n isFixed: boolean = false\n): Rect {\n const isOffsetParentAnElement = isHTMLElement(offsetParent);\n const offsetParentIsScaled =\n isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n const documentElement = getDocumentElement(offsetParent);\n const rect = getBoundingClientRect(\n elementOrVirtualElement,\n offsetParentIsScaled\n );\n\n let scroll = { scrollLeft: 0, scrollTop: 0 };\n let offsets = { x: 0, y: 0 };\n\n if (isOffsetParentAnElement || (!isOffsetParentAnElement && !isFixed)) {\n if (\n getNodeName(offsetParent) !== 'body' ||\n // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)\n ) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height,\n };\n}\n","// @flow\nimport type { Rect } from '../types';\nimport getBoundingClientRect from './getBoundingClientRect';\n\n// Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\nexport default function getLayoutRect(element: HTMLElement): Rect {\n const clientRect = getBoundingClientRect(element);\n\n // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n let width = element.offsetWidth;\n let height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width,\n height,\n };\n}\n","// @flow\nimport getNodeName from './getNodeName';\nimport getDocumentElement from './getDocumentElement';\nimport { isShadowRoot } from './instanceOf';\n\nexport default function getParentNode(element: Node | ShadowRoot): Node {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (\n // this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || // DOM Element detected\n (isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n );\n}\n","// @flow\nimport getParentNode from './getParentNode';\nimport isScrollParent from './isScrollParent';\nimport getNodeName from './getNodeName';\nimport { isHTMLElement } from './instanceOf';\n\nexport default function getScrollParent(node: Node): HTMLElement {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}\n","// @flow\nimport getScrollParent from './getScrollParent';\nimport getParentNode from './getParentNode';\nimport getWindow from './getWindow';\nimport type { Window, VisualViewport } from '../types';\nimport isScrollParent from './isScrollParent';\n\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\nexport default function listScrollParents(\n element: Node,\n list: Array = []\n): Array {\n const scrollParent = getScrollParent(element);\n const isBody = scrollParent === element.ownerDocument?.body;\n const win = getWindow(scrollParent);\n const target = isBody\n ? [win].concat(\n win.visualViewport || [],\n isScrollParent(scrollParent) ? scrollParent : []\n )\n : scrollParent;\n const updatedList = list.concat(target);\n\n return isBody\n ? updatedList\n : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}\n","// @flow\nimport getNodeName from './getNodeName';\n\nexport default function isTableElement(element: Element): boolean {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}\n","// @flow\nimport getWindow from './getWindow';\nimport getNodeName from './getNodeName';\nimport getComputedStyle from './getComputedStyle';\nimport { isHTMLElement } from './instanceOf';\nimport isTableElement from './isTableElement';\nimport getParentNode from './getParentNode';\n\nfunction getTrueOffsetParent(element: Element): ?Element {\n if (\n !isHTMLElement(element) ||\n // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed'\n ) {\n return null;\n }\n\n return element.offsetParent;\n}\n\n// `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\nfunction getContainingBlock(element: Element) {\n const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1;\n const isIE = navigator.userAgent.indexOf('Trident') !== -1;\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n const elementCss = getComputedStyle(element);\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n let currentNode = getParentNode(element);\n\n while (\n isHTMLElement(currentNode) &&\n ['html', 'body'].indexOf(getNodeName(currentNode)) < 0\n ) {\n const css = getComputedStyle(currentNode);\n\n // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n if (\n css.transform !== 'none' ||\n css.perspective !== 'none' ||\n css.contain === 'paint' ||\n ['transform', 'perspective'].indexOf(css.willChange) !== -1 ||\n (isFirefox && css.willChange === 'filter') ||\n (isFirefox && css.filter && css.filter !== 'none')\n ) {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n}\n\n// Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\nexport default function getOffsetParent(element: Element) {\n const window = getWindow(element);\n\n let offsetParent = getTrueOffsetParent(element);\n\n while (\n offsetParent &&\n isTableElement(offsetParent) &&\n getComputedStyle(offsetParent).position === 'static'\n ) {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (\n offsetParent &&\n (getNodeName(offsetParent) === 'html' ||\n (getNodeName(offsetParent) === 'body' &&\n getComputedStyle(offsetParent).position === 'static'))\n ) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}\n","// @flow\nexport const top: 'top' = 'top';\nexport const bottom: 'bottom' = 'bottom';\nexport const right: 'right' = 'right';\nexport const left: 'left' = 'left';\nexport const auto: 'auto' = 'auto';\nexport type BasePlacement =\n | typeof top\n | typeof bottom\n | typeof right\n | typeof left;\nexport const basePlacements: Array = [top, bottom, right, left];\n\nexport const start: 'start' = 'start';\nexport const end: 'end' = 'end';\nexport type Variation = typeof start | typeof end;\n\nexport const clippingParents: 'clippingParents' = 'clippingParents';\nexport const viewport: 'viewport' = 'viewport';\nexport type Boundary =\n | HTMLElement\n | Array\n | typeof clippingParents;\nexport type RootBoundary = typeof viewport | 'document';\n\nexport const popper: 'popper' = 'popper';\nexport const reference: 'reference' = 'reference';\nexport type Context = typeof popper | typeof reference;\n\nexport type VariationPlacement =\n | 'top-start'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-end'\n | 'right-start'\n | 'right-end'\n | 'left-start'\n | 'left-end';\nexport type AutoPlacement = 'auto' | 'auto-start' | 'auto-end';\nexport type ComputedPlacement = VariationPlacement | BasePlacement;\nexport type Placement = AutoPlacement | BasePlacement | VariationPlacement;\n\nexport const variationPlacements: Array = basePlacements.reduce(\n (acc: Array, placement: BasePlacement) =>\n acc.concat([(`${placement}-${start}`: any), (`${placement}-${end}`: any)]),\n []\n);\nexport const placements: Array = [...basePlacements, auto].reduce(\n (\n acc: Array,\n placement: BasePlacement | typeof auto\n ): Array =>\n acc.concat([\n placement,\n (`${placement}-${start}`: any),\n (`${placement}-${end}`: any),\n ]),\n []\n);\n\n// modifiers that need to read the DOM\nexport const beforeRead: 'beforeRead' = 'beforeRead';\nexport const read: 'read' = 'read';\nexport const afterRead: 'afterRead' = 'afterRead';\n// pure-logic modifiers\nexport const beforeMain: 'beforeMain' = 'beforeMain';\nexport const main: 'main' = 'main';\nexport const afterMain: 'afterMain' = 'afterMain';\n// modifier with the purpose to write to the DOM (or write into a framework state)\nexport const beforeWrite: 'beforeWrite' = 'beforeWrite';\nexport const write: 'write' = 'write';\nexport const afterWrite: 'afterWrite' = 'afterWrite';\nexport const modifierPhases: Array = [\n beforeRead,\n read,\n afterRead,\n beforeMain,\n main,\n afterMain,\n beforeWrite,\n write,\n afterWrite,\n];\n\nexport type ModifierPhases =\n | typeof beforeRead\n | typeof read\n | typeof afterRead\n | typeof beforeMain\n | typeof main\n | typeof afterMain\n | typeof beforeWrite\n | typeof write\n | typeof afterWrite;\n","// @flow\nimport type { Modifier } from '../types';\nimport { modifierPhases } from '../enums';\n\n// source: https://stackoverflow.com/questions/49875255\nfunction order(modifiers) {\n const map = new Map();\n const visited = new Set();\n const result = [];\n\n modifiers.forEach(modifier => {\n map.set(modifier.name, modifier);\n });\n\n // On visiting object, check for its dependencies and visit them recursively\n function sort(modifier: Modifier) {\n visited.add(modifier.name);\n\n const requires = [\n ...(modifier.requires || []),\n ...(modifier.requiresIfExists || []),\n ];\n\n requires.forEach(dep => {\n if (!visited.has(dep)) {\n const depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n\n result.push(modifier);\n }\n\n modifiers.forEach(modifier => {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n\n return result;\n}\n\nexport default function orderModifiers(\n modifiers: Array>\n): Array> {\n // order based on dependencies\n const orderedModifiers = order(modifiers);\n\n // order based on phase\n return modifierPhases.reduce((acc, phase) => {\n return acc.concat(\n orderedModifiers.filter(modifier => modifier.phase === phase)\n );\n }, []);\n}\n","// @flow\n\nexport default function debounce(fn: Function): () => Promise {\n let pending;\n return () => {\n if (!pending) {\n pending = new Promise(resolve => {\n Promise.resolve().then(() => {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}\n","// @flow\n\nexport default function format(str: string, ...args: Array) {\n return [...args].reduce((p, c) => p.replace(/%s/, c), str);\n}\n","// @flow\nimport format from './format';\nimport { modifierPhases } from '../enums';\n\nconst INVALID_MODIFIER_ERROR =\n 'Popper: modifier \"%s\" provided an invalid %s property, expected %s but got %s';\nconst MISSING_DEPENDENCY_ERROR =\n 'Popper: modifier \"%s\" requires \"%s\", but \"%s\" modifier is not available';\nconst VALID_PROPERTIES = [\n 'name',\n 'enabled',\n 'phase',\n 'fn',\n 'effect',\n 'requires',\n 'options',\n];\n\nexport default function validateModifiers(modifiers: Array): void {\n modifiers.forEach((modifier) => {\n [...Object.keys(modifier), ...VALID_PROPERTIES]\n // IE11-compatible replacement for `new Set(iterable)`\n .filter((value, index, self) => self.indexOf(value) === index)\n .forEach((key) => {\n switch (key) {\n case 'name':\n if (typeof modifier.name !== 'string') {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n String(modifier.name),\n '\"name\"',\n '\"string\"',\n `\"${String(modifier.name)}\"`\n )\n );\n }\n break;\n case 'enabled':\n if (typeof modifier.enabled !== 'boolean') {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"enabled\"',\n '\"boolean\"',\n `\"${String(modifier.enabled)}\"`\n )\n );\n }\n break;\n case 'phase':\n if (modifierPhases.indexOf(modifier.phase) < 0) {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"phase\"',\n `either ${modifierPhases.join(', ')}`,\n `\"${String(modifier.phase)}\"`\n )\n );\n }\n break;\n case 'fn':\n if (typeof modifier.fn !== 'function') {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"fn\"',\n '\"function\"',\n `\"${String(modifier.fn)}\"`\n )\n );\n }\n break;\n case 'effect':\n if (\n modifier.effect != null &&\n typeof modifier.effect !== 'function'\n ) {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"effect\"',\n '\"function\"',\n `\"${String(modifier.fn)}\"`\n )\n );\n }\n break;\n case 'requires':\n if (\n modifier.requires != null &&\n !Array.isArray(modifier.requires)\n ) {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"requires\"',\n '\"array\"',\n `\"${String(modifier.requires)}\"`\n )\n );\n }\n break;\n case 'requiresIfExists':\n if (!Array.isArray(modifier.requiresIfExists)) {\n console.error(\n format(\n INVALID_MODIFIER_ERROR,\n modifier.name,\n '\"requiresIfExists\"',\n '\"array\"',\n `\"${String(modifier.requiresIfExists)}\"`\n )\n );\n }\n break;\n case 'options':\n case 'data':\n break;\n default:\n console.error(\n `PopperJS: an invalid property has been provided to the \"${\n modifier.name\n }\" modifier, valid properties are ${VALID_PROPERTIES.map(\n (s) => `\"${s}\"`\n ).join(', ')}; but \"${key}\" was provided.`\n );\n }\n\n modifier.requires &&\n modifier.requires.forEach((requirement) => {\n if (modifiers.find((mod) => mod.name === requirement) == null) {\n console.error(\n format(\n MISSING_DEPENDENCY_ERROR,\n String(modifier.name),\n requirement,\n requirement\n )\n );\n }\n });\n });\n });\n}\n","// @flow\n\nexport default function uniqueBy(arr: Array, fn: T => any): Array {\n const identifiers = new Set();\n\n return arr.filter(item => {\n const identifier = fn(item);\n\n if (!identifiers.has(identifier)) {\n identifiers.add(identifier);\n return true;\n }\n });\n}\n","// @flow\nimport { type BasePlacement, type Placement, auto } from '../enums';\n\nexport default function getBasePlacement(\n placement: Placement | typeof auto\n): BasePlacement {\n return (placement.split('-')[0]: any);\n}\n","// @flow\nimport type { Modifier } from '../types';\n\nexport default function mergeByName(\n modifiers: Array<$Shape>>\n): Array<$Shape>> {\n const merged = modifiers.reduce((merged, current) => {\n const existing = merged[current.name];\n merged[current.name] = existing\n ? {\n ...existing,\n ...current,\n options: { ...existing.options, ...current.options },\n data: { ...existing.data, ...current.data },\n }\n : current;\n return merged;\n }, {});\n\n // IE11 does not support Object.values\n return Object.keys(merged).map(key => merged[key]);\n}\n","// @flow\nimport getWindow from './getWindow';\nimport getDocumentElement from './getDocumentElement';\nimport getWindowScrollBarX from './getWindowScrollBarX';\n\nexport default function getViewportRect(element: Element) {\n const win = getWindow(element);\n const html = getDocumentElement(element);\n const visualViewport = win.visualViewport;\n\n let width = html.clientWidth;\n let height = html.clientHeight;\n let x = 0;\n let y = 0;\n\n // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper\n // can be obscured underneath it.\n // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even\n // if it isn't open, so if this isn't available, the popper will be detected\n // to overflow the bottom of the screen too early.\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n\n // Uses Layout Viewport (like Chrome; Safari does not currently)\n // In Chrome, it returns a value very close to 0 (+/-) but contains rounding\n // errors due to floating point numbers, so we need to check precision.\n // Safari returns a number <= 0, usually < -1 when pinch-zoomed\n\n // Feature detection fails in mobile emulation mode in Chrome.\n // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) <\n // 0.001\n // Fallback here: \"Not Safari\" userAgent\n if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width,\n height,\n x: x + getWindowScrollBarX(element),\n y,\n };\n}\n","// @flow\nexport const max = Math.max;\nexport const min = Math.min;\nexport const round = Math.round;\n","// @flow\nimport type { Rect } from '../types';\nimport getDocumentElement from './getDocumentElement';\nimport getComputedStyle from './getComputedStyle';\nimport getWindowScrollBarX from './getWindowScrollBarX';\nimport getWindowScroll from './getWindowScroll';\nimport { max } from '../utils/math';\n\n// Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\nexport default function getDocumentRect(element: HTMLElement): Rect {\n const html = getDocumentElement(element);\n const winScroll = getWindowScroll(element);\n const body = element.ownerDocument?.body;\n\n const width = max(\n html.scrollWidth,\n html.clientWidth,\n body ? body.scrollWidth : 0,\n body ? body.clientWidth : 0\n );\n const height = max(\n html.scrollHeight,\n html.clientHeight,\n body ? body.scrollHeight : 0,\n body ? body.clientHeight : 0\n );\n\n let x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n const y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return { width, height, x, y };\n}\n","// @flow\nimport { isShadowRoot } from './instanceOf';\n\nexport default function contains(parent: Element, child: Element) {\n const rootNode = child.getRootNode && child.getRootNode();\n\n // First, attempt with faster native method\n if (parent.contains(child)) {\n return true;\n }\n // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n let next = child;\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n }\n // $FlowFixMe[prop-missing]: need a better way to handle this...\n next = next.parentNode || next.host;\n } while (next);\n }\n\n // Give up, the result is false\n return false;\n}\n","// @flow\nimport type { Rect, ClientRectObject } from '../types';\n\nexport default function rectToClientRect(rect: Rect): ClientRectObject {\n return {\n ...rect,\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height,\n };\n}\n","// @flow\nimport type { ClientRectObject } from '../types';\nimport type { Boundary, RootBoundary } from '../enums';\nimport { viewport } from '../enums';\nimport getViewportRect from './getViewportRect';\nimport getDocumentRect from './getDocumentRect';\nimport listScrollParents from './listScrollParents';\nimport getOffsetParent from './getOffsetParent';\nimport getDocumentElement from './getDocumentElement';\nimport getComputedStyle from './getComputedStyle';\nimport { isElement, isHTMLElement } from './instanceOf';\nimport getBoundingClientRect from './getBoundingClientRect';\nimport getParentNode from './getParentNode';\nimport contains from './contains';\nimport getNodeName from './getNodeName';\nimport rectToClientRect from '../utils/rectToClientRect';\nimport { max, min } from '../utils/math';\n\nfunction getInnerBoundingClientRect(element: Element) {\n const rect = getBoundingClientRect(element);\n\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n\n return rect;\n}\n\nfunction getClientRectFromMixedType(\n element: Element,\n clippingParent: Element | RootBoundary\n): ClientRectObject {\n return clippingParent === viewport\n ? rectToClientRect(getViewportRect(element))\n : isHTMLElement(clippingParent)\n ? getInnerBoundingClientRect(clippingParent)\n : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n}\n\n// A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\nfunction getClippingParents(element: Element): Array {\n const clippingParents = listScrollParents(getParentNode(element));\n const canEscapeClipping =\n ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n const clipperElement =\n canEscapeClipping && isHTMLElement(element)\n ? getOffsetParent(element)\n : element;\n\n if (!isElement(clipperElement)) {\n return [];\n }\n\n // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n return clippingParents.filter(\n (clippingParent) =>\n isElement(clippingParent) &&\n contains(clippingParent, clipperElement) &&\n getNodeName(clippingParent) !== 'body'\n );\n}\n\n// Gets the maximum area that the element is visible in due to any number of\n// clipping parents\nexport default function getClippingRect(\n element: Element,\n boundary: Boundary,\n rootBoundary: RootBoundary\n): ClientRectObject {\n const mainClippingParents =\n boundary === 'clippingParents'\n ? getClippingParents(element)\n : [].concat(boundary);\n const clippingParents = [...mainClippingParents, rootBoundary];\n const firstClippingParent = clippingParents[0];\n\n const clippingRect = clippingParents.reduce((accRect, clippingParent) => {\n const rect = getClientRectFromMixedType(element, clippingParent);\n\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent));\n\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n\n return clippingRect;\n}\n","// @flow\nimport { type Variation, type Placement } from '../enums';\n\nexport default function getVariation(placement: Placement): ?Variation {\n return (placement.split('-')[1]: any);\n}\n","// @flow\nimport type { Placement } from '../enums';\n\nexport default function getMainAxisFromPlacement(\n placement: Placement\n): 'x' | 'y' {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}\n","// @flow\nimport getBasePlacement from './getBasePlacement';\nimport getVariation from './getVariation';\nimport getMainAxisFromPlacement from './getMainAxisFromPlacement';\nimport type {\n Rect,\n PositioningStrategy,\n Offsets,\n ClientRectObject,\n} from '../types';\nimport { top, right, bottom, left, start, end, type Placement } from '../enums';\n\nexport default function computeOffsets({\n reference,\n element,\n placement,\n}: {\n reference: Rect | ClientRectObject,\n element: Rect | ClientRectObject,\n strategy: PositioningStrategy,\n placement?: Placement,\n}): Offsets {\n const basePlacement = placement ? getBasePlacement(placement) : null;\n const variation = placement ? getVariation(placement) : null;\n const commonX = reference.x + reference.width / 2 - element.width / 2;\n const commonY = reference.y + reference.height / 2 - element.height / 2;\n\n let offsets;\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height,\n };\n break;\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height,\n };\n break;\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY,\n };\n break;\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY,\n };\n break;\n default:\n offsets = {\n x: reference.x,\n y: reference.y,\n };\n }\n\n const mainAxis = basePlacement\n ? getMainAxisFromPlacement(basePlacement)\n : null;\n\n if (mainAxis != null) {\n const len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] =\n offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n case end:\n offsets[mainAxis] =\n offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n default:\n }\n }\n\n return offsets;\n}\n","// @flow\nimport type { SideObject } from '../types';\n\nexport default function getFreshSideObject(): SideObject {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n };\n}\n","// @flow\nimport type { SideObject } from '../types';\nimport getFreshSideObject from './getFreshSideObject';\n\nexport default function mergePaddingObject(\n paddingObject: $Shape\n): SideObject {\n return {\n ...getFreshSideObject(),\n ...paddingObject,\n };\n}\n","// @flow\n\nexport default function expandToHashMap<\n T: number | string | boolean,\n K: string\n>(value: T, keys: Array): { [key: string]: T } {\n return keys.reduce((hashMap, key) => {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}\n","// @flow\nimport type { State, SideObject, Padding } from '../types';\nimport type { Placement, Boundary, RootBoundary, Context } from '../enums';\nimport getClippingRect from '../dom-utils/getClippingRect';\nimport getDocumentElement from '../dom-utils/getDocumentElement';\nimport getBoundingClientRect from '../dom-utils/getBoundingClientRect';\nimport computeOffsets from './computeOffsets';\nimport rectToClientRect from './rectToClientRect';\nimport {\n clippingParents,\n reference,\n popper,\n bottom,\n top,\n right,\n basePlacements,\n viewport,\n} from '../enums';\nimport { isElement } from '../dom-utils/instanceOf';\nimport mergePaddingObject from './mergePaddingObject';\nimport expandToHashMap from './expandToHashMap';\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n placement: Placement,\n boundary: Boundary,\n rootBoundary: RootBoundary,\n elementContext: Context,\n altBoundary: boolean,\n padding: Padding,\n};\n\nexport default function detectOverflow(\n state: State,\n options: $Shape = {}\n): SideObject {\n const {\n placement = state.placement,\n boundary = clippingParents,\n rootBoundary = viewport,\n elementContext = popper,\n altBoundary = false,\n padding = 0,\n } = options;\n\n const paddingObject = mergePaddingObject(\n typeof padding !== 'number'\n ? padding\n : expandToHashMap(padding, basePlacements)\n );\n\n const altContext = elementContext === popper ? reference : popper;\n\n const popperRect = state.rects.popper;\n const element = state.elements[altBoundary ? altContext : elementContext];\n\n const clippingClientRect = getClippingRect(\n isElement(element)\n ? element\n : element.contextElement || getDocumentElement(state.elements.popper),\n boundary,\n rootBoundary\n );\n\n const referenceClientRect = getBoundingClientRect(state.elements.reference);\n\n const popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement,\n });\n\n const popperClientRect = rectToClientRect({\n ...popperRect,\n ...popperOffsets,\n });\n\n const elementClientRect =\n elementContext === popper ? popperClientRect : referenceClientRect;\n\n // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n const overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom:\n elementClientRect.bottom -\n clippingClientRect.bottom +\n paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right:\n elementClientRect.right - clippingClientRect.right + paddingObject.right,\n };\n\n const offsetData = state.modifiersData.offset;\n\n // Offsets can be applied only to the popper element\n if (elementContext === popper && offsetData) {\n const offset = offsetData[placement];\n\n Object.keys(overflowOffsets).forEach((key) => {\n const multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n const axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}\n","// @flow\nimport type {\n State,\n OptionsGeneric,\n Modifier,\n Instance,\n VirtualElement,\n} from './types';\nimport getCompositeRect from './dom-utils/getCompositeRect';\nimport getLayoutRect from './dom-utils/getLayoutRect';\nimport listScrollParents from './dom-utils/listScrollParents';\nimport getOffsetParent from './dom-utils/getOffsetParent';\nimport getComputedStyle from './dom-utils/getComputedStyle';\nimport orderModifiers from './utils/orderModifiers';\nimport debounce from './utils/debounce';\nimport validateModifiers from './utils/validateModifiers';\nimport uniqueBy from './utils/uniqueBy';\nimport getBasePlacement from './utils/getBasePlacement';\nimport mergeByName from './utils/mergeByName';\nimport detectOverflow from './utils/detectOverflow';\nimport { isElement } from './dom-utils/instanceOf';\nimport { auto } from './enums';\n\nconst INVALID_ELEMENT_ERROR =\n 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';\nconst INFINITE_LOOP_ERROR =\n 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';\n\nconst DEFAULT_OPTIONS: OptionsGeneric = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute',\n};\n\ntype PopperGeneratorArgs = {\n defaultModifiers?: Array>,\n defaultOptions?: $Shape>,\n};\n\nfunction areValidElements(...args: Array): boolean {\n return !args.some(\n (element) =>\n !(element && typeof element.getBoundingClientRect === 'function')\n );\n}\n\nexport function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {\n const {\n defaultModifiers = [],\n defaultOptions = DEFAULT_OPTIONS,\n } = generatorOptions;\n\n return function createPopper>>(\n reference: Element | VirtualElement,\n popper: HTMLElement,\n options: $Shape> = defaultOptions\n ): Instance {\n let state: $Shape = {\n placement: 'bottom',\n orderedModifiers: [],\n options: { ...DEFAULT_OPTIONS, ...defaultOptions },\n modifiersData: {},\n elements: {\n reference,\n popper,\n },\n attributes: {},\n styles: {},\n };\n\n let effectCleanupFns: Array<() => void> = [];\n let isDestroyed = false;\n\n const instance = {\n state,\n setOptions(setOptionsAction) {\n const options =\n typeof setOptionsAction === 'function'\n ? setOptionsAction(state.options)\n : setOptionsAction;\n\n cleanupModifierEffects();\n\n state.options = {\n // $FlowFixMe[exponential-spread]\n ...defaultOptions,\n ...state.options,\n ...options,\n };\n\n state.scrollParents = {\n reference: isElement(reference)\n ? listScrollParents(reference)\n : reference.contextElement\n ? listScrollParents(reference.contextElement)\n : [],\n popper: listScrollParents(popper),\n };\n\n // Orders the modifiers based on their dependencies and `phase`\n // properties\n const orderedModifiers = orderModifiers(\n mergeByName([...defaultModifiers, ...state.options.modifiers])\n );\n\n // Strip out disabled modifiers\n state.orderedModifiers = orderedModifiers.filter((m) => m.enabled);\n\n // Validate the provided modifiers so that the consumer will get warned\n // if one of the modifiers is invalid for any reason\n if (__DEV__) {\n const modifiers = uniqueBy(\n [...orderedModifiers, ...state.options.modifiers],\n ({ name }) => name\n );\n\n validateModifiers(modifiers);\n\n if (getBasePlacement(state.options.placement) === auto) {\n const flipModifier = state.orderedModifiers.find(\n ({ name }) => name === 'flip'\n );\n\n if (!flipModifier) {\n console.error(\n [\n 'Popper: \"auto\" placements require the \"flip\" modifier be',\n 'present and enabled to work.',\n ].join(' ')\n );\n }\n }\n\n const {\n marginTop,\n marginRight,\n marginBottom,\n marginLeft,\n } = getComputedStyle(popper);\n\n // We no longer take into account `margins` on the popper, and it can\n // cause bugs with positioning, so we'll warn the consumer\n if (\n [marginTop, marginRight, marginBottom, marginLeft].some((margin) =>\n parseFloat(margin)\n )\n ) {\n console.warn(\n [\n 'Popper: CSS \"margin\" styles cannot be used to apply padding',\n 'between the popper and its reference element or boundary.',\n 'To replicate margin, use the `offset` modifier, as well as',\n 'the `padding` option in the `preventOverflow` and `flip`',\n 'modifiers.',\n ].join(' ')\n );\n }\n }\n\n runModifierEffects();\n\n return instance.update();\n },\n\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n const { reference, popper } = state.elements;\n\n // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n if (!areValidElements(reference, popper)) {\n if (__DEV__) {\n console.error(INVALID_ELEMENT_ERROR);\n }\n return;\n }\n\n // Store the reference and popper rects to be read by modifiers\n state.rects = {\n reference: getCompositeRect(\n reference,\n getOffsetParent(popper),\n state.options.strategy === 'fixed'\n ),\n popper: getLayoutRect(popper),\n };\n\n // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n state.reset = false;\n\n state.placement = state.options.placement;\n\n // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n state.orderedModifiers.forEach(\n (modifier) =>\n (state.modifiersData[modifier.name] = {\n ...modifier.data,\n })\n );\n\n let __debug_loops__ = 0;\n for (let index = 0; index < state.orderedModifiers.length; index++) {\n if (__DEV__) {\n __debug_loops__ += 1;\n if (__debug_loops__ > 100) {\n console.error(INFINITE_LOOP_ERROR);\n break;\n }\n }\n\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n const { fn, options = {}, name } = state.orderedModifiers[index];\n\n if (typeof fn === 'function') {\n state = fn({ state, options, name, instance }) || state;\n }\n }\n },\n\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce<$Shape>(\n () =>\n new Promise<$Shape>((resolve) => {\n instance.forceUpdate();\n resolve(state);\n })\n ),\n\n destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n },\n };\n\n if (!areValidElements(reference, popper)) {\n if (__DEV__) {\n console.error(INVALID_ELEMENT_ERROR);\n }\n return instance;\n }\n\n instance.setOptions(options).then((state) => {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n });\n\n // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n function runModifierEffects() {\n state.orderedModifiers.forEach(({ name, options = {}, effect }) => {\n if (typeof effect === 'function') {\n const cleanupFn = effect({ state, name, instance, options });\n const noopFn = () => {};\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach((fn) => fn());\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\n\nexport const createPopper = popperGenerator();\n\n// eslint-disable-next-line import/no-unused-modules\nexport { detectOverflow };\n","// @flow\nimport type { ModifierArguments, Modifier } from '../types';\nimport getWindow from '../dom-utils/getWindow';\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n scroll: boolean,\n resize: boolean,\n};\n\nconst passive = { passive: true };\n\nfunction effect({ state, instance, options }: ModifierArguments) {\n const { scroll = true, resize = true } = options;\n\n const window = getWindow(state.elements.popper);\n const scrollParents = [\n ...state.scrollParents.reference,\n ...state.scrollParents.popper,\n ];\n\n if (scroll) {\n scrollParents.forEach(scrollParent => {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return () => {\n if (scroll) {\n scrollParents.forEach(scrollParent => {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type EventListenersModifier = Modifier<'eventListeners', Options>;\nexport default ({\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: () => {},\n effect,\n data: {},\n}: EventListenersModifier);\n","// @flow\nimport type { ModifierArguments, Modifier } from '../types';\nimport computeOffsets from '../utils/computeOffsets';\n\nfunction popperOffsets({ state, name }: ModifierArguments<{||}>) {\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement,\n });\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type PopperOffsetsModifier = Modifier<'popperOffsets', {||}>;\nexport default ({\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {},\n}: PopperOffsetsModifier);\n","// @flow\nimport type {\n PositioningStrategy,\n Offsets,\n Modifier,\n ModifierArguments,\n Rect,\n Window,\n} from '../types';\nimport {\n type BasePlacement,\n type Variation,\n top,\n left,\n right,\n bottom,\n end,\n} from '../enums';\nimport getOffsetParent from '../dom-utils/getOffsetParent';\nimport getWindow from '../dom-utils/getWindow';\nimport getDocumentElement from '../dom-utils/getDocumentElement';\nimport getComputedStyle from '../dom-utils/getComputedStyle';\nimport getBasePlacement from '../utils/getBasePlacement';\nimport getVariation from '../utils/getVariation';\nimport { round } from '../utils/math';\n\n// eslint-disable-next-line import/no-unused-modules\nexport type RoundOffsets = (\n offsets: $Shape<{ x: number, y: number, centerOffset: number }>\n) => Offsets;\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n gpuAcceleration: boolean,\n adaptive: boolean,\n roundOffsets?: boolean | RoundOffsets,\n};\n\nconst unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto',\n};\n\n// Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\nfunction roundOffsetsByDPR({ x, y }): Offsets {\n const win: Window = window;\n const dpr = win.devicePixelRatio || 1;\n\n return {\n x: round(round(x * dpr) / dpr) || 0,\n y: round(round(y * dpr) / dpr) || 0,\n };\n}\n\nexport function mapToStyles({\n popper,\n popperRect,\n placement,\n variation,\n offsets,\n position,\n gpuAcceleration,\n adaptive,\n roundOffsets,\n}: {\n popper: HTMLElement,\n popperRect: Rect,\n placement: BasePlacement,\n variation: ?Variation,\n offsets: $Shape<{ x: number, y: number, centerOffset: number }>,\n position: PositioningStrategy,\n gpuAcceleration: boolean,\n adaptive: boolean,\n roundOffsets: boolean | RoundOffsets,\n}) {\n let { x = 0, y = 0 } =\n roundOffsets === true\n ? roundOffsetsByDPR(offsets)\n : typeof roundOffsets === 'function'\n ? roundOffsets(offsets)\n : offsets;\n\n const hasX = offsets.hasOwnProperty('x');\n const hasY = offsets.hasOwnProperty('y');\n\n let sideX: string = left;\n let sideY: string = top;\n\n const win: Window = window;\n\n if (adaptive) {\n let offsetParent = getOffsetParent(popper);\n let heightProp = 'clientHeight';\n let widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (\n getComputedStyle(offsetParent).position !== 'static' &&\n position === 'absolute'\n ) {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n }\n\n // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n offsetParent = (offsetParent: Element);\n\n if (\n placement === top ||\n ((placement === left || placement === right) && variation === end)\n ) {\n sideY = bottom;\n // $FlowFixMe[prop-missing]\n y -= offsetParent[heightProp] - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (\n placement === left ||\n ((placement === top || placement === bottom) && variation === end)\n ) {\n sideX = right;\n // $FlowFixMe[prop-missing]\n x -= offsetParent[widthProp] - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n const commonStyles = {\n position,\n ...(adaptive && unsetSides),\n };\n\n if (gpuAcceleration) {\n return {\n ...commonStyles,\n [sideY]: hasY ? '0' : '',\n [sideX]: hasX ? '0' : '',\n // Layer acceleration can disable subpixel rendering which causes slightly\n // blurry text on low PPI displays, so we want to use 2D transforms\n // instead\n transform:\n (win.devicePixelRatio || 1) <= 1\n ? `translate(${x}px, ${y}px)`\n : `translate3d(${x}px, ${y}px, 0)`,\n };\n }\n\n return {\n ...commonStyles,\n [sideY]: hasY ? `${y}px` : '',\n [sideX]: hasX ? `${x}px` : '',\n transform: '',\n };\n}\n\nfunction computeStyles({ state, options }: ModifierArguments) {\n const {\n gpuAcceleration = true,\n adaptive = true,\n // defaults to use builtin `roundOffsetsByDPR`\n roundOffsets = true,\n } = options;\n\n if (__DEV__) {\n const transitionProperty =\n getComputedStyle(state.elements.popper).transitionProperty || '';\n\n if (\n adaptive &&\n ['transform', 'top', 'right', 'bottom', 'left'].some(\n (property) => transitionProperty.indexOf(property) >= 0\n )\n ) {\n console.warn(\n [\n 'Popper: Detected CSS transitions on at least one of the following',\n 'CSS properties: \"transform\", \"top\", \"right\", \"bottom\", \"left\".',\n '\\n\\n',\n 'Disable the \"computeStyles\" modifier\\'s `adaptive` option to allow',\n 'for smooth transitions, or remove these properties from the CSS',\n 'transition declaration on the popper element if only transitioning',\n 'opacity or background-color for example.',\n '\\n\\n',\n 'We recommend using the popper element as a wrapper around an inner',\n 'element that can have any CSS property transitioned for animations.',\n ].join(' ')\n );\n }\n }\n\n const commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration,\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = {\n ...state.styles.popper,\n ...mapToStyles({\n ...commonStyles,\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive,\n roundOffsets,\n }),\n };\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = {\n ...state.styles.arrow,\n ...mapToStyles({\n ...commonStyles,\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets,\n }),\n };\n }\n\n state.attributes.popper = {\n ...state.attributes.popper,\n 'data-popper-placement': state.placement,\n };\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type ComputeStylesModifier = Modifier<'computeStyles', Options>;\nexport default ({\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {},\n}: ComputeStylesModifier);\n","// @flow\nimport type { Modifier, ModifierArguments } from '../types';\nimport getNodeName from '../dom-utils/getNodeName';\nimport { isHTMLElement } from '../dom-utils/instanceOf';\n\n// This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles({ state }: ModifierArguments<{||}>) {\n Object.keys(state.elements).forEach((name) => {\n const style = state.styles[name] || {};\n\n const attributes = state.attributes[name] || {};\n const element = state.elements[name];\n\n // arrow is optional + virtual elements\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n Object.assign(element.style, style);\n\n Object.keys(attributes).forEach((name) => {\n const value = attributes[name];\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect({ state }: ModifierArguments<{||}>) {\n const initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0',\n },\n arrow: {\n position: 'absolute',\n },\n reference: {},\n };\n\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return () => {\n Object.keys(state.elements).forEach((name) => {\n const element = state.elements[name];\n const attributes = state.attributes[name] || {};\n\n const styleProperties = Object.keys(\n state.styles.hasOwnProperty(name)\n ? state.styles[name]\n : initialStyles[name]\n );\n\n // Set all values to an empty string to unset them\n const style = styleProperties.reduce((style, property) => {\n style[property] = '';\n return style;\n }, {});\n\n // arrow is optional + virtual elements\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n\n Object.keys(attributes).forEach((attribute) => {\n element.removeAttribute(attribute);\n });\n });\n };\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type ApplyStylesModifier = Modifier<'applyStyles', {||}>;\nexport default ({\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect,\n requires: ['computeStyles'],\n}: ApplyStylesModifier);\n","// @flow\nimport type { Placement } from '../enums';\nimport type { ModifierArguments, Modifier, Rect, Offsets } from '../types';\nimport getBasePlacement from '../utils/getBasePlacement';\nimport { top, left, right, placements } from '../enums';\n\ntype OffsetsFunction = ({\n popper: Rect,\n reference: Rect,\n placement: Placement,\n}) => [?number, ?number];\n\ntype Offset = OffsetsFunction | [?number, ?number];\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n offset: Offset,\n};\n\nexport function distanceAndSkiddingToXY(\n placement: Placement,\n rects: { popper: Rect, reference: Rect },\n offset: Offset\n): Offsets {\n const basePlacement = getBasePlacement(placement);\n const invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n let [skidding, distance] =\n typeof offset === 'function'\n ? offset({\n ...rects,\n placement,\n })\n : offset;\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n\n return [left, right].indexOf(basePlacement) >= 0\n ? { x: distance, y: skidding }\n : { x: skidding, y: distance };\n}\n\nfunction offset({ state, options, name }: ModifierArguments) {\n const { offset = [0, 0] } = options;\n\n const data = placements.reduce((acc, placement) => {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n\n const { x, y } = data[state.placement];\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type OffsetModifier = Modifier<'offset', Options>;\nexport default ({\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset,\n}: OffsetModifier);\n","// @flow\nimport type { Placement } from '../enums';\n\nconst hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };\n\nexport default function getOppositePlacement(placement: Placement): Placement {\n return (placement.replace(\n /left|right|bottom|top/g,\n matched => hash[matched]\n ): any);\n}\n","// @flow\nimport type { Placement } from '../enums';\n\nconst hash = { start: 'end', end: 'start' };\n\nexport default function getOppositeVariationPlacement(\n placement: Placement\n): Placement {\n return (placement.replace(/start|end/g, matched => hash[matched]): any);\n}\n","// @flow\nimport type { State, Padding } from '../types';\nimport type {\n Placement,\n ComputedPlacement,\n Boundary,\n RootBoundary,\n} from '../enums';\nimport getVariation from './getVariation';\nimport {\n variationPlacements,\n basePlacements,\n placements as allPlacements,\n} from '../enums';\nimport detectOverflow from './detectOverflow';\nimport getBasePlacement from './getBasePlacement';\n\ntype Options = {\n placement: Placement,\n padding: Padding,\n boundary: Boundary,\n rootBoundary: RootBoundary,\n flipVariations: boolean,\n allowedAutoPlacements?: Array,\n};\n\ntype OverflowsMap = { [ComputedPlacement]: number };\n\nexport default function computeAutoPlacement(\n state: $Shape,\n options: Options = {}\n): Array {\n const {\n placement,\n boundary,\n rootBoundary,\n padding,\n flipVariations,\n allowedAutoPlacements = allPlacements,\n } = options;\n\n const variation = getVariation(placement);\n\n const placements = variation\n ? flipVariations\n ? variationPlacements\n : variationPlacements.filter(\n (placement) => getVariation(placement) === variation\n )\n : basePlacements;\n\n let allowedPlacements = placements.filter(\n (placement) => allowedAutoPlacements.indexOf(placement) >= 0\n );\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n\n if (__DEV__) {\n console.error(\n [\n 'Popper: The `allowedAutoPlacements` option did not allow any',\n 'placements. Ensure the `placement` option matches the variation',\n 'of the allowed placements.',\n 'For example, \"auto\" cannot be used to allow \"bottom-start\".',\n 'Use \"auto-start\" instead.',\n ].join(' ')\n );\n }\n }\n\n // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n const overflows: OverflowsMap = allowedPlacements.reduce((acc, placement) => {\n acc[placement] = detectOverflow(state, {\n placement,\n boundary,\n rootBoundary,\n padding,\n })[getBasePlacement(placement)];\n\n return acc;\n }, {});\n\n return Object.keys(overflows).sort((a, b) => overflows[a] - overflows[b]);\n}\n","// @flow\nimport type { Placement, Boundary, RootBoundary } from '../enums';\nimport type { ModifierArguments, Modifier, Padding } from '../types';\nimport getOppositePlacement from '../utils/getOppositePlacement';\nimport getBasePlacement from '../utils/getBasePlacement';\nimport getOppositeVariationPlacement from '../utils/getOppositeVariationPlacement';\nimport detectOverflow from '../utils/detectOverflow';\nimport computeAutoPlacement from '../utils/computeAutoPlacement';\nimport { bottom, top, start, right, left, auto } from '../enums';\nimport getVariation from '../utils/getVariation';\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n mainAxis: boolean,\n altAxis: boolean,\n fallbackPlacements: Array,\n padding: Padding,\n boundary: Boundary,\n rootBoundary: RootBoundary,\n altBoundary: boolean,\n flipVariations: boolean,\n allowedAutoPlacements: Array,\n};\n\nfunction getExpandedFallbackPlacements(placement: Placement): Array {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n const oppositePlacement = getOppositePlacement(placement);\n\n return [\n getOppositeVariationPlacement(placement),\n oppositePlacement,\n getOppositeVariationPlacement(oppositePlacement),\n ];\n}\n\nfunction flip({ state, options, name }: ModifierArguments) {\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n const {\n mainAxis: checkMainAxis = true,\n altAxis: checkAltAxis = true,\n fallbackPlacements: specifiedFallbackPlacements,\n padding,\n boundary,\n rootBoundary,\n altBoundary,\n flipVariations = true,\n allowedAutoPlacements,\n } = options;\n\n const preferredPlacement = state.options.placement;\n const basePlacement = getBasePlacement(preferredPlacement);\n const isBasePlacement = basePlacement === preferredPlacement;\n\n const fallbackPlacements =\n specifiedFallbackPlacements ||\n (isBasePlacement || !flipVariations\n ? [getOppositePlacement(preferredPlacement)]\n : getExpandedFallbackPlacements(preferredPlacement));\n\n const placements = [preferredPlacement, ...fallbackPlacements].reduce(\n (acc, placement) => {\n return acc.concat(\n getBasePlacement(placement) === auto\n ? computeAutoPlacement(state, {\n placement,\n boundary,\n rootBoundary,\n padding,\n flipVariations,\n allowedAutoPlacements,\n })\n : placement\n );\n },\n []\n );\n\n const referenceRect = state.rects.reference;\n const popperRect = state.rects.popper;\n\n const checksMap = new Map();\n let makeFallbackChecks = true;\n let firstFittingPlacement = placements[0];\n\n for (let i = 0; i < placements.length; i++) {\n const placement = placements[i];\n const basePlacement = getBasePlacement(placement);\n const isStartVariation = getVariation(placement) === start;\n const isVertical = [top, bottom].indexOf(basePlacement) >= 0;\n const len = isVertical ? 'width' : 'height';\n\n const overflow = detectOverflow(state, {\n placement,\n boundary,\n rootBoundary,\n altBoundary,\n padding,\n });\n\n let mainVariationSide: any = isVertical\n ? isStartVariation\n ? right\n : left\n : isStartVariation\n ? bottom\n : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n const altVariationSide: any = getOppositePlacement(mainVariationSide);\n\n const checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(\n overflow[mainVariationSide] <= 0,\n overflow[altVariationSide] <= 0\n );\n }\n\n if (checks.every((check) => check)) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n const numberOfChecks = flipVariations ? 3 : 1;\n\n for (let i = numberOfChecks; i > 0; i--) {\n const fittingPlacement = placements.find((placement) => {\n const checks = checksMap.get(placement);\n if (checks) {\n return checks.slice(0, i).every((check) => check);\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n break;\n }\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type FlipModifier = Modifier<'flip', Options>;\nexport default ({\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: { _skip: false },\n}: FlipModifier);\n","// @flow\n\nexport default function getAltAxis(axis: 'x' | 'y'): 'x' | 'y' {\n return axis === 'x' ? 'y' : 'x';\n}\n","// @flow\nimport { max as mathMax, min as mathMin } from './math';\n\nexport default function within(\n min: number,\n value: number,\n max: number\n): number {\n return mathMax(min, mathMin(value, max));\n}\n","// @flow\nimport { top, left, right, bottom, start } from '../enums';\nimport type { Placement, Boundary, RootBoundary } from '../enums';\nimport type { Rect, ModifierArguments, Modifier, Padding } from '../types';\nimport getBasePlacement from '../utils/getBasePlacement';\nimport getMainAxisFromPlacement from '../utils/getMainAxisFromPlacement';\nimport getAltAxis from '../utils/getAltAxis';\nimport within from '../utils/within';\nimport getLayoutRect from '../dom-utils/getLayoutRect';\nimport getOffsetParent from '../dom-utils/getOffsetParent';\nimport detectOverflow from '../utils/detectOverflow';\nimport getVariation from '../utils/getVariation';\nimport getFreshSideObject from '../utils/getFreshSideObject';\nimport { max as mathMax, min as mathMin } from '../utils/math';\n\ntype TetherOffset =\n | (({\n popper: Rect,\n reference: Rect,\n placement: Placement,\n }) => number)\n | number;\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n /* Prevents boundaries overflow on the main axis */\n mainAxis: boolean,\n /* Prevents boundaries overflow on the alternate axis */\n altAxis: boolean,\n /* The area to check the popper is overflowing in */\n boundary: Boundary,\n /* If the popper is not overflowing the main area, fallback to this one */\n rootBoundary: RootBoundary,\n /* Use the reference's \"clippingParents\" boundary context */\n altBoundary: boolean,\n /**\n * Allows the popper to overflow from its boundaries to keep it near its\n * reference element\n */\n tether: boolean,\n /* Offsets when the `tether` option should activate */\n tetherOffset: TetherOffset,\n /* Sets a padding to the provided boundary */\n padding: Padding,\n};\n\nfunction preventOverflow({ state, options, name }: ModifierArguments) {\n const {\n mainAxis: checkMainAxis = true,\n altAxis: checkAltAxis = false,\n boundary,\n rootBoundary,\n altBoundary,\n padding,\n tether = true,\n tetherOffset = 0,\n } = options;\n\n const overflow = detectOverflow(state, {\n boundary,\n rootBoundary,\n padding,\n altBoundary,\n });\n const basePlacement = getBasePlacement(state.placement);\n const variation = getVariation(state.placement);\n const isBasePlacement = !variation;\n const mainAxis = getMainAxisFromPlacement(basePlacement);\n const altAxis = getAltAxis(mainAxis);\n const popperOffsets = state.modifiersData.popperOffsets;\n const referenceRect = state.rects.reference;\n const popperRect = state.rects.popper;\n const tetherOffsetValue =\n typeof tetherOffset === 'function'\n ? tetherOffset({\n ...state.rects,\n placement: state.placement,\n })\n : tetherOffset;\n\n const data = { x: 0, y: 0 };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis || checkAltAxis) {\n const mainSide = mainAxis === 'y' ? top : left;\n const altSide = mainAxis === 'y' ? bottom : right;\n const len = mainAxis === 'y' ? 'height' : 'width';\n const offset = popperOffsets[mainAxis];\n\n const min = popperOffsets[mainAxis] + overflow[mainSide];\n const max = popperOffsets[mainAxis] - overflow[altSide];\n\n const additive = tether ? -popperRect[len] / 2 : 0;\n\n const minLen = variation === start ? referenceRect[len] : popperRect[len];\n const maxLen = variation === start ? -popperRect[len] : -referenceRect[len];\n\n // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n const arrowElement = state.elements.arrow;\n const arrowRect =\n tether && arrowElement\n ? getLayoutRect(arrowElement)\n : { width: 0, height: 0 };\n const arrowPaddingObject = state.modifiersData['arrow#persistent']\n ? state.modifiersData['arrow#persistent'].padding\n : getFreshSideObject();\n const arrowPaddingMin = arrowPaddingObject[mainSide];\n const arrowPaddingMax = arrowPaddingObject[altSide];\n\n // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n const arrowLen = within(0, referenceRect[len], arrowRect[len]);\n\n const minOffset = isBasePlacement\n ? referenceRect[len] / 2 -\n additive -\n arrowLen -\n arrowPaddingMin -\n tetherOffsetValue\n : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue;\n const maxOffset = isBasePlacement\n ? -referenceRect[len] / 2 +\n additive +\n arrowLen +\n arrowPaddingMax +\n tetherOffsetValue\n : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue;\n\n const arrowOffsetParent =\n state.elements.arrow && getOffsetParent(state.elements.arrow);\n const clientOffset = arrowOffsetParent\n ? mainAxis === 'y'\n ? arrowOffsetParent.clientTop || 0\n : arrowOffsetParent.clientLeft || 0\n : 0;\n\n const offsetModifierValue = state.modifiersData.offset\n ? state.modifiersData.offset[state.placement][mainAxis]\n : 0;\n\n const tetherMin =\n popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset;\n const tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue;\n\n if (checkMainAxis) {\n const preventedOffset = within(\n tether ? mathMin(min, tetherMin) : min,\n offset,\n tether ? mathMax(max, tetherMax) : max\n );\n\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n const mainSide = mainAxis === 'x' ? top : left;\n const altSide = mainAxis === 'x' ? bottom : right;\n const offset = popperOffsets[altAxis];\n\n const min = offset + overflow[mainSide];\n const max = offset - overflow[altSide];\n\n const preventedOffset = within(\n tether ? mathMin(min, tetherMin) : min,\n offset,\n tether ? mathMax(max, tetherMax) : max\n );\n\n popperOffsets[altAxis] = preventedOffset;\n data[altAxis] = preventedOffset - offset;\n }\n }\n\n state.modifiersData[name] = data;\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type PreventOverflowModifier = Modifier<'preventOverflow', Options>;\nexport default ({\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset'],\n}: PreventOverflowModifier);\n","// @flow\nimport type { Modifier, ModifierArguments, Padding, Rect } from '../types';\nimport type { Placement } from '../enums';\nimport getBasePlacement from '../utils/getBasePlacement';\nimport getLayoutRect from '../dom-utils/getLayoutRect';\nimport contains from '../dom-utils/contains';\nimport getOffsetParent from '../dom-utils/getOffsetParent';\nimport getMainAxisFromPlacement from '../utils/getMainAxisFromPlacement';\nimport within from '../utils/within';\nimport mergePaddingObject from '../utils/mergePaddingObject';\nimport expandToHashMap from '../utils/expandToHashMap';\nimport { left, right, basePlacements, top, bottom } from '../enums';\nimport { isHTMLElement } from '../dom-utils/instanceOf';\n\n// eslint-disable-next-line import/no-unused-modules\nexport type Options = {\n element: HTMLElement | string | null,\n padding:\n | Padding\n | (({|\n popper: Rect,\n reference: Rect,\n placement: Placement,\n |}) => Padding),\n};\n\nconst toPaddingObject = (padding, state) => {\n padding =\n typeof padding === 'function'\n ? padding({ ...state.rects, placement: state.placement })\n : padding;\n\n return mergePaddingObject(\n typeof padding !== 'number'\n ? padding\n : expandToHashMap(padding, basePlacements)\n );\n};\n\nfunction arrow({ state, name, options }: ModifierArguments) {\n const arrowElement = state.elements.arrow;\n const popperOffsets = state.modifiersData.popperOffsets;\n const basePlacement = getBasePlacement(state.placement);\n const axis = getMainAxisFromPlacement(basePlacement);\n const isVertical = [left, right].indexOf(basePlacement) >= 0;\n const len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n const paddingObject = toPaddingObject(options.padding, state);\n const arrowRect = getLayoutRect(arrowElement);\n const minProp = axis === 'y' ? top : left;\n const maxProp = axis === 'y' ? bottom : right;\n\n const endDiff =\n state.rects.reference[len] +\n state.rects.reference[axis] -\n popperOffsets[axis] -\n state.rects.popper[len];\n const startDiff = popperOffsets[axis] - state.rects.reference[axis];\n\n const arrowOffsetParent = getOffsetParent(arrowElement);\n const clientSize = arrowOffsetParent\n ? axis === 'y'\n ? arrowOffsetParent.clientHeight || 0\n : arrowOffsetParent.clientWidth || 0\n : 0;\n\n const centerToReference = endDiff / 2 - startDiff / 2;\n\n // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n const min = paddingObject[minProp];\n const max = clientSize - arrowRect[len] - paddingObject[maxProp];\n const center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n const offset = within(min, center, max);\n\n // Prevents breaking syntax highlighting...\n const axisProp: string = axis;\n state.modifiersData[name] = {\n [axisProp]: offset,\n centerOffset: offset - center,\n };\n}\n\nfunction effect({ state, options }: ModifierArguments) {\n let { element: arrowElement = '[data-popper-arrow]' } = options;\n\n if (arrowElement == null) {\n return;\n }\n\n // CSS selector\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (__DEV__) {\n if (!isHTMLElement(arrowElement)) {\n console.error(\n [\n 'Popper: \"arrow\" element must be an HTMLElement (not an SVGElement).',\n 'To use an SVG arrow, wrap it in an HTMLElement that will be used as',\n 'the arrow.',\n ].join(' ')\n );\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n if (__DEV__) {\n console.error(\n [\n 'Popper: \"arrow\" modifier\\'s `element` must be a child of the popper',\n 'element.',\n ].join(' ')\n );\n }\n\n return;\n }\n\n state.elements.arrow = arrowElement;\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type ArrowModifier = Modifier<'arrow', Options>;\nexport default ({\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow'],\n}: ArrowModifier);\n","// @flow\nimport type {\n ModifierArguments,\n Modifier,\n Rect,\n SideObject,\n Offsets,\n} from '../types';\nimport { top, bottom, left, right } from '../enums';\nimport detectOverflow from '../utils/detectOverflow';\n\nfunction getSideOffsets(\n overflow: SideObject,\n rect: Rect,\n preventedOffsets: Offsets = { x: 0, y: 0 }\n): SideObject {\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x,\n };\n}\n\nfunction isAnySideFullyClipped(overflow: SideObject): boolean {\n return [top, right, bottom, left].some((side) => overflow[side] >= 0);\n}\n\nfunction hide({ state, name }: ModifierArguments<{||}>) {\n const referenceRect = state.rects.reference;\n const popperRect = state.rects.popper;\n const preventedOffsets = state.modifiersData.preventOverflow;\n\n const referenceOverflow = detectOverflow(state, {\n elementContext: 'reference',\n });\n const popperAltOverflow = detectOverflow(state, {\n altBoundary: true,\n });\n\n const referenceClippingOffsets = getSideOffsets(\n referenceOverflow,\n referenceRect\n );\n const popperEscapeOffsets = getSideOffsets(\n popperAltOverflow,\n popperRect,\n preventedOffsets\n );\n\n const isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n const hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n\n state.modifiersData[name] = {\n referenceClippingOffsets,\n popperEscapeOffsets,\n isReferenceHidden,\n hasPopperEscaped,\n };\n\n state.attributes.popper = {\n ...state.attributes.popper,\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped,\n };\n}\n\n// eslint-disable-next-line import/no-unused-modules\nexport type HideModifier = Modifier<'hide', {||}>;\nexport default ({\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide,\n}: HideModifier);\n","// @flow\nimport { popperGenerator, detectOverflow } from './createPopper';\n\nimport eventListeners from './modifiers/eventListeners';\nimport popperOffsets from './modifiers/popperOffsets';\nimport computeStyles from './modifiers/computeStyles';\nimport applyStyles from './modifiers/applyStyles';\n\nexport type * from './types';\n\nconst defaultModifiers = [\n eventListeners,\n popperOffsets,\n computeStyles,\n applyStyles,\n];\n\nconst createPopper = popperGenerator({ defaultModifiers });\n\n// eslint-disable-next-line import/no-unused-modules\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };\n","// @flow\nimport { popperGenerator, detectOverflow } from './createPopper';\n\nimport eventListeners from './modifiers/eventListeners';\nimport popperOffsets from './modifiers/popperOffsets';\nimport computeStyles from './modifiers/computeStyles';\nimport applyStyles from './modifiers/applyStyles';\nimport offset from './modifiers/offset';\nimport flip from './modifiers/flip';\nimport preventOverflow from './modifiers/preventOverflow';\nimport arrow from './modifiers/arrow';\nimport hide from './modifiers/hide';\n\nexport type * from './types';\n\nconst defaultModifiers = [\n eventListeners,\n popperOffsets,\n computeStyles,\n applyStyles,\n offset,\n flip,\n preventOverflow,\n arrow,\n hide,\n];\n\nconst createPopper = popperGenerator({ defaultModifiers });\n\n// eslint-disable-next-line import/no-unused-modules\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };\n// eslint-disable-next-line import/no-unused-modules\nexport { createPopper as createPopperLite } from './popper-lite';\n// eslint-disable-next-line import/no-unused-modules\nexport * from './modifiers';\n"],"names":["getBoundingClientRect","element","includeScale","rect","scaleX","scaleY","width","height","top","right","bottom","left","x","y","getWindow","node","window","toString","ownerDocument","defaultView","getWindowScroll","win","scrollLeft","pageXOffset","scrollTop","pageYOffset","isElement","OwnElement","Element","isHTMLElement","HTMLElement","isShadowRoot","ShadowRoot","getHTMLElementScroll","getNodeScroll","getNodeName","nodeName","toLowerCase","getDocumentElement","document","documentElement","getWindowScrollBarX","getComputedStyle","isScrollParent","overflow","overflowX","overflowY","test","isElementScaled","offsetWidth","offsetHeight","getCompositeRect","elementOrVirtualElement","offsetParent","isFixed","isOffsetParentAnElement","scroll","offsets","clientLeft","clientTop","getLayoutRect","clientRect","Math","abs","offsetLeft","offsetTop","getParentNode","assignedSlot","parentNode","host","getScrollParent","indexOf","body","listScrollParents","list","scrollParent","isBody","target","concat","visualViewport","updatedList","isTableElement","getTrueOffsetParent","position","getContainingBlock","isFirefox","navigator","userAgent","isIE","elementCss","currentNode","css","transform","perspective","contain","willChange","filter","getOffsetParent","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","order","modifiers","map","Map","visited","Set","result","forEach","modifier","set","name","sort","add","requires","requiresIfExists","dep","has","depModifier","get","push","orderModifiers","orderedModifiers","phase","debounce","fn","pending","Promise","resolve","then","undefined","format","str","args","p","c","replace","INVALID_MODIFIER_ERROR","MISSING_DEPENDENCY_ERROR","VALID_PROPERTIES","validateModifiers","Object","keys","value","index","self","key","console","error","String","enabled","join","effect","Array","isArray","s","requirement","find","mod","uniqueBy","arr","identifiers","item","identifier","getBasePlacement","split","mergeByName","merged","current","existing","options","data","getViewportRect","html","clientWidth","clientHeight","max","min","round","getDocumentRect","winScroll","scrollWidth","scrollHeight","direction","contains","parent","child","rootNode","getRootNode","next","isSameNode","rectToClientRect","getInnerBoundingClientRect","getClientRectFromMixedType","clippingParent","getClippingParents","canEscapeClipping","clipperElement","getClippingRect","boundary","rootBoundary","mainClippingParents","firstClippingParent","clippingRect","accRect","getVariation","getMainAxisFromPlacement","computeOffsets","basePlacement","variation","commonX","commonY","mainAxis","len","getFreshSideObject","mergePaddingObject","paddingObject","expandToHashMap","hashMap","detectOverflow","state","elementContext","altBoundary","padding","altContext","popperRect","rects","elements","clippingClientRect","contextElement","referenceClientRect","popperOffsets","strategy","popperClientRect","elementClientRect","overflowOffsets","offsetData","modifiersData","offset","multiply","axis","INVALID_ELEMENT_ERROR","INFINITE_LOOP_ERROR","DEFAULT_OPTIONS","areValidElements","some","popperGenerator","generatorOptions","defaultModifiers","defaultOptions","createPopper","attributes","styles","effectCleanupFns","isDestroyed","instance","setOptions","setOptionsAction","cleanupModifierEffects","scrollParents","m","flipModifier","marginTop","marginRight","marginBottom","marginLeft","margin","parseFloat","warn","runModifierEffects","update","forceUpdate","reset","__debug_loops__","length","destroy","onFirstUpdate","cleanupFn","noopFn","passive","resize","addEventListener","removeEventListener","unsetSides","roundOffsetsByDPR","dpr","devicePixelRatio","mapToStyles","gpuAcceleration","adaptive","roundOffsets","hasX","hasOwnProperty","hasY","sideX","sideY","heightProp","widthProp","commonStyles","computeStyles","transitionProperty","property","arrow","applyStyles","style","assign","removeAttribute","setAttribute","initialStyles","styleProperties","attribute","distanceAndSkiddingToXY","invertDistance","skidding","distance","hash","getOppositePlacement","matched","getOppositeVariationPlacement","computeAutoPlacement","flipVariations","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","a","b","getExpandedFallbackPlacements","oppositePlacement","flip","_skip","checkMainAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","preferredPlacement","isBasePlacement","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","numberOfChecks","fittingPlacement","slice","getAltAxis","within","mathMax","mathMin","preventOverflow","tether","tetherOffset","tetherOffsetValue","mainSide","altSide","additive","minLen","maxLen","arrowElement","arrowRect","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","arrowOffsetParent","clientOffset","offsetModifierValue","tetherMin","tetherMax","preventedOffset","toPaddingObject","minProp","maxProp","endDiff","startDiff","clientSize","centerToReference","center","axisProp","centerOffset","querySelector","getSideOffsets","preventedOffsets","isAnySideFullyClipped","side","hide","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","eventListeners"],"mappings":"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"} \ No newline at end of file diff --git a/public/route-recognizer.js.map b/public/route-recognizer.js.map deleted file mode 100644 index 1c846e3a4d..0000000000 --- a/public/route-recognizer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"route-recognizer.js","sources":["route-recognizer/util.ts","route-recognizer/dsl.ts","route-recognizer/normalizer.ts","route-recognizer.ts"],"sourcesContent":["const createObject = Object.create;\nexport function createMap() {\n const map: { [key: string]: T | undefined } = createObject(null);\n map[\"__\"] = undefined;\n delete map[\"__\"];\n return map;\n}\n","import { createMap } from \"./util\";\n\nexport interface Delegate {\n contextEntered?(context: string, route: MatchDSL): void;\n willAddRoute?(context: string | undefined, route: string): string;\n}\n\nexport type Opaque = {} | void | null | undefined;\n\nexport interface Route {\n path: string;\n handler: Opaque;\n queryParams?: string[];\n}\n\nexport interface RouteRecognizer {\n delegate: Delegate | undefined;\n add(routes: Route[]): void;\n}\n\nexport interface MatchCallback {\n (match: MatchDSL): void;\n}\n\nexport interface MatchDSL {\n (path: string): ToDSL;\n (path: string, callback: MatchCallback): void;\n}\n\nexport interface ToDSL {\n to(name: string, callback?: MatchCallback): void;\n}\n\nclass Target implements ToDSL {\n path: string;\n matcher: Matcher;\n delegate: Delegate | undefined;\n\n constructor(path: string, matcher: Matcher, delegate: Delegate | undefined) {\n this.path = path;\n this.matcher = matcher;\n this.delegate = delegate;\n }\n\n to(target: string, callback: MatchCallback) {\n let delegate = this.delegate;\n\n if (delegate && delegate.willAddRoute) {\n target = delegate.willAddRoute(this.matcher.target, target);\n }\n\n this.matcher.add(this.path, target);\n\n if (callback) {\n if (callback.length === 0) { throw new Error(\"You must have an argument in the function passed to `to`\"); }\n this.matcher.addChild(this.path, target, callback, this.delegate);\n }\n }\n}\n\nexport class Matcher {\n routes: {\n [path: string]: string | undefined;\n };\n children: {\n [path: string]: Matcher | undefined;\n };\n target: string | undefined;\n\n constructor(target?: string) {\n this.routes = createMap();\n this.children = createMap();\n this.target = target;\n }\n\n add(path: string, target: string) {\n this.routes[path] = target;\n }\n\n addChild(path: string, target: string, callback: MatchCallback, delegate: Delegate | undefined) {\n let matcher = new Matcher(target);\n this.children[path] = matcher;\n\n let match = generateMatch(path, matcher, delegate);\n\n if (delegate && delegate.contextEntered) {\n delegate.contextEntered(target, match);\n }\n\n callback(match);\n }\n}\n\nfunction generateMatch(startingPath: string, matcher: Matcher, delegate: Delegate | undefined): MatchDSL {\n function match(path: string): ToDSL;\n function match(path: string, callback: MatchCallback): void;\n function match(path: string, callback?: MatchCallback): ToDSL | void {\n let fullPath = startingPath + path;\n if (callback) {\n callback(generateMatch(fullPath, matcher, delegate));\n } else {\n return new Target(fullPath, matcher, delegate);\n }\n };\n return match;\n}\n\nfunction addRoute(routeArray: Route[], path: string, handler: any) {\n let len = 0;\n for (let i = 0; i < routeArray.length; i++) {\n len += routeArray[i].path.length;\n }\n\n path = path.substr(len);\n let route = { path: path, handler: handler };\n routeArray.push(route);\n}\n\nfunction eachRoute(baseRoute: Route[], matcher: Matcher, callback: (this: T, routes: Route[]) => void, binding: T) {\n let routes = matcher.routes;\n let paths = Object.keys(routes);\n for (let i = 0; i < paths.length; i++) {\n let path = paths[i];\n let routeArray = baseRoute.slice();\n addRoute(routeArray, path, routes[path]);\n let nested = matcher.children[path];\n if (nested) {\n eachRoute(routeArray, nested, callback, binding);\n } else {\n callback.call(binding, routeArray);\n }\n }\n}\n\nexport default function (this: T, callback: MatchCallback, addRouteCallback?: (routeRecognizer: T, routes: Route[]) => void) {\n let matcher = new Matcher();\n\n callback(generateMatch(\"\", matcher, this.delegate));\n\n eachRoute([], matcher, function(routes: Route[]) {\n if (addRouteCallback) { addRouteCallback(this, routes); }\n else { this.add(routes); }\n }, this);\n}\n","// Normalizes percent-encoded values in `path` to upper-case and decodes percent-encoded\n// values that are not reserved (i.e., unicode characters, emoji, etc). The reserved\n// chars are \"/\" and \"%\".\n// Safe to call multiple times on the same path.\nexport function normalizePath(path: string): string {\n return path.split(\"/\")\n .map(normalizeSegment)\n .join(\"/\");\n}\n\n// We want to ensure the characters \"%\" and \"/\" remain in percent-encoded\n// form when normalizing paths, so replace them with their encoded form after\n// decoding the rest of the path\nconst SEGMENT_RESERVED_CHARS = /%|\\//g;\nexport function normalizeSegment(segment: string) {\n if (segment.length < 3 || segment.indexOf(\"%\") === -1) return segment;\n return decodeURIComponent(segment).replace(SEGMENT_RESERVED_CHARS, encodeURIComponent);\n}\n\n// We do not want to encode these characters when generating dynamic path segments\n// See https://tools.ietf.org/html/rfc3986#section-3.3\n// sub-delims: \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\"\n// others allowed by RFC 3986: \":\", \"@\"\n//\n// First encode the entire path segment, then decode any of the encoded special chars.\n//\n// The chars \"!\", \"'\", \"(\", \")\", \"*\" do not get changed by `encodeURIComponent`,\n// so the possible encoded chars are:\n// ['%24', '%26', '%2B', '%2C', '%3B', '%3D', '%3A', '%40'].\nconst PATH_SEGMENT_ENCODINGS = /%(?:2(?:4|6|B|C)|3(?:B|D|A)|40)/g;\n\nexport function encodePathSegment(str: string) {\n return encodeURIComponent(str).replace(PATH_SEGMENT_ENCODINGS, decodeURIComponent);\n}\n","import { createMap } from \"./route-recognizer/util\";\nimport map, { Delegate, Route, Opaque, MatchCallback } from \"./route-recognizer/dsl\";\nimport { normalizePath, normalizeSegment, encodePathSegment } from \"./route-recognizer/normalizer\";\nexport { Delegate, MatchCallback } from './route-recognizer/dsl';\n\nconst enum CHARS {\n ANY = -1,\n STAR = 42,\n SLASH = 47,\n COLON = 58\n}\n\nconst escapeRegex = /(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\)/g;\n\nconst isArray = Array.isArray;\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction getParam(params: Params | null | undefined, key: string): string {\n if (typeof params !== \"object\" || params === null) {\n throw new Error(\"You must pass an object as the second argument to `generate`.\");\n }\n\n if (!hasOwnProperty.call(params, key)) {\n throw new Error(\"You must provide param `\" + key + \"` to `generate`.\");\n }\n\n let value = params[key];\n let str = typeof value === \"string\" ? value : \"\" + value;\n if (str.length === 0) {\n throw new Error(\"You must provide a param `\" + key + \"`.\");\n }\n return str;\n}\n\nconst enum SegmentType {\n Static = 0,\n Dynamic = 1,\n Star = 2,\n Epsilon = 4\n}\n\nconst enum SegmentFlags {\n Static = 2 << SegmentType.Static,\n Dynamic = 2 << SegmentType.Dynamic,\n Star = 2 << SegmentType.Star,\n Epsilon = 2 << SegmentType.Epsilon,\n Named = Dynamic | Star,\n Decoded = Dynamic,\n Counted = Static | Dynamic | Star\n}\n\nconst eachChar: ((segment: Segment, currentState: State) => State)[] = [];\neachChar[SegmentType.Static] = function (segment: Segment, currentState: State) {\n let state = currentState;\n let value = segment.value;\n for (let i = 0; i < value.length; i++) {\n let ch = value.charCodeAt(i);\n state = state.put(ch, false, false);\n }\n return state;\n};\neachChar[SegmentType.Dynamic] = function (_: Segment, currentState: State) {\n return currentState.put(CHARS.SLASH, true, true);\n};\neachChar[SegmentType.Star] = function (_: Segment, currentState: State) {\n return currentState.put(CHARS.ANY, false, true);\n};\neachChar[SegmentType.Epsilon] = function (_: Segment, currentState: State) {\n return currentState;\n};\n\nconst regex: ((segment: Segment) => string)[] = [];\nregex[SegmentType.Static] = function (segment: Segment) {\n return segment.value.replace(escapeRegex, \"\\\\$1\");\n};\nregex[SegmentType.Dynamic] = function () {\n return \"([^/]+)\";\n};\nregex[SegmentType.Star] = function () {\n return \"(.+)\";\n};\nregex[SegmentType.Epsilon] = function () {\n return \"\";\n};\n\nconst generate: ((segment: Segment, params?: Params | null) => string)[] = [];\ngenerate[SegmentType.Static] = function (segment: Segment) {\n return segment.value;\n};\ngenerate[SegmentType.Dynamic] = function (segment: Segment, params?: Params) {\n let value = getParam(params, segment.value);\n if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {\n return encodePathSegment(value);\n } else {\n return value;\n }\n};\ngenerate[SegmentType.Star] = function (segment: Segment, params?: Params) {\n return getParam(params, segment.value);\n};\ngenerate[SegmentType.Epsilon] = function () {\n return \"\";\n};\n\n// A Segment represents a segment in the original route description.\n// Each Segment type provides an `eachChar` and `regex` method.\n//\n// The `eachChar` method invokes the callback with one or more character\n// specifications. A character specification consumes one or more input\n// characters.\n//\n// The `regex` method returns a regex fragment for the segment. If the\n// segment is a dynamic of star segment, the regex fragment also includes\n// a capture.\n//\n// A character specification contains:\n//\n// * `validChars`: a String with a list of all valid characters, or\n// * `invalidChars`: a String with a list of all invalid characters\n// * `repeat`: true if the character specification can repeat\ninterface Segment {\n type: SegmentType;\n value: string;\n}\n\nexport interface Params {\n [key: string]: Opaque;\n [key: number]: Opaque;\n queryParams?: QueryParams | null;\n}\n\ninterface PopulatedParsedHandlers {\n names: string[];\n shouldDecodes: any[];\n}\n\nconst EmptyObject = Object.freeze({});\ntype EmptyObject = Readonly<{}>\n\nconst EmptyArray = Object.freeze([]) as ReadonlyArray;\ntype EmptyArray = ReadonlyArray;\n\ninterface EmptyParsedHandlers {\n names: EmptyArray;\n shouldDecodes: EmptyArray;\n}\n\ntype ParsedHandler = PopulatedParsedHandlers | EmptyParsedHandlers;\n\n// The `names` will be populated with the paramter name for each dynamic/star\n// segment. `shouldDecodes` will be populated with a boolean for each dyanamic/star\n// segment, indicating whether it should be decoded during recognition.\nfunction parse(segments: Segment[], route: string, types: [number, number, number]) {\n // normalize route as not starting with a \"/\". Recognition will\n // also normalize.\n if (route.length > 0 && route.charCodeAt(0) === CHARS.SLASH) { route = route.substr(1); }\n\n let parts = route.split(\"/\");\n let names: void | string[] = undefined;\n let shouldDecodes: void | any[] = undefined;\n\n for (let i = 0; i < parts.length; i++) {\n let part = parts[i];\n let flags: SegmentFlags = 0;\n let type: SegmentType = 0;\n\n if (part === \"\") {\n type = SegmentType.Epsilon;\n } else if (part.charCodeAt(0) === CHARS.COLON) {\n type = SegmentType.Dynamic;\n } else if (part.charCodeAt(0) === CHARS.STAR) {\n type = SegmentType.Star;\n } else {\n type = SegmentType.Static;\n }\n\n flags = 2 << type;\n\n if (flags & SegmentFlags.Named) {\n part = part.slice(1);\n names = names || [];\n names.push(part);\n\n shouldDecodes = shouldDecodes || [];\n shouldDecodes.push((flags & SegmentFlags.Decoded) !== 0);\n }\n\n if (flags & SegmentFlags.Counted) {\n types[type]++;\n }\n\n segments.push({\n type,\n value: normalizeSegment(part)\n });\n }\n\n return {\n names: names || EmptyArray,\n shouldDecodes: shouldDecodes || EmptyArray,\n } as ParsedHandler;\n}\n\nfunction isEqualCharSpec(spec: CharSpec, char: number, negate: boolean) {\n return spec.char === char && spec.negate === negate;\n}\n\ninterface EmptyHandler {\n handler: Opaque;\n names: EmptyArray;\n shouldDecodes: EmptyArray;\n}\n\ninterface PopulatedHandler {\n handler: Opaque;\n names: string [];\n shouldDecodes: boolean[];\n}\n\ntype Handler = EmptyHandler | PopulatedHandler;\n\n// A State has a character specification and (`charSpec`) and a list of possible\n// subsequent states (`nextStates`).\n//\n// If a State is an accepting state, it will also have several additional\n// properties:\n//\n// * `regex`: A regular expression that is used to extract parameters from paths\n// that reached this accepting state.\n// * `handlers`: Information on how to convert the list of captures into calls\n// to registered handlers with the specified parameters\n// * `types`: How many static, dynamic or star segments in this route. Used to\n// decide which route to use if multiple registered routes match a path.\n//\n// Currently, State is implemented naively by looping over `nextStates` and\n// comparing a character specification against a character. A more efficient\n// implementation would use a hash of keys pointing at one or more next states.\nclass State implements CharSpec {\n states: State[];\n id: number;\n negate: boolean;\n char: number;\n nextStates: number[] | number | null;\n pattern: string;\n _regex: RegExp | undefined;\n handlers: Handler[] | undefined;\n types: [number, number, number] | undefined;\n\n constructor(states: State[], id: number, char: number, negate: boolean, repeat: boolean) {\n this.states = states;\n this.id = id;\n this.char = char;\n this.negate = negate;\n this.nextStates = repeat ? id : null;\n this.pattern = \"\";\n this._regex = undefined;\n this.handlers = undefined;\n this.types = undefined;\n }\n\n regex(): RegExp {\n if (!this._regex) {\n this._regex = new RegExp(this.pattern);\n }\n return this._regex;\n }\n\n get(char: number, negate: boolean): State | void {\n let nextStates = this.nextStates;\n if (nextStates === null) return;\n if (isArray(nextStates)) {\n for (let i = 0; i < nextStates.length; i++) {\n let child = this.states[nextStates[i]];\n if (isEqualCharSpec(child, char, negate)) {\n return child;\n }\n }\n } else {\n let child = this.states[nextStates];\n if (isEqualCharSpec(child, char, negate)) {\n return child;\n }\n }\n }\n\n put(char: number, negate: boolean, repeat: boolean) {\n let state: State | void;\n\n // If the character specification already exists in a child of the current\n // state, just return that state.\n if (state = this.get(char, negate)) { return state; }\n\n // Make a new state for the character spec\n let states = this.states;\n state = new State(states, states.length, char, negate, repeat);\n states[states.length] = state;\n\n // Insert the new state as a child of the current state\n if (this.nextStates == null) {\n this.nextStates = state.id;\n } else if (isArray(this.nextStates)) {\n this.nextStates.push(state.id);\n } else {\n this.nextStates = [this.nextStates, state.id];\n }\n\n // Return the new state\n return state;\n }\n\n // Find a list of child states matching the next character\n match(ch: number): State[] {\n let nextStates = this.nextStates;\n if (!nextStates) return [];\n\n let returned: State[] = [];\n if (isArray(nextStates)) {\n for (let i = 0; i < nextStates.length; i++) {\n let child = this.states[nextStates[i]];\n\n if (isMatch(child, ch)) {\n returned.push(child);\n }\n }\n } else {\n let child = this.states[nextStates];\n if (isMatch(child, ch)) {\n returned.push(child);\n }\n }\n return returned;\n }\n}\n\nfunction isMatch(spec: CharSpec, char: number) {\n return spec.negate ? spec.char !== char && spec.char !== CHARS.ANY : spec.char === char || spec.char === CHARS.ANY;\n}\n\n// This is a somewhat naive strategy, but should work in a lot of cases\n// A better strategy would properly resolve /posts/:id/new and /posts/edit/:id.\n//\n// This strategy generally prefers more static and less dynamic matching.\n// Specifically, it\n//\n// * prefers fewer stars to more, then\n// * prefers using stars for less of the match to more, then\n// * prefers fewer dynamic segments to more, then\n// * prefers more static segments to more\nfunction sortSolutions(states: State[]) {\n return states.sort(function(a, b) {\n let [ astatics, adynamics, astars ] = a.types || [0, 0, 0];\n let [ bstatics, bdynamics, bstars ] = b.types || [0, 0, 0];\n if (astars !== bstars) { return astars - bstars; }\n\n if (astars) {\n if (astatics !== bstatics) { return bstatics - astatics; }\n if (adynamics !== bdynamics) { return bdynamics - adynamics; }\n }\n\n if (adynamics !== bdynamics) { return adynamics - bdynamics; }\n if (astatics !== bstatics) { return bstatics - astatics; }\n\n return 0;\n });\n}\n\nfunction recognizeChar(states: State[], ch: number) {\n let nextStates: State[] = [];\n\n for (let i = 0, l = states.length; i < l; i++) {\n let state = states[i];\n\n nextStates = nextStates.concat(state.match(ch));\n }\n\n return nextStates;\n}\n\n\nexport interface QueryParams {\n [param: string]: any[] | any | null | undefined;\n}\n\nexport interface Result {\n handler: Opaque;\n params: Params;\n isDynamic: boolean;\n}\n\nexport interface Results extends ArrayLike {\n queryParams: QueryParams;\n slice(start?: number, end?: number): Result[];\n splice(start: number, deleteCount: number, ...items: Result[]): Result[];\n push(...results: Result[]): number;\n}\n\nclass RecognizeResults implements Results {\n queryParams: QueryParams;\n length = 0;\n [index: number]: Result | undefined;\n splice: (start: number, deleteCount: number, ...items: Result[]) => Result[];\n slice: (start?: number, end?: number) => Result[];\n push: (...results: Result[]) => number;\n\n constructor(queryParams?: QueryParams) {\n this.queryParams = queryParams || {};\n }\n};\n\nRecognizeResults.prototype.splice = Array.prototype.splice;\nRecognizeResults.prototype.slice = Array.prototype.slice;\nRecognizeResults.prototype.push = Array.prototype.push;\n\nfunction findHandler(state: State, originalPath: string, queryParams: QueryParams): Results {\n let handlers = state.handlers;\n let regex: RegExp = state.regex();\n if (!regex || !handlers) throw new Error(\"state not initialized\");\n let captures: RegExpMatchArray | null = originalPath.match(regex);\n let currentCapture = 1;\n let result = new RecognizeResults(queryParams);\n\n result.length = handlers.length;\n\n for (let i = 0; i < handlers.length; i++) {\n let handler = handlers[i];\n let names = handler.names;\n let shouldDecodes = handler.shouldDecodes;\n let params: EmptyObject | Params = EmptyObject;\n\n let isDynamic = false;\n\n if (names !== EmptyArray && shouldDecodes !== EmptyArray) {\n for (let j = 0; j < names.length; j++) {\n isDynamic = true;\n let name = names[j];\n let capture = captures && captures[currentCapture++];\n\n if (params === EmptyObject) {\n params = {};\n }\n\n if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS && shouldDecodes[j]) {\n (params)[name] = capture && decodeURIComponent(capture);\n } else {\n (params)[name] = capture;\n }\n }\n }\n\n result[i] = {\n handler: handler.handler,\n params,\n isDynamic\n };\n }\n\n return result;\n}\n\nfunction decodeQueryParamPart(part: string): string {\n // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1\n part = part.replace(/\\+/gm, \"%20\");\n let result;\n try {\n result = decodeURIComponent(part);\n } catch (error) {result = \"\"; }\n return result;\n}\n\ninterface NamedRoute {\n segments: Segment[];\n handlers: Handler[];\n}\n\nclass RouteRecognizer {\n private states: State[];\n private rootState: State;\n private names: {\n [name: string]: NamedRoute | undefined;\n } = createMap();\n map: (context: MatchCallback, addCallback?: (router: this, routes: Route[]) => void) => void;\n delegate: Delegate | undefined;\n\n constructor() {\n let states: State[] = [];\n let state = new State(states, 0, CHARS.ANY, true, false);\n states[0] = state;\n this.states = states;\n this.rootState = state;\n }\n\n static VERSION = \"0.3.4\";\n // Set to false to opt-out of encoding and decoding path segments.\n // See https://github.com/tildeio/route-recognizer/pull/55\n static ENCODE_AND_DECODE_PATH_SEGMENTS = true;\n static Normalizer = {\n normalizeSegment, normalizePath, encodePathSegment\n };\n\n add(routes: Route[], options?: { as: string }) {\n let currentState = this.rootState;\n let pattern = \"^\";\n let types: [number, number, number] = [0, 0, 0];\n let handlers: Handler[] = new Array(routes.length);\n let allSegments: Segment[] = [];\n\n let isEmpty = true;\n let j = 0;\n for (let i = 0; i < routes.length; i++) {\n let route = routes[i];\n let { names, shouldDecodes } = parse(allSegments, route.path, types);\n\n // preserve j so it points to the start of newly added segments\n for (; j < allSegments.length; j++) {\n let segment = allSegments[j];\n\n if (segment.type === SegmentType.Epsilon) { continue; }\n\n isEmpty = false;\n\n // Add a \"/\" for the new segment\n currentState = currentState.put(CHARS.SLASH, false, false);\n pattern += \"/\";\n\n // Add a representation of the segment to the NFA and regex\n currentState = eachChar[segment.type](segment, currentState);\n pattern += regex[segment.type](segment);\n }\n handlers[i] = {\n handler: route.handler,\n names,\n shouldDecodes\n };\n }\n\n if (isEmpty) {\n currentState = currentState.put(CHARS.SLASH, false, false);\n pattern += \"/\";\n }\n\n currentState.handlers = handlers;\n currentState.pattern = pattern + \"$\";\n currentState.types = types;\n\n let name: string | undefined;\n if (typeof options === \"object\" && options !== null && options.as) {\n name = options.as;\n }\n\n if (name) {\n // if (this.names[name]) {\n // throw new Error(\"You may not add a duplicate route named `\" + name + \"`.\");\n // }\n\n this.names[name] = {\n segments: allSegments,\n handlers\n };\n }\n }\n\n handlersFor(name: string) {\n let route = this.names[name];\n\n if (!route) { throw new Error(\"There is no route named \" + name); }\n\n let result = new Array(route.handlers.length);\n\n for (let i = 0; i < route.handlers.length; i++) {\n let handler = route.handlers[i];\n result[i] = handler;\n }\n\n return result;\n }\n\n hasRoute(name: string) {\n return !!this.names[name];\n }\n\n generate(name: string, params?: Params | null) {\n let route = this.names[name];\n let output = \"\";\n if (!route) { throw new Error(\"There is no route named \" + name); }\n\n let segments: Segment[] = route.segments;\n\n for (let i = 0; i < segments.length; i++) {\n let segment: Segment = segments[i];\n\n if (segment.type === SegmentType.Epsilon) {\n continue;\n }\n\n output += \"/\";\n output += generate[segment.type](segment, params);\n }\n\n if (output.charAt(0) !== \"/\") { output = \"/\" + output; }\n\n if (params && params.queryParams) {\n output += this.generateQueryString(params.queryParams);\n }\n\n return output;\n }\n\n generateQueryString(params: QueryParams) {\n let pairs: string[] = [];\n let keys: string[] = Object.keys(params);\n keys.sort();\n for (let i = 0; i < keys.length; i++) {\n let key = keys[i];\n let value = params[key];\n if (value == null) {\n continue;\n }\n let pair = encodeURIComponent(key);\n if (isArray(value)) {\n for (let j = 0; j < value.length; j++) {\n let arrayPair = key + \"[]\" + \"=\" + encodeURIComponent(value[j]);\n pairs.push(arrayPair);\n }\n } else {\n pair += \"=\" + encodeURIComponent(value);\n pairs.push(pair);\n }\n }\n\n if (pairs.length === 0) { return \"\"; }\n\n return \"?\" + pairs.join(\"&\");\n }\n\n parseQueryString(queryString: string): QueryParams {\n let pairs = queryString.split(\"&\");\n let queryParams: QueryParams = {};\n for (let i = 0; i < pairs.length; i++) {\n let pair = pairs[i].split(\"=\"),\n key = decodeQueryParamPart(pair[0]),\n keyLength = key.length,\n isArray = false,\n value;\n if (pair.length === 1) {\n value = \"true\";\n } else {\n // Handle arrays\n if (keyLength > 2 && key.slice(keyLength - 2) === \"[]\") {\n isArray = true;\n key = key.slice(0, keyLength - 2);\n if (!queryParams[key]) {\n queryParams[key] = [];\n }\n }\n value = pair[1] ? decodeQueryParamPart(pair[1]) : \"\";\n }\n if (isArray) {\n (queryParams[key]).push(value);\n } else {\n queryParams[key] = value;\n }\n }\n return queryParams;\n }\n\n recognize(path: string): Results | undefined {\n let results: Results | undefined;\n let states: State[] = [ this.rootState ];\n let queryParams = {};\n let isSlashDropped = false;\n let hashStart = path.indexOf(\"#\");\n if (hashStart !== -1) {\n path = path.substr(0, hashStart);\n }\n\n let queryStart = path.indexOf(\"?\");\n if (queryStart !== -1) {\n let queryString = path.substr(queryStart + 1, path.length);\n path = path.substr(0, queryStart);\n queryParams = this.parseQueryString(queryString);\n }\n\n if (path.charAt(0) !== \"/\") { path = \"/\" + path; }\n let originalPath = path;\n\n if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {\n path = normalizePath(path);\n } else {\n path = decodeURI(path);\n originalPath = decodeURI(originalPath);\n }\n\n let pathLen = path.length;\n if (pathLen > 1 && path.charAt(pathLen - 1) === \"/\") {\n path = path.substr(0, pathLen - 1);\n originalPath = originalPath.substr(0, originalPath.length - 1);\n isSlashDropped = true;\n }\n\n for (let i = 0; i < path.length; i++) {\n states = recognizeChar(states, path.charCodeAt(i));\n if (!states.length) { break; }\n }\n\n let solutions: State[] = [];\n for (let i = 0; i < states.length; i++) {\n if (states[i].handlers) { solutions.push(states[i]); }\n }\n\n states = sortSolutions(solutions);\n\n let state = solutions[0];\n\n if (state && state.handlers) {\n // if a trailing slash was dropped and a star segment is the last segment\n // specified, put the trailing slash back\n if (isSlashDropped && state.pattern && state.pattern.slice(-5) === \"(.+)$\") {\n originalPath = originalPath + \"/\";\n }\n results = findHandler(state, originalPath, queryParams);\n }\n\n return results;\n }\n}\n\nRouteRecognizer.prototype.map = map;\n\nexport default RouteRecognizer;\n\ninterface CharSpec {\n negate: boolean;\n char: number;\n}\n"],"names":["const","let","this","child","i"],"mappings":"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"} \ No newline at end of file diff --git a/public/tippy.umd.js.map b/public/tippy.umd.js.map deleted file mode 100644 index 82b7f97066..0000000000 --- a/public/tippy.umd.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"tippy.umd.js","sources":["../src/constants.ts","../src/utils.ts","../src/dom-utils.ts","../src/bindGlobalEventListeners.ts","../src/browser.ts","../src/validation.ts","../src/props.ts","../src/template.ts","../src/createTippy.ts","../src/index.ts","../src/addons/createSingleton.ts","../src/addons/delegate.ts","../src/plugins/animateFill.ts","../src/plugins/followCursor.ts","../src/plugins/inlinePositioning.ts","../src/plugins/sticky.ts","../build/base-umd.js"],"sourcesContent":["export const ROUND_ARROW =\n '';\n\nexport const BOX_CLASS = `__NAMESPACE_PREFIX__-box`;\nexport const CONTENT_CLASS = `__NAMESPACE_PREFIX__-content`;\nexport const BACKDROP_CLASS = `__NAMESPACE_PREFIX__-backdrop`;\nexport const ARROW_CLASS = `__NAMESPACE_PREFIX__-arrow`;\nexport const SVG_ARROW_CLASS = `__NAMESPACE_PREFIX__-svg-arrow`;\n\nexport const TOUCH_OPTIONS = {passive: true, capture: true};\n\nexport const TIPPY_DEFAULT_APPEND_TO = () => document.body;\n","import {BasePlacement, Placement} from './types';\n\nexport function hasOwnProperty(\n obj: Record,\n key: string\n): boolean {\n return {}.hasOwnProperty.call(obj, key);\n}\n\nexport function getValueAtIndexOrReturn(\n value: T | [T | null, T | null],\n index: number,\n defaultValue: T | [T, T]\n): T {\n if (Array.isArray(value)) {\n const v = value[index];\n return v == null\n ? Array.isArray(defaultValue)\n ? defaultValue[index]\n : defaultValue\n : v;\n }\n\n return value;\n}\n\nexport function isType(value: any, type: string): boolean {\n const str = {}.toString.call(value);\n return str.indexOf('[object') === 0 && str.indexOf(`${type}]`) > -1;\n}\n\nexport function invokeWithArgsOrReturn(value: any, args: any[]): any {\n return typeof value === 'function' ? value(...args) : value;\n}\n\nexport function debounce(\n fn: (arg: T) => void,\n ms: number\n): (arg: T) => void {\n // Avoid wrapping in `setTimeout` if ms is 0 anyway\n if (ms === 0) {\n return fn;\n }\n\n let timeout: any;\n\n return (arg): void => {\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n fn(arg);\n }, ms);\n };\n}\n\nexport function removeProperties(obj: T, keys: string[]): Partial {\n const clone = {...obj};\n keys.forEach((key) => {\n delete (clone as any)[key];\n });\n return clone;\n}\n\nexport function splitBySpaces(value: string): string[] {\n return value.split(/\\s+/).filter(Boolean);\n}\n\nexport function normalizeToArray(value: T | T[]): T[] {\n return ([] as T[]).concat(value);\n}\n\nexport function pushIfUnique(arr: T[], value: T): void {\n if (arr.indexOf(value) === -1) {\n arr.push(value);\n }\n}\n\nexport function appendPxIfNumber(value: string | number): string {\n return typeof value === 'number' ? `${value}px` : value;\n}\n\nexport function unique(arr: T[]): T[] {\n return arr.filter((item, index) => arr.indexOf(item) === index);\n}\n\nexport function getNumber(value: string | number): number {\n return typeof value === 'number' ? value : parseFloat(value);\n}\n\nexport function getBasePlacement(placement: Placement): BasePlacement {\n return placement.split('-')[0] as BasePlacement;\n}\n\nexport function arrayFrom(value: ArrayLike): any[] {\n return [].slice.call(value);\n}\n\nexport function removeUndefinedProps(\n obj: Record\n): Partial> {\n return Object.keys(obj).reduce((acc, key) => {\n if (obj[key] !== undefined) {\n (acc as any)[key] = obj[key];\n }\n\n return acc;\n }, {});\n}\n","import {ReferenceElement, Targets} from './types';\nimport {PopperTreeData} from './types-internal';\nimport {arrayFrom, isType, normalizeToArray, getBasePlacement} from './utils';\n\nexport function div(): HTMLDivElement {\n return document.createElement('div');\n}\n\nexport function isElement(value: unknown): value is Element | DocumentFragment {\n return ['Element', 'Fragment'].some((type) => isType(value, type));\n}\n\nexport function isNodeList(value: unknown): value is NodeList {\n return isType(value, 'NodeList');\n}\n\nexport function isMouseEvent(value: unknown): value is MouseEvent {\n return isType(value, 'MouseEvent');\n}\n\nexport function isReferenceElement(value: any): value is ReferenceElement {\n return !!(value && value._tippy && value._tippy.reference === value);\n}\n\nexport function getArrayOfElements(value: Targets): Element[] {\n if (isElement(value)) {\n return [value];\n }\n\n if (isNodeList(value)) {\n return arrayFrom(value);\n }\n\n if (Array.isArray(value)) {\n return value;\n }\n\n return arrayFrom(document.querySelectorAll(value));\n}\n\nexport function setTransitionDuration(\n els: (HTMLDivElement | null)[],\n value: number\n): void {\n els.forEach((el) => {\n if (el) {\n el.style.transitionDuration = `${value}ms`;\n }\n });\n}\n\nexport function setVisibilityState(\n els: (HTMLDivElement | null)[],\n state: 'visible' | 'hidden'\n): void {\n els.forEach((el) => {\n if (el) {\n el.setAttribute('data-state', state);\n }\n });\n}\n\nexport function getOwnerDocument(\n elementOrElements: Element | Element[]\n): Document {\n const [element] = normalizeToArray(elementOrElements);\n\n // Elements created via a