shimmer 0.0.1 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36dcc714d5d4ddff50af58d02889b9c193754c8a8c8a48b38e989f898805adc5
4
- data.tar.gz: 3a3d6e7b848df467c7aa6ceb2f51071cd325f06da912dbc2926f0f7b7d986193
3
+ metadata.gz: 32060fa00134259da2eb2723aa0ba4e14a95bbd85f9b214797e76778f2c1500b
4
+ data.tar.gz: 18c942b4f19f582e7ae45a89aad5b03d6192201ea0849e14bf9e8f567dccc069
5
5
  SHA512:
6
- metadata.gz: 7716f31b96c8d62da7c94151b59a6006ef12ddd918314aceb3026094cfb414634285e583979e0bc6b0932e16d36e95b69199d0e83c03933148ceaaf3002eac0d
7
- data.tar.gz: 64803d66f26ed93da2265b453493bfe768344273cabb9e0c077678d7c881d73eebdee1701801767adef198da8cda100cc72f5486687c56df2fdb8de865f84dac
6
+ metadata.gz: 752d5f326fcf637ec3fa4d37e3cd6c5a9df88d7c408697ad7c3153b65f75ef1c8fc7f6cbeb8e29d44efe25b314a209a3bf615bd30146f704beb05cd1b156ca52
7
+ data.tar.gz: f8465c8a123e351a84e970fcbb6629e79a0b2759095c6378f248255cb41fdacdbb0dbce46e8751438c0737669d22e2eb8dfe8a9afb3574adfc0d0e031976b716
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ ; EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ ; top-most EditorConfig file
4
+ root = true
5
+
6
+ ; Unix-style newlines with a newline ending every file
7
+ [*]
8
+ indent_style = spaces
9
+ end_of_line = lf
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
12
+ indent_size = 2
data/.eslintrc.js ADDED
@@ -0,0 +1,25 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ es6: true,
5
+ },
6
+ parser: "@typescript-eslint/parser",
7
+ extends: [
8
+ "plugin:@typescript-eslint/recommended",
9
+ "plugin:prettier/recommended",
10
+ ],
11
+ parserOptions: {
12
+ ecmaVersion: 2018,
13
+ sourceType: "module",
14
+ },
15
+ plugins: ["@typescript-eslint"],
16
+ rules: {
17
+ "no-console": 2,
18
+ "@typescript-eslint/explicit-function-return-type": [
19
+ "error",
20
+ {
21
+ allowExpressions: true,
22
+ },
23
+ ],
24
+ },
25
+ };
data/.prettierrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "trailingComma": "es5"
3
+ }
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.3
data/.solargraph.yml ADDED
@@ -0,0 +1,4 @@
1
+ plugins:
2
+ - solargraph-standardrb
3
+ reporters:
4
+ - standardrb
@@ -0,0 +1,3 @@
1
+ {
2
+ "solargraph.useBundler": true
3
+ }
data/Gemfile CHANGED
@@ -6,3 +6,5 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
+ gem "standard"
10
+ gem "solargraph-standardrb"
data/Gemfile.lock ADDED
@@ -0,0 +1,87 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shimmer (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ backport (1.2.0)
11
+ benchmark (0.2.0)
12
+ diff-lcs (1.4.4)
13
+ e2mmap (0.1.0)
14
+ jaro_winkler (1.5.4)
15
+ kramdown (2.3.1)
16
+ rexml
17
+ kramdown-parser-gfm (1.1.0)
18
+ kramdown (~> 2.0)
19
+ nokogiri (1.12.5-x86_64-darwin)
20
+ racc (~> 1.4)
21
+ nokogiri (1.12.5-x86_64-linux)
22
+ racc (~> 1.4)
23
+ parallel (1.21.0)
24
+ parser (3.0.3.2)
25
+ ast (~> 2.4.1)
26
+ racc (1.6.0)
27
+ rainbow (3.0.0)
28
+ rake (13.0.6)
29
+ regexp_parser (2.2.0)
30
+ reverse_markdown (2.1.1)
31
+ nokogiri
32
+ rexml (3.2.5)
33
+ rubocop (1.23.0)
34
+ parallel (~> 1.10)
35
+ parser (>= 3.0.0.0)
36
+ rainbow (>= 2.2.2, < 4.0)
37
+ regexp_parser (>= 1.8, < 3.0)
38
+ rexml
39
+ rubocop-ast (>= 1.12.0, < 2.0)
40
+ ruby-progressbar (~> 1.7)
41
+ unicode-display_width (>= 1.4.0, < 3.0)
42
+ rubocop-ast (1.15.0)
43
+ parser (>= 3.0.1.1)
44
+ rubocop-performance (1.12.0)
45
+ rubocop (>= 1.7.0, < 2.0)
46
+ rubocop-ast (>= 0.4.0)
47
+ ruby-progressbar (1.11.0)
48
+ solargraph (0.44.2)
49
+ backport (~> 1.2)
50
+ benchmark
51
+ bundler (>= 1.17.2)
52
+ diff-lcs (~> 1.4)
53
+ e2mmap
54
+ jaro_winkler (~> 1.5)
55
+ kramdown (~> 2.3)
56
+ kramdown-parser-gfm (~> 1.1)
57
+ parser (~> 3.0)
58
+ reverse_markdown (>= 1.0.5, < 3)
59
+ rubocop (>= 0.52)
60
+ thor (~> 1.0)
61
+ tilt (~> 2.0)
62
+ yard (~> 0.9, >= 0.9.24)
63
+ solargraph-standardrb (0.0.4)
64
+ solargraph (>= 0.39.1)
65
+ standard (>= 0.4.1)
66
+ standard (1.5.0)
67
+ rubocop (= 1.23.0)
68
+ rubocop-performance (= 1.12.0)
69
+ thor (1.1.0)
70
+ tilt (2.0.10)
71
+ unicode-display_width (2.1.0)
72
+ webrick (1.7.0)
73
+ yard (0.9.27)
74
+ webrick (~> 1.7.0)
75
+
76
+ PLATFORMS
77
+ x86_64-darwin-21
78
+ x86_64-linux
79
+
80
+ DEPENDENCIES
81
+ rake (~> 13.0)
82
+ shimmer!
83
+ solargraph-standardrb
84
+ standard
85
+
86
+ BUNDLED WITH
87
+ 2.2.32
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ class FilesController < ActionController::Base
5
+ def show
6
+ expires_in 1.year, public: true
7
+ request.session_options[:skip] = true # prevents a session cookie from being set (would prevent caching on CDNs)
8
+ proxy = FileProxy.restore(params.require(:id))
9
+ send_data proxy.file,
10
+ filename: proxy.filename.to_s,
11
+ type: proxy.content_type,
12
+ disposition: "inline"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ class SitemapsController < ActionController::Base
5
+ def show
6
+ path = "sitemaps/#{params.require(:path)}.gz"
7
+ send_data ActiveStorage::Blob.service.download(path)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ class SitemapJob < ActiveJob::Base
5
+ def perform
6
+ SitemapGenerator::Interpreter.run
7
+ SitemapGenerator::Sitemap.ping_search_engines
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://github.com/rails/rails/issues/22965
4
+ module Shimmer
5
+ class CloudflareProxy
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ return @app.call(env) unless env["HTTP_CF_VISITOR"]
12
+
13
+ env["HTTP_X_FORWARDED_PROTO"] = JSON.parse(env["HTTP_CF_VISITOR"])["scheme"]
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Shimmer
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ path = File.expand_path(__dir__)
5
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :db do
4
+ desc "Downloads the app database from heroku to local db"
5
+ task pull_data: :environment do
6
+ config = ActiveRecord::Base.connection_db_config.config
7
+ # binding.pry
8
+ ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] = "1"
9
+ Rake::Task["db:drop"].invoke
10
+ ENV["PGUSER"] = config["username"]
11
+ ENV["PGHOST"] = config["host"]
12
+ ENV["PGPORT"] = config["port"].to_s
13
+ sh "heroku pg:pull DATABASE_URL #{config["database"]} --app thefetishtraveller"
14
+ sh "rails db:environment:set"
15
+ sh "RAILS_ENV=test rails db:create"
16
+ end
17
+
18
+ task pull_assets: :environment do
19
+ config = JSON.parse(`heroku config --json`)
20
+ ENV["AWS_DEFAULT_REGION"] = config.fetch("AWS_REGION")
21
+ bucket = config.fetch("AWS_BUCKET")
22
+ ENV["AWS_ACCESS_KEY_ID"] = config.fetch("AWS_ACCESS_KEY_ID")
23
+ ENV["AWS_SECRET_ACCESS_KEY"] = config.fetch("AWS_SECRET_ACCESS_KEY")
24
+ storage_folder = Rails.root.join("storage")
25
+ download_folder = storage_folder.join("downloads")
26
+ FileUtils.mkdir_p download_folder
27
+ sh "aws s3 sync s3://#{bucket} #{storage_folder}/downloads"
28
+ download_folder.each_child do |file|
29
+ next if file.directory?
30
+
31
+ new_path = storage_folder.join file.basename.to_s.then { |e| [e[0..1], e[2..3], e] }.join("/")
32
+ FileUtils.mkdir_p(new_path.dirname)
33
+ FileUtils.cp(file, new_path)
34
+ end
35
+ # purge variants
36
+ ActiveStorage::VariantRecord.delete_all
37
+ ActiveStorage::Blob.update_all(service_name: :local)
38
+ end
39
+
40
+ desc "Download all app data, including assets"
41
+ task pull: [:pull_data, :pull_assets]
42
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Executes all linters and tests"
4
+ task :lint do
5
+ sh "bundle exec standardrb --fix"
6
+ sh "yarn lint"
7
+ sh "i18n-tasks health"
8
+ sh "bin/rspec"
9
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module FileHelper
5
+ def image_tag(source, **options)
6
+ return nil if source.blank?
7
+
8
+ if source.is_a?(ActiveStorage::Variant) || source.is_a?(ActiveStorage::Attached) || source.is_a?(ActiveStorage::Attachment) || source.is_a?(ActionText::Attachment)
9
+ attachment = source
10
+ width = options[:width]
11
+ height = options[:height]
12
+ source = image_file_url(source, width: width, height: height)
13
+ options[:loading] = :lazy
14
+ options[:srcset] = "#{source} 1x, #{image_file_url(attachment, width: width.to_i * 2, height: height ? height.to_i * 2 : nil)} 2x" if options[:width].present?
15
+ end
16
+ super source, options
17
+ end
18
+
19
+ def image_file_url(source, width: nil, height: nil)
20
+ return if source.blank?
21
+ return source if source.is_a?(String)
22
+
23
+ blob = source.try(:blob) || source
24
+ Shimmer::FileProxy.new(blob_id: blob.id, width: width, height: height).url
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ class FileProxy
5
+ attr_reader :blob_id, :resize
6
+
7
+ delegate :message_verifier, to: :class
8
+ delegate :content_type, :filename, to: :blob
9
+
10
+ class << self
11
+ def restore(id)
12
+ blob_id, resize = message_verifier.verified(id)
13
+ new blob_id: blob_id, resize: resize
14
+ end
15
+ end
16
+
17
+ def initialize(blob_id:, resize: nil, width: nil, height: nil)
18
+ @blob_id = blob_id
19
+ if !resize && width
20
+ resize = if height
21
+ "#{width}x#{height}>"
22
+ else
23
+ "#{width}x"
24
+ end
25
+ end
26
+ @resize = resize
27
+ end
28
+
29
+ class << self
30
+ def message_verifier
31
+ @message_verifier ||= ApplicationRecord.signed_id_verifier
32
+ end
33
+ end
34
+
35
+ def path
36
+ Rails.application.routes.url_helpers.file_path(id, locale: nil)
37
+ end
38
+
39
+ def url(protocol: Rails.env.production? ? :https : :http)
40
+ Rails.application.routes.url_helpers.file_url(id, locale: nil, protocol: protocol)
41
+ end
42
+
43
+ def blob
44
+ @blob ||= ActiveStorage::Blob.find(blob_id)
45
+ end
46
+
47
+ def resizeable
48
+ resize.present? && !blob.content_type.include?("svg")
49
+ end
50
+
51
+ def variant
52
+ @variantvariant ||= resizeable ? blob.representation(resize: resize).processed : blob
53
+ end
54
+
55
+ def file
56
+ @file ||= blob.service.download(variant.key)
57
+ end
58
+
59
+ private
60
+
61
+ def id
62
+ @id ||= message_verifier.generate([blob_id, resize])
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Localizable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :set_locale
9
+
10
+ def default_url_options(options = {})
11
+ options = {locale: I18n.locale}.merge options
12
+ options[:debug] = true if I18n.debug?
13
+ options
14
+ end
15
+
16
+ def request_locale
17
+ request.env["HTTP_ACCEPT_LANGUAGE"].to_s[0..1].downcase.presence&.then { |e| e if I18n.available_locales.include?(e.to_sym) }
18
+ end
19
+
20
+ def set_locale
21
+ I18n.locale = url_locale || request_locale || I18n.default_locale
22
+ I18n.debug = params.key?(:debug)
23
+ end
24
+
25
+ def check_locale
26
+ redirect_to url_for(locale: I18n.locale) if params[:locale].blank? && request.get? && request.format.html?
27
+ end
28
+
29
+ def url_locale
30
+ params[:locale]
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module I18n
37
+ UNTRANSLATED_SCOPES = ["number", "transliterate", "date", "datetime", "errors", "helpers", "support", "time", "faker"].map { |k| "#{k}." }
38
+
39
+ thread_mattr_accessor :debug
40
+
41
+ class << self
42
+ alias_method :old_translate, :translate
43
+ def translate(key, options = {})
44
+ key = key.to_s.downcase
45
+ untranslated = UNTRANSLATED_SCOPES.any? { |e| key.include? e }
46
+ key_name = [options[:scope], key].flatten.compact.join(".")
47
+ option_names = options.except(:count, :default, :raise, :scope).map { |k, v| "#{k}=#{v}" }.join(", ")
48
+ return "#{key_name} #{option_names}" if I18n.debug && !untranslated
49
+
50
+ options.reverse_merge!(default: old_translate(key, **options.merge(locale: :de))) if untranslated
51
+ old_translate(key, **options)
52
+ end
53
+ alias_method :t, :translate
54
+
55
+ def debug?
56
+ debug
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module RemoteNavigation
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def ui
9
+ @ui ||= RemoteNavigator.new turbo_stream: turbo_stream
10
+ end
11
+
12
+ def default_render
13
+ return render_modal if shimmer_request?
14
+ return super unless ui.updates?
15
+
16
+ render turbo_stream: ui.queued_updates.join("\n")
17
+ end
18
+
19
+ helper_method :modal_path
20
+ def modal_path(url, id: nil, size: nil, close: false)
21
+ "javascript:ui.modal.open(#{{url: url, id: id, size: size, close: close}.to_json})"
22
+ end
23
+
24
+ helper_method :close_modal_path
25
+ def close_modal_path(id: nil)
26
+ "javascript:ui.modal.close(#{{id: id}.to_json})"
27
+ end
28
+
29
+ def shimmer_request?
30
+ request.headers["X-Shimmer"].present?
31
+ end
32
+
33
+ def render_modal
34
+ enforce_modal
35
+ render layout: false
36
+ end
37
+
38
+ def enforce_modal
39
+ raise "trying to render a modal from a regular request" unless shimmer_request?
40
+ end
41
+ end
42
+ end
43
+
44
+ class RemoteNavigator
45
+ attr_reader :turbo_stream
46
+
47
+ def initialize(turbo_stream:)
48
+ @turbo_stream = turbo_stream
49
+ end
50
+
51
+ def queued_updates
52
+ @queued_updates ||= []
53
+ end
54
+
55
+ def updates?
56
+ queued_updates.any?
57
+ end
58
+
59
+ def run_javascript(script)
60
+ queued_updates.push turbo_stream.append "shimmer", "<div class='hidden' data-controller='remote-navigation'>#{script}</div>"
61
+ end
62
+
63
+ def replace(id, with: id, **locals)
64
+ queued_updates.push turbo_stream.replace(id, partial: with, locals: locals)
65
+ end
66
+
67
+ def prepend(id, with:, **locals)
68
+ queued_updates.push turbo_stream.prepend(id, partial: with, locals: locals)
69
+ end
70
+
71
+ def append(id, with:, **locals)
72
+ queued_updates.push turbo_stream.append(id, partial: with, locals: locals)
73
+ end
74
+
75
+ def remove(id)
76
+ queued_updates.push turbo_stream.remove(id)
77
+ end
78
+
79
+ def open_modal(path)
80
+ run_javascript "ui.modal.open('#{path}')"
81
+ end
82
+
83
+ def close_modal
84
+ run_javascript "ui.modal.close()"
85
+ end
86
+
87
+ def navigate_to(path)
88
+ close_modal
89
+ path = polymorphic_path(path) unless path.is_a?(String)
90
+ run_javascript "Turbo.visit('#{path}')"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ class SitemapAdapter
5
+ def write(location, raw_data)
6
+ SitemapGenerator::FileAdapter.new.write(location, raw_data)
7
+ ActiveStorage::Blob.service.upload("sitemaps/#{location.path_in_public}", File.open(location.path))
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shimmer
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.4"
5
5
  end
data/lib/shimmer.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "shimmer/version"
4
+ require_relative "shimmer/railtie" if defined?(Rails::Railtie)
5
+ Dir["#{File.expand_path("../lib/shimmer/middlewares", __dir__)}/*"].each { |e| require e }
6
+ Dir["#{File.expand_path("../lib/shimmer/controllers", __dir__)}/*"].each { |e| require e }
7
+ Dir["#{File.expand_path("../lib/shimmer/jobs", __dir__)}/*"].each { |e| require e }
8
+ Dir["#{File.expand_path("../lib/shimmer/utils", __dir__)}/*"].each { |e| require e }
4
9
 
5
10
  module Shimmer
6
11
  class Error < StandardError; end
data/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@nerdgeschoss/shimmer",
3
+ "version": "0.0.1",
4
+ "description": "Simple application development in Rails",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "yarn build:js && yarn build:types",
13
+ "build:js": "NODE_ENV=production rollup -c",
14
+ "build:types": "tsc --emitDeclarationOnly",
15
+ "format": "prettier --write \"src/**/*.{ts,css,scss,json,yml}\"",
16
+ "lint": "yarn lint:types && yarn lint:style",
17
+ "lint:types": "tsc --noEmit",
18
+ "lint:style": "eslint src/**/*.ts --max-warnings 0"
19
+ },
20
+ "contributors": [
21
+ "Jens Ravens"
22
+ ],
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@rails/request.js": "^0.0.6"
26
+ },
27
+ "devDependencies": {
28
+ "@typescript-eslint/eslint-plugin": "^5.6.0",
29
+ "@typescript-eslint/parser": "^5.6.0",
30
+ "esbuild": "^0.14.2",
31
+ "eslint": "^8.4.1",
32
+ "eslint-config-prettier": "^8.3.0",
33
+ "eslint-plugin-prettier": "^4.0.0",
34
+ "prettier": "^2.5.1",
35
+ "rollup": "^2.61.0",
36
+ "rollup-plugin-cleaner": "^1.0.0",
37
+ "rollup-plugin-esbuild": "^4.7.2",
38
+ "typescript": "^4.1.3"
39
+ },
40
+ "keywords": [
41
+ "rails",
42
+ "form",
43
+ "modal"
44
+ ],
45
+ "bugs": {
46
+ "url": "https://github.com/nerdgeschoss/shimmer/issues"
47
+ },
48
+ "homepage": "https://github.com/nerdgeschoss/shimmer#readme"
49
+ }
data/rollup.config.js ADDED
@@ -0,0 +1,26 @@
1
+ import esbuild from "rollup-plugin-esbuild";
2
+ import cleaner from "rollup-plugin-cleaner";
3
+ import pkg from "./package.json";
4
+
5
+ export default {
6
+ input: "./src/index.ts",
7
+ external: ["@rails/request.js"],
8
+ plugins: [
9
+ cleaner({
10
+ targets: ["./dist/"],
11
+ }),
12
+ esbuild({
13
+ target: "es6",
14
+ }),
15
+ ],
16
+ output: [
17
+ {
18
+ file: pkg.main,
19
+ format: "cjs",
20
+ },
21
+ {
22
+ file: pkg.module,
23
+ format: "es",
24
+ },
25
+ ],
26
+ };
data/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ declare global {
2
+ interface Window {
3
+ ui: typeof ui;
4
+ }
5
+ }
6
+
7
+ import { ModalPresenter } from "./modal";
8
+ import "./touch";
9
+
10
+ export const ui = {
11
+ modal: new ModalPresenter(),
12
+ };
13
+
14
+ window.ui = ui;
15
+
16
+ export { registerServiceWorker } from "./serviceworker";
17
+ export { currentLocale } from "./locale";
data/src/locale.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function currentLocale(): string {
2
+ return (document.documentElement.lang || "en") as string;
3
+ }