shimmer 0.0.4 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +172 -14
- data/lib/shimmer/utils/localizable.rb +25 -24
- data/lib/shimmer/utils/remote_navigation.rb +26 -7
- data/lib/shimmer/version.rb +1 -1
- data/package.json +3 -1
- data/rollup.config.js +1 -1
- data/src/controllers/remote-navigation.ts +9 -0
- data/src/index.ts +27 -12
- data/src/modal.ts +1 -28
- data/src/popover.ts +89 -0
- data/src/util.ts +31 -0
- data/yarn.lock +10 -0
- metadata +9 -7
- data/shimmer.gemspec +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 875686b1fc81225c69cbbaedbf38327709de9233c784ee72c32ca2eff2c8fe5d
|
4
|
+
data.tar.gz: 324f34718ecca0e2b0d1a88c7f46c59532686b4ad149cdefa3a9098ed997a33f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d640f8e34aef1dd6bbf18e792c6c34ecd3c10a11559f01dcda1877260ae7447d62a8207aee94951028a87000cc3288502ccc2a6ade8037150cefe0132972541
|
7
|
+
data.tar.gz: fcf2e88cccb9ee701051358858f6c9c9e8f9978bab1d723571cb0a58eb9d937dde684c068827d6dec98ab2169a4dbf2a29cb9d26ce9476d5a2e6803f98c42cc3
|
data/README.md
CHANGED
@@ -1,38 +1,196 @@
|
|
1
|
-
# Shimmer
|
1
|
+
# Shimmer - Because Ruby could be more shiny!
|
2
2
|
|
3
|
-
|
3
|
+
Shimmer is a collection of Rails extensions that bring advanced UI features into your app and make your life easier as a developer.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
|
7
|
+
### Static File Serving
|
8
|
+
|
9
|
+
`ActiveStorage` is great, but serving of files, especially behind a CDN, can be complicated to get right. This can be fixed easily:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# use an image tag
|
13
|
+
image_tag user.avatar, width: 300
|
14
|
+
```
|
15
|
+
|
16
|
+
This extension overrides `image_tag` and also supplies a matching `image_file_url` that automatically resizes your image and creates a static, cacheable url.
|
17
|
+
|
18
|
+
### Modals
|
19
|
+
|
20
|
+
Modals are the designer's best friend, but developers usually hate them for their complexity. Fear no more: Shimmer has you covered.
|
21
|
+
|
22
|
+
```slim
|
23
|
+
a href=modal_path(new_post_path) Create a new Post
|
24
|
+
```
|
25
|
+
|
26
|
+
This will open a modal on click and then asynchronously request the modal content from the controller. Modals can also be controlled via JavaScript via the global `ui` variable:
|
27
|
+
|
28
|
+
```js
|
29
|
+
ui.modal.open({ url: "/posts/new" });
|
30
|
+
ui.modal.close();
|
31
|
+
```
|
32
|
+
|
33
|
+
### Popovers
|
34
|
+
|
35
|
+
When modals are annoying to implement, popovers are even worse. Thankfully, Shimmer comes to the rescue:
|
36
|
+
|
37
|
+
```slim
|
38
|
+
a href=popover_path(new_post_path, placement: :left)
|
39
|
+
```
|
40
|
+
|
41
|
+
This will request `new_post_path` and display it left of the anchor thanks to PopperJS.
|
42
|
+
|
43
|
+
### Remote Navigation
|
44
|
+
|
45
|
+
Remote navigation takes Hotwire to the next level with built-in navigation actions, integrated with modals and popovers.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
def create
|
49
|
+
@post = current_user.posts.create! post_params
|
50
|
+
ui.navigate_to @post
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
This will automatically close the current modal or popover and navigate via Turbo Drive to the post's url - no redirects necessary.
|
55
|
+
|
56
|
+
The `ui` helper comes with several built-in functions:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# run any kind of javascript upon request completion
|
60
|
+
ui.run_javascript("alert('hello world')")
|
61
|
+
|
62
|
+
# open or replace a modal's content (dependent on its ID)
|
63
|
+
ui.open_modal(new_post_path, size: :small)
|
64
|
+
|
65
|
+
# close an open modal
|
66
|
+
ui.close_modal
|
67
|
+
|
68
|
+
# same methods also available for popovers
|
69
|
+
ui.open_popover(new_post_path, selector: "#user-profile", placement: :left)
|
70
|
+
ui.close_popover
|
71
|
+
|
72
|
+
# navigate via Turbo Drive
|
73
|
+
ui.navigate_to(@post)
|
74
|
+
|
75
|
+
# manipulate the page's content
|
76
|
+
ui.append(@post, with: "comments/comment", comment: @comment)
|
77
|
+
ui.prepend("#user-profile", with: "users/extra")
|
78
|
+
ui.replace(@post)
|
79
|
+
ui.remove(@post)
|
80
|
+
```
|
81
|
+
|
82
|
+
### Sitemaps
|
83
|
+
|
84
|
+
Want to implement sitemaps, but the ephemeral filesystem of Heroku hates you? Here's a simple way to upload sitemaps:
|
85
|
+
|
86
|
+
- install the sitemap gem and configure the `sitemap.rb` as usual
|
87
|
+
- use the shimmer adapter to automatically upload your sitemap to your configured ActiveStorage adapter
|
88
|
+
- use the shimmer controller to display the sitemap in your app
|
89
|
+
- (optional) tell sidekiq scheduler to regularly update your sitemap
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# sitemap.rb
|
93
|
+
SitemapGenerator::Sitemap.adapter = Shimmer::SitemapAdapter.new
|
94
|
+
|
95
|
+
# routes.rb
|
96
|
+
get "sitemaps/*path", to: "shimmer/sitemaps#show"
|
97
|
+
|
98
|
+
# sidekiq.yml
|
99
|
+
:schedule:
|
100
|
+
sitemap:
|
101
|
+
cron: '0 0 12 * * * Europe/Berlin' # every day at 16:00, Berlin time
|
102
|
+
class: Shimmer::SitemapJob
|
103
|
+
```
|
104
|
+
|
105
|
+
### Cloudflare Support
|
106
|
+
|
107
|
+
As you might have noticed, Cloudflare SSL will cause some issues with your Rails app if you're not using SSL strict mode (https://github.com/rails/rails/issues/22965). If you can't switch to strict mode, go for the standard flexible mode instead and add this middleware to your stack:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# application.rb
|
111
|
+
config.middleware.use Shimmer::CloudflareProxy
|
112
|
+
```
|
113
|
+
|
114
|
+
### Heroku Database Helpers
|
115
|
+
|
116
|
+
Can't reproduce an issue with your local test data and just want the production or staging data on your development machine? Here you go:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
rails db:pull
|
120
|
+
```
|
121
|
+
|
122
|
+
This will drop your local database and pull in the database of your connected Heroku app (make sure you executed `heroku git:remote -a your_app` before to have the git remote). But what about assets you might ask? Easy - assets are pulled from S3 as well via the AWS CLI automatically (make sure your environment variables in Heroku are correctly named as `AWS_REGION`, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) and the database is updated to use your local filesystem instead.
|
123
|
+
|
124
|
+
If you don't want the asset support, you can also only pull the database or only the assets:
|
125
|
+
|
126
|
+
```bash
|
127
|
+
rails db:pull_data
|
128
|
+
rails db:pull_assets
|
129
|
+
```
|
130
|
+
|
131
|
+
### Localizable Routes with Browser Locale Support
|
132
|
+
|
133
|
+
To localize a page via urls, this will help you tremendously.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# routes.rb
|
137
|
+
Rails.application.routes.draw do
|
138
|
+
scope "/(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
|
139
|
+
get "login", to: "sessions#new"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
From now on you can prefix your routes with a locale like `/de/login` or `/en/login` and `I18n.locale` will automatically be set. If there is no locale in the url (it's optional), this will automatically use the browser's locale.
|
145
|
+
|
146
|
+
You want to redirect from unlocalized paths? Add a before action to your controller:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
before_action :check_locale
|
150
|
+
```
|
151
|
+
|
152
|
+
Trying to figure out which key a certain translation on the page has? Append `?debug` to the url and `I18n.debug?` will be set - which leads to keys being printed on the page.
|
6
153
|
|
7
154
|
## Installation
|
8
155
|
|
9
156
|
Add this line to your application's Gemfile:
|
10
157
|
|
11
158
|
```ruby
|
12
|
-
gem
|
159
|
+
gem "shimmer"
|
13
160
|
```
|
14
161
|
|
15
162
|
And then execute:
|
16
163
|
|
17
|
-
|
164
|
+
```bash
|
165
|
+
$ bundle install
|
166
|
+
```
|
18
167
|
|
19
|
-
|
168
|
+
Add some configuration to your project:
|
20
169
|
|
21
|
-
|
170
|
+
```ruby
|
171
|
+
# routes.rb
|
22
172
|
|
23
|
-
|
173
|
+
resources :files, only: :show, controller: "shimmer/files"
|
24
174
|
|
25
|
-
|
175
|
+
# application_controller.rb
|
176
|
+
class ApplicationController < ActionController::Base
|
177
|
+
include Shimmer::Localizable
|
178
|
+
include Shimmer::RemoteNavigation
|
179
|
+
end
|
180
|
+
```
|
26
181
|
|
27
|
-
|
182
|
+
```ts
|
183
|
+
// application.ts
|
28
184
|
|
29
|
-
|
185
|
+
import { start } from "@nerdgeschoss/shimmer";
|
186
|
+
import { application } from "controllers/application";
|
30
187
|
|
31
|
-
|
188
|
+
start({ application });
|
189
|
+
```
|
32
190
|
|
33
191
|
## Contributing
|
34
192
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
193
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nerdgeschoss/shimmer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nerdgeschoss/shimmer/blob/master/CODE_OF_CONDUCT.md).
|
36
194
|
|
37
195
|
## License
|
38
196
|
|
@@ -40,4 +198,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
198
|
|
41
199
|
## Code of Conduct
|
42
200
|
|
43
|
-
Everyone interacting in the Shimmer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
201
|
+
Everyone interacting in the Shimmer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nerdgeschoss/shimmer/blob/master/CODE_OF_CONDUCT.md).
|
@@ -29,31 +29,32 @@ module Shimmer
|
|
29
29
|
def url_locale
|
30
30
|
params[:locale]
|
31
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
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
33
|
+
I18n.class_eval do
|
34
|
+
next if defined? debug
|
35
|
+
|
36
|
+
thread_mattr_accessor :debug
|
37
|
+
|
38
|
+
class << self
|
39
|
+
alias_method :old_translate, :translate
|
40
|
+
def translate(key, options = {})
|
41
|
+
untranslated_scopes = ["number", "transliterate", "date", "datetime", "errors", "helpers", "support", "time", "faker"].map { |k| "#{k}." }
|
42
|
+
key = key.to_s.downcase
|
43
|
+
untranslated = untranslated_scopes.any? { |e| key.include? e }
|
44
|
+
key_name = [options[:scope], key].flatten.compact.join(".")
|
45
|
+
option_names = options.except(:count, :default, :raise, :scope).map { |k, v| "#{k}=#{v}" }.join(", ")
|
46
|
+
return "#{key_name} #{option_names}" if I18n.debug && !untranslated
|
47
|
+
|
48
|
+
options.reverse_merge!(default: old_translate(key, **options.merge(locale: :de))) if untranslated
|
49
|
+
old_translate(key, **options)
|
50
|
+
end
|
51
|
+
alias_method :t, :translate
|
52
|
+
|
53
|
+
def debug?
|
54
|
+
debug
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
57
58
|
end
|
58
59
|
end
|
59
60
|
end
|
@@ -6,7 +6,7 @@ module Shimmer
|
|
6
6
|
|
7
7
|
included do
|
8
8
|
def ui
|
9
|
-
@ui ||= RemoteNavigator.new
|
9
|
+
@ui ||= RemoteNavigator.new(self)
|
10
10
|
end
|
11
11
|
|
12
12
|
def default_render
|
@@ -17,7 +17,7 @@ module Shimmer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
helper_method :modal_path
|
20
|
-
def modal_path(url, id: nil, size: nil, close:
|
20
|
+
def modal_path(url, id: nil, size: nil, close: true)
|
21
21
|
"javascript:ui.modal.open(#{{url: url, id: id, size: size, close: close}.to_json})"
|
22
22
|
end
|
23
23
|
|
@@ -26,6 +26,11 @@ module Shimmer
|
|
26
26
|
"javascript:ui.modal.close(#{{id: id}.to_json})"
|
27
27
|
end
|
28
28
|
|
29
|
+
helper_method :popover_path
|
30
|
+
def popover_path(url, id: nil, selector: nil, placement: nil)
|
31
|
+
"javascript:ui.popover.open(#{{url: url, id: id, selector: selector, placement: placement}.compact.to_json})"
|
32
|
+
end
|
33
|
+
|
29
34
|
def shimmer_request?
|
30
35
|
request.headers["X-Shimmer"].present?
|
31
36
|
end
|
@@ -42,10 +47,10 @@ module Shimmer
|
|
42
47
|
end
|
43
48
|
|
44
49
|
class RemoteNavigator
|
45
|
-
|
50
|
+
delegate :polymorphic_path, to: :@controller
|
46
51
|
|
47
|
-
def initialize(
|
48
|
-
@
|
52
|
+
def initialize(controller)
|
53
|
+
@controller = controller
|
49
54
|
end
|
50
55
|
|
51
56
|
def queued_updates
|
@@ -76,18 +81,32 @@ module Shimmer
|
|
76
81
|
queued_updates.push turbo_stream.remove(id)
|
77
82
|
end
|
78
83
|
|
79
|
-
def open_modal(path)
|
80
|
-
run_javascript "ui.modal.open(
|
84
|
+
def open_modal(path, id: nil, size: nil, close: true)
|
85
|
+
run_javascript "ui.modal.open(#{{url: url, id: id, size: size, close: close}.to_json})"
|
81
86
|
end
|
82
87
|
|
83
88
|
def close_modal
|
84
89
|
run_javascript "ui.modal.close()"
|
85
90
|
end
|
86
91
|
|
92
|
+
def open_popover(path, selector:, placement: nil)
|
93
|
+
run_javascript "ui.popover.open(#{{url: url, selector: selector, placement: placement}.to_json})"
|
94
|
+
end
|
95
|
+
|
96
|
+
def close_popover
|
97
|
+
run_javascript "ui.popover.close()"
|
98
|
+
end
|
99
|
+
|
87
100
|
def navigate_to(path)
|
88
101
|
close_modal
|
89
102
|
path = polymorphic_path(path) unless path.is_a?(String)
|
90
103
|
run_javascript "Turbo.visit('#{path}')"
|
91
104
|
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def turbo_stream
|
109
|
+
@controller.send(:turbo_stream)
|
110
|
+
end
|
92
111
|
end
|
93
112
|
end
|
data/lib/shimmer/version.rb
CHANGED
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@nerdgeschoss/shimmer",
|
3
|
-
"version": "0.0
|
3
|
+
"version": "4.0.0",
|
4
4
|
"description": "Simple application development in Rails",
|
5
5
|
"main": "dist/index.cjs.js",
|
6
6
|
"module": "dist/index.esm.js",
|
@@ -22,6 +22,8 @@
|
|
22
22
|
],
|
23
23
|
"license": "MIT",
|
24
24
|
"dependencies": {
|
25
|
+
"@hotwired/stimulus": "^3.0.1",
|
26
|
+
"@popperjs/core": "^2.11.0",
|
25
27
|
"@rails/request.js": "^0.0.6"
|
26
28
|
},
|
27
29
|
"devDependencies": {
|
data/rollup.config.js
CHANGED
data/src/index.ts
CHANGED
@@ -1,17 +1,32 @@
|
|
1
|
-
|
2
|
-
interface Window {
|
3
|
-
ui: typeof ui;
|
4
|
-
}
|
5
|
-
}
|
6
|
-
|
1
|
+
import type { Application } from "@hotwired/stimulus";
|
7
2
|
import { ModalPresenter } from "./modal";
|
3
|
+
import { PopoverPresenter } from "./popover";
|
4
|
+
import RemoteNavigationController from "./controllers/remote-navigation";
|
8
5
|
import "./touch";
|
9
6
|
|
10
|
-
export const ui = {
|
11
|
-
modal: new ModalPresenter(),
|
12
|
-
};
|
13
|
-
|
14
|
-
window.ui = ui;
|
15
|
-
|
16
7
|
export { registerServiceWorker } from "./serviceworker";
|
17
8
|
export { currentLocale } from "./locale";
|
9
|
+
|
10
|
+
declare global {
|
11
|
+
interface Window {
|
12
|
+
ui?: {
|
13
|
+
modal: ModalPresenter;
|
14
|
+
popover: PopoverPresenter;
|
15
|
+
};
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
export async function start({
|
20
|
+
application,
|
21
|
+
}: {
|
22
|
+
application: Application;
|
23
|
+
}): Promise<void> {
|
24
|
+
const root = document.createElement("div");
|
25
|
+
root.id = "shimmer";
|
26
|
+
document.body.append(root);
|
27
|
+
application.register("remote-navigation", RemoteNavigationController);
|
28
|
+
window.ui = {
|
29
|
+
modal: new ModalPresenter(),
|
30
|
+
popover: new PopoverPresenter(),
|
31
|
+
};
|
32
|
+
}
|
data/src/modal.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { loaded, createElement, nextFrame, getHTML } from "./util";
|
2
2
|
|
3
3
|
export interface ModalOptions {
|
4
4
|
id?: string;
|
@@ -7,33 +7,6 @@ export interface ModalOptions {
|
|
7
7
|
close?: boolean;
|
8
8
|
}
|
9
9
|
|
10
|
-
const loaded: Promise<void> = new Promise((res) => {
|
11
|
-
document.addEventListener("DOMContentLoaded", () => {
|
12
|
-
res();
|
13
|
-
});
|
14
|
-
});
|
15
|
-
|
16
|
-
async function nextFrame(): Promise<void> {
|
17
|
-
return new Promise((res) => {
|
18
|
-
setTimeout(res, 10);
|
19
|
-
});
|
20
|
-
}
|
21
|
-
|
22
|
-
async function getHTML(url: string): Promise<string> {
|
23
|
-
const response = await get(url, { headers: { "X-Shimmer": "true" } });
|
24
|
-
if (response.ok) {
|
25
|
-
return await response.response.text();
|
26
|
-
}
|
27
|
-
return "";
|
28
|
-
}
|
29
|
-
|
30
|
-
function createElement(parent: HTMLElement, className: string): HTMLDivElement {
|
31
|
-
const element = document.createElement("div");
|
32
|
-
element.className = className;
|
33
|
-
parent.append(element);
|
34
|
-
return element;
|
35
|
-
}
|
36
|
-
|
37
10
|
export class ModalPresenter {
|
38
11
|
private modals: Record<string, Modal> = {};
|
39
12
|
|
data/src/popover.ts
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
import { Instance as Popper, createPopper, Placement } from "@popperjs/core";
|
2
|
+
import { createElement, getHTML } from "./util";
|
3
|
+
|
4
|
+
export interface PopoverOptions {
|
5
|
+
id?: string;
|
6
|
+
url: string;
|
7
|
+
selector?: HTMLElement | string;
|
8
|
+
placement?: Placement;
|
9
|
+
}
|
10
|
+
|
11
|
+
export class PopoverPresenter {
|
12
|
+
private popovers: Record<string, Popover> = {};
|
13
|
+
private lastClickedElement?: HTMLElement;
|
14
|
+
|
15
|
+
constructor() {
|
16
|
+
document.addEventListener("click", this.trackElement);
|
17
|
+
}
|
18
|
+
|
19
|
+
async open(options: PopoverOptions): Promise<void> {
|
20
|
+
const id = (options.id = options.id ?? "default-popover");
|
21
|
+
options.selector = options.selector ?? this.lastClickedElement;
|
22
|
+
(this.popovers[id] = new Popover()).open(options);
|
23
|
+
}
|
24
|
+
|
25
|
+
async close({ id }: { id?: string } = {}): Promise<void> {
|
26
|
+
let promise: Promise<unknown> | null = null;
|
27
|
+
if (id) {
|
28
|
+
promise = this.popovers[id]?.close();
|
29
|
+
delete this.popovers[id];
|
30
|
+
} else {
|
31
|
+
promise = Promise.all(Object.values(this.popovers).map((e) => e.close()));
|
32
|
+
this.popovers = {};
|
33
|
+
}
|
34
|
+
await promise;
|
35
|
+
}
|
36
|
+
|
37
|
+
private trackElement = (event: MouseEvent): void => {
|
38
|
+
this.lastClickedElement = event.target as HTMLElement;
|
39
|
+
};
|
40
|
+
}
|
41
|
+
|
42
|
+
export class Popover {
|
43
|
+
private popper?: Popper;
|
44
|
+
private popoverDiv?: HTMLDivElement;
|
45
|
+
|
46
|
+
async open({ url, selector, placement }: PopoverOptions): Promise<void> {
|
47
|
+
const root =
|
48
|
+
typeof selector === "string"
|
49
|
+
? document.querySelector(selector)
|
50
|
+
: selector;
|
51
|
+
if (!root) {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
const popoverDiv = createElement(document.body, "popover");
|
55
|
+
const arrow = createElement(popoverDiv, "popover__arrow");
|
56
|
+
arrow.setAttribute("data-popper-arrow", "true");
|
57
|
+
const content = createElement(popoverDiv, "popover__content");
|
58
|
+
content.innerHTML = await getHTML(url);
|
59
|
+
this.popper = createPopper(root, popoverDiv, {
|
60
|
+
placement: placement ?? "auto",
|
61
|
+
modifiers: [
|
62
|
+
{
|
63
|
+
name: "offset",
|
64
|
+
options: {
|
65
|
+
offset: [0, 8],
|
66
|
+
},
|
67
|
+
},
|
68
|
+
],
|
69
|
+
});
|
70
|
+
this.popoverDiv = popoverDiv;
|
71
|
+
document.addEventListener("click", this.clickOutside);
|
72
|
+
}
|
73
|
+
|
74
|
+
async close(): Promise<void> {
|
75
|
+
this.popper?.destroy();
|
76
|
+
this.popper = undefined;
|
77
|
+
document.removeEventListener("click", this.clickOutside);
|
78
|
+
this.popoverDiv?.remove();
|
79
|
+
this.popoverDiv = undefined;
|
80
|
+
}
|
81
|
+
|
82
|
+
private clickOutside = (event: MouseEvent): void => {
|
83
|
+
if (this.popoverDiv?.contains(event.target as HTMLElement)) {
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
event.preventDefault();
|
87
|
+
this.close();
|
88
|
+
};
|
89
|
+
}
|
data/src/util.ts
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
import { get } from "@rails/request.js";
|
2
|
+
|
3
|
+
export async function getHTML(url: string): Promise<string> {
|
4
|
+
const response = await get(url, { headers: { "X-Shimmer": "true" } });
|
5
|
+
if (response.ok) {
|
6
|
+
return await response.response.text();
|
7
|
+
}
|
8
|
+
return "";
|
9
|
+
}
|
10
|
+
|
11
|
+
export const loaded: Promise<void> = new Promise((res) => {
|
12
|
+
document.addEventListener("DOMContentLoaded", () => {
|
13
|
+
res();
|
14
|
+
});
|
15
|
+
});
|
16
|
+
|
17
|
+
export async function nextFrame(): Promise<void> {
|
18
|
+
return new Promise((res) => {
|
19
|
+
setTimeout(res, 10);
|
20
|
+
});
|
21
|
+
}
|
22
|
+
|
23
|
+
export function createElement(
|
24
|
+
parent: HTMLElement,
|
25
|
+
className: string
|
26
|
+
): HTMLDivElement {
|
27
|
+
const element = document.createElement("div");
|
28
|
+
element.className = className;
|
29
|
+
parent.append(element);
|
30
|
+
return element;
|
31
|
+
}
|
data/yarn.lock
CHANGED
@@ -17,6 +17,11 @@
|
|
17
17
|
minimatch "^3.0.4"
|
18
18
|
strip-json-comments "^3.1.1"
|
19
19
|
|
20
|
+
"@hotwired/stimulus@^3.0.1":
|
21
|
+
version "3.0.1"
|
22
|
+
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.0.1.tgz#141f15645acaa3b133b7c247cad58ae252ffae85"
|
23
|
+
integrity sha512-oHsJhgY2cip+K2ED7vKUNd2P+BEswVhrCYcJ802DSsblJFv7mPFVk3cQKvm2vHgHeDVdnj7oOKrBbzp1u8D+KA==
|
24
|
+
|
20
25
|
"@humanwhocodes/config-array@^0.9.2":
|
21
26
|
version "0.9.2"
|
22
27
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914"
|
@@ -52,6 +57,11 @@
|
|
52
57
|
"@nodelib/fs.scandir" "2.1.5"
|
53
58
|
fastq "^1.6.0"
|
54
59
|
|
60
|
+
"@popperjs/core@^2.11.0":
|
61
|
+
version "2.11.0"
|
62
|
+
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
|
63
|
+
integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
|
64
|
+
|
55
65
|
"@rails/request.js@^0.0.6":
|
56
66
|
version "0.0.6"
|
57
67
|
resolved "https://registry.yarnpkg.com/@rails/request.js/-/request.js-0.0.6.tgz#5f0347a9f363e50ec45118c7134080490cda81d8"
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shimmer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Ravens
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description:
|
14
14
|
email:
|
15
15
|
- jens@nerdgeschoss.de
|
16
16
|
executables: []
|
@@ -48,12 +48,14 @@ files:
|
|
48
48
|
- lib/shimmer/version.rb
|
49
49
|
- package.json
|
50
50
|
- rollup.config.js
|
51
|
-
-
|
51
|
+
- src/controllers/remote-navigation.ts
|
52
52
|
- src/index.ts
|
53
53
|
- src/locale.ts
|
54
54
|
- src/modal.ts
|
55
|
+
- src/popover.ts
|
55
56
|
- src/serviceworker.ts
|
56
57
|
- src/touch.ts
|
58
|
+
- src/util.ts
|
57
59
|
- tsconfig.json
|
58
60
|
- typings.d.ts
|
59
61
|
- yarn.lock
|
@@ -63,7 +65,7 @@ licenses:
|
|
63
65
|
metadata:
|
64
66
|
homepage_uri: https://github.com/nerdgeschoss/shimmer
|
65
67
|
source_code_uri: https://github.com/nerdgeschoss/shimmer
|
66
|
-
post_install_message:
|
68
|
+
post_install_message:
|
67
69
|
rdoc_options: []
|
68
70
|
require_paths:
|
69
71
|
- lib
|
@@ -79,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
81
|
version: '0'
|
80
82
|
requirements: []
|
81
83
|
rubygems_version: 3.2.32
|
82
|
-
signing_key:
|
84
|
+
signing_key:
|
83
85
|
specification_version: 4
|
84
86
|
summary: Shimmer brings all the bells and whistles of a hotwired application, right
|
85
87
|
from the start.
|
data/shimmer.gemspec
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "lib/shimmer/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "shimmer"
|
7
|
-
spec.version = Shimmer::VERSION
|
8
|
-
spec.authors = ["Jens Ravens"]
|
9
|
-
spec.email = ["jens@nerdgeschoss.de"]
|
10
|
-
|
11
|
-
spec.summary = "Shimmer brings all the bells and whistles of a hotwired application, right from the start."
|
12
|
-
spec.homepage = "https://github.com/nerdgeschoss/shimmer"
|
13
|
-
spec.license = "MIT"
|
14
|
-
spec.required_ruby_version = ">= 2.6.0"
|
15
|
-
|
16
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
-
|
19
|
-
# Specify which files should be added to the gem when it is released.
|
20
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
-
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
24
|
-
end
|
25
|
-
end
|
26
|
-
spec.bindir = "exe"
|
27
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = ["lib"]
|
29
|
-
|
30
|
-
# Uncomment to register a new dependency of your gem
|
31
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
32
|
-
|
33
|
-
# For more information and examples about making a new gem, checkout our
|
34
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
35
|
-
end
|