74 | ```
75 |
76 | You can also use a `JSON` file (`data/collection.json`), as the following example:
77 |
78 | ```json
79 | [
80 | {
81 | "name": "Item 1",
82 | "description": "Ad aut libero. Adipisci asperiores repudiandae. Sunt expedita sunt.",
83 | "tags": ["tag1", "tag2"],
84 | "price": 99.9
85 | },
86 | {
87 | "name": "Item 2",
88 | "description": "Incidunt cupiditate rerum. Enim quo pariatur. Commodi provident dolores.",
89 | "tags": ["tag2"],
90 | "price": 149.9
91 | }
92 | ]
93 | ```
94 |
95 | #### Remote Collection
96 |
97 | You can also fetch your collection from a remote resource. To do so, you should define the `remote_collection` setting in the [configuration file](#customization):
98 |
99 | ```yaml
100 | remote_collection: https://example.com/collection.json
101 | ```
102 |
103 | #### Nested attributes
104 |
105 | Your items can have nested attributes too:
106 |
107 | ```yaml
108 | - name: Leanne Graham
109 | email: leanne_graham@example.com
110 | address:
111 | street: Kulas Light
112 | city: Gwenborough
113 | zipcode: 92998-3874
114 | geo:
115 | lat: -37.3159
116 | lng: 81.1496
117 | ```
118 |
119 | ### Magic attributes
120 |
121 | Some names help Tonic to automatically render your items (and its related filters) as beautiful as possible by default:
122 |
123 | - name (required! should be unique!)
124 | - description
125 | - category
126 | - tags (shoud be an array)
127 | - images (shoud be an array)
128 |
129 | More ✨ rules:
130 |
131 | - Automatic 🎬 video embeds from urls (YouTube, Vimeo and more).
132 | - Audio ⏯️ player for audio files.
133 | - If the attribute name ends with `_at` (`published_at`, `updated_at`, ...), it's automatically parsed as 📅 date.
134 | - The `detail_page_link` attribute makes the detail page to 🏃 jump to an external page, [see more](#detail-pages).
135 |
136 | ### Customization
137 |
138 | Configure your site via the `data/config.yaml` file. All options:
139 |
140 | ```yaml
141 | # Main settings
142 | title: My Collection
143 | description: |
144 | Welcome to my awesome collection!
145 | More information in the following section.
146 | remote_collection: https://example.com/collection.json
147 | detail_pages: true
148 |
149 | # Style/UI
150 | main_color: "#0891b2"
151 | background_color: "#e0f2fe"
152 | font_family: "Fira Sans"
153 | logo: "/images/logo.png"
154 | links:
155 | - text: About Us
156 | url: '/about'
157 | - text: Contact
158 | url: '/contact'
159 | footer_content: Follow us on Twitter and Instagram
160 | hide_filters: false
161 | hide_sorting: false
162 | hide_sharing: false
163 | sharing_platforms:
164 | - facebook
165 | - twitter
166 | - whatsapp
167 | - email
168 | item_card_image: true
169 |
170 | # Sorting
171 | sorting:
172 | default_order: "price desc"
173 | exclude:
174 | - clicks
175 |
176 | # Filters
177 | filters:
178 | type:
179 | category: text
180 | status: radio_buttons
181 | exclude:
182 | - summary
183 | - long_description
184 | ```
185 |
186 | #### Detail pages
187 |
188 | If disabled, detail pages won't be generated. Remember you can link to an external page by defining the `detail_page_link` attribute in any of the items of your collection.
189 |
190 | ```yaml
191 | - name: Awesome Product
192 | description: This product is fantastic!
193 | price: 99.9
194 | detail_page_link: https://my-shop.com/product/awesome-product
195 | ```
196 |
197 | If you only want to customize how they look, you can do it by editing the HTML template, check the [Advanced customization](#advanced-customization) section.
198 |
199 | #### Font family
200 |
201 | You can use remote fonts from [Google Fonts](https://fonts.google.com) by adding the family name in the `font_family` option:
202 |
203 | ```yaml
204 | font_family: "Fira Sans" # other examples: Lato, Roboto, Oswald, Montserrat, ...
205 | ```
206 |
207 | #### Sharing
208 |
209 | If you want to customize the available sharing options, you should use the `sharing_platforms` option passing an array.
210 |
211 | By default, the supported platforms are: Facebook, Twitter, Linkedin, Pinterest, Whatsapp, Telegram and Email.
212 |
213 | #### Sorting
214 |
215 | By default, the `name` attribute and all integer attributes are used to build the sorting options.
216 |
217 | Available options:
218 |
219 | - `default_order` By default: "name asc".
220 | - `exclude` Exclude attributes from sorting options.
221 |
222 | #### Filters
223 |
224 | Tonic analyzes your collection (especially the *1st item*) to infer the best option given the attribute type and other parameters (like number of unique values). It can be overridden in case you want a different filter type for X reasons.
225 |
226 | Available options:
227 |
228 | - `type` Forces an attribute to render a specific filter type. Available types:
229 | - `text`
230 | - `select`
231 | - `radio_buttons`
232 | - `numeric_range`
233 | - `numeric_select_range`
234 | - `date_range`
235 | - `tags`
236 | - `boolean`
237 | - `exclude` Exclude attributes from filters.
238 |
239 | ### Advanced customization
240 |
241 | If you want to fully customize your Tonic instance, you can do it by editing the files under the `source/*` folder.
242 |
243 | For example, if you want to customize the auto-generated HTML for your items, you can do it by editing these files:
244 |
245 | - Item card partial: [`source/templates/collection/_item_card.html.erb`](source/templates/collection/_item_card.html.erb)
246 | - Item detail page: [`source/templates/collection/detail_page.html.erb`](source/templates/collection/detail_page.html.erb)
247 |
248 | **NOTE** In both templates you can use the `item` object to access any attribute: `item.name`, `item.description`, etc.
249 |
250 | You can also add more pages to your Tonic site by just adding HTML templates (`*.html.erb`) under the `source/*` directory. After all, Tonic uses [Middleman](https://middlemanapp.com) under the hood, so you can even use Markdown and/or many other template engines.
251 |
252 | ## License
253 |
254 | Copyright (c) Subgin. Tonic is released under the [MIT](LICENSE) License.
255 |
--------------------------------------------------------------------------------
/bin/build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle exec middleman build --verbose
4 |
--------------------------------------------------------------------------------
/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle exec middleman server
4 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bundle install
4 | yarn install
5 |
--------------------------------------------------------------------------------
/config.rb:
--------------------------------------------------------------------------------
1 | require "lib/tonic"
2 |
3 | activate :directory_indexes
4 | activate :inline_svg
5 | activate :external_pipeline,
6 | name: :esbuild,
7 | command: build? ? "yarn build" : "yarn dev",
8 | source: "dist",
9 | latency: 1
10 |
11 | configure :development do
12 | activate :livereload
13 | end
14 |
15 | configure :build do
16 | ignore File.join(config[:js_dir], "*") # handled by External Pipeline
17 | activate :asset_hash
18 | activate :relative_assets
19 | end
20 |
21 | Tonic.start(self)
22 |
--------------------------------------------------------------------------------
/data/collection.yaml:
--------------------------------------------------------------------------------
1 | - name: Heavy Duty Leather Pants
2 | description: |
3 |
Nesciunt aut nulla. Natus repellat ab. Et perspiciatis quia. Fugit molestiae quos. Aut veniam et.
4 |
Ex est numquam. Vitae praesentium quis. Deleniti veritatis est. Reprehenderit velit illum.
5 |
Qui maxime aliquam. Tenetur magnam ipsam. Quo totam labore. Et quia sunt. Quo aut ab. Assumenda qui et.
6 | category: Transportation
7 | tags:
8 | - musician
9 | - lifeguard
10 | status: Beta
11 | price: 9.9
12 | downloads: 2000
13 | images:
14 | - https://picsum.photos/600
15 | - https://picsum.photos/600
16 | - https://picsum.photos/600
17 | - https://picsum.photos/600
18 | video: https://www.youtube.com/watch?v=ZaBXpWaA20g
19 | audio: https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3
20 | premium: false
21 | website: https://pfannerstill.biz/emory.lubowitz
22 | published_at: '2022-02-22'
23 | address:
24 | street: Kulas Light
25 | city: Gwenborough
26 | zipcode: 92998-3874
27 | geo:
28 | lat: -37.3159
29 | lng: 81.1496
30 | - name: Intelligent Granite Hat
31 | description: Et aut natus. Incidunt non autem. Magnam optio accusantium. Quis aut
32 | accusamus. Omnis ab illo. Ut repellendus cumque. Optio est error. Sed officia sint.
33 | Ut eum veritatis. Quos libero deserunt. Aut asperiores in. Aut ullam rerum. Tempora
34 | soluta illo. Qui voluptatem maiores. Consequuntur ut ut.
35 | category: Wireless
36 | tags:
37 | - doctor
38 | - librarian
39 | status: Updated
40 | price: 299.9
41 | downloads: 1500
42 | images:
43 | - https://picsum.photos/600
44 | - https://picsum.photos/600
45 | - https://picsum.photos/600
46 | - https://picsum.photos/600
47 | - https://picsum.photos/600
48 | premium: false
49 | website: https://ratke.com/domenic.runte
50 | published_at: '2022-09-12'
51 | detail_page_link: https://example.com
52 | - name: Sleek Leather Plate
53 | description: Vitae animi id. Amet a enim. Ipsa autem praesentium. Voluptatum tenetur
54 | illo. Dolor numquam harum. Blanditiis suscipit qui. A exercitationem est. Distinctio
55 | aut quia. Nihil omnis facere. Officia consequatur dolores. Facere saepe optio.
56 | Provident quis sed. Minus ut eum. Aperiam officia qui. Odio deleniti quae.
57 | category: Wireless
58 | tags:
59 | - painter
60 | status: Beta
61 | price: 750.0
62 | downloads: 120
63 | images:
64 | - https://picsum.photos/600
65 | - https://picsum.photos/600
66 | - https://picsum.photos/600
67 | - https://picsum.photos/600
68 | - https://picsum.photos/600
69 | premium: false
70 | website: https://lebsack.net/nanette
71 | published_at: '2018-05-22'
72 | - name: Aerodynamic Leather Car
73 | description: Iusto laboriosam ut. Dolore minus voluptatibus. Qui iusto ipsam. Explicabo
74 | accusantium officia. Quaerat delectus aut. Odio deleniti commodi. Veniam ut ipsam.
75 | Cum tempora ab. Nesciunt occaecati cumque. Cupiditate quasi enim. Sunt consequatur
76 | eos. Molestias impedit debitis. Commodi ea voluptas. Ut voluptas voluptates. Quos
77 | blanditiis accusamus.
78 | category: Legislative Office
79 | tags:
80 | - optician
81 | status: Beta
82 | price: 1200.0
83 | downloads: 500
84 | images:
85 | - https://picsum.photos/600
86 | premium: false
87 | website: https://nikolaus.io/armando.braun
88 | published_at: '2018-02-16'
89 | - name: Heavy Duty Marble Bottle
90 | description: Asperiores numquam tempore. Omnis ipsam voluptatem. Inventore dolorem
91 | optio. Magnam quis ea. Porro ipsa rerum. Voluptatum similique amet. Id saepe est.
92 | Unde totam ipsum. Eos alias in. Qui voluptate nobis. Error qui itaque. Officiis
93 | quae officia. Ut deserunt quia. Nihil maxime esse. Laudantium nobis quae.
94 | category: Transportation
95 | tags:
96 | - optician
97 | - librarian
98 | - philosopher
99 | status: Updated
100 | price: 200.0
101 | downloads: 250
102 | images:
103 | - https://picsum.photos/600
104 | - https://picsum.photos/600
105 | - https://picsum.photos/600
106 | - https://picsum.photos/600
107 | - https://picsum.photos/600
108 | premium: true
109 | website: https://christiansen.info/bradley.durgan
110 | published_at: '2019-05-13'
111 | - name: Ergonomic Silk Watch
112 | description: Itaque nisi inventore. Est consequatur blanditiis. Et quidem delectus. Quia
113 | corrupti delectus. Sit eos ad. Sed doloribus esse. Esse deleniti fuga. Voluptatem
114 | nihil quisquam. Delectus eum atque. Quaerat qui dolorem. Perferendis est sit. Non
115 | sint saepe. Et consequatur et. Quibusdam sit est. Harum tempora ut.
116 | category: Legislative Office
117 | tags:
118 | - receptionist
119 | - doctor
120 | status: Updated
121 | price: 299.9
122 | downloads: 900
123 | images:
124 | - https://picsum.photos/600
125 | - https://picsum.photos/600
126 | - https://picsum.photos/600
127 | - https://picsum.photos/600
128 | premium: true
129 | website: https://johnson.net/nickie
130 | published_at: '2018-12-28'
131 | - name: Lightweight Concrete Shirt
132 | description: Voluptas vitae enim. Sit est voluptatibus. Harum corporis ut. Eaque
133 | et quia. Laudantium voluptates esse. Hic in molestiae. Impedit aut aut. Aut minima
134 | quia. Sint amet et. Suscipit dolorem fuga. Quam quas officia. Aut sunt hic. Aut
135 | commodi exercitationem. Molestias in veritatis. A accusamus culpa.
136 | category: Luxury Goods & Jewelry
137 | tags:
138 | - coach
139 | - soldier
140 | - politician
141 | status: New
142 | price: 1200.0
143 | downloads: 540
144 | images:
145 | - https://picsum.photos/600
146 | - https://picsum.photos/600
147 | - https://picsum.photos/600
148 | premium: true
149 | website: https://kreiger.com/delphia_doyle
150 | published_at: '2018-09-16'
151 | - name: Aerodynamic Copper Knife
152 | description: Assumenda earum quas. Mollitia beatae voluptatem. Nisi dolor voluptate. Id
153 | nulla at. Ex sit molestiae. Vitae reiciendis occaecati. Repellat rerum saepe. Velit
154 | iste sint. Ut voluptatem id. Dolores reiciendis recusandae. Laudantium mollitia
155 | et. Minus provident nam. Et reprehenderit omnis. Consequatur deleniti aut. Qui
156 | recusandae velit.
157 | category: Wireless
158 | tags:
159 | - physicist
160 | - politician
161 | status: New
162 | price: 600.0
163 | downloads: 1800
164 | images:
165 | - https://picsum.photos/600
166 | - https://picsum.photos/600
167 | - https://picsum.photos/600
168 | premium: true
169 | website: https://krajcik.io/lyman.greenfelder
170 | published_at: '2018-07-11'
171 | - name: Awesome Iron Hat
172 | description: Temporibus quos aut. Totam commodi et. Nihil voluptas veniam. Dolorem
173 | voluptas repellat. Dicta numquam non. Facilis voluptatem officia. Enim quo culpa.
174 | Corporis eveniet eos. Sed ipsum quos. Molestias accusantium illo. Sequi dicta ut.
175 | Ea aut alias. Maiores animi enim. In dolore rem. Voluptatem nostrum voluptas.
176 | category: Wireless
177 | tags:
178 | - politician
179 | - scientist
180 | - painter
181 | status: Beta
182 | price: 99.9
183 | downloads: 1000
184 | images:
185 | - https://picsum.photos/600
186 | - https://picsum.photos/600
187 | - https://picsum.photos/600
188 | - https://picsum.photos/600
189 | premium: true
190 | website: https://wiza.name/yong
191 | published_at: '2021-03-02'
192 | - name: Durable Bronze Watch
193 | description: Quia sed neque. Quibusdam eos eaque. Cumque soluta ullam. Voluptates
194 | est sunt. Nulla nihil qui. Aut dignissimos iure. Culpa quae eius. Rem neque magnam.
195 | Est sint similique. Impedit et sed. Aut ipsam laboriosam. Ipsum impedit voluptatem. Illo
196 | ut et. Earum quaerat cumque. Non enim dolorem.
197 | category: Wireless
198 | tags:
199 | - painter
200 | - musician
201 | - scientist
202 | status: New
203 | price: 19.9
204 | downloads: 60
205 | images:
206 | - https://picsum.photos/600
207 | - https://picsum.photos/600
208 | premium: true
209 | website: https://bogan.name/erik
210 | published_at: '2020-08-01'
211 | - name: Lightweight Granite Shirt
212 | description: Molestiae veniam ducimus. Harum veniam eligendi. Inventore quis explicabo. Sit
213 | minus aut. Id minus quia. Iusto velit laudantium. Nihil aut accusamus. Corporis
214 | facere explicabo. Ut non culpa. Veritatis quo atque. Ut deleniti repellat. Quibusdam
215 | qui dolorem. Autem similique qui. Numquam eligendi magni. Id minus ut.
216 | category: Transportation
217 | tags:
218 | - philosopher
219 | - painter
220 | - soldier
221 | status: New
222 | price: 99.9
223 | downloads: 200
224 | images:
225 | - https://picsum.photos/600
226 | - https://picsum.photos/600
227 | - https://picsum.photos/600
228 | - https://picsum.photos/600
229 | premium: false
230 | website: https://stracke-feil.info/sonya
231 | published_at: '2019-09-11'
232 | - name: Gorgeous Plastic Bench
233 | description: Earum sint molestiae. Odio sed voluptatem. Maxime magni earum. Aut voluptatibus
234 | tempore. Voluptates mollitia repellendus. Deleniti dolorem excepturi. Voluptas
235 | ea voluptatibus. Cumque eaque laboriosam. Nam ut dignissimos. Sunt aut et. Ut consequatur
236 | magni. Et enim est. A voluptates ut. Nihil sed provident. Sed aliquid voluptatem.
237 | category: Transportation
238 | tags:
239 | - librarian
240 | status: New
241 | price: 30.0
242 | downloads: 20
243 | images:
244 | - https://picsum.photos/600
245 | - https://picsum.photos/600
246 | premium: false
247 | website: https://cummerata.org/luke
248 | published_at: '2021-01-16'
249 | - name: Ergonomic Leather Coat
250 | description: Nihil sed ut. Aperiam voluptatibus architecto. Velit ullam rerum. Distinctio
251 | ut dolorem. Eligendi adipisci est. Debitis consequatur vero. Nostrum rerum animi.
252 | Vel quia quo. Vero nobis excepturi. At deserunt molestiae. Quis harum repellendus.
253 | Repellendus velit aliquid. Ipsam in deserunt. Et amet iusto. Voluptas consectetur
254 | nihil.
255 | category: Transportation
256 | tags:
257 | - doctor
258 | status: Beta
259 | price: 30.0
260 | downloads: 500
261 | images:
262 | - https://picsum.photos/600
263 | - https://picsum.photos/600
264 | - https://picsum.photos/600
265 | premium: false
266 | website: https://heathcote.info/lashawn_schaefer
267 | published_at: '2019-08-09'
268 | - name: Ergonomic Iron Wallet
269 | description: Deserunt molestiae incidunt. Sit quis repellendus. Necessitatibus impedit
270 | et. Aut rerum laboriosam. Voluptas qui libero. Expedita eos dolores. Officiis ipsum
271 | qui. Quaerat ut ipsum. Optio omnis beatae. Officiis voluptatum illo. Expedita omnis
272 | vitae. Quod beatae veritatis. Nobis veritatis ratione. Ipsam sequi in. Sed eaque
273 | doloribus.
274 | category: Law Practice
275 | tags:
276 | - painter
277 | - lawyer
278 | status: New
279 | price: 300.0
280 | downloads: 400
281 | images:
282 | - https://picsum.photos/600
283 | premium: false
284 | website: https://leannon.org/jefferey
285 | published_at: '2018-02-16'
286 | - name: Sleek Iron Pants
287 | description: Eum autem est. Suscipit et sed. Error ut quis. Ex voluptatibus adipisci.
288 | Quidem recusandae sequi. Corporis quidem explicabo. Error qui voluptas. Vitae accusantium
289 | dolores. Eum non ullam. Qui et odit. Et ad dolore. Tempore exercitationem facere. Nam
290 | numquam et. Quo enim nihil. Dolor dolor suscipit.
291 | category: Accounting
292 | tags:
293 | - receptionist
294 | status: New
295 | price: 200.0
296 | downloads: 1200
297 | images:
298 | - https://picsum.photos/600
299 | premium: false
300 | website: https://kessler-mcglynn.com/josue
301 | published_at: '2019-02-14'
302 | - name: Incredible Rubber Clock
303 | description: Voluptatibus qui quaerat. Aut ea enim. Laboriosam laborum veritatis. Doloremque
304 | saepe corporis. Accusamus delectus mollitia. Beatae quasi eum. Debitis quia ut.
305 | Ab quos autem. Et voluptatibus occaecati. Explicabo laboriosam eos. Ut molestias
306 | reiciendis. Sed qui ut. Deleniti dicta hic. Repellat quos veniam. Ullam omnis voluptatem.
307 | category: Wireless
308 | tags:
309 | - coach
310 | - web-developer
311 | - politician
312 | status: New
313 | price: 19.9
314 | downloads: 90
315 | images:
316 | - https://picsum.photos/600
317 | - https://picsum.photos/600
318 | - https://picsum.photos/600
319 | - https://picsum.photos/600
320 | - https://picsum.photos/600
321 | premium: true
322 | website: https://marvin-franecki.org/crystle_mclaughlin
323 | published_at: '2018-07-12'
324 | - name: Synergistic Wooden Chair
325 | description: Voluptas dolores delectus. Atque exercitationem eius. In est consequuntur. Maiores
326 | aut veniam. Quo quibusdam vel. Rem nam animi. Ullam et eum. Voluptas harum est.
327 | Rem exercitationem quaerat. Fuga sed rerum. Et exercitationem quae. Sunt impedit
328 | qui. Dolore libero ut. Officiis rem et. Dolorem aspernatur esse.
329 | category: Wireless
330 | tags:
331 | - librarian
332 | - musician
333 | status: Updated
334 | price: 24.9
335 | downloads: 1600
336 | images:
337 | - https://picsum.photos/600
338 | - https://picsum.photos/600
339 | premium: true
340 | website: https://jaskolski.org/katharyn
341 | published_at: '2021-11-08'
342 | - name: Enormous Paper Car
343 | description: Quis sequi dignissimos. Quis illum ratione. Illum dolorem voluptates. Cupiditate
344 | qui dicta. Deleniti totam temporibus. Amet rerum suscipit. Itaque doloremque perspiciatis.
345 | Iure beatae placeat. Eos tempore et. Suscipit sint qui. Vero veniam eos. Voluptas
346 | qui dolorum. Quam voluptatem magnam. Est accusantium quas. Consequatur laboriosam
347 | iure.
348 | category: Luxury Goods & Jewelry
349 | tags:
350 | - lawyer
351 | - scientist
352 | status: Updated
353 | price: 30.0
354 | downloads: 200
355 | images:
356 | - https://picsum.photos/600
357 | - https://picsum.photos/600
358 | - https://picsum.photos/600
359 | - https://picsum.photos/600
360 | - https://picsum.photos/600
361 | premium: true
362 | website: https://schultz.info/kerry
363 | published_at: '2021-01-19'
364 | - name: Aerodynamic Wool Table
365 | description: Earum iure rerum. Sequi in est. Delectus ducimus sint. Aspernatur perspiciatis
366 | rerum. Qui et ea. Enim amet est. Dolor qui deserunt. Mollitia beatae rerum. Et
367 | numquam harum. Rerum est ipsum. Unde facere cumque. Dolor et sed. Mollitia tenetur
368 | provident. Nihil illum blanditiis. Hic numquam quis.
369 | category: Luxury Goods & Jewelry
370 | tags:
371 | - philosopher
372 | - physicist
373 | status: Updated
374 | price: 24.9
375 | downloads: 100
376 | images:
377 | - https://picsum.photos/600
378 | - https://picsum.photos/600
379 | - https://picsum.photos/600
380 | - https://picsum.photos/600
381 | - https://picsum.photos/600
382 | premium: false
383 | website: https://gottlieb.name/dewayne
384 | published_at: '2022-07-30'
385 | - name: Sleek Iron Hat
386 | description: Rerum possimus vitae. Aspernatur saepe qui. Harum rem voluptas. Inventore
387 | debitis quo. Velit ex quaerat. In rerum animi. Repellendus pariatur culpa. Odit
388 | voluptatem et. Eius distinctio impedit. Id in amet. Officiis nisi deserunt. Ex
389 | quae quasi. Atque et quia. Voluptates et voluptate. Itaque soluta excepturi.
390 | category: Luxury Goods & Jewelry
391 | tags:
392 | - painter
393 | status: New
394 | price: 300.0
395 | downloads: 220
396 | images:
397 | - https://picsum.photos/600
398 | premium: false
399 | website: https://gutmann.info/charley
400 | published_at: '2020-08-22'
401 |
--------------------------------------------------------------------------------
/data/config.yaml:
--------------------------------------------------------------------------------
1 | # Main settings
2 | title: My Collection
3 | description: This is an example of a Tonic project.
4 |
5 | # Style/UI
6 | main_color: "#6366f1"
7 | background_color: "#e2e8f0"
8 | font_family: "Lato"
9 | links:
10 | - text: About Us
11 | url: "#"
12 | - text: Contact
13 | url: "#"
14 |
15 | # Sorting
16 | sorting:
17 | default_order: "price asc"
18 |
19 | # Filters
20 | filters:
21 | exclude:
22 | - video
23 | - audio
24 |
--------------------------------------------------------------------------------
/lib/tonic.rb:
--------------------------------------------------------------------------------
1 | require "yaml"
2 | require "open-uri"
3 |
4 | require_relative "tonic/utils"
5 | require_relative "tonic/helpers"
6 | require_relative "tonic/filters"
7 |
8 | module Tonic
9 | VERSION = "0.18.0"
10 | REPO = "https://github.com/Subgin/tonic"
11 | MAGIC_ATTRS = %w(name description images category tags id dom_id detail_page_link)
12 | SKIP_FOR_FILTERS = MAGIC_ATTRS - %w(category tags)
13 | DEFAULT_COLOR = "#3e76d1"
14 | DEFAULT_BG_COLOR = "#f3f4f6"
15 | DEFAULT_ORDER = "name asc"
16 | SHARING_PLATFORMS = %w(facebook twitter linkedin pinterest whatsapp telegram email)
17 |
18 | def self.start(context)
19 | # Inject helpers
20 | context.helpers Tonic::Utils, Tonic::Helpers, Tonic::Filters
21 |
22 | # Fetch remote collection if any
23 | if collection_url = raw_config["remote_collection"]
24 | remote_collection = URI.open(collection_url).read rescue nil
25 | File.write("data/collection.yaml", remote_collection) if remote_collection
26 | end
27 |
28 | # Create a detail page for each item if enabled
29 | if raw_config.fetch("detail_pages", true)
30 | context.data.collection.each do |item|
31 | context.proxy "/#{Tonic::Utils.slugify(item.name)}.html", "/templates/collection/detail_page.html", locals: { item: item }
32 | end
33 | end
34 |
35 | # Do not build detail page template
36 | context.ignore "/templates/collection/detail_page.html"
37 | end
38 |
39 | private
40 |
41 | def self.raw_config
42 | @raw_config ||= YAML.load_file("data/config.yaml")
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/tonic/filters.rb:
--------------------------------------------------------------------------------
1 | module Tonic
2 | module Filters
3 | def render_filters
4 | attributes = tonic_collection.flat_map(&:keys).uniq.sort
5 |
6 | attributes.map do |attribute|
7 | next if Tonic::SKIP_FOR_FILTERS.include?(attribute)
8 | next if config.filters&.exclude&.include?(attribute)
9 |
10 | content_tag(:div, class: "px-6 py-3 border-b border-gray-500 w-full") do
11 | type = config.filters&.type&.dig(attribute)
12 | smart_filter(attribute, type)
13 | end
14 | end.compact.join
15 | end
16 |
17 | private
18 |
19 | def smart_filter(attribute, type = nil)
20 | # Take sample value from 1st element
21 | value = tonic_collection[0][attribute]
22 |
23 | if !type
24 | type = "tags" if value.is_a?(Array)
25 | type = "boolean" if is_bool?(value)
26 | type = "text" if is_hash?(value)
27 | type = smart_numeric_filter(attribute) if value.is_a?(Numeric)
28 | type = smart_text_filter(attribute, value) if value.is_a?(String)
29 | end
30 |
31 | send("#{type}_filter", attribute)
32 | end
33 |
34 | def smart_numeric_filter(attribute)
35 | uniq_values = fetch_values(attribute).size
36 |
37 | if uniq_values <= 5
38 | "radio_buttons"
39 | elsif uniq_values <= 15
40 | "numeric_select_range"
41 | else
42 | "numeric_range"
43 | end
44 | end
45 |
46 | def smart_text_filter(attribute, value)
47 | if attribute == "category"
48 | "select"
49 | elsif attribute.end_with?("_at") && is_date?(value)
50 | "date_range"
51 | elsif single_word?(value) && !is_url?(value) && !is_email?(value)
52 | uniq_values = fetch_values(attribute).size
53 | uniq_values <= 5 ? "radio_buttons" : "select"
54 | else
55 | "text"
56 | end
57 | end
58 |
59 | def label(attribute)
60 | content_tag(:label, attribute.humanize, class: "text-white py-1")
61 | end
62 |
63 | def text_filter(attribute)
64 | partial("templates/filters/text", locals: { attribute: attribute })
65 | end
66 |
67 | def numeric_range_filter(attribute)
68 | range = fetch_values(attribute)
69 | min, max = range.minmax
70 |
71 | partial("templates/filters/numeric_range", locals: { attribute: attribute, min: min, max: max })
72 | end
73 |
74 | def numeric_select_range_filter(attribute)
75 | options = fetch_values(attribute)
76 | min, max = options.minmax
77 | options = ["All"] + options.sort
78 |
79 | partial("templates/filters/numeric_select_range", locals: { attribute: attribute, options: options, min: min, max: max })
80 | end
81 |
82 | def date_range_filter(attribute)
83 | range = fetch_values(attribute)
84 | min, max = range.minmax
85 |
86 | partial("templates/filters/date_range", locals: { attribute: attribute, min: min, max: max })
87 | end
88 |
89 | def tags_filter(attribute)
90 | tags = fetch_values(attribute).sort
91 |
92 | partial("templates/filters/tags", locals: { attribute: attribute, tags: tags })
93 | end
94 |
95 | def select_filter(attribute)
96 | options = fetch_values(attribute)
97 | options = ["All"] + options.sort
98 |
99 | partial("templates/filters/select", locals: { attribute: attribute, options: options })
100 | end
101 |
102 | def boolean_filter(attribute)
103 | partial("templates/filters/boolean", locals: { attribute: attribute })
104 | end
105 |
106 | def radio_buttons_filter(attribute)
107 | options = fetch_values(attribute)
108 | options = ["All"] + options.sort
109 |
110 | partial("templates/filters/radio_buttons", locals: { attribute: attribute, options: options })
111 | end
112 |
113 | def fetch_values(attribute)
114 | tonic_collection.flat_map(&:"#{attribute}").compact.uniq
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/lib/tonic/helpers.rb:
--------------------------------------------------------------------------------
1 | module Tonic
2 | module Helpers
3 | def config
4 | data.config.reverse_merge(
5 | title: "Tonic Example",
6 | detail_pages: true,
7 | item_card_image: true,
8 | sorting: { default_order: Tonic::DEFAULT_ORDER }
9 | )
10 | end
11 |
12 | def tonic_collection
13 | data.collection.each do |item|
14 | item.id = slugify(item.name)
15 | item.dom_id = "item_#{item.id}"
16 |
17 | validate_item!(item)
18 | end
19 | end
20 |
21 | def rest_of_attrs(item)
22 | (item.keys - Tonic::MAGIC_ATTRS).sort
23 | end
24 |
25 | def detail_page_url(item)
26 | if item.detail_page_link.present?
27 | item.detail_page_link
28 | elsif config.detail_pages
29 | "/#{item.id}"
30 | end
31 | end
32 |
33 | def sorting_options
34 | options = tonic_collection[0].select do |k, v|
35 | k == "name" ||
36 | v.is_a?(Numeric) ||
37 | (v.is_a?(String) && k.end_with?("_at") && is_date?(v))
38 | end.keys
39 |
40 | if exclude = config.sorting.exclude
41 | options = options - exclude
42 | end
43 |
44 | options.flat_map do |option|
45 | ["#{option} asc", "#{option} desc"]
46 | end.sort
47 | end
48 |
49 | def sort_link(option)
50 | attribute, direction = option.split(" ")
51 |
52 | link_to "#{attribute.humanize} #{direction.upcase}", "#", onclick: "sortBy('#{option}')", data: { sort_by: option }
53 | end
54 |
55 | def sharing_platforms
56 | return Tonic::SHARING_PLATFORMS if !config.sharing_platforms
57 |
58 | Tonic::SHARING_PLATFORMS.select do |platform|
59 | config.sharing_platforms.include?(platform)
60 | end
61 | end
62 |
63 | private
64 |
65 | def validate_item!(item)
66 | if item.name.blank?
67 | raise "[Tonic] Name can't be blank:\n#{item.to_h}\n"
68 | end
69 |
70 | if data.collection.count { |el| el.name == item.name } > 1
71 | raise "[Tonic] Name should be unique:\n#{item.to_h}\n"
72 | end
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/tonic/utils.rb:
--------------------------------------------------------------------------------
1 | module Tonic
2 | module Utils
3 | extend self
4 |
5 | def slugify(text)
6 | text&.parameterize
7 | end
8 |
9 | def strip_truncate(html, length)
10 | truncate(strip_tags(html), length: length)
11 | end
12 |
13 | def single_word?(string)
14 | !string.strip.include? " "
15 | end
16 |
17 | def render_tags(tags)
18 | return if !tags
19 |
20 | tags.sort.map do |tag|
21 | "#{tag}"
22 | end.join(" ")
23 | end
24 |
25 | def render_hash(hash)
26 | hash.map do |k, v|
27 | if is_hash?(v)
28 | render_hash(v)
29 | else
30 | "#{k.titleize}: #{v}"
31 | end
32 | end.join(" | ")
33 | end
34 |
35 | def render_video(video_url)
36 | embed_url = VideoInfo.new(video_url).embed_url
37 |
38 | ""
39 | end
40 |
41 | def render_audio(audio_url)
42 | ""
43 | end
44 |
45 | def is_bool?(value)
46 | value.is_a?(TrueClass) || value.is_a?(FalseClass)
47 | end
48 |
49 | def is_date?(value)
50 | Date.parse(value)
51 | rescue Date::Error
52 | false
53 | end
54 |
55 | def is_url?(string)
56 | string.match?(URI.regexp)
57 | end
58 |
59 | def is_email?(string)
60 | string.match?(URI::MailTo::EMAIL_REGEXP)
61 | end
62 |
63 | def is_hash?(object)
64 | object.class.name.end_with?("Hash")
65 | end
66 |
67 | def is_video?(string)
68 | VideoInfo.valid_url?(string)
69 | end
70 |
71 | def is_audio?(string)
72 | string.match?(/\.(mp3|ogg|wav)$/)
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tonic",
3 | "private": true,
4 | "scripts": {
5 | "dev": "yarn dev:js & yarn dev:css",
6 | "build": "yarn build:js & yarn build:css",
7 | "dev:js": "esbuild ./source/javascripts/application.js --bundle --watch=forever --outdir=dist",
8 | "build:js": "esbuild ./source/javascripts/application.js --bundle --minify --outdir=dist",
9 | "dev:css": "tailwindcss -i ./source/stylesheets/application.css -o ./dist/application.css --watch",
10 | "build:css": "tailwindcss -i ./source/stylesheets/application.css -o ./dist/application.css --minify"
11 | },
12 | "dependencies": {
13 | "@tailwindcss/forms": "^0.5.7",
14 | "esbuild": "^0.25.0",
15 | "ralix": "^1.4.0",
16 | "sharer.js": "^0.5.1",
17 | "tailwindcss": "^3.3.6"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/source/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Subgin/tonic/816489d22f91bb48b07365e06f4171bae6f1dae4/source/images/favicon.ico
--------------------------------------------------------------------------------
/source/images/icons/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/source/images/icons/chevron-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/source/images/icons/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/source/images/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/share.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/email.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/facebook.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/linkedin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/pinterest.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/telegram.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/twitter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/images/icons/sharing/whatsapp.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/index.html.erb:
--------------------------------------------------------------------------------
1 | ---
2 | title: Home
3 | ---
4 |
5 |