dugway 1.2.0 → 1.3.0
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 +4 -4
- data/.github/workflows/main.yml +1 -1
- data/README.md +32 -7
- data/dugway.gemspec +11 -9
- data/lib/dugway/cli/server.rb +67 -7
- data/lib/dugway/liquid/drops/artists_drop.rb +12 -0
- data/lib/dugway/liquid/drops/base_drop.rb +27 -2
- data/lib/dugway/liquid/drops/categories_drop.rb +12 -0
- data/lib/dugway/liquid/drops/pages_drop.rb +30 -2
- data/lib/dugway/liquid/drops/product_drop.rb +11 -0
- data/lib/dugway/liquid/drops/products_drop.rb +39 -0
- data/lib/dugway/liquid/drops/theme_drop.rb +29 -6
- data/lib/dugway/liquid/filters/core_filters.rb +169 -7
- data/lib/dugway/liquid/filters/font_filters.rb +1 -0
- data/lib/dugway/liquid/filters/util_filters.rb +1 -10
- data/lib/dugway/liquid/tags/get.rb +6 -6
- data/lib/dugway/liquid/tags/paginate.rb +61 -11
- data/lib/dugway/store.rb +39 -1
- data/lib/dugway/theme.rb +72 -33
- data/lib/dugway/version.rb +1 -1
- data/lib/dugway.rb +24 -1
- data/locales/storefront.de.yml +2 -0
- data/locales/storefront.en-CA.yml +2 -0
- data/locales/storefront.en-GB.yml +2 -0
- data/locales/storefront.en-US.yml +2 -0
- data/locales/storefront.es-ES.yml +2 -0
- data/locales/storefront.es-MX.yml +2 -0
- data/locales/storefront.fr-CA.yml +2 -0
- data/locales/storefront.fr-FR.yml +2 -0
- data/locales/storefront.id.yml +2 -0
- data/locales/storefront.it.yml +2 -0
- data/locales/storefront.ja.yml +2 -0
- data/locales/storefront.ko.yml +2 -0
- data/locales/storefront.nl.yml +2 -0
- data/locales/storefront.pl.yml +2 -0
- data/locales/storefront.pt-BR.yml +2 -0
- data/locales/storefront.pt-PT.yml +2 -0
- data/locales/storefront.ro.yml +2 -0
- data/locales/storefront.sv.yml +2 -0
- data/locales/storefront.tr.yml +2 -0
- data/locales/storefront.zh-CN.yml +2 -0
- data/locales/storefront.zh-TW.yml +2 -0
- data/mise.toml +2 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/units/dugway/liquid/drops/pages_drop_spec.rb +186 -7
- data/spec/units/dugway/liquid/drops/product_drop_spec.rb +17 -0
- data/spec/units/dugway/liquid/filters/core_filters_spec.rb +301 -3
- data/spec/units/dugway/store_spec.rb +18 -0
- data/spec/units/dugway/theme_spec.rb +246 -0
- metadata +54 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4174e554f06cf4a1a822d0cb83d503ab518d4f8b9bd7dee012a0858773268e4d
|
4
|
+
data.tar.gz: 9d5eafb84ff1a12b9bcddf7d8b7edeeb00de0621165114743de2de71da73df61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f8c749e06f7764d2a89ca64057ae4c736892d969d09e187fd406013b42fde0c73e738ec01bc13e15344634c108a488bb2432162cdd9bb6ed57a2cf1bd43b217
|
7
|
+
data.tar.gz: 8ed84a5455fc6dbea4fdb3734b80dbb081fc23972681f945b12911efcd83e7cdf2f199956893f147410e3dce890a9b6924f0d29340bffe87ea4015228fa0e9d2
|
data/.github/workflows/main.yml
CHANGED
data/README.md
CHANGED
@@ -28,11 +28,7 @@ using [RubyInstaller](http://rubyinstaller.org).
|
|
28
28
|
|
29
29
|
Supported Ruby versions:
|
30
30
|
|
31
|
-
- 2.
|
32
|
-
- 2.4
|
33
|
-
- 2.5
|
34
|
-
- 2.6
|
35
|
-
- 2.7
|
31
|
+
- 2.7+
|
36
32
|
|
37
33
|
### Install Dugway
|
38
34
|
|
@@ -99,12 +95,12 @@ your code into multiple files in separate directories.
|
|
99
95
|
|
100
96
|
#### Using Sprockets
|
101
97
|
|
102
|
-
[Sprockets](https://github.com/
|
98
|
+
[Sprockets](https://github.com/rails/sprockets) allows you to bring in
|
103
99
|
CSS and JavaScript from different directories into a single file. We've created
|
104
100
|
**stylesheets** and **javascripts** directories by default that you can put
|
105
101
|
your code in, but you could delete those and put files anywhere you'd like.
|
106
102
|
After that, use [Sprockets
|
107
|
-
directives](https://github.com/
|
103
|
+
directives](https://github.com/rails/sprockets#directives)
|
108
104
|
to package them into **theme.css** and **theme.js**.
|
109
105
|
|
110
106
|
##### theme.css
|
@@ -193,6 +189,35 @@ dugway server
|
|
193
189
|
By default this will serve your theme at http://127.0.0.1:9292/. You can then stop
|
194
190
|
the server by hitting CTRL+C.
|
195
191
|
|
192
|
+
### Running on a custom port
|
193
|
+
|
194
|
+
To run Dugway on a different port, use the `--port` (or `-p`) option:
|
195
|
+
|
196
|
+
```
|
197
|
+
dugway server --port 3000
|
198
|
+
dugway server -p 8080
|
199
|
+
```
|
200
|
+
|
201
|
+
This is particularly useful for running multiple theme instances simultaneously:
|
202
|
+
|
203
|
+
```
|
204
|
+
# Terminal 1 - Theme A
|
205
|
+
cd /path/to/theme-a
|
206
|
+
dugway server --port 3000
|
207
|
+
|
208
|
+
# Terminal 2 - Theme B
|
209
|
+
cd /path/to/theme-b
|
210
|
+
dugway server --port 3001
|
211
|
+
```
|
212
|
+
|
213
|
+
You can also customize the host address:
|
214
|
+
|
215
|
+
```
|
216
|
+
dugway server --host 0.0.0.0 --port 3000
|
217
|
+
```
|
218
|
+
|
219
|
+
### Logging
|
220
|
+
|
196
221
|
If you want to run with logging, enable it via your theme's `settings.json`
|
197
222
|
file with `"log": true`. When Dugway is running, it will then create a log
|
198
223
|
in `log/dugway.log` in your theme's directory. You can then log output
|
data/dugway.gemspec
CHANGED
@@ -16,32 +16,34 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.platform = Gem::Platform::RUBY
|
18
18
|
s.require_path = 'lib'
|
19
|
-
s.required_ruby_version = '>= 2.
|
19
|
+
s.required_ruby_version = '>= 2.7', '< 3'
|
20
20
|
s.executables << 'dugway'
|
21
21
|
|
22
22
|
s.add_dependency('bundler', '>= 1.0.0')
|
23
23
|
s.add_dependency('rack', '~> 1.4.1')
|
24
24
|
s.add_dependency('rack-mount', '~> 0.8.3')
|
25
|
-
s.add_dependency('activesupport', '~> 5.2')
|
26
|
-
s.add_dependency('liquid', '~>
|
25
|
+
s.add_dependency('activesupport', '~> 5.2.8')
|
26
|
+
s.add_dependency('liquid', '~> 5.5.1')
|
27
27
|
s.add_dependency('bigdecimal', '~> 1.4.4')
|
28
28
|
s.add_dependency('coffee-script', '~> 2.4.1')
|
29
29
|
s.add_dependency('sass', '~> 3.4.25')
|
30
|
-
s.add_dependency('sprockets', '~> 2.
|
30
|
+
s.add_dependency('sprockets', '~> 2.12')
|
31
31
|
s.add_dependency('sprockets-sass', '~> 1.3.1')
|
32
32
|
s.add_dependency('compass', '~> 1.0.3')
|
33
|
-
s.add_dependency('
|
33
|
+
s.add_dependency('sassc', '~> 2.4')
|
34
|
+
s.add_dependency('httparty', '~> 0.21.0')
|
34
35
|
s.add_dependency('better_errors', '~> 0.9.0')
|
35
36
|
s.add_dependency('will_paginate', '~> 3.0.4')
|
36
|
-
s.add_dependency('i18n', '1.
|
37
|
+
s.add_dependency('i18n', '~> 1.12')
|
37
38
|
s.add_dependency('htmlentities', '~> 4.3.1')
|
38
|
-
s.add_dependency('listen', '~> 3.
|
39
|
-
s.add_dependency('thor', '~>
|
39
|
+
s.add_dependency('listen', '~> 3.8')
|
40
|
+
s.add_dependency('thor', '~> 1.2')
|
40
41
|
s.add_dependency('rubyzip', '~> 0.9.9')
|
41
|
-
s.add_dependency('terser', '~> 1.
|
42
|
+
s.add_dependency('terser', '~> 1.2')
|
42
43
|
s.add_dependency('thin', '~> 1.8.0')
|
43
44
|
s.add_dependency('bigcartel-theme-fonts', '>= 1.0.0')
|
44
45
|
s.add_dependency('bigcartel-currency-locales', '>= 1.0.0')
|
46
|
+
s.add_dependency('multi_json', '~> 1.15.0')
|
45
47
|
|
46
48
|
s.add_development_dependency('rake', '~> 10.0.3')
|
47
49
|
s.add_development_dependency('rspec', '~> 2.14.1')
|
data/lib/dugway/cli/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'listen'
|
3
|
+
require 'rbconfig'
|
3
4
|
|
4
5
|
module Dugway
|
5
6
|
module Cli
|
@@ -40,13 +41,72 @@ module Dugway
|
|
40
41
|
|
41
42
|
Thread.new { listener.start }
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
begin
|
45
|
+
Rack::Server.start({
|
46
|
+
:config => File.join(Dir.pwd, 'config.ru'),
|
47
|
+
:environment => 'none',
|
48
|
+
:Host => options[:host],
|
49
|
+
:Port => options[:port],
|
50
|
+
:server => options[:server]
|
51
|
+
})
|
52
|
+
rescue Errno::EADDRINUSE
|
53
|
+
handle_port_in_use_error(options[:port])
|
54
|
+
exit 1
|
55
|
+
rescue Errno::EACCES
|
56
|
+
handle_permission_error(options[:port])
|
57
|
+
exit 1
|
58
|
+
rescue Errno::EADDRNOTAVAIL
|
59
|
+
handle_address_error(options[:host], options[:port])
|
60
|
+
exit 1
|
61
|
+
rescue StandardError => e
|
62
|
+
if e.message.include?("port is in use") || e.message.include?("no acceptor")
|
63
|
+
handle_port_in_use_error(options[:port])
|
64
|
+
elsif e.message.include?("requires root privileges")
|
65
|
+
handle_permission_error(options[:port])
|
66
|
+
else
|
67
|
+
puts "\nError starting server: #{e.message}"
|
68
|
+
puts "Try running with different options or check your theme configuration."
|
69
|
+
end
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def handle_port_in_use_error(port)
|
77
|
+
suggested_port = port + 1
|
78
|
+
puts "\nError: Port #{port} is already in use!"
|
79
|
+
puts "Try running with a different port:"
|
80
|
+
puts " dugway server --port #{suggested_port}"
|
81
|
+
puts " dugway server -p #{suggested_port}"
|
82
|
+
puts "\nTo see what's using port #{port}, run:"
|
83
|
+
puts " #{port_check_command(port)}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_permission_error(port)
|
87
|
+
puts "\nError: Permission denied to bind to port #{port}!"
|
88
|
+
if port < 1024
|
89
|
+
puts "Ports below 1024 require root privileges. Try a port above 1024:"
|
90
|
+
puts " dugway server --port 3000"
|
91
|
+
puts " dugway server --port 8080"
|
92
|
+
else
|
93
|
+
puts "Check your system permissions or try a different port."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle_address_error(host, port)
|
98
|
+
puts "\nError: Cannot bind to address #{host}!"
|
99
|
+
puts "Try using a different host address:"
|
100
|
+
puts " dugway server --host 0.0.0.0 --port #{port}"
|
101
|
+
puts " dugway server --host 127.0.0.1 --port #{port}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def port_check_command(port)
|
105
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
106
|
+
"netstat -ano | findstr :#{port}"
|
107
|
+
else
|
108
|
+
"lsof -i :#{port}"
|
109
|
+
end
|
50
110
|
end
|
51
111
|
end
|
52
112
|
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
module Dugway
|
2
2
|
module Drops
|
3
3
|
class ArtistsDrop < BaseDrop
|
4
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for method access
|
5
|
+
def liquid_method_missing(method)
|
6
|
+
# Try to find artist by permalink
|
7
|
+
result = before_method(method.to_s)
|
8
|
+
# Wrap hash results in ArtistDrop if it looks like an artist
|
9
|
+
if result.is_a?(Hash) && result['permalink']
|
10
|
+
ArtistDrop.new(result)
|
11
|
+
else
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
def all
|
5
17
|
@all ||= source
|
6
18
|
end
|
@@ -34,9 +34,14 @@ module Dugway
|
|
34
34
|
return source.send(method_or_key)
|
35
35
|
elsif source.respond_to?('has_key?') && source.has_key?(method_or_key)
|
36
36
|
return source[method_or_key]
|
37
|
-
elsif source.is_a?(Array) && source.first
|
37
|
+
elsif source.is_a?(Array) && source.first
|
38
|
+
# Handle both hash items and drop items in array
|
38
39
|
for item in source
|
39
|
-
|
40
|
+
if item.is_a?(Hash) && item['permalink'] == method_or_key.to_s
|
41
|
+
return item
|
42
|
+
elsif item.respond_to?(:source) && item.source.is_a?(Hash) && item.source['permalink'] == method_or_key.to_s
|
43
|
+
return item
|
44
|
+
end
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
@@ -47,6 +52,11 @@ module Dugway
|
|
47
52
|
before_method(method.to_s)
|
48
53
|
end
|
49
54
|
|
55
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for property access
|
56
|
+
def liquid_method_missing(method_name)
|
57
|
+
before_method(method_name.to_s)
|
58
|
+
end
|
59
|
+
|
50
60
|
def errors
|
51
61
|
@context['errors']
|
52
62
|
end
|
@@ -54,6 +64,21 @@ module Dugway
|
|
54
64
|
def error(msg)
|
55
65
|
errors << msg
|
56
66
|
end
|
67
|
+
|
68
|
+
# Liquid 5.x compatibility: make drops JSON-serializable
|
69
|
+
# Only return source for JSON serialization, not for filter processing
|
70
|
+
def to_liquid
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def as_json(*args)
|
75
|
+
if source.is_a?(Hash)
|
76
|
+
# Convert Ruby hash to proper JSON-compatible format
|
77
|
+
source.stringify_keys
|
78
|
+
else
|
79
|
+
{}
|
80
|
+
end
|
81
|
+
end
|
57
82
|
end
|
58
83
|
end
|
59
84
|
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
module Dugway
|
2
2
|
module Drops
|
3
3
|
class CategoriesDrop < BaseDrop
|
4
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for method access
|
5
|
+
def liquid_method_missing(method)
|
6
|
+
# Try to find category by permalink
|
7
|
+
result = before_method(method.to_s)
|
8
|
+
# Wrap hash results in CategoryDrop if it looks like a category
|
9
|
+
if result.is_a?(Hash) && result['permalink']
|
10
|
+
CategoryDrop.new(result)
|
11
|
+
else
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
def all
|
5
17
|
@all ||= source
|
6
18
|
end
|
@@ -1,12 +1,40 @@
|
|
1
1
|
module Dugway
|
2
2
|
module Drops
|
3
3
|
class PagesDrop < BaseDrop
|
4
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for method access
|
5
|
+
def liquid_method_missing(method)
|
6
|
+
# Try to find page by permalink
|
7
|
+
result = before_method(method.to_s)
|
8
|
+
# Wrap hash results in PageDrop if it looks like a page
|
9
|
+
if result.is_a?(Hash) && result['permalink']
|
10
|
+
PageDrop.new(result)
|
11
|
+
else
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
def all
|
5
|
-
@all ||= source.select { |page| page['category'] == 'custom' }
|
17
|
+
@all ||= source.select { |page| (page.source['category'] == 'custom' || page.source['category'] == 'external') && !page.source['required'] }
|
6
18
|
end
|
7
19
|
|
8
20
|
def cart
|
9
|
-
@cart ||= source.find { |page| page['permalink'] == 'cart' }
|
21
|
+
@cart ||= source.find { |page| page.source['permalink'] == 'cart' }
|
22
|
+
end
|
23
|
+
|
24
|
+
def subscribe_page
|
25
|
+
@subscribe_page ||= store.subscribe_url ? source.find { |page| page.source['url'] == store.subscribe_url } : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def custom_pages
|
29
|
+
@custom_pages ||= source.select { |page| page.source['category'] == 'custom' && !page.source['required'] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def external_pages
|
33
|
+
@external_pages ||= source.select { |page| page.source['category'] == 'external' && !page.source['required'] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def required_pages
|
37
|
+
@required_pages ||= source.select { |page| page.source['required'] == true }
|
10
38
|
end
|
11
39
|
end
|
12
40
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
module Dugway
|
2
2
|
module Drops
|
3
3
|
class ProductDrop < BaseDrop
|
4
|
+
|
5
|
+
# Liquid 5.x compatibility: ensure proper string representation
|
6
|
+
def to_s
|
7
|
+
name || "Product ##{id}"
|
8
|
+
end
|
4
9
|
def created_at
|
5
10
|
Time.parse(source['created_at'])
|
6
11
|
end
|
@@ -112,6 +117,12 @@ module Dugway
|
|
112
117
|
end
|
113
118
|
end
|
114
119
|
|
120
|
+
# Used to simulate price suffix behavior
|
121
|
+
# In prod this is a per-product setting but for dugway it needs to be global
|
122
|
+
def price_suffix
|
123
|
+
store.price_suffix
|
124
|
+
end
|
125
|
+
|
115
126
|
private
|
116
127
|
|
117
128
|
def price_min_max
|
@@ -3,6 +3,45 @@ require 'will_paginate/array'
|
|
3
3
|
module Dugway
|
4
4
|
module Drops
|
5
5
|
class ProductsDrop < BaseDrop
|
6
|
+
|
7
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for method access
|
8
|
+
def liquid_method_missing(method)
|
9
|
+
method_str = method.to_s
|
10
|
+
|
11
|
+
# Handle known methods
|
12
|
+
case method_str
|
13
|
+
when 'all'
|
14
|
+
all
|
15
|
+
when 'current'
|
16
|
+
current
|
17
|
+
when 'on_sale'
|
18
|
+
on_sale
|
19
|
+
else
|
20
|
+
# Try to find product by permalink
|
21
|
+
result = before_method(method_str)
|
22
|
+
# Wrap hash results in ProductDrop if it looks like a product
|
23
|
+
if result.is_a?(Hash) && result['permalink']
|
24
|
+
ProductDrop.new(result)
|
25
|
+
else
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Also ensure before_method works as fallback
|
32
|
+
def before_method(method_or_key)
|
33
|
+
case method_or_key.to_s
|
34
|
+
when 'all'
|
35
|
+
all
|
36
|
+
when 'current'
|
37
|
+
current
|
38
|
+
when 'on_sale'
|
39
|
+
on_sale
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
6
45
|
def all
|
7
46
|
sort_and_paginate source
|
8
47
|
end
|
@@ -8,13 +8,36 @@ module Dugway
|
|
8
8
|
@definitions = definitions || {}
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# Liquid 5.x compatibility: implement liquid_method_missing for property access
|
12
|
+
def liquid_method_missing(method)
|
13
|
+
method_str = method.to_s
|
14
|
+
method_sym = method.to_sym
|
15
|
+
|
16
|
+
# Try different key formats - source might have string or symbol keys
|
17
|
+
# Use has_key? to handle false/nil values properly
|
18
|
+
if source.has_key?(method_str)
|
19
|
+
value = source[method_str]
|
20
|
+
elsif source.has_key?(method_sym)
|
21
|
+
value = source[method_sym]
|
22
|
+
elsif source.has_key?(method_str.to_sym)
|
23
|
+
value = source[method_str.to_sym]
|
24
|
+
elsif source.has_key?(method_sym.to_s)
|
25
|
+
value = source[method_sym.to_s]
|
26
|
+
else
|
27
|
+
value = nil
|
15
28
|
end
|
16
|
-
|
17
|
-
|
29
|
+
|
30
|
+
# Check for image settings - need to wrap in ImageDrop
|
31
|
+
if !value.nil? && settings_images.find { |image| image['variable'] == method_str }
|
32
|
+
return ImageDrop.new(value.is_a?(Hash) ? value.stringify_keys : value)
|
33
|
+
end
|
34
|
+
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
38
|
+
def before_method(method_or_key)
|
39
|
+
# Fallback for older Liquid versions
|
40
|
+
liquid_method_missing(method_or_key)
|
18
41
|
end
|
19
42
|
|
20
43
|
# Newer API for theme images.
|
@@ -40,21 +40,89 @@ module Dugway
|
|
40
40
|
when 'sign' then money_with_sign(amount)
|
41
41
|
when 'code' then money_with_code(amount)
|
42
42
|
when 'sign_and_code' then money_with_sign_and_code(amount)
|
43
|
-
|
43
|
+
when 'rounded' then
|
44
|
+
begin
|
45
|
+
number_to_currency(amount.ceil, { :unit => '', :format => '%n', :precision => 0 }).to_s.strip
|
46
|
+
rescue FrozenError => e
|
47
|
+
sprintf('%.0f', amount.ceil.to_f)
|
48
|
+
end
|
49
|
+
when 'sign_rounded' then money_with_sign(amount.ceil, 0)
|
50
|
+
when 'code_rounded' then money_with_code(amount.ceil, 0)
|
51
|
+
else
|
52
|
+
begin
|
53
|
+
number_to_currency(amount, { :unit => '', :format => '%n' }).to_s.strip
|
54
|
+
rescue FrozenError => e
|
55
|
+
# Fallback for frozen hash issues in newer Ruby versions
|
56
|
+
sprintf('%.2f', amount.to_f)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def product_price(product, money_format = nil, range_format = nil)
|
62
|
+
return unless product
|
63
|
+
|
64
|
+
if product['variable_pricing']
|
65
|
+
case range_format
|
66
|
+
when 'min_only'
|
67
|
+
money(product['min_price'], money_format)
|
68
|
+
when 'max_only'
|
69
|
+
money(product['max_price'], money_format)
|
70
|
+
else # default, nil, or any other value
|
71
|
+
format_price_range(product['min_price'], product['max_price'], money_format)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
money(product['default_price'] || product['price'], money_format)
|
44
75
|
end
|
45
76
|
end
|
46
77
|
|
47
|
-
def money_with_sign(amount)
|
48
|
-
|
49
|
-
|
78
|
+
def money_with_sign(amount, precision = nil)
|
79
|
+
begin
|
80
|
+
unit = I18n.translate('number.currency.format.unit')
|
81
|
+
options = { :unit => "<span class=\"currency_sign\">#{HTMLEntities.new.encode(unit, :named)}</span>" }
|
82
|
+
options[:precision] = precision if precision
|
83
|
+
number_to_currency(amount, options)
|
84
|
+
rescue FrozenError => e
|
85
|
+
# Fallback for frozen hash issues
|
86
|
+
format_string = precision == 0 ? '%.0f' : '%.2f'
|
87
|
+
"<span class=\"currency_sign\">$</span>#{sprintf(format_string, amount.to_f)}"
|
88
|
+
end
|
50
89
|
end
|
51
90
|
|
52
|
-
def money_with_code(amount)
|
53
|
-
|
91
|
+
def money_with_code(amount, precision = nil)
|
92
|
+
begin
|
93
|
+
# Format number without currency unit, respecting locale's number formatting rules
|
94
|
+
number_locale = normalize_currency_locale_for_formatting(currency['locale'])
|
95
|
+
options = { :unit => '', :format => '%n', :locale => number_locale }
|
96
|
+
options[:precision] = precision if precision
|
97
|
+
formatted_number = number_to_currency(amount, options).to_s.strip
|
98
|
+
|
99
|
+
# Use locale-specific format string to ensure proper currency code placement
|
100
|
+
# Keep original locale for bigcartel-currency-locales gem compatibility
|
101
|
+
format_string = I18n.translate('number.currency.format.format',
|
102
|
+
:locale => currency['locale'],
|
103
|
+
:default => '%u%n')
|
104
|
+
|
105
|
+
code_span = "<span class=\"currency_code\">#{currency['code']}</span>"
|
106
|
+
format_string.gsub('%n', formatted_number).gsub('%u', code_span)
|
107
|
+
rescue => e
|
108
|
+
# Fallback for frozen hash or I18n issues
|
109
|
+
format_string = precision == 0 ? '%.0f' : '%.2f'
|
110
|
+
"<span class=\"currency_code\">#{currency['code']}</span>#{sprintf(format_string, amount.to_f)}"
|
111
|
+
end
|
54
112
|
end
|
55
113
|
|
56
114
|
def money_with_sign_and_code(amount)
|
57
|
-
%{#{ money_with_sign(amount) } <span class="currency_code">#{ currency['code'] }</span>}
|
115
|
+
%{#{ money_with_sign(amount).to_s } <span class="currency_code">#{ currency['code'] }</span>}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Liquid 5.x compatibility: ensure proper JSON output
|
119
|
+
def to_json(input)
|
120
|
+
# Handle Liquid Drops specially - use as_json for serialization
|
121
|
+
if input.respond_to?(:as_json)
|
122
|
+
JSON.generate(input.as_json)
|
123
|
+
else
|
124
|
+
JSON.generate(input)
|
125
|
+
end
|
58
126
|
end
|
59
127
|
|
60
128
|
# Shipping Filters
|
@@ -137,10 +205,104 @@ module Dugway
|
|
137
205
|
|
138
206
|
private
|
139
207
|
|
208
|
+
def currency
|
209
|
+
@context.registers[:currency]
|
210
|
+
end
|
211
|
+
|
140
212
|
def contact_tab_index
|
141
213
|
@contact_tab_index ||= 0
|
142
214
|
@contact_tab_index += 1
|
143
215
|
end
|
216
|
+
|
217
|
+
def format_price_range(min_price, max_price, money_format)
|
218
|
+
case money_format
|
219
|
+
when "sign"
|
220
|
+
format_range_with_sign(min_price, max_price)
|
221
|
+
when "code"
|
222
|
+
format_range_with_code(min_price, max_price)
|
223
|
+
when "sign_rounded"
|
224
|
+
format_range_with_sign(min_price.ceil, max_price.ceil, 0)
|
225
|
+
when "code_rounded"
|
226
|
+
format_range_with_code(min_price.ceil, max_price.ceil, 0)
|
227
|
+
when "rounded"
|
228
|
+
format_range_plain(min_price.ceil, max_price.ceil, 0)
|
229
|
+
else
|
230
|
+
format_range_plain(min_price, max_price)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def format_range_with_sign(min_price, max_price, precision = nil)
|
235
|
+
# Sign on both prices for clarity
|
236
|
+
# US: $10.00 - $20.00 | FR: 10,00 € - 20,00 €
|
237
|
+
min_formatted = money_with_sign(min_price, precision)
|
238
|
+
max_formatted = money_with_sign(max_price, precision)
|
239
|
+
"#{min_formatted} - #{max_formatted}"
|
240
|
+
end
|
241
|
+
|
242
|
+
def format_range_with_code(min_price, max_price, precision = nil)
|
243
|
+
# Single code in locale-appropriate position
|
244
|
+
# US: $10.00 - 20.00 USD | FR: 10,00 - 20,00 EUR
|
245
|
+
code_span = "<span class=\"currency_code\">#{currency['code']}</span>"
|
246
|
+
range_string = build_number_range(min_price, max_price, precision)
|
247
|
+
apply_currency_format(range_string, code_span)
|
248
|
+
end
|
249
|
+
|
250
|
+
def format_range_plain(min_price, max_price, precision = nil)
|
251
|
+
# No currency indicators
|
252
|
+
begin
|
253
|
+
options = { :unit => '', :format => '%n' }
|
254
|
+
options[:precision] = precision if precision
|
255
|
+
min_formatted = number_to_currency(min_price, options).to_s.strip
|
256
|
+
max_formatted = number_to_currency(max_price, options).to_s.strip
|
257
|
+
"#{min_formatted} - #{max_formatted}"
|
258
|
+
rescue FrozenError => e
|
259
|
+
# Fallback for frozen hash issues
|
260
|
+
format_string = precision == 0 ? '%.0f' : '%.2f'
|
261
|
+
"#{sprintf(format_string, min_price.to_f)} - #{sprintf(format_string, max_price.to_f)}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def build_number_range(min_price, max_price, precision = nil)
|
266
|
+
min_formatted = format_number_plain(min_price, precision)
|
267
|
+
max_formatted = format_number_plain(max_price, precision)
|
268
|
+
"#{min_formatted} - #{max_formatted}"
|
269
|
+
end
|
270
|
+
|
271
|
+
def format_number_plain(amount, precision = nil)
|
272
|
+
begin
|
273
|
+
number_locale = normalize_currency_locale_for_formatting(currency['locale'])
|
274
|
+
options = { :unit => '', :format => '%n', :locale => number_locale }
|
275
|
+
options[:precision] = precision if precision
|
276
|
+
number_to_currency(amount, options).to_s.strip
|
277
|
+
rescue FrozenError => e
|
278
|
+
format_string = precision == 0 ? '%.0f' : '%.2f'
|
279
|
+
sprintf(format_string, amount.to_f)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def apply_currency_format(range_string, currency_span)
|
284
|
+
# Use I18n to get locale-specific format like storefront does
|
285
|
+
begin
|
286
|
+
# Keep original locale for bigcartel-currency-locales gem compatibility
|
287
|
+
format_string = I18n.translate('number.currency.format.format',
|
288
|
+
locale: currency['locale'],
|
289
|
+
default: '%u%n')
|
290
|
+
format_string.gsub('%n', range_string).gsub('%u', currency_span)
|
291
|
+
rescue
|
292
|
+
# Fallback if I18n fails
|
293
|
+
"#{range_string} #{currency_span}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def normalize_currency_locale_for_formatting(locale)
|
298
|
+
# Handle invalid 'eu' locale from production system for number formatting only
|
299
|
+
case locale
|
300
|
+
when 'eu'
|
301
|
+
'de-DE' # Use German locale for EUR number formatting (1.200,99 style)
|
302
|
+
else
|
303
|
+
locale
|
304
|
+
end
|
305
|
+
end
|
144
306
|
end
|
145
307
|
end
|
146
308
|
end
|