discourse/lib/tasks/javascript.rake

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

279 lines
8.3 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
2020-07-16 03:29:12 +08:00
def public_root
"#{Rails.root}/public"
end
def public_js
2020-07-16 03:29:12 +08:00
"#{public_root}/javascripts"
end
def vendor_js
"#{Rails.root}/vendor/assets/javascripts"
end
def library_src
"#{Rails.root}/node_modules"
end
def html_for_section(group)
icons =
group["icons"].map do |icon|
class_attr = icon["diversity"] ? " class=\"diversity\"" : ""
" {{replace-emoji \":#{icon["name"]}:\" (hash lazy=true#{class_attr} tabIndex=\"0\")}}"
end
<<~HTML
<div class="section" data-section="#{group["name"]}">
<div class="section-header">
<span class="title">{{i18n "emoji_picker.#{group["name"]}"}}</span>
</div>
<div class="section-group">
#{icons.join("\n").strip}
</div>
</div>
HTML
end
def write_template(path, task_name, template)
header = <<~JS
// DO NOT EDIT THIS FILE!!!
// Update it by running `rake javascript:#{task_name}`
JS
basename = File.basename(path)
output_path = "#{Rails.root}/app/assets/javascripts/#{path}"
File.write(output_path, "#{header}\n\n#{template}")
puts "#{basename} created"
system("pnpm prettier --write #{output_path}", exception: true)
puts "#{basename} prettified"
end
def write_hbs_template(path, task_name, template)
header = <<~HBS
{{!-- DO NOT EDIT THIS FILE!!! --}}
{{!-- Update it by running `rake javascript:#{task_name}` --}}
HBS
basename = File.basename(path)
output_path = "#{Rails.root}/app/assets/javascripts/#{path}"
File.write(output_path, "#{header}\n#{template}")
system("pnpm prettier --write #{output_path}", exception: true)
puts "#{basename} created"
end
def dependencies
[
{ source: "chart.js/dist/chart.min.js", public: true },
{ source: "chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js", public: true },
{ source: "magnific-popup/dist/jquery.magnific-popup.min.js", public: true },
{ source: "pikaday/pikaday.js", public: true },
{ source: "moment/moment.js" },
{ source: "moment/locale/.", destination: "moment-locale" },
{
source: "moment-timezone/builds/moment-timezone-with-data-10-year-range.js",
destination: "moment-timezone-with-data.js",
},
{
source: "@discourse/moment-timezone-names-translations/locales/.",
destination: "moment-timezone-names-locale",
},
{
source: "squoosh/codecs/mozjpeg/enc/mozjpeg_enc.js",
destination: "squoosh",
public: true,
skip_versioning: true,
},
{
source: "squoosh/codecs/mozjpeg/enc/mozjpeg_enc.wasm",
destination: "squoosh",
public: true,
skip_versioning: true,
},
{
source: "squoosh/codecs/resize/pkg/squoosh_resize.js",
destination: "squoosh",
public: true,
skip_versioning: true,
},
{
source: "squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm",
destination: "squoosh",
public: true,
skip_versioning: true,
},
]
end
def node_package_name(f)
f[:package_name] || f[:source].split("/").first
end
def public_path_name(f)
f[:destination] || node_package_name(f)
end
def absolute_sourcemap(dest)
File.open(dest) do |file|
contents = file.read
contents.gsub!(/sourceMappingURL=(.*)/, 'sourceMappingURL=/\1')
File.open(dest, "w+") { |d| d.write(contents) }
end
end
task "javascript:update_constants" => :environment do
task_name = "update_constants"
auto_groups =
Group::AUTO_GROUPS.inject({}) do |result, (group_name, group_id)|
result.merge(
group_name => {
id: group_id,
automatic: true,
name: group_name,
display_name: group_name,
},
)
end
write_template("discourse/app/lib/constants.js", task_name, <<~JS)
export const SEARCH_PRIORITIES = #{Searchable::PRIORITIES.to_json};
export const SEARCH_PHRASE_REGEXP = '#{Search::PHRASE_MATCH_REGEXP_PATTERN}';
export const SIDEBAR_URL = {
max_icon_length: #{SidebarUrl::MAX_ICON_LENGTH},
max_name_length: #{SidebarUrl::MAX_NAME_LENGTH},
max_value_length: #{SidebarUrl::MAX_VALUE_LENGTH}
}
export const SIDEBAR_SECTION = {
max_title_length: #{SidebarSection::MAX_TITLE_LENGTH},
}
export const AUTO_GROUPS = #{auto_groups.to_json};
export const GROUP_SMTP_SSL_MODES = #{Group.smtp_ssl_modes.to_json};
export const MAX_NOTIFICATIONS_LIMIT_PARAMS = #{NotificationsController::INDEX_LIMIT};
export const TOPIC_VISIBILITY_REASONS = #{Topic.visibility_reasons.to_json};
export const SYSTEM_FLAG_IDS = #{PostActionType.types.to_json};
export const SITE_SETTING_REQUIRES_CONFIRMATION_TYPES = #{SiteSettings::TypeSupervisor::REQUIRES_CONFIRMATION_TYPES.to_json};
export const MAX_UNOPTIMIZED_CATEGORIES = #{CategoryList::MAX_UNOPTIMIZED_CATEGORIES};
JS
pretty_notifications = Notification.types.map { |n| " #{n[0]}: #{n[1]}," }.join("\n")
write_template("discourse/tests/fixtures/concerns/notification-types.js", task_name, <<~JS)
export const NOTIFICATION_TYPES = {
#{pretty_notifications}
};
JS
write_template("pretty-text/addon/emoji/data.js", task_name, <<~JS)
export const emojis = #{Emoji.standard.map(&:name).flatten.inspect};
export const tonableEmojis = #{Emoji.tonable_emojis.flatten.inspect};
export const aliases = #{Emoji.aliases.inspect.gsub("=>", ":")};
export const searchAliases = #{Emoji.search_aliases.inspect.gsub("=>", ":")};
export const translations = #{Emoji.translations.inspect.gsub("=>", ":")};
export const replacements = #{Emoji.unicode_replacements_json};
JS
write_template("pretty-text/addon/emoji/version.js", task_name, <<~JS)
export const IMAGE_VERSION = "#{Emoji::EMOJI_VERSION}";
JS
groups_json = JSON.parse(File.read("lib/emoji/groups.json"))
emoji_buttons = groups_json.map { |group| <<~HTML }
DEV: Remove usage of {{action}} modifiers - Take 2 (#18476) This PR enables the [`no-action-modifiers`](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-action-modifiers.md) template lint rule and removes all usages of the `{{action}}` modifier in core. In general, instances of `{{action "x"}}` have been replaced with `{{on "click" (action "x")}}`. In many cases, such as for `a` elements, we also need to prevent default event handling to avoid unwanted side effects. While the `{{action}}` modifier internally calls `event.preventDefault()`, we need to handle these cases more explicitly. For this purpose, this PR also adds the [ember-event-helpers](https://github.com/buschtoens/ember-event-helpers) dependency so we can use the `prevent-default` handler. For instance: ``` <a href {{on "click" (prevent-default (action "x"))}}>Do X</a> ``` Note that `action` has not in general been refactored away as a helper yet. In general, all event handlers should be methods on the corresponding component and referenced directly (e.g. `{{on "click" this.doSomething}}`). However, the `action` helper is used extensively throughout the codebase and often references methods in the `actions` hash on controllers or routes. Thus this refactor will also be extensive and probably deserves a separate PR. Note: This work was done to complement #17767 by minimizing the potential impact of the `action` modifier override, which uses private API and arguably should be replaced with an AST transform. This is a followup to #18333, which had to be reverted because it did not account for the default treatment of modifier keys by the {{action}} modifier. Commits: * Enable `no-action-modifiers` template lint rule * Replace {{action "x"}} with {{on "click" (action "x")}} * Remove unnecessary action helper usage * Remove ctl+click tests for user-menu These tests now break in Chrome when used with addEventListener. As per the comment, they can probably be safely removed. * Prevent default event handlers to avoid unwanted side effects Uses `event.preventDefault()` in event handlers to prevent default event handling. This had been done automatically by the `action` modifier, but is not always desirable or necessary. * Restore UserCardContents#showUser action to avoid regression By keeping the `showUser` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showUser` argument that's been passed. * Revert EditCategoryTab#selectTab -> EditCategoryTab#select Avoid potential breaking change in themes / plugins * Restore GroupCardContents#showGroup action to avoid regression By keeping the `showGroup` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showGroup` argument that's been passed. * Restore SecondFactorAddTotp#showSecondFactorKey action to avoid regression By keeping the `showSecondFactorKey` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showSecondFactorKey` property that's maintained on the controller. * Refactor away from `actions` hash in ChooseMessage component * Modernize EmojiPicker#onCategorySelection usage * Modernize SearchResultEntry#logClick usage * Modernize Discovery::Categories#showInserted usage * Modernize Preferences::Account#resendConfirmationEmail usage * Modernize MultiSelect::SelectedCategory#onSelectedNameClick usage * Favor fn over action in SelectedChoice component * Modernize WizardStep event handlers * Favor fn over action usage in buttons * Restore Login#forgotPassword action to avoid possible regression * Introduce modKeysPressed utility Returns an array of modifier keys that are pressed during a given `MouseEvent` or `KeyboardEvent`. * Don't interfere with click events on links with `href` values when modifier keys are pressed
2022-10-05 20:08:54 +08:00
<button type="button" data-section="#{group["name"]}" {{on "click" (fn this.onCategorySelection "#{group["name"]}")}} class="btn btn-default category-button emoji">
{{replace-emoji ":#{group["tabicon"]}:"}}
</button>
HTML
emoji_sections = groups_json.map { |group| html_for_section(group) }
components_dir = "discourse/app/components"
write_hbs_template("#{components_dir}/emoji-group-buttons.hbs", task_name, emoji_buttons.join)
write_hbs_template("#{components_dir}/emoji-group-sections.hbs", task_name, emoji_sections.join)
end
task "javascript:update" => "clean_up" do
require "uglifier"
system("pnpm install", exception: true)
versions = {}
start = Time.now
dependencies.each do |f|
src = "#{library_src}/#{f[:source]}"
if f[:destination]
filename = f[:destination]
else
filename = f[:source].split("/").last
end
2020-07-16 03:29:12 +08:00
if f[:public_root]
dest = "#{public_root}/#{filename}"
elsif f[:public]
if f[:skip_versioning]
dest = "#{public_js}/#{filename}"
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.downcase] = "#{package_dir_name}/#{package_version}/#{filename}"
path = "#{public_js}/#{package_dir_name}/#{package_version}"
dest = "#{path}/#{filename}"
FileUtils.mkdir_p(path) unless File.exist?(path)
end
else
dest = "#{vendor_js}/#{filename}"
end
STDERR.puts "New dependency added: #{dest}" unless File.exist?(dest)
FileUtils.cp_r(src, dest)
end
write_template("discourse/app/lib/public-js-versions.js", "update", <<~JS)
export const PUBLIC_JS_VERSIONS = #{versions.to_json};
JS
STDERR.puts "Completed copying dependencies: #{(Time.now - start).round(2)} secs"
end
task "javascript:clean_up" do
processed = []
dependencies.each do |f|
next unless f[:public] && !f[:skip_versioning]
package_dir_name = public_path_name(f)
next if processed.include?(package_dir_name)
versions = Dir["#{File.join(public_js, package_dir_name)}/*"].collect { |p| p.split("/").last }
next if versions.blank?
versions = versions.sort { |a, b| Gem::Version.new(a) <=> Gem::Version.new(b) }
puts "Keeping #{package_dir_name} version: #{versions[-1]}"
# Keep the most recent version
versions[0..-2].each do |version|
remove_path = File.join(public_js, package_dir_name, version)
puts "Removing: #{remove_path}"
FileUtils.remove_dir(remove_path)
end
processed << package_dir_name
end
end