# frozen_string_literal: true def public_root "#{Rails.root}/public" end def public_js "#{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
{{i18n "emoji_picker.#{group["name"]}"}}
#{icons.join("\n").strip}
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" `yarn run prettier --write #{output_path}` 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}") `yarn run prettier --write #{output_path}` puts "#{basename} created" end def dependencies [ { source: "ace-builds/src-min-noconflict/ace.js", destination: "ace.js", public: true }, { source: "@json-editor/json-editor/dist/jsoneditor.js", package_name: "@json-editor/json-editor", public: true, }, { source: "chart.js/dist/chart.min.js", public: true }, { source: "chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js", public: true }, { source: "diffhtml/dist/diffhtml.min.js", public: true }, { source: "magnific-popup/dist/jquery.magnific-popup.min.js", public: true }, { source: "pikaday/pikaday.js", public: true }, { source: "@highlightjs/cdn-assets/.", destination: "highlightjs" }, { 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: "workbox-sw/build/.", destination: "workbox", public: true, skip_versioning: true }, { source: "workbox-routing/build/.", destination: "workbox", public: true, skip_versioning: true, }, { source: "workbox-core/build/.", destination: "workbox", public: true, skip_versioning: true }, { source: "workbox-strategies/build/.", destination: "workbox", public: true, skip_versioning: true, }, { source: "workbox-expiration/build/.", destination: "workbox", public: true, skip_versioning: true, }, { source: "workbox-cacheable-response/build/.", destination: "workbox", skip_versioning: true, public: true, }, { 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" 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}, } 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 langs = [] Dir .glob("vendor/assets/javascripts/highlightjs/languages/*.min.js") .each { |f| langs << File.basename(f, ".min.js") } bundle = HighlightJs.bundle(langs) ctx = MiniRacer::Context.new hljs_aliases = ctx.eval(<<~JS) #{bundle} let aliases = {}; hljs.listLanguages().forEach((lang) => { if (hljs.getLanguage(lang).aliases) { aliases[lang] = hljs.getLanguage(lang).aliases; } }); aliases; JS write_template("pretty-text/addon/highlightjs-aliases.js", task_name, <<~JS) export const HLJS_ALIASES = #{hljs_aliases.to_json}; JS ctx.dispose 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 } 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" yarn = system("yarn install") abort('Unable to run "yarn install"') unless yarn 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 if src.include? "highlightjs" puts "Cleanup highlightjs styles and install smaller test bundle" system("rm -rf node_modules/@highlightjs/cdn-assets/styles") # We don't need every language for tests langs = %w[javascript sql ruby] test_bundle_dest = "vendor/assets/javascripts/highlightjs/highlight-test-bundle.min.js" File.write(test_bundle_dest, HighlightJs.bundle(langs)) end 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 if src.include? "ace.js" versions["ace/ace.js"] = versions.delete("ace.js") ace_root = "#{library_src}/ace-builds/src-min-noconflict/" addtl_files = %w[ ext-searchbox mode-html mode-scss mode-sql mode-yaml theme-chrome theme-chaos worker-html ] dest_path = dest.split("/")[0..-2].join("/") addtl_files.each { |file| FileUtils.cp_r("#{ace_root}#{file}.js", dest_path) } end STDERR.puts "New dependency added: #{dest}" unless File.exist?(dest) if f[:uglify] File.write(dest, Uglifier.new.compile(File.read(src))) else FileUtils.cp_r(src, dest) end 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 unless versions.present? 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